1 /* 2 * Copyright (C) 2019 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 package com.android.car; 17 18 import android.annotation.NonNull; 19 import android.annotation.Nullable; 20 import android.annotation.SuppressLint; 21 import android.bluetooth.BluetoothAdapter; 22 import android.bluetooth.BluetoothDevice; 23 import android.bluetooth.BluetoothProfile; 24 import android.util.SparseArray; 25 26 import java.io.ByteArrayOutputStream; 27 import java.io.IOException; 28 import java.nio.ByteBuffer; 29 import java.nio.ByteOrder; 30 import java.util.UUID; 31 import java.util.concurrent.ThreadLocalRandom; 32 33 /** 34 * Some potentially useful static methods. 35 */ 36 public class Utils { 37 static final Boolean DBG = false; 38 // https://developer.android.com/reference/java/util/UUID 39 private static final int UUID_LENGTH = 16; 40 41 42 /* 43 * Maps of types and status to human readable strings 44 */ 45 46 private static final SparseArray<String> sAdapterStates = new SparseArray<String>(); 47 private static final SparseArray<String> sBondStates = new SparseArray<String>(); 48 private static final SparseArray<String> sConnectionStates = new SparseArray<String>(); 49 private static final SparseArray<String> sProfileNames = new SparseArray<String>(); 50 static { 51 52 // Bluetooth Adapter states sAdapterStates.put(BluetoothAdapter.STATE_ON, "On")53 sAdapterStates.put(BluetoothAdapter.STATE_ON, "On"); sAdapterStates.put(BluetoothAdapter.STATE_OFF, "Off")54 sAdapterStates.put(BluetoothAdapter.STATE_OFF, "Off"); sAdapterStates.put(BluetoothAdapter.STATE_TURNING_ON, "Turning On")55 sAdapterStates.put(BluetoothAdapter.STATE_TURNING_ON, "Turning On"); sAdapterStates.put(BluetoothAdapter.STATE_TURNING_OFF, "Turning Off")56 sAdapterStates.put(BluetoothAdapter.STATE_TURNING_OFF, "Turning Off"); 57 58 // Device Bonding states sBondStates.put(BluetoothDevice.BOND_BONDED, "Bonded")59 sBondStates.put(BluetoothDevice.BOND_BONDED, "Bonded"); sBondStates.put(BluetoothDevice.BOND_BONDING, "Bonding")60 sBondStates.put(BluetoothDevice.BOND_BONDING, "Bonding"); sBondStates.put(BluetoothDevice.BOND_NONE, "Unbonded")61 sBondStates.put(BluetoothDevice.BOND_NONE, "Unbonded"); 62 63 // Device and Profile Connection states sConnectionStates.put(BluetoothAdapter.STATE_CONNECTED, "Connected")64 sConnectionStates.put(BluetoothAdapter.STATE_CONNECTED, "Connected"); sConnectionStates.put(BluetoothAdapter.STATE_DISCONNECTED, "Disconnected")65 sConnectionStates.put(BluetoothAdapter.STATE_DISCONNECTED, "Disconnected"); sConnectionStates.put(BluetoothAdapter.STATE_CONNECTING, "Connecting")66 sConnectionStates.put(BluetoothAdapter.STATE_CONNECTING, "Connecting"); sConnectionStates.put(BluetoothAdapter.STATE_DISCONNECTING, "Disconnecting")67 sConnectionStates.put(BluetoothAdapter.STATE_DISCONNECTING, "Disconnecting"); 68 69 // Profile Names sProfileNames.put(BluetoothProfile.HEADSET, "HFP Server")70 sProfileNames.put(BluetoothProfile.HEADSET, "HFP Server"); sProfileNames.put(BluetoothProfile.A2DP, "A2DP Source")71 sProfileNames.put(BluetoothProfile.A2DP, "A2DP Source"); sProfileNames.put(BluetoothProfile.HEALTH, "HDP")72 sProfileNames.put(BluetoothProfile.HEALTH, "HDP"); sProfileNames.put(BluetoothProfile.HID_HOST, "HID Host")73 sProfileNames.put(BluetoothProfile.HID_HOST, "HID Host"); sProfileNames.put(BluetoothProfile.PAN, "PAN")74 sProfileNames.put(BluetoothProfile.PAN, "PAN"); sProfileNames.put(BluetoothProfile.PBAP, "PBAP Server")75 sProfileNames.put(BluetoothProfile.PBAP, "PBAP Server"); sProfileNames.put(BluetoothProfile.GATT, "GATT Client")76 sProfileNames.put(BluetoothProfile.GATT, "GATT Client"); sProfileNames.put(BluetoothProfile.GATT_SERVER, "GATT Server")77 sProfileNames.put(BluetoothProfile.GATT_SERVER, "GATT Server"); sProfileNames.put(BluetoothProfile.MAP, "MAP Server")78 sProfileNames.put(BluetoothProfile.MAP, "MAP Server"); sProfileNames.put(BluetoothProfile.SAP, "SAP")79 sProfileNames.put(BluetoothProfile.SAP, "SAP"); sProfileNames.put(BluetoothProfile.A2DP_SINK, "A2DP Sink")80 sProfileNames.put(BluetoothProfile.A2DP_SINK, "A2DP Sink"); sProfileNames.put(BluetoothProfile.AVRCP_CONTROLLER, "AVRCP Controller")81 sProfileNames.put(BluetoothProfile.AVRCP_CONTROLLER, "AVRCP Controller"); sProfileNames.put(BluetoothProfile.AVRCP, "AVRCP Target")82 sProfileNames.put(BluetoothProfile.AVRCP, "AVRCP Target"); sProfileNames.put(BluetoothProfile.HEADSET_CLIENT, "HFP Client")83 sProfileNames.put(BluetoothProfile.HEADSET_CLIENT, "HFP Client"); sProfileNames.put(BluetoothProfile.PBAP_CLIENT, "PBAP Client")84 sProfileNames.put(BluetoothProfile.PBAP_CLIENT, "PBAP Client"); sProfileNames.put(BluetoothProfile.MAP_CLIENT, "MAP Client")85 sProfileNames.put(BluetoothProfile.MAP_CLIENT, "MAP Client"); sProfileNames.put(BluetoothProfile.HID_DEVICE, "HID Device")86 sProfileNames.put(BluetoothProfile.HID_DEVICE, "HID Device"); sProfileNames.put(BluetoothProfile.OPP, "OPP")87 sProfileNames.put(BluetoothProfile.OPP, "OPP"); sProfileNames.put(BluetoothProfile.HEARING_AID, "Hearing Aid")88 sProfileNames.put(BluetoothProfile.HEARING_AID, "Hearing Aid"); 89 } 90 getDeviceDebugInfo(BluetoothDevice device)91 static String getDeviceDebugInfo(BluetoothDevice device) { 92 if (device == null) { 93 return "(null)"; 94 } 95 return "(name = " + device.getName() + ", addr = " + device.getAddress() + ")"; 96 } 97 getProfileName(int profile)98 static String getProfileName(int profile) { 99 String name = sProfileNames.get(profile, "Unknown"); 100 return "(" + profile + ") " + name; 101 } 102 getConnectionStateName(int state)103 static String getConnectionStateName(int state) { 104 String name = sConnectionStates.get(state, "Unknown"); 105 return "(" + state + ") " + name; 106 } 107 getBondStateName(int state)108 static String getBondStateName(int state) { 109 String name = sBondStates.get(state, "Unknown"); 110 return "(" + state + ") " + name; 111 } 112 getAdapterStateName(int state)113 static String getAdapterStateName(int state) { 114 String name = sAdapterStates.get(state, "Unknown"); 115 return "(" + state + ") " + name; 116 } 117 getProfilePriorityName(int priority)118 static String getProfilePriorityName(int priority) { 119 String name = ""; 120 if (priority >= BluetoothProfile.PRIORITY_AUTO_CONNECT) { 121 name = "PRIORITY_AUTO_CONNECT"; 122 } else if (priority >= BluetoothProfile.PRIORITY_ON) { 123 name = "PRIORITY_ON"; 124 } else if (priority >= BluetoothProfile.PRIORITY_OFF) { 125 name = "PRIORITY_OFF"; 126 } else { 127 name = "PRIORITY_UNDEFINED"; 128 } 129 return "(" + priority + ") " + name; 130 } 131 132 /** 133 * An utility class to dump transition events across different car service components. 134 * The output will be of the form 135 * <p> 136 * "Time <svc name>: [optional context information] changed from <from state> to <to state>" 137 * This can be used in conjunction with the dump() method to dump this information through 138 * adb shell dumpsys activity service com.android.car 139 * <p> 140 * A specific service in CarService can choose to use a circular buffer of N records to keep 141 * track of the last N transitions. 142 */ 143 public static class TransitionLog { 144 private String mServiceName; // name of the service or tag 145 private Object mFromState; // old state 146 private Object mToState; // new state 147 private long mTimestampMs; // System.currentTimeMillis() 148 private String mExtra; // Additional information as a String 149 TransitionLog(String name, Object fromState, Object toState, long timestamp, String extra)150 public TransitionLog(String name, Object fromState, Object toState, long timestamp, 151 String extra) { 152 this(name, fromState, toState, timestamp); 153 mExtra = extra; 154 } 155 TransitionLog(String name, Object fromState, Object toState, long timeStamp)156 public TransitionLog(String name, Object fromState, Object toState, long timeStamp) { 157 mServiceName = name; 158 mFromState = fromState; 159 mToState = toState; 160 mTimestampMs = timeStamp; 161 } 162 timeToLog(long timestamp)163 private CharSequence timeToLog(long timestamp) { 164 return android.text.format.DateFormat.format("MM-dd HH:mm:ss", timestamp); 165 } 166 167 @Override toString()168 public String toString() { 169 return timeToLog(mTimestampMs) + " " + mServiceName + ": " + (mExtra != null ? mExtra 170 : "") + " changed from " + mFromState + " to " + mToState; 171 } 172 } 173 174 /** 175 * Returns a byte buffer corresponding to the passed long argument. 176 * 177 * @param primitive data to convert format. 178 */ longToBytes(long primitive)179 public static byte[] longToBytes(long primitive) { 180 ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES); 181 buffer.putLong(primitive); 182 return buffer.array(); 183 } 184 185 /** 186 * Returns a byte buffer corresponding to the passed long argument. 187 * 188 * @param array data to convert format. 189 */ bytesToLong(byte[] array)190 public static long bytesToLong(byte[] array) { 191 ByteBuffer buffer = ByteBuffer.allocate(Long.SIZE / Byte.SIZE); 192 buffer.put(array); 193 buffer.flip(); 194 long value = buffer.getLong(); 195 return value; 196 } 197 198 /** 199 * Returns a String in Hex format that is formed from the bytes in the byte array 200 * Useful for debugging 201 * 202 * @param array the byte array 203 * @return the Hex string version of the input byte array 204 */ byteArrayToHexString(byte[] array)205 public static String byteArrayToHexString(byte[] array) { 206 StringBuilder sb = new StringBuilder(array.length * 2); 207 for (byte b : array) { 208 sb.append(String.format("%02x", b)); 209 } 210 return sb.toString(); 211 } 212 213 /** 214 * Convert UUID to Big Endian byte array 215 * 216 * @param uuid UUID to convert 217 * @return the byte array representing the UUID 218 */ 219 @NonNull uuidToBytes(@onNull UUID uuid)220 public static byte[] uuidToBytes(@NonNull UUID uuid) { 221 222 return ByteBuffer.allocate(UUID_LENGTH) 223 .order(ByteOrder.BIG_ENDIAN) 224 .putLong(uuid.getMostSignificantBits()) 225 .putLong(uuid.getLeastSignificantBits()) 226 .array(); 227 } 228 229 /** 230 * Convert Big Endian byte array to UUID 231 * 232 * @param bytes byte array to convert 233 * @return the UUID representing the byte array, or null if not a valid UUID 234 */ 235 @Nullable bytesToUUID(@onNull byte[] bytes)236 public static UUID bytesToUUID(@NonNull byte[] bytes) { 237 if (bytes.length != UUID_LENGTH) { 238 return null; 239 } 240 241 ByteBuffer buffer = ByteBuffer.wrap(bytes); 242 return new UUID(buffer.getLong(), buffer.getLong()); 243 } 244 245 /** 246 * Generate a random zero-filled string of given length 247 * 248 * @param length of string 249 * @return generated string 250 */ 251 @SuppressLint("DefaultLocale") // Should always have the same format regardless of locale generateRandomNumberString(int length)252 public static String generateRandomNumberString(int length) { 253 return String.format("%0" + length + "d", 254 ThreadLocalRandom.current().nextInt((int) Math.pow(10, length))); 255 } 256 257 258 /** 259 * Concatentate the given 2 byte arrays 260 * 261 * @param a input array 1 262 * @param b input array 2 263 * @return concatenated array of arrays 1 and 2 264 */ 265 @Nullable concatByteArrays(@ullable byte[] a, @Nullable byte[] b)266 public static byte[] concatByteArrays(@Nullable byte[] a, @Nullable byte[] b) { 267 ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); 268 try { 269 if (a != null) { 270 outputStream.write(a); 271 } 272 if (b != null) { 273 outputStream.write(b); 274 } 275 } catch (IOException e) { 276 return null; 277 } 278 return outputStream.toByteArray(); 279 } 280 281 } 282