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