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