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