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
17package com.android.bluetooth;
18
19import android.app.ActivityManager;
20import android.app.ActivityThread;
21import android.app.AppOpsManager;
22import android.bluetooth.BluetoothAdapter;
23import android.bluetooth.BluetoothDevice;
24import android.content.Context;
25import android.content.ContextWrapper;
26import android.content.pm.PackageManager;
27import android.content.pm.UserInfo;
28import android.os.Binder;
29import android.os.Build;
30import android.os.ParcelUuid;
31import android.os.Process;
32import android.os.UserHandle;
33import android.os.UserManager;
34import android.util.Log;
35
36import java.io.IOException;
37import java.io.InputStream;
38import java.io.OutputStream;
39import java.nio.ByteBuffer;
40import java.nio.ByteOrder;
41import java.util.List;
42import java.util.UUID;
43import java.util.concurrent.TimeUnit;
44
45/**
46 * @hide
47 */
48
49final public class Utils {
50    private static final String TAG = "BluetoothUtils";
51    private static final int MICROS_PER_UNIT = 625;
52
53    static final int BD_ADDR_LEN = 6; // bytes
54    static final int BD_UUID_LEN = 16; // bytes
55
56    public static String getAddressStringFromByte(byte[] address) {
57        if (address == null || address.length != BD_ADDR_LEN) {
58            return null;
59        }
60
61        return String.format("%02X:%02X:%02X:%02X:%02X:%02X",
62                address[0], address[1], address[2], address[3], address[4],
63                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 byte[] intToByteArray(int value) {
102        ByteBuffer converter = ByteBuffer.allocate(4);
103        converter.order(ByteOrder.nativeOrder());
104        converter.putInt(value);
105        return converter.array();
106    }
107
108    public static byte[] uuidToByteArray(ParcelUuid pUuid) {
109        int length = BD_UUID_LEN;
110        ByteBuffer converter = ByteBuffer.allocate(length);
111        converter.order(ByteOrder.BIG_ENDIAN);
112        long msb, lsb;
113        UUID uuid = pUuid.getUuid();
114        msb = uuid.getMostSignificantBits();
115        lsb = uuid.getLeastSignificantBits();
116        converter.putLong(msb);
117        converter.putLong(8, lsb);
118        return converter.array();
119    }
120
121    public static byte[] uuidsToByteArray(ParcelUuid[] uuids) {
122        int length = uuids.length * BD_UUID_LEN;
123        ByteBuffer converter = ByteBuffer.allocate(length);
124        converter.order(ByteOrder.BIG_ENDIAN);
125        UUID uuid;
126        long msb, lsb;
127        for (int i = 0; i < uuids.length; i++) {
128            uuid = uuids[i].getUuid();
129            msb = uuid.getMostSignificantBits();
130            lsb = uuid.getLeastSignificantBits();
131            converter.putLong(i * BD_UUID_LEN, msb);
132            converter.putLong(i * BD_UUID_LEN + 8, lsb);
133        }
134        return converter.array();
135    }
136
137    public static ParcelUuid[] byteArrayToUuid(byte[] val) {
138        int numUuids = val.length / BD_UUID_LEN;
139        ParcelUuid[] puuids = new ParcelUuid[numUuids];
140        UUID uuid;
141        int offset = 0;
142
143        ByteBuffer converter = ByteBuffer.wrap(val);
144        converter.order(ByteOrder.BIG_ENDIAN);
145
146        for (int i = 0; i < numUuids; i++) {
147            puuids[i] = new ParcelUuid(new UUID(converter.getLong(offset),
148                    converter.getLong(offset + 8)));
149            offset += BD_UUID_LEN;
150        }
151        return puuids;
152    }
153
154    public static String debugGetAdapterStateString(int state) {
155        switch (state) {
156            case BluetoothAdapter.STATE_OFF:
157                return "STATE_OFF";
158            case BluetoothAdapter.STATE_ON:
159                return "STATE_ON";
160            case BluetoothAdapter.STATE_TURNING_ON:
161                return "STATE_TURNING_ON";
162            case BluetoothAdapter.STATE_TURNING_OFF:
163                return "STATE_TURNING_OFF";
164            default:
165                return "UNKNOWN";
166        }
167    }
168
169    public static void copyStream(InputStream is, OutputStream os, int bufferSize)
170            throws IOException {
171        if (is != null && os != null) {
172            byte[] buffer = new byte[bufferSize];
173            int bytesRead = 0;
174            while ((bytesRead = is.read(buffer)) >= 0) {
175                os.write(buffer, 0, bytesRead);
176            }
177        }
178    }
179
180    public static void safeCloseStream(InputStream is) {
181        if (is != null) {
182            try {
183                is.close();
184            } catch (Throwable t) {
185                Log.d(TAG, "Error closing stream", t);
186            }
187        }
188    }
189
190    public static void safeCloseStream(OutputStream os) {
191        if (os != null) {
192            try {
193                os.close();
194            } catch (Throwable t) {
195                Log.d(TAG, "Error closing stream", t);
196            }
197        }
198    }
199
200    public static boolean checkCaller() {
201        boolean ok;
202        // Get the caller's user id then clear the calling identity
203        // which will be restored in the finally clause.
204        int callingUser = UserHandle.getCallingUserId();
205        int callingUid = Binder.getCallingUid();
206        long ident = Binder.clearCallingIdentity();
207
208        try {
209            // With calling identity cleared the current user is the foreground user.
210            int foregroundUser = ActivityManager.getCurrentUser();
211            ok = (foregroundUser == callingUser);
212            if (!ok) {
213                // Always allow SystemUI/System access.
214                final int systemUiUid = ActivityThread.getPackageManager().getPackageUid(
215                        "com.android.systemui", PackageManager.MATCH_SYSTEM_ONLY,
216                        UserHandle.USER_SYSTEM);
217                ok = (systemUiUid == callingUid) || (Process.SYSTEM_UID == callingUid);
218            }
219        } catch (Exception ex) {
220            Log.e(TAG, "checkIfCallerIsSelfOrForegroundUser: Exception ex=" + ex);
221            ok = false;
222        } finally {
223            Binder.restoreCallingIdentity(ident);
224        }
225        return ok;
226    }
227
228    public static boolean checkCallerAllowManagedProfiles(Context mContext) {
229        if (mContext == null) {
230            return checkCaller();
231        }
232        boolean ok;
233        // Get the caller's user id and if it's a managed profile, get it's parents
234        // id, then clear the calling identity
235        // which will be restored in the finally clause.
236        int callingUser = UserHandle.getCallingUserId();
237        int callingUid = Binder.getCallingUid();
238        long ident = Binder.clearCallingIdentity();
239        try {
240            UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
241            UserInfo ui = um.getProfileParent(callingUser);
242            int parentUser = (ui != null) ? ui.id : UserHandle.USER_NULL;
243            // With calling identity cleared the current user is the foreground user.
244            int foregroundUser = ActivityManager.getCurrentUser();
245            ok = (foregroundUser == callingUser) ||
246                    (foregroundUser == parentUser);
247            if (!ok) {
248                // Always allow SystemUI/System access.
249                final int systemUiUid = ActivityThread.getPackageManager().getPackageUid(
250                        "com.android.systemui", PackageManager.MATCH_SYSTEM_ONLY,
251                        UserHandle.USER_SYSTEM);
252                ok = (systemUiUid == callingUid) || (Process.SYSTEM_UID == callingUid);
253            }
254        } catch (Exception ex) {
255            Log.e(TAG, "checkCallerAllowManagedProfiles: Exception ex=" + ex);
256            ok = false;
257        } finally {
258            Binder.restoreCallingIdentity(ident);
259        }
260        return ok;
261    }
262
263    /**
264     * Enforce the context has android.Manifest.permission.BLUETOOTH_ADMIN permission. A
265     * {@link SecurityException} would be thrown if neither the calling process or the application
266     * does not have BLUETOOTH_ADMIN permission.
267     *
268     * @param context Context for the permission check.
269     */
270    public static void enforceAdminPermission(ContextWrapper context) {
271        context.enforceCallingOrSelfPermission(android.Manifest.permission.BLUETOOTH_ADMIN,
272                "Need BLUETOOTH_ADMIN permission");
273    }
274
275    /**
276     * Checks that calling process has android.Manifest.permission.ACCESS_COARSE_LOCATION or
277     * android.Manifest.permission.ACCESS_FINE_LOCATION and a corresponding app op is allowed
278     */
279    public static boolean checkCallerHasLocationPermission(Context context, AppOpsManager appOps,
280            String callingPackage) {
281        if (context.checkCallingOrSelfPermission(android.Manifest.permission.
282                ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED
283                && isAppOppAllowed(appOps, AppOpsManager.OP_FINE_LOCATION, callingPackage)) {
284            return true;
285        }
286
287        if (context.checkCallingOrSelfPermission(android.Manifest.permission.
288                ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED
289                && isAppOppAllowed(appOps, AppOpsManager.OP_COARSE_LOCATION, callingPackage)) {
290            return true;
291        }
292        // Enforce location permission for apps targeting M and later versions
293        if (isMApp(context, callingPackage)) {
294            // PEERS_MAC_ADDRESS is another way to get scan results without
295            // requiring location permissions, so only throw an exception here
296            // if PEERS_MAC_ADDRESS permission is missing as well
297            if (!checkCallerHasPeersMacAddressPermission(context)) {
298                throw new SecurityException("Need ACCESS_COARSE_LOCATION or "
299                        + "ACCESS_FINE_LOCATION permission to get scan results");
300            }
301        } else {
302            // Pre-M apps running in the foreground should continue getting scan results
303            if (isForegroundApp(context, callingPackage)) {
304                return true;
305            }
306            Log.e(TAG, "Permission denial: Need ACCESS_COARSE_LOCATION or ACCESS_FINE_LOCATION "
307                    + "permission to get scan results");
308        }
309        return false;
310    }
311
312    /**
313     * Returns true if the caller holds PEERS_MAC_ADDRESS.
314     */
315    public static boolean checkCallerHasPeersMacAddressPermission(Context context) {
316        return context.checkCallingOrSelfPermission(
317                android.Manifest.permission.PEERS_MAC_ADDRESS) == PackageManager.PERMISSION_GRANTED;
318    }
319
320    public static boolean isLegacyForegroundApp(Context context, String pkgName) {
321        return !isMApp(context, pkgName) && isForegroundApp(context, pkgName);
322    }
323
324    private static boolean isMApp(Context context, String pkgName) {
325        try {
326            return context.getPackageManager().getApplicationInfo(pkgName, 0)
327                    .targetSdkVersion >= Build.VERSION_CODES.M;
328        } catch (PackageManager.NameNotFoundException e) {
329            // In case of exception, assume M app
330        }
331        return true;
332    }
333
334    /**
335     * Return true if the specified package name is a foreground app.
336     *
337     * @param pkgName application package name.
338     */
339    private static boolean isForegroundApp(Context context, String pkgName) {
340        ActivityManager am = (ActivityManager)context.getSystemService(Context.ACTIVITY_SERVICE);
341        List<ActivityManager.RunningTaskInfo> tasks = am.getRunningTasks(1);
342        return !tasks.isEmpty() && pkgName.equals(tasks.get(0).topActivity.getPackageName());
343    }
344
345    private static boolean isAppOppAllowed(AppOpsManager appOps, int op, String callingPackage) {
346        return appOps.noteOp(op, Binder.getCallingUid(), callingPackage)
347                == AppOpsManager.MODE_ALLOWED;
348    }
349
350    /**
351     * Converts {@code millisecond} to unit. Each unit is 0.625 millisecond.
352     */
353    public static int millsToUnit(int milliseconds) {
354        return (int) (TimeUnit.MILLISECONDS.toMicros(milliseconds) / MICROS_PER_UNIT);
355    }
356}
357