1 /* 2 * Copyright 2024 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.server.ranging; 18 19 import static android.ranging.raw.RawRangingDevice.UPDATE_RATE_FREQUENT; 20 import static android.ranging.raw.RawRangingDevice.UPDATE_RATE_INFREQUENT; 21 import static android.ranging.raw.RawRangingDevice.UPDATE_RATE_NORMAL; 22 23 import static java.lang.Math.min; 24 25 import android.annotation.IntDef; 26 import android.app.AlarmManager; 27 import android.bluetooth.BluetoothStatusCodes; 28 import android.os.SystemClock; 29 import android.ranging.raw.RawRangingDevice.RangingUpdateRate; 30 import android.util.Range; 31 32 import com.google.common.base.Ascii; 33 import com.google.common.base.Splitter; 34 import com.google.common.collect.ImmutableList; 35 import com.google.common.collect.ImmutableMap; 36 37 import java.lang.annotation.ElementType; 38 import java.lang.annotation.Target; 39 import java.nio.ByteBuffer; 40 import java.nio.ByteOrder; 41 import java.time.Duration; 42 import java.util.BitSet; 43 import java.util.List; 44 import java.util.Optional; 45 46 /** 47 * Utilities for {@link com.android.ranging}. 48 */ 49 public class RangingUtils { 50 51 private static final String MEASUREMENT_TIME_LIMIT_EXCEEDED = "measurementTimeLimitExceeded"; 52 53 /** 54 * A basic synchronized state machine. 55 * 56 * @param <E> enum representing the different states of the machine. 57 */ 58 public static class StateMachine<E extends Enum<E>> { 59 private E mState; 60 StateMachine(E start)61 public StateMachine(E start) { 62 mState = start; 63 } 64 65 /** Gets the current state */ getState()66 public synchronized E getState() { 67 return mState; 68 } 69 70 /** Sets the current state */ setState(E state)71 public synchronized void setState(E state) { 72 mState = state; 73 } 74 75 /** 76 * Sets the current state. 77 * 78 * @return true if the state was successfully changed, false if the current state is 79 * already {@code state}. 80 */ changeStateTo(E state)81 public synchronized boolean changeStateTo(E state) { 82 if (mState == state) { 83 return false; 84 } 85 setState(state); 86 return true; 87 } 88 89 /** 90 * If the current state is {@code from}, sets it to {@code to}. 91 * 92 * @return true if the current state is {@code from}, false otherwise. 93 */ transition(E from, E to)94 public synchronized boolean transition(E from, E to) { 95 if (mState != from) { 96 return false; 97 } 98 mState = to; 99 return true; 100 } 101 102 /** 103 * Atomically get the current state before setting it to a new one. 104 * 105 * @return the previous state before it was set to the provided one. 106 */ getAndSet(E state)107 public synchronized E getAndSet(E state) { 108 E previousState = mState; 109 mState = state; 110 return previousState; 111 } 112 113 @Override toString()114 public String toString() { 115 return "StateMachine{ " 116 + mState 117 + "}"; 118 } 119 } 120 121 public static class Conversions { 122 /** 123 * Converts a list of integers to a byte array representing a bitmap of the integers. Given 124 * integers are first shifted by the shift param amount before being placed into the bitmap 125 * (e.g int x results in bit at pos "x - shift" being set). 126 */ intListToByteArrayBitmap( List<Integer> list, int expectedSizeBytes, int shift)127 public static byte[] intListToByteArrayBitmap( 128 List<Integer> list, int expectedSizeBytes, int shift) { 129 BitSet bitSet = new BitSet(expectedSizeBytes * 8); 130 for (int i : list) { 131 bitSet.set(i - shift); 132 } 133 byte[] byteArray = new byte[expectedSizeBytes]; 134 System.arraycopy(bitSet.toByteArray(), 0, byteArray, 0, 135 min(expectedSizeBytes, bitSet.toByteArray().length)); 136 return byteArray; 137 } 138 139 /** 140 * Converts a byte array representing a bitmap of integers to a list of integers. The 141 * resulting integers are shifted by the shift param amount (e.g bit set at pos x results 142 * to "x + shift" int in the final list). 143 */ byteArrayToIntList(byte[] byteArray, int shift)144 public static ImmutableList<Integer> byteArrayToIntList(byte[] byteArray, int shift) { 145 ImmutableList.Builder<Integer> list = ImmutableList.builder(); 146 BitSet bitSet = BitSet.valueOf(byteArray); 147 for (int i = 0; i < bitSet.length(); i++) { 148 if (bitSet.get(i)) { 149 list.add(i + shift); 150 } 151 } 152 return list.build(); 153 } 154 155 /** Converts an int to a byte array of a given size, using little endianness. */ intToByteArray(int value, int expectedSizeBytes)156 public static byte[] intToByteArray(int value, int expectedSizeBytes) { 157 ByteBuffer buffer = ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN); 158 buffer.putInt(value).rewind(); 159 byte[] byteArray = new byte[expectedSizeBytes]; 160 buffer.get(byteArray, 0, min(expectedSizeBytes, 4)); 161 return byteArray; 162 } 163 164 /** Converts the given byte array to an integer using little endianness. */ byteArrayToInt(byte[] byteArray)165 public static int byteArrayToInt(byte[] byteArray) { 166 ByteBuffer buffer = ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN); 167 buffer.put(byteArray).rewind(); 168 return buffer.getInt(); 169 } 170 171 /** 172 * Converts a Bluetooth MAC address from byte array to string format. Throws if input byte 173 * array is not of correct format. 174 * 175 * <p>e.g. {-84, 55, 67, -68, -87, 40} -> "AC:37:43:BC:A9:28". 176 */ macAddressToString(byte[] macAddress)177 public static String macAddressToString(byte[] macAddress) { 178 if (macAddress == null || macAddress.length != 6) { 179 throw new IllegalArgumentException("Invalid mac address byte array"); 180 } 181 StringBuilder sb = new StringBuilder(18); 182 for (byte b : macAddress) { 183 if (sb.length() > 0) { 184 sb.append(':'); 185 } 186 sb.append(String.format("%02x", b)); 187 } 188 return Ascii.toUpperCase(sb.toString()); 189 } 190 191 /** 192 * Convert a Bluetooth MAC address from string to byte array format. Throws if input string 193 * is not of correct format. 194 * 195 * <p>e.g. "AC:37:43:BC:A9:28" -> {-84, 55, 67, -68, -87, 40}. 196 */ macAddressToBytes(String macAddress)197 public static byte[] macAddressToBytes(String macAddress) { 198 if (macAddress.isEmpty()) { 199 throw new IllegalArgumentException("MAC address cannot be empty"); 200 } 201 202 byte[] bytes = new byte[6]; 203 List<String> address = Splitter.on(':').splitToList(macAddress); 204 if (address.size() != 6) { 205 throw new IllegalArgumentException("Invalid MAC address format"); 206 } 207 for (int i = 0; i < 6; i++) { 208 bytes[i] = Integer.decode("0x" + address.get(i)).byteValue(); 209 } 210 return bytes; 211 } 212 213 /** 214 * Convert the hex string to byte array. 215 */ hexStringToByteArray(String hex)216 public static byte[] hexStringToByteArray(String hex) { 217 // remove whitespace in the hex string. 218 hex = hex.replaceAll("\\s", ""); 219 220 int len = hex.length(); 221 if (len % 2 != 0) { 222 // Pad the hex string with a leading zero. 223 hex = String.format("0%s", hex); 224 len++; 225 } 226 byte[] data = new byte[len / 2]; 227 for (int i = 0; i < len; i += 2) { 228 data[i / 2] = 229 (byte) ((Character.digit(hex.charAt(i), 16) << 4) 230 | Character.digit(hex.charAt(i + 1), 16)); 231 } 232 return data; 233 } 234 } 235 convertNanosToMillis(long timestampNanos)236 public static long convertNanosToMillis(long timestampNanos) { 237 return timestampNanos / 1_000_000L; 238 } 239 setMeasurementsLimitTimeout( AlarmManager alarmManager, AlarmManager.OnAlarmListener measurementLimitListener, int measurementsLimit, int rangingIntervalMs)240 public static void setMeasurementsLimitTimeout( 241 AlarmManager alarmManager, 242 AlarmManager.OnAlarmListener measurementLimitListener, 243 int measurementsLimit, int rangingIntervalMs) { 244 if (alarmManager == null) { 245 return; 246 } 247 alarmManager.setExact( 248 AlarmManager.ELAPSED_REALTIME_WAKEUP, 249 SystemClock.elapsedRealtime() + ((long) measurementsLimit * rangingIntervalMs), 250 MEASUREMENT_TIME_LIMIT_EXCEEDED, 251 measurementLimitListener, 252 null 253 ); 254 } 255 256 /** 257 * Covert bluetooth reason code to ranging reason code. 258 */ convertBluetoothReasonCode(int bluetoothReasonCode)259 public static @InternalReason int convertBluetoothReasonCode(int bluetoothReasonCode) { 260 return switch (bluetoothReasonCode) { 261 case BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ENABLED, 262 BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ALLOWED, 263 BluetoothStatusCodes.ERROR_DEVICE_NOT_BONDED, 264 BluetoothStatusCodes.FEATURE_NOT_SUPPORTED, 265 BluetoothStatusCodes.ERROR_BAD_PARAMETERS -> InternalReason.UNSUPPORTED; 266 267 case BluetoothStatusCodes.ERROR_MISSING_BLUETOOTH_CONNECT_PERMISSION, 268 BluetoothStatusCodes.ERROR_PROFILE_SERVICE_NOT_BOUND, 269 BluetoothStatusCodes.ERROR_NOT_ACTIVE_DEVICE, 270 BluetoothStatusCodes.ERROR_NO_ACTIVE_DEVICES, 271 BluetoothStatusCodes.ERROR_PROFILE_NOT_CONNECTED, 272 BluetoothStatusCodes.ERROR_HARDWARE_GENERIC, 273 BluetoothStatusCodes.ERROR_LOCAL_NOT_ENOUGH_RESOURCES, 274 BluetoothStatusCodes.ERROR_REMOTE_NOT_ENOUGH_RESOURCES, 275 BluetoothStatusCodes.ERROR_REMOTE_OPERATION_REJECTED, 276 BluetoothStatusCodes.ERROR_ALREADY_IN_TARGET_STATE, 277 BluetoothStatusCodes.ERROR_CALLBACK_NOT_REGISTERED, 278 BluetoothStatusCodes.ERROR_ANOTHER_ACTIVE_REQUEST, 279 BluetoothStatusCodes.ERROR_REMOTE_OPERATION_NOT_SUPPORTED -> 280 InternalReason.INTERNAL_ERROR; 281 282 case BluetoothStatusCodes.ERROR_TIMEOUT, 283 BluetoothStatusCodes.ERROR_REMOTE_LINK_ERROR, 284 BluetoothStatusCodes.ERROR_NO_LE_CONNECTION -> InternalReason.NO_PEERS_FOUND; 285 286 case BluetoothStatusCodes.REASON_LOCAL_APP_REQUEST -> InternalReason.LOCAL_REQUEST; 287 288 case BluetoothStatusCodes.REASON_REMOTE_REQUEST -> InternalReason.REMOTE_REQUEST; 289 290 case BluetoothStatusCodes.REASON_LOCAL_STACK_REQUEST, 291 BluetoothStatusCodes.REASON_SYSTEM_POLICY -> InternalReason.SYSTEM_POLICY; 292 293 default -> InternalReason.UNKNOWN; 294 }; 295 } 296 getUpdateRateFromDurationRange( Range<Duration> preferred, ImmutableMap<@RangingUpdateRate Integer, Duration> allowed )297 public static Optional<@RangingUpdateRate Integer> getUpdateRateFromDurationRange( 298 Range<Duration> preferred, 299 ImmutableMap<@RangingUpdateRate Integer, Duration> allowed 300 ) { 301 if (preferred.getLower().compareTo(allowed.get(UPDATE_RATE_INFREQUENT)) > 0) { 302 // Range of preferred durations lies entirely above allowed durations 303 return Optional.of(UPDATE_RATE_INFREQUENT); 304 } 305 if (preferred.getUpper().compareTo(allowed.get(UPDATE_RATE_FREQUENT)) < 0) { 306 // Range of preferred durations lies entirely below allowed durations 307 return Optional.of(UPDATE_RATE_FREQUENT); 308 } 309 // Otherwise, the intervals overlap. Pick fastest we can. 310 if (preferred.contains(allowed.get(UPDATE_RATE_FREQUENT))) { 311 return Optional.of(UPDATE_RATE_FREQUENT); 312 } else if (preferred.contains(allowed.get(UPDATE_RATE_NORMAL))) { 313 return Optional.of(UPDATE_RATE_NORMAL); 314 } else if (preferred.contains(allowed.get(UPDATE_RATE_INFREQUENT))) { 315 return Optional.of(UPDATE_RATE_INFREQUENT); 316 } else { 317 return Optional.empty(); 318 } 319 } 320 321 @IntDef(value = { 322 InternalReason.UNKNOWN, 323 InternalReason.LOCAL_REQUEST, 324 InternalReason.REMOTE_REQUEST, 325 InternalReason.UNSUPPORTED, 326 InternalReason.SYSTEM_POLICY, 327 InternalReason.NO_PEERS_FOUND, 328 InternalReason.INTERNAL_ERROR, 329 InternalReason.BACKGROUND_RANGING_POLICY, 330 InternalReason.PEER_CAPABILITIES_MISMATCH, 331 }) 332 @Target({ElementType.TYPE_USE}) 333 public @interface InternalReason { 334 int UNKNOWN = 0; 335 int LOCAL_REQUEST = 1; 336 int REMOTE_REQUEST = 2; 337 int UNSUPPORTED = 3; 338 int SYSTEM_POLICY = 4; 339 int NO_PEERS_FOUND = 5; 340 int INTERNAL_ERROR = 6; 341 int BACKGROUND_RANGING_POLICY = 7; 342 int PEER_CAPABILITIES_MISMATCH = 8; 343 } 344 } 345