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.Context; 20 import android.content.SharedPreferences; 21 import android.content.res.Configuration; 22 import android.text.TextUtils; 23 import android.widget.Toast; 24 25 import com.android.car.settings.R; 26 import com.android.car.settings.common.Logger; 27 import com.android.car.ui.AlertDialogBuilder; 28 import com.android.settingslib.bluetooth.LocalBluetoothAdapter; 29 import com.android.settingslib.bluetooth.LocalBluetoothManager; 30 import com.android.settingslib.bluetooth.LocalBluetoothManager.BluetoothManagerCallback; 31 32 /** 33 * BluetoothUtils provides an interface to the preferences 34 * related to Bluetooth. 35 */ 36 final class BluetoothUtils { 37 private static final Logger LOG = new Logger(BluetoothUtils.class); 38 private static final String SHARED_PREFERENCES_NAME = "bluetooth_settings"; 39 40 private static final BluetoothManagerCallback mOnInitCallback = new BluetoothManagerCallback() { 41 @Override 42 public void onBluetoothManagerInitialized(Context appContext, 43 LocalBluetoothManager bluetoothManager) { 44 com.android.settingslib.bluetooth.BluetoothUtils.setErrorListener( 45 com.android.car.settings.bluetooth.BluetoothUtils::showError); 46 } 47 }; 48 49 // If a device was picked from the device picker or was in discoverable mode 50 // in the last 60 seconds, show the pairing dialogs in foreground instead 51 // of raising notifications 52 private static final int GRACE_PERIOD_TO_SHOW_DIALOGS_IN_FOREGROUND = 60 * 1000; 53 54 private static final String KEY_LAST_SELECTED_DEVICE = "last_selected_device"; 55 56 private static final String KEY_LAST_SELECTED_DEVICE_TIME = "last_selected_device_time"; 57 58 private static final String KEY_DISCOVERABLE_END_TIMESTAMP = "discoverable_end_timestamp"; 59 BluetoothUtils()60 private BluetoothUtils() { 61 } 62 showError(Context context, String name, int messageResId)63 static void showError(Context context, String name, int messageResId) { 64 showError(context, name, messageResId, getLocalBtManager(context)); 65 } 66 showError(Context context, String name, int messageResId, LocalBluetoothManager manager)67 private static void showError(Context context, String name, int messageResId, 68 LocalBluetoothManager manager) { 69 String message = context.getString(messageResId, name); 70 Context activity = manager.getForegroundActivity(); 71 if (manager.isForegroundActivity()) { 72 new AlertDialogBuilder(activity) 73 .setTitle(R.string.bluetooth_error_title) 74 .setMessage(message) 75 .setPositiveButton(android.R.string.ok, null) 76 .create() 77 .show(); 78 } else { 79 Toast.makeText(context, message, Toast.LENGTH_SHORT).show(); 80 } 81 } 82 getSharedPreferences(Context context)83 private static SharedPreferences getSharedPreferences(Context context) { 84 return context.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE); 85 } 86 getDiscoverableEndTimestamp(Context context)87 static long getDiscoverableEndTimestamp(Context context) { 88 return getSharedPreferences(context).getLong( 89 KEY_DISCOVERABLE_END_TIMESTAMP, 0); 90 } 91 shouldShowDialogInForeground(Context context, String deviceAddress, String deviceName)92 static boolean shouldShowDialogInForeground(Context context, 93 String deviceAddress, String deviceName) { 94 LocalBluetoothManager manager = getLocalBtManager(context); 95 if (manager == null) { 96 LOG.v("manager == null - do not show dialog."); 97 return false; 98 } 99 100 // If Bluetooth Settings is visible 101 if (manager.isForegroundActivity()) { 102 return true; 103 } 104 105 // If in appliance mode, do not show dialog in foreground. 106 if ((context.getResources().getConfiguration().uiMode & 107 Configuration.UI_MODE_TYPE_APPLIANCE) == Configuration.UI_MODE_TYPE_APPLIANCE) { 108 LOG.v("in appliance mode - do not show dialog."); 109 return false; 110 } 111 112 long currentTimeMillis = System.currentTimeMillis(); 113 SharedPreferences sharedPreferences = getSharedPreferences(context); 114 115 // If the device was in discoverABLE mode recently 116 long lastDiscoverableEndTime = sharedPreferences.getLong( 117 KEY_DISCOVERABLE_END_TIMESTAMP, 0); 118 if ((lastDiscoverableEndTime + GRACE_PERIOD_TO_SHOW_DIALOGS_IN_FOREGROUND) 119 > currentTimeMillis) { 120 return true; 121 } 122 123 // If the device was discoverING recently 124 LocalBluetoothAdapter adapter = manager.getBluetoothAdapter(); 125 if (adapter != null) { 126 if (adapter.isDiscovering()) { 127 return true; 128 } 129 if ((adapter.getDiscoveryEndMillis() + 130 GRACE_PERIOD_TO_SHOW_DIALOGS_IN_FOREGROUND) > currentTimeMillis) { 131 return true; 132 } 133 } 134 135 // If the device was picked in the device picker recently 136 if (deviceAddress != null) { 137 String lastSelectedDevice = sharedPreferences.getString( 138 KEY_LAST_SELECTED_DEVICE, null); 139 140 if (deviceAddress.equals(lastSelectedDevice)) { 141 long lastDeviceSelectedTime = sharedPreferences.getLong( 142 KEY_LAST_SELECTED_DEVICE_TIME, 0); 143 if ((lastDeviceSelectedTime + GRACE_PERIOD_TO_SHOW_DIALOGS_IN_FOREGROUND) 144 > currentTimeMillis) { 145 return true; 146 } 147 } 148 } 149 150 151 if (!TextUtils.isEmpty(deviceName)) { 152 // If the device is a custom BT keyboard specifically for this device 153 String packagedKeyboardName = context.getString( 154 com.android.internal.R.string.config_packagedKeyboardName); 155 if (deviceName.equals(packagedKeyboardName)) { 156 LOG.v("showing dialog for packaged keyboard"); 157 return true; 158 } 159 } 160 161 LOG.v("Found no reason to show the dialog - do not show dialog."); 162 return false; 163 } 164 persistSelectedDeviceInPicker(Context context, String deviceAddress)165 static void persistSelectedDeviceInPicker(Context context, String deviceAddress) { 166 SharedPreferences.Editor editor = getSharedPreferences(context).edit(); 167 editor.putString(KEY_LAST_SELECTED_DEVICE, deviceAddress); 168 editor.putLong(KEY_LAST_SELECTED_DEVICE_TIME, System.currentTimeMillis()); 169 editor.apply(); 170 } 171 persistDiscoverableEndTimestamp(Context context, long endTimestamp)172 static void persistDiscoverableEndTimestamp(Context context, long endTimestamp) { 173 SharedPreferences.Editor editor = getSharedPreferences(context).edit(); 174 editor.putLong(KEY_DISCOVERABLE_END_TIMESTAMP, endTimestamp); 175 editor.apply(); 176 } 177 getLocalBtManager(Context context)178 public static LocalBluetoothManager getLocalBtManager(Context context) { 179 return LocalBluetoothManager.getInstance(context, mOnInitCallback); 180 } 181 } 182