• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2011 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.settings.bluetooth;
18 
19 import static android.os.Process.BLUETOOTH_UID;
20 
21 import android.app.settings.SettingsEnums;
22 import android.bluetooth.BluetoothDevice;
23 import android.bluetooth.BluetoothProfile;
24 import android.content.Context;
25 import android.content.DialogInterface;
26 import android.content.pm.ActivityInfo;
27 import android.content.pm.PackageInfo;
28 import android.content.pm.PackageManager;
29 import android.content.pm.PackageManager.NameNotFoundException;
30 import android.os.UserHandle;
31 import android.provider.DeviceConfig;
32 import android.provider.Settings;
33 import android.text.TextUtils;
34 import android.util.Log;
35 import android.widget.Toast;
36 
37 import androidx.annotation.NonNull;
38 import androidx.annotation.VisibleForTesting;
39 import androidx.appcompat.app.AlertDialog;
40 
41 import com.android.settings.R;
42 import com.android.settings.core.SettingsUIDeviceConfig;
43 import com.android.settings.overlay.FeatureFactory;
44 import com.android.settingslib.bluetooth.BluetoothUtils;
45 import com.android.settingslib.bluetooth.BluetoothUtils.ErrorListener;
46 import com.android.settingslib.bluetooth.LocalBluetoothManager;
47 import com.android.settingslib.bluetooth.LocalBluetoothManager.BluetoothManagerCallback;
48 
49 /**
50  * Utils is a helper class that contains constants for various
51  * Android resource IDs, debug logging flags, and static methods
52  * for creating dialogs.
53  */
54 public final class Utils {
55 
56     private static final String TAG = "BluetoothUtils";
57 
58     static final boolean V = BluetoothUtils.V; // verbose logging
59     static final boolean D = BluetoothUtils.D;  // regular logging
60 
Utils()61     private Utils() {
62     }
63 
getConnectionStateSummary(int connectionState)64     public static int getConnectionStateSummary(int connectionState) {
65         switch (connectionState) {
66             case BluetoothProfile.STATE_CONNECTED:
67                 return R.string.bluetooth_connected;
68             case BluetoothProfile.STATE_CONNECTING:
69                 return R.string.bluetooth_connecting;
70             case BluetoothProfile.STATE_DISCONNECTED:
71                 return R.string.bluetooth_disconnected;
72             case BluetoothProfile.STATE_DISCONNECTING:
73                 return R.string.bluetooth_disconnecting;
74             default:
75                 return 0;
76         }
77     }
78 
79     // Create (or recycle existing) and show disconnect dialog.
showDisconnectDialog(Context context, AlertDialog dialog, DialogInterface.OnClickListener disconnectListener, CharSequence title, CharSequence message)80     static AlertDialog showDisconnectDialog(Context context,
81             AlertDialog dialog,
82             DialogInterface.OnClickListener disconnectListener,
83             CharSequence title, CharSequence message) {
84         if (dialog == null) {
85             dialog = new AlertDialog.Builder(context)
86                     .setPositiveButton(android.R.string.ok, disconnectListener)
87                     .setNegativeButton(android.R.string.cancel, null)
88                     .create();
89         } else {
90             if (dialog.isShowing()) {
91                 dialog.dismiss();
92             }
93             // use disconnectListener for the correct profile(s)
94             CharSequence okText = context.getText(android.R.string.ok);
95             dialog.setButton(DialogInterface.BUTTON_POSITIVE,
96                     okText, disconnectListener);
97         }
98         dialog.setTitle(title);
99         dialog.setMessage(message);
100         dialog.show();
101         return dialog;
102     }
103 
104     @VisibleForTesting
showConnectingError(Context context, String name, LocalBluetoothManager manager)105     static void showConnectingError(Context context, String name, LocalBluetoothManager manager) {
106         FeatureFactory.getFactory(context).getMetricsFeatureProvider().visible(context,
107                 SettingsEnums.PAGE_UNKNOWN, SettingsEnums.ACTION_SETTINGS_BLUETOOTH_CONNECT_ERROR,
108                 0);
109         showError(context, name, R.string.bluetooth_connecting_error_message, manager);
110     }
111 
showError(Context context, String name, int messageResId)112     static void showError(Context context, String name, int messageResId) {
113         showError(context, name, messageResId, getLocalBtManager(context));
114     }
115 
showError(Context context, String name, int messageResId, LocalBluetoothManager manager)116     private static void showError(Context context, String name, int messageResId,
117             LocalBluetoothManager manager) {
118         String message = context.getString(messageResId, name);
119         Context activity = manager.getForegroundActivity();
120         if (manager.isForegroundActivity()) {
121             try {
122                 new AlertDialog.Builder(activity)
123                         .setTitle(R.string.bluetooth_error_title)
124                         .setMessage(message)
125                         .setPositiveButton(android.R.string.ok, null)
126                         .show();
127             } catch (Exception e) {
128                 Log.e(TAG, "Cannot show error dialog.", e);
129             }
130         } else {
131             Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
132         }
133     }
134 
getLocalBtManager(Context context)135     public static LocalBluetoothManager getLocalBtManager(Context context) {
136         return LocalBluetoothManager.getInstance(context, mOnInitCallback);
137     }
138 
createRemoteName(Context context, BluetoothDevice device)139     public static String createRemoteName(Context context, BluetoothDevice device) {
140         String mRemoteName = device != null ? device.getAlias() : null;
141 
142         if (mRemoteName == null) {
143             mRemoteName = context.getString(R.string.unknown);
144         }
145         return mRemoteName;
146     }
147 
148     private static final ErrorListener mErrorListener = new ErrorListener() {
149         @Override
150         public void onShowError(Context context, String name, int messageResId) {
151             showError(context, name, messageResId);
152         }
153     };
154 
155     private static final BluetoothManagerCallback mOnInitCallback = new BluetoothManagerCallback() {
156         @Override
157         public void onBluetoothManagerInitialized(Context appContext,
158                 LocalBluetoothManager bluetoothManager) {
159             BluetoothUtils.setErrorListener(mErrorListener);
160         }
161     };
162 
isBluetoothScanningEnabled(Context context)163     public static boolean isBluetoothScanningEnabled(Context context) {
164         return Settings.Global.getInt(context.getContentResolver(),
165                 Settings.Global.BLE_SCAN_ALWAYS_AVAILABLE, 0) == 1;
166     }
167 
168     /**
169      * Check if the Bluetooth device supports advanced details header
170      *
171      * @param bluetoothDevice the BluetoothDevice to get metadata
172      * @return true if it supports advanced details header, false otherwise.
173      */
isAdvancedDetailsHeader(@onNull BluetoothDevice bluetoothDevice)174     public static boolean isAdvancedDetailsHeader(@NonNull BluetoothDevice bluetoothDevice) {
175         final boolean advancedEnabled = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SETTINGS_UI,
176                 SettingsUIDeviceConfig.BT_ADVANCED_HEADER_ENABLED, true);
177         if (!advancedEnabled) {
178             Log.d(TAG, "isAdvancedDetailsHeader: advancedEnabled is false");
179             return false;
180         }
181         // The metadata is for Android R
182         final boolean untetheredHeadset = BluetoothUtils.getBooleanMetaData(bluetoothDevice,
183                 BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET);
184         if (untetheredHeadset) {
185             Log.d(TAG, "isAdvancedDetailsHeader: untetheredHeadset is true");
186             return true;
187         }
188         // The metadata is for Android S
189         final String deviceType = BluetoothUtils.getStringMetaData(bluetoothDevice,
190                 BluetoothDevice.METADATA_DEVICE_TYPE);
191         if (TextUtils.equals(deviceType, BluetoothDevice.DEVICE_TYPE_UNTETHERED_HEADSET)
192                 || TextUtils.equals(deviceType, BluetoothDevice.DEVICE_TYPE_WATCH)
193                 || TextUtils.equals(deviceType, BluetoothDevice.DEVICE_TYPE_DEFAULT)) {
194             Log.d(TAG, "isAdvancedDetailsHeader: deviceType is " + deviceType);
195             return true;
196         }
197         return false;
198     }
199 
200     /**
201      * Returns the Bluetooth Package name
202      */
findBluetoothPackageName(Context context)203     public static String findBluetoothPackageName(Context context)
204             throws NameNotFoundException {
205         // this activity will always be in the package where the rest of Bluetooth lives
206         final String sentinelActivity = "com.android.bluetooth.opp.BluetoothOppLauncherActivity";
207         PackageManager packageManager = context.createContextAsUser(UserHandle.SYSTEM, 0)
208                 .getPackageManager();
209         String[] allPackages = packageManager.getPackagesForUid(BLUETOOTH_UID);
210         String matchedPackage = null;
211         for (String candidatePackage : allPackages) {
212             PackageInfo packageInfo;
213             try {
214                 packageInfo =
215                         packageManager.getPackageInfo(
216                                 candidatePackage,
217                                 PackageManager.GET_ACTIVITIES
218                                         | PackageManager.MATCH_ANY_USER
219                                         | PackageManager.MATCH_UNINSTALLED_PACKAGES
220                                         | PackageManager.MATCH_DISABLED_COMPONENTS);
221             } catch (NameNotFoundException e) {
222                 // rethrow
223                 throw e;
224             }
225             if (packageInfo.activities == null) {
226                 continue;
227             }
228             for (ActivityInfo activity : packageInfo.activities) {
229                 if (sentinelActivity.equals(activity.name)) {
230                     if (matchedPackage == null) {
231                         matchedPackage = candidatePackage;
232                     } else {
233                         throw new NameNotFoundException("multiple main bluetooth packages found");
234                     }
235                 }
236             }
237         }
238         if (matchedPackage != null) {
239             return matchedPackage;
240         }
241         throw new NameNotFoundException("Could not find main bluetooth package");
242     }
243 }
244