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 package com.android.server.uwb.advertisement; 17 18 import static com.android.server.uwb.util.DataTypeConversionUtil.macAddressByteArrayToLong; 19 20 import androidx.annotation.Nullable; 21 22 import com.android.internal.annotations.VisibleForTesting; 23 import com.android.server.uwb.DeviceConfigFacade; 24 import com.android.server.uwb.UwbInjector; 25 import com.android.server.uwb.data.UwbOwrAoaMeasurement; 26 27 import java.util.ArrayList; 28 import java.util.Optional; 29 import java.util.concurrent.ConcurrentHashMap; 30 31 public class UwbAdvertiseManager { 32 private static final String TAG = "UwbAdvertiseManager"; 33 34 private final ConcurrentHashMap<Long, UwbAdvertiseTarget> mAdvertiseTargetMap = 35 new ConcurrentHashMap<>(); 36 37 private final UwbInjector mUwbInjector; 38 private final DeviceConfigFacade mDeviceConfigFacade; 39 UwbAdvertiseManager(UwbInjector uwbInjector, DeviceConfigFacade deviceConfigFacade)40 public UwbAdvertiseManager(UwbInjector uwbInjector, DeviceConfigFacade deviceConfigFacade) { 41 this.mUwbInjector = uwbInjector; 42 this.mDeviceConfigFacade = deviceConfigFacade; 43 } 44 45 /** 46 * Check if the current device is pointing at the remote device, from which we have received 47 * One-way Ranging AoA Measurement(s). 48 */ isPointedTarget(byte[] macAddressBytes)49 public boolean isPointedTarget(byte[] macAddressBytes) { 50 UwbAdvertiseTarget uwbAdvertiseTarget = getAdvertiseTarget( 51 macAddressByteArrayToLong(macAddressBytes)); 52 if (uwbAdvertiseTarget == null) { 53 return false; 54 } 55 56 if (!uwbAdvertiseTarget.isWithinCriterionAngle()) { 57 return false; 58 } 59 if (!isWithinCriterionVariance(uwbAdvertiseTarget)) { 60 return false; 61 } 62 63 if (!isWithinTimeThreshold(uwbAdvertiseTarget)) { 64 return false; 65 } 66 return true; 67 } 68 69 /** 70 * Store a One-way Ranging AoA Measurement from the remote device in a UWB ranging session. 71 */ updateAdvertiseTarget(UwbOwrAoaMeasurement uwbOwrAoaMeasurement)72 public void updateAdvertiseTarget(UwbOwrAoaMeasurement uwbOwrAoaMeasurement) { 73 // First check if there exists a stale UwbAdvertiseTarget for the device, and remove it. 74 checkAndRemoveStaleAdvertiseTarget(uwbOwrAoaMeasurement.mMacAddress); 75 76 // Now store the new measurement for the device. 77 updateAdvertiseTargetInfo(uwbOwrAoaMeasurement); 78 } 79 80 /** 81 * Remove all the stored AdvertiseTarget data for the given device. 82 */ removeAdvertiseTarget(long macAddress)83 public void removeAdvertiseTarget(long macAddress) { 84 mAdvertiseTargetMap.remove(macAddress); 85 } 86 isWithinCriterionVariance(UwbAdvertiseTarget uwbAdvertiseTarget)87 private boolean isWithinCriterionVariance(UwbAdvertiseTarget uwbAdvertiseTarget) { 88 if (!uwbAdvertiseTarget.isVarianceCalculated()) { 89 return false; 90 } 91 92 int trustedValueOfVariance = mDeviceConfigFacade.getAdvertiseTrustedVarianceValue(); 93 if (uwbAdvertiseTarget.getVarianceOfAzimuth() > trustedValueOfVariance) { 94 return false; 95 } 96 97 if (uwbAdvertiseTarget.getVarianceOfElevation() > trustedValueOfVariance) { 98 return false; 99 } 100 return true; 101 } 102 checkAndRemoveStaleAdvertiseTarget(byte[] macAddressBytes)103 private void checkAndRemoveStaleAdvertiseTarget(byte[] macAddressBytes) { 104 long macAddress = macAddressByteArrayToLong(macAddressBytes); 105 UwbAdvertiseTarget uwbAdvertiseTarget = getAdvertiseTarget(macAddress); 106 if (uwbAdvertiseTarget == null) { 107 return; 108 } 109 110 if (!isWithinTimeThreshold(uwbAdvertiseTarget)) { 111 removeAdvertiseTarget(macAddress); 112 } 113 } 114 isWithinTimeThreshold(UwbAdvertiseTarget uwbAdvertiseTarget)115 private boolean isWithinTimeThreshold(UwbAdvertiseTarget uwbAdvertiseTarget) { 116 long currentTime = mUwbInjector.getElapsedSinceBootMillis(); 117 if (currentTime - uwbAdvertiseTarget.getLastUpdatedTime() 118 > mDeviceConfigFacade.getAdvertiseTimeThresholdMillis()) { 119 return false; 120 } 121 return true; 122 } 123 updateAdvertiseTargetInfo( UwbOwrAoaMeasurement uwbOwrAoaMeasurement)124 private UwbAdvertiseTarget updateAdvertiseTargetInfo( 125 UwbOwrAoaMeasurement uwbOwrAoaMeasurement) { 126 long currentTime = mUwbInjector.getElapsedSinceBootMillis(); 127 long macAddress = macAddressByteArrayToLong(uwbOwrAoaMeasurement.getMacAddress()); 128 129 UwbAdvertiseTarget advertiseTarget = getOrAddAdvertiseTarget(macAddress); 130 advertiseTarget.calculateAoaVariance(uwbOwrAoaMeasurement); 131 advertiseTarget.updateLastMeasuredTime(currentTime); 132 133 return advertiseTarget; 134 } 135 136 @VisibleForTesting 137 @Nullable getAdvertiseTarget(long macAddress)138 public UwbAdvertiseTarget getAdvertiseTarget(long macAddress) { 139 return isAdvertiseTargetExist(macAddress) ? mAdvertiseTargetMap.get(macAddress) : null; 140 } 141 getOrAddAdvertiseTarget(long macAddress)142 private UwbAdvertiseTarget getOrAddAdvertiseTarget(long macAddress) { 143 UwbAdvertiseTarget uwbAdvertiseTarget; 144 if (isAdvertiseTargetExist(macAddress)) { 145 uwbAdvertiseTarget = mAdvertiseTargetMap.get(macAddress); 146 } else { 147 uwbAdvertiseTarget = addAdvertiseTarget(macAddress); 148 } 149 return uwbAdvertiseTarget; 150 } 151 isAdvertiseTargetExist(long macAddress)152 private boolean isAdvertiseTargetExist(long macAddress) { 153 return mAdvertiseTargetMap.containsKey(macAddress); 154 } 155 addAdvertiseTarget(long macAddress)156 private UwbAdvertiseTarget addAdvertiseTarget(long macAddress) { 157 UwbAdvertiseTarget advertiseTarget = new UwbAdvertiseTarget(macAddress); 158 mAdvertiseTargetMap.put(macAddress, advertiseTarget); 159 return advertiseTarget; 160 } 161 162 /** 163 * Stored Owr Aoa Measurements for the remote devices. The data should be cleared when the 164 * UWB session is closed. 165 */ 166 @VisibleForTesting 167 public class UwbAdvertiseTarget { 168 private final long mMacAddress; 169 private final ArrayList<Double> mRecentAoaAzimuth = new ArrayList<>(); 170 private final ArrayList<Double> mRecentAoaElevation = new ArrayList<>(); 171 private double mVarianceOfAzimuth; 172 private double mVarianceOfElevation; 173 private long mLastMeasuredTime; 174 private boolean mIsVarianceCalculated; 175 UwbAdvertiseTarget(long macAddress)176 private UwbAdvertiseTarget(long macAddress) { 177 mMacAddress = macAddress; 178 mIsVarianceCalculated = false; 179 } 180 calculateAoaVariance(UwbOwrAoaMeasurement owrAoaMeasurement)181 private void calculateAoaVariance(UwbOwrAoaMeasurement owrAoaMeasurement) { 182 double aoaAzimuth = owrAoaMeasurement.getAoaAzimuth(); 183 double aoaElevation = owrAoaMeasurement.getAoaElevation(); 184 185 mRecentAoaAzimuth.add(aoaAzimuth); 186 mRecentAoaElevation.add(aoaElevation); 187 188 int arraySizeToCheck = mDeviceConfigFacade.getAdvertiseArraySizeToCheck(); 189 int arrayStartIndex = mDeviceConfigFacade.getAdvertiseArrayStartIndexToCalVariance(); 190 int arrayEndIndex = mDeviceConfigFacade.getAdvertiseArrayEndIndexToCalVariance(); 191 192 if (mRecentAoaAzimuth.size() > arraySizeToCheck) { 193 mRecentAoaAzimuth.remove(0); 194 mRecentAoaElevation.remove(0); 195 } 196 197 if (mRecentAoaAzimuth.size() == arraySizeToCheck) { 198 double[] azimuthArr = 199 mRecentAoaAzimuth 200 .subList(arrayStartIndex, arrayEndIndex) 201 .stream() 202 .mapToDouble(Double::doubleValue) 203 .toArray(); 204 mVarianceOfAzimuth = getVariance(azimuthArr); 205 double[] elevationArr = 206 mRecentAoaElevation 207 .subList(arrayStartIndex, arrayEndIndex) 208 .stream() 209 .mapToDouble(Double::doubleValue) 210 .toArray(); 211 mVarianceOfElevation = getVariance(elevationArr); 212 mIsVarianceCalculated = true; 213 } else { 214 mIsVarianceCalculated = false; 215 } 216 } 217 isWithinCriterionAngle(double aoa)218 private boolean isWithinCriterionAngle(double aoa) { 219 return Math.abs(aoa) <= mDeviceConfigFacade.getAdvertiseAoaCriteriaAngle(); 220 } 221 isWithinCriterionAngle()222 private boolean isWithinCriterionAngle() { 223 Optional<Double> outsideCriterionAngle; 224 225 // Check if any stored AoaAzimuth value is outside the criterion angle range. 226 outsideCriterionAngle = mRecentAoaAzimuth.stream() 227 .filter(x -> !isWithinCriterionAngle(x)) 228 .findFirst(); 229 if (outsideCriterionAngle.isPresent()) return false; 230 231 // Check if any stored AoaElevation value is outside the criterion angle range. 232 outsideCriterionAngle = mRecentAoaElevation.stream() 233 .filter(x -> !isWithinCriterionAngle(x)) 234 .findFirst(); 235 return !outsideCriterionAngle.isPresent(); 236 } 237 238 // TODO(b/246678053): Can we receive measurements that are out of order (in terms of the 239 // timestamp) ? In that case we should store the largest timestamp, not the latest, as this 240 // stored value is used later for validating the measurements are within the time threshold. updateLastMeasuredTime(long time)241 private void updateLastMeasuredTime(long time) { 242 mLastMeasuredTime = time; 243 } 244 getVarianceOfAzimuth()245 private double getVarianceOfAzimuth() { 246 return mVarianceOfAzimuth; 247 } 248 getVarianceOfElevation()249 private double getVarianceOfElevation() { 250 return mVarianceOfElevation; 251 } 252 getLastUpdatedTime()253 private long getLastUpdatedTime() { 254 return mLastMeasuredTime; 255 } 256 257 @VisibleForTesting isVarianceCalculated()258 public boolean isVarianceCalculated() { 259 return mIsVarianceCalculated; 260 } 261 getAverage(double[] array)262 private double getAverage(double[] array) { 263 double sum = 0.0; 264 265 for (int i = 0; i < array.length; i++) sum += array[i]; 266 267 return sum / array.length; 268 } 269 getVariance(double[] array)270 private double getVariance(double[] array) { 271 if (array.length < 2) return Double.NaN; 272 273 double sum = 0.0; 274 double ret; 275 double avg = getAverage(array); 276 277 for (int i = 0; i < array.length; i++) { 278 sum += Math.pow(array[i] - avg, 2); 279 } 280 ret = (double) sum / array.length; 281 282 return ret; 283 } 284 285 @Override toString()286 public String toString() { 287 return " mMacAddress : " 288 + mMacAddress 289 + ", mVarOfAzimuth : " 290 + mVarianceOfAzimuth 291 + ", mVarOfElevation : " 292 + mVarianceOfElevation 293 + ", mRecentAoaAzimuth : " 294 + mRecentAoaAzimuth 295 + ", mRecentAoaElevation : " 296 + mRecentAoaElevation; 297 } 298 } 299 } 300