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 android.app.settings.SettingsEnums; 20 import android.bluetooth.BluetoothDevice; 21 import android.bluetooth.BluetoothProfile; 22 import android.content.Context; 23 import android.content.DialogInterface; 24 import android.provider.DeviceConfig; 25 import android.provider.Settings; 26 import android.text.TextUtils; 27 import android.util.Log; 28 import android.widget.Toast; 29 30 import androidx.annotation.NonNull; 31 import androidx.annotation.VisibleForTesting; 32 import androidx.appcompat.app.AlertDialog; 33 34 import com.android.settings.R; 35 import com.android.settings.core.SettingsUIDeviceConfig; 36 import com.android.settings.overlay.FeatureFactory; 37 import com.android.settingslib.bluetooth.BluetoothUtils; 38 import com.android.settingslib.bluetooth.BluetoothUtils.ErrorListener; 39 import com.android.settingslib.bluetooth.LocalBluetoothManager; 40 import com.android.settingslib.bluetooth.LocalBluetoothManager.BluetoothManagerCallback; 41 42 /** 43 * Utils is a helper class that contains constants for various 44 * Android resource IDs, debug logging flags, and static methods 45 * for creating dialogs. 46 */ 47 public final class Utils { 48 49 private static final String TAG = "BluetoothUtils"; 50 51 static final boolean V = BluetoothUtils.V; // verbose logging 52 static final boolean D = BluetoothUtils.D; // regular logging 53 Utils()54 private Utils() { 55 } 56 getConnectionStateSummary(int connectionState)57 public static int getConnectionStateSummary(int connectionState) { 58 switch (connectionState) { 59 case BluetoothProfile.STATE_CONNECTED: 60 return R.string.bluetooth_connected; 61 case BluetoothProfile.STATE_CONNECTING: 62 return R.string.bluetooth_connecting; 63 case BluetoothProfile.STATE_DISCONNECTED: 64 return R.string.bluetooth_disconnected; 65 case BluetoothProfile.STATE_DISCONNECTING: 66 return R.string.bluetooth_disconnecting; 67 default: 68 return 0; 69 } 70 } 71 72 // Create (or recycle existing) and show disconnect dialog. showDisconnectDialog(Context context, AlertDialog dialog, DialogInterface.OnClickListener disconnectListener, CharSequence title, CharSequence message)73 static AlertDialog showDisconnectDialog(Context context, 74 AlertDialog dialog, 75 DialogInterface.OnClickListener disconnectListener, 76 CharSequence title, CharSequence message) { 77 if (dialog == null) { 78 dialog = new AlertDialog.Builder(context) 79 .setPositiveButton(android.R.string.ok, disconnectListener) 80 .setNegativeButton(android.R.string.cancel, null) 81 .create(); 82 } else { 83 if (dialog.isShowing()) { 84 dialog.dismiss(); 85 } 86 // use disconnectListener for the correct profile(s) 87 CharSequence okText = context.getText(android.R.string.ok); 88 dialog.setButton(DialogInterface.BUTTON_POSITIVE, 89 okText, disconnectListener); 90 } 91 dialog.setTitle(title); 92 dialog.setMessage(message); 93 dialog.show(); 94 return dialog; 95 } 96 97 @VisibleForTesting showConnectingError(Context context, String name, LocalBluetoothManager manager)98 static void showConnectingError(Context context, String name, LocalBluetoothManager manager) { 99 FeatureFactory.getFactory(context).getMetricsFeatureProvider().visible(context, 100 SettingsEnums.PAGE_UNKNOWN, SettingsEnums.ACTION_SETTINGS_BLUETOOTH_CONNECT_ERROR, 101 0); 102 showError(context, name, R.string.bluetooth_connecting_error_message, manager); 103 } 104 showError(Context context, String name, int messageResId)105 static void showError(Context context, String name, int messageResId) { 106 showError(context, name, messageResId, getLocalBtManager(context)); 107 } 108 showError(Context context, String name, int messageResId, LocalBluetoothManager manager)109 private static void showError(Context context, String name, int messageResId, 110 LocalBluetoothManager manager) { 111 String message = context.getString(messageResId, name); 112 Context activity = manager.getForegroundActivity(); 113 if (manager.isForegroundActivity()) { 114 try { 115 new AlertDialog.Builder(activity) 116 .setTitle(R.string.bluetooth_error_title) 117 .setMessage(message) 118 .setPositiveButton(android.R.string.ok, null) 119 .show(); 120 } catch (Exception e) { 121 Log.e(TAG, "Cannot show error dialog.", e); 122 } 123 } else { 124 Toast.makeText(context, message, Toast.LENGTH_SHORT).show(); 125 } 126 } 127 getLocalBtManager(Context context)128 public static LocalBluetoothManager getLocalBtManager(Context context) { 129 return LocalBluetoothManager.getInstance(context, mOnInitCallback); 130 } 131 createRemoteName(Context context, BluetoothDevice device)132 public static String createRemoteName(Context context, BluetoothDevice device) { 133 String mRemoteName = device != null ? device.getAlias() : null; 134 135 if (mRemoteName == null) { 136 mRemoteName = context.getString(R.string.unknown); 137 } 138 return mRemoteName; 139 } 140 141 private static final ErrorListener mErrorListener = new ErrorListener() { 142 @Override 143 public void onShowError(Context context, String name, int messageResId) { 144 showError(context, name, messageResId); 145 } 146 }; 147 148 private static final BluetoothManagerCallback mOnInitCallback = new BluetoothManagerCallback() { 149 @Override 150 public void onBluetoothManagerInitialized(Context appContext, 151 LocalBluetoothManager bluetoothManager) { 152 BluetoothUtils.setErrorListener(mErrorListener); 153 } 154 }; 155 isBluetoothScanningEnabled(Context context)156 public static boolean isBluetoothScanningEnabled(Context context) { 157 return Settings.Global.getInt(context.getContentResolver(), 158 Settings.Global.BLE_SCAN_ALWAYS_AVAILABLE, 0) == 1; 159 } 160 161 /** 162 * Check if the Bluetooth device supports advanced details header 163 * 164 * @param bluetoothDevice the BluetoothDevice to get metadata 165 * @return true if it supports advanced details header, false otherwise. 166 */ isAdvancedDetailsHeader(@onNull BluetoothDevice bluetoothDevice)167 public static boolean isAdvancedDetailsHeader(@NonNull BluetoothDevice bluetoothDevice) { 168 final boolean advancedEnabled = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SETTINGS_UI, 169 SettingsUIDeviceConfig.BT_ADVANCED_HEADER_ENABLED, true); 170 if (!advancedEnabled) { 171 Log.d(TAG, "isAdvancedDetailsHeader: advancedEnabled is false"); 172 return false; 173 } 174 // The metadata is for Android R 175 final boolean untetheredHeadset = BluetoothUtils.getBooleanMetaData(bluetoothDevice, 176 BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET); 177 if (untetheredHeadset) { 178 Log.d(TAG, "isAdvancedDetailsHeader: untetheredHeadset is true"); 179 return true; 180 } 181 // The metadata is for Android S 182 final String deviceType = BluetoothUtils.getStringMetaData(bluetoothDevice, 183 BluetoothDevice.METADATA_DEVICE_TYPE); 184 if (TextUtils.equals(deviceType, BluetoothDevice.DEVICE_TYPE_UNTETHERED_HEADSET) 185 || TextUtils.equals(deviceType, BluetoothDevice.DEVICE_TYPE_WATCH) 186 || TextUtils.equals(deviceType, BluetoothDevice.DEVICE_TYPE_DEFAULT)) { 187 Log.d(TAG, "isAdvancedDetailsHeader: deviceType is " + deviceType); 188 return true; 189 } 190 return false; 191 } 192 } 193