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