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