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.BluetoothCsipSetCoordinator; 23 import android.bluetooth.BluetoothDevice; 24 import android.bluetooth.BluetoothProfile; 25 import android.content.Context; 26 import android.content.DialogInterface; 27 import android.content.pm.ActivityInfo; 28 import android.content.pm.PackageInfo; 29 import android.content.pm.PackageManager; 30 import android.content.pm.PackageManager.NameNotFoundException; 31 import android.os.UserHandle; 32 import android.provider.Settings; 33 import android.util.Log; 34 import android.widget.Toast; 35 36 import androidx.annotation.VisibleForTesting; 37 import androidx.appcompat.app.AlertDialog; 38 39 import com.android.settings.R; 40 import com.android.settings.flags.Flags; 41 import com.android.settings.overlay.FeatureFactory; 42 import com.android.settingslib.bluetooth.BluetoothUtils; 43 import com.android.settingslib.bluetooth.BluetoothUtils.ErrorListener; 44 import com.android.settingslib.bluetooth.CachedBluetoothDevice; 45 import com.android.settingslib.bluetooth.LocalBluetoothManager; 46 import com.android.settingslib.bluetooth.LocalBluetoothManager.BluetoothManagerCallback; 47 import com.android.settingslib.utils.ThreadUtils; 48 49 import com.google.common.base.Supplier; 50 51 import java.util.ArrayList; 52 import java.util.List; 53 import java.util.concurrent.ExecutionException; 54 import java.util.concurrent.FutureTask; 55 56 /** 57 * Utils is a helper class that contains constants for various 58 * Android resource IDs, debug logging flags, and static methods 59 * for creating dialogs. 60 */ 61 public final class Utils { 62 63 private static final String TAG = "BluetoothUtils"; 64 65 static final boolean V = BluetoothUtils.V; // verbose logging 66 static final boolean D = BluetoothUtils.D; // regular logging 67 Utils()68 private Utils() { 69 } 70 getConnectionStateSummary(int connectionState)71 public static int getConnectionStateSummary(int connectionState) { 72 switch (connectionState) { 73 case BluetoothProfile.STATE_CONNECTED: 74 return com.android.settingslib.R.string.bluetooth_connected; 75 case BluetoothProfile.STATE_CONNECTING: 76 return com.android.settingslib.R.string.bluetooth_connecting; 77 case BluetoothProfile.STATE_DISCONNECTED: 78 return com.android.settingslib.R.string.bluetooth_disconnected; 79 case BluetoothProfile.STATE_DISCONNECTING: 80 return com.android.settingslib.R.string.bluetooth_disconnecting; 81 default: 82 return 0; 83 } 84 } 85 86 // Create (or recycle existing) and show disconnect dialog. showDisconnectDialog(Context context, AlertDialog dialog, DialogInterface.OnClickListener disconnectListener, CharSequence title, CharSequence message)87 static AlertDialog showDisconnectDialog(Context context, 88 AlertDialog dialog, 89 DialogInterface.OnClickListener disconnectListener, 90 CharSequence title, CharSequence message) { 91 if (dialog == null) { 92 dialog = new AlertDialog.Builder(context) 93 .setPositiveButton(android.R.string.ok, disconnectListener) 94 .setNegativeButton(android.R.string.cancel, null) 95 .create(); 96 } else { 97 if (dialog.isShowing()) { 98 dialog.dismiss(); 99 } 100 // use disconnectListener for the correct profile(s) 101 CharSequence okText = context.getText(android.R.string.ok); 102 dialog.setButton(DialogInterface.BUTTON_POSITIVE, 103 okText, disconnectListener); 104 } 105 dialog.setTitle(title); 106 dialog.setMessage(message); 107 dialog.show(); 108 return dialog; 109 } 110 111 @VisibleForTesting showConnectingError(Context context, String name, LocalBluetoothManager manager)112 static void showConnectingError(Context context, String name, LocalBluetoothManager manager) { 113 FeatureFactory.getFeatureFactory().getMetricsFeatureProvider().visible(context, 114 SettingsEnums.PAGE_UNKNOWN, SettingsEnums.ACTION_SETTINGS_BLUETOOTH_CONNECT_ERROR, 115 0); 116 showError(context, name, R.string.bluetooth_connecting_error_message, manager); 117 } 118 showError(Context context, String name, int messageResId)119 static void showError(Context context, String name, int messageResId) { 120 showError(context, name, messageResId, getLocalBtManager(context)); 121 } 122 showError(Context context, String name, int messageResId, LocalBluetoothManager manager)123 private static void showError(Context context, String name, int messageResId, 124 LocalBluetoothManager manager) { 125 String message = context.getString(messageResId, name); 126 Context activity = manager.getForegroundActivity(); 127 if (manager.isForegroundActivity()) { 128 try { 129 new AlertDialog.Builder(activity) 130 .setTitle(R.string.bluetooth_error_title) 131 .setMessage(message) 132 .setPositiveButton(android.R.string.ok, null) 133 .show(); 134 } catch (Exception e) { 135 Log.e(TAG, "Cannot show error dialog.", e); 136 } 137 } else { 138 Toast.makeText(context, message, Toast.LENGTH_SHORT).show(); 139 } 140 } 141 getLocalBtManager(Context context)142 public static LocalBluetoothManager getLocalBtManager(Context context) { 143 return LocalBluetoothManager.getInstance(context, mOnInitCallback); 144 } 145 146 /** 147 * Obtains a {@link LocalBluetoothManager}. 148 * 149 * To avoid StrictMode ThreadPolicy violation, will get it in another thread. 150 */ getLocalBluetoothManager(Context context)151 public static LocalBluetoothManager getLocalBluetoothManager(Context context) { 152 final FutureTask<LocalBluetoothManager> localBtManagerFutureTask = new FutureTask<>( 153 // Avoid StrictMode ThreadPolicy violation 154 () -> getLocalBtManager(context)); 155 try { 156 localBtManagerFutureTask.run(); 157 return localBtManagerFutureTask.get(); 158 } catch (InterruptedException | ExecutionException e) { 159 Log.w(TAG, "Error getting LocalBluetoothManager.", e); 160 return null; 161 } 162 } 163 createRemoteName(Context context, BluetoothDevice device)164 public static String createRemoteName(Context context, BluetoothDevice device) { 165 String mRemoteName = device != null ? device.getAlias() : null; 166 167 if (mRemoteName == null) { 168 mRemoteName = context.getString(R.string.unknown); 169 } 170 return mRemoteName; 171 } 172 173 private static final ErrorListener mErrorListener = new ErrorListener() { 174 @Override 175 public void onShowError(Context context, String name, int messageResId) { 176 showError(context, name, messageResId); 177 } 178 }; 179 180 private static final BluetoothManagerCallback mOnInitCallback = new BluetoothManagerCallback() { 181 @Override 182 public void onBluetoothManagerInitialized(Context appContext, 183 LocalBluetoothManager bluetoothManager) { 184 BluetoothUtils.setErrorListener(mErrorListener); 185 } 186 }; 187 isBluetoothScanningEnabled(Context context)188 public static boolean isBluetoothScanningEnabled(Context context) { 189 return Settings.Global.getInt(context.getContentResolver(), 190 Settings.Global.BLE_SCAN_ALWAYS_AVAILABLE, 0) == 1; 191 } 192 193 /** 194 * Returns the Bluetooth Package name 195 */ findBluetoothPackageName(Context context)196 public static String findBluetoothPackageName(Context context) 197 throws NameNotFoundException { 198 // this activity will always be in the package where the rest of Bluetooth lives 199 final String sentinelActivity = "com.android.bluetooth.opp.BluetoothOppLauncherActivity"; 200 PackageManager packageManager = context.createContextAsUser(UserHandle.SYSTEM, 0) 201 .getPackageManager(); 202 String[] allPackages = packageManager.getPackagesForUid(BLUETOOTH_UID); 203 String matchedPackage = null; 204 for (String candidatePackage : allPackages) { 205 PackageInfo packageInfo; 206 try { 207 packageInfo = 208 packageManager.getPackageInfo( 209 candidatePackage, 210 PackageManager.GET_ACTIVITIES 211 | PackageManager.MATCH_ANY_USER 212 | PackageManager.MATCH_UNINSTALLED_PACKAGES 213 | PackageManager.MATCH_DISABLED_COMPONENTS); 214 } catch (NameNotFoundException e) { 215 // rethrow 216 throw e; 217 } 218 if (packageInfo.activities == null) { 219 continue; 220 } 221 for (ActivityInfo activity : packageInfo.activities) { 222 if (sentinelActivity.equals(activity.name)) { 223 if (matchedPackage == null) { 224 matchedPackage = candidatePackage; 225 } else { 226 throw new NameNotFoundException("multiple main bluetooth packages found"); 227 } 228 } 229 } 230 } 231 if (matchedPackage != null) { 232 return matchedPackage; 233 } 234 throw new NameNotFoundException("Could not find main bluetooth package"); 235 } 236 237 /** 238 * Returns all cachedBluetoothDevices with the same groupId. 239 * @param cachedBluetoothDevice The main cachedBluetoothDevice. 240 * @return all cachedBluetoothDevices with the same groupId. 241 */ getAllOfCachedBluetoothDevices( LocalBluetoothManager localBtMgr, CachedBluetoothDevice cachedBluetoothDevice)242 public static List<CachedBluetoothDevice> getAllOfCachedBluetoothDevices( 243 LocalBluetoothManager localBtMgr, 244 CachedBluetoothDevice cachedBluetoothDevice) { 245 List<CachedBluetoothDevice> cachedBluetoothDevices = new ArrayList<>(); 246 if (cachedBluetoothDevice == null) { 247 Log.e(TAG, "getAllOfCachedBluetoothDevices: no cachedBluetoothDevice"); 248 return cachedBluetoothDevices; 249 } 250 int deviceGroupId = cachedBluetoothDevice.getGroupId(); 251 if (deviceGroupId == BluetoothCsipSetCoordinator.GROUP_ID_INVALID) { 252 cachedBluetoothDevices.add(cachedBluetoothDevice); 253 return cachedBluetoothDevices; 254 } 255 256 if (localBtMgr == null) { 257 Log.e(TAG, "getAllOfCachedBluetoothDevices: no LocalBluetoothManager"); 258 return cachedBluetoothDevices; 259 } 260 CachedBluetoothDevice mainDevice = 261 localBtMgr.getCachedDeviceManager().getCachedDevicesCopy().stream() 262 .filter(cachedDevice -> cachedDevice.getGroupId() == deviceGroupId) 263 .findFirst().orElse(null); 264 if (mainDevice == null) { 265 Log.e(TAG, "getAllOfCachedBluetoothDevices: groupId = " + deviceGroupId 266 + ", no main device."); 267 return cachedBluetoothDevices; 268 } 269 cachedBluetoothDevice = mainDevice; 270 cachedBluetoothDevices.add(cachedBluetoothDevice); 271 for (CachedBluetoothDevice member : cachedBluetoothDevice.getMemberDevice()) { 272 cachedBluetoothDevices.add(member); 273 } 274 Log.d(TAG, "getAllOfCachedBluetoothDevices: groupId = " + deviceGroupId 275 + " , cachedBluetoothDevice = " + cachedBluetoothDevice 276 + " , deviceList = " + cachedBluetoothDevices); 277 return cachedBluetoothDevices; 278 } 279 280 /** 281 * Preloads the values and run the Runnable afterwards. 282 * @param suppliers the value supplier, should be a memoized supplier 283 * @param runnable the runnable to be run after value is preloaded 284 */ preloadAndRun(List<Supplier<?>> suppliers, Runnable runnable)285 public static void preloadAndRun(List<Supplier<?>> suppliers, Runnable runnable) { 286 if (!Flags.enableOffloadBluetoothOperationsToBackgroundThread()) { 287 runnable.run(); 288 return; 289 } 290 ThreadUtils.postOnBackgroundThread(() -> { 291 for (Supplier<?> supplier : suppliers) { 292 supplier.get(); 293 } 294 ThreadUtils.postOnMainThread(runnable); 295 }); 296 } 297 } 298