• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 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 static com.google.common.primitives.Bytes.concat;
22 
23 import android.accounts.Account;
24 import android.annotation.Nullable;
25 import android.content.Context;
26 import android.nearby.FastPairDevice;
27 import android.nearby.NearbyDevice;
28 import android.util.Log;
29 
30 import com.android.internal.annotations.VisibleForTesting;
31 import com.android.server.nearby.common.ble.decode.FastPairDecoder;
32 import com.android.server.nearby.common.ble.util.RangingUtils;
33 import com.android.server.nearby.common.bloomfilter.BloomFilter;
34 import com.android.server.nearby.common.bloomfilter.FastPairBloomFilterHasher;
35 import com.android.server.nearby.common.locator.Locator;
36 import com.android.server.nearby.fastpair.cache.DiscoveryItem;
37 import com.android.server.nearby.fastpair.cache.FastPairCacheManager;
38 import com.android.server.nearby.fastpair.halfsheet.FastPairHalfSheetManager;
39 import com.android.server.nearby.provider.FastPairDataProvider;
40 import com.android.server.nearby.util.DataUtils;
41 import com.android.server.nearby.util.Hex;
42 
43 import java.util.List;
44 
45 import service.proto.Cache;
46 import service.proto.Data;
47 import service.proto.Rpcs;
48 
49 /**
50  * Handler that handle fast pair related broadcast.
51  */
52 public class FastPairAdvHandler {
53     Context mContext;
54     String mBleAddress;
55     // Need to be deleted after notification manager in use.
56     private boolean mIsFirst = false;
57     private FastPairDataProvider mPairDataProvider;
58     private static final double NEARBY_DISTANCE_THRESHOLD = 0.6;
59 
60     /** The types about how the bloomfilter is processed. */
61     public enum ProcessBloomFilterType {
62         IGNORE, // The bloomfilter is not handled. e.g. distance is too far away.
63         CACHE, // The bloomfilter is recognized in the local cache.
64         FOOTPRINT, // Need to check the bloomfilter from the footprints.
65         ACCOUNT_KEY_HIT // The specified account key was hit the bloom filter.
66     }
67 
68     /**
69      * Constructor function.
70      */
FastPairAdvHandler(Context context)71     public FastPairAdvHandler(Context context) {
72         mContext = context;
73     }
74 
75     @VisibleForTesting
FastPairAdvHandler(Context context, FastPairDataProvider dataProvider)76     FastPairAdvHandler(Context context, FastPairDataProvider dataProvider) {
77         mContext = context;
78         mPairDataProvider = dataProvider;
79     }
80 
81     /**
82      * Handles all of the scanner result. Fast Pair will handle model id broadcast bloomfilter
83      * broadcast and battery level broadcast.
84      */
handleBroadcast(NearbyDevice device)85     public void handleBroadcast(NearbyDevice device) {
86         FastPairDevice fastPairDevice = (FastPairDevice) device;
87         mBleAddress = fastPairDevice.getBluetoothAddress();
88         if (mPairDataProvider == null) {
89             mPairDataProvider = FastPairDataProvider.getInstance();
90         }
91         if (mPairDataProvider == null) {
92             return;
93         }
94 
95         if (FastPairDecoder.checkModelId(fastPairDevice.getData())) {
96             byte[] model = FastPairDecoder.getModelId(fastPairDevice.getData());
97             Log.d(TAG, "On discovery model id " + Hex.bytesToStringLowercase(model));
98             // Use api to get anti spoofing key from model id.
99             try {
100                 List<Account> accountList = mPairDataProvider.loadFastPairEligibleAccounts();
101                 Rpcs.GetObservedDeviceResponse response =
102                         mPairDataProvider.loadFastPairAntispoofKeyDeviceMetadata(model);
103                 if (response == null) {
104                     Log.e(TAG, "server does not have model id "
105                             + Hex.bytesToStringLowercase(model));
106                     return;
107                 }
108                 // Check the distance of the device if the distance is larger than the threshold
109                 // do not show half sheet.
110                 if (!isNearby(fastPairDevice.getRssi(),
111                         response.getDevice().getBleTxPower() == 0 ? fastPairDevice.getTxPower()
112                                 : response.getDevice().getBleTxPower())) {
113                     return;
114                 }
115                 Locator.get(mContext, FastPairHalfSheetManager.class).showHalfSheet(
116                         DataUtils.toScanFastPairStoreItem(
117                                 response, mBleAddress,
118                                 accountList.isEmpty() ? null : accountList.get(0).name));
119             } catch (IllegalStateException e) {
120                 Log.e(TAG, "OEM does not construct fast pair data proxy correctly");
121             }
122         } else {
123             // Start to process bloom filter
124             try {
125                 List<Account> accountList = mPairDataProvider.loadFastPairEligibleAccounts();
126                 byte[] bloomFilterByteArray = FastPairDecoder
127                         .getBloomFilter(fastPairDevice.getData());
128                 byte[] bloomFilterSalt = FastPairDecoder
129                         .getBloomFilterSalt(fastPairDevice.getData());
130                 if (bloomFilterByteArray == null || bloomFilterByteArray.length == 0) {
131                     return;
132                 }
133                 for (Account account : accountList) {
134                     List<Data.FastPairDeviceWithAccountKey> listDevices =
135                             mPairDataProvider.loadFastPairDeviceWithAccountKey(account);
136                     Data.FastPairDeviceWithAccountKey recognizedDevice =
137                             findRecognizedDevice(listDevices,
138                                     new BloomFilter(bloomFilterByteArray,
139                                             new FastPairBloomFilterHasher()), bloomFilterSalt);
140 
141                     if (recognizedDevice != null) {
142                         Log.d(TAG, "find matched device show notification to remind"
143                                 + " user to pair");
144                         // Check the distance of the device if the distance is larger than the
145                         // threshold
146                         // do not show half sheet.
147                         if (!isNearby(fastPairDevice.getRssi(),
148                                 recognizedDevice.getDiscoveryItem().getTxPower() == 0
149                                         ? fastPairDevice.getTxPower()
150                                         : recognizedDevice.getDiscoveryItem().getTxPower())) {
151                             return;
152                         }
153                         // Check if the device is already paired
154                         List<Cache.StoredFastPairItem> storedFastPairItemList =
155                                 Locator.get(mContext, FastPairCacheManager.class)
156                                         .getAllSavedStoredFastPairItem();
157                         Cache.StoredFastPairItem recognizedStoredFastPairItem =
158                                 findRecognizedDeviceFromCachedItem(storedFastPairItemList,
159                                         new BloomFilter(bloomFilterByteArray,
160                                                 new FastPairBloomFilterHasher()), bloomFilterSalt);
161                         if (recognizedStoredFastPairItem != null) {
162                             // The bloomfilter is recognized in the cache so the device is paired
163                             // before
164                             Log.d(TAG, "bloom filter is recognized in the cache");
165                             continue;
166                         } else {
167                             Log.d(TAG, "bloom filter is recognized not paired before should"
168                                     + "show subsequent pairing notification");
169                             if (mIsFirst) {
170                                 mIsFirst = false;
171                                 // Get full info from api the initial request will only return
172                                 // part of the info due to size limit.
173                                 List<Data.FastPairDeviceWithAccountKey> resList =
174                                         mPairDataProvider.loadFastPairDeviceWithAccountKey(account,
175                                                 List.of(recognizedDevice.getAccountKey()
176                                                         .toByteArray()));
177                                 if (resList != null && resList.size() > 0) {
178                                     //Saved device from footprint does not have ble address so
179                                     // fill ble address with current scan result.
180                                     Cache.StoredDiscoveryItem storedDiscoveryItem =
181                                             resList.get(0).getDiscoveryItem().toBuilder()
182                                                     .setMacAddress(
183                                                             fastPairDevice.getBluetoothAddress())
184                                                     .build();
185                                     Locator.get(mContext, FastPairController.class).pair(
186                                             new DiscoveryItem(mContext, storedDiscoveryItem),
187                                             resList.get(0).getAccountKey().toByteArray(),
188                                             /** companionApp=*/null);
189                                 }
190                             }
191                         }
192 
193                         return;
194                     }
195                 }
196             } catch (IllegalStateException e) {
197                 Log.e(TAG, "OEM does not construct fast pair data proxy correctly");
198             }
199 
200         }
201     }
202 
203     /**
204      * Checks the bloom filter to see if any of the devices are recognized and should have a
205      * notification displayed for them. A device is recognized if the account key + salt combination
206      * is inside the bloom filter.
207      */
208     @Nullable
findRecognizedDevice( List<Data.FastPairDeviceWithAccountKey> devices, BloomFilter bloomFilter, byte[] salt)209     static Data.FastPairDeviceWithAccountKey findRecognizedDevice(
210             List<Data.FastPairDeviceWithAccountKey> devices, BloomFilter bloomFilter, byte[] salt) {
211         Log.d(TAG, "saved devices size in the account is " + devices.size());
212         for (Data.FastPairDeviceWithAccountKey device : devices) {
213             if (device.getAccountKey().toByteArray() == null || salt == null) {
214                 return null;
215             }
216             byte[] rotatedKey = concat(device.getAccountKey().toByteArray(), salt);
217             StringBuilder sb = new StringBuilder();
218             for (byte b : rotatedKey) {
219                 sb.append(b);
220             }
221             if (bloomFilter.possiblyContains(rotatedKey)) {
222                 Log.d(TAG, "match " + sb.toString());
223                 return device;
224             } else {
225                 Log.d(TAG, "not match " + sb.toString());
226             }
227         }
228         return null;
229     }
230 
231     @Nullable
findRecognizedDeviceFromCachedItem( List<Cache.StoredFastPairItem> devices, BloomFilter bloomFilter, byte[] salt)232     static Cache.StoredFastPairItem findRecognizedDeviceFromCachedItem(
233             List<Cache.StoredFastPairItem> devices, BloomFilter bloomFilter, byte[] salt) {
234         for (Cache.StoredFastPairItem device : devices) {
235             if (device.getAccountKey().toByteArray() == null || salt == null) {
236                 return null;
237             }
238             byte[] rotatedKey = concat(device.getAccountKey().toByteArray(), salt);
239             if (bloomFilter.possiblyContains(rotatedKey)) {
240                 return device;
241             }
242         }
243         return null;
244     }
245 
246     /**
247      * Check the device distance for certain rssi value.
248      */
isNearby(int rssi, int txPower)249     boolean isNearby(int rssi, int txPower) {
250         return RangingUtils.distanceFromRssiAndTxPower(rssi, txPower) < NEARBY_DISTANCE_THRESHOLD;
251     }
252 
253 }
254