Home | History | Annotate | Download | only in bluetooth
      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