• 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.google.common.primitives.Bytes.concat;
20 
21 import android.accounts.Account;
22 import android.annotation.Nullable;
23 import android.content.Context;
24 import android.nearby.FastPairDevice;
25 import android.text.TextUtils;
26 import android.util.Log;
27 
28 import androidx.annotation.WorkerThread;
29 
30 import com.android.server.nearby.common.bluetooth.fastpair.BluetoothAddress;
31 import com.android.server.nearby.common.eventloop.Annotations;
32 import com.android.server.nearby.common.eventloop.EventLoop;
33 import com.android.server.nearby.common.eventloop.NamedRunnable;
34 import com.android.server.nearby.common.locator.Locator;
35 import com.android.server.nearby.fastpair.cache.DiscoveryItem;
36 import com.android.server.nearby.fastpair.cache.FastPairCacheManager;
37 import com.android.server.nearby.fastpair.footprint.FastPairUploadInfo;
38 import com.android.server.nearby.fastpair.footprint.FootprintsDeviceManager;
39 import com.android.server.nearby.fastpair.halfsheet.FastPairHalfSheetManager;
40 import com.android.server.nearby.fastpair.notification.FastPairNotificationManager;
41 import com.android.server.nearby.fastpair.pairinghandler.PairingProgressHandlerBase;
42 import com.android.server.nearby.provider.FastPairDataProvider;
43 
44 import com.google.common.hash.Hashing;
45 import com.google.protobuf.ByteString;
46 import com.google.protobuf.InvalidProtocolBufferException;
47 
48 import java.util.ArrayList;
49 import java.util.List;
50 import java.util.concurrent.Executors;
51 import java.util.concurrent.Future;
52 
53 import service.proto.Cache;
54 
55 /**
56  * FastPair controller after get the info from intent handler Fast Pair controller is responsible
57  * for pairing control.
58  */
59 public class FastPairController {
60     private static final String TAG = "FastPairController";
61     private final Context mContext;
62     private final EventLoop mEventLoop;
63     private final FastPairCacheManager mFastPairCacheManager;
64     private final FootprintsDeviceManager mFootprintsDeviceManager;
65     private boolean mIsFastPairing = false;
66     // boolean flag whether upload to footprint or not.
67     private boolean mShouldUpload = false;
68     @Nullable
69     private Callback mCallback;
70 
FastPairController(Context context)71     public FastPairController(Context context) {
72         mContext = context;
73         mEventLoop = Locator.get(mContext, EventLoop.class);
74         mFastPairCacheManager = Locator.get(mContext, FastPairCacheManager.class);
75         mFootprintsDeviceManager = Locator.get(mContext, FootprintsDeviceManager.class);
76     }
77 
78     /**
79      * Should be called on create lifecycle.
80      */
81     @WorkerThread
onCreate()82     public void onCreate() {
83         mEventLoop.postRunnable(new NamedRunnable("FastPairController::InitializeScanner") {
84             @Override
85             public void run() {
86                 // init scanner here and start scan.
87             }
88         });
89     }
90 
91     /**
92      * Should be called on destroy lifecycle.
93      */
94     @WorkerThread
onDestroy()95     public void onDestroy() {
96         mEventLoop.postRunnable(new NamedRunnable("FastPairController::DestroyScanner") {
97             @Override
98             public void run() {
99                 // Unregister scanner from here
100             }
101         });
102     }
103 
104     /**
105      * Pairing function.
106      */
pair(FastPairDevice fastPairDevice)107     public void pair(FastPairDevice fastPairDevice) {
108         byte[] discoveryItem = fastPairDevice.getData();
109         String modelId = fastPairDevice.getModelId();
110 
111         Log.v(TAG, "pair: fastPairDevice " + fastPairDevice);
112         mEventLoop.postRunnable(
113                 new NamedRunnable("fastPairWith=" + modelId) {
114                     @Override
115                     public void run() {
116                         try {
117                             DiscoveryItem item = new DiscoveryItem(mContext,
118                                     Cache.StoredDiscoveryItem.parseFrom(discoveryItem));
119                             if (TextUtils.isEmpty(item.getMacAddress())) {
120                                 Log.w(TAG, "There is no mac address in the DiscoveryItem,"
121                                         + " ignore pairing");
122                                 return;
123                             }
124                             // Check enabled state to prevent multiple pair attempts if we get the
125                             // intent more than once (this can happen due to an Android platform
126                             // bug - b/31459521).
127                             if (item.getState()
128                                     != Cache.StoredDiscoveryItem.State.STATE_ENABLED) {
129                                 Log.d(TAG, "Incorrect state, ignore pairing");
130                                 return;
131                             }
132                             FastPairNotificationManager fastPairNotificationManager =
133                                     Locator.get(mContext, FastPairNotificationManager.class);
134                             FastPairHalfSheetManager fastPairHalfSheetManager =
135                                     Locator.get(mContext, FastPairHalfSheetManager.class);
136                             mFastPairCacheManager.saveDiscoveryItem(item);
137 
138                             PairingProgressHandlerBase pairingProgressHandlerBase =
139                                     PairingProgressHandlerBase.create(
140                                             mContext,
141                                             item,
142                                             /* companionApp= */ null,
143                                             /* accountKey= */ null,
144                                             mFootprintsDeviceManager,
145                                             fastPairNotificationManager,
146                                             fastPairHalfSheetManager,
147                                             /* isRetroactivePair= */ false);
148 
149                             pair(item,
150                                     /* accountKey= */ null,
151                                     /* companionApp= */ null,
152                                     pairingProgressHandlerBase);
153                         } catch (InvalidProtocolBufferException e) {
154                             Log.w(TAG,
155                                     "Error parsing serialized discovery item with size "
156                                             + discoveryItem.length);
157                         }
158                     }
159                 });
160     }
161 
162     /**
163      * Subsequent pairing entry.
164      */
pair(DiscoveryItem item, @Nullable byte[] accountKey, @Nullable String companionApp)165     public void pair(DiscoveryItem item,
166             @Nullable byte[] accountKey,
167             @Nullable String companionApp) {
168         FastPairNotificationManager fastPairNotificationManager =
169                 Locator.get(mContext, FastPairNotificationManager.class);
170         FastPairHalfSheetManager fastPairHalfSheetManager =
171                 Locator.get(mContext, FastPairHalfSheetManager.class);
172         PairingProgressHandlerBase pairingProgressHandlerBase =
173                 PairingProgressHandlerBase.create(
174                         mContext,
175                         item,
176                         /* companionApp= */ null,
177                         /* accountKey= */ accountKey,
178                         mFootprintsDeviceManager,
179                         fastPairNotificationManager,
180                         fastPairHalfSheetManager,
181                         /* isRetroactivePair= */ false);
182         pair(item, accountKey, companionApp, pairingProgressHandlerBase);
183     }
184     /**
185      * Pairing function
186      */
187     @Annotations.EventThread
pair( DiscoveryItem item, @Nullable byte[] accountKey, @Nullable String companionApp, PairingProgressHandlerBase pairingProgressHandlerBase)188     public void pair(
189             DiscoveryItem item,
190             @Nullable byte[] accountKey,
191             @Nullable String companionApp,
192             PairingProgressHandlerBase pairingProgressHandlerBase) {
193         if (mIsFastPairing) {
194             Log.d(TAG, "FastPair: fastpairing, skip pair request");
195             return;
196         }
197         mIsFastPairing = true;
198         Log.d(TAG, "FastPair: start pair");
199 
200         // Hide all "tap to pair" notifications until after the flow completes.
201         mEventLoop.removeRunnable(mReEnableAllDeviceItemsRunnable);
202         if (mCallback != null) {
203             mCallback.fastPairUpdateDeviceItemsEnabled(false);
204         }
205 
206         Future<Void> task =
207                 FastPairManager.pair(
208                         Executors.newSingleThreadExecutor(),
209                         mContext,
210                         item,
211                         accountKey,
212                         companionApp,
213                         mFootprintsDeviceManager,
214                         pairingProgressHandlerBase);
215         mIsFastPairing = false;
216     }
217 
218     /** Fixes a companion app package name with extra spaces. */
trimCompanionApp(String companionApp)219     private static String trimCompanionApp(String companionApp) {
220         return companionApp == null ? null : companionApp.trim();
221     }
222 
223     /**
224      * Function to handle when scanner find bloomfilter.
225      */
226     @Annotations.EventThread
onBloomFilterDetect(FastPairAdvHandler handler, boolean advertiseInRange)227     public FastPairAdvHandler.ProcessBloomFilterType onBloomFilterDetect(FastPairAdvHandler handler,
228             boolean advertiseInRange) {
229         if (mIsFastPairing) {
230             return FastPairAdvHandler.ProcessBloomFilterType.IGNORE;
231         }
232         // Check if the device is in the cache or footprint.
233         return FastPairAdvHandler.ProcessBloomFilterType.CACHE;
234     }
235 
236     /**
237      * Add newly paired device info to footprint
238      */
239     @WorkerThread
addDeviceToFootprint(String publicAddress, byte[] accountKey, DiscoveryItem discoveryItem)240     public void addDeviceToFootprint(String publicAddress, byte[] accountKey,
241             DiscoveryItem discoveryItem) {
242         if (!mShouldUpload) {
243             return;
244         }
245         Log.d(TAG, "upload device to footprint");
246         FastPairManager.processBackgroundTask(() -> {
247             Cache.StoredDiscoveryItem storedDiscoveryItem =
248                     prepareStoredDiscoveryItemForFootprints(discoveryItem);
249             byte[] hashValue =
250                     Hashing.sha256()
251                             .hashBytes(
252                                     concat(accountKey, BluetoothAddress.decode(publicAddress)))
253                             .asBytes();
254             FastPairUploadInfo uploadInfo =
255                     new FastPairUploadInfo(storedDiscoveryItem, ByteString.copyFrom(accountKey),
256                             ByteString.copyFrom(hashValue));
257             // account data place holder here
258             try {
259                 FastPairDataProvider fastPairDataProvider = FastPairDataProvider.getInstance();
260                 if (fastPairDataProvider == null) {
261                     return;
262                 }
263                 List<Account> accountList = fastPairDataProvider.loadFastPairEligibleAccounts();
264                 if (accountList.size() > 0) {
265                     fastPairDataProvider.optIn(accountList.get(0));
266                     fastPairDataProvider.upload(accountList.get(0), uploadInfo);
267                 }
268             } catch (IllegalStateException e) {
269                 Log.e(TAG, "OEM does not construct fast pair data proxy correctly");
270             }
271         });
272     }
273 
274     @Nullable
getStoredDiscoveryItemFromAddressForFootprints( String bleAddress)275     private Cache.StoredDiscoveryItem getStoredDiscoveryItemFromAddressForFootprints(
276             String bleAddress) {
277 
278         List<DiscoveryItem> discoveryItems = new ArrayList<>();
279         //cacheManager.getAllDiscoveryItems();
280         for (DiscoveryItem discoveryItem : discoveryItems) {
281             if (bleAddress.equals(discoveryItem.getMacAddress())) {
282                 return prepareStoredDiscoveryItemForFootprints(discoveryItem);
283             }
284         }
285         return null;
286     }
287 
prepareStoredDiscoveryItemForFootprints( DiscoveryItem discoveryItem)288     static Cache.StoredDiscoveryItem prepareStoredDiscoveryItemForFootprints(
289             DiscoveryItem discoveryItem) {
290         Cache.StoredDiscoveryItem.Builder storedDiscoveryItem =
291                 discoveryItem.getCopyOfStoredItem().toBuilder();
292         // Strip the mac address so we aren't storing it in the cloud and ensure the item always
293         // starts as enabled and in a good state.
294         storedDiscoveryItem.clearMacAddress();
295 
296         return storedDiscoveryItem.build();
297     }
298 
299     /**
300      * FastPairConnection will check whether write account key result if the account key is
301      * generated change the parameter.
302      */
setShouldUpload(boolean shouldUpload)303     public void setShouldUpload(boolean shouldUpload) {
304         mShouldUpload = shouldUpload;
305     }
306 
307     private final NamedRunnable mReEnableAllDeviceItemsRunnable =
308             new NamedRunnable("reEnableAllDeviceItems") {
309                 @Override
310                 public void run() {
311                     if (mCallback != null) {
312                         mCallback.fastPairUpdateDeviceItemsEnabled(true);
313                     }
314                 }
315             };
316 
317     interface Callback {
fastPairUpdateDeviceItemsEnabled(boolean enabled)318         void fastPairUpdateDeviceItemsEnabled(boolean enabled);
319     }
320 }