• 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 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