1 /*
2  * Copyright (C) 2022 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 androidx.core.uwb.backend.impl.internal;
18 
19 import static android.uwb.UwbManager.AdapterStateCallback.STATE_CHANGED_REASON_SYSTEM_POLICY;
20 import static android.uwb.UwbManager.AdapterStateCallback.STATE_CHANGED_REASON_SYSTEM_REGULATION;
21 
22 import android.os.Build;
23 import android.os.Build.VERSION_CODES;
24 import android.uwb.AngleMeasurement;
25 import android.uwb.AngleOfArrivalMeasurement;
26 import android.uwb.DistanceMeasurement;
27 import android.uwb.RangingSession;
28 
29 import androidx.annotation.Nullable;
30 import androidx.annotation.RequiresApi;
31 
32 import java.util.ArrayList;
33 import java.util.List;
34 
35 /** Utility class to help convert results from system API to GMSCore API */
36 @RequiresApi(api = VERSION_CODES.S)
37 final class Conversions {
38 
createMeasurement(double value, double confidence, boolean valid)39     private static RangingMeasurement createMeasurement(double value, double confidence,
40             boolean valid) {
41         @RangingMeasurement.Confidence int confidenceLevel;
42         if (confidence > 0.9) {
43             confidenceLevel = RangingMeasurement.CONFIDENCE_HIGH;
44         } else if (confidence > 0.5) {
45             confidenceLevel = RangingMeasurement.CONFIDENCE_MEDIUM;
46         } else {
47             confidenceLevel = RangingMeasurement.CONFIDENCE_LOW;
48         }
49         return new RangingMeasurement(confidenceLevel, (float) value, valid);
50     }
51 
isDlTdoaMeasurement(android.uwb.RangingMeasurement measurement)52     public static boolean isDlTdoaMeasurement(android.uwb.RangingMeasurement measurement) {
53         if (Build.VERSION.SDK_INT <= VERSION_CODES.TIRAMISU) {
54             return false;
55         }
56         try {
57             return com.google.uwb.support.dltdoa.DlTDoAMeasurement.isDlTDoAMeasurement(
58                     measurement.getRangingMeasurementMetadata());
59         } catch (NoSuchMethodError e) {
60             return false;
61         }
62     }
63 
64     /** Convert system API's {@link android.uwb.RangingMeasurement} to {@link RangingPosition} */
65     @Nullable
convertToPosition(android.uwb.RangingMeasurement measurement)66     static RangingPosition convertToPosition(android.uwb.RangingMeasurement measurement) {
67         RangingMeasurement distance;
68         DlTdoaMeasurement dlTdoaMeasurement = null;
69         if (isDlTdoaMeasurement(measurement)) {
70             com.google.uwb.support.dltdoa.DlTDoAMeasurement
71                     dlTDoAMeasurement = com.google.uwb.support.dltdoa.DlTDoAMeasurement.fromBundle(
72                     measurement.getRangingMeasurementMetadata());
73             // Return null if Dl-TDoA measurement is not valid.
74             if (dlTDoAMeasurement.getMessageControl() == 0) {
75                 return null;
76             }
77             dlTdoaMeasurement = new DlTdoaMeasurement(
78                     dlTDoAMeasurement.getMessageType(),
79                     dlTDoAMeasurement.getMessageControl(),
80                     dlTDoAMeasurement.getBlockIndex(),
81                     dlTDoAMeasurement.getRoundIndex(),
82                     dlTDoAMeasurement.getNLoS(),
83                     dlTDoAMeasurement.getTxTimestamp(),
84                     dlTDoAMeasurement.getRxTimestamp(),
85                     dlTDoAMeasurement.getAnchorCfo(),
86                     dlTDoAMeasurement.getCfo(),
87                     dlTDoAMeasurement.getInitiatorReplyTime(),
88                     dlTDoAMeasurement.getResponderReplyTime(),
89                     dlTDoAMeasurement.getInitiatorResponderTof(),
90                     dlTDoAMeasurement.getAnchorLocation(),
91                     dlTDoAMeasurement.getActiveRangingRounds()
92             );
93             // No distance measurement for DL-TDoa, make it invalid.
94             distance = createMeasurement(0.0, 0.0, false);
95         } else {
96             DistanceMeasurement distanceMeasurement = measurement.getDistanceMeasurement();
97             if (distanceMeasurement == null) {
98                 return null;
99             }
100             distance = createMeasurement(
101                     distanceMeasurement.getMeters(),
102                     distanceMeasurement.getConfidenceLevel(),
103                     true);
104         }
105         AngleOfArrivalMeasurement aoaMeasurement = measurement.getAngleOfArrivalMeasurement();
106 
107         RangingMeasurement azimuth = null;
108         RangingMeasurement altitude = null;
109         if (aoaMeasurement != null) {
110             AngleMeasurement azimuthMeasurement = aoaMeasurement.getAzimuth();
111             if (azimuthMeasurement != null && !isMeasurementAllZero(azimuthMeasurement)) {
112                 azimuth =
113                         createMeasurement(
114                                 Math.toDegrees(azimuthMeasurement.getRadians()),
115                                 azimuthMeasurement.getConfidenceLevel(),
116                                 true);
117             }
118             AngleMeasurement altitudeMeasurement = aoaMeasurement.getAltitude();
119             if (altitudeMeasurement != null && !isMeasurementAllZero(altitudeMeasurement)) {
120                 altitude =
121                         createMeasurement(
122                                 Math.toDegrees(altitudeMeasurement.getRadians()),
123                                 altitudeMeasurement.getConfidenceLevel(),
124                                 true);
125             }
126         }
127         if (Build.VERSION.SDK_INT >= VERSION_CODES.TIRAMISU) {
128             return new RangingPosition(
129                     distance,
130                     azimuth,
131                     altitude,
132                     dlTdoaMeasurement,
133                     measurement.getElapsedRealtimeNanos(),
134                     measurement.getRssiDbm());
135         }
136         return new RangingPosition(
137                 distance, azimuth, altitude, measurement.getElapsedRealtimeNanos());
138     }
139 
isMeasurementAllZero(AngleMeasurement measurement)140     private static boolean isMeasurementAllZero(AngleMeasurement measurement) {
141         return measurement.getRadians() == 0
142                 && measurement.getErrorRadians() == 0
143                 && measurement.getConfidenceLevel() == 0;
144     }
145 
146     @RangingSessionCallback.RangingSuspendedReason
convertReason(int reason)147     static int convertReason(int reason) {
148         if (reason == RangingSession.Callback.REASON_BAD_PARAMETERS) {
149             return RangingSessionCallback.REASON_WRONG_PARAMETERS;
150         }
151 
152         if (reason == RangingSession.Callback.REASON_LOCAL_REQUEST) {
153             return RangingSessionCallback.REASON_STOP_RANGING_CALLED;
154         }
155 
156         if (reason == RangingSession.Callback.REASON_REMOTE_REQUEST) {
157             return RangingSessionCallback.REASON_STOPPED_BY_PEER;
158         }
159 
160         if (reason == RangingSession.Callback.REASON_MAX_SESSIONS_REACHED) {
161             return RangingSessionCallback.REASON_FAILED_TO_START;
162         }
163 
164         if (reason == RangingSession.Callback.REASON_PROTOCOL_SPECIFIC_ERROR) {
165             return RangingSessionCallback.REASON_MAX_RANGING_ROUND_RETRY_REACHED;
166         }
167 
168         if (reason == RangingSession.Callback.REASON_SYSTEM_POLICY) {
169             return RangingSessionCallback.REASON_SYSTEM_POLICY;
170         }
171 
172         return RangingSessionCallback.REASON_UNKNOWN;
173     }
174 
175     @UwbAvailabilityCallback.UwbStateChangeReason
convertAdapterStateReason(int reason)176     static int convertAdapterStateReason(int reason) {
177         return switch (reason) {
178             case STATE_CHANGED_REASON_SYSTEM_POLICY -> UwbAvailabilityCallback.REASON_SYSTEM_POLICY;
179             case STATE_CHANGED_REASON_SYSTEM_REGULATION ->
180                     UwbAvailabilityCallback.REASON_COUNTRY_CODE_ERROR;
181             default -> UwbAvailabilityCallback.REASON_UNKNOWN;
182         };
183     }
convertUwbAddress(UwbAddress address, boolean reverseMacAddress)184     static android.uwb.UwbAddress convertUwbAddress(UwbAddress address, boolean reverseMacAddress) {
185         return reverseMacAddress
186                 ? android.uwb.UwbAddress.fromBytes(getReverseBytes(address.toBytes()))
187                 : android.uwb.UwbAddress.fromBytes(address.toBytes());
188     }
189 
convertUwbAddressList( UwbAddress[] addressList, boolean reverseMacAddress)190     static List<android.uwb.UwbAddress> convertUwbAddressList(
191             UwbAddress[] addressList, boolean reverseMacAddress) {
192         List<android.uwb.UwbAddress> list = new ArrayList<>();
193         for (UwbAddress address : addressList) {
194             list.add(convertUwbAddress(address, reverseMacAddress));
195         }
196         return list;
197     }
198 
getReverseBytes(byte[] data)199     static byte[] getReverseBytes(byte[] data) {
200         byte[] buffer = new byte[data.length];
201         for (int i = 0; i < data.length; i++) {
202             buffer[i] = data[data.length - 1 - i];
203         }
204         return buffer;
205     }
206 
Conversions()207     private Conversions() {}
208 }
209