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 }