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 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 66 public static byte[] getByteAddress(BluetoothDevice device) { 67 return getBytesFromAddress(device.getAddress()); 68 } 69 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 85 public static int byteArrayToInt(byte[] valueBuf) { 86 return byteArrayToInt(valueBuf, 0); 87 } 88 89 public static short byteArrayToShort(byte[] valueBuf) { 90 ByteBuffer converter = ByteBuffer.wrap(valueBuf); 91 converter.order(ByteOrder.nativeOrder()); 92 return converter.getShort(); 93 } 94 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 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 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 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 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 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 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 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 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 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 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; 226 public static void setSystemUiUid(int uid) { 227 Utils.sSystemUiUid = uid; 228 } 229 230 static int sForegroundUserId = UserHandle.USER_NULL; 231 public static void setForegroundUserId(int uid) { 232 Utils.sForegroundUserId = uid; 233 } 234 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 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 */ 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 */ 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 */ 319 public static boolean checkCallerHasPeersMacAddressPermission(Context context) { 320 return context.checkCallingOrSelfPermission(android.Manifest.permission.PEERS_MAC_ADDRESS) 321 == PackageManager.PERMISSION_GRANTED; 322 } 323 324 public static boolean isLegacyForegroundApp(Context context, String pkgName) { 325 return !isMApp(context, pkgName) && isForegroundApp(context, pkgName); 326 } 327 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 */ 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 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 */ 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 */ 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 */ 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 */ 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 */ 403 public static String getUidPidString() { 404 return "uid/pid=" + Binder.getCallingUid() + "/" + Binder.getCallingPid(); 405 } 406 } 407