1 /* 2 * Copyright (C) 2017 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.car.settings.bluetooth; 18 19 import static android.os.UserManager.DISALLOW_CONFIG_BLUETOOTH; 20 21 import static com.android.car.settings.common.PreferenceController.AVAILABLE; 22 import static com.android.car.settings.common.PreferenceController.AVAILABLE_FOR_VIEWING; 23 import static com.android.car.settings.common.PreferenceController.DISABLED_FOR_PROFILE; 24 import static com.android.car.settings.enterprise.ActionDisabledByAdminDialogFragment.DISABLED_BY_ADMIN_CONFIRM_DIALOG_TAG; 25 import static com.android.car.settings.enterprise.EnterpriseUtils.hasUserRestrictionByDpm; 26 import static com.android.car.settings.enterprise.EnterpriseUtils.hasUserRestrictionByUm; 27 28 import android.content.ComponentName; 29 import android.content.Context; 30 import android.content.SharedPreferences; 31 import android.content.res.Configuration; 32 import android.text.TextUtils; 33 import android.widget.Toast; 34 35 import com.android.car.settings.R; 36 import com.android.car.settings.common.FragmentController; 37 import com.android.car.settings.common.Logger; 38 import com.android.car.settings.enterprise.EnterpriseUtils; 39 import com.android.car.ui.AlertDialogBuilder; 40 import com.android.settingslib.bluetooth.LocalBluetoothAdapter; 41 import com.android.settingslib.bluetooth.LocalBluetoothManager; 42 import com.android.settingslib.bluetooth.LocalBluetoothManager.BluetoothManagerCallback; 43 44 /** 45 * BluetoothUtils provides an interface to the preferences 46 * related to Bluetooth. 47 */ 48 final class BluetoothUtils { 49 private static final Logger LOG = new Logger(BluetoothUtils.class); 50 private static final String SHARED_PREFERENCES_NAME = "bluetooth_settings"; 51 52 private static final BluetoothManagerCallback mOnInitCallback = new BluetoothManagerCallback() { 53 @Override 54 public void onBluetoothManagerInitialized(Context appContext, 55 LocalBluetoothManager bluetoothManager) { 56 com.android.settingslib.bluetooth.BluetoothUtils.setErrorListener( 57 com.android.car.settings.bluetooth.BluetoothUtils::showError); 58 } 59 }; 60 61 // If a device was picked from the device picker or was in discoverable mode 62 // in the last 60 seconds, show the pairing dialogs in foreground instead 63 // of raising notifications 64 private static final int GRACE_PERIOD_TO_SHOW_DIALOGS_IN_FOREGROUND = 60 * 1000; 65 66 private static final String KEY_LAST_SELECTED_DEVICE = "last_selected_device"; 67 68 private static final String KEY_LAST_SELECTED_DEVICE_TIME = "last_selected_device_time"; 69 70 private static final String KEY_DISCOVERABLE_END_TIMESTAMP = "discoverable_end_timestamp"; 71 BluetoothUtils()72 private BluetoothUtils() { 73 } 74 showError(Context context, String name, int messageResId)75 static void showError(Context context, String name, int messageResId) { 76 showError(context, name, messageResId, getLocalBtManager(context)); 77 } 78 showError(Context context, String name, int messageResId, LocalBluetoothManager manager)79 private static void showError(Context context, String name, int messageResId, 80 LocalBluetoothManager manager) { 81 String message = context.getString(messageResId, name); 82 Context activity = manager.getForegroundActivity(); 83 if (manager.isForegroundActivity()) { 84 new AlertDialogBuilder(activity) 85 .setTitle(R.string.bluetooth_error_title) 86 .setMessage(message) 87 .setPositiveButton(android.R.string.ok, null) 88 .create() 89 .show(); 90 } else { 91 Toast.makeText(context, message, Toast.LENGTH_SHORT).show(); 92 } 93 } 94 getSharedPreferences(Context context)95 private static SharedPreferences getSharedPreferences(Context context) { 96 return context.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE); 97 } 98 getDiscoverableEndTimestamp(Context context)99 static long getDiscoverableEndTimestamp(Context context) { 100 return getSharedPreferences(context).getLong( 101 KEY_DISCOVERABLE_END_TIMESTAMP, 0); 102 } 103 shouldShowDialogInForeground(Context context, String deviceAddress, String deviceName)104 static boolean shouldShowDialogInForeground(Context context, 105 String deviceAddress, String deviceName) { 106 LocalBluetoothManager manager = getLocalBtManager(context); 107 if (manager == null) { 108 LOG.v("manager == null - do not show dialog."); 109 return false; 110 } 111 112 // If Bluetooth Settings is visible 113 if (manager.isForegroundActivity()) { 114 return true; 115 } 116 117 // If in appliance mode, do not show dialog in foreground. 118 if ((context.getResources().getConfiguration().uiMode & 119 Configuration.UI_MODE_TYPE_APPLIANCE) == Configuration.UI_MODE_TYPE_APPLIANCE) { 120 LOG.v("in appliance mode - do not show dialog."); 121 return false; 122 } 123 124 long currentTimeMillis = System.currentTimeMillis(); 125 SharedPreferences sharedPreferences = getSharedPreferences(context); 126 127 // If the device was in discoverABLE mode recently 128 long lastDiscoverableEndTime = sharedPreferences.getLong( 129 KEY_DISCOVERABLE_END_TIMESTAMP, 0); 130 if ((lastDiscoverableEndTime + GRACE_PERIOD_TO_SHOW_DIALOGS_IN_FOREGROUND) 131 > currentTimeMillis) { 132 return true; 133 } 134 135 // If the device was discoverING recently 136 LocalBluetoothAdapter adapter = manager.getBluetoothAdapter(); 137 if (adapter != null) { 138 if (adapter.isDiscovering()) { 139 return true; 140 } 141 if ((adapter.getDiscoveryEndMillis() + 142 GRACE_PERIOD_TO_SHOW_DIALOGS_IN_FOREGROUND) > currentTimeMillis) { 143 return true; 144 } 145 } 146 147 // If the device was picked in the device picker recently 148 if (deviceAddress != null) { 149 String lastSelectedDevice = sharedPreferences.getString( 150 KEY_LAST_SELECTED_DEVICE, null); 151 152 if (deviceAddress.equals(lastSelectedDevice)) { 153 long lastDeviceSelectedTime = sharedPreferences.getLong( 154 KEY_LAST_SELECTED_DEVICE_TIME, 0); 155 if ((lastDeviceSelectedTime + GRACE_PERIOD_TO_SHOW_DIALOGS_IN_FOREGROUND) 156 > currentTimeMillis) { 157 return true; 158 } 159 } 160 } 161 162 163 if (!TextUtils.isEmpty(deviceName)) { 164 // If the device is a custom BT keyboard specifically for this device 165 String packagedKeyboardName = context.getString( 166 com.android.internal.R.string.config_packagedKeyboardName); 167 if (deviceName.equals(packagedKeyboardName)) { 168 LOG.v("showing dialog for packaged keyboard"); 169 return true; 170 } 171 } 172 173 LOG.v("Found no reason to show the dialog - do not show dialog."); 174 return false; 175 } 176 getAvailabilityStatusRestricted(Context context)177 static int getAvailabilityStatusRestricted(Context context) { 178 if (hasUserRestrictionByUm(context, DISALLOW_CONFIG_BLUETOOTH)) { 179 return DISABLED_FOR_PROFILE; 180 } 181 if (hasUserRestrictionByDpm(context, DISALLOW_CONFIG_BLUETOOTH)) { 182 return AVAILABLE_FOR_VIEWING; 183 } 184 return AVAILABLE; 185 } 186 onClickWhileDisabled(Context context, FragmentController fragmentController)187 static void onClickWhileDisabled(Context context, FragmentController fragmentController) { 188 189 if (hasUserRestrictionByDpm(context, DISALLOW_CONFIG_BLUETOOTH)) { 190 showActionDisabledByAdminDialog(context, fragmentController); 191 } else { 192 showActionUnavailableToast(context); 193 } 194 } 195 showActionDisabledByAdminDialog(Context context, FragmentController fragmentController)196 static void showActionDisabledByAdminDialog(Context context, 197 FragmentController fragmentController) { 198 fragmentController.showDialog( 199 EnterpriseUtils.getActionDisabledByAdminDialog(context, DISALLOW_CONFIG_BLUETOOTH), 200 DISABLED_BY_ADMIN_CONFIRM_DIALOG_TAG); 201 } 202 showActionUnavailableToast(Context context)203 static void showActionUnavailableToast(Context context) { 204 Toast.makeText(context, context.getString(R.string.action_unavailable), 205 Toast.LENGTH_LONG).show(); 206 LOG.d(context.getString(R.string.action_unavailable)); 207 } 208 persistSelectedDeviceInPicker(Context context, String deviceAddress)209 static void persistSelectedDeviceInPicker(Context context, String deviceAddress) { 210 SharedPreferences.Editor editor = getSharedPreferences(context).edit(); 211 editor.putString(KEY_LAST_SELECTED_DEVICE, deviceAddress); 212 editor.putLong(KEY_LAST_SELECTED_DEVICE_TIME, System.currentTimeMillis()); 213 editor.apply(); 214 } 215 persistDiscoverableEndTimestamp(Context context, long endTimestamp)216 static void persistDiscoverableEndTimestamp(Context context, long endTimestamp) { 217 SharedPreferences.Editor editor = getSharedPreferences(context).edit(); 218 editor.putLong(KEY_DISCOVERABLE_END_TIMESTAMP, endTimestamp); 219 editor.apply(); 220 } 221 getLocalBtManager(Context context)222 public static LocalBluetoothManager getLocalBtManager(Context context) { 223 return LocalBluetoothManager.getInstance(context, mOnInitCallback); 224 } 225 226 /** 227 * Determines whether to enable bluetooth scanning or not depending on the calling package. The 228 * calling package should be Settings or SystemUi. 229 * 230 * @param context The context to call 231 * @param callingPackageName The package name of the calling activity 232 * @return Whether bluetooth scanning should be enabled 233 */ shouldEnableBTScanning(Context context, String callingPackageName)234 public static boolean shouldEnableBTScanning(Context context, String callingPackageName) { 235 // Find Settings package name 236 String settingsPackageName = context.getPackageName(); 237 238 // Find SystemUi package name 239 String systemUiPackageName; 240 String flattenName = context.getResources() 241 .getString(com.android.internal.R.string.config_systemUIServiceComponent); 242 if (TextUtils.isEmpty(flattenName)) { 243 throw new IllegalStateException("No " 244 + "com.android.internal.R.string.config_systemUIServiceComponent resource"); 245 } 246 try { 247 ComponentName componentName = ComponentName.unflattenFromString(flattenName); 248 systemUiPackageName = componentName.getPackageName(); 249 } catch (RuntimeException e) { 250 throw new IllegalStateException("Invalid component name defined by " 251 + "com.android.internal.R.string.config_systemUIServiceComponent resource: " 252 + flattenName); 253 } 254 255 return TextUtils.equals(callingPackageName, settingsPackageName) 256 || TextUtils.equals(callingPackageName, systemUiPackageName); 257 } 258 } 259