• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2012 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.bluetooth;
18 
19 import android.app.ActivityManager;
20 import android.app.AppOpsManager;
21 import android.bluetooth.BluetoothAdapter;
22 import android.bluetooth.BluetoothDevice;
23 import android.content.Context;
24 import android.content.ContextWrapper;
25 import android.content.pm.PackageManager;
26 import android.content.pm.UserInfo;
27 import android.os.Binder;
28 import android.os.Build;
29 import android.os.ParcelUuid;
30 import android.os.Process;
31 import android.os.SystemProperties;
32 import android.os.UserHandle;
33 import android.os.UserManager;
34 import android.util.Log;
35 
36 import java.io.IOException;
37 import java.io.InputStream;
38 import java.io.OutputStream;
39 import java.nio.ByteBuffer;
40 import java.nio.ByteOrder;
41 import java.util.List;
42 import java.util.UUID;
43 import java.util.concurrent.TimeUnit;
44 
45 /**
46  * @hide
47  */
48 
49 public final class Utils {
50     private static final String TAG = "BluetoothUtils";
51     private static final int MICROS_PER_UNIT = 625;
52     private static final String PTS_TEST_MODE_PROPERTY = "persist.bluetooth.pts";
53 
54     static final int BD_ADDR_LEN = 6; // bytes
55     static final int BD_UUID_LEN = 16; // bytes
56 
getAddressStringFromByte(byte[] address)57     public static String getAddressStringFromByte(byte[] address) {
58         if (address == null || address.length != BD_ADDR_LEN) {
59             return null;
60         }
61 
62         return String.format("%02X:%02X:%02X:%02X:%02X:%02X", address[0], address[1], address[2],
63                 address[3], address[4], address[5]);
64     }
65 
getByteAddress(BluetoothDevice device)66     public static byte[] getByteAddress(BluetoothDevice device) {
67         return getBytesFromAddress(device.getAddress());
68     }
69 
getBytesFromAddress(String address)70     public static byte[] getBytesFromAddress(String address) {
71         int i, j = 0;
72         byte[] output = new byte[BD_ADDR_LEN];
73 
74         for (i = 0; i < address.length(); i++) {
75             if (address.charAt(i) != ':') {
76                 output[j] = (byte) Integer.parseInt(address.substring(i, i + 2), BD_UUID_LEN);
77                 j++;
78                 i++;
79             }
80         }
81 
82         return output;
83     }
84 
byteArrayToInt(byte[] valueBuf)85     public static int byteArrayToInt(byte[] valueBuf) {
86         return byteArrayToInt(valueBuf, 0);
87     }
88 
byteArrayToShort(byte[] valueBuf)89     public static short byteArrayToShort(byte[] valueBuf) {
90         ByteBuffer converter = ByteBuffer.wrap(valueBuf);
91         converter.order(ByteOrder.nativeOrder());
92         return converter.getShort();
93     }
94 
byteArrayToInt(byte[] valueBuf, int offset)95     public static int byteArrayToInt(byte[] valueBuf, int offset) {
96         ByteBuffer converter = ByteBuffer.wrap(valueBuf);
97         converter.order(ByteOrder.nativeOrder());
98         return converter.getInt(offset);
99     }
100 
byteArrayToString(byte[] valueBuf)101     public static String byteArrayToString(byte[] valueBuf) {
102         StringBuilder sb = new StringBuilder();
103         for (int idx = 0; idx < valueBuf.length; idx++) {
104             if (idx != 0) {
105                 sb.append(" ");
106             }
107             sb.append(String.format("%02x", valueBuf[idx]));
108         }
109         return sb.toString();
110     }
111 
intToByteArray(int value)112     public static byte[] intToByteArray(int value) {
113         ByteBuffer converter = ByteBuffer.allocate(4);
114         converter.order(ByteOrder.nativeOrder());
115         converter.putInt(value);
116         return converter.array();
117     }
118 
uuidToByteArray(ParcelUuid pUuid)119     public static byte[] uuidToByteArray(ParcelUuid pUuid) {
120         int length = BD_UUID_LEN;
121         ByteBuffer converter = ByteBuffer.allocate(length);
122         converter.order(ByteOrder.BIG_ENDIAN);
123         long msb, lsb;
124         UUID uuid = pUuid.getUuid();
125         msb = uuid.getMostSignificantBits();
126         lsb = uuid.getLeastSignificantBits();
127         converter.putLong(msb);
128         converter.putLong(8, lsb);
129         return converter.array();
130     }
131 
uuidsToByteArray(ParcelUuid[] uuids)132     public static byte[] uuidsToByteArray(ParcelUuid[] uuids) {
133         int length = uuids.length * BD_UUID_LEN;
134         ByteBuffer converter = ByteBuffer.allocate(length);
135         converter.order(ByteOrder.BIG_ENDIAN);
136         UUID uuid;
137         long msb, lsb;
138         for (int i = 0; i < uuids.length; i++) {
139             uuid = uuids[i].getUuid();
140             msb = uuid.getMostSignificantBits();
141             lsb = uuid.getLeastSignificantBits();
142             converter.putLong(i * BD_UUID_LEN, msb);
143             converter.putLong(i * BD_UUID_LEN + 8, lsb);
144         }
145         return converter.array();
146     }
147 
byteArrayToUuid(byte[] val)148     public static ParcelUuid[] byteArrayToUuid(byte[] val) {
149         int numUuids = val.length / BD_UUID_LEN;
150         ParcelUuid[] puuids = new ParcelUuid[numUuids];
151         UUID uuid;
152         int offset = 0;
153 
154         ByteBuffer converter = ByteBuffer.wrap(val);
155         converter.order(ByteOrder.BIG_ENDIAN);
156 
157         for (int i = 0; i < numUuids; i++) {
158             puuids[i] = new ParcelUuid(
159                     new UUID(converter.getLong(offset), converter.getLong(offset + 8)));
160             offset += BD_UUID_LEN;
161         }
162         return puuids;
163     }
164 
debugGetAdapterStateString(int state)165     public static String debugGetAdapterStateString(int state) {
166         switch (state) {
167             case BluetoothAdapter.STATE_OFF:
168                 return "STATE_OFF";
169             case BluetoothAdapter.STATE_ON:
170                 return "STATE_ON";
171             case BluetoothAdapter.STATE_TURNING_ON:
172                 return "STATE_TURNING_ON";
173             case BluetoothAdapter.STATE_TURNING_OFF:
174                 return "STATE_TURNING_OFF";
175             default:
176                 return "UNKNOWN";
177         }
178     }
179 
ellipsize(String s)180     public static String ellipsize(String s) {
181         // Only ellipsize release builds
182         if (!Build.TYPE.equals("user")) {
183             return s;
184         }
185         if (s == null) {
186             return null;
187         }
188         if (s.length() < 3) {
189             return s;
190         }
191         return s.charAt(0) + "⋯" + s.charAt(s.length() - 1);
192     }
193 
copyStream(InputStream is, OutputStream os, int bufferSize)194     public static void copyStream(InputStream is, OutputStream os, int bufferSize)
195             throws IOException {
196         if (is != null && os != null) {
197             byte[] buffer = new byte[bufferSize];
198             int bytesRead = 0;
199             while ((bytesRead = is.read(buffer)) >= 0) {
200                 os.write(buffer, 0, bytesRead);
201             }
202         }
203     }
204 
safeCloseStream(InputStream is)205     public static void safeCloseStream(InputStream is) {
206         if (is != null) {
207             try {
208                 is.close();
209             } catch (Throwable t) {
210                 Log.d(TAG, "Error closing stream", t);
211             }
212         }
213     }
214 
safeCloseStream(OutputStream os)215     public static void safeCloseStream(OutputStream os) {
216         if (os != null) {
217             try {
218                 os.close();
219             } catch (Throwable t) {
220                 Log.d(TAG, "Error closing stream", t);
221             }
222         }
223     }
224 
225     static int sSystemUiUid = UserHandle.USER_NULL;
setSystemUiUid(int uid)226     public static void setSystemUiUid(int uid) {
227         Utils.sSystemUiUid = uid;
228     }
229 
230     static int sForegroundUserId = UserHandle.USER_NULL;
setForegroundUserId(int uid)231     public static void setForegroundUserId(int uid) {
232         Utils.sForegroundUserId = uid;
233     }
234 
checkCaller()235     public static boolean checkCaller() {
236         int callingUser = UserHandle.getCallingUserId();
237         int callingUid = Binder.getCallingUid();
238         return (sForegroundUserId == callingUser) || (sSystemUiUid == callingUid)
239                 || (Process.SYSTEM_UID == callingUid);
240     }
241 
checkCallerAllowManagedProfiles(Context mContext)242     public static boolean checkCallerAllowManagedProfiles(Context mContext) {
243         if (mContext == null) {
244             return checkCaller();
245         }
246         int callingUser = UserHandle.getCallingUserId();
247         int callingUid = Binder.getCallingUid();
248 
249         // Use the Bluetooth process identity when making call to get parent user
250         long ident = Binder.clearCallingIdentity();
251         try {
252             UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
253             UserInfo ui = um.getProfileParent(callingUser);
254             int parentUser = (ui != null) ? ui.id : UserHandle.USER_NULL;
255 
256             // Always allow SystemUI/System access.
257             return (sForegroundUserId == callingUser) || (sForegroundUserId == parentUser)
258                     || (sSystemUiUid == callingUid) || (Process.SYSTEM_UID == callingUid);
259         } catch (Exception ex) {
260             Log.e(TAG, "checkCallerAllowManagedProfiles: Exception ex=" + ex);
261             return false;
262         } finally {
263             Binder.restoreCallingIdentity(ident);
264         }
265     }
266 
267     /**
268      * Enforce the context has android.Manifest.permission.BLUETOOTH_ADMIN permission. A
269      * {@link SecurityException} would be thrown if neither the calling process or the application
270      * does not have BLUETOOTH_ADMIN permission.
271      *
272      * @param context Context for the permission check.
273      */
enforceAdminPermission(ContextWrapper context)274     public static void enforceAdminPermission(ContextWrapper context) {
275         context.enforceCallingOrSelfPermission(android.Manifest.permission.BLUETOOTH_ADMIN,
276                 "Need BLUETOOTH_ADMIN permission");
277     }
278 
279     /**
280      * Checks that calling process has android.Manifest.permission.ACCESS_COARSE_LOCATION or
281      * android.Manifest.permission.ACCESS_FINE_LOCATION and a corresponding app op is allowed
282      */
checkCallerHasLocationPermission(Context context, AppOpsManager appOps, String callingPackage)283     public static boolean checkCallerHasLocationPermission(Context context, AppOpsManager appOps,
284             String callingPackage) {
285         if (context.checkCallingOrSelfPermission(android.Manifest.permission.ACCESS_FINE_LOCATION)
286                 == PackageManager.PERMISSION_GRANTED && isAppOppAllowed(
287                         appOps, AppOpsManager.OP_FINE_LOCATION, callingPackage)) {
288             return true;
289         }
290 
291         if (context.checkCallingOrSelfPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION)
292                 == PackageManager.PERMISSION_GRANTED && isAppOppAllowed(
293                         appOps, AppOpsManager.OP_COARSE_LOCATION, callingPackage)) {
294             return true;
295         }
296         // Enforce location permission for apps targeting M and later versions
297         if (isMApp(context, callingPackage)) {
298             // PEERS_MAC_ADDRESS is another way to get scan results without
299             // requiring location permissions, so only throw an exception here
300             // if PEERS_MAC_ADDRESS permission is missing as well
301             if (!checkCallerHasPeersMacAddressPermission(context)) {
302                 throw new SecurityException("Need ACCESS_COARSE_LOCATION or "
303                         + "ACCESS_FINE_LOCATION permission to get scan results");
304             }
305         } else {
306             // Pre-M apps running in the foreground should continue getting scan results
307             if (isForegroundApp(context, callingPackage)) {
308                 return true;
309             }
310             Log.e(TAG, "Permission denial: Need ACCESS_COARSE_LOCATION or ACCESS_FINE_LOCATION "
311                     + "permission to get scan results");
312         }
313         return false;
314     }
315 
316     /**
317      * Returns true if the caller holds PEERS_MAC_ADDRESS.
318      */
checkCallerHasPeersMacAddressPermission(Context context)319     public static boolean checkCallerHasPeersMacAddressPermission(Context context) {
320         return context.checkCallingOrSelfPermission(android.Manifest.permission.PEERS_MAC_ADDRESS)
321                 == PackageManager.PERMISSION_GRANTED;
322     }
323 
isLegacyForegroundApp(Context context, String pkgName)324     public static boolean isLegacyForegroundApp(Context context, String pkgName) {
325         return !isMApp(context, pkgName) && isForegroundApp(context, pkgName);
326     }
327 
isMApp(Context context, String pkgName)328     private static boolean isMApp(Context context, String pkgName) {
329         try {
330             return context.getPackageManager().getApplicationInfo(pkgName, 0).targetSdkVersion
331                     >= Build.VERSION_CODES.M;
332         } catch (PackageManager.NameNotFoundException e) {
333             // In case of exception, assume M app
334         }
335         return true;
336     }
337 
338     /**
339      * Return true if the specified package name is a foreground app.
340      *
341      * @param pkgName application package name.
342      */
isForegroundApp(Context context, String pkgName)343     private static boolean isForegroundApp(Context context, String pkgName) {
344         ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
345         List<ActivityManager.RunningTaskInfo> tasks = am.getRunningTasks(1);
346         return !tasks.isEmpty() && pkgName.equals(tasks.get(0).topActivity.getPackageName());
347     }
348 
isAppOppAllowed(AppOpsManager appOps, int op, String callingPackage)349     private static boolean isAppOppAllowed(AppOpsManager appOps, int op, String callingPackage) {
350         return appOps.noteOp(op, Binder.getCallingUid(), callingPackage)
351                 == AppOpsManager.MODE_ALLOWED;
352     }
353 
354     /**
355      * Converts {@code millisecond} to unit. Each unit is 0.625 millisecond.
356      */
millsToUnit(int milliseconds)357     public static int millsToUnit(int milliseconds) {
358         return (int) (TimeUnit.MILLISECONDS.toMicros(milliseconds) / MICROS_PER_UNIT);
359     }
360 
361     /**
362      * Check if we are running in BluetoothInstrumentationTest context by trying to load
363      * com.android.bluetooth.FileSystemWriteTest. If we are not in Instrumentation test mode, this
364      * class should not be found. Thus, the assumption is that FileSystemWriteTest must exist.
365      * If FileSystemWriteTest is removed in the future, another test class in
366      * BluetoothInstrumentationTest should be used instead
367      *
368      * @return true if in BluetoothInstrumentationTest, false otherwise
369      */
isInstrumentationTestMode()370     public static boolean isInstrumentationTestMode() {
371         try {
372             return Class.forName("com.android.bluetooth.FileSystemWriteTest") != null;
373         } catch (ClassNotFoundException exception) {
374             return false;
375         }
376     }
377 
378     /**
379      * Throws {@link IllegalStateException} if we are not in BluetoothInstrumentationTest. Useful
380      * for ensuring certain methods only get called in BluetoothInstrumentationTest
381      */
enforceInstrumentationTestMode()382     public static void enforceInstrumentationTestMode() {
383         if (!isInstrumentationTestMode()) {
384             throw new IllegalStateException("Not in BluetoothInstrumentationTest");
385         }
386     }
387 
388     /**
389      * Check if we are running in PTS test mode. To enable/disable PTS test mode, invoke
390      * {@code adb shell setprop persist.bluetooth.pts true/false}
391      *
392      * @return true if in PTS Test mode, false otherwise
393      */
isPtsTestMode()394     public static boolean isPtsTestMode() {
395         return SystemProperties.getBoolean(PTS_TEST_MODE_PROPERTY, false);
396     }
397 
398     /**
399      * Get uid/pid string in a binder call
400      *
401      * @return "uid/pid=xxxx/yyyy"
402      */
getUidPidString()403     public static String getUidPidString() {
404         return "uid/pid=" + Binder.getCallingUid() + "/" + Binder.getCallingPid();
405     }
406 }
407