1 /* 2 * Copyright (C) 2021 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 com.android.server.nearby.fastpair; 18 19 import static com.android.server.nearby.fastpair.Constant.TAG; 20 21 import android.annotation.Nullable; 22 import android.annotation.WorkerThread; 23 import android.app.KeyguardManager; 24 import android.bluetooth.BluetoothAdapter; 25 import android.bluetooth.BluetoothDevice; 26 import android.bluetooth.BluetoothManager; 27 import android.content.BroadcastReceiver; 28 import android.content.ContentResolver; 29 import android.content.Context; 30 import android.content.Intent; 31 import android.content.IntentFilter; 32 import android.database.ContentObserver; 33 import android.nearby.FastPairDevice; 34 import android.nearby.NearbyDevice; 35 import android.nearby.NearbyManager; 36 import android.nearby.ScanCallback; 37 import android.nearby.ScanRequest; 38 import android.net.Uri; 39 import android.provider.Settings; 40 import android.util.Log; 41 42 import androidx.annotation.NonNull; 43 44 import com.android.server.nearby.common.ble.decode.FastPairDecoder; 45 import com.android.server.nearby.common.bluetooth.BluetoothException; 46 import com.android.server.nearby.common.bluetooth.fastpair.FastPairConnection; 47 import com.android.server.nearby.common.bluetooth.fastpair.FastPairDualConnection; 48 import com.android.server.nearby.common.bluetooth.fastpair.PairingException; 49 import com.android.server.nearby.common.bluetooth.fastpair.Preferences; 50 import com.android.server.nearby.common.bluetooth.fastpair.ReflectionException; 51 import com.android.server.nearby.common.bluetooth.fastpair.SimpleBroadcastReceiver; 52 import com.android.server.nearby.common.eventloop.Annotations; 53 import com.android.server.nearby.common.eventloop.EventLoop; 54 import com.android.server.nearby.common.eventloop.NamedRunnable; 55 import com.android.server.nearby.common.locator.Locator; 56 import com.android.server.nearby.common.locator.LocatorContextWrapper; 57 import com.android.server.nearby.fastpair.cache.DiscoveryItem; 58 import com.android.server.nearby.fastpair.cache.FastPairCacheManager; 59 import com.android.server.nearby.fastpair.footprint.FootprintsDeviceManager; 60 import com.android.server.nearby.fastpair.halfsheet.FastPairHalfSheetManager; 61 import com.android.server.nearby.fastpair.pairinghandler.PairingProgressHandlerBase; 62 import com.android.server.nearby.util.ForegroundThread; 63 import com.android.server.nearby.util.Hex; 64 65 import com.google.common.collect.ImmutableList; 66 import com.google.protobuf.ByteString; 67 68 import java.security.GeneralSecurityException; 69 import java.util.concurrent.ExecutionException; 70 import java.util.concurrent.Executor; 71 import java.util.concurrent.ExecutorService; 72 import java.util.concurrent.Executors; 73 import java.util.concurrent.Future; 74 import java.util.concurrent.TimeUnit; 75 import java.util.concurrent.TimeoutException; 76 77 import service.proto.Cache; 78 import service.proto.Rpcs; 79 80 /** 81 * FastPairManager is the class initiated in nearby service to handle Fast Pair related 82 * work. 83 */ 84 85 public class FastPairManager { 86 87 private static final String ACTION_PREFIX = UserActionHandler.PREFIX; 88 private static final int WAIT_FOR_UNLOCK_MILLIS = 5000; 89 90 /** A notification ID which should be dismissed */ 91 public static final String EXTRA_NOTIFICATION_ID = ACTION_PREFIX + "EXTRA_NOTIFICATION_ID"; 92 public static final String ACTION_RESOURCES_APK = "android.nearby.SHOW_HALFSHEET"; 93 94 private static Executor sFastPairExecutor; 95 96 private ContentObserver mFastPairScanChangeContentObserver = null; 97 98 final LocatorContextWrapper mLocatorContextWrapper; 99 final IntentFilter mIntentFilter; 100 final Locator mLocator; 101 private boolean mScanEnabled; 102 103 private final BroadcastReceiver mScreenBroadcastReceiver = new BroadcastReceiver() { 104 @Override 105 public void onReceive(Context context, Intent intent) { 106 if (intent.getAction().equals(Intent.ACTION_SCREEN_ON) 107 || intent.getAction().equals(Intent.ACTION_BOOT_COMPLETED)) { 108 Log.d(TAG, "onReceive: ACTION_SCREEN_ON or boot complete."); 109 invalidateScan(); 110 } else if (intent.getAction().equals(BluetoothDevice.ACTION_BOND_STATE_CHANGED)) { 111 processBluetoothConnectionEvent(intent); 112 } 113 } 114 }; 115 FastPairManager(LocatorContextWrapper contextWrapper)116 public FastPairManager(LocatorContextWrapper contextWrapper) { 117 mLocatorContextWrapper = contextWrapper; 118 mIntentFilter = new IntentFilter(); 119 mLocator = mLocatorContextWrapper.getLocator(); 120 mLocator.bind(new FastPairModule()); 121 Rpcs.GetObservedDeviceResponse getObservedDeviceResponse = 122 Rpcs.GetObservedDeviceResponse.newBuilder().build(); 123 } 124 125 final ScanCallback mScanCallback = new ScanCallback() { 126 @Override 127 public void onDiscovered(@NonNull NearbyDevice device) { 128 Locator.get(mLocatorContextWrapper, FastPairAdvHandler.class).handleBroadcast(device); 129 } 130 131 @Override 132 public void onUpdated(@NonNull NearbyDevice device) { 133 FastPairDevice fastPairDevice = (FastPairDevice) device; 134 byte[] modelArray = FastPairDecoder.getModelId(fastPairDevice.getData()); 135 Log.d(TAG, "update model id" + Hex.bytesToStringLowercase(modelArray)); 136 } 137 138 @Override 139 public void onLost(@NonNull NearbyDevice device) { 140 FastPairDevice fastPairDevice = (FastPairDevice) device; 141 byte[] modelArray = FastPairDecoder.getModelId(fastPairDevice.getData()); 142 Log.d(TAG, "lost model id" + Hex.bytesToStringLowercase(modelArray)); 143 } 144 }; 145 146 /** 147 * Function called when nearby service start. 148 */ initiate()149 public void initiate() { 150 mIntentFilter.addAction(Intent.ACTION_SCREEN_ON); 151 mIntentFilter.addAction(Intent.ACTION_SCREEN_OFF); 152 mIntentFilter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED); 153 mIntentFilter.addAction(Intent.ACTION_BOOT_COMPLETED); 154 155 mLocatorContextWrapper.getContext() 156 .registerReceiver(mScreenBroadcastReceiver, mIntentFilter); 157 158 Locator.getFromContextWrapper(mLocatorContextWrapper, FastPairCacheManager.class); 159 // Default false for now. 160 mScanEnabled = NearbyManager.getFastPairScanEnabled(mLocatorContextWrapper.getContext()); 161 registerFastPairScanChangeContentObserver(mLocatorContextWrapper.getContentResolver()); 162 } 163 164 /** 165 * Function to free up fast pair resource. 166 */ cleanUp()167 public void cleanUp() { 168 mLocatorContextWrapper.getContext().unregisterReceiver(mScreenBroadcastReceiver); 169 if (mFastPairScanChangeContentObserver != null) { 170 mLocatorContextWrapper.getContentResolver().unregisterContentObserver( 171 mFastPairScanChangeContentObserver); 172 } 173 } 174 175 /** 176 * Starts fast pair process. 177 */ 178 @Annotations.EventThread pair( ExecutorService executor, Context context, DiscoveryItem item, @Nullable byte[] accountKey, @Nullable String companionApp, FootprintsDeviceManager footprints, PairingProgressHandlerBase pairingProgressHandlerBase)179 public static Future<Void> pair( 180 ExecutorService executor, 181 Context context, 182 DiscoveryItem item, 183 @Nullable byte[] accountKey, 184 @Nullable String companionApp, 185 FootprintsDeviceManager footprints, 186 PairingProgressHandlerBase pairingProgressHandlerBase) { 187 return executor.submit( 188 () -> pairInternal(context, item, companionApp, accountKey, footprints, 189 pairingProgressHandlerBase), /* result= */ null); 190 } 191 192 /** 193 * Starts fast pair 194 */ 195 @WorkerThread pairInternal( Context context, DiscoveryItem item, @Nullable String companionApp, @Nullable byte[] accountKey, FootprintsDeviceManager footprints, PairingProgressHandlerBase pairingProgressHandlerBase)196 public static void pairInternal( 197 Context context, 198 DiscoveryItem item, 199 @Nullable String companionApp, 200 @Nullable byte[] accountKey, 201 FootprintsDeviceManager footprints, 202 PairingProgressHandlerBase pairingProgressHandlerBase) { 203 FastPairHalfSheetManager fastPairHalfSheetManager = 204 Locator.get(context, FastPairHalfSheetManager.class); 205 try { 206 pairingProgressHandlerBase.onPairingStarted(); 207 if (pairingProgressHandlerBase.skipWaitingScreenUnlock()) { 208 // Do nothing due to we are not showing the status notification in some pairing 209 // types, e.g. the retroactive pairing. 210 } else { 211 // If the screen is locked when the user taps to pair, the screen will unlock. We 212 // must wait for the unlock to complete before showing the status notification, or 213 // it won't be heads-up. 214 pairingProgressHandlerBase.onWaitForScreenUnlock(); 215 waitUntilScreenIsUnlocked(context); 216 pairingProgressHandlerBase.onScreenUnlocked(); 217 } 218 BluetoothAdapter bluetoothAdapter = getBluetoothAdapter(context); 219 220 boolean isBluetoothEnabled = bluetoothAdapter != null && bluetoothAdapter.isEnabled(); 221 if (!isBluetoothEnabled) { 222 if (bluetoothAdapter == null || !bluetoothAdapter.enable()) { 223 Log.d(TAG, "FastPair: Failed to enable bluetooth"); 224 return; 225 } 226 Log.v(TAG, "FastPair: Enabling bluetooth for fast pair"); 227 228 Locator.get(context, EventLoop.class) 229 .postRunnable( 230 new NamedRunnable("enableBluetoothToast") { 231 @Override 232 public void run() { 233 Log.d(TAG, "Enable bluetooth toast test"); 234 } 235 }); 236 // Set up call back to call this function again once bluetooth has been 237 // enabled; this does not seem to be a problem as the device connects without a 238 // problem, but in theory the timeout also includes turning on bluetooth now. 239 } 240 241 pairingProgressHandlerBase.onReadyToPair(); 242 243 String modelId = item.getTriggerId(); 244 Preferences.Builder prefsBuilder = 245 Preferences.builderFromGmsLog() 246 .setEnableBrEdrHandover(false) 247 .setIgnoreDiscoveryError(true); 248 pairingProgressHandlerBase.onSetupPreferencesBuilder(prefsBuilder); 249 if (item.getFastPairInformation() != null) { 250 prefsBuilder.setSkipConnectingProfiles( 251 item.getFastPairInformation().getDataOnlyConnection()); 252 } 253 // When add watch and auto device needs to change the config 254 prefsBuilder.setRejectMessageAccess(true); 255 prefsBuilder.setRejectPhonebookAccess(true); 256 prefsBuilder.setHandlePasskeyConfirmationByUi(false); 257 258 FastPairConnection connection = new FastPairDualConnection( 259 context, item.getMacAddress(), 260 prefsBuilder.build(), 261 null); 262 pairingProgressHandlerBase.onPairingSetupCompleted(); 263 264 FastPairConnection.SharedSecret sharedSecret; 265 if ((accountKey != null || item.getAuthenticationPublicKeySecp256R1() != null)) { 266 sharedSecret = 267 connection.pair( 268 accountKey != null ? accountKey 269 : item.getAuthenticationPublicKeySecp256R1()); 270 if (accountKey == null) { 271 // Account key is null so it is initial pairing 272 if (sharedSecret != null) { 273 Locator.get(context, FastPairController.class).addDeviceToFootprint( 274 connection.getPublicAddress(), sharedSecret.getKey(), item); 275 cacheFastPairDevice(context, connection.getPublicAddress(), 276 sharedSecret.getKey(), item); 277 } 278 } 279 } else { 280 // Fast Pair one 281 connection.pair(); 282 } 283 // TODO(b/213373051): Merge logic with pairingProgressHandlerBase or delete the 284 // pairingProgressHandlerBase class. 285 fastPairHalfSheetManager.showPairingSuccessHalfSheet(connection.getPublicAddress()); 286 pairingProgressHandlerBase.onPairingSuccess(connection.getPublicAddress()); 287 } catch (BluetoothException 288 | InterruptedException 289 | ReflectionException 290 | TimeoutException 291 | ExecutionException 292 | PairingException 293 | GeneralSecurityException e) { 294 Log.e(TAG, "Failed to pair.", e); 295 296 // TODO(b/213373051): Merge logic with pairingProgressHandlerBase or delete the 297 // pairingProgressHandlerBase class. 298 fastPairHalfSheetManager.showPairingFailed(); 299 pairingProgressHandlerBase.onPairingFailed(e); 300 } 301 } 302 cacheFastPairDevice(Context context, String publicAddress, byte[] key, DiscoveryItem item)303 private static void cacheFastPairDevice(Context context, String publicAddress, byte[] key, 304 DiscoveryItem item) { 305 try { 306 Locator.get(context, EventLoop.class).postAndWait( 307 new NamedRunnable("FastPairCacheDevice") { 308 @Override 309 public void run() { 310 Cache.StoredFastPairItem storedFastPairItem = 311 Cache.StoredFastPairItem.newBuilder() 312 .setMacAddress(publicAddress) 313 .setAccountKey(ByteString.copyFrom(key)) 314 .setModelId(item.getTriggerId()) 315 .addAllFeatures(item.getFastPairInformation() == null 316 ? ImmutableList.of() : 317 item.getFastPairInformation().getFeaturesList()) 318 .setDiscoveryItem(item.getCopyOfStoredItem()) 319 .build(); 320 Locator.get(context, FastPairCacheManager.class) 321 .putStoredFastPairItem(storedFastPairItem); 322 } 323 } 324 ); 325 } catch (InterruptedException e) { 326 Log.e(TAG, "Fail to insert paired device into cache"); 327 } 328 } 329 330 /** Checks if the pairing is initial pairing with fast pair 2.0 design. */ isThroughFastPair2InitialPairing( DiscoveryItem item, @Nullable byte[] accountKey)331 public static boolean isThroughFastPair2InitialPairing( 332 DiscoveryItem item, @Nullable byte[] accountKey) { 333 return accountKey == null && item.getAuthenticationPublicKeySecp256R1() != null; 334 } 335 waitUntilScreenIsUnlocked(Context context)336 private static void waitUntilScreenIsUnlocked(Context context) 337 throws InterruptedException, ExecutionException, TimeoutException { 338 KeyguardManager keyguardManager = context.getSystemService(KeyguardManager.class); 339 340 // KeyguardManager's isScreenLocked() counterintuitively returns false when the lock screen 341 // is showing if the user has set "swipe to unlock" (i.e. no required password, PIN, or 342 // pattern) So we use this method instead, which returns true when on the lock screen 343 // regardless. 344 if (keyguardManager.isKeyguardLocked()) { 345 Log.v(TAG, "FastPair: Screen is locked, waiting until unlocked " 346 + "to show status notifications."); 347 try (SimpleBroadcastReceiver isUnlockedReceiver = 348 SimpleBroadcastReceiver.oneShotReceiver( 349 context, FlagUtils.getPreferencesBuilder().build(), 350 Intent.ACTION_USER_PRESENT)) { 351 isUnlockedReceiver.await(WAIT_FOR_UNLOCK_MILLIS, TimeUnit.MILLISECONDS); 352 } 353 } 354 } 355 registerFastPairScanChangeContentObserver(ContentResolver resolver)356 private void registerFastPairScanChangeContentObserver(ContentResolver resolver) { 357 mFastPairScanChangeContentObserver = new ContentObserver(ForegroundThread.getHandler()) { 358 @Override 359 public void onChange(boolean selfChange, Uri uri) { 360 super.onChange(selfChange, uri); 361 setScanEnabled( 362 NearbyManager.getFastPairScanEnabled(mLocatorContextWrapper.getContext())); 363 } 364 }; 365 try { 366 resolver.registerContentObserver( 367 Settings.Secure.getUriFor(NearbyManager.FAST_PAIR_SCAN_ENABLED), 368 /* notifyForDescendants= */ false, 369 mFastPairScanChangeContentObserver); 370 } catch (SecurityException e) { 371 Log.e(TAG, "Failed to register content observer for fast pair scan.", e); 372 } 373 } 374 375 /** 376 * Processed task in a background thread 377 */ 378 @Annotations.EventThread processBackgroundTask(Runnable runnable)379 public static void processBackgroundTask(Runnable runnable) { 380 getExecutor().execute(runnable); 381 } 382 383 /** 384 * This function should only be called on main thread since there is no lock 385 */ getExecutor()386 private static Executor getExecutor() { 387 if (sFastPairExecutor != null) { 388 return sFastPairExecutor; 389 } 390 sFastPairExecutor = Executors.newSingleThreadExecutor(); 391 return sFastPairExecutor; 392 } 393 394 /** 395 * Null when the Nearby Service is not available. 396 */ 397 @Nullable getNearbyManager()398 private NearbyManager getNearbyManager() { 399 return (NearbyManager) mLocatorContextWrapper 400 .getApplicationContext().getSystemService(Context.NEARBY_SERVICE); 401 } setScanEnabled(boolean scanEnabled)402 private void setScanEnabled(boolean scanEnabled) { 403 if (mScanEnabled == scanEnabled) { 404 return; 405 } 406 mScanEnabled = scanEnabled; 407 invalidateScan(); 408 } 409 410 /** 411 * Starts or stops scanning according to mAllowScan value. 412 */ invalidateScan()413 private void invalidateScan() { 414 NearbyManager nearbyManager = getNearbyManager(); 415 if (nearbyManager == null) { 416 Log.w(TAG, "invalidateScan: " 417 + "failed to start or stop scannning because NearbyManager is null."); 418 return; 419 } 420 if (mScanEnabled) { 421 Log.v(TAG, "invalidateScan: scan is enabled"); 422 nearbyManager.startScan(new ScanRequest.Builder() 423 .setScanType(ScanRequest.SCAN_TYPE_FAST_PAIR).build(), 424 ForegroundThread.getExecutor(), 425 mScanCallback); 426 } else { 427 Log.v(TAG, "invalidateScan: scan is disabled"); 428 nearbyManager.stopScan(mScanCallback); 429 } 430 } 431 432 /** 433 * When certain device is forgotten we need to remove the info from database because the info 434 * is no longer useful. 435 */ processBluetoothConnectionEvent(Intent intent)436 private void processBluetoothConnectionEvent(Intent intent) { 437 int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, 438 BluetoothDevice.ERROR); 439 if (bondState == BluetoothDevice.BOND_NONE) { 440 BluetoothDevice device = 441 intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 442 if (device != null) { 443 Log.d("FastPairService", "Forget device detect"); 444 processBackgroundTask(new Runnable() { 445 @Override 446 public void run() { 447 mLocatorContextWrapper.getLocator().get(FastPairCacheManager.class) 448 .removeStoredFastPairItem(device.getAddress()); 449 } 450 }); 451 } 452 453 } 454 } 455 456 /** 457 * Helper function to get bluetooth adapter. 458 */ 459 @Nullable getBluetoothAdapter(Context context)460 public static BluetoothAdapter getBluetoothAdapter(Context context) { 461 BluetoothManager manager = context.getSystemService(BluetoothManager.class); 462 return manager == null ? null : manager.getAdapter(); 463 } 464 } 465