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