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.server.nearby.fastpair.Constant.DEVICE_PAIRING_FRAGMENT_TYPE; 20 import static com.android.server.nearby.fastpair.Constant.EXTRA_BINDER; 21 import static com.android.server.nearby.fastpair.Constant.EXTRA_BUNDLE; 22 import static com.android.server.nearby.fastpair.Constant.EXTRA_HALF_SHEET_INFO; 23 import static com.android.server.nearby.fastpair.Constant.EXTRA_HALF_SHEET_TYPE; 24 import static com.android.server.nearby.fastpair.FastPairManager.ACTION_RESOURCES_APK; 25 26 import android.bluetooth.BluetoothDevice; 27 import android.content.ComponentName; 28 import android.content.Context; 29 import android.content.Intent; 30 import android.content.pm.PackageManager; 31 import android.content.pm.ResolveInfo; 32 import android.nearby.FastPairDevice; 33 import android.nearby.FastPairStatusCallback; 34 import android.nearby.PairStatusMetadata; 35 import android.os.Bundle; 36 import android.os.UserHandle; 37 import android.util.Log; 38 39 import com.android.internal.annotations.VisibleForTesting; 40 import com.android.server.nearby.common.locator.LocatorContextWrapper; 41 import com.android.server.nearby.fastpair.FastPairController; 42 import com.android.server.nearby.fastpair.cache.DiscoveryItem; 43 import com.android.server.nearby.util.Environment; 44 45 import java.util.List; 46 import java.util.stream.Collectors; 47 48 import service.proto.Cache; 49 50 /** 51 * Fast Pair ux manager for half sheet. 52 */ 53 public class FastPairHalfSheetManager { 54 private static final String ACTIVITY_INTENT_ACTION = "android.nearby.SHOW_HALFSHEET"; 55 private static final String HALF_SHEET_CLASS_NAME = 56 "com.android.nearby.halfsheet.HalfSheetActivity"; 57 private static final String TAG = "FPHalfSheetManager"; 58 59 private String mHalfSheetApkPkgName; 60 private final LocatorContextWrapper mLocatorContextWrapper; 61 62 FastPairUiServiceImpl mFastPairUiService; 63 FastPairHalfSheetManager(Context context)64 public FastPairHalfSheetManager(Context context) { 65 this(new LocatorContextWrapper(context)); 66 } 67 68 @VisibleForTesting FastPairHalfSheetManager(LocatorContextWrapper locatorContextWrapper)69 FastPairHalfSheetManager(LocatorContextWrapper locatorContextWrapper) { 70 mLocatorContextWrapper = locatorContextWrapper; 71 mFastPairUiService = new FastPairUiServiceImpl(); 72 } 73 74 /** 75 * Invokes half sheet in the other apk. This function can only be called in Nearby because other 76 * app can't get the correct component name. 77 */ showHalfSheet(Cache.ScanFastPairStoreItem scanFastPairStoreItem)78 public void showHalfSheet(Cache.ScanFastPairStoreItem scanFastPairStoreItem) { 79 try { 80 if (mLocatorContextWrapper != null) { 81 String packageName = getHalfSheetApkPkgName(); 82 if (packageName == null) { 83 Log.e(TAG, "package name is null"); 84 return; 85 } 86 mFastPairUiService.setFastPairController( 87 mLocatorContextWrapper.getLocator().get(FastPairController.class)); 88 Bundle bundle = new Bundle(); 89 bundle.putBinder(EXTRA_BINDER, mFastPairUiService); 90 mLocatorContextWrapper 91 .startActivityAsUser(new Intent(ACTIVITY_INTENT_ACTION) 92 .putExtra(EXTRA_HALF_SHEET_INFO, 93 scanFastPairStoreItem.toByteArray()) 94 .putExtra(EXTRA_HALF_SHEET_TYPE, 95 DEVICE_PAIRING_FRAGMENT_TYPE) 96 .putExtra(EXTRA_BUNDLE, bundle) 97 .setComponent(new ComponentName(packageName, 98 HALF_SHEET_CLASS_NAME)), 99 UserHandle.CURRENT); 100 } 101 } catch (IllegalStateException e) { 102 Log.e(TAG, "Can't resolve package that contains half sheet"); 103 } 104 } 105 106 /** 107 * Shows pairing fail half sheet. 108 */ showPairingFailed()109 public void showPairingFailed() { 110 FastPairStatusCallback pairStatusCallback = mFastPairUiService.getPairStatusCallback(); 111 if (pairStatusCallback != null) { 112 Log.v(TAG, "showPairingFailed: pairStatusCallback not NULL"); 113 pairStatusCallback.onPairUpdate(new FastPairDevice.Builder().build(), 114 new PairStatusMetadata(PairStatusMetadata.Status.FAIL)); 115 } else { 116 Log.w(TAG, "FastPairHalfSheetManager failed to show success half sheet because " 117 + "the pairStatusCallback is null"); 118 } 119 } 120 121 /** 122 * Get the half sheet status whether it is foreground or dismissed 123 */ getHalfSheetForegroundState()124 public boolean getHalfSheetForegroundState() { 125 return true; 126 } 127 128 /** 129 * Show passkey confirmation info on half sheet 130 */ showPasskeyConfirmation(BluetoothDevice device, int passkey)131 public void showPasskeyConfirmation(BluetoothDevice device, int passkey) { 132 } 133 134 /** 135 * This function will handle pairing steps for half sheet. 136 */ showPairingHalfSheet(DiscoveryItem item)137 public void showPairingHalfSheet(DiscoveryItem item) { 138 Log.d(TAG, "show pairing half sheet"); 139 } 140 141 /** 142 * Shows pairing success info. 143 */ showPairingSuccessHalfSheet(String address)144 public void showPairingSuccessHalfSheet(String address) { 145 FastPairStatusCallback pairStatusCallback = mFastPairUiService.getPairStatusCallback(); 146 if (pairStatusCallback != null) { 147 pairStatusCallback.onPairUpdate( 148 new FastPairDevice.Builder().setBluetoothAddress(address).build(), 149 new PairStatusMetadata(PairStatusMetadata.Status.SUCCESS)); 150 } else { 151 Log.w(TAG, "FastPairHalfSheetManager failed to show success half sheet because " 152 + "the pairStatusCallback is null"); 153 } 154 } 155 156 /** 157 * Removes dismiss runnable. 158 */ disableDismissRunnable()159 public void disableDismissRunnable() { 160 } 161 162 /** 163 * Destroys the bluetooth pairing controller. 164 */ destroyBluetoothPairController()165 public void destroyBluetoothPairController() { 166 } 167 168 /** 169 * Notify manager the pairing has finished. 170 */ notifyPairingProcessDone(boolean success, String address, DiscoveryItem item)171 public void notifyPairingProcessDone(boolean success, String address, DiscoveryItem item) { 172 } 173 174 /** 175 * Gets the package name of HalfSheet.apk 176 * getHalfSheetApkPkgName may invoke PackageManager multiple times and it does not have 177 * race condition check. Since there is no lock for mHalfSheetApkPkgName. 178 */ getHalfSheetApkPkgName()179 String getHalfSheetApkPkgName() { 180 if (mHalfSheetApkPkgName != null) { 181 return mHalfSheetApkPkgName; 182 } 183 List<ResolveInfo> resolveInfos = mLocatorContextWrapper 184 .getPackageManager().queryIntentActivities( 185 new Intent(ACTION_RESOURCES_APK), 186 PackageManager.MATCH_SYSTEM_ONLY); 187 188 // remove apps that don't live in the nearby apex 189 resolveInfos.removeIf(info -> 190 !Environment.isAppInNearbyApex(info.activityInfo.applicationInfo)); 191 192 if (resolveInfos.isEmpty()) { 193 // Resource APK not loaded yet, print a stack trace to see where this is called from 194 Log.e("FastPairManager", "Attempted to fetch resources before halfsheet " 195 + " APK is installed or package manager can't resolve correctly!", 196 new IllegalStateException()); 197 return null; 198 } 199 200 if (resolveInfos.size() > 1) { 201 // multiple apps found, log a warning, but continue 202 Log.w("FastPairManager", "Found > 1 APK that can resolve halfsheet APK intent: " 203 + resolveInfos.stream() 204 .map(info -> info.activityInfo.applicationInfo.packageName) 205 .collect(Collectors.joining(", "))); 206 } 207 208 // Assume the first ResolveInfo is the one we're looking for 209 ResolveInfo info = resolveInfos.get(0); 210 mHalfSheetApkPkgName = info.activityInfo.applicationInfo.packageName; 211 Log.i("FastPairManager", "Found halfsheet APK at: " + mHalfSheetApkPkgName); 212 return mHalfSheetApkPkgName; 213 } 214 } 215