• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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