• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 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.uwb;
18 
19 import static android.Manifest.permission.UWB_RANGING;
20 import static android.permission.PermissionManager.PERMISSION_GRANTED;
21 
22 import static java.lang.Math.toRadians;
23 
24 import android.annotation.NonNull;
25 import android.annotation.Nullable;
26 import android.app.ActivityManager;
27 import android.app.AlarmManager;
28 import android.content.ApexEnvironment;
29 import android.content.AttributionSource;
30 import android.content.Context;
31 import android.content.pm.ApplicationInfo;
32 import android.content.pm.PackageManager;
33 import android.database.ContentObserver;
34 import android.location.Geocoder;
35 import android.net.Uri;
36 import android.os.Binder;
37 import android.os.Handler;
38 import android.os.HandlerThread;
39 import android.os.Looper;
40 import android.os.Process;
41 import android.os.SystemClock;
42 import android.os.SystemProperties;
43 import android.os.UserHandle;
44 import android.os.UserManager;
45 import android.permission.PermissionManager;
46 import android.provider.Settings;
47 import android.util.AtomicFile;
48 import android.util.Log;
49 
50 import com.android.server.uwb.advertisement.UwbAdvertiseManager;
51 import com.android.server.uwb.data.ServiceProfileData;
52 import com.android.server.uwb.jni.NativeUwbManager;
53 import com.android.server.uwb.multchip.UwbMultichipData;
54 import com.android.server.uwb.pm.ProfileManager;
55 import com.android.uwb.flags.FeatureFlags;
56 import com.android.uwb.fusion.UwbFilterEngine;
57 import com.android.uwb.fusion.filtering.IFilter;
58 import com.android.uwb.fusion.filtering.MedAvgFilter;
59 import com.android.uwb.fusion.filtering.MedAvgRotationFilter;
60 import com.android.uwb.fusion.filtering.PositionFilterImpl;
61 import com.android.uwb.fusion.pose.GyroPoseSource;
62 import com.android.uwb.fusion.pose.IPoseSource;
63 import com.android.uwb.fusion.pose.IntegPoseSource;
64 import com.android.uwb.fusion.pose.RotationPoseSource;
65 import com.android.uwb.fusion.pose.SixDofPoseSource;
66 import com.android.uwb.fusion.primers.AoaPrimer;
67 import com.android.uwb.fusion.primers.BackAzimuthPrimer;
68 import com.android.uwb.fusion.primers.ElevationPrimer;
69 import com.android.uwb.fusion.primers.FovPrimer;
70 
71 import java.io.File;
72 import java.util.HashMap;
73 import java.util.Locale;
74 import java.util.Map;
75 import java.util.concurrent.ExecutionException;
76 import java.util.concurrent.ExecutorService;
77 import java.util.concurrent.Executors;
78 import java.util.concurrent.FutureTask;
79 import java.util.concurrent.TimeUnit;
80 import java.util.concurrent.TimeoutException;
81 import java.util.concurrent.locks.ReentrantLock;
82 
83 /**
84  * To be used for dependency injection (especially helps mocking static dependencies).
85  */
86 public class UwbInjector {
87     private static final String TAG = "UwbInjector";
88     private static final String APEX_NAME = "com.android.uwb";
89     private static final String VENDOR_SERVICE_NAME = "uwb_vendor";
90     private static final String BOOT_DEFAULT_UWB_COUNTRY_CODE = "ro.boot.uwbcountrycode";
91     private static final int APP_INFO_FLAGS_SYSTEM_APP =
92             ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
93 
94     private final UwbContext mContext;
95     private final Looper mLooper;
96     private final PermissionManager mPermissionManager;
97     private final UserManager mUserManager;
98     private final UwbConfigStore mUwbConfigStore;
99     private final ProfileManager mProfileManager;
100     private final UwbSettingsStore mUwbSettingsStore;
101     private final NativeUwbManager mNativeUwbManager;
102     private final UwbCountryCode mUwbCountryCode;
103     private final UciLogModeStore mUciLogModeStore;
104     private final UwbServiceCore mUwbService;
105     private final UwbMetrics mUwbMetrics;
106     private final DeviceConfigFacade mDeviceConfigFacade;
107     private final UwbMultichipData mUwbMultichipData;
108     private final SystemBuildProperties mSystemBuildProperties;
109     private final UwbDiagnostics mUwbDiagnostics;
110     private IPoseSource mDefaultPoseSource;
111     private final ReentrantLock mPoseLock = new ReentrantLock();
112     private int mPoseSourceRefCount = 0;
113 
114     private final UwbSessionManager mUwbSessionManager;
115     private final FeatureFlags mFeatureFlags;
116 
UwbInjector(@onNull UwbContext context)117     public UwbInjector(@NonNull UwbContext context) {
118         // Create UWB service thread.
119         HandlerThread uwbHandlerThread = new HandlerThread("UwbService");
120         uwbHandlerThread.start();
121         mLooper = uwbHandlerThread.getLooper();
122 
123         mContext = context;
124         mPermissionManager = context.getSystemService(PermissionManager.class);
125         mUserManager = mContext.getSystemService(UserManager.class);
126         mUwbConfigStore = new UwbConfigStore(context, new Handler(mLooper), this,
127                 UwbConfigStore.createSharedFiles());
128         mProfileManager = new ProfileManager(context, new Handler(mLooper),
129                 mUwbConfigStore, this);
130         mUwbSettingsStore = new UwbSettingsStore(
131                 context, new Handler(mLooper),
132                 new AtomicFile(new File(getDeviceProtectedDataDir(),
133                         UwbSettingsStore.FILE_NAME)), this);
134         mUwbMultichipData = new UwbMultichipData(mContext);
135         mUciLogModeStore = new UciLogModeStore(mUwbSettingsStore);
136         mNativeUwbManager = new NativeUwbManager(this, mUciLogModeStore, mUwbMultichipData);
137         mUwbCountryCode =
138                 new UwbCountryCode(mContext, mNativeUwbManager, new Handler(mLooper), this);
139         mUwbMetrics = new UwbMetrics(this);
140         mDeviceConfigFacade = new DeviceConfigFacade(new Handler(mLooper), mContext);
141         UwbConfigurationManager uwbConfigurationManager =
142                 new UwbConfigurationManager(mNativeUwbManager, this);
143         UwbSessionNotificationManager uwbSessionNotificationManager =
144                 new UwbSessionNotificationManager(this);
145         UwbAdvertiseManager uwbAdvertiseManager = new UwbAdvertiseManager(this,
146                 mDeviceConfigFacade);
147         mUwbSessionManager =
148                 new UwbSessionManager(uwbConfigurationManager, mNativeUwbManager, mUwbMetrics,
149                         uwbAdvertiseManager, uwbSessionNotificationManager, this,
150                         mContext.getSystemService(AlarmManager.class),
151                         mContext.getSystemService(ActivityManager.class),
152                         mLooper);
153         mUwbService = new UwbServiceCore(mContext, mNativeUwbManager, mUwbMetrics,
154                 mUwbCountryCode, mUwbSessionManager, uwbConfigurationManager, this, mLooper);
155         mSystemBuildProperties = new SystemBuildProperties();
156         mUwbDiagnostics = new UwbDiagnostics(mContext, this, mSystemBuildProperties);
157         mFeatureFlags = new com.android.uwb.flags.FeatureFlagsImpl();
158     }
159 
getFeatureFlags()160     public FeatureFlags getFeatureFlags() {
161         return mFeatureFlags;
162     }
163 
getUwbServiceLooper()164     public Looper getUwbServiceLooper() {
165         return mLooper;
166     }
167 
getUserManager()168     public UserManager getUserManager() {
169         return mUserManager;
170     }
171 
172     /**
173      * Construct an instance of {@link ServiceProfileData}.
174      */
makeServiceProfileData(ServiceProfileData.DataSource dataSource)175     public ServiceProfileData makeServiceProfileData(ServiceProfileData.DataSource dataSource) {
176         return new ServiceProfileData(dataSource);
177     }
178 
getProfileManager()179     public ProfileManager getProfileManager() {
180         return mProfileManager;
181     }
182 
getUwbConfigStore()183     public UwbConfigStore getUwbConfigStore() {
184         return mUwbConfigStore;
185     }
186 
getUwbSettingsStore()187     public UwbSettingsStore getUwbSettingsStore() {
188         return mUwbSettingsStore;
189     }
190 
getNativeUwbManager()191     public NativeUwbManager getNativeUwbManager() {
192         return mNativeUwbManager;
193     }
194 
getUwbCountryCode()195     public UwbCountryCode getUwbCountryCode() {
196         return mUwbCountryCode;
197     }
198 
getUciLogModeStore()199     public UciLogModeStore getUciLogModeStore() {
200         return mUciLogModeStore;
201     }
202 
getUwbMetrics()203     public UwbMetrics getUwbMetrics() {
204         return mUwbMetrics;
205     }
206 
getDeviceConfigFacade()207     public DeviceConfigFacade getDeviceConfigFacade() {
208         return mDeviceConfigFacade;
209     }
210 
getMultichipData()211     public UwbMultichipData getMultichipData() {
212         return mUwbMultichipData;
213     }
214 
getUwbServiceCore()215     public UwbServiceCore getUwbServiceCore() {
216         return mUwbService;
217     }
218 
getUwbDiagnostics()219     public UwbDiagnostics getUwbDiagnostics() {
220         return mUwbDiagnostics;
221     }
222 
getUwbSessionManager()223     public UwbSessionManager getUwbSessionManager() {
224         return mUwbSessionManager;
225     }
226 
227     /**
228      * Create a UwbShellCommand instance.
229      */
makeUwbShellCommand(UwbServiceImpl uwbService)230     public UwbShellCommand makeUwbShellCommand(UwbServiceImpl uwbService) {
231         return new UwbShellCommand(this, uwbService, mContext);
232     }
233 
234     /**
235      * Creates a Geocoder.
236      */
237     @Nullable
makeGeocoder()238     public Geocoder makeGeocoder() {
239         return new Geocoder(mContext);
240     }
241 
242     /**
243      * Returns whether geocoder is supported on this device or not.
244      */
isGeocoderPresent()245     public boolean isGeocoderPresent() {
246         return Geocoder.isPresent();
247     }
248 
249     /**
250      * Throws security exception if the UWB_RANGING permission is not granted for the calling app.
251      *
252      * <p>Should be used in situations where the app op should not be noted.
253      */
enforceUwbRangingPermissionForPreflight( @onNull AttributionSource attributionSource)254     public void enforceUwbRangingPermissionForPreflight(
255             @NonNull AttributionSource attributionSource) {
256         if (!attributionSource.checkCallingUid()) {
257             throw new SecurityException("Invalid attribution source " + attributionSource
258                     + ", callingUid: " + Binder.getCallingUid());
259         }
260         int permissionCheckResult = mPermissionManager.checkPermissionForPreflight(
261                 UWB_RANGING, attributionSource);
262         if (permissionCheckResult != PERMISSION_GRANTED) {
263             throw new SecurityException("Caller does not hold UWB_RANGING permission");
264         }
265     }
266 
267     /**
268      * Returns true if the UWB_RANGING permission is granted for the calling app.
269      *
270      * <p>Used for checking permission before first data delivery for the session.
271      */
checkUwbRangingPermissionForStartDataDelivery( @onNull AttributionSource attributionSource, @NonNull String message)272     public boolean checkUwbRangingPermissionForStartDataDelivery(
273             @NonNull AttributionSource attributionSource, @NonNull String message) {
274         int permissionCheckResult = mPermissionManager.checkPermissionForStartDataDelivery(
275                 UWB_RANGING, attributionSource, message);
276         return permissionCheckResult == PERMISSION_GRANTED;
277     }
278 
279     /** Indicate permission manager that the ranging session is done or stopped. */
finishUwbRangingPermissionForDataDelivery( @onNull AttributionSource attributionSource)280     public void finishUwbRangingPermissionForDataDelivery(
281             @NonNull AttributionSource attributionSource) {
282         mPermissionManager.finishDataDelivery(UWB_RANGING, attributionSource);
283     }
284 
285     /**
286      * Get device protected storage dir for the UWB apex.
287      */
288     @NonNull
getDeviceProtectedDataDir()289     public static File getDeviceProtectedDataDir() {
290         return ApexEnvironment.getApexEnvironment(APEX_NAME).getDeviceProtectedDataDir();
291     }
292 
293     /**
294      * Get integer value from Settings.
295      *
296      * @throws Settings.SettingNotFoundException
297      */
getGlobalSettingsInt(@onNull String key)298     public int getGlobalSettingsInt(@NonNull String key) throws Settings.SettingNotFoundException {
299         return Settings.Global.getInt(mContext.getContentResolver(), key);
300     }
301 
302     /**
303      * Get integer value from Settings.
304      */
getGlobalSettingsInt(@onNull String key, int defValue)305     public int getGlobalSettingsInt(@NonNull String key, int defValue) {
306         return Settings.Global.getInt(mContext.getContentResolver(), key, defValue);
307     }
308 
309     /**
310      * Get string value from Settings.
311      */
312     @Nullable
getGlobalSettingsString(@onNull String key)313     public String getGlobalSettingsString(@NonNull String key) {
314         return Settings.Global.getString(mContext.getContentResolver(), key);
315     }
316 
317     /**
318      * Helper method for classes to register a ContentObserver
319      * {@see ContentResolver#registerContentObserver(Uri,boolean,ContentObserver)}.
320      */
registerContentObserver(Uri uri, boolean notifyForDescendants, ContentObserver contentObserver)321     public void registerContentObserver(Uri uri, boolean notifyForDescendants,
322             ContentObserver contentObserver) {
323         mContext.getContentResolver().registerContentObserver(uri, notifyForDescendants,
324                 contentObserver);
325     }
326 
327     /**
328      * Helper method for classes to unregister a ContentObserver
329      * {@see ContentResolver#unregisterContentObserver(ContentObserver)}.
330      */
unregisterContentObserver(ContentObserver contentObserver)331     public void unregisterContentObserver(ContentObserver contentObserver) {
332         mContext.getContentResolver().unregisterContentObserver(contentObserver);
333     }
334 
335     /**
336      * Uwb user specific folder.
337      */
getCredentialProtectedDataDirForUser(int userId)338     public static File getCredentialProtectedDataDirForUser(int userId) {
339         return ApexEnvironment.getApexEnvironment(APEX_NAME)
340                 .getCredentialProtectedDataDirForUser(UserHandle.of(userId));
341     }
342 
343     /**
344      * Get the current time of the clock in milliseconds.
345      *
346      * @return Current time in milliseconds.
347      */
getWallClockMillis()348     public long getWallClockMillis() {
349         return System.currentTimeMillis();
350     }
351 
352     /**
353      * Returns milliseconds since boot, including time spent in sleep.
354      *
355      * @return Current time since boot in milliseconds.
356      */
getElapsedSinceBootMillis()357     public long getElapsedSinceBootMillis() {
358         return SystemClock.elapsedRealtime();
359     }
360 
361     /**
362      * Returns nanoseconds since boot, including time spent in sleep.
363      *
364      * @return Current time since boot in milliseconds.
365      */
getElapsedSinceBootNanos()366     public long getElapsedSinceBootNanos() {
367         return SystemClock.elapsedRealtimeNanos();
368     }
369 
370     /**
371      * Is this a valid country code
372      *
373      * @param countryCode A 2-Character alphanumeric country code.
374      * @return true if the countryCode is valid, false otherwise.
375      */
isValidCountryCode(String countryCode)376     private static boolean isValidCountryCode(String countryCode) {
377         return countryCode != null && countryCode.length() == 2
378                 && countryCode.chars().allMatch(Character::isLetterOrDigit);
379     }
380 
381     /**
382      * Default country code stored in system property
383      *
384      * @return Country code if available, null otherwise.
385      */
getOemDefaultCountryCode()386     public String getOemDefaultCountryCode() {
387         String country = SystemProperties.get(BOOT_DEFAULT_UWB_COUNTRY_CODE);
388         return isValidCountryCode(country) ? country.toUpperCase(Locale.US) : null;
389     }
390 
391     /**
392      * Helper method creating a context based on the app's uid (to deal with multi user scenarios)
393      */
394     @Nullable
createPackageContextAsUser(int uid)395     private Context createPackageContextAsUser(int uid) {
396         Context userContext;
397         try {
398             userContext = mContext.createPackageContextAsUser(mContext.getPackageName(), 0,
399                     UserHandle.getUserHandleForUid(uid));
400         } catch (PackageManager.NameNotFoundException e) {
401             Log.e(TAG, "Unknown package name");
402             return null;
403         }
404         if (userContext == null) {
405             Log.e(TAG, "Unable to retrieve user context for " + uid);
406             return null;
407         }
408         return userContext;
409     }
410 
411     /** Helper method to check if the app is a system app. */
isSystemApp(int uid, @NonNull String packageName)412     public boolean isSystemApp(int uid, @NonNull String packageName) {
413         try {
414             ApplicationInfo info = createPackageContextAsUser(uid)
415                     .getPackageManager()
416                     .getApplicationInfo(packageName, 0);
417             return (info.flags & APP_INFO_FLAGS_SYSTEM_APP) != 0;
418         } catch (PackageManager.NameNotFoundException e) {
419             // In case of exception, assume unknown app (more strict checking)
420             // Note: This case will never happen since checkPackage is
421             // called to verify validity before checking App's version.
422             Log.e(TAG, "Failed to get the app info", e);
423         }
424         return false;
425     }
426 
427     /** Whether the uid is signed with the same key as the platform. */
isAppSignedWithPlatformKey(int uid)428     public boolean isAppSignedWithPlatformKey(int uid) {
429         return mContext.getPackageManager().checkSignatures(uid, Process.SYSTEM_UID)
430                 == PackageManager.SIGNATURE_MATCH;
431     }
432 
433     private static Map<String, Integer> sOverridePackageImportance = new HashMap();
setOverridePackageImportance(String packageName, int importance)434     public void setOverridePackageImportance(String packageName, int importance) {
435         sOverridePackageImportance.put(packageName, importance);
436     }
resetOverridePackageImportance(String packageName)437     public void resetOverridePackageImportance(String packageName) {
438         sOverridePackageImportance.remove(packageName);
439     }
440 
441     /** Helper method to retrieve app importance. */
getPackageImportance(int uid, @NonNull String packageName)442     private int getPackageImportance(int uid, @NonNull String packageName) {
443         if (sOverridePackageImportance.containsKey(packageName)) {
444             Log.w(TAG, "Overriding package importance for testing");
445             return sOverridePackageImportance.get(packageName);
446         }
447         try {
448             return createPackageContextAsUser(uid)
449                     .getSystemService(ActivityManager.class)
450                     .getPackageImportance(packageName);
451         } catch (SecurityException e) {
452             Log.e(TAG, "Failed to retrieve the app importance", e);
453             return ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE;
454         }
455     }
456 
457     /** Helper method to check if the app is from foreground app/service. */
isForegroundAppOrServiceImportance(int importance)458     public static boolean isForegroundAppOrServiceImportance(int importance) {
459         return importance <= ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE;
460     }
461 
462     /** Helper method to check if the app is from foreground app/service. */
isForegroundAppOrService(int uid, @NonNull String packageName)463     public boolean isForegroundAppOrService(int uid, @NonNull String packageName) {
464         long identity = Binder.clearCallingIdentity();
465         try {
466             return isForegroundAppOrServiceImportance(getPackageImportance(uid, packageName));
467         } catch (SecurityException e) {
468             Log.e(TAG, "Failed to retrieve the app importance", e);
469             return false;
470         } finally {
471             Binder.restoreCallingIdentity(identity);
472         }
473     }
474 
475     /* Helps to mock the executor for tests */
runTaskOnSingleThreadExecutor(FutureTask<Integer> task, int timeoutMs)476     public int runTaskOnSingleThreadExecutor(FutureTask<Integer> task, int timeoutMs)
477             throws InterruptedException, TimeoutException, ExecutionException {
478         ExecutorService executor = Executors.newSingleThreadExecutor();
479         executor.submit(task);
480         try {
481             return task.get(timeoutMs, TimeUnit.MILLISECONDS);
482         } catch (TimeoutException e) {
483             executor.shutdownNow();
484             throw e;
485         }
486     }
487 
isMulticastListNtfV2Supported()488     public boolean isMulticastListNtfV2Supported() {
489         return mContext.getResources().getBoolean(
490                         com.android.uwb.resources.R.bool.is_multicast_list_update_ntf_v2_supported);
491     }
492 
isMulticastListRspV2Supported()493     public boolean isMulticastListRspV2Supported() {
494         return mContext.getResources().getBoolean(
495                         com.android.uwb.resources.R.bool.is_multicast_list_update_rsp_v2_supported);
496     }
497 
isCccSupportedTwoByteConfigIdLittleEndian()498     public boolean isCccSupportedTwoByteConfigIdLittleEndian() {
499         return mContext.getResources().getBoolean(
500                 com.android.uwb.resources.R.bool.ccc_two_byte_config_id_little_endian_supported
501         );
502     }
503 
isPrivilegedApp(int uid, String packageName)504     private boolean isPrivilegedApp(int uid, String packageName) {
505         return isSystemApp(uid, packageName) || isAppSignedWithPlatformKey(uid);
506     }
507 
508     /**
509      * Check the attribution source chain to check if there are any 3p apps.
510      * @return AttributionSource of first non-system app found in the chain, null otherwise.
511      */
512     @Nullable
getAnyNonPrivilegedAppInAttributionSource( AttributionSource attributionSource)513     public AttributionSource getAnyNonPrivilegedAppInAttributionSource(
514             AttributionSource attributionSource) {
515         // Iterate attribution source chain to ensure that there is no non-fg 3p app in the
516         // request.
517         while (attributionSource != null) {
518             int uid = attributionSource.getUid();
519             String packageName = attributionSource.getPackageName();
520             if (!isPrivilegedApp(uid, packageName)) {
521                 return attributionSource;
522             }
523             attributionSource = attributionSource.getNext();
524         }
525         return null;
526     }
527 
528     /**
529      * Gets the configured pose source, which is reference counted. If there are no references
530      * to the pose source, one will be created based on the device configuration. This may
531      * @return A shared or new pose source, or null if one is not configured or available.
532      */
acquirePoseSource()533     public IPoseSource acquirePoseSource() {
534         mPoseLock.lock();
535         try {
536             // Keep our ref counts accurate because isEnableFilters can change at runtime.
537             mPoseSourceRefCount++;
538 
539             if (!getDeviceConfigFacade().isEnableFilters()) {
540                 return null;
541             }
542 
543             if (mDefaultPoseSource != null) {
544                 // Already have a pose source.
545                 return mDefaultPoseSource;
546             }
547 
548             switch (mDeviceConfigFacade.getPoseSourceType()) {
549                 case NONE:
550                     mDefaultPoseSource = null;
551                     break;
552                 case ROTATION_VECTOR:
553                     mDefaultPoseSource = new RotationPoseSource(mContext, 100);
554                     break;
555                 case GYRO:
556                     mDefaultPoseSource = new GyroPoseSource(mContext, 100);
557                     break;
558                 case SIXDOF:
559                     mDefaultPoseSource = new SixDofPoseSource(mContext, 100);
560                     break;
561                 case DOUBLE_INTEGRATE:
562                     mDefaultPoseSource = new IntegPoseSource(mContext, 100);
563                     break;
564             }
565 
566             return mDefaultPoseSource;
567         } catch (Exception ex) {
568             Log.e(TAG, "Unable to create the configured UWB pose source: "
569                     + ex.getMessage());
570             mPoseSourceRefCount--;
571             return null;
572         } finally {
573             mPoseLock.unlock();
574         }
575     }
576 
577     /**
578      * Decrements the reference counts to the default pose source, and closes the source once the
579      * count reaches zero. This must be called once for each time acquirePoseSource() is called.
580      */
releasePoseSource()581     public void releasePoseSource() {
582         mPoseLock.lock();
583         try {
584             // Keep our ref counts accurate because isEnableFilters can change at runtime.
585             --mPoseSourceRefCount;
586             if (mPoseSourceRefCount <= 0 && mDefaultPoseSource != null) {
587                 mDefaultPoseSource.close();
588                 mDefaultPoseSource = null;
589             }
590         } finally {
591             mPoseLock.unlock();
592         }
593     }
594 
595     /**
596      * Creates a filter engine using the default pose source. A default pose source must first be
597      * acquired with {@link #acquirePoseSource()}.
598      *
599      * @return A fully configured filter engine, or null if filtering is disabled.
600      */
createFilterEngine(IPoseSource poseSource)601     public UwbFilterEngine createFilterEngine(IPoseSource poseSource) {
602         DeviceConfigFacade cfg = getDeviceConfigFacade();
603         if (!cfg.isEnableFilters()) {
604             return null;
605         }
606 
607         // This could go wrong if the config flags or overlay have bad values.
608         try {
609             IFilter azimuthFilter = new MedAvgRotationFilter(
610                     cfg.getFilterAngleWindow(),
611                     cfg.getFilterAngleInliersPercent() / 100f);
612             IFilter elevationFilter = new MedAvgRotationFilter(
613                     cfg.getFilterAngleWindow(),
614                     cfg.getFilterAngleInliersPercent() / 100f);
615             IFilter distanceFilter = new MedAvgFilter(
616                     cfg.getFilterDistanceWindow(),
617                     cfg.getFilterDistanceInliersPercent() / 100f);
618 
619             PositionFilterImpl posFilter = new PositionFilterImpl(
620                     azimuthFilter,
621                     elevationFilter,
622                     distanceFilter);
623 
624             UwbFilterEngine.Builder builder = new UwbFilterEngine.Builder().setFilter(posFilter);
625 
626             if (poseSource != null) {
627                 builder.setPoseSource(poseSource);
628             }
629 
630             // Order is important.
631             if (cfg.isEnablePrimerEstElevation()) {
632                 builder.addPrimer(new ElevationPrimer());
633             }
634 
635             // AoAPrimer requires an elevation estimation in order to convert to spherical coords.
636             if (cfg.isEnablePrimerAoA()) {
637                 builder.addPrimer(new AoaPrimer());
638             }
639 
640             // Fov requires an elevation and a spherical coord.
641             if (cfg.isEnablePrimerFov()) {
642                 builder.addPrimer(new FovPrimer((float) toRadians(cfg.getPrimerFovDegree())));
643             }
644 
645             // Back azimuth detection requires true spherical.
646             if (cfg.isEnableBackAzimuth()) {
647                 builder.addPrimer(new BackAzimuthPrimer(
648                         cfg.getFrontAzimuthRadiansPerSecond(),
649                         cfg.getBackAzimuthRadiansPerSecond(),
650                         cfg.getBackAzimuthWindow(),
651                         cfg.isEnableBackAzimuthMasking(),
652                         cfg.getMirrorScoreStdRadians(),
653                         cfg.getBackNoiseInfluenceCoeff()));
654             }
655 
656             return builder.build();
657         } catch (Exception ex) {
658             Log.e(TAG, "Unable to create UWB filter engine: " + ex.getMessage());
659             return null;
660         }
661     }
662 }
663