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