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.audio; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.content.Context; 22 import android.hardware.Sensor; 23 import android.hardware.SensorManager; 24 import android.media.AudioAttributes; 25 import android.media.AudioDeviceAttributes; 26 import android.media.AudioDeviceInfo; 27 import android.media.AudioFormat; 28 import android.media.AudioSystem; 29 import android.media.INativeSpatializerCallback; 30 import android.media.ISpatializer; 31 import android.media.ISpatializerCallback; 32 import android.media.ISpatializerHeadToSoundStagePoseCallback; 33 import android.media.ISpatializerHeadTrackerAvailableCallback; 34 import android.media.ISpatializerHeadTrackingCallback; 35 import android.media.ISpatializerHeadTrackingModeCallback; 36 import android.media.ISpatializerOutputCallback; 37 import android.media.MediaMetrics; 38 import android.media.SpatializationLevel; 39 import android.media.SpatializationMode; 40 import android.media.Spatializer; 41 import android.media.SpatializerHeadTrackingMode; 42 import android.os.RemoteCallbackList; 43 import android.os.RemoteException; 44 import android.text.TextUtils; 45 import android.util.Log; 46 import android.util.Pair; 47 import android.util.SparseIntArray; 48 49 import com.android.internal.annotations.GuardedBy; 50 51 import java.io.PrintWriter; 52 import java.util.ArrayList; 53 import java.util.List; 54 import java.util.Locale; 55 import java.util.Objects; 56 import java.util.UUID; 57 58 /** 59 * A helper class to manage Spatializer related functionality 60 */ 61 public class SpatializerHelper { 62 63 private static final String TAG = "AS.SpatializerHelper"; 64 private static final boolean DEBUG = true; 65 private static final boolean DEBUG_MORE = false; 66 logd(String s)67 private static void logd(String s) { 68 if (DEBUG) { 69 Log.i(TAG, s); 70 } 71 } 72 73 private final @NonNull AudioSystemAdapter mASA; 74 private final @NonNull AudioService mAudioService; 75 private @Nullable SensorManager mSensorManager; 76 77 //------------------------------------------------------------ 78 79 private static final SparseIntArray SPAT_MODE_FOR_DEVICE_TYPE = new SparseIntArray(15) { 80 { 81 append(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, SpatializationMode.SPATIALIZER_TRANSAURAL); 82 append(AudioDeviceInfo.TYPE_WIRED_HEADSET, SpatializationMode.SPATIALIZER_BINAURAL); 83 append(AudioDeviceInfo.TYPE_WIRED_HEADPHONES, SpatializationMode.SPATIALIZER_BINAURAL); 84 // assumption for A2DP: mostly headsets 85 append(AudioDeviceInfo.TYPE_BLUETOOTH_A2DP, SpatializationMode.SPATIALIZER_BINAURAL); 86 append(AudioDeviceInfo.TYPE_DOCK, SpatializationMode.SPATIALIZER_TRANSAURAL); 87 append(AudioDeviceInfo.TYPE_USB_ACCESSORY, SpatializationMode.SPATIALIZER_TRANSAURAL); 88 append(AudioDeviceInfo.TYPE_USB_DEVICE, SpatializationMode.SPATIALIZER_TRANSAURAL); 89 append(AudioDeviceInfo.TYPE_USB_HEADSET, SpatializationMode.SPATIALIZER_BINAURAL); 90 append(AudioDeviceInfo.TYPE_LINE_ANALOG, SpatializationMode.SPATIALIZER_TRANSAURAL); 91 append(AudioDeviceInfo.TYPE_LINE_DIGITAL, SpatializationMode.SPATIALIZER_TRANSAURAL); 92 append(AudioDeviceInfo.TYPE_AUX_LINE, SpatializationMode.SPATIALIZER_TRANSAURAL); 93 append(AudioDeviceInfo.TYPE_HEARING_AID, SpatializationMode.SPATIALIZER_BINAURAL); 94 append(AudioDeviceInfo.TYPE_BLE_HEADSET, SpatializationMode.SPATIALIZER_BINAURAL); 95 append(AudioDeviceInfo.TYPE_BLE_SPEAKER, SpatializationMode.SPATIALIZER_TRANSAURAL); 96 // assumption that BLE broadcast would be mostly consumed on headsets 97 append(AudioDeviceInfo.TYPE_BLE_BROADCAST, SpatializationMode.SPATIALIZER_BINAURAL); 98 } 99 }; 100 101 private static final int[] WIRELESS_TYPES = { AudioDeviceInfo.TYPE_BLUETOOTH_SCO, 102 AudioDeviceInfo.TYPE_BLUETOOTH_A2DP, 103 AudioDeviceInfo.TYPE_BLE_HEADSET, 104 AudioDeviceInfo.TYPE_BLE_SPEAKER, 105 AudioDeviceInfo.TYPE_BLE_BROADCAST 106 }; 107 108 // Spatializer state machine 109 /*package*/ static final int STATE_UNINITIALIZED = 0; 110 /*package*/ static final int STATE_NOT_SUPPORTED = 1; 111 /*package*/ static final int STATE_DISABLED_UNAVAILABLE = 3; 112 /*package*/ static final int STATE_ENABLED_UNAVAILABLE = 4; 113 /*package*/ static final int STATE_ENABLED_AVAILABLE = 5; 114 /*package*/ static final int STATE_DISABLED_AVAILABLE = 6; 115 private int mState = STATE_UNINITIALIZED; 116 117 private boolean mFeatureEnabled = false; 118 /** current level as reported by native Spatializer in callback */ 119 private int mSpatLevel = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE; 120 private int mCapableSpatLevel = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE; 121 private boolean mTransauralSupported = false; 122 private boolean mBinauralSupported = false; 123 private boolean mIsHeadTrackingSupported = false; 124 private int[] mSupportedHeadTrackingModes = new int[0]; 125 private int mActualHeadTrackingMode = Spatializer.HEAD_TRACKING_MODE_UNSUPPORTED; 126 private int mDesiredHeadTrackingMode = Spatializer.HEAD_TRACKING_MODE_RELATIVE_WORLD; 127 private boolean mHeadTrackerAvailable = false; 128 /** 129 * The desired head tracking mode when enabling head tracking, tracks mDesiredHeadTrackingMode, 130 * except when head tracking gets disabled through setting the desired mode to 131 * {@link Spatializer#HEAD_TRACKING_MODE_DISABLED}. 132 */ 133 private int mDesiredHeadTrackingModeWhenEnabled = Spatializer.HEAD_TRACKING_MODE_RELATIVE_WORLD; 134 private int mSpatOutput = 0; 135 private @Nullable ISpatializer mSpat; 136 private @Nullable SpatializerCallback mSpatCallback; 137 private @Nullable SpatializerHeadTrackingCallback mSpatHeadTrackingCallback = 138 new SpatializerHeadTrackingCallback(); 139 private @Nullable HelperDynamicSensorCallback mDynSensorCallback; 140 141 // default attributes and format that determine basic availability of spatialization 142 private static final AudioAttributes DEFAULT_ATTRIBUTES = new AudioAttributes.Builder() 143 .setUsage(AudioAttributes.USAGE_MEDIA) 144 .build(); 145 private static final AudioFormat DEFAULT_FORMAT = new AudioFormat.Builder() 146 .setEncoding(AudioFormat.ENCODING_PCM_16BIT) 147 .setSampleRate(48000) 148 .setChannelMask(AudioFormat.CHANNEL_OUT_5POINT1) 149 .build(); 150 // device array to store the routing for the default attributes and format, initialized to 151 // an empty list as routing hasn't been established yet 152 private static ArrayList<AudioDeviceAttributes> sRoutingDevices = new ArrayList<>(0); 153 154 //--------------------------------------------------------------- 155 // audio device compatibility / enabled 156 /** 157 * List of device types that can be used on this device with Spatial Audio. 158 * It is initialized based on the transaural/binaural capabilities 159 * of the effect. 160 */ 161 private final ArrayList<Integer> mSACapableDeviceTypes = new ArrayList<>(0); 162 163 /** 164 * List of devices where Spatial Audio is possible. Each device can be enabled or disabled 165 * (== user choice to use or not) 166 */ 167 @GuardedBy("this") 168 private final ArrayList<SADeviceState> mSADevices = new ArrayList<>(0); 169 170 //------------------------------------------------------ 171 // initialization 172 @SuppressWarnings("StaticAssignmentInConstructor") SpatializerHelper(@onNull AudioService mother, @NonNull AudioSystemAdapter asa, boolean binauralEnabledDefault, boolean transauralEnabledDefault, boolean headTrackingEnabledDefault)173 SpatializerHelper(@NonNull AudioService mother, @NonNull AudioSystemAdapter asa, 174 boolean binauralEnabledDefault, 175 boolean transauralEnabledDefault, 176 boolean headTrackingEnabledDefault) { 177 mAudioService = mother; 178 mASA = asa; 179 // "StaticAssignmentInConstructor" warning is suppressed as the SpatializerHelper being 180 // constructed here is the factory for SADeviceState, thus SADeviceState and its 181 // private static field sHeadTrackingEnabledDefault should never be accessed directly. 182 SADeviceState.sBinauralEnabledDefault = binauralEnabledDefault; 183 SADeviceState.sTransauralEnabledDefault = transauralEnabledDefault; 184 SADeviceState.sHeadTrackingEnabledDefault = headTrackingEnabledDefault; 185 } 186 init(boolean effectExpected, @Nullable String settings)187 synchronized void init(boolean effectExpected, @Nullable String settings) { 188 loglogi("init effectExpected=" + effectExpected); 189 if (!effectExpected) { 190 loglogi("init(): setting state to STATE_NOT_SUPPORTED due to effect not expected"); 191 mState = STATE_NOT_SUPPORTED; 192 return; 193 } 194 if (mState != STATE_UNINITIALIZED) { 195 throw new IllegalStateException(logloge("init() called in state " + mState)); 196 } 197 // is there a spatializer? 198 mSpatCallback = new SpatializerCallback(); 199 final ISpatializer spat = AudioSystem.getSpatializer(mSpatCallback); 200 if (spat == null) { 201 loglogi("init(): No Spatializer found"); 202 mState = STATE_NOT_SUPPORTED; 203 return; 204 } 205 // capabilities of spatializer? 206 resetCapabilities(); 207 208 try { 209 byte[] levels = spat.getSupportedLevels(); 210 if (levels == null 211 || levels.length == 0 212 || (levels.length == 1 213 && levels[0] == Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE)) { 214 logloge("init(): found Spatializer is useless"); 215 mState = STATE_NOT_SUPPORTED; 216 return; 217 } 218 for (byte level : levels) { 219 loglogi("init(): found support for level: " + level); 220 if (level == Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_MULTICHANNEL) { 221 loglogi("init(): setting capable level to LEVEL_MULTICHANNEL"); 222 mCapableSpatLevel = level; 223 break; 224 } 225 } 226 227 // Note: head tracking support must be initialized before spatialization modes as 228 // addCompatibleAudioDevice() calls onRoutingUpdated() which will initialize the 229 // sensors according to mIsHeadTrackingSupported. 230 mIsHeadTrackingSupported = spat.isHeadTrackingSupported(); 231 if (mIsHeadTrackingSupported) { 232 final byte[] values = spat.getSupportedHeadTrackingModes(); 233 ArrayList<Integer> list = new ArrayList<>(0); 234 for (byte value : values) { 235 switch (value) { 236 case SpatializerHeadTrackingMode.OTHER: 237 case SpatializerHeadTrackingMode.DISABLED: 238 // not expected here, skip 239 break; 240 case SpatializerHeadTrackingMode.RELATIVE_WORLD: 241 case SpatializerHeadTrackingMode.RELATIVE_SCREEN: 242 list.add(headTrackingModeTypeToSpatializerInt(value)); 243 break; 244 default: 245 Log.e(TAG, "Unexpected head tracking mode:" + value, 246 new IllegalArgumentException("invalid mode")); 247 break; 248 } 249 } 250 mSupportedHeadTrackingModes = new int[list.size()]; 251 for (int i = 0; i < list.size(); i++) { 252 mSupportedHeadTrackingModes[i] = list.get(i); 253 } 254 mActualHeadTrackingMode = 255 headTrackingModeTypeToSpatializerInt(spat.getActualHeadTrackingMode()); 256 } else { 257 mDesiredHeadTrackingModeWhenEnabled = Spatializer.HEAD_TRACKING_MODE_UNSUPPORTED; 258 mDesiredHeadTrackingMode = Spatializer.HEAD_TRACKING_MODE_UNSUPPORTED; 259 } 260 261 byte[] spatModes = spat.getSupportedModes(); 262 for (byte mode : spatModes) { 263 switch (mode) { 264 case SpatializationMode.SPATIALIZER_BINAURAL: 265 mBinauralSupported = true; 266 break; 267 case SpatializationMode.SPATIALIZER_TRANSAURAL: 268 mTransauralSupported = true; 269 break; 270 default: 271 logloge("init(): Spatializer reports unknown supported mode:" + mode); 272 break; 273 } 274 } 275 // if neither transaural nor binaural is supported, bail 276 if (!mBinauralSupported && !mTransauralSupported) { 277 mState = STATE_NOT_SUPPORTED; 278 return; 279 } 280 281 // initialize list of compatible devices 282 for (int i = 0; i < SPAT_MODE_FOR_DEVICE_TYPE.size(); i++) { 283 int mode = SPAT_MODE_FOR_DEVICE_TYPE.valueAt(i); 284 if ((mode == (int) SpatializationMode.SPATIALIZER_BINAURAL && mBinauralSupported) 285 || (mode == (int) SpatializationMode.SPATIALIZER_TRANSAURAL 286 && mTransauralSupported)) { 287 mSACapableDeviceTypes.add(SPAT_MODE_FOR_DEVICE_TYPE.keyAt(i)); 288 } 289 } 290 291 // When initialized from AudioService, the settings string will be non-null. 292 // Saved settings need to be applied after spatialization support is initialized above. 293 if (settings != null) { 294 setSADeviceSettings(settings); 295 } 296 297 // for both transaural / binaural, we are not forcing enablement as the init() method 298 // could have been called another time after boot in case of audioserver restart 299 addCompatibleAudioDevice( 300 new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_SPEAKER, ""), 301 false /*forceEnable*/); 302 // not force-enabling as this device might already be in the device list 303 addCompatibleAudioDevice( 304 new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_WIRED_HEADPHONE, ""), 305 false /*forceEnable*/); 306 } catch (RemoteException e) { 307 resetCapabilities(); 308 } finally { 309 if (spat != null) { 310 try { 311 spat.release(); 312 } catch (RemoteException e) { /* capable level remains at NONE*/ } 313 } 314 } 315 if (mCapableSpatLevel == Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE) { 316 mState = STATE_NOT_SUPPORTED; 317 return; 318 } 319 mState = STATE_DISABLED_UNAVAILABLE; 320 sRoutingDevices = getRoutingDevices(DEFAULT_ATTRIBUTES); 321 // note at this point mSpat is still not instantiated 322 } 323 324 /** 325 * Like init() but resets the state and spatializer levels 326 * @param featureEnabled 327 */ reset(boolean featureEnabled)328 synchronized void reset(boolean featureEnabled) { 329 loglogi("Resetting featureEnabled=" + featureEnabled); 330 releaseSpat(); 331 mState = STATE_UNINITIALIZED; 332 mSpatLevel = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE; 333 mActualHeadTrackingMode = Spatializer.HEAD_TRACKING_MODE_UNSUPPORTED; 334 init(true, null /* settings */); 335 setSpatializerEnabledInt(featureEnabled); 336 } 337 resetCapabilities()338 private void resetCapabilities() { 339 mCapableSpatLevel = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE; 340 mBinauralSupported = false; 341 mTransauralSupported = false; 342 mIsHeadTrackingSupported = false; 343 mSupportedHeadTrackingModes = new int[0]; 344 } 345 346 //------------------------------------------------------ 347 // routing monitoring onRoutingUpdated()348 synchronized void onRoutingUpdated() { 349 if (!mFeatureEnabled) { 350 return; 351 } 352 switch (mState) { 353 case STATE_UNINITIALIZED: 354 case STATE_NOT_SUPPORTED: 355 return; 356 case STATE_DISABLED_UNAVAILABLE: 357 case STATE_ENABLED_UNAVAILABLE: 358 case STATE_ENABLED_AVAILABLE: 359 case STATE_DISABLED_AVAILABLE: 360 break; 361 } 362 363 sRoutingDevices = getRoutingDevices(DEFAULT_ATTRIBUTES); 364 365 // check validity of routing information 366 if (sRoutingDevices.isEmpty()) { 367 logloge("onRoutingUpdated: no device, no Spatial Audio"); 368 setDispatchAvailableState(false); 369 // not changing the spatializer level as this is likely a transient state 370 return; 371 } 372 final AudioDeviceAttributes currentDevice = sRoutingDevices.get(0); 373 374 // is media routed to a new device? 375 if (isWireless(currentDevice.getType())) { 376 addWirelessDeviceIfNew(currentDevice); 377 } 378 379 // find if media device enabled / available 380 final Pair<Boolean, Boolean> enabledAvailable = evaluateState(currentDevice); 381 382 boolean able = false; 383 if (enabledAvailable.second) { 384 // available for Spatial audio, check w/ effect 385 able = canBeSpatializedOnDevice(DEFAULT_ATTRIBUTES, DEFAULT_FORMAT, sRoutingDevices); 386 loglogi("onRoutingUpdated: can spatialize media 5.1:" + able 387 + " on device:" + currentDevice); 388 setDispatchAvailableState(able); 389 } else { 390 loglogi("onRoutingUpdated: device:" + currentDevice 391 + " not available for Spatial Audio"); 392 setDispatchAvailableState(false); 393 } 394 395 boolean enabled = able && enabledAvailable.first; 396 if (enabled) { 397 loglogi("Enabling Spatial Audio since enabled for media device:" 398 + currentDevice); 399 } else { 400 loglogi("Disabling Spatial Audio since disabled for media device:" 401 + currentDevice); 402 } 403 if (mSpat != null) { 404 byte level = enabled ? (byte) Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_MULTICHANNEL 405 : (byte) Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE; 406 loglogi("Setting spatialization level to: " + level); 407 try { 408 mSpat.setLevel(level); 409 } catch (RemoteException e) { 410 Log.e(TAG, "onRoutingUpdated() Can't set spatializer level", e); 411 // try to recover by resetting the native spatializer state 412 postReset(); 413 return; 414 } 415 } 416 417 setDispatchFeatureEnabledState(enabled, "onRoutingUpdated"); 418 419 if (mDesiredHeadTrackingMode != Spatializer.HEAD_TRACKING_MODE_UNSUPPORTED 420 && mDesiredHeadTrackingMode != Spatializer.HEAD_TRACKING_MODE_DISABLED) { 421 postInitSensors(); 422 } 423 } 424 postReset()425 private void postReset() { 426 mAudioService.postResetSpatializer(); 427 } 428 429 //------------------------------------------------------ 430 // spatializer callback from native 431 private final class SpatializerCallback extends INativeSpatializerCallback.Stub { 432 onLevelChanged(byte level)433 public void onLevelChanged(byte level) { 434 loglogi("SpatializerCallback.onLevelChanged level:" + level); 435 synchronized (SpatializerHelper.this) { 436 mSpatLevel = spatializationLevelToSpatializerInt(level); 437 } 438 // TODO use reported spat level to change state 439 440 // init sensors 441 postInitSensors(); 442 } 443 onOutputChanged(int output)444 public void onOutputChanged(int output) { 445 loglogi("SpatializerCallback.onOutputChanged output:" + output); 446 int oldOutput; 447 synchronized (SpatializerHelper.this) { 448 oldOutput = mSpatOutput; 449 mSpatOutput = output; 450 } 451 if (oldOutput != output) { 452 dispatchOutputUpdate(output); 453 } 454 } 455 }; 456 457 //------------------------------------------------------ 458 // spatializer head tracking callback from native 459 private final class SpatializerHeadTrackingCallback 460 extends ISpatializerHeadTrackingCallback.Stub { onHeadTrackingModeChanged(byte mode)461 public void onHeadTrackingModeChanged(byte mode) { 462 int oldMode, newMode; 463 synchronized (this) { 464 oldMode = mActualHeadTrackingMode; 465 mActualHeadTrackingMode = headTrackingModeTypeToSpatializerInt(mode); 466 newMode = mActualHeadTrackingMode; 467 } 468 loglogi("SpatializerHeadTrackingCallback.onHeadTrackingModeChanged mode:" 469 + Spatializer.headtrackingModeToString(newMode)); 470 if (oldMode != newMode) { 471 dispatchActualHeadTrackingMode(newMode); 472 } 473 } 474 onHeadToSoundStagePoseUpdated(float[] headToStage)475 public void onHeadToSoundStagePoseUpdated(float[] headToStage) { 476 if (headToStage == null) { 477 Log.e(TAG, "SpatializerHeadTrackingCallback.onHeadToStagePoseUpdated" 478 + "null transform"); 479 return; 480 } 481 if (headToStage.length != 6) { 482 Log.e(TAG, "SpatializerHeadTrackingCallback.onHeadToStagePoseUpdated" 483 + " invalid transform length" + headToStage.length); 484 return; 485 } 486 if (DEBUG_MORE) { 487 // 6 values * (4 digits + 1 dot + 2 brackets) = 42 characters 488 StringBuilder t = new StringBuilder(42); 489 for (float val : headToStage) { 490 t.append("[").append(String.format(Locale.ENGLISH, "%.3f", val)).append("]"); 491 } 492 loglogi("SpatializerHeadTrackingCallback.onHeadToStagePoseUpdated headToStage:" 493 + t); 494 } 495 dispatchPoseUpdate(headToStage); 496 } 497 }; 498 499 //------------------------------------------------------ 500 // dynamic sensor callback 501 private final class HelperDynamicSensorCallback extends SensorManager.DynamicSensorCallback { 502 @Override onDynamicSensorConnected(Sensor sensor)503 public void onDynamicSensorConnected(Sensor sensor) { 504 postInitSensors(); 505 } 506 507 @Override onDynamicSensorDisconnected(Sensor sensor)508 public void onDynamicSensorDisconnected(Sensor sensor) { 509 postInitSensors(); 510 } 511 } 512 513 //------------------------------------------------------ 514 // compatible devices 515 /** 516 * Return the list of compatible devices, which reflects the device compatible with the 517 * spatializer effect, and those that have been explicitly enabled or disabled 518 * @return the list of compatible audio devices 519 */ getCompatibleAudioDevices()520 synchronized @NonNull List<AudioDeviceAttributes> getCompatibleAudioDevices() { 521 // build unionOf(mCompatibleAudioDevices, mEnabledDevice) - mDisabledAudioDevices 522 ArrayList<AudioDeviceAttributes> compatList = new ArrayList<>(); 523 for (SADeviceState deviceState : mSADevices) { 524 if (deviceState.mEnabled) { 525 compatList.add(deviceState.getAudioDeviceAttributes()); 526 } 527 } 528 return compatList; 529 } 530 addCompatibleAudioDevice(@onNull AudioDeviceAttributes ada)531 synchronized void addCompatibleAudioDevice(@NonNull AudioDeviceAttributes ada) { 532 addCompatibleAudioDevice(ada, true /*forceEnable*/); 533 } 534 535 /** 536 * Add the given device to the list of devices for which spatial audio will be available 537 * (== possible). 538 * @param ada the compatible device 539 * @param forceEnable if true, spatial audio is enabled for this device, regardless of whether 540 * this device was already in the list. If false, the enabled field is only 541 * set to true if the device is added to the list, otherwise, if already 542 * present, the setting is left untouched. 543 */ 544 @GuardedBy("this") addCompatibleAudioDevice(@onNull AudioDeviceAttributes ada, boolean forceEnable)545 private void addCompatibleAudioDevice(@NonNull AudioDeviceAttributes ada, 546 boolean forceEnable) { 547 if (!isDeviceCompatibleWithSpatializationModes(ada)) { 548 return; 549 } 550 loglogi("addCompatibleAudioDevice: dev=" + ada); 551 final SADeviceState deviceState = findDeviceStateForAudioDeviceAttributes(ada); 552 SADeviceState deviceUpdated = null; // non-null on update. 553 if (deviceState != null) { 554 if (forceEnable && !deviceState.mEnabled) { 555 deviceUpdated = deviceState; 556 deviceUpdated.mEnabled = true; 557 } 558 } else { 559 // When adding, force the device type to be a canonical one. 560 final int canonicalDeviceType = getCanonicalDeviceType(ada.getType()); 561 if (canonicalDeviceType == AudioDeviceInfo.TYPE_UNKNOWN) { 562 Log.e(TAG, "addCompatibleAudioDevice with incompatible AudioDeviceAttributes " 563 + ada); 564 return; 565 } 566 deviceUpdated = new SADeviceState(canonicalDeviceType, ada.getAddress()); 567 mSADevices.add(deviceUpdated); 568 } 569 if (deviceUpdated != null) { 570 onRoutingUpdated(); 571 mAudioService.persistSpatialAudioDeviceSettings(); 572 logDeviceState(deviceUpdated, "addCompatibleAudioDevice"); 573 } 574 } 575 576 private static final String METRICS_DEVICE_PREFIX = "audio.spatializer.device."; 577 578 // Device logging is accomplished in the Java Audio Service level. 579 // (System capabilities is done in the Native AudioPolicyManager level). 580 // 581 // There may be different devices with the same device type (aliasing). 582 // We always send the full device state info on each change. logDeviceState(SADeviceState deviceState, String event)583 private void logDeviceState(SADeviceState deviceState, String event) { 584 final int deviceType = AudioDeviceInfo.convertDeviceTypeToInternalDevice( 585 deviceState.mDeviceType); 586 final String deviceName = AudioSystem.getDeviceName(deviceType); 587 new MediaMetrics.Item(METRICS_DEVICE_PREFIX + deviceName) 588 .set(MediaMetrics.Property.ADDRESS, deviceState.mDeviceAddress) 589 .set(MediaMetrics.Property.ENABLED, deviceState.mEnabled ? "true" : "false") 590 .set(MediaMetrics.Property.EVENT, TextUtils.emptyIfNull(event)) 591 .set(MediaMetrics.Property.HAS_HEAD_TRACKER, 592 deviceState.mHasHeadTracker ? "true" : "false") // this may be updated later. 593 .set(MediaMetrics.Property.HEAD_TRACKER_ENABLED, 594 deviceState.mHeadTrackerEnabled ? "true" : "false") 595 .record(); 596 } 597 removeCompatibleAudioDevice(@onNull AudioDeviceAttributes ada)598 synchronized void removeCompatibleAudioDevice(@NonNull AudioDeviceAttributes ada) { 599 loglogi("removeCompatibleAudioDevice: dev=" + ada); 600 601 final SADeviceState deviceState = findDeviceStateForAudioDeviceAttributes(ada); 602 if (deviceState != null && deviceState.mEnabled) { 603 deviceState.mEnabled = false; 604 onRoutingUpdated(); 605 mAudioService.persistSpatialAudioDeviceSettings(); 606 logDeviceState(deviceState, "removeCompatibleAudioDevice"); 607 } 608 } 609 610 /** 611 * Returns a possibly aliased device type which is used 612 * for spatial audio settings (or TYPE_UNKNOWN if it doesn't exist). 613 */ getCanonicalDeviceType(int deviceType)614 private static @AudioDeviceInfo.AudioDeviceType int getCanonicalDeviceType(int deviceType) { 615 if (isWireless(deviceType)) return deviceType; 616 617 final int spatMode = SPAT_MODE_FOR_DEVICE_TYPE.get(deviceType, Integer.MIN_VALUE); 618 if (spatMode == SpatializationMode.SPATIALIZER_TRANSAURAL) { 619 return AudioDeviceInfo.TYPE_BUILTIN_SPEAKER; 620 } else if (spatMode == SpatializationMode.SPATIALIZER_BINAURAL) { 621 return AudioDeviceInfo.TYPE_WIRED_HEADPHONES; 622 } 623 return AudioDeviceInfo.TYPE_UNKNOWN; 624 } 625 626 /** 627 * Returns the Spatial Audio device state for an audio device attributes 628 * or null if it does not exist. 629 */ 630 @GuardedBy("this") 631 @Nullable findDeviceStateForAudioDeviceAttributes(AudioDeviceAttributes ada)632 private SADeviceState findDeviceStateForAudioDeviceAttributes(AudioDeviceAttributes ada) { 633 final int deviceType = ada.getType(); 634 final boolean isWireless = isWireless(deviceType); 635 final int canonicalDeviceType = getCanonicalDeviceType(deviceType); 636 637 for (SADeviceState deviceState : mSADevices) { 638 if (deviceState.mDeviceType == canonicalDeviceType 639 && (!isWireless || ada.getAddress().equals(deviceState.mDeviceAddress))) { 640 return deviceState; 641 } 642 } 643 return null; 644 } 645 646 /** 647 * Return if Spatial Audio is enabled and available for the given device 648 * @param ada 649 * @return a pair of boolean, 1/ enabled? 2/ available? 650 */ evaluateState(AudioDeviceAttributes ada)651 private synchronized Pair<Boolean, Boolean> evaluateState(AudioDeviceAttributes ada) { 652 final @AudioDeviceInfo.AudioDeviceType int deviceType = ada.getType(); 653 // is the device type capable of doing SA? 654 if (!mSACapableDeviceTypes.contains(deviceType)) { 655 Log.i(TAG, "Device incompatible with Spatial Audio dev:" + ada); 656 return new Pair<>(false, false); 657 } 658 // what spatialization mode to use for this device? 659 final int spatMode = SPAT_MODE_FOR_DEVICE_TYPE.get(deviceType, Integer.MIN_VALUE); 660 if (spatMode == Integer.MIN_VALUE) { 661 // error case, device not found 662 Log.e(TAG, "no spatialization mode found for device type:" + deviceType); 663 return new Pair<>(false, false); 664 } 665 final SADeviceState deviceState = findDeviceStateForAudioDeviceAttributes(ada); 666 if (deviceState == null) { 667 // no matching device state? 668 Log.i(TAG, "no spatialization device state found for Spatial Audio device:" + ada); 669 return new Pair<>(false, false); 670 } 671 // found the matching device state. 672 return new Pair<>(deviceState.mEnabled, true /* available */); 673 } 674 addWirelessDeviceIfNew(@onNull AudioDeviceAttributes ada)675 private synchronized void addWirelessDeviceIfNew(@NonNull AudioDeviceAttributes ada) { 676 if (!isDeviceCompatibleWithSpatializationModes(ada)) { 677 return; 678 } 679 if (findDeviceStateForAudioDeviceAttributes(ada) == null) { 680 // wireless device types should be canonical, but we translate to be sure. 681 final int canonicalDeviceType = getCanonicalDeviceType((ada.getType())); 682 if (canonicalDeviceType == AudioDeviceInfo.TYPE_UNKNOWN) { 683 Log.e(TAG, "addWirelessDeviceIfNew with incompatible AudioDeviceAttributes " 684 + ada); 685 return; 686 } 687 final SADeviceState deviceState = 688 new SADeviceState(canonicalDeviceType, ada.getAddress()); 689 mSADevices.add(deviceState); 690 mAudioService.persistSpatialAudioDeviceSettings(); 691 logDeviceState(deviceState, "addWirelessDeviceIfNew"); // may be updated later. 692 } 693 } 694 695 //------------------------------------------------------ 696 // states 697 isEnabled()698 synchronized boolean isEnabled() { 699 switch (mState) { 700 case STATE_UNINITIALIZED: 701 case STATE_NOT_SUPPORTED: 702 case STATE_DISABLED_UNAVAILABLE: 703 case STATE_DISABLED_AVAILABLE: 704 return false; 705 case STATE_ENABLED_UNAVAILABLE: 706 case STATE_ENABLED_AVAILABLE: 707 default: 708 return true; 709 } 710 } 711 isAvailable()712 synchronized boolean isAvailable() { 713 switch (mState) { 714 case STATE_UNINITIALIZED: 715 case STATE_NOT_SUPPORTED: 716 case STATE_ENABLED_UNAVAILABLE: 717 case STATE_DISABLED_UNAVAILABLE: 718 return false; 719 case STATE_DISABLED_AVAILABLE: 720 case STATE_ENABLED_AVAILABLE: 721 default: 722 return true; 723 } 724 } 725 isAvailableForDevice(@onNull AudioDeviceAttributes ada)726 synchronized boolean isAvailableForDevice(@NonNull AudioDeviceAttributes ada) { 727 if (ada.getRole() != AudioDeviceAttributes.ROLE_OUTPUT) { 728 return false; 729 } 730 return findDeviceStateForAudioDeviceAttributes(ada) != null; 731 } 732 canBeSpatializedOnDevice(@onNull AudioAttributes attributes, @NonNull AudioFormat format, @NonNull ArrayList<AudioDeviceAttributes> devices)733 private synchronized boolean canBeSpatializedOnDevice(@NonNull AudioAttributes attributes, 734 @NonNull AudioFormat format, @NonNull ArrayList<AudioDeviceAttributes> devices) { 735 if (devices.isEmpty()) { 736 return false; 737 } 738 if (isDeviceCompatibleWithSpatializationModes(devices.get(0))) { 739 AudioDeviceAttributes[] devArray = new AudioDeviceAttributes[devices.size()]; 740 return AudioSystem.canBeSpatialized(attributes, format, devices.toArray(devArray)); 741 } 742 return false; 743 } 744 isDeviceCompatibleWithSpatializationModes(@onNull AudioDeviceAttributes ada)745 private boolean isDeviceCompatibleWithSpatializationModes(@NonNull AudioDeviceAttributes ada) { 746 // modeForDevice will be neither transaural or binaural for devices that do not support 747 // spatial audio. For instance mono devices like earpiece, speaker safe or sco must 748 // not be included. 749 final byte modeForDevice = (byte) SPAT_MODE_FOR_DEVICE_TYPE.get(ada.getType(), 750 /*default when type not found*/ -1); 751 if ((modeForDevice == SpatializationMode.SPATIALIZER_BINAURAL && mBinauralSupported) 752 || (modeForDevice == SpatializationMode.SPATIALIZER_TRANSAURAL 753 && mTransauralSupported)) { 754 return true; 755 } 756 return false; 757 } 758 setFeatureEnabled(boolean enabled)759 synchronized void setFeatureEnabled(boolean enabled) { 760 loglogi("setFeatureEnabled(" + enabled + ") was featureEnabled:" + mFeatureEnabled); 761 if (mFeatureEnabled == enabled) { 762 return; 763 } 764 mFeatureEnabled = enabled; 765 if (mFeatureEnabled) { 766 if (mState == STATE_NOT_SUPPORTED) { 767 Log.e(TAG, "Can't enabled Spatial Audio, unsupported"); 768 return; 769 } 770 if (mState == STATE_UNINITIALIZED) { 771 init(true, null /* settings */); 772 } 773 setSpatializerEnabledInt(true); 774 } else { 775 setSpatializerEnabledInt(false); 776 } 777 } 778 setSpatializerEnabledInt(boolean enabled)779 synchronized void setSpatializerEnabledInt(boolean enabled) { 780 switch (mState) { 781 case STATE_UNINITIALIZED: 782 if (enabled) { 783 throw (new IllegalStateException("Can't enable when uninitialized")); 784 } 785 break; 786 case STATE_NOT_SUPPORTED: 787 if (enabled) { 788 Log.e(TAG, "Can't enable when unsupported"); 789 } 790 break; 791 case STATE_DISABLED_UNAVAILABLE: 792 case STATE_DISABLED_AVAILABLE: 793 if (enabled) { 794 createSpat(); 795 onRoutingUpdated(); 796 // onRoutingUpdated() can update the "enabled" state based on context 797 // and will call setDispatchFeatureEnabledState(). 798 } // else { nothing to do as already disabled } 799 break; 800 case STATE_ENABLED_UNAVAILABLE: 801 case STATE_ENABLED_AVAILABLE: 802 if (!enabled) { 803 releaseSpat(); 804 setDispatchFeatureEnabledState(false, "setSpatializerEnabledInt"); 805 } // else { nothing to do as already enabled } 806 break; 807 } 808 } 809 getCapableImmersiveAudioLevel()810 synchronized int getCapableImmersiveAudioLevel() { 811 return mCapableSpatLevel; 812 } 813 814 final RemoteCallbackList<ISpatializerCallback> mStateCallbacks = 815 new RemoteCallbackList<ISpatializerCallback>(); 816 registerStateCallback( @onNull ISpatializerCallback callback)817 synchronized void registerStateCallback( 818 @NonNull ISpatializerCallback callback) { 819 mStateCallbacks.register(callback); 820 } 821 unregisterStateCallback( @onNull ISpatializerCallback callback)822 synchronized void unregisterStateCallback( 823 @NonNull ISpatializerCallback callback) { 824 mStateCallbacks.unregister(callback); 825 } 826 827 /** 828 * Update the feature state, no-op if no change 829 * @param featureEnabled 830 */ setDispatchFeatureEnabledState(boolean featureEnabled, String source)831 private synchronized void setDispatchFeatureEnabledState(boolean featureEnabled, String source) 832 { 833 if (featureEnabled) { 834 switch (mState) { 835 case STATE_DISABLED_UNAVAILABLE: 836 mState = STATE_ENABLED_UNAVAILABLE; 837 break; 838 case STATE_DISABLED_AVAILABLE: 839 mState = STATE_ENABLED_AVAILABLE; 840 break; 841 case STATE_ENABLED_AVAILABLE: 842 case STATE_ENABLED_UNAVAILABLE: 843 // already enabled: no-op 844 loglogi("setDispatchFeatureEnabledState(" + featureEnabled 845 + ") no dispatch: mState:" 846 + spatStateString(mState) + " src:" + source); 847 return; 848 default: 849 throw (new IllegalStateException("Invalid mState:" + mState 850 + " for enabled true")); 851 } 852 } else { 853 switch (mState) { 854 case STATE_ENABLED_UNAVAILABLE: 855 mState = STATE_DISABLED_UNAVAILABLE; 856 break; 857 case STATE_ENABLED_AVAILABLE: 858 mState = STATE_DISABLED_AVAILABLE; 859 break; 860 case STATE_DISABLED_AVAILABLE: 861 case STATE_DISABLED_UNAVAILABLE: 862 // already disabled: no-op 863 loglogi("setDispatchFeatureEnabledState(" + featureEnabled 864 + ") no dispatch: mState:" + spatStateString(mState) 865 + " src:" + source); 866 return; 867 default: 868 throw (new IllegalStateException("Invalid mState:" + mState 869 + " for enabled false")); 870 } 871 } 872 loglogi("setDispatchFeatureEnabledState(" + featureEnabled 873 + ") mState:" + spatStateString(mState)); 874 final int nbCallbacks = mStateCallbacks.beginBroadcast(); 875 for (int i = 0; i < nbCallbacks; i++) { 876 try { 877 mStateCallbacks.getBroadcastItem(i) 878 .dispatchSpatializerEnabledChanged(featureEnabled); 879 } catch (RemoteException e) { 880 Log.e(TAG, "Error in dispatchSpatializerEnabledChanged", e); 881 } 882 } 883 mStateCallbacks.finishBroadcast(); 884 } 885 setDispatchAvailableState(boolean available)886 private synchronized void setDispatchAvailableState(boolean available) { 887 switch (mState) { 888 case STATE_UNINITIALIZED: 889 case STATE_NOT_SUPPORTED: 890 throw (new IllegalStateException( 891 "Should not update available state in state:" + mState)); 892 case STATE_DISABLED_UNAVAILABLE: 893 if (available) { 894 mState = STATE_DISABLED_AVAILABLE; 895 break; 896 } else { 897 // already in unavailable state 898 loglogi("setDispatchAvailableState(" + available 899 + ") no dispatch: mState:" + spatStateString(mState)); 900 return; 901 } 902 case STATE_ENABLED_UNAVAILABLE: 903 if (available) { 904 mState = STATE_ENABLED_AVAILABLE; 905 break; 906 } else { 907 // already in unavailable state 908 loglogi("setDispatchAvailableState(" + available 909 + ") no dispatch: mState:" + spatStateString(mState)); 910 return; 911 } 912 case STATE_DISABLED_AVAILABLE: 913 if (available) { 914 // already in available state 915 loglogi("setDispatchAvailableState(" + available 916 + ") no dispatch: mState:" + spatStateString(mState)); 917 return; 918 } else { 919 mState = STATE_DISABLED_UNAVAILABLE; 920 break; 921 } 922 case STATE_ENABLED_AVAILABLE: 923 if (available) { 924 // already in available state 925 loglogi("setDispatchAvailableState(" + available 926 + ") no dispatch: mState:" + spatStateString(mState)); 927 return; 928 } else { 929 mState = STATE_ENABLED_UNAVAILABLE; 930 break; 931 } 932 } 933 loglogi("setDispatchAvailableState(" + available + ") mState:" + spatStateString(mState)); 934 final int nbCallbacks = mStateCallbacks.beginBroadcast(); 935 for (int i = 0; i < nbCallbacks; i++) { 936 try { 937 mStateCallbacks.getBroadcastItem(i) 938 .dispatchSpatializerAvailableChanged(available); 939 } catch (RemoteException e) { 940 Log.e(TAG, "Error in dispatchSpatializerEnabledChanged", e); 941 } 942 } 943 mStateCallbacks.finishBroadcast(); 944 } 945 946 //------------------------------------------------------ 947 // native Spatializer management 948 949 /** 950 * precondition: mState == STATE_DISABLED_* 951 */ createSpat()952 private void createSpat() { 953 if (mSpat == null) { 954 mSpatCallback = new SpatializerCallback(); 955 mSpat = AudioSystem.getSpatializer(mSpatCallback); 956 try { 957 //TODO: register heatracking callback only when sensors are registered 958 if (mIsHeadTrackingSupported) { 959 mActualHeadTrackingMode = 960 headTrackingModeTypeToSpatializerInt(mSpat.getActualHeadTrackingMode()); 961 mSpat.registerHeadTrackingCallback(mSpatHeadTrackingCallback); 962 } 963 } catch (RemoteException e) { 964 Log.e(TAG, "Can't configure head tracking", e); 965 mState = STATE_NOT_SUPPORTED; 966 mCapableSpatLevel = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE; 967 mActualHeadTrackingMode = Spatializer.HEAD_TRACKING_MODE_UNSUPPORTED; 968 } 969 } 970 } 971 972 /** 973 * precondition: mState == STATE_ENABLED_* 974 */ releaseSpat()975 private void releaseSpat() { 976 if (mSpat != null) { 977 mSpatCallback = null; 978 try { 979 if (mIsHeadTrackingSupported) { 980 mSpat.registerHeadTrackingCallback(null); 981 } 982 mHeadTrackerAvailable = false; 983 mSpat.release(); 984 } catch (RemoteException e) { 985 Log.e(TAG, "Can't set release spatializer cleanly", e); 986 } 987 mSpat = null; 988 } 989 } 990 991 //------------------------------------------------------ 992 // virtualization capabilities canBeSpatialized( @onNull AudioAttributes attributes, @NonNull AudioFormat format)993 synchronized boolean canBeSpatialized( 994 @NonNull AudioAttributes attributes, @NonNull AudioFormat format) { 995 switch (mState) { 996 case STATE_UNINITIALIZED: 997 case STATE_NOT_SUPPORTED: 998 case STATE_ENABLED_UNAVAILABLE: 999 case STATE_DISABLED_UNAVAILABLE: 1000 logd("canBeSpatialized false due to state:" + mState); 1001 return false; 1002 case STATE_DISABLED_AVAILABLE: 1003 case STATE_ENABLED_AVAILABLE: 1004 break; 1005 } 1006 1007 // filter on AudioAttributes usage 1008 switch (attributes.getUsage()) { 1009 case AudioAttributes.USAGE_MEDIA: 1010 case AudioAttributes.USAGE_GAME: 1011 break; 1012 default: 1013 logd("canBeSpatialized false due to usage:" + attributes.getUsage()); 1014 return false; 1015 } 1016 1017 // going through adapter to take advantage of routing cache 1018 final ArrayList<AudioDeviceAttributes> devices = getRoutingDevices(attributes); 1019 if (devices.isEmpty()) { 1020 logloge("canBeSpatialized got no device for " + attributes); 1021 return false; 1022 } 1023 final boolean able = canBeSpatializedOnDevice(attributes, format, devices); 1024 logd("canBeSpatialized usage:" + attributes.getUsage() 1025 + " format:" + format.toLogFriendlyString() + " returning " + able); 1026 return able; 1027 } 1028 1029 //------------------------------------------------------ 1030 // head tracking 1031 final RemoteCallbackList<ISpatializerHeadTrackingModeCallback> mHeadTrackingModeCallbacks = 1032 new RemoteCallbackList<ISpatializerHeadTrackingModeCallback>(); 1033 registerHeadTrackingModeCallback( @onNull ISpatializerHeadTrackingModeCallback callback)1034 synchronized void registerHeadTrackingModeCallback( 1035 @NonNull ISpatializerHeadTrackingModeCallback callback) { 1036 mHeadTrackingModeCallbacks.register(callback); 1037 } 1038 unregisterHeadTrackingModeCallback( @onNull ISpatializerHeadTrackingModeCallback callback)1039 synchronized void unregisterHeadTrackingModeCallback( 1040 @NonNull ISpatializerHeadTrackingModeCallback callback) { 1041 mHeadTrackingModeCallbacks.unregister(callback); 1042 } 1043 1044 final RemoteCallbackList<ISpatializerHeadTrackerAvailableCallback> mHeadTrackerCallbacks = 1045 new RemoteCallbackList<>(); 1046 registerHeadTrackerAvailableCallback( @onNull ISpatializerHeadTrackerAvailableCallback cb, boolean register)1047 synchronized void registerHeadTrackerAvailableCallback( 1048 @NonNull ISpatializerHeadTrackerAvailableCallback cb, boolean register) { 1049 if (register) { 1050 mHeadTrackerCallbacks.register(cb); 1051 } else { 1052 mHeadTrackerCallbacks.unregister(cb); 1053 } 1054 } 1055 getSupportedHeadTrackingModes()1056 synchronized int[] getSupportedHeadTrackingModes() { 1057 return mSupportedHeadTrackingModes; 1058 } 1059 getActualHeadTrackingMode()1060 synchronized int getActualHeadTrackingMode() { 1061 return mActualHeadTrackingMode; 1062 } 1063 getDesiredHeadTrackingMode()1064 synchronized int getDesiredHeadTrackingMode() { 1065 return mDesiredHeadTrackingMode; 1066 } 1067 setGlobalTransform(@onNull float[] transform)1068 synchronized void setGlobalTransform(@NonNull float[] transform) { 1069 if (transform.length != 6) { 1070 throw new IllegalArgumentException("invalid array size" + transform.length); 1071 } 1072 if (!checkSpatializerForHeadTracking("setGlobalTransform")) { 1073 return; 1074 } 1075 try { 1076 mSpat.setGlobalTransform(transform); 1077 } catch (RemoteException e) { 1078 Log.e(TAG, "Error calling setGlobalTransform", e); 1079 } 1080 } 1081 recenterHeadTracker()1082 synchronized void recenterHeadTracker() { 1083 if (!checkSpatializerForHeadTracking("recenterHeadTracker")) { 1084 return; 1085 } 1086 try { 1087 mSpat.recenterHeadTracker(); 1088 } catch (RemoteException e) { 1089 Log.e(TAG, "Error calling recenterHeadTracker", e); 1090 } 1091 } 1092 setDisplayOrientation(float displayOrientation)1093 synchronized void setDisplayOrientation(float displayOrientation) { 1094 if (!checkSpatializer("setDisplayOrientation")) { 1095 return; 1096 } 1097 try { 1098 mSpat.setDisplayOrientation(displayOrientation); 1099 } catch (RemoteException e) { 1100 Log.e(TAG, "Error calling setDisplayOrientation", e); 1101 } 1102 } 1103 setFoldState(boolean folded)1104 synchronized void setFoldState(boolean folded) { 1105 if (!checkSpatializer("setFoldState")) { 1106 return; 1107 } 1108 try { 1109 mSpat.setFoldState(folded); 1110 } catch (RemoteException e) { 1111 Log.e(TAG, "Error calling setFoldState", e); 1112 } 1113 } 1114 setDesiredHeadTrackingMode(@patializer.HeadTrackingModeSet int mode)1115 synchronized void setDesiredHeadTrackingMode(@Spatializer.HeadTrackingModeSet int mode) { 1116 if (!checkSpatializerForHeadTracking("setDesiredHeadTrackingMode")) { 1117 return; 1118 } 1119 if (mode != Spatializer.HEAD_TRACKING_MODE_DISABLED) { 1120 mDesiredHeadTrackingModeWhenEnabled = mode; 1121 } 1122 try { 1123 if (mDesiredHeadTrackingMode != mode) { 1124 mDesiredHeadTrackingMode = mode; 1125 dispatchDesiredHeadTrackingMode(mode); 1126 } 1127 Log.i(TAG, "setDesiredHeadTrackingMode(" 1128 + Spatializer.headtrackingModeToString(mode) + ")"); 1129 mSpat.setDesiredHeadTrackingMode(spatializerIntToHeadTrackingModeType(mode)); 1130 } catch (RemoteException e) { 1131 Log.e(TAG, "Error calling setDesiredHeadTrackingMode", e); 1132 } 1133 } 1134 setHeadTrackerEnabled(boolean enabled, @NonNull AudioDeviceAttributes ada)1135 synchronized void setHeadTrackerEnabled(boolean enabled, @NonNull AudioDeviceAttributes ada) { 1136 if (!mIsHeadTrackingSupported) { 1137 Log.v(TAG, "no headtracking support, ignoring setHeadTrackerEnabled to " + enabled 1138 + " for " + ada); 1139 } 1140 final SADeviceState deviceState = findDeviceStateForAudioDeviceAttributes(ada); 1141 if (deviceState == null) return; 1142 if (!deviceState.mHasHeadTracker) { 1143 Log.e(TAG, "Called setHeadTrackerEnabled enabled:" + enabled 1144 + " device:" + ada + " on a device without headtracker"); 1145 return; 1146 } 1147 Log.i(TAG, "setHeadTrackerEnabled enabled:" + enabled + " device:" + ada); 1148 deviceState.mHeadTrackerEnabled = enabled; 1149 mAudioService.persistSpatialAudioDeviceSettings(); 1150 logDeviceState(deviceState, "setHeadTrackerEnabled"); 1151 1152 // check current routing to see if it affects the headtracking mode 1153 if (sRoutingDevices.isEmpty()) { 1154 logloge("setHeadTrackerEnabled: no device, bailing"); 1155 return; 1156 } 1157 final AudioDeviceAttributes currentDevice = sRoutingDevices.get(0); 1158 if (currentDevice.getType() == ada.getType() 1159 && currentDevice.getAddress().equals(ada.getAddress())) { 1160 setDesiredHeadTrackingMode(enabled ? mDesiredHeadTrackingModeWhenEnabled 1161 : Spatializer.HEAD_TRACKING_MODE_DISABLED); 1162 if (enabled && !mHeadTrackerAvailable) { 1163 postInitSensors(); 1164 } 1165 } 1166 } 1167 hasHeadTracker(@onNull AudioDeviceAttributes ada)1168 synchronized boolean hasHeadTracker(@NonNull AudioDeviceAttributes ada) { 1169 if (!mIsHeadTrackingSupported) { 1170 Log.v(TAG, "no headtracking support, hasHeadTracker always false for " + ada); 1171 return false; 1172 } 1173 final SADeviceState deviceState = findDeviceStateForAudioDeviceAttributes(ada); 1174 return deviceState != null && deviceState.mHasHeadTracker; 1175 } 1176 1177 /** 1178 * Configures device in list as having a head tracker 1179 * @param ada 1180 * @return true if the head tracker is enabled, false otherwise or if device not found 1181 */ setHasHeadTracker(@onNull AudioDeviceAttributes ada)1182 synchronized boolean setHasHeadTracker(@NonNull AudioDeviceAttributes ada) { 1183 if (!mIsHeadTrackingSupported) { 1184 Log.v(TAG, "no headtracking support, setHasHeadTracker always false for " + ada); 1185 return false; 1186 } 1187 final SADeviceState deviceState = findDeviceStateForAudioDeviceAttributes(ada); 1188 if (deviceState != null) { 1189 if (!deviceState.mHasHeadTracker) { 1190 deviceState.mHasHeadTracker = true; 1191 mAudioService.persistSpatialAudioDeviceSettings(); 1192 logDeviceState(deviceState, "setHasHeadTracker"); 1193 } 1194 return deviceState.mHeadTrackerEnabled; 1195 } 1196 Log.e(TAG, "setHasHeadTracker: device not found for:" + ada); 1197 return false; 1198 } 1199 isHeadTrackerEnabled(@onNull AudioDeviceAttributes ada)1200 synchronized boolean isHeadTrackerEnabled(@NonNull AudioDeviceAttributes ada) { 1201 if (!mIsHeadTrackingSupported) { 1202 Log.v(TAG, "no headtracking support, isHeadTrackerEnabled always false for " + ada); 1203 return false; 1204 } 1205 final SADeviceState deviceState = findDeviceStateForAudioDeviceAttributes(ada); 1206 return deviceState != null 1207 && deviceState.mHasHeadTracker && deviceState.mHeadTrackerEnabled; 1208 } 1209 isHeadTrackerAvailable()1210 synchronized boolean isHeadTrackerAvailable() { 1211 return mHeadTrackerAvailable; 1212 } 1213 checkSpatializer(String funcName)1214 private boolean checkSpatializer(String funcName) { 1215 switch (mState) { 1216 case STATE_UNINITIALIZED: 1217 case STATE_NOT_SUPPORTED: 1218 return false; 1219 case STATE_ENABLED_UNAVAILABLE: 1220 case STATE_DISABLED_UNAVAILABLE: 1221 case STATE_DISABLED_AVAILABLE: 1222 case STATE_ENABLED_AVAILABLE: 1223 if (mSpat == null) { 1224 // try to recover by resetting the native spatializer state 1225 Log.e(TAG, "checkSpatializer(): called from " + funcName 1226 + "(), native spatializer should not be null in state: " + mState); 1227 postReset(); 1228 return false; 1229 } 1230 break; 1231 } 1232 return true; 1233 } 1234 checkSpatializerForHeadTracking(String funcName)1235 private boolean checkSpatializerForHeadTracking(String funcName) { 1236 return checkSpatializer(funcName) && mIsHeadTrackingSupported; 1237 } 1238 dispatchActualHeadTrackingMode(int newMode)1239 private void dispatchActualHeadTrackingMode(int newMode) { 1240 final int nbCallbacks = mHeadTrackingModeCallbacks.beginBroadcast(); 1241 for (int i = 0; i < nbCallbacks; i++) { 1242 try { 1243 mHeadTrackingModeCallbacks.getBroadcastItem(i) 1244 .dispatchSpatializerActualHeadTrackingModeChanged(newMode); 1245 } catch (RemoteException e) { 1246 Log.e(TAG, "Error in dispatchSpatializerActualHeadTrackingModeChanged(" 1247 + newMode + ")", e); 1248 } 1249 } 1250 mHeadTrackingModeCallbacks.finishBroadcast(); 1251 } 1252 dispatchDesiredHeadTrackingMode(int newMode)1253 private void dispatchDesiredHeadTrackingMode(int newMode) { 1254 final int nbCallbacks = mHeadTrackingModeCallbacks.beginBroadcast(); 1255 for (int i = 0; i < nbCallbacks; i++) { 1256 try { 1257 mHeadTrackingModeCallbacks.getBroadcastItem(i) 1258 .dispatchSpatializerDesiredHeadTrackingModeChanged(newMode); 1259 } catch (RemoteException e) { 1260 Log.e(TAG, "Error in dispatchSpatializerDesiredHeadTrackingModeChanged(" 1261 + newMode + ")", e); 1262 } 1263 } 1264 mHeadTrackingModeCallbacks.finishBroadcast(); 1265 } 1266 dispatchHeadTrackerAvailable(boolean available)1267 private void dispatchHeadTrackerAvailable(boolean available) { 1268 final int nbCallbacks = mHeadTrackerCallbacks.beginBroadcast(); 1269 for (int i = 0; i < nbCallbacks; i++) { 1270 try { 1271 mHeadTrackerCallbacks.getBroadcastItem(i) 1272 .dispatchSpatializerHeadTrackerAvailable(available); 1273 } catch (RemoteException e) { 1274 Log.e(TAG, "Error in dispatchSpatializerHeadTrackerAvailable(" 1275 + available + ")", e); 1276 } 1277 } 1278 mHeadTrackerCallbacks.finishBroadcast(); 1279 } 1280 1281 //------------------------------------------------------ 1282 // head pose 1283 final RemoteCallbackList<ISpatializerHeadToSoundStagePoseCallback> mHeadPoseCallbacks = 1284 new RemoteCallbackList<ISpatializerHeadToSoundStagePoseCallback>(); 1285 registerHeadToSoundstagePoseCallback( @onNull ISpatializerHeadToSoundStagePoseCallback callback)1286 synchronized void registerHeadToSoundstagePoseCallback( 1287 @NonNull ISpatializerHeadToSoundStagePoseCallback callback) { 1288 mHeadPoseCallbacks.register(callback); 1289 } 1290 unregisterHeadToSoundstagePoseCallback( @onNull ISpatializerHeadToSoundStagePoseCallback callback)1291 synchronized void unregisterHeadToSoundstagePoseCallback( 1292 @NonNull ISpatializerHeadToSoundStagePoseCallback callback) { 1293 mHeadPoseCallbacks.unregister(callback); 1294 } 1295 dispatchPoseUpdate(float[] pose)1296 private void dispatchPoseUpdate(float[] pose) { 1297 final int nbCallbacks = mHeadPoseCallbacks.beginBroadcast(); 1298 for (int i = 0; i < nbCallbacks; i++) { 1299 try { 1300 mHeadPoseCallbacks.getBroadcastItem(i) 1301 .dispatchPoseChanged(pose); 1302 } catch (RemoteException e) { 1303 Log.e(TAG, "Error in dispatchPoseChanged", e); 1304 } 1305 } 1306 mHeadPoseCallbacks.finishBroadcast(); 1307 } 1308 1309 //------------------------------------------------------ 1310 // vendor parameters setEffectParameter(int key, @NonNull byte[] value)1311 synchronized void setEffectParameter(int key, @NonNull byte[] value) { 1312 switch (mState) { 1313 case STATE_UNINITIALIZED: 1314 case STATE_NOT_SUPPORTED: 1315 throw (new IllegalStateException( 1316 "Can't set parameter key:" + key + " without a spatializer")); 1317 case STATE_ENABLED_UNAVAILABLE: 1318 case STATE_DISABLED_UNAVAILABLE: 1319 case STATE_DISABLED_AVAILABLE: 1320 case STATE_ENABLED_AVAILABLE: 1321 if (mSpat == null) { 1322 Log.e(TAG, "setParameter(" + key + "): null spatializer in state: " + mState); 1323 return; 1324 } 1325 break; 1326 } 1327 // mSpat != null 1328 try { 1329 mSpat.setParameter(key, value); 1330 } catch (RemoteException e) { 1331 Log.e(TAG, "Error in setParameter for key:" + key, e); 1332 } 1333 } 1334 getEffectParameter(int key, @NonNull byte[] value)1335 synchronized void getEffectParameter(int key, @NonNull byte[] value) { 1336 switch (mState) { 1337 case STATE_UNINITIALIZED: 1338 case STATE_NOT_SUPPORTED: 1339 throw (new IllegalStateException( 1340 "Can't get parameter key:" + key + " without a spatializer")); 1341 case STATE_ENABLED_UNAVAILABLE: 1342 case STATE_DISABLED_UNAVAILABLE: 1343 case STATE_DISABLED_AVAILABLE: 1344 case STATE_ENABLED_AVAILABLE: 1345 if (mSpat == null) { 1346 Log.e(TAG, "getParameter(" + key + "): null spatializer in state: " + mState); 1347 return; 1348 } 1349 break; 1350 } 1351 // mSpat != null 1352 try { 1353 mSpat.getParameter(key, value); 1354 } catch (RemoteException e) { 1355 Log.e(TAG, "Error in getParameter for key:" + key, e); 1356 } 1357 } 1358 1359 //------------------------------------------------------ 1360 // output 1361 1362 /** @see Spatializer#getOutput */ getOutput()1363 synchronized int getOutput() { 1364 switch (mState) { 1365 case STATE_UNINITIALIZED: 1366 case STATE_NOT_SUPPORTED: 1367 throw (new IllegalStateException( 1368 "Can't get output without a spatializer")); 1369 case STATE_ENABLED_UNAVAILABLE: 1370 case STATE_DISABLED_UNAVAILABLE: 1371 case STATE_DISABLED_AVAILABLE: 1372 case STATE_ENABLED_AVAILABLE: 1373 if (mSpat == null) { 1374 throw (new IllegalStateException( 1375 "null Spatializer for getOutput")); 1376 } 1377 break; 1378 } 1379 // mSpat != null 1380 try { 1381 return mSpat.getOutput(); 1382 } catch (RemoteException e) { 1383 Log.e(TAG, "Error in getOutput", e); 1384 return 0; 1385 } 1386 } 1387 1388 final RemoteCallbackList<ISpatializerOutputCallback> mOutputCallbacks = 1389 new RemoteCallbackList<ISpatializerOutputCallback>(); 1390 registerSpatializerOutputCallback( @onNull ISpatializerOutputCallback callback)1391 synchronized void registerSpatializerOutputCallback( 1392 @NonNull ISpatializerOutputCallback callback) { 1393 mOutputCallbacks.register(callback); 1394 } 1395 unregisterSpatializerOutputCallback( @onNull ISpatializerOutputCallback callback)1396 synchronized void unregisterSpatializerOutputCallback( 1397 @NonNull ISpatializerOutputCallback callback) { 1398 mOutputCallbacks.unregister(callback); 1399 } 1400 dispatchOutputUpdate(int output)1401 private void dispatchOutputUpdate(int output) { 1402 final int nbCallbacks = mOutputCallbacks.beginBroadcast(); 1403 for (int i = 0; i < nbCallbacks; i++) { 1404 try { 1405 mOutputCallbacks.getBroadcastItem(i).dispatchSpatializerOutputChanged(output); 1406 } catch (RemoteException e) { 1407 Log.e(TAG, "Error in dispatchOutputUpdate", e); 1408 } 1409 } 1410 mOutputCallbacks.finishBroadcast(); 1411 } 1412 1413 //------------------------------------------------------ 1414 // sensors postInitSensors()1415 private void postInitSensors() { 1416 mAudioService.postInitSpatializerHeadTrackingSensors(); 1417 } 1418 onInitSensors()1419 synchronized void onInitSensors() { 1420 final boolean init = mFeatureEnabled && (mSpatLevel != SpatializationLevel.NONE); 1421 final String action = init ? "initializing" : "releasing"; 1422 if (mSpat == null) { 1423 logloge("not " + action + " sensors, null spatializer"); 1424 return; 1425 } 1426 if (!mIsHeadTrackingSupported) { 1427 logloge("not " + action + " sensors, spatializer doesn't support headtracking"); 1428 return; 1429 } 1430 int headHandle = -1; 1431 int screenHandle = -1; 1432 if (init) { 1433 if (mSensorManager == null) { 1434 try { 1435 mSensorManager = (SensorManager) 1436 mAudioService.mContext.getSystemService(Context.SENSOR_SERVICE); 1437 mDynSensorCallback = new HelperDynamicSensorCallback(); 1438 mSensorManager.registerDynamicSensorCallback(mDynSensorCallback); 1439 } catch (Exception e) { 1440 Log.e(TAG, "Error with SensorManager, can't initialize sensors", e); 1441 mSensorManager = null; 1442 mDynSensorCallback = null; 1443 return; 1444 } 1445 } 1446 // initialize sensor handles 1447 // TODO check risk of race condition for updating the association of a head tracker 1448 // and an audio device: 1449 // does this happen before routing is updated? 1450 // avoid by supporting adding device here AND in onRoutingUpdated() 1451 headHandle = getHeadSensorHandleUpdateTracker(); 1452 loglogi("head tracker sensor handle initialized to " + headHandle); 1453 screenHandle = getScreenSensorHandle(); 1454 Log.i(TAG, "found screen sensor handle initialized to " + screenHandle); 1455 } else { 1456 if (mSensorManager != null && mDynSensorCallback != null) { 1457 mSensorManager.unregisterDynamicSensorCallback(mDynSensorCallback); 1458 mSensorManager = null; 1459 mDynSensorCallback = null; 1460 } 1461 // -1 is disable value for both screen and head tracker handles 1462 } 1463 try { 1464 Log.i(TAG, "setScreenSensor:" + screenHandle); 1465 mSpat.setScreenSensor(screenHandle); 1466 } catch (Exception e) { 1467 Log.e(TAG, "Error calling setScreenSensor:" + screenHandle, e); 1468 } 1469 try { 1470 Log.i(TAG, "setHeadSensor:" + headHandle); 1471 mSpat.setHeadSensor(headHandle); 1472 if (mHeadTrackerAvailable != (headHandle != -1)) { 1473 mHeadTrackerAvailable = (headHandle != -1); 1474 dispatchHeadTrackerAvailable(mHeadTrackerAvailable); 1475 } 1476 } catch (Exception e) { 1477 Log.e(TAG, "Error calling setHeadSensor:" + headHandle, e); 1478 } 1479 setDesiredHeadTrackingMode(mDesiredHeadTrackingMode); 1480 } 1481 1482 //------------------------------------------------------ 1483 // SDK <-> AIDL converters headTrackingModeTypeToSpatializerInt(byte mode)1484 private static int headTrackingModeTypeToSpatializerInt(byte mode) { 1485 switch (mode) { 1486 case SpatializerHeadTrackingMode.OTHER: 1487 return Spatializer.HEAD_TRACKING_MODE_OTHER; 1488 case SpatializerHeadTrackingMode.DISABLED: 1489 return Spatializer.HEAD_TRACKING_MODE_DISABLED; 1490 case SpatializerHeadTrackingMode.RELATIVE_WORLD: 1491 return Spatializer.HEAD_TRACKING_MODE_RELATIVE_WORLD; 1492 case SpatializerHeadTrackingMode.RELATIVE_SCREEN: 1493 return Spatializer.HEAD_TRACKING_MODE_RELATIVE_DEVICE; 1494 default: 1495 throw (new IllegalArgumentException("Unexpected head tracking mode:" + mode)); 1496 } 1497 } 1498 spatializerIntToHeadTrackingModeType(int sdkMode)1499 private static byte spatializerIntToHeadTrackingModeType(int sdkMode) { 1500 switch (sdkMode) { 1501 case Spatializer.HEAD_TRACKING_MODE_OTHER: 1502 return SpatializerHeadTrackingMode.OTHER; 1503 case Spatializer.HEAD_TRACKING_MODE_DISABLED: 1504 return SpatializerHeadTrackingMode.DISABLED; 1505 case Spatializer.HEAD_TRACKING_MODE_RELATIVE_WORLD: 1506 return SpatializerHeadTrackingMode.RELATIVE_WORLD; 1507 case Spatializer.HEAD_TRACKING_MODE_RELATIVE_DEVICE: 1508 return SpatializerHeadTrackingMode.RELATIVE_SCREEN; 1509 default: 1510 throw (new IllegalArgumentException("Unexpected head tracking mode:" + sdkMode)); 1511 } 1512 } 1513 spatializationLevelToSpatializerInt(byte level)1514 private static int spatializationLevelToSpatializerInt(byte level) { 1515 switch (level) { 1516 case SpatializationLevel.NONE: 1517 return Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE; 1518 case SpatializationLevel.SPATIALIZER_MULTICHANNEL: 1519 return Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_MULTICHANNEL; 1520 case SpatializationLevel.SPATIALIZER_MCHAN_BED_PLUS_OBJECTS: 1521 return Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_MCHAN_BED_PLUS_OBJECTS; 1522 default: 1523 throw (new IllegalArgumentException("Unexpected spatializer level:" + level)); 1524 } 1525 } 1526 dump(PrintWriter pw)1527 void dump(PrintWriter pw) { 1528 pw.println("SpatializerHelper:"); 1529 pw.println("\tmState:" + mState); 1530 pw.println("\tmSpatLevel:" + mSpatLevel); 1531 pw.println("\tmCapableSpatLevel:" + mCapableSpatLevel); 1532 pw.println("\tmIsHeadTrackingSupported:" + mIsHeadTrackingSupported); 1533 StringBuilder modesString = new StringBuilder(); 1534 for (int mode : mSupportedHeadTrackingModes) { 1535 modesString.append(Spatializer.headtrackingModeToString(mode)).append(" "); 1536 } 1537 pw.println("\tsupported head tracking modes:" + modesString); 1538 pw.println("\tmDesiredHeadTrackingMode:" 1539 + Spatializer.headtrackingModeToString(mDesiredHeadTrackingMode)); 1540 pw.println("\tmActualHeadTrackingMode:" 1541 + Spatializer.headtrackingModeToString(mActualHeadTrackingMode)); 1542 pw.println("\theadtracker available:" + mHeadTrackerAvailable); 1543 pw.println("\tsupports binaural:" + mBinauralSupported + " / transaural:" 1544 + mTransauralSupported); 1545 pw.println("\tmSpatOutput:" + mSpatOutput); 1546 pw.println("\tdevices:"); 1547 for (SADeviceState device : mSADevices) { 1548 pw.println("\t\t" + device); 1549 } 1550 } 1551 1552 /*package*/ static final class SADeviceState { 1553 private static boolean sBinauralEnabledDefault = true; 1554 private static boolean sTransauralEnabledDefault = true; 1555 private static boolean sHeadTrackingEnabledDefault = false; 1556 final @AudioDeviceInfo.AudioDeviceType int mDeviceType; 1557 final @NonNull String mDeviceAddress; 1558 boolean mEnabled; 1559 boolean mHasHeadTracker = false; 1560 boolean mHeadTrackerEnabled; 1561 static final String SETTING_FIELD_SEPARATOR = ","; 1562 static final String SETTING_DEVICE_SEPARATOR_CHAR = "|"; 1563 static final String SETTING_DEVICE_SEPARATOR = "\\|"; 1564 1565 /** 1566 * Constructor 1567 * @param deviceType 1568 * @param address must be non-null for wireless devices 1569 * @throws NullPointerException if a null address is passed for a wireless device 1570 */ SADeviceState(@udioDeviceInfo.AudioDeviceType int deviceType, @Nullable String address)1571 SADeviceState(@AudioDeviceInfo.AudioDeviceType int deviceType, @Nullable String address) { 1572 mDeviceType = deviceType; 1573 mDeviceAddress = isWireless(deviceType) ? Objects.requireNonNull(address) : ""; 1574 final int spatMode = SPAT_MODE_FOR_DEVICE_TYPE.get(deviceType, Integer.MIN_VALUE); 1575 mEnabled = spatMode == SpatializationMode.SPATIALIZER_BINAURAL 1576 ? sBinauralEnabledDefault 1577 : spatMode == SpatializationMode.SPATIALIZER_TRANSAURAL 1578 ? sTransauralEnabledDefault 1579 : false; 1580 mHeadTrackerEnabled = sHeadTrackingEnabledDefault; 1581 } 1582 1583 @Override equals(Object obj)1584 public boolean equals(Object obj) { 1585 if (this == obj) { 1586 return true; 1587 } 1588 if (obj == null) { 1589 return false; 1590 } 1591 // type check and cast 1592 if (getClass() != obj.getClass()) { 1593 return false; 1594 } 1595 final SADeviceState sads = (SADeviceState) obj; 1596 return mDeviceType == sads.mDeviceType 1597 && mDeviceAddress.equals(sads.mDeviceAddress) 1598 && mEnabled == sads.mEnabled 1599 && mHasHeadTracker == sads.mHasHeadTracker 1600 && mHeadTrackerEnabled == sads.mHeadTrackerEnabled; 1601 } 1602 1603 @Override hashCode()1604 public int hashCode() { 1605 return Objects.hash(mDeviceType, mDeviceAddress, mEnabled, mHasHeadTracker, 1606 mHeadTrackerEnabled); 1607 } 1608 1609 @Override toString()1610 public String toString() { 1611 return "type: " + mDeviceType + " addr: " + mDeviceAddress + " enabled: " + mEnabled 1612 + " HT: " + mHasHeadTracker + " HTenabled: " + mHeadTrackerEnabled; 1613 } 1614 toPersistableString()1615 String toPersistableString() { 1616 return (new StringBuilder().append(mDeviceType) 1617 .append(SETTING_FIELD_SEPARATOR).append(mDeviceAddress) 1618 .append(SETTING_FIELD_SEPARATOR).append(mEnabled ? "1" : "0") 1619 .append(SETTING_FIELD_SEPARATOR).append(mHasHeadTracker ? "1" : "0") 1620 .append(SETTING_FIELD_SEPARATOR).append(mHeadTrackerEnabled ? "1" : "0") 1621 .toString()); 1622 } 1623 fromPersistedString(@ullable String persistedString)1624 static @Nullable SADeviceState fromPersistedString(@Nullable String persistedString) { 1625 if (persistedString == null) { 1626 return null; 1627 } 1628 if (persistedString.isEmpty()) { 1629 return null; 1630 } 1631 String[] fields = TextUtils.split(persistedString, SETTING_FIELD_SEPARATOR); 1632 if (fields.length != 5) { 1633 // expecting all fields, fewer may mean corruption, ignore those settings 1634 return null; 1635 } 1636 try { 1637 final int deviceType = Integer.parseInt(fields[0]); 1638 final SADeviceState deviceState = new SADeviceState(deviceType, fields[1]); 1639 deviceState.mEnabled = Integer.parseInt(fields[2]) == 1; 1640 deviceState.mHasHeadTracker = Integer.parseInt(fields[3]) == 1; 1641 deviceState.mHeadTrackerEnabled = Integer.parseInt(fields[4]) == 1; 1642 return deviceState; 1643 } catch (NumberFormatException e) { 1644 Log.e(TAG, "unable to parse setting for SADeviceState: " + persistedString, e); 1645 return null; 1646 } 1647 } 1648 getAudioDeviceAttributes()1649 public AudioDeviceAttributes getAudioDeviceAttributes() { 1650 return new AudioDeviceAttributes(AudioDeviceAttributes.ROLE_OUTPUT, 1651 mDeviceType, mDeviceAddress == null ? "" : mDeviceAddress); 1652 } 1653 1654 } 1655 getSADeviceSettings()1656 /*package*/ synchronized String getSADeviceSettings() { 1657 // expected max size of each String for each SADeviceState is 25 (accounting for separator) 1658 final StringBuilder settingsBuilder = new StringBuilder(mSADevices.size() * 25); 1659 for (int i = 0; i < mSADevices.size(); i++) { 1660 settingsBuilder.append(mSADevices.get(i).toPersistableString()); 1661 if (i != mSADevices.size() - 1) { 1662 settingsBuilder.append(SADeviceState.SETTING_DEVICE_SEPARATOR_CHAR); 1663 } 1664 } 1665 return settingsBuilder.toString(); 1666 } 1667 setSADeviceSettings(@onNull String persistedSettings)1668 /*package*/ synchronized void setSADeviceSettings(@NonNull String persistedSettings) { 1669 String[] devSettings = TextUtils.split(Objects.requireNonNull(persistedSettings), 1670 SADeviceState.SETTING_DEVICE_SEPARATOR); 1671 // small list, not worth overhead of Arrays.stream(devSettings) 1672 for (String setting : devSettings) { 1673 SADeviceState devState = SADeviceState.fromPersistedString(setting); 1674 // Note if the device is not compatible with spatialization mode 1675 // or the device type is not canonical, it is ignored. 1676 if (devState != null 1677 && devState.mDeviceType == getCanonicalDeviceType(devState.mDeviceType) 1678 && isDeviceCompatibleWithSpatializationModes( 1679 devState.getAudioDeviceAttributes())) { 1680 mSADevices.add(devState); 1681 logDeviceState(devState, "setSADeviceSettings"); 1682 } 1683 } 1684 } 1685 spatStateString(int state)1686 private static String spatStateString(int state) { 1687 switch (state) { 1688 case STATE_UNINITIALIZED: 1689 return "STATE_UNINITIALIZED"; 1690 case STATE_NOT_SUPPORTED: 1691 return "STATE_NOT_SUPPORTED"; 1692 case STATE_DISABLED_UNAVAILABLE: 1693 return "STATE_DISABLED_UNAVAILABLE"; 1694 case STATE_ENABLED_UNAVAILABLE: 1695 return "STATE_ENABLED_UNAVAILABLE"; 1696 case STATE_ENABLED_AVAILABLE: 1697 return "STATE_ENABLED_AVAILABLE"; 1698 case STATE_DISABLED_AVAILABLE: 1699 return "STATE_DISABLED_AVAILABLE"; 1700 default: 1701 return "invalid state"; 1702 } 1703 } 1704 isWireless(@udioDeviceInfo.AudioDeviceType int deviceType)1705 private static boolean isWireless(@AudioDeviceInfo.AudioDeviceType int deviceType) { 1706 for (int type : WIRELESS_TYPES) { 1707 if (type == deviceType) { 1708 return true; 1709 } 1710 } 1711 return false; 1712 } 1713 getHeadSensorHandleUpdateTracker()1714 private int getHeadSensorHandleUpdateTracker() { 1715 int headHandle = -1; 1716 if (sRoutingDevices.isEmpty()) { 1717 logloge("getHeadSensorHandleUpdateTracker: no device, no head tracker"); 1718 return headHandle; 1719 } 1720 final AudioDeviceAttributes currentDevice = sRoutingDevices.get(0); 1721 UUID routingDeviceUuid = mAudioService.getDeviceSensorUuid(currentDevice); 1722 // We limit only to Sensor.TYPE_HEAD_TRACKER here to avoid confusion 1723 // with gaming sensors. (Note that Sensor.TYPE_ROTATION_VECTOR 1724 // and Sensor.TYPE_GAME_ROTATION_VECTOR are supported internally by 1725 // SensorPoseProvider). 1726 // Note: this is a dynamic sensor list right now. 1727 List<Sensor> sensors = mSensorManager.getDynamicSensorList(Sensor.TYPE_HEAD_TRACKER); 1728 for (Sensor sensor : sensors) { 1729 final UUID uuid = sensor.getUuid(); 1730 if (uuid.equals(routingDeviceUuid)) { 1731 headHandle = sensor.getHandle(); 1732 if (!setHasHeadTracker(currentDevice)) { 1733 headHandle = -1; 1734 } 1735 break; 1736 } 1737 if (uuid.equals(UuidUtils.STANDALONE_UUID)) { 1738 headHandle = sensor.getHandle(); 1739 // we do not break, perhaps we find a head tracker on device. 1740 } 1741 } 1742 return headHandle; 1743 } 1744 getScreenSensorHandle()1745 private int getScreenSensorHandle() { 1746 int screenHandle = -1; 1747 Sensor screenSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR); 1748 if (screenSensor != null) { 1749 screenHandle = screenSensor.getHandle(); 1750 } 1751 return screenHandle; 1752 } 1753 1754 /** 1755 * Returns routing for the given attributes 1756 * @param aa AudioAttributes whose routing is being queried 1757 * @return a non-null never-empty list of devices. If the routing query failed, the list 1758 * will contain null. 1759 */ getRoutingDevices(AudioAttributes aa)1760 private @NonNull ArrayList<AudioDeviceAttributes> getRoutingDevices(AudioAttributes aa) { 1761 final ArrayList<AudioDeviceAttributes> devices = mASA.getDevicesForAttributes( 1762 aa, false /* forVolume */); 1763 for (AudioDeviceAttributes ada : devices) { 1764 if (ada == null) { 1765 // invalid entry, reject this routing query by returning an empty list 1766 return new ArrayList<>(0); 1767 } 1768 } 1769 return devices; 1770 } 1771 loglogi(String msg)1772 private static void loglogi(String msg) { 1773 AudioService.sSpatialLogger.loglogi(msg, TAG); 1774 } 1775 logloge(String msg)1776 private static String logloge(String msg) { 1777 AudioService.sSpatialLogger.loglog(msg, AudioEventLogger.Event.ALOGE, TAG); 1778 return msg; 1779 } 1780 1781 //------------------------------------------------ 1782 // for testing purposes only 1783 clearSADevices()1784 /*package*/ void clearSADevices() { 1785 mSADevices.clear(); 1786 } 1787 forceStateForTest(int state)1788 /*package*/ synchronized void forceStateForTest(int state) { 1789 mState = state; 1790 } 1791 initForTest(boolean hasBinaural, boolean hasTransaural)1792 /*package*/ synchronized void initForTest(boolean hasBinaural, boolean hasTransaural) { 1793 mBinauralSupported = hasBinaural; 1794 mTransauralSupported = hasTransaural; 1795 } 1796 } 1797