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