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