• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 public 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 
72     public static final String BLUETOOTH_SHOW_DEVICES_WITHOUT_NAMES_PROPERTY =
73             "persist.bluetooth.showdeviceswithoutnames";
74 
BluetoothUtils()75     private BluetoothUtils() {
76     }
77 
showError(Context context, String name, int messageResId)78     static void showError(Context context, String name, int messageResId) {
79         showError(context, name, messageResId, getLocalBtManager(context));
80     }
81 
showError(Context context, String name, int messageResId, LocalBluetoothManager manager)82     private static void showError(Context context, String name, int messageResId,
83             LocalBluetoothManager manager) {
84         String message = context.getString(messageResId, name);
85         Context activity = manager.getForegroundActivity();
86         if (manager.isForegroundActivity()) {
87             new AlertDialogBuilder(activity)
88                     .setTitle(R.string.bluetooth_error_title)
89                     .setMessage(message)
90                     .setPositiveButton(android.R.string.ok, null)
91                     .create()
92                     .show();
93         } else {
94             Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
95         }
96     }
97 
getSharedPreferences(Context context)98     private static SharedPreferences getSharedPreferences(Context context) {
99         return context.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
100     }
101 
getDiscoverableEndTimestamp(Context context)102     static long getDiscoverableEndTimestamp(Context context) {
103         return getSharedPreferences(context).getLong(
104                 KEY_DISCOVERABLE_END_TIMESTAMP, 0);
105     }
106 
shouldShowDialogInForeground(Context context, String deviceAddress, String deviceName)107     static boolean shouldShowDialogInForeground(Context context,
108             String deviceAddress, String deviceName) {
109         LocalBluetoothManager manager = getLocalBtManager(context);
110         if (manager == null) {
111             LOG.v("manager == null - do not show dialog.");
112             return false;
113         }
114 
115         // If Bluetooth Settings is visible
116         if (manager.isForegroundActivity()) {
117             return true;
118         }
119 
120         // If in appliance mode, do not show dialog in foreground.
121         if ((context.getResources().getConfiguration().uiMode &
122                 Configuration.UI_MODE_TYPE_APPLIANCE) == Configuration.UI_MODE_TYPE_APPLIANCE) {
123             LOG.v("in appliance mode - do not show dialog.");
124             return false;
125         }
126 
127         long currentTimeMillis = System.currentTimeMillis();
128         SharedPreferences sharedPreferences = getSharedPreferences(context);
129 
130         // If the device was in discoverABLE mode recently
131         long lastDiscoverableEndTime = sharedPreferences.getLong(
132                 KEY_DISCOVERABLE_END_TIMESTAMP, 0);
133         if ((lastDiscoverableEndTime + GRACE_PERIOD_TO_SHOW_DIALOGS_IN_FOREGROUND)
134                 > currentTimeMillis) {
135             return true;
136         }
137 
138         // If the device was discoverING recently
139         LocalBluetoothAdapter adapter = manager.getBluetoothAdapter();
140         if (adapter != null) {
141             if (adapter.isDiscovering()) {
142                 return true;
143             }
144             if ((adapter.getDiscoveryEndMillis() +
145                     GRACE_PERIOD_TO_SHOW_DIALOGS_IN_FOREGROUND) > currentTimeMillis) {
146                 return true;
147             }
148         }
149 
150         // If the device was picked in the device picker recently
151         if (deviceAddress != null) {
152             String lastSelectedDevice = sharedPreferences.getString(
153                     KEY_LAST_SELECTED_DEVICE, null);
154 
155             if (deviceAddress.equals(lastSelectedDevice)) {
156                 long lastDeviceSelectedTime = sharedPreferences.getLong(
157                         KEY_LAST_SELECTED_DEVICE_TIME, 0);
158                 if ((lastDeviceSelectedTime + GRACE_PERIOD_TO_SHOW_DIALOGS_IN_FOREGROUND)
159                         > currentTimeMillis) {
160                     return true;
161                 }
162             }
163         }
164 
165 
166         if (!TextUtils.isEmpty(deviceName)) {
167             // If the device is a custom BT keyboard specifically for this device
168             String packagedKeyboardName = context.getString(
169                     com.android.internal.R.string.config_packagedKeyboardName);
170             if (deviceName.equals(packagedKeyboardName)) {
171                 LOG.v("showing dialog for packaged keyboard");
172                 return true;
173             }
174         }
175 
176         LOG.v("Found no reason to show the dialog - do not show dialog.");
177         return false;
178     }
179 
getAvailabilityStatusRestricted(Context context)180     static int getAvailabilityStatusRestricted(Context context) {
181         if (hasUserRestrictionByUm(context, DISALLOW_CONFIG_BLUETOOTH)) {
182             return DISABLED_FOR_PROFILE;
183         }
184         if (hasUserRestrictionByDpm(context, DISALLOW_CONFIG_BLUETOOTH)) {
185             return AVAILABLE_FOR_VIEWING;
186         }
187         return AVAILABLE;
188     }
189 
onClickWhileDisabled(Context context, FragmentController fragmentController)190     static void onClickWhileDisabled(Context context, FragmentController fragmentController) {
191 
192         if (hasUserRestrictionByDpm(context, DISALLOW_CONFIG_BLUETOOTH)) {
193             showActionDisabledByAdminDialog(context, fragmentController);
194         } else {
195             showActionUnavailableToast(context);
196         }
197     }
198 
showActionDisabledByAdminDialog(Context context, FragmentController fragmentController)199     static void showActionDisabledByAdminDialog(Context context,
200                 FragmentController fragmentController) {
201         fragmentController.showDialog(
202                 EnterpriseUtils.getActionDisabledByAdminDialog(context, DISALLOW_CONFIG_BLUETOOTH),
203                 DISABLED_BY_ADMIN_CONFIRM_DIALOG_TAG);
204     }
205 
showActionUnavailableToast(Context context)206     static void showActionUnavailableToast(Context context) {
207         Toast.makeText(context, context.getString(R.string.action_unavailable),
208                 Toast.LENGTH_LONG).show();
209         LOG.d(context.getString(R.string.action_unavailable));
210     }
211 
persistSelectedDeviceInPicker(Context context, String deviceAddress)212     static void persistSelectedDeviceInPicker(Context context, String deviceAddress) {
213         SharedPreferences.Editor editor = getSharedPreferences(context).edit();
214         editor.putString(KEY_LAST_SELECTED_DEVICE, deviceAddress);
215         editor.putLong(KEY_LAST_SELECTED_DEVICE_TIME, System.currentTimeMillis());
216         editor.apply();
217     }
218 
persistDiscoverableEndTimestamp(Context context, long endTimestamp)219     static void persistDiscoverableEndTimestamp(Context context, long endTimestamp) {
220         SharedPreferences.Editor editor = getSharedPreferences(context).edit();
221         editor.putLong(KEY_DISCOVERABLE_END_TIMESTAMP, endTimestamp);
222         editor.apply();
223     }
224 
getLocalBtManager(Context context)225     public static LocalBluetoothManager getLocalBtManager(Context context) {
226         return LocalBluetoothManager.getInstance(context, mOnInitCallback);
227     }
228 
229     /**
230      * Determines whether to enable bluetooth scanning or not depending on the calling package. The
231      * calling package should be Settings or SystemUi.
232      *
233      * @param context The context to call
234      * @param callingPackageName The package name of the calling activity
235      * @return Whether bluetooth scanning should be enabled
236      */
shouldEnableBTScanning(Context context, String callingPackageName)237     public static boolean shouldEnableBTScanning(Context context, String callingPackageName) {
238         // Find Settings package name
239         String settingsPackageName = context.getPackageName();
240 
241         // Find SystemUi package name
242         String systemUiPackageName;
243         String flattenName = context.getResources()
244                 .getString(com.android.internal.R.string.config_systemUIServiceComponent);
245         if (TextUtils.isEmpty(flattenName)) {
246             throw new IllegalStateException("No "
247                     + "com.android.internal.R.string.config_systemUIServiceComponent resource");
248         }
249         try {
250             ComponentName componentName = ComponentName.unflattenFromString(flattenName);
251             systemUiPackageName =  componentName.getPackageName();
252         } catch (RuntimeException e) {
253             throw new IllegalStateException("Invalid component name defined by "
254                     + "com.android.internal.R.string.config_systemUIServiceComponent resource: "
255                     + flattenName);
256         }
257 
258         return TextUtils.equals(callingPackageName, settingsPackageName)
259                 || TextUtils.equals(callingPackageName, systemUiPackageName);
260     }
261 }
262