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