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.halfsheet; 18 19 import static com.android.nearby.halfsheet.constants.Constant.DEVICE_PAIRING_FRAGMENT_TYPE; 20 import static com.android.nearby.halfsheet.constants.Constant.EXTRA_BINDER; 21 import static com.android.nearby.halfsheet.constants.Constant.EXTRA_BUNDLE; 22 import static com.android.nearby.halfsheet.constants.Constant.EXTRA_HALF_SHEET_CONTENT; 23 import static com.android.nearby.halfsheet.constants.Constant.EXTRA_HALF_SHEET_INFO; 24 import static com.android.nearby.halfsheet.constants.Constant.EXTRA_HALF_SHEET_TYPE; 25 import static com.android.nearby.halfsheet.constants.Constant.FAST_PAIR_HALF_SHEET_HELP_URL; 26 import static com.android.nearby.halfsheet.constants.Constant.RESULT_FAIL; 27 28 import static java.util.concurrent.TimeUnit.SECONDS; 29 30 import android.annotation.UiThread; 31 import android.app.ActivityManager; 32 import android.app.KeyguardManager; 33 import android.bluetooth.BluetoothDevice; 34 import android.content.ComponentName; 35 import android.content.Context; 36 import android.content.Intent; 37 import android.content.pm.PackageManager; 38 import android.nearby.FastPairDevice; 39 import android.nearby.FastPairStatusCallback; 40 import android.nearby.PairStatusMetadata; 41 import android.os.Bundle; 42 import android.os.UserHandle; 43 import android.util.Log; 44 import android.util.LruCache; 45 import android.widget.Toast; 46 47 import com.android.internal.annotations.VisibleForTesting; 48 import com.android.nearby.halfsheet.R; 49 import com.android.server.nearby.common.eventloop.Annotations; 50 import com.android.server.nearby.common.eventloop.EventLoop; 51 import com.android.server.nearby.common.eventloop.NamedRunnable; 52 import com.android.server.nearby.common.locator.Locator; 53 import com.android.server.nearby.common.locator.LocatorContextWrapper; 54 import com.android.server.nearby.fastpair.FastPairController; 55 import com.android.server.nearby.fastpair.PackageUtils; 56 import com.android.server.nearby.fastpair.blocklist.Blocklist; 57 import com.android.server.nearby.fastpair.cache.DiscoveryItem; 58 59 import java.util.HashMap; 60 import java.util.List; 61 import java.util.Locale; 62 import java.util.Map; 63 import java.util.concurrent.atomic.AtomicInteger; 64 65 import service.proto.Cache; 66 67 /** 68 * Fast Pair ux manager for half sheet. 69 */ 70 public class FastPairHalfSheetManager { 71 private static final String ACTIVITY_INTENT_ACTION = "android.nearby.SHOW_HALFSHEET"; 72 private static final String HALF_SHEET_CLASS_NAME = 73 "com.android.nearby.halfsheet.HalfSheetActivity"; 74 private static final String TAG = "FPHalfSheetManager"; 75 public static final String FINISHED_STATE = "FINISHED_STATE"; 76 @VisibleForTesting static final String DISMISS_HALFSHEET_RUNNABLE_NAME = "DismissHalfSheet"; 77 @VisibleForTesting static final String SHOW_TOAST_RUNNABLE_NAME = "SuccessPairingToast"; 78 79 // The timeout to ban half sheet after user trigger the ban logic odd number of time: 5 mins 80 private static final int DURATION_RESURFACE_HALFSHEET_FIRST_DISMISS_MILLI_SECONDS = 300000; 81 // Number of seconds half sheet will show after the advertisement is no longer seen. 82 private static final int HALF_SHEET_TIME_OUT_SECONDS = 12; 83 84 static final int HALFSHEET_ID_SEED = "new_fast_pair_half_sheet".hashCode(); 85 86 private String mHalfSheetApkPkgName; 87 private boolean mIsHalfSheetForeground = false; 88 private boolean mIsActivePairing = false; 89 private Cache.ScanFastPairStoreItem mCurrentScanFastPairStoreItem = null; 90 private final LocatorContextWrapper mLocatorContextWrapper; 91 private final AtomicInteger mNotificationIds = new AtomicInteger(HALFSHEET_ID_SEED); 92 private FastPairHalfSheetBlocklist mHalfSheetBlocklist; 93 // Todo: Make "16" a flag, which can be updated from the server side. 94 final LruCache<String, Integer> mModelIdMap = new LruCache<>(16); 95 HalfSheetDismissState mHalfSheetDismissState = HalfSheetDismissState.ACTIVE; 96 // Ban count map track the number of ban happens to certain model id 97 // If the model id is baned by the odd number of time it is banned for 5 mins 98 // if the model id is banned even number of time ban 24 hours. 99 private final Map<Integer, Integer> mBanCountMap = new HashMap<>(); 100 101 FastPairUiServiceImpl mFastPairUiService; 102 private NamedRunnable mDismissRunnable; 103 104 /** 105 * Half sheet state default is active. If user dismiss half sheet once controller will mark half 106 * sheet as dismiss state. If user dismiss half sheet twice controller will mark half sheet as 107 * ban state for certain period of time. 108 */ 109 enum HalfSheetDismissState { 110 ACTIVE, 111 DISMISS, 112 BAN 113 } 114 FastPairHalfSheetManager(Context context)115 public FastPairHalfSheetManager(Context context) { 116 this(new LocatorContextWrapper(context)); 117 mHalfSheetBlocklist = new FastPairHalfSheetBlocklist(); 118 } 119 120 @VisibleForTesting FastPairHalfSheetManager(LocatorContextWrapper locatorContextWrapper)121 public FastPairHalfSheetManager(LocatorContextWrapper locatorContextWrapper) { 122 mLocatorContextWrapper = locatorContextWrapper; 123 mFastPairUiService = new FastPairUiServiceImpl(); 124 mHalfSheetBlocklist = new FastPairHalfSheetBlocklist(); 125 } 126 127 /** 128 * Invokes half sheet in the other apk. This function can only be called in Nearby because other 129 * app can't get the correct component name. 130 */ showHalfSheet(Cache.ScanFastPairStoreItem scanFastPairStoreItem)131 public void showHalfSheet(Cache.ScanFastPairStoreItem scanFastPairStoreItem) { 132 String modelId = scanFastPairStoreItem.getModelId().toLowerCase(Locale.ROOT); 133 if (modelId == null) { 134 Log.d(TAG, "model id not found"); 135 return; 136 } 137 138 synchronized (mModelIdMap) { 139 if (mModelIdMap.get(modelId) == null) { 140 mModelIdMap.put(modelId, createNewHalfSheetId()); 141 } 142 } 143 int halfSheetId = mModelIdMap.get(modelId); 144 145 if (!allowedToShowHalfSheet(halfSheetId)) { 146 Log.d(TAG, "Not allow to show initial Half sheet"); 147 return; 148 } 149 150 // If currently half sheet UI is in the foreground, 151 // DO NOT request start-activity to avoid unnecessary memory usage 152 if (mIsHalfSheetForeground) { 153 updateForegroundHalfSheet(scanFastPairStoreItem); 154 return; 155 } else { 156 // If the half sheet is not in foreground but the system is still pairing 157 // with the same device, mark as duplicate request and skip. 158 if (mCurrentScanFastPairStoreItem != null && mIsActivePairing 159 && mCurrentScanFastPairStoreItem.getAddress().toLowerCase(Locale.ROOT) 160 .equals(scanFastPairStoreItem.getAddress().toLowerCase(Locale.ROOT))) { 161 Log.d(TAG, "Same device is pairing."); 162 return; 163 } 164 } 165 166 try { 167 if (mLocatorContextWrapper != null) { 168 String packageName = getHalfSheetApkPkgName(); 169 if (packageName == null) { 170 Log.e(TAG, "package name is null"); 171 return; 172 } 173 mFastPairUiService.setFastPairController( 174 mLocatorContextWrapper.getLocator().get(FastPairController.class)); 175 Bundle bundle = new Bundle(); 176 bundle.putBinder(EXTRA_BINDER, mFastPairUiService); 177 mLocatorContextWrapper 178 .startActivityAsUser(new Intent(ACTIVITY_INTENT_ACTION) 179 .putExtra(EXTRA_HALF_SHEET_INFO, 180 scanFastPairStoreItem.toByteArray()) 181 .putExtra(EXTRA_HALF_SHEET_TYPE, 182 DEVICE_PAIRING_FRAGMENT_TYPE) 183 .putExtra(EXTRA_BUNDLE, bundle) 184 .setComponent(new ComponentName(packageName, 185 HALF_SHEET_CLASS_NAME)), 186 UserHandle.CURRENT); 187 mHalfSheetBlocklist.updateState(halfSheetId, Blocklist.BlocklistState.ACTIVE); 188 } 189 } catch (IllegalStateException e) { 190 Log.e(TAG, "Can't resolve package that contains half sheet"); 191 } 192 Log.d(TAG, "show initial half sheet."); 193 mCurrentScanFastPairStoreItem = scanFastPairStoreItem; 194 mIsHalfSheetForeground = true; 195 enableAutoDismiss(scanFastPairStoreItem.getAddress(), HALF_SHEET_TIME_OUT_SECONDS); 196 } 197 198 /** 199 * Auto dismiss half sheet after timeout 200 */ 201 @VisibleForTesting enableAutoDismiss(String address, long timeoutDuration)202 void enableAutoDismiss(String address, long timeoutDuration) { 203 if (mDismissRunnable == null 204 || !mDismissRunnable.name.equals(DISMISS_HALFSHEET_RUNNABLE_NAME)) { 205 mDismissRunnable = 206 new NamedRunnable(DISMISS_HALFSHEET_RUNNABLE_NAME) { 207 @Override 208 public void run() { 209 Log.d(TAG, "Dismiss the half sheet after " 210 + timeoutDuration + " seconds"); 211 // BMW car kit will advertise even after pairing start, 212 // to avoid the half sheet be dismissed during active pairing, 213 // If the half sheet is in the pairing state, disable the auto dismiss. 214 // See b/182396106 215 if (mIsActivePairing) { 216 return; 217 } 218 mIsHalfSheetForeground = false; 219 FastPairStatusCallback pairStatusCallback = 220 mFastPairUiService.getPairStatusCallback(); 221 if (pairStatusCallback != null) { 222 pairStatusCallback.onPairUpdate(new FastPairDevice.Builder() 223 .setBluetoothAddress(address).build(), 224 new PairStatusMetadata(PairStatusMetadata.Status.DISMISS)); 225 } else { 226 Log.w(TAG, "pairStatusCallback is null," 227 + " failed to enable auto dismiss "); 228 } 229 } 230 }; 231 } 232 if (Locator.get(mLocatorContextWrapper, EventLoop.class).isPosted(mDismissRunnable)) { 233 disableDismissRunnable(); 234 } 235 Locator.get(mLocatorContextWrapper, EventLoop.class) 236 .postRunnableDelayed(mDismissRunnable, SECONDS.toMillis(timeoutDuration)); 237 } 238 updateForegroundHalfSheet(Cache.ScanFastPairStoreItem scanFastPairStoreItem)239 private void updateForegroundHalfSheet(Cache.ScanFastPairStoreItem scanFastPairStoreItem) { 240 if (mCurrentScanFastPairStoreItem == null) { 241 return; 242 } 243 if (mCurrentScanFastPairStoreItem.getAddress().toLowerCase(Locale.ROOT) 244 .equals(scanFastPairStoreItem.getAddress().toLowerCase(Locale.ROOT))) { 245 // If current address is the same, reset the timeout. 246 Log.d(TAG, "same Address device, reset the auto dismiss timeout"); 247 enableAutoDismiss(scanFastPairStoreItem.getAddress(), HALF_SHEET_TIME_OUT_SECONDS); 248 } else { 249 // If current address is different, not reset timeout 250 // wait for half sheet auto dismiss or manually dismiss to start new pair. 251 if (mCurrentScanFastPairStoreItem.getModelId().toLowerCase(Locale.ROOT) 252 .equals(scanFastPairStoreItem.getModelId().toLowerCase(Locale.ROOT))) { 253 Log.d(TAG, "same model id device is also nearby"); 254 } 255 Log.d(TAG, "showInitialHalfsheet: address changed, from " 256 + mCurrentScanFastPairStoreItem.getAddress() 257 + " to " + scanFastPairStoreItem.getAddress()); 258 } 259 } 260 261 /** 262 * Show passkey confirmation info on half sheet 263 */ showPasskeyConfirmation(BluetoothDevice device, int passkey)264 public void showPasskeyConfirmation(BluetoothDevice device, int passkey) { 265 } 266 267 /** 268 * This function will handle pairing steps for half sheet. 269 */ showPairingHalfSheet(DiscoveryItem item)270 public void showPairingHalfSheet(DiscoveryItem item) { 271 Log.d(TAG, "show pairing half sheet"); 272 } 273 274 /** 275 * Shows pairing success info. 276 * If the half sheet is not shown, show toast to remind user. 277 */ showPairingSuccessHalfSheet(String address)278 public void showPairingSuccessHalfSheet(String address) { 279 resetPairingStateDisableAutoDismiss(); 280 if (mIsHalfSheetForeground) { 281 FastPairStatusCallback pairStatusCallback = mFastPairUiService.getPairStatusCallback(); 282 if (pairStatusCallback == null) { 283 Log.w(TAG, "FastPairHalfSheetManager failed to show success half sheet because " 284 + "the pairStatusCallback is null"); 285 return; 286 } 287 Log.d(TAG, "showPairingSuccess: pairStatusCallback not NULL"); 288 pairStatusCallback.onPairUpdate( 289 new FastPairDevice.Builder().setBluetoothAddress(address).build(), 290 new PairStatusMetadata(PairStatusMetadata.Status.SUCCESS)); 291 } else { 292 Locator.get(mLocatorContextWrapper, EventLoop.class) 293 .postRunnable( 294 new NamedRunnable(SHOW_TOAST_RUNNABLE_NAME) { 295 @Override 296 public void run() { 297 try { 298 Toast.makeText(mLocatorContextWrapper, 299 mLocatorContextWrapper 300 .getPackageManager() 301 .getResourcesForApplication( 302 getHalfSheetApkPkgName()) 303 .getString(R.string.fast_pair_device_ready), 304 Toast.LENGTH_LONG).show(); 305 } catch (PackageManager.NameNotFoundException e) { 306 Log.d(TAG, "showPairingSuccess fail:" 307 + " package name cannot be found "); 308 e.printStackTrace(); 309 } 310 } 311 }); 312 } 313 } 314 315 /** 316 * Shows pairing fail half sheet. 317 * If the half sheet is not shown, create a new half sheet to help user go to Setting 318 * to manually pair with the device. 319 */ showPairingFailed()320 public void showPairingFailed() { 321 resetPairingStateDisableAutoDismiss(); 322 if (mCurrentScanFastPairStoreItem == null) { 323 return; 324 } 325 if (mIsHalfSheetForeground) { 326 FastPairStatusCallback pairStatusCallback = mFastPairUiService.getPairStatusCallback(); 327 if (pairStatusCallback != null) { 328 Log.v(TAG, "showPairingFailed: pairStatusCallback not NULL"); 329 pairStatusCallback.onPairUpdate( 330 new FastPairDevice.Builder() 331 .setBluetoothAddress(mCurrentScanFastPairStoreItem.getAddress()) 332 .build(), 333 new PairStatusMetadata(PairStatusMetadata.Status.FAIL)); 334 } else { 335 Log.w(TAG, "FastPairHalfSheetManager failed to show fail half sheet because " 336 + "the pairStatusCallback is null"); 337 } 338 } else { 339 String packageName = getHalfSheetApkPkgName(); 340 if (packageName == null) { 341 Log.e(TAG, "package name is null"); 342 return; 343 } 344 Bundle bundle = new Bundle(); 345 bundle.putBinder(EXTRA_BINDER, mFastPairUiService); 346 mLocatorContextWrapper 347 .startActivityAsUser(new Intent(ACTIVITY_INTENT_ACTION) 348 .putExtra(EXTRA_HALF_SHEET_INFO, 349 mCurrentScanFastPairStoreItem.toByteArray()) 350 .putExtra(EXTRA_HALF_SHEET_TYPE, 351 DEVICE_PAIRING_FRAGMENT_TYPE) 352 .putExtra(EXTRA_HALF_SHEET_CONTENT, RESULT_FAIL) 353 .putExtra(EXTRA_BUNDLE, bundle) 354 .setComponent(new ComponentName(packageName, 355 HALF_SHEET_CLASS_NAME)), 356 UserHandle.CURRENT); 357 Log.d(TAG, "Starts a new half sheet to showPairingFailed"); 358 String modelId = mCurrentScanFastPairStoreItem.getModelId().toLowerCase(Locale.ROOT); 359 if (modelId == null || mModelIdMap.get(modelId) == null) { 360 Log.d(TAG, "info not enough"); 361 return; 362 } 363 int halfSheetId = mModelIdMap.get(modelId); 364 mHalfSheetBlocklist.updateState(halfSheetId, Blocklist.BlocklistState.ACTIVE); 365 } 366 } 367 368 /** 369 * Removes dismiss half sheet runnable. When half sheet shows, there is timer for half sheet to 370 * dismiss. But when user is pairing, half sheet should not dismiss. 371 * So this function disable the runnable. 372 */ disableDismissRunnable()373 public void disableDismissRunnable() { 374 if (mDismissRunnable == null) { 375 return; 376 } 377 Log.d(TAG, "remove dismiss runnable"); 378 Locator.get(mLocatorContextWrapper, EventLoop.class).removeRunnable(mDismissRunnable); 379 } 380 381 /** 382 * When user first click back button or click the empty space in half sheet the half sheet will 383 * be banned for certain short period of time for that device model id. When user click cancel 384 * or dismiss half sheet for the second time the half sheet related item should be added to 385 * blocklist so the half sheet will not show again to interrupt user. 386 * 387 * @param modelId half sheet display item modelId. 388 */ 389 @Annotations.EventThread dismiss(String modelId)390 public void dismiss(String modelId) { 391 Log.d(TAG, "HalfSheetManager report dismiss device modelId: " + modelId); 392 mIsHalfSheetForeground = false; 393 Integer halfSheetId = mModelIdMap.get(modelId); 394 if (mDismissRunnable != null 395 && Locator.get(mLocatorContextWrapper, EventLoop.class) 396 .isPosted(mDismissRunnable)) { 397 disableDismissRunnable(); 398 } 399 if (halfSheetId != null) { 400 Log.d(TAG, "id: " + halfSheetId + " half sheet is dismissed"); 401 boolean isDontShowAgain = 402 !mHalfSheetBlocklist.updateState(halfSheetId, 403 Blocklist.BlocklistState.DISMISSED); 404 if (isDontShowAgain) { 405 if (!mBanCountMap.containsKey(halfSheetId)) { 406 mBanCountMap.put(halfSheetId, 0); 407 } 408 int dismissCountTrack = mBanCountMap.get(halfSheetId) + 1; 409 mBanCountMap.put(halfSheetId, dismissCountTrack); 410 if (dismissCountTrack % 2 == 1) { 411 Log.d(TAG, "id: " + halfSheetId + " half sheet is short time banned"); 412 mHalfSheetBlocklist.forceUpdateState(halfSheetId, 413 Blocklist.BlocklistState.DO_NOT_SHOW_AGAIN); 414 } else { 415 Log.d(TAG, "id: " + halfSheetId + " half sheet is long time banned"); 416 mHalfSheetBlocklist.updateState(halfSheetId, 417 Blocklist.BlocklistState.DO_NOT_SHOW_AGAIN_LONG); 418 } 419 } 420 } 421 } 422 423 /** 424 * Changes the half sheet ban state to active. 425 */ 426 @UiThread resetBanState(String modelId)427 public void resetBanState(String modelId) { 428 Log.d(TAG, "HalfSheetManager reset device ban state modelId: " + modelId); 429 Integer halfSheetId = mModelIdMap.get(modelId); 430 if (halfSheetId == null) { 431 Log.d(TAG, "halfSheetId not found."); 432 return; 433 } 434 mHalfSheetBlocklist.resetBlockState(halfSheetId); 435 } 436 437 // Invokes this method to reset some states when showing the pairing result. resetPairingStateDisableAutoDismiss()438 private void resetPairingStateDisableAutoDismiss() { 439 mIsActivePairing = false; 440 if (mDismissRunnable != null && Locator.get(mLocatorContextWrapper, EventLoop.class) 441 .isPosted(mDismissRunnable)) { 442 disableDismissRunnable(); 443 } 444 } 445 446 /** 447 * When the device pairing finished should remove the suppression for the model id 448 * so the user canntry twice if the user want to. 449 */ reportDonePairing(int halfSheetId)450 public void reportDonePairing(int halfSheetId) { 451 mHalfSheetBlocklist.removeBlocklist(halfSheetId); 452 } 453 454 @VisibleForTesting getHalfSheetBlocklist()455 public FastPairHalfSheetBlocklist getHalfSheetBlocklist() { 456 return mHalfSheetBlocklist; 457 } 458 459 /** 460 * Destroys the bluetooth pairing controller. 461 */ destroyBluetoothPairController()462 public void destroyBluetoothPairController() { 463 } 464 465 /** 466 * Notifies manager the pairing has finished. 467 */ notifyPairingProcessDone(boolean success, String address, DiscoveryItem item)468 public void notifyPairingProcessDone(boolean success, String address, DiscoveryItem item) { 469 mCurrentScanFastPairStoreItem = null; 470 mIsHalfSheetForeground = false; 471 } 472 allowedToShowHalfSheet(int halfSheetId)473 private boolean allowedToShowHalfSheet(int halfSheetId) { 474 // Half Sheet will not show when the screen is locked so disable half sheet 475 KeyguardManager keyguardManager = 476 mLocatorContextWrapper.getSystemService(KeyguardManager.class); 477 if (keyguardManager != null && keyguardManager.isKeyguardLocked()) { 478 Log.d(TAG, "device is locked"); 479 return false; 480 } 481 482 // Check whether the blocklist state has expired 483 if (mHalfSheetBlocklist.isStateExpired(halfSheetId)) { 484 mHalfSheetBlocklist.removeBlocklist(halfSheetId); 485 mBanCountMap.remove(halfSheetId); 486 } 487 488 // Half Sheet will not show when the model id is banned 489 if (mHalfSheetBlocklist.isBlocklisted( 490 halfSheetId, DURATION_RESURFACE_HALFSHEET_FIRST_DISMISS_MILLI_SECONDS)) { 491 Log.d(TAG, "id: " + halfSheetId + " is blocked"); 492 return false; 493 } 494 return !isHelpPageForeground(); 495 } 496 497 /** 498 * Checks if the user already open the info page, return true to suppress half sheet. 499 * ActivityManager#getRunningTasks to get the most recent task and check the baseIntent's 500 * url to see if we should suppress half sheet. 501 */ isHelpPageForeground()502 private boolean isHelpPageForeground() { 503 ActivityManager activityManager = 504 mLocatorContextWrapper.getSystemService(ActivityManager.class); 505 if (activityManager == null) { 506 Log.d(TAG, "ActivityManager is null"); 507 return false; 508 } 509 try { 510 List<ActivityManager.RunningTaskInfo> taskInfos = activityManager.getRunningTasks(1); 511 if (taskInfos.isEmpty()) { 512 Log.d(TAG, "Empty running tasks"); 513 return false; 514 } 515 String url = taskInfos.get(0).baseIntent.getDataString(); 516 Log.d(TAG, "Info page url:" + url); 517 if (FAST_PAIR_HALF_SHEET_HELP_URL.equals(url)) { 518 return true; 519 } 520 } catch (SecurityException e) { 521 Log.d(TAG, "Unable to get running tasks"); 522 } 523 return false; 524 } 525 526 /** Report actively pairing when the Fast Pair starts. */ reportActivelyPairing()527 public void reportActivelyPairing() { 528 mIsActivePairing = true; 529 } 530 531 createNewHalfSheetId()532 private Integer createNewHalfSheetId() { 533 return mNotificationIds.getAndIncrement(); 534 } 535 536 /** Gets the half sheet status whether it is foreground or dismissed */ getHalfSheetForeground()537 public boolean getHalfSheetForeground() { 538 return mIsHalfSheetForeground; 539 } 540 541 /** Sets whether the half sheet is at the foreground or not. */ setHalfSheetForeground(boolean state)542 public void setHalfSheetForeground(boolean state) { 543 mIsHalfSheetForeground = state; 544 } 545 546 /** Returns whether the fast pair is actively pairing . */ 547 @VisibleForTesting isActivePairing()548 public boolean isActivePairing() { 549 return mIsActivePairing; 550 } 551 552 /** Sets fast pair to be active pairing or not, used for testing. */ 553 @VisibleForTesting setIsActivePairing(boolean isActivePairing)554 public void setIsActivePairing(boolean isActivePairing) { 555 mIsActivePairing = isActivePairing; 556 } 557 558 /** 559 * Gets the package name of HalfSheet.apk 560 * getHalfSheetApkPkgName may invoke PackageManager multiple times and it does not have 561 * race condition check. Since there is no lock for mHalfSheetApkPkgName. 562 */ getHalfSheetApkPkgName()563 private String getHalfSheetApkPkgName() { 564 if (mHalfSheetApkPkgName != null) { 565 return mHalfSheetApkPkgName; 566 } 567 mHalfSheetApkPkgName = PackageUtils.getHalfSheetApkPkgName(mLocatorContextWrapper); 568 Log.v(TAG, "Found halfsheet APK at: " + mHalfSheetApkPkgName); 569 return mHalfSheetApkPkgName; 570 } 571 } 572