blob: 462c9381b9049c7452336d8303fd003e68576eeb [file] [log] [blame]
Aurimas Liutikas88c7ff12023-08-10 12:42:26 -07001/*
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
17package com.android.server.audio;
18
19import android.annotation.NonNull;
20import android.annotation.Nullable;
21import android.content.Context;
22import android.hardware.Sensor;
23import android.hardware.SensorManager;
24import android.media.AudioAttributes;
25import android.media.AudioDeviceAttributes;
26import android.media.AudioDeviceInfo;
27import android.media.AudioFormat;
28import android.media.AudioSystem;
29import android.media.INativeSpatializerCallback;
30import android.media.ISpatializer;
31import android.media.ISpatializerCallback;
32import android.media.ISpatializerHeadToSoundStagePoseCallback;
33import android.media.ISpatializerHeadTrackerAvailableCallback;
34import android.media.ISpatializerHeadTrackingCallback;
35import android.media.ISpatializerHeadTrackingModeCallback;
36import android.media.ISpatializerOutputCallback;
37import android.media.MediaMetrics;
38import android.media.SpatializationLevel;
39import android.media.SpatializationMode;
40import android.media.Spatializer;
41import android.media.SpatializerHeadTrackingMode;
42import android.os.RemoteCallbackList;
43import android.os.RemoteException;
44import android.text.TextUtils;
45import android.util.Log;
46import android.util.Pair;
47import android.util.SparseIntArray;
48
49import com.android.internal.annotations.GuardedBy;
50import com.android.server.utils.EventLogger;
51
52import java.io.PrintWriter;
53import java.util.ArrayList;
54import java.util.List;
55import java.util.Locale;
56import java.util.Objects;
57import java.util.UUID;
58
59/**
60 * A helper class to manage Spatializer related functionality
61 */
62public class SpatializerHelper {
63
64 private static final String TAG = "AS.SpatializerHelper";
65 private static final boolean DEBUG = true;
66 private static final boolean DEBUG_MORE = false;
67
68 private static void logd(String s) {
69 if (DEBUG) {
70 Log.i(TAG, s);
71 }
72 }
73
74 private final @NonNull AudioSystemAdapter mASA;
75 private final @NonNull AudioService mAudioService;
76 private @Nullable SensorManager mSensorManager;
77
78 //------------------------------------------------------------
79
80 private static final SparseIntArray SPAT_MODE_FOR_DEVICE_TYPE = new SparseIntArray(14) {
81 {
82 append(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, SpatializationMode.SPATIALIZER_TRANSAURAL);
83 append(AudioDeviceInfo.TYPE_WIRED_HEADSET, SpatializationMode.SPATIALIZER_BINAURAL);
84 append(AudioDeviceInfo.TYPE_WIRED_HEADPHONES, SpatializationMode.SPATIALIZER_BINAURAL);
85 // assumption for A2DP: mostly headsets
86 append(AudioDeviceInfo.TYPE_BLUETOOTH_A2DP, SpatializationMode.SPATIALIZER_BINAURAL);
87 append(AudioDeviceInfo.TYPE_DOCK, SpatializationMode.SPATIALIZER_TRANSAURAL);
88 append(AudioDeviceInfo.TYPE_USB_ACCESSORY, SpatializationMode.SPATIALIZER_TRANSAURAL);
89 append(AudioDeviceInfo.TYPE_USB_DEVICE, SpatializationMode.SPATIALIZER_TRANSAURAL);
90 append(AudioDeviceInfo.TYPE_USB_HEADSET, SpatializationMode.SPATIALIZER_BINAURAL);
91 append(AudioDeviceInfo.TYPE_LINE_ANALOG, SpatializationMode.SPATIALIZER_TRANSAURAL);
92 append(AudioDeviceInfo.TYPE_LINE_DIGITAL, SpatializationMode.SPATIALIZER_TRANSAURAL);
93 append(AudioDeviceInfo.TYPE_AUX_LINE, SpatializationMode.SPATIALIZER_TRANSAURAL);
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")
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
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 */
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
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
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
425 private void postReset() {
426 mAudioService.postResetSpatializer();
427 }
428
429 //------------------------------------------------------
430 // spatializer callback from native
431 private final class SpatializerCallback extends INativeSpatializerCallback.Stub {
432
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
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 {
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
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
503 public void onDynamicSensorConnected(Sensor sensor) {
504 postInitSensors();
505 }
506
507 @Override
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 */
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
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")
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.
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
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 */
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
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 */
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
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
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
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
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
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
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
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
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
810 synchronized int getCapableImmersiveAudioLevel() {
811 return mCapableSpatLevel;
812 }
813
814 final RemoteCallbackList<ISpatializerCallback> mStateCallbacks =
815 new RemoteCallbackList<ISpatializerCallback>();
816
817 synchronized void registerStateCallback(
818 @NonNull ISpatializerCallback callback) {
819 mStateCallbacks.register(callback);
820 }
821
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 */
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
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 */
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 */
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
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
1034 synchronized void registerHeadTrackingModeCallback(
1035 @NonNull ISpatializerHeadTrackingModeCallback callback) {
1036 mHeadTrackingModeCallbacks.register(callback);
1037 }
1038
1039 synchronized void unregisterHeadTrackingModeCallback(
1040 @NonNull ISpatializerHeadTrackingModeCallback callback) {
1041 mHeadTrackingModeCallbacks.unregister(callback);
1042 }
1043
1044 final RemoteCallbackList<ISpatializerHeadTrackerAvailableCallback> mHeadTrackerCallbacks =
1045 new RemoteCallbackList<>();
1046
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
1056 synchronized int[] getSupportedHeadTrackingModes() {
1057 return mSupportedHeadTrackingModes;
1058 }
1059
1060 synchronized int getActualHeadTrackingMode() {
1061 return mActualHeadTrackingMode;
1062 }
1063
1064 synchronized int getDesiredHeadTrackingMode() {
1065 return mDesiredHeadTrackingMode;
1066 }
1067
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
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
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
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
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
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
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 */
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
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
1210 synchronized boolean isHeadTrackerAvailable() {
1211 return mHeadTrackerAvailable;
1212 }
1213
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
1235 private boolean checkSpatializerForHeadTracking(String funcName) {
1236 return checkSpatializer(funcName) && mIsHeadTrackingSupported;
1237 }
1238
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
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
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
1286 synchronized void registerHeadToSoundstagePoseCallback(
1287 @NonNull ISpatializerHeadToSoundStagePoseCallback callback) {
1288 mHeadPoseCallbacks.register(callback);
1289 }
1290
1291 synchronized void unregisterHeadToSoundstagePoseCallback(
1292 @NonNull ISpatializerHeadToSoundStagePoseCallback callback) {
1293 mHeadPoseCallbacks.unregister(callback);
1294 }
1295
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
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
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 */
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
1391 synchronized void registerSpatializerOutputCallback(
1392 @NonNull ISpatializerOutputCallback callback) {
1393 mOutputCallbacks.register(callback);
1394 }
1395
1396 synchronized void unregisterSpatializerOutputCallback(
1397 @NonNull ISpatializerOutputCallback callback) {
1398 mOutputCallbacks.unregister(callback);
1399 }
1400
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
1415 private void postInitSensors() {
1416 mAudioService.postInitSpatializerHeadTrackingSensors();
1417 }
1418
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
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
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
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
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 */
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
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
1604 public int hashCode() {
1605 return Objects.hash(mDeviceType, mDeviceAddress, mEnabled, mHasHeadTracker,
1606 mHeadTrackerEnabled);
1607 }
1608
1609 @Override
1610 public String toString() {
1611 return "type: " + mDeviceType + " addr: " + mDeviceAddress + " enabled: " + mEnabled
1612 + " HT: " + mHasHeadTracker + " HTenabled: " + mHeadTrackerEnabled;
1613 }
1614
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
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
1649 public AudioDeviceAttributes getAudioDeviceAttributes() {
1650 return new AudioDeviceAttributes(AudioDeviceAttributes.ROLE_OUTPUT,
1651 mDeviceType, mDeviceAddress == null ? "" : mDeviceAddress);
1652 }
1653
1654 }
1655
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
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
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
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
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
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 */
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
1772 private static void loglogi(String msg) {
1773 AudioService.sSpatialLogger.enqueueAndLog(msg, EventLogger.Event.ALOGI, TAG);
1774 }
1775
1776 private static String logloge(String msg) {
1777 AudioService.sSpatialLogger.enqueueAndLog(msg, EventLogger.Event.ALOGE, TAG);
1778 return msg;
1779 }
1780
1781 //------------------------------------------------
1782 // for testing purposes only
1783
1784 /*package*/ void clearSADevices() {
1785 mSADevices.clear();
1786 }
1787
1788 /*package*/ synchronized void forceStateForTest(int state) {
1789 mState = state;
1790 }
1791
1792 /*package*/ synchronized void initForTest(boolean hasBinaural, boolean hasTransaural) {
1793 mBinauralSupported = hasBinaural;
1794 mTransauralSupported = hasTransaural;
1795 }
1796}