• 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.BluetoothCsipSetCoordinator;
23 import android.bluetooth.BluetoothDevice;
24 import android.bluetooth.BluetoothProfile;
25 import android.content.Context;
26 import android.content.DialogInterface;
27 import android.content.pm.ActivityInfo;
28 import android.content.pm.PackageInfo;
29 import android.content.pm.PackageManager;
30 import android.content.pm.PackageManager.NameNotFoundException;
31 import android.os.UserHandle;
32 import android.provider.Settings;
33 import android.util.Log;
34 import android.widget.Toast;
35 
36 import androidx.annotation.VisibleForTesting;
37 import androidx.appcompat.app.AlertDialog;
38 
39 import com.android.settings.R;
40 import com.android.settings.overlay.FeatureFactory;
41 import com.android.settingslib.bluetooth.BluetoothUtils;
42 import com.android.settingslib.bluetooth.BluetoothUtils.ErrorListener;
43 import com.android.settingslib.bluetooth.CachedBluetoothDevice;
44 import com.android.settingslib.bluetooth.LocalBluetoothManager;
45 import com.android.settingslib.bluetooth.LocalBluetoothManager.BluetoothManagerCallback;
46 
47 import java.util.ArrayList;
48 import java.util.List;
49 import java.util.concurrent.ExecutionException;
50 import java.util.concurrent.FutureTask;
51 
52 /**
53  * Utils is a helper class that contains constants for various
54  * Android resource IDs, debug logging flags, and static methods
55  * for creating dialogs.
56  */
57 public final class Utils {
58 
59     private static final String TAG = "BluetoothUtils";
60 
61     static final boolean V = BluetoothUtils.V; // verbose logging
62     static final boolean D = BluetoothUtils.D;  // regular logging
63 
Utils()64     private Utils() {
65     }
66 
getConnectionStateSummary(int connectionState)67     public static int getConnectionStateSummary(int connectionState) {
68         switch (connectionState) {
69             case BluetoothProfile.STATE_CONNECTED:
70                 return R.string.bluetooth_connected;
71             case BluetoothProfile.STATE_CONNECTING:
72                 return R.string.bluetooth_connecting;
73             case BluetoothProfile.STATE_DISCONNECTED:
74                 return R.string.bluetooth_disconnected;
75             case BluetoothProfile.STATE_DISCONNECTING:
76                 return R.string.bluetooth_disconnecting;
77             default:
78                 return 0;
79         }
80     }
81 
82     // Create (or recycle existing) and show disconnect dialog.
showDisconnectDialog(Context context, AlertDialog dialog, DialogInterface.OnClickListener disconnectListener, CharSequence title, CharSequence message)83     static AlertDialog showDisconnectDialog(Context context,
84             AlertDialog dialog,
85             DialogInterface.OnClickListener disconnectListener,
86             CharSequence title, CharSequence message) {
87         if (dialog == null) {
88             dialog = new AlertDialog.Builder(context)
89                     .setPositiveButton(android.R.string.ok, disconnectListener)
90                     .setNegativeButton(android.R.string.cancel, null)
91                     .create();
92         } else {
93             if (dialog.isShowing()) {
94                 dialog.dismiss();
95             }
96             // use disconnectListener for the correct profile(s)
97             CharSequence okText = context.getText(android.R.string.ok);
98             dialog.setButton(DialogInterface.BUTTON_POSITIVE,
99                     okText, disconnectListener);
100         }
101         dialog.setTitle(title);
102         dialog.setMessage(message);
103         dialog.show();
104         return dialog;
105     }
106 
107     @VisibleForTesting
showConnectingError(Context context, String name, LocalBluetoothManager manager)108     static void showConnectingError(Context context, String name, LocalBluetoothManager manager) {
109         FeatureFactory.getFactory(context).getMetricsFeatureProvider().visible(context,
110                 SettingsEnums.PAGE_UNKNOWN, SettingsEnums.ACTION_SETTINGS_BLUETOOTH_CONNECT_ERROR,
111                 0);
112         showError(context, name, R.string.bluetooth_connecting_error_message, manager);
113     }
114 
showError(Context context, String name, int messageResId)115     static void showError(Context context, String name, int messageResId) {
116         showError(context, name, messageResId, getLocalBtManager(context));
117     }
118 
showError(Context context, String name, int messageResId, LocalBluetoothManager manager)119     private static void showError(Context context, String name, int messageResId,
120             LocalBluetoothManager manager) {
121         String message = context.getString(messageResId, name);
122         Context activity = manager.getForegroundActivity();
123         if (manager.isForegroundActivity()) {
124             try {
125                 new AlertDialog.Builder(activity)
126                         .setTitle(R.string.bluetooth_error_title)
127                         .setMessage(message)
128                         .setPositiveButton(android.R.string.ok, null)
129                         .show();
130             } catch (Exception e) {
131                 Log.e(TAG, "Cannot show error dialog.", e);
132             }
133         } else {
134             Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
135         }
136     }
137 
getLocalBtManager(Context context)138     public static LocalBluetoothManager getLocalBtManager(Context context) {
139         return LocalBluetoothManager.getInstance(context, mOnInitCallback);
140     }
141 
142     /**
143      * Obtains a {@link LocalBluetoothManager}.
144      *
145      * To avoid StrictMode ThreadPolicy violation, will get it in another thread.
146      */
getLocalBluetoothManager(Context context)147     public static LocalBluetoothManager getLocalBluetoothManager(Context context) {
148         final FutureTask<LocalBluetoothManager> localBtManagerFutureTask = new FutureTask<>(
149                 // Avoid StrictMode ThreadPolicy violation
150                 () -> getLocalBtManager(context));
151         try {
152             localBtManagerFutureTask.run();
153             return localBtManagerFutureTask.get();
154         } catch (InterruptedException | ExecutionException e) {
155             Log.w(TAG, "Error getting LocalBluetoothManager.", e);
156             return null;
157         }
158     }
159 
createRemoteName(Context context, BluetoothDevice device)160     public static String createRemoteName(Context context, BluetoothDevice device) {
161         String mRemoteName = device != null ? device.getAlias() : null;
162 
163         if (mRemoteName == null) {
164             mRemoteName = context.getString(R.string.unknown);
165         }
166         return mRemoteName;
167     }
168 
169     private static final ErrorListener mErrorListener = new ErrorListener() {
170         @Override
171         public void onShowError(Context context, String name, int messageResId) {
172             showError(context, name, messageResId);
173         }
174     };
175 
176     private static final BluetoothManagerCallback mOnInitCallback = new BluetoothManagerCallback() {
177         @Override
178         public void onBluetoothManagerInitialized(Context appContext,
179                 LocalBluetoothManager bluetoothManager) {
180             BluetoothUtils.setErrorListener(mErrorListener);
181         }
182     };
183 
isBluetoothScanningEnabled(Context context)184     public static boolean isBluetoothScanningEnabled(Context context) {
185         return Settings.Global.getInt(context.getContentResolver(),
186                 Settings.Global.BLE_SCAN_ALWAYS_AVAILABLE, 0) == 1;
187     }
188 
189     /**
190      * Returns the Bluetooth Package name
191      */
findBluetoothPackageName(Context context)192     public static String findBluetoothPackageName(Context context)
193             throws NameNotFoundException {
194         // this activity will always be in the package where the rest of Bluetooth lives
195         final String sentinelActivity = "com.android.bluetooth.opp.BluetoothOppLauncherActivity";
196         PackageManager packageManager = context.createContextAsUser(UserHandle.SYSTEM, 0)
197                 .getPackageManager();
198         String[] allPackages = packageManager.getPackagesForUid(BLUETOOTH_UID);
199         String matchedPackage = null;
200         for (String candidatePackage : allPackages) {
201             PackageInfo packageInfo;
202             try {
203                 packageInfo =
204                         packageManager.getPackageInfo(
205                                 candidatePackage,
206                                 PackageManager.GET_ACTIVITIES
207                                         | PackageManager.MATCH_ANY_USER
208                                         | PackageManager.MATCH_UNINSTALLED_PACKAGES
209                                         | PackageManager.MATCH_DISABLED_COMPONENTS);
210             } catch (NameNotFoundException e) {
211                 // rethrow
212                 throw e;
213             }
214             if (packageInfo.activities == null) {
215                 continue;
216             }
217             for (ActivityInfo activity : packageInfo.activities) {
218                 if (sentinelActivity.equals(activity.name)) {
219                     if (matchedPackage == null) {
220                         matchedPackage = candidatePackage;
221                     } else {
222                         throw new NameNotFoundException("multiple main bluetooth packages found");
223                     }
224                 }
225             }
226         }
227         if (matchedPackage != null) {
228             return matchedPackage;
229         }
230         throw new NameNotFoundException("Could not find main bluetooth package");
231     }
232 
233     /**
234      * Returns all cachedBluetoothDevices with the same groupId.
235      * @param cachedBluetoothDevice The main cachedBluetoothDevice.
236      * @return all cachedBluetoothDevices with the same groupId.
237      */
getAllOfCachedBluetoothDevices( LocalBluetoothManager localBtMgr, CachedBluetoothDevice cachedBluetoothDevice)238     public static List<CachedBluetoothDevice> getAllOfCachedBluetoothDevices(
239             LocalBluetoothManager localBtMgr,
240             CachedBluetoothDevice cachedBluetoothDevice) {
241         List<CachedBluetoothDevice> cachedBluetoothDevices = new ArrayList<>();
242         if (cachedBluetoothDevice == null) {
243             Log.e(TAG, "getAllOfCachedBluetoothDevices: no cachedBluetoothDevice");
244             return cachedBluetoothDevices;
245         }
246         int deviceGroupId = cachedBluetoothDevice.getGroupId();
247         if (deviceGroupId == BluetoothCsipSetCoordinator.GROUP_ID_INVALID) {
248             cachedBluetoothDevices.add(cachedBluetoothDevice);
249             return cachedBluetoothDevices;
250         }
251 
252         if (localBtMgr == null) {
253             Log.e(TAG, "getAllOfCachedBluetoothDevices: no LocalBluetoothManager");
254             return cachedBluetoothDevices;
255         }
256         CachedBluetoothDevice mainDevice =
257                 localBtMgr.getCachedDeviceManager().getCachedDevicesCopy().stream()
258                         .filter(cachedDevice -> cachedDevice.getGroupId() == deviceGroupId)
259                         .findFirst().orElse(null);
260         if (mainDevice == null) {
261             Log.e(TAG, "getAllOfCachedBluetoothDevices: groupId = " + deviceGroupId
262                     + ", no main device.");
263             return cachedBluetoothDevices;
264         }
265         cachedBluetoothDevice = mainDevice;
266         cachedBluetoothDevices.add(cachedBluetoothDevice);
267         for (CachedBluetoothDevice member : cachedBluetoothDevice.getMemberDevice()) {
268             cachedBluetoothDevices.add(member);
269         }
270         Log.d(TAG, "getAllOfCachedBluetoothDevices: groupId = " + deviceGroupId
271                 + " , cachedBluetoothDevice = " + cachedBluetoothDevice
272                 + " , deviceList = " + cachedBluetoothDevices);
273         return cachedBluetoothDevices;
274     }
275 }
276