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