• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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