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