1 /* 2 * Copyright (C) 2023 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.net.wifi.sharedconnectivity.app; 18 19 import android.annotation.CallbackExecutor; 20 import android.annotation.NonNull; 21 import android.annotation.Nullable; 22 import android.annotation.RequiresPermission; 23 import android.annotation.SuppressLint; 24 import android.annotation.SystemApi; 25 import android.annotation.TestApi; 26 import android.content.BroadcastReceiver; 27 import android.content.ComponentName; 28 import android.content.Context; 29 import android.content.Intent; 30 import android.content.IntentFilter; 31 import android.content.ServiceConnection; 32 import android.content.res.Resources; 33 import android.net.wifi.sharedconnectivity.service.ISharedConnectivityCallback; 34 import android.net.wifi.sharedconnectivity.service.ISharedConnectivityService; 35 import android.net.wifi.sharedconnectivity.service.SharedConnectivityService; 36 import android.os.Binder; 37 import android.os.IBinder; 38 import android.os.IInterface; 39 import android.os.RemoteException; 40 import android.os.UserManager; 41 import android.text.TextUtils; 42 import android.util.Log; 43 44 import com.android.internal.R; 45 import com.android.internal.annotations.GuardedBy; 46 47 import java.util.HashMap; 48 import java.util.List; 49 import java.util.Map; 50 import java.util.Objects; 51 import java.util.concurrent.Executor; 52 53 /** 54 * This class is the library used by consumers of Shared Connectivity data to bind to the service, 55 * receive callbacks from, and send user actions to the service. 56 * 57 * A client must register at least one callback so that the manager will bind to the service. Once 58 * all callbacks are unregistered, the manager will unbind from the service. When the client no 59 * longer needs Shared Connectivity data, the client must unregister. 60 * 61 * The methods {@link #connectHotspotNetwork}, {@link #disconnectHotspotNetwork}, 62 * {@link #connectKnownNetwork} and {@link #forgetKnownNetwork} are not valid and will return false 63 * and getter methods will fail and return null if not called between 64 * {@link SharedConnectivityClientCallback#onServiceConnected()} 65 * and {@link SharedConnectivityClientCallback#onServiceDisconnected()} or if 66 * {@link SharedConnectivityClientCallback#onRegisterCallbackFailed} was called. 67 * 68 * @hide 69 */ 70 @SystemApi 71 public class SharedConnectivityManager { 72 private static final String TAG = SharedConnectivityManager.class.getSimpleName(); 73 private static final boolean DEBUG = false; 74 75 private static final class SharedConnectivityCallbackProxy extends 76 ISharedConnectivityCallback.Stub { 77 private final Executor mExecutor; 78 private final SharedConnectivityClientCallback mCallback; 79 SharedConnectivityCallbackProxy( @onNull @allbackExecutor Executor executor, @NonNull SharedConnectivityClientCallback callback)80 SharedConnectivityCallbackProxy( 81 @NonNull @CallbackExecutor Executor executor, 82 @NonNull SharedConnectivityClientCallback callback) { 83 mExecutor = executor; 84 mCallback = callback; 85 } 86 87 @Override onServiceConnected()88 public void onServiceConnected() { 89 if (mCallback != null) { 90 final long token = Binder.clearCallingIdentity(); 91 try { 92 mExecutor.execute(() -> mCallback.onServiceConnected()); 93 } finally { 94 Binder.restoreCallingIdentity(token); 95 } 96 } 97 } 98 99 @Override onHotspotNetworksUpdated(@onNull List<HotspotNetwork> networks)100 public void onHotspotNetworksUpdated(@NonNull List<HotspotNetwork> networks) { 101 if (mCallback != null) { 102 final long token = Binder.clearCallingIdentity(); 103 try { 104 mExecutor.execute(() -> mCallback.onHotspotNetworksUpdated(networks)); 105 } finally { 106 Binder.restoreCallingIdentity(token); 107 } 108 } 109 } 110 111 @Override onKnownNetworksUpdated(@onNull List<KnownNetwork> networks)112 public void onKnownNetworksUpdated(@NonNull List<KnownNetwork> networks) { 113 if (mCallback != null) { 114 final long token = Binder.clearCallingIdentity(); 115 try { 116 mExecutor.execute(() -> mCallback.onKnownNetworksUpdated(networks)); 117 } finally { 118 Binder.restoreCallingIdentity(token); 119 } 120 } 121 } 122 123 @Override onSharedConnectivitySettingsChanged( @onNull SharedConnectivitySettingsState state)124 public void onSharedConnectivitySettingsChanged( 125 @NonNull SharedConnectivitySettingsState state) { 126 if (mCallback != null) { 127 final long token = Binder.clearCallingIdentity(); 128 try { 129 mExecutor.execute(() -> mCallback.onSharedConnectivitySettingsChanged(state)); 130 } finally { 131 Binder.restoreCallingIdentity(token); 132 } 133 } 134 } 135 136 @Override onHotspotNetworkConnectionStatusChanged( @onNull HotspotNetworkConnectionStatus status)137 public void onHotspotNetworkConnectionStatusChanged( 138 @NonNull HotspotNetworkConnectionStatus status) { 139 if (mCallback != null) { 140 final long token = Binder.clearCallingIdentity(); 141 try { 142 mExecutor.execute(() -> 143 mCallback.onHotspotNetworkConnectionStatusChanged(status)); 144 } finally { 145 Binder.restoreCallingIdentity(token); 146 } 147 } 148 } 149 150 @Override onKnownNetworkConnectionStatusChanged( @onNull KnownNetworkConnectionStatus status)151 public void onKnownNetworkConnectionStatusChanged( 152 @NonNull KnownNetworkConnectionStatus status) { 153 if (mCallback != null) { 154 final long token = Binder.clearCallingIdentity(); 155 try { 156 mExecutor.execute(() -> 157 mCallback.onKnownNetworkConnectionStatusChanged(status)); 158 } finally { 159 Binder.restoreCallingIdentity(token); 160 } 161 } 162 } 163 } 164 165 private ISharedConnectivityService mService; 166 @GuardedBy("mProxyDataLock") 167 private final Map<SharedConnectivityClientCallback, SharedConnectivityCallbackProxy> 168 mProxyMap = new HashMap<>(); 169 @GuardedBy("mProxyDataLock") 170 private final Map<SharedConnectivityClientCallback, SharedConnectivityCallbackProxy> 171 mCallbackProxyCache = new HashMap<>(); 172 // Makes sure mProxyMap and mCallbackProxyCache are locked together when one of them is used. 173 private final Object mProxyDataLock = new Object(); 174 private final Context mContext; 175 private final String mServicePackageName; 176 private final String mIntentAction; 177 private ServiceConnection mServiceConnection; 178 private UserManager mUserManager; 179 180 /** 181 * Creates a new instance of {@link SharedConnectivityManager}. 182 * 183 * @return An instance of {@link SharedConnectivityManager} or null if the shared connectivity 184 * service is not found. 185 * @hide 186 */ 187 @Nullable create(@onNull Context context)188 public static SharedConnectivityManager create(@NonNull Context context) { 189 Resources resources = context.getResources(); 190 try { 191 String servicePackageName = resources.getString( 192 R.string.config_sharedConnectivityServicePackage); 193 String serviceIntentAction = resources.getString( 194 R.string.config_sharedConnectivityServiceIntentAction); 195 if (TextUtils.isEmpty(servicePackageName) || TextUtils.isEmpty(serviceIntentAction)) { 196 Log.e(TAG, "To support shared connectivity service on this device, the" 197 + " service's package name and intent action strings must not be empty"); 198 return null; 199 } 200 return new SharedConnectivityManager(context, servicePackageName, serviceIntentAction); 201 } catch (Resources.NotFoundException e) { 202 Log.e(TAG, "To support shared connectivity service on this device, the service's" 203 + " package name and intent action strings must be defined"); 204 } 205 return null; 206 } 207 208 /** 209 * @hide 210 */ 211 @SuppressLint("ManagerLookup") 212 @TestApi 213 @Nullable create(@onNull Context context, @NonNull String servicePackageName, @NonNull String serviceIntentAction)214 public static SharedConnectivityManager create(@NonNull Context context, 215 @NonNull String servicePackageName, @NonNull String serviceIntentAction) { 216 return new SharedConnectivityManager(context, servicePackageName, serviceIntentAction); 217 } 218 SharedConnectivityManager(@onNull Context context, String servicePackageName, String serviceIntentAction)219 private SharedConnectivityManager(@NonNull Context context, String servicePackageName, 220 String serviceIntentAction) { 221 mContext = context; 222 mServicePackageName = servicePackageName; 223 mIntentAction = serviceIntentAction; 224 mUserManager = context.getSystemService(UserManager.class); 225 } 226 bind()227 private void bind() { 228 mServiceConnection = new ServiceConnection() { 229 @Override 230 public void onServiceConnected(ComponentName name, IBinder service) { 231 if (DEBUG) Log.i(TAG, "onServiceConnected"); 232 mService = ISharedConnectivityService.Stub.asInterface(service); 233 synchronized (mProxyDataLock) { 234 if (!mCallbackProxyCache.isEmpty()) { 235 mCallbackProxyCache.keySet().forEach(callback -> 236 registerCallbackInternal( 237 callback, mCallbackProxyCache.get(callback))); 238 mCallbackProxyCache.clear(); 239 } 240 } 241 } 242 243 @Override 244 public void onServiceDisconnected(ComponentName name) { 245 if (DEBUG) Log.i(TAG, "onServiceDisconnected"); 246 mService = null; 247 synchronized (mProxyDataLock) { 248 if (!mCallbackProxyCache.isEmpty()) { 249 mCallbackProxyCache.keySet().forEach( 250 SharedConnectivityClientCallback::onServiceDisconnected); 251 mCallbackProxyCache.clear(); 252 } 253 if (!mProxyMap.isEmpty()) { 254 mProxyMap.keySet().forEach( 255 SharedConnectivityClientCallback::onServiceDisconnected); 256 mProxyMap.clear(); 257 } 258 } 259 } 260 }; 261 262 boolean result = mContext.bindService( 263 new Intent().setPackage(mServicePackageName).setAction(mIntentAction), 264 mServiceConnection, Context.BIND_AUTO_CREATE); 265 if (!result) { 266 if (DEBUG) Log.i(TAG, "bindService failed"); 267 mServiceConnection = null; 268 if (mUserManager != null && !mUserManager.isUserUnlocked()) { // In direct boot mode 269 IntentFilter intentFilter = new IntentFilter(); 270 intentFilter.addAction(Intent.ACTION_USER_UNLOCKED); 271 mContext.registerReceiver(mBroadcastReceiver, intentFilter); 272 } else { 273 synchronized (mProxyDataLock) { 274 if (!mCallbackProxyCache.isEmpty()) { 275 mCallbackProxyCache.keySet().forEach( 276 callback -> callback.onRegisterCallbackFailed( 277 new IllegalStateException( 278 "Failed to bind after user unlock"))); 279 mCallbackProxyCache.clear(); 280 } 281 } 282 } 283 } 284 } 285 286 private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { 287 @Override 288 public void onReceive(Context context, Intent intent) { 289 context.unregisterReceiver(mBroadcastReceiver); 290 bind(); 291 } 292 }; 293 294 /** 295 * @hide 296 */ 297 @TestApi 298 @NonNull getBroadcastReceiver()299 public BroadcastReceiver getBroadcastReceiver() { 300 return mBroadcastReceiver; 301 } 302 registerCallbackInternal(SharedConnectivityClientCallback callback, SharedConnectivityCallbackProxy proxy)303 private void registerCallbackInternal(SharedConnectivityClientCallback callback, 304 SharedConnectivityCallbackProxy proxy) { 305 try { 306 mService.registerCallback(proxy); 307 synchronized (mProxyDataLock) { 308 mProxyMap.put(callback, proxy); 309 } 310 } catch (RemoteException e) { 311 Log.e(TAG, "Exception in registerCallback", e); 312 callback.onRegisterCallbackFailed(e); 313 } 314 } 315 316 /** 317 * @hide 318 */ 319 @TestApi setService(@ullable IInterface service)320 public void setService(@Nullable IInterface service) { 321 mService = (ISharedConnectivityService) service; 322 } 323 324 /** 325 * @hide 326 */ 327 @TestApi 328 @Nullable getServiceConnection()329 public ServiceConnection getServiceConnection() { 330 return mServiceConnection; 331 } 332 unbind()333 private void unbind() { 334 if (mServiceConnection != null) { 335 mContext.unbindService(mServiceConnection); 336 mServiceConnection = null; 337 mService = null; 338 } 339 } 340 341 /** 342 * Registers a callback for receiving updates to the list of Hotspot Networks, Known Networks, 343 * shared connectivity settings state, hotspot network connection status and known network 344 * connection status. 345 * Automatically binds to implementation of {@link SharedConnectivityService} specified in 346 * the device overlay when the first callback is registered. 347 * The {@link SharedConnectivityClientCallback#onRegisterCallbackFailed} will be called if the 348 * registration failed. 349 * 350 * @param executor The Executor used to invoke the callback. 351 * @param callback The callback of type {@link SharedConnectivityClientCallback} that is invoked 352 * when the service updates its data. 353 */ 354 @RequiresPermission(anyOf = {android.Manifest.permission.NETWORK_SETTINGS, 355 android.Manifest.permission.NETWORK_SETUP_WIZARD}) registerCallback(@onNull @allbackExecutor Executor executor, @NonNull SharedConnectivityClientCallback callback)356 public void registerCallback(@NonNull @CallbackExecutor Executor executor, 357 @NonNull SharedConnectivityClientCallback callback) { 358 Objects.requireNonNull(executor, "executor cannot be null"); 359 Objects.requireNonNull(callback, "callback cannot be null"); 360 361 if (mProxyMap.containsKey(callback) || mCallbackProxyCache.containsKey(callback)) { 362 Log.e(TAG, "Callback already registered"); 363 callback.onRegisterCallbackFailed(new IllegalStateException( 364 "Callback already registered")); 365 return; 366 } 367 368 SharedConnectivityCallbackProxy proxy = 369 new SharedConnectivityCallbackProxy(executor, callback); 370 if (mService == null) { 371 boolean shouldBind; 372 synchronized (mProxyDataLock) { 373 // Size can be 1 in different cases of register/unregister sequences. If size is 0 374 // Bind never happened or unbind was called. 375 shouldBind = mCallbackProxyCache.size() == 0; 376 mCallbackProxyCache.put(callback, proxy); 377 } 378 if (shouldBind) { 379 bind(); 380 } 381 return; 382 } 383 registerCallbackInternal(callback, proxy); 384 } 385 386 /** 387 * Unregisters a callback. 388 * Unbinds from {@link SharedConnectivityService} when no more callbacks are registered. 389 * 390 * @return Returns true if the callback was successfully unregistered, false otherwise. 391 */ 392 @RequiresPermission(anyOf = {android.Manifest.permission.NETWORK_SETTINGS, 393 android.Manifest.permission.NETWORK_SETUP_WIZARD}) unregisterCallback( @onNull SharedConnectivityClientCallback callback)394 public boolean unregisterCallback( 395 @NonNull SharedConnectivityClientCallback callback) { 396 Objects.requireNonNull(callback, "callback cannot be null"); 397 398 if (!mProxyMap.containsKey(callback) && !mCallbackProxyCache.containsKey(callback)) { 399 Log.e(TAG, "Callback not found, cannot unregister"); 400 return false; 401 } 402 403 // Try to unregister the broadcast receiver to guard against memory leaks. 404 try { 405 mContext.unregisterReceiver(mBroadcastReceiver); 406 } catch (IllegalArgumentException e) { 407 // This is fine, it means the receiver was never registered or was already unregistered. 408 } 409 410 if (mService == null) { 411 boolean shouldUnbind; 412 synchronized (mProxyDataLock) { 413 mCallbackProxyCache.remove(callback); 414 // Connection was never established, so all registered callbacks are in the cache. 415 shouldUnbind = mCallbackProxyCache.isEmpty(); 416 } 417 if (shouldUnbind) { 418 unbind(); 419 } 420 return true; 421 } 422 423 try { 424 boolean shouldUnbind; 425 synchronized (mProxyDataLock) { 426 mService.unregisterCallback(mProxyMap.get(callback)); 427 mProxyMap.remove(callback); 428 shouldUnbind = mProxyMap.isEmpty(); 429 } 430 if (shouldUnbind) { 431 unbind(); 432 } 433 } catch (RemoteException e) { 434 Log.e(TAG, "Exception in unregisterCallback", e); 435 return false; 436 } 437 return true; 438 } 439 440 /** 441 * Send command to the implementation of {@link SharedConnectivityService} requesting connection 442 * to the specified Hotspot Network. 443 * 444 * @param network {@link HotspotNetwork} object representing the network the user has requested 445 * a connection to. 446 * @return Returns true if the service received the command. Does not guarantee that the 447 * connection was successful. 448 */ 449 @RequiresPermission(anyOf = {android.Manifest.permission.NETWORK_SETTINGS, 450 android.Manifest.permission.NETWORK_SETUP_WIZARD}) connectHotspotNetwork(@onNull HotspotNetwork network)451 public boolean connectHotspotNetwork(@NonNull HotspotNetwork network) { 452 Objects.requireNonNull(network, "Hotspot network cannot be null"); 453 454 if (mService == null) { 455 return false; 456 } 457 458 try { 459 mService.connectHotspotNetwork(network); 460 } catch (RemoteException e) { 461 Log.e(TAG, "Exception in connectHotspotNetwork", e); 462 return false; 463 } 464 return true; 465 } 466 467 /** 468 * Send command to the implementation of {@link SharedConnectivityService} requesting 469 * disconnection from the active Hotspot Network. 470 * 471 * @param network {@link HotspotNetwork} object representing the network the user has requested 472 * to disconnect from. 473 * @return Returns true if the service received the command. Does not guarantee that the 474 * disconnection was successful. 475 */ 476 @RequiresPermission(anyOf = {android.Manifest.permission.NETWORK_SETTINGS, 477 android.Manifest.permission.NETWORK_SETUP_WIZARD}) disconnectHotspotNetwork(@onNull HotspotNetwork network)478 public boolean disconnectHotspotNetwork(@NonNull HotspotNetwork network) { 479 if (mService == null) { 480 return false; 481 } 482 483 try { 484 mService.disconnectHotspotNetwork(network); 485 } catch (RemoteException e) { 486 Log.e(TAG, "Exception in disconnectHotspotNetwork", e); 487 return false; 488 } 489 return true; 490 } 491 492 /** 493 * Send command to the implementation of {@link SharedConnectivityService} requesting connection 494 * to the specified Known Network. 495 * 496 * @param network {@link KnownNetwork} object representing the network the user has requested 497 * a connection to. 498 * @return Returns true if the service received the command. Does not guarantee that the 499 * connection was successful. 500 */ 501 @RequiresPermission(anyOf = {android.Manifest.permission.NETWORK_SETTINGS, 502 android.Manifest.permission.NETWORK_SETUP_WIZARD}) connectKnownNetwork(@onNull KnownNetwork network)503 public boolean connectKnownNetwork(@NonNull KnownNetwork network) { 504 Objects.requireNonNull(network, "Known network cannot be null"); 505 506 if (mService == null) { 507 return false; 508 } 509 510 try { 511 mService.connectKnownNetwork(network); 512 } catch (RemoteException e) { 513 Log.e(TAG, "Exception in connectKnownNetwork", e); 514 return false; 515 } 516 return true; 517 } 518 519 /** 520 * Send command to the implementation of {@link SharedConnectivityService} requesting removal of 521 * the specified Known Network from the list of Known Networks. 522 * 523 * @return Returns true if the service received the command. Does not guarantee that the 524 * forget action was successful. 525 */ 526 @RequiresPermission(anyOf = {android.Manifest.permission.NETWORK_SETTINGS, 527 android.Manifest.permission.NETWORK_SETUP_WIZARD}) forgetKnownNetwork(@onNull KnownNetwork network)528 public boolean forgetKnownNetwork(@NonNull KnownNetwork network) { 529 Objects.requireNonNull(network, "Known network cannot be null"); 530 531 if (mService == null) { 532 return false; 533 } 534 535 try { 536 mService.forgetKnownNetwork(network); 537 } catch (RemoteException e) { 538 Log.e(TAG, "Exception in forgetKnownNetwork", e); 539 return false; 540 } 541 return true; 542 } 543 544 /** 545 * Gets the list of hotspot networks the user can select to connect to. 546 * 547 * @return Returns a {@link List} of {@link HotspotNetwork} objects, null on failure. 548 */ 549 @RequiresPermission(anyOf = {android.Manifest.permission.NETWORK_SETTINGS, 550 android.Manifest.permission.NETWORK_SETUP_WIZARD}) 551 @SuppressWarnings("NullableCollection") 552 @Nullable getHotspotNetworks()553 public List<HotspotNetwork> getHotspotNetworks() { 554 if (mService == null) { 555 return null; 556 } 557 558 try { 559 return mService.getHotspotNetworks(); 560 } catch (RemoteException e) { 561 Log.e(TAG, "Exception in getHotspotNetworks", e); 562 } 563 return null; 564 } 565 566 /** 567 * Gets the list of known networks the user can select to connect to. 568 * 569 * @return Returns a {@link List} of {@link KnownNetwork} objects, null on failure. 570 */ 571 @RequiresPermission(anyOf = {android.Manifest.permission.NETWORK_SETTINGS, 572 android.Manifest.permission.NETWORK_SETUP_WIZARD}) 573 @SuppressWarnings("NullableCollection") 574 @Nullable getKnownNetworks()575 public List<KnownNetwork> getKnownNetworks() { 576 if (mService == null) { 577 return null; 578 } 579 580 try { 581 return mService.getKnownNetworks(); 582 } catch (RemoteException e) { 583 Log.e(TAG, "Exception in getKnownNetworks", e); 584 } 585 return null; 586 } 587 588 /** 589 * Gets the shared connectivity settings state. 590 * 591 * @return Returns a {@link SharedConnectivitySettingsState} object with the state, null on 592 * failure. 593 */ 594 @RequiresPermission(anyOf = {android.Manifest.permission.NETWORK_SETTINGS, 595 android.Manifest.permission.NETWORK_SETUP_WIZARD}) 596 @Nullable getSettingsState()597 public SharedConnectivitySettingsState getSettingsState() { 598 if (mService == null) { 599 return null; 600 } 601 602 try { 603 return mService.getSettingsState(); 604 } catch (RemoteException e) { 605 Log.e(TAG, "Exception in getSettingsState", e); 606 } 607 return null; 608 } 609 610 /** 611 * Gets the connection status of the hotspot network the user selected to connect to. 612 * 613 * @return Returns a {@link HotspotNetworkConnectionStatus} object with the connection status, 614 * null on failure. If no connection is active the status will be 615 * {@link HotspotNetworkConnectionStatus#CONNECTION_STATUS_UNKNOWN}. 616 */ 617 @RequiresPermission(anyOf = {android.Manifest.permission.NETWORK_SETTINGS, 618 android.Manifest.permission.NETWORK_SETUP_WIZARD}) 619 @Nullable getHotspotNetworkConnectionStatus()620 public HotspotNetworkConnectionStatus getHotspotNetworkConnectionStatus() { 621 if (mService == null) { 622 return null; 623 } 624 625 try { 626 return mService.getHotspotNetworkConnectionStatus(); 627 } catch (RemoteException e) { 628 Log.e(TAG, "Exception in getHotspotNetworkConnectionStatus", e); 629 } 630 return null; 631 } 632 633 /** 634 * Gets the connection status of the known network the user selected to connect to. 635 * 636 * @return Returns a {@link KnownNetworkConnectionStatus} object with the connection status, 637 * null on failure. If no connection is active the status will be 638 * {@link KnownNetworkConnectionStatus#CONNECTION_STATUS_UNKNOWN}. 639 */ 640 @RequiresPermission(anyOf = {android.Manifest.permission.NETWORK_SETTINGS, 641 android.Manifest.permission.NETWORK_SETUP_WIZARD}) 642 @Nullable getKnownNetworkConnectionStatus()643 public KnownNetworkConnectionStatus getKnownNetworkConnectionStatus() { 644 if (mService == null) { 645 return null; 646 } 647 648 try { 649 return mService.getKnownNetworkConnectionStatus(); 650 } catch (RemoteException e) { 651 Log.e(TAG, "Exception in getKnownNetworkConnectionStatus", e); 652 } 653 return null; 654 } 655 } 656