1 /* 2 * Copyright (C) 2011 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.settings.bluetooth; 18 19 import static android.os.Process.BLUETOOTH_UID; 20 21 import android.app.settings.SettingsEnums; 22 import android.bluetooth.BluetoothDevice; 23 import android.bluetooth.BluetoothProfile; 24 import android.content.Context; 25 import android.content.DialogInterface; 26 import android.content.pm.ActivityInfo; 27 import android.content.pm.PackageInfo; 28 import android.content.pm.PackageManager; 29 import android.content.pm.PackageManager.NameNotFoundException; 30 import android.os.UserHandle; 31 import android.provider.DeviceConfig; 32 import android.provider.Settings; 33 import android.text.TextUtils; 34 import android.util.Log; 35 import android.widget.Toast; 36 37 import androidx.annotation.NonNull; 38 import androidx.annotation.VisibleForTesting; 39 import androidx.appcompat.app.AlertDialog; 40 41 import com.android.settings.R; 42 import com.android.settings.core.SettingsUIDeviceConfig; 43 import com.android.settings.overlay.FeatureFactory; 44 import com.android.settingslib.bluetooth.BluetoothUtils; 45 import com.android.settingslib.bluetooth.BluetoothUtils.ErrorListener; 46 import com.android.settingslib.bluetooth.LocalBluetoothManager; 47 import com.android.settingslib.bluetooth.LocalBluetoothManager.BluetoothManagerCallback; 48 49 /** 50 * Utils is a helper class that contains constants for various 51 * Android resource IDs, debug logging flags, and static methods 52 * for creating dialogs. 53 */ 54 public final class Utils { 55 56 private static final String TAG = "BluetoothUtils"; 57 58 static final boolean V = BluetoothUtils.V; // verbose logging 59 static final boolean D = BluetoothUtils.D; // regular logging 60 Utils()61 private Utils() { 62 } 63 getConnectionStateSummary(int connectionState)64 public static int getConnectionStateSummary(int connectionState) { 65 switch (connectionState) { 66 case BluetoothProfile.STATE_CONNECTED: 67 return R.string.bluetooth_connected; 68 case BluetoothProfile.STATE_CONNECTING: 69 return R.string.bluetooth_connecting; 70 case BluetoothProfile.STATE_DISCONNECTED: 71 return R.string.bluetooth_disconnected; 72 case BluetoothProfile.STATE_DISCONNECTING: 73 return R.string.bluetooth_disconnecting; 74 default: 75 return 0; 76 } 77 } 78 79 // Create (or recycle existing) and show disconnect dialog. showDisconnectDialog(Context context, AlertDialog dialog, DialogInterface.OnClickListener disconnectListener, CharSequence title, CharSequence message)80 static AlertDialog showDisconnectDialog(Context context, 81 AlertDialog dialog, 82 DialogInterface.OnClickListener disconnectListener, 83 CharSequence title, CharSequence message) { 84 if (dialog == null) { 85 dialog = new AlertDialog.Builder(context) 86 .setPositiveButton(android.R.string.ok, disconnectListener) 87 .setNegativeButton(android.R.string.cancel, null) 88 .create(); 89 } else { 90 if (dialog.isShowing()) { 91 dialog.dismiss(); 92 } 93 // use disconnectListener for the correct profile(s) 94 CharSequence okText = context.getText(android.R.string.ok); 95 dialog.setButton(DialogInterface.BUTTON_POSITIVE, 96 okText, disconnectListener); 97 } 98 dialog.setTitle(title); 99 dialog.setMessage(message); 100 dialog.show(); 101 return dialog; 102 } 103 104 @VisibleForTesting showConnectingError(Context context, String name, LocalBluetoothManager manager)105 static void showConnectingError(Context context, String name, LocalBluetoothManager manager) { 106 FeatureFactory.getFactory(context).getMetricsFeatureProvider().visible(context, 107 SettingsEnums.PAGE_UNKNOWN, SettingsEnums.ACTION_SETTINGS_BLUETOOTH_CONNECT_ERROR, 108 0); 109 showError(context, name, R.string.bluetooth_connecting_error_message, manager); 110 } 111 showError(Context context, String name, int messageResId)112 static void showError(Context context, String name, int messageResId) { 113 showError(context, name, messageResId, getLocalBtManager(context)); 114 } 115 showError(Context context, String name, int messageResId, LocalBluetoothManager manager)116 private static void showError(Context context, String name, int messageResId, 117 LocalBluetoothManager manager) { 118 String message = context.getString(messageResId, name); 119 Context activity = manager.getForegroundActivity(); 120 if (manager.isForegroundActivity()) { 121 try { 122 new AlertDialog.Builder(activity) 123 .setTitle(R.string.bluetooth_error_title) 124 .setMessage(message) 125 .setPositiveButton(android.R.string.ok, null) 126 .show(); 127 } catch (Exception e) { 128 Log.e(TAG, "Cannot show error dialog.", e); 129 } 130 } else { 131 Toast.makeText(context, message, Toast.LENGTH_SHORT).show(); 132 } 133 } 134 getLocalBtManager(Context context)135 public static LocalBluetoothManager getLocalBtManager(Context context) { 136 return LocalBluetoothManager.getInstance(context, mOnInitCallback); 137 } 138 createRemoteName(Context context, BluetoothDevice device)139 public static String createRemoteName(Context context, BluetoothDevice device) { 140 String mRemoteName = device != null ? device.getAlias() : null; 141 142 if (mRemoteName == null) { 143 mRemoteName = context.getString(R.string.unknown); 144 } 145 return mRemoteName; 146 } 147 148 private static final ErrorListener mErrorListener = new ErrorListener() { 149 @Override 150 public void onShowError(Context context, String name, int messageResId) { 151 showError(context, name, messageResId); 152 } 153 }; 154 155 private static final BluetoothManagerCallback mOnInitCallback = new BluetoothManagerCallback() { 156 @Override 157 public void onBluetoothManagerInitialized(Context appContext, 158 LocalBluetoothManager bluetoothManager) { 159 BluetoothUtils.setErrorListener(mErrorListener); 160 } 161 }; 162 isBluetoothScanningEnabled(Context context)163 public static boolean isBluetoothScanningEnabled(Context context) { 164 return Settings.Global.getInt(context.getContentResolver(), 165 Settings.Global.BLE_SCAN_ALWAYS_AVAILABLE, 0) == 1; 166 } 167 168 /** 169 * Check if the Bluetooth device supports advanced details header 170 * 171 * @param bluetoothDevice the BluetoothDevice to get metadata 172 * @return true if it supports advanced details header, false otherwise. 173 */ isAdvancedDetailsHeader(@onNull BluetoothDevice bluetoothDevice)174 public static boolean isAdvancedDetailsHeader(@NonNull BluetoothDevice bluetoothDevice) { 175 final boolean advancedEnabled = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SETTINGS_UI, 176 SettingsUIDeviceConfig.BT_ADVANCED_HEADER_ENABLED, true); 177 if (!advancedEnabled) { 178 Log.d(TAG, "isAdvancedDetailsHeader: advancedEnabled is false"); 179 return false; 180 } 181 // The metadata is for Android R 182 final boolean untetheredHeadset = BluetoothUtils.getBooleanMetaData(bluetoothDevice, 183 BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET); 184 if (untetheredHeadset) { 185 Log.d(TAG, "isAdvancedDetailsHeader: untetheredHeadset is true"); 186 return true; 187 } 188 // The metadata is for Android S 189 final String deviceType = BluetoothUtils.getStringMetaData(bluetoothDevice, 190 BluetoothDevice.METADATA_DEVICE_TYPE); 191 if (TextUtils.equals(deviceType, BluetoothDevice.DEVICE_TYPE_UNTETHERED_HEADSET) 192 || TextUtils.equals(deviceType, BluetoothDevice.DEVICE_TYPE_WATCH) 193 || TextUtils.equals(deviceType, BluetoothDevice.DEVICE_TYPE_DEFAULT)) { 194 Log.d(TAG, "isAdvancedDetailsHeader: deviceType is " + deviceType); 195 return true; 196 } 197 return false; 198 } 199 200 /** 201 * Returns the Bluetooth Package name 202 */ findBluetoothPackageName(Context context)203 public static String findBluetoothPackageName(Context context) 204 throws NameNotFoundException { 205 // this activity will always be in the package where the rest of Bluetooth lives 206 final String sentinelActivity = "com.android.bluetooth.opp.BluetoothOppLauncherActivity"; 207 PackageManager packageManager = context.createContextAsUser(UserHandle.SYSTEM, 0) 208 .getPackageManager(); 209 String[] allPackages = packageManager.getPackagesForUid(BLUETOOTH_UID); 210 String matchedPackage = null; 211 for (String candidatePackage : allPackages) { 212 PackageInfo packageInfo; 213 try { 214 packageInfo = 215 packageManager.getPackageInfo( 216 candidatePackage, 217 PackageManager.GET_ACTIVITIES 218 | PackageManager.MATCH_ANY_USER 219 | PackageManager.MATCH_UNINSTALLED_PACKAGES 220 | PackageManager.MATCH_DISABLED_COMPONENTS); 221 } catch (NameNotFoundException e) { 222 // rethrow 223 throw e; 224 } 225 if (packageInfo.activities == null) { 226 continue; 227 } 228 for (ActivityInfo activity : packageInfo.activities) { 229 if (sentinelActivity.equals(activity.name)) { 230 if (matchedPackage == null) { 231 matchedPackage = candidatePackage; 232 } else { 233 throw new NameNotFoundException("multiple main bluetooth packages found"); 234 } 235 } 236 } 237 } 238 if (matchedPackage != null) { 239 return matchedPackage; 240 } 241 throw new NameNotFoundException("Could not find main bluetooth package"); 242 } 243 } 244