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 package com.android.nearby.halfsheet.utils; 17 18 import static com.android.nearby.halfsheet.constants.UserActionHandlerBase.ACTION_FAST_PAIR; 19 import static com.android.nearby.halfsheet.constants.UserActionHandlerBase.EXTRA_COMPANION_APP; 20 21 import android.bluetooth.BluetoothAdapter; 22 import android.bluetooth.BluetoothDevice; 23 import android.bluetooth.BluetoothManager; 24 import android.content.Context; 25 import android.content.Intent; 26 import android.content.pm.PackageManager; 27 import android.util.Log; 28 29 import androidx.annotation.NonNull; 30 import androidx.annotation.Nullable; 31 32 import java.net.URISyntaxException; 33 34 import service.proto.Cache; 35 36 /** 37 * Util class in half sheet apk 38 */ 39 public class FastPairUtils { 40 41 /** FastPair util method check certain app is install on the device or not. */ isAppInstalled(Context context, String packageName)42 public static boolean isAppInstalled(Context context, String packageName) { 43 try { 44 context.getPackageManager().getPackageInfo(packageName, 0); 45 return true; 46 } catch (PackageManager.NameNotFoundException e) { 47 return false; 48 } 49 } 50 51 /** FastPair util method to properly format the action url extra. */ 52 @Nullable getCompanionAppFromActionUrl(String actionUrl)53 public static String getCompanionAppFromActionUrl(String actionUrl) { 54 try { 55 Intent intent = Intent.parseUri(actionUrl, Intent.URI_INTENT_SCHEME); 56 if (!intent.getAction().equals(ACTION_FAST_PAIR)) { 57 Log.e("FastPairUtils", "Companion app launch attempted from malformed action url"); 58 return null; 59 } 60 return intent.getStringExtra(EXTRA_COMPANION_APP); 61 } catch (URISyntaxException e) { 62 Log.e("FastPairUtils", "FastPair: fail to get companion app info from discovery item"); 63 return null; 64 } 65 } 66 67 /** 68 * Converts {@link service.proto.Cache.StoredDiscoveryItem} from 69 * {@link service.proto.Cache.ScanFastPairStoreItem} 70 */ convertFrom(Cache.ScanFastPairStoreItem item)71 public static Cache.StoredDiscoveryItem convertFrom(Cache.ScanFastPairStoreItem item) { 72 return convertFrom(item, /* isSubsequentPair= */ false); 73 } 74 75 /** 76 * Converts a {@link service.proto.Cache.ScanFastPairStoreItem} 77 * to a {@link service.proto.Cache.StoredDiscoveryItem}. 78 * 79 * <p>This is needed to make the new Fast Pair scanning stack compatible with the rest of the 80 * legacy Fast Pair code. 81 */ convertFrom( Cache.ScanFastPairStoreItem item, boolean isSubsequentPair)82 public static Cache.StoredDiscoveryItem convertFrom( 83 Cache.ScanFastPairStoreItem item, boolean isSubsequentPair) { 84 return Cache.StoredDiscoveryItem.newBuilder() 85 .setId(item.getModelId()) 86 .setFirstObservationTimestampMillis(item.getFirstObservationTimestampMillis()) 87 .setLastObservationTimestampMillis(item.getLastObservationTimestampMillis()) 88 .setActionUrl(item.getActionUrl()) 89 .setActionUrlType(Cache.ResolvedUrlType.APP) 90 .setTitle( 91 isSubsequentPair 92 ? item.getFastPairStrings().getTapToPairWithoutAccount() 93 : item.getDeviceName()) 94 .setMacAddress(item.getAddress()) 95 .setState(Cache.StoredDiscoveryItem.State.STATE_ENABLED) 96 .setTriggerId(item.getModelId()) 97 .setIconPng(item.getIconPng()) 98 .setIconFifeUrl(item.getIconFifeUrl()) 99 .setDescription( 100 isSubsequentPair 101 ? item.getDeviceName() 102 : item.getFastPairStrings().getTapToPairWithoutAccount()) 103 .setAuthenticationPublicKeySecp256R1(item.getAntiSpoofingPublicKey()) 104 .setCompanionDetail(item.getCompanionDetail()) 105 .setFastPairStrings(item.getFastPairStrings()) 106 .setFastPairInformation( 107 Cache.FastPairInformation.newBuilder() 108 .setDataOnlyConnection(item.getDataOnlyConnection()) 109 .setTrueWirelessImages(item.getTrueWirelessImages()) 110 .setAssistantSupported(item.getAssistantSupported()) 111 .setCompanyName(item.getCompanyName())) 112 .build(); 113 } 114 115 /** 116 * Returns true the application is installed and can be opened on device. 117 */ isLaunchable(@onNull Context context, String companionApp)118 public static boolean isLaunchable(@NonNull Context context, String companionApp) { 119 return isAppInstalled(context, companionApp) 120 && createCompanionAppIntent(context, companionApp, null) != null; 121 } 122 123 /** 124 * Returns an intent to launch given the package name and bluetooth address (if provided). 125 * Returns null if no such an intent can be found. 126 */ 127 @Nullable createCompanionAppIntent(@onNull Context context, String packageName, @Nullable String address)128 public static Intent createCompanionAppIntent(@NonNull Context context, String packageName, 129 @Nullable String address) { 130 Intent intent = context.getPackageManager().getLaunchIntentForPackage(packageName); 131 if (intent == null) { 132 return null; 133 } 134 if (address != null) { 135 BluetoothAdapter adapter = getBluetoothAdapter(context); 136 if (adapter != null) { 137 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, adapter.getRemoteDevice(address)); 138 } 139 } 140 return intent; 141 } 142 143 @Nullable getBluetoothAdapter(@onNull Context context)144 private static BluetoothAdapter getBluetoothAdapter(@NonNull Context context) { 145 BluetoothManager bluetoothManager = context.getSystemService(BluetoothManager.class); 146 return bluetoothManager == null ? null : bluetoothManager.getAdapter(); 147 } 148 FastPairUtils()149 private FastPairUtils() {} 150 } 151