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