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