• 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.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