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.AudioFormat;
27 import android.media.AudioSystem;
28 import android.media.INativeSpatializerCallback;
29 import android.media.ISpatializer;
30 import android.media.ISpatializerCallback;
31 import android.media.ISpatializerHeadToSoundStagePoseCallback;
32 import android.media.ISpatializerHeadTrackingCallback;
33 import android.media.ISpatializerHeadTrackingModeCallback;
34 import android.media.ISpatializerOutputCallback;
35 import android.media.SpatializationLevel;
36 import android.media.Spatializer;
37 import android.media.SpatializerHeadTrackingMode;
38 import android.os.RemoteCallbackList;
39 import android.os.RemoteException;
40 import android.util.Log;
41 
42 import java.util.ArrayList;
43 import java.util.List;
44 import java.util.Locale;
45 
46 /**
47  * A helper class to manage Spatializer related functionality
48  */
49 public class SpatializerHelper {
50 
51     private static final String TAG = "AS.SpatializerHelper";
52     private static final boolean DEBUG = true;
53     private static final boolean DEBUG_MORE = false;
54 
logd(String s)55     private static void logd(String s) {
56         if (DEBUG) {
57             Log.i(TAG, s);
58         }
59     }
60 
61     private final @NonNull AudioSystemAdapter mASA;
62     private final @NonNull AudioService mAudioService;
63     private @Nullable SensorManager mSensorManager;
64 
65     //------------------------------------------------------------
66     /** head tracker sensor name */
67     // TODO: replace with generic head tracker sensor name.
68     //       the current implementation refers to the "google" namespace but will be replaced
69     //       by an android name at the next API level revision, it is not Google-specific.
70     //       Also see "TODO-HT" in onInitSensors() method
71     private static final String HEADTRACKER_SENSOR =
72             "com.google.hardware.sensor.hid_dynamic.headtracker";
73 
74     // Spatializer state machine
75     private static final int STATE_UNINITIALIZED = 0;
76     private static final int STATE_NOT_SUPPORTED = 1;
77     private static final int STATE_DISABLED_UNAVAILABLE = 3;
78     private static final int STATE_ENABLED_UNAVAILABLE = 4;
79     private static final int STATE_ENABLED_AVAILABLE = 5;
80     private static final int STATE_DISABLED_AVAILABLE = 6;
81     private int mState = STATE_UNINITIALIZED;
82 
83     /** current level as reported by native Spatializer in callback */
84     private int mSpatLevel = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE;
85     private int mCapableSpatLevel = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE;
86     private int mActualHeadTrackingMode = Spatializer.HEAD_TRACKING_MODE_UNSUPPORTED;
87     private int mDesiredHeadTrackingMode = Spatializer.HEAD_TRACKING_MODE_UNSUPPORTED;
88     private int mSpatOutput = 0;
89     private @Nullable ISpatializer mSpat;
90     private @Nullable SpatializerCallback mSpatCallback;
91     private @Nullable SpatializerHeadTrackingCallback mSpatHeadTrackingCallback;
92     private @Nullable HelperDynamicSensorCallback mDynSensorCallback;
93 
94     // default attributes and format that determine basic availability of spatialization
95     private static final AudioAttributes DEFAULT_ATTRIBUTES = new AudioAttributes.Builder()
96             .setUsage(AudioAttributes.USAGE_MEDIA)
97             .build();
98     private static final AudioFormat DEFAULT_FORMAT = new AudioFormat.Builder()
99             .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
100             .setSampleRate(48000)
101             .setChannelMask(AudioFormat.CHANNEL_OUT_5POINT1)
102             .build();
103     // device array to store the routing for the default attributes and format, size 1 because
104     // media is never expected to be duplicated
105     private static final AudioDeviceAttributes[] ROUTING_DEVICES = new AudioDeviceAttributes[1];
106 
107     //---------------------------------------------------------------
108     // audio device compatibility / enabled
109 
110     private final ArrayList<AudioDeviceAttributes> mCompatibleAudioDevices = new ArrayList<>(0);
111 
112     //------------------------------------------------------
113     // initialization
SpatializerHelper(@onNull AudioService mother, @NonNull AudioSystemAdapter asa)114     SpatializerHelper(@NonNull AudioService mother, @NonNull AudioSystemAdapter asa) {
115         mAudioService = mother;
116         mASA = asa;
117     }
118 
init(boolean effectExpected)119     synchronized void init(boolean effectExpected) {
120         Log.i(TAG, "Initializing");
121         if (!effectExpected) {
122             Log.i(TAG, "Setting state to STATE_NOT_SUPPORTED due to effect not expected");
123             mState = STATE_NOT_SUPPORTED;
124             return;
125         }
126         if (mState != STATE_UNINITIALIZED) {
127             throw new IllegalStateException(("init() called in state:" + mState));
128         }
129         // is there a spatializer?
130         mSpatCallback = new SpatializerCallback();
131         final ISpatializer spat = AudioSystem.getSpatializer(mSpatCallback);
132         if (spat == null) {
133             Log.i(TAG, "init(): No Spatializer found");
134             mState = STATE_NOT_SUPPORTED;
135             return;
136         }
137         // capabilities of spatializer?
138         try {
139             byte[] levels = spat.getSupportedLevels();
140             if (levels == null
141                     || levels.length == 0
142                     || (levels.length == 1
143                     && levels[0] == Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE)) {
144                 Log.e(TAG, "Spatializer is useless");
145                 mState = STATE_NOT_SUPPORTED;
146                 return;
147             }
148             for (byte level : levels) {
149                 logd("found support for level: " + level);
150                 if (level == Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_MULTICHANNEL) {
151                     logd("Setting capable level to LEVEL_MULTICHANNEL");
152                     mCapableSpatLevel = level;
153                     break;
154                 }
155             }
156         } catch (RemoteException e) {
157             /* capable level remains at NONE*/
158         } finally {
159             if (spat != null) {
160                 try {
161                     spat.release();
162                 } catch (RemoteException e) { /* capable level remains at NONE*/ }
163             }
164         }
165         if (mCapableSpatLevel == Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE) {
166             mState = STATE_NOT_SUPPORTED;
167             return;
168         }
169         mState = STATE_DISABLED_UNAVAILABLE;
170         // note at this point mSpat is still not instantiated
171     }
172 
173     /**
174      * Like init() but resets the state and spatializer levels
175      * @param featureEnabled
176      */
reset(boolean featureEnabled)177     synchronized void reset(boolean featureEnabled) {
178         Log.i(TAG, "Resetting");
179         mState = STATE_UNINITIALIZED;
180         mSpatLevel = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE;
181         mCapableSpatLevel = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE;
182         mActualHeadTrackingMode = Spatializer.HEAD_TRACKING_MODE_UNSUPPORTED;
183         init(true);
184         setFeatureEnabled(featureEnabled);
185     }
186 
187     //------------------------------------------------------
188     // routing monitoring
onRoutingUpdated()189     void onRoutingUpdated() {
190         switch (mState) {
191             case STATE_UNINITIALIZED:
192             case STATE_NOT_SUPPORTED:
193                 return;
194             case STATE_DISABLED_UNAVAILABLE:
195             case STATE_ENABLED_UNAVAILABLE:
196             case STATE_ENABLED_AVAILABLE:
197             case STATE_DISABLED_AVAILABLE:
198                 break;
199         }
200         mASA.getDevicesForAttributes(DEFAULT_ATTRIBUTES).toArray(ROUTING_DEVICES);
201         final boolean able =
202                 AudioSystem.canBeSpatialized(DEFAULT_ATTRIBUTES, DEFAULT_FORMAT, ROUTING_DEVICES);
203         logd("onRoutingUpdated: can spatialize media 5.1:" + able
204                 + " on device:" + ROUTING_DEVICES[0]);
205         setDispatchAvailableState(able);
206     }
207 
208     //------------------------------------------------------
209     // spatializer callback from native
210     private final class SpatializerCallback extends INativeSpatializerCallback.Stub {
211 
onLevelChanged(byte level)212         public void onLevelChanged(byte level) {
213             logd("SpatializerCallback.onLevelChanged level:" + level);
214             synchronized (SpatializerHelper.this) {
215                 mSpatLevel = spatializationLevelToSpatializerInt(level);
216             }
217             // TODO use reported spat level to change state
218 
219             // init sensors
220             postInitSensors();
221         }
222 
onOutputChanged(int output)223         public void onOutputChanged(int output) {
224             logd("SpatializerCallback.onOutputChanged output:" + output);
225             int oldOutput;
226             synchronized (SpatializerHelper.this) {
227                 oldOutput = mSpatOutput;
228                 mSpatOutput = output;
229             }
230             if (oldOutput != output) {
231                 dispatchOutputUpdate(output);
232             }
233         }
234     };
235 
236     //------------------------------------------------------
237     // spatializer head tracking callback from native
238     private final class SpatializerHeadTrackingCallback
239             extends ISpatializerHeadTrackingCallback.Stub {
onHeadTrackingModeChanged(byte mode)240         public void onHeadTrackingModeChanged(byte mode)  {
241             logd("SpatializerHeadTrackingCallback.onHeadTrackingModeChanged mode:" + mode);
242             int oldMode, newMode;
243             synchronized (this) {
244                 oldMode = mActualHeadTrackingMode;
245                 mActualHeadTrackingMode = headTrackingModeTypeToSpatializerInt(mode);
246                 newMode = mActualHeadTrackingMode;
247             }
248             if (oldMode != newMode) {
249                 dispatchActualHeadTrackingMode(newMode);
250             }
251         }
252 
onHeadToSoundStagePoseUpdated(float[] headToStage)253         public void onHeadToSoundStagePoseUpdated(float[] headToStage)  {
254             if (headToStage == null) {
255                 Log.e(TAG, "SpatializerHeadTrackingCallback.onHeadToStagePoseUpdated"
256                         + "null transform");
257                 return;
258             }
259             if (headToStage.length != 6) {
260                 Log.e(TAG, "SpatializerHeadTrackingCallback.onHeadToStagePoseUpdated"
261                         + " invalid transform length" + headToStage.length);
262                 return;
263             }
264             if (DEBUG_MORE) {
265                 // 6 values * (4 digits + 1 dot + 2 brackets) = 42 characters
266                 StringBuilder t = new StringBuilder(42);
267                 for (float val : headToStage) {
268                     t.append("[").append(String.format(Locale.ENGLISH, "%.3f", val)).append("]");
269                 }
270                 logd("SpatializerHeadTrackingCallback.onHeadToStagePoseUpdated headToStage:" + t);
271             }
272             dispatchPoseUpdate(headToStage);
273         }
274     };
275 
276     //------------------------------------------------------
277     // dynamic sensor callback
278     private final class HelperDynamicSensorCallback extends SensorManager.DynamicSensorCallback {
279         @Override
onDynamicSensorConnected(Sensor sensor)280         public void onDynamicSensorConnected(Sensor sensor) {
281             postInitSensors();
282         }
283 
284         @Override
onDynamicSensorDisconnected(Sensor sensor)285         public void onDynamicSensorDisconnected(Sensor sensor) {
286             postInitSensors();
287         }
288     }
289 
290     //------------------------------------------------------
291     // compatible devices
292     /**
293      * @return a shallow copy of the list of compatible audio devices
294      */
getCompatibleAudioDevices()295     synchronized @NonNull List<AudioDeviceAttributes> getCompatibleAudioDevices() {
296         return (List<AudioDeviceAttributes>) mCompatibleAudioDevices.clone();
297     }
298 
addCompatibleAudioDevice(@onNull AudioDeviceAttributes ada)299     synchronized void addCompatibleAudioDevice(@NonNull AudioDeviceAttributes ada) {
300         if (!mCompatibleAudioDevices.contains(ada)) {
301             mCompatibleAudioDevices.add(ada);
302         }
303     }
304 
removeCompatibleAudioDevice(@onNull AudioDeviceAttributes ada)305     synchronized void removeCompatibleAudioDevice(@NonNull AudioDeviceAttributes ada) {
306         mCompatibleAudioDevices.remove(ada);
307     }
308 
309     //------------------------------------------------------
310     // states
311 
isEnabled()312     synchronized boolean isEnabled() {
313         switch (mState) {
314             case STATE_UNINITIALIZED:
315             case STATE_NOT_SUPPORTED:
316             case STATE_DISABLED_UNAVAILABLE:
317             case STATE_DISABLED_AVAILABLE:
318                 return false;
319             case STATE_ENABLED_UNAVAILABLE:
320             case STATE_ENABLED_AVAILABLE:
321             default:
322                 return true;
323         }
324     }
325 
isAvailable()326     synchronized boolean isAvailable() {
327         switch (mState) {
328             case STATE_UNINITIALIZED:
329             case STATE_NOT_SUPPORTED:
330             case STATE_ENABLED_UNAVAILABLE:
331             case STATE_DISABLED_UNAVAILABLE:
332                 return false;
333             case STATE_DISABLED_AVAILABLE:
334             case STATE_ENABLED_AVAILABLE:
335             default:
336                 return true;
337         }
338     }
339 
setFeatureEnabled(boolean enabled)340     synchronized void setFeatureEnabled(boolean enabled) {
341         switch (mState) {
342             case STATE_UNINITIALIZED:
343                 if (enabled) {
344                     throw(new IllegalStateException("Can't enable when uninitialized"));
345                 }
346                 return;
347             case STATE_NOT_SUPPORTED:
348                 if (enabled) {
349                     Log.e(TAG, "Can't enable when unsupported");
350                 }
351                 return;
352             case STATE_DISABLED_UNAVAILABLE:
353             case STATE_DISABLED_AVAILABLE:
354                 if (enabled) {
355                     createSpat();
356                     break;
357                 } else {
358                     // already in disabled state
359                     return;
360                 }
361             case STATE_ENABLED_UNAVAILABLE:
362             case STATE_ENABLED_AVAILABLE:
363                 if (!enabled) {
364                     releaseSpat();
365                     break;
366                 } else {
367                     // already in enabled state
368                     return;
369                 }
370         }
371         setDispatchFeatureEnabledState(enabled);
372     }
373 
getCapableImmersiveAudioLevel()374     synchronized int getCapableImmersiveAudioLevel() {
375         return mCapableSpatLevel;
376     }
377 
378     final RemoteCallbackList<ISpatializerCallback> mStateCallbacks =
379             new RemoteCallbackList<ISpatializerCallback>();
380 
registerStateCallback( @onNull ISpatializerCallback callback)381     synchronized void registerStateCallback(
382             @NonNull ISpatializerCallback callback) {
383         mStateCallbacks.register(callback);
384     }
385 
unregisterStateCallback( @onNull ISpatializerCallback callback)386     synchronized void unregisterStateCallback(
387             @NonNull ISpatializerCallback callback) {
388         mStateCallbacks.unregister(callback);
389     }
390 
391     /**
392      * precondition: mState = STATE_*
393      *               isFeatureEnabled() != featureEnabled
394      * @param featureEnabled
395      */
setDispatchFeatureEnabledState(boolean featureEnabled)396     private synchronized void setDispatchFeatureEnabledState(boolean featureEnabled) {
397         if (featureEnabled) {
398             switch (mState) {
399                 case STATE_DISABLED_UNAVAILABLE:
400                     mState = STATE_ENABLED_UNAVAILABLE;
401                     break;
402                 case STATE_DISABLED_AVAILABLE:
403                     mState = STATE_ENABLED_AVAILABLE;
404                     break;
405                 default:
406                     throw(new IllegalStateException("Invalid mState:" + mState
407                             + " for enabled true"));
408             }
409         } else {
410             switch (mState) {
411                 case STATE_ENABLED_UNAVAILABLE:
412                     mState = STATE_DISABLED_UNAVAILABLE;
413                     break;
414                 case STATE_ENABLED_AVAILABLE:
415                     mState = STATE_DISABLED_AVAILABLE;
416                     break;
417                 default:
418                     throw (new IllegalStateException("Invalid mState:" + mState
419                             + " for enabled false"));
420             }
421         }
422         final int nbCallbacks = mStateCallbacks.beginBroadcast();
423         for (int i = 0; i < nbCallbacks; i++) {
424             try {
425                 mStateCallbacks.getBroadcastItem(i)
426                         .dispatchSpatializerEnabledChanged(featureEnabled);
427             } catch (RemoteException e) {
428                 Log.e(TAG, "Error in dispatchSpatializerEnabledChanged", e);
429             }
430         }
431         mStateCallbacks.finishBroadcast();
432         mAudioService.persistSpatialAudioEnabled(featureEnabled);
433     }
434 
setDispatchAvailableState(boolean available)435     private synchronized void setDispatchAvailableState(boolean available) {
436         switch (mState) {
437             case STATE_UNINITIALIZED:
438             case STATE_NOT_SUPPORTED:
439                 throw(new IllegalStateException(
440                         "Should not update available state in state:" + mState));
441             case STATE_DISABLED_UNAVAILABLE:
442                 if (available) {
443                     mState = STATE_DISABLED_AVAILABLE;
444                     break;
445                 } else {
446                     // already in unavailable state
447                     return;
448                 }
449             case STATE_ENABLED_UNAVAILABLE:
450                 if (available) {
451                     mState = STATE_ENABLED_AVAILABLE;
452                     break;
453                 } else {
454                     // already in unavailable state
455                     return;
456                 }
457             case STATE_DISABLED_AVAILABLE:
458                 if (available) {
459                     // already in available state
460                     return;
461                 } else {
462                     mState = STATE_DISABLED_UNAVAILABLE;
463                     break;
464                 }
465             case STATE_ENABLED_AVAILABLE:
466                 if (available) {
467                     // already in available state
468                     return;
469                 } else {
470                     mState = STATE_ENABLED_UNAVAILABLE;
471                     break;
472                 }
473         }
474         final int nbCallbacks = mStateCallbacks.beginBroadcast();
475         for (int i = 0; i < nbCallbacks; i++) {
476             try {
477                 mStateCallbacks.getBroadcastItem(i)
478                         .dispatchSpatializerAvailableChanged(available);
479             } catch (RemoteException e) {
480                 Log.e(TAG, "Error in dispatchSpatializerEnabledChanged", e);
481             }
482         }
483         mStateCallbacks.finishBroadcast();
484     }
485 
486     //------------------------------------------------------
487     // native Spatializer management
488 
489     /**
490      * precondition: mState == STATE_DISABLED_*
491      */
createSpat()492     private void createSpat() {
493         if (mSpat == null) {
494             mSpatCallback = new SpatializerCallback();
495             mSpatHeadTrackingCallback = new SpatializerHeadTrackingCallback();
496             mSpat = AudioSystem.getSpatializer(mSpatCallback);
497             try {
498                 mSpat.setLevel((byte)  Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_MULTICHANNEL);
499                 //TODO: register heatracking callback only when sensors are registered
500                 if (mSpat.isHeadTrackingSupported()) {
501                     mSpat.registerHeadTrackingCallback(mSpatHeadTrackingCallback);
502                 }
503             } catch (RemoteException e) {
504                 Log.e(TAG, "Can't set spatializer level", e);
505                 mState = STATE_NOT_SUPPORTED;
506                 mCapableSpatLevel = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE;
507             }
508         }
509     }
510 
511     /**
512      * precondition: mState == STATE_ENABLED_*
513      */
releaseSpat()514     private void releaseSpat() {
515         if (mSpat != null) {
516             mSpatCallback = null;
517             try {
518                 mSpat.registerHeadTrackingCallback(null);
519                 mSpat.release();
520                 mSpat = null;
521             } catch (RemoteException e) {
522                 Log.e(TAG, "Can't set release spatializer cleanly", e);
523             }
524         }
525     }
526 
527     //------------------------------------------------------
528     // virtualization capabilities
canBeSpatialized( @onNull AudioAttributes attributes, @NonNull AudioFormat format)529     synchronized boolean canBeSpatialized(
530             @NonNull AudioAttributes attributes, @NonNull AudioFormat format) {
531         logd("canBeSpatialized usage:" + attributes.getUsage()
532                 + " format:" + format.toLogFriendlyString());
533         switch (mState) {
534             case STATE_UNINITIALIZED:
535             case STATE_NOT_SUPPORTED:
536             case STATE_ENABLED_UNAVAILABLE:
537             case STATE_DISABLED_UNAVAILABLE:
538                 logd("canBeSpatialized false due to state:" + mState);
539                 return false;
540             case STATE_DISABLED_AVAILABLE:
541             case STATE_ENABLED_AVAILABLE:
542                 break;
543         }
544 
545         // filter on AudioAttributes usage
546         switch (attributes.getUsage()) {
547             case AudioAttributes.USAGE_MEDIA:
548             case AudioAttributes.USAGE_GAME:
549                 break;
550             default:
551                 logd("canBeSpatialized false due to usage:" + attributes.getUsage());
552                 return false;
553         }
554         AudioDeviceAttributes[] devices = new AudioDeviceAttributes[1];
555         // going through adapter to take advantage of routing cache
556         mASA.getDevicesForAttributes(attributes).toArray(devices);
557         final boolean able = AudioSystem.canBeSpatialized(attributes, format, devices);
558         logd("canBeSpatialized returning " + able);
559         return able;
560     }
561 
562     //------------------------------------------------------
563     // head tracking
564     final RemoteCallbackList<ISpatializerHeadTrackingModeCallback> mHeadTrackingModeCallbacks =
565             new RemoteCallbackList<ISpatializerHeadTrackingModeCallback>();
566 
registerHeadTrackingModeCallback( @onNull ISpatializerHeadTrackingModeCallback callback)567     synchronized void registerHeadTrackingModeCallback(
568             @NonNull ISpatializerHeadTrackingModeCallback callback) {
569         mHeadTrackingModeCallbacks.register(callback);
570     }
571 
unregisterHeadTrackingModeCallback( @onNull ISpatializerHeadTrackingModeCallback callback)572     synchronized void unregisterHeadTrackingModeCallback(
573             @NonNull ISpatializerHeadTrackingModeCallback callback) {
574         mHeadTrackingModeCallbacks.unregister(callback);
575     }
576 
getSupportedHeadTrackingModes()577     synchronized int[] getSupportedHeadTrackingModes() {
578         switch (mState) {
579             case STATE_UNINITIALIZED:
580                 return new int[0];
581             case STATE_NOT_SUPPORTED:
582                 // return an empty list when Spatializer functionality is not supported
583                 // because the list of head tracking modes you can set is actually empty
584                 // as defined in {@link Spatializer#getSupportedHeadTrackingModes()}
585                 return new int[0];
586             case STATE_ENABLED_UNAVAILABLE:
587             case STATE_DISABLED_UNAVAILABLE:
588             case STATE_DISABLED_AVAILABLE:
589             case STATE_ENABLED_AVAILABLE:
590                 if (mSpat == null) {
591                     return new int[0];
592                 }
593                 break;
594         }
595         // mSpat != null
596         try {
597             final byte[] values = mSpat.getSupportedHeadTrackingModes();
598             ArrayList<Integer> list = new ArrayList<>(0);
599             for (byte value : values) {
600                 switch (value) {
601                     case SpatializerHeadTrackingMode.OTHER:
602                     case SpatializerHeadTrackingMode.DISABLED:
603                         // not expected here, skip
604                         break;
605                     case SpatializerHeadTrackingMode.RELATIVE_WORLD:
606                     case SpatializerHeadTrackingMode.RELATIVE_SCREEN:
607                         list.add(headTrackingModeTypeToSpatializerInt(value));
608                         break;
609                     default:
610                         Log.e(TAG, "Unexpected head tracking mode:" + value,
611                                 new IllegalArgumentException("invalid mode"));
612                         break;
613                 }
614             }
615             int[] modes = new int[list.size()];
616             for (int i = 0; i < list.size(); i++) {
617                 modes[i] = list.get(i);
618             }
619             return modes;
620         } catch (RemoteException e) {
621             Log.e(TAG, "Error calling getSupportedHeadTrackingModes", e);
622             return new int[] { Spatializer.HEAD_TRACKING_MODE_UNSUPPORTED };
623         }
624     }
625 
getActualHeadTrackingMode()626     synchronized int getActualHeadTrackingMode() {
627         switch (mState) {
628             case STATE_UNINITIALIZED:
629                 return Spatializer.HEAD_TRACKING_MODE_DISABLED;
630             case STATE_NOT_SUPPORTED:
631                 return Spatializer.HEAD_TRACKING_MODE_UNSUPPORTED;
632             case STATE_ENABLED_UNAVAILABLE:
633             case STATE_DISABLED_UNAVAILABLE:
634             case STATE_DISABLED_AVAILABLE:
635             case STATE_ENABLED_AVAILABLE:
636                 if (mSpat == null) {
637                     return Spatializer.HEAD_TRACKING_MODE_DISABLED;
638                 }
639                 break;
640         }
641         // mSpat != null
642         try {
643             return headTrackingModeTypeToSpatializerInt(mSpat.getActualHeadTrackingMode());
644         } catch (RemoteException e) {
645             Log.e(TAG, "Error calling getActualHeadTrackingMode", e);
646             return Spatializer.HEAD_TRACKING_MODE_UNSUPPORTED;
647         }
648     }
649 
getDesiredHeadTrackingMode()650     synchronized int getDesiredHeadTrackingMode() {
651         return mDesiredHeadTrackingMode;
652     }
653 
setGlobalTransform(@onNull float[] transform)654     synchronized void setGlobalTransform(@NonNull float[] transform) {
655         if (transform.length != 6) {
656             throw new IllegalArgumentException("invalid array size" + transform.length);
657         }
658         if (!checkSpatForHeadTracking("setGlobalTransform")) {
659             return;
660         }
661         try {
662             mSpat.setGlobalTransform(transform);
663         } catch (RemoteException e) {
664             Log.e(TAG, "Error calling setGlobalTransform", e);
665         }
666     }
667 
recenterHeadTracker()668     synchronized void recenterHeadTracker() {
669         if (!checkSpatForHeadTracking("recenterHeadTracker")) {
670             return;
671         }
672         try {
673             mSpat.recenterHeadTracker();
674         } catch (RemoteException e) {
675             Log.e(TAG, "Error calling recenterHeadTracker", e);
676         }
677     }
678 
setDesiredHeadTrackingMode(@patializer.HeadTrackingModeSet int mode)679     synchronized void setDesiredHeadTrackingMode(@Spatializer.HeadTrackingModeSet int mode) {
680         if (!checkSpatForHeadTracking("setDesiredHeadTrackingMode")) {
681             return;
682         }
683         try {
684             if (mode != mDesiredHeadTrackingMode) {
685                 mSpat.setDesiredHeadTrackingMode(spatializerIntToHeadTrackingModeType(mode));
686                 mDesiredHeadTrackingMode = mode;
687                 dispatchDesiredHeadTrackingMode(mode);
688             }
689 
690         } catch (RemoteException e) {
691             Log.e(TAG, "Error calling setDesiredHeadTrackingMode", e);
692         }
693     }
694 
checkSpatForHeadTracking(String funcName)695     private boolean checkSpatForHeadTracking(String funcName) {
696         switch (mState) {
697             case STATE_UNINITIALIZED:
698             case STATE_NOT_SUPPORTED:
699                 return false;
700             case STATE_ENABLED_UNAVAILABLE:
701             case STATE_DISABLED_UNAVAILABLE:
702             case STATE_DISABLED_AVAILABLE:
703             case STATE_ENABLED_AVAILABLE:
704                 if (mSpat == null) {
705                     throw (new IllegalStateException(
706                             "null Spatializer when calling " + funcName));
707                 }
708                 break;
709         }
710         return true;
711     }
712 
dispatchActualHeadTrackingMode(int newMode)713     private void dispatchActualHeadTrackingMode(int newMode) {
714         final int nbCallbacks = mHeadTrackingModeCallbacks.beginBroadcast();
715         for (int i = 0; i < nbCallbacks; i++) {
716             try {
717                 mHeadTrackingModeCallbacks.getBroadcastItem(i)
718                         .dispatchSpatializerActualHeadTrackingModeChanged(newMode);
719             } catch (RemoteException e) {
720                 Log.e(TAG, "Error in dispatchSpatializerActualHeadTrackingModeChanged", e);
721             }
722         }
723         mHeadTrackingModeCallbacks.finishBroadcast();
724     }
725 
dispatchDesiredHeadTrackingMode(int newMode)726     private void dispatchDesiredHeadTrackingMode(int newMode) {
727         final int nbCallbacks = mHeadTrackingModeCallbacks.beginBroadcast();
728         for (int i = 0; i < nbCallbacks; i++) {
729             try {
730                 mHeadTrackingModeCallbacks.getBroadcastItem(i)
731                         .dispatchSpatializerDesiredHeadTrackingModeChanged(newMode);
732             } catch (RemoteException e) {
733                 Log.e(TAG, "Error in dispatchSpatializerDesiredHeadTrackingModeChanged", e);
734             }
735         }
736         mHeadTrackingModeCallbacks.finishBroadcast();
737     }
738 
739     //------------------------------------------------------
740     // head pose
741     final RemoteCallbackList<ISpatializerHeadToSoundStagePoseCallback> mHeadPoseCallbacks =
742             new RemoteCallbackList<ISpatializerHeadToSoundStagePoseCallback>();
743 
registerHeadToSoundstagePoseCallback( @onNull ISpatializerHeadToSoundStagePoseCallback callback)744     synchronized void registerHeadToSoundstagePoseCallback(
745             @NonNull ISpatializerHeadToSoundStagePoseCallback callback) {
746         mHeadPoseCallbacks.register(callback);
747     }
748 
unregisterHeadToSoundstagePoseCallback( @onNull ISpatializerHeadToSoundStagePoseCallback callback)749     synchronized void unregisterHeadToSoundstagePoseCallback(
750             @NonNull ISpatializerHeadToSoundStagePoseCallback callback) {
751         mHeadPoseCallbacks.unregister(callback);
752     }
753 
dispatchPoseUpdate(float[] pose)754     private void dispatchPoseUpdate(float[] pose) {
755         final int nbCallbacks = mHeadPoseCallbacks.beginBroadcast();
756         for (int i = 0; i < nbCallbacks; i++) {
757             try {
758                 mHeadPoseCallbacks.getBroadcastItem(i)
759                         .dispatchPoseChanged(pose);
760             } catch (RemoteException e) {
761                 Log.e(TAG, "Error in dispatchPoseChanged", e);
762             }
763         }
764         mHeadPoseCallbacks.finishBroadcast();
765     }
766 
767     //------------------------------------------------------
768     // vendor parameters
setEffectParameter(int key, @NonNull byte[] value)769     synchronized void setEffectParameter(int key, @NonNull byte[] value) {
770         switch (mState) {
771             case STATE_UNINITIALIZED:
772             case STATE_NOT_SUPPORTED:
773                 throw (new IllegalStateException(
774                         "Can't set parameter key:" + key + " without a spatializer"));
775             case STATE_ENABLED_UNAVAILABLE:
776             case STATE_DISABLED_UNAVAILABLE:
777             case STATE_DISABLED_AVAILABLE:
778             case STATE_ENABLED_AVAILABLE:
779                 if (mSpat == null) {
780                     throw (new IllegalStateException(
781                             "null Spatializer for setParameter for key:" + key));
782                 }
783                 break;
784         }
785         // mSpat != null
786         try {
787             mSpat.setParameter(key, value);
788         } catch (RemoteException e) {
789             Log.e(TAG, "Error in setParameter for key:" + key, e);
790         }
791     }
792 
getEffectParameter(int key, @NonNull byte[] value)793     synchronized void getEffectParameter(int key, @NonNull byte[] value) {
794         switch (mState) {
795             case STATE_UNINITIALIZED:
796             case STATE_NOT_SUPPORTED:
797                 throw (new IllegalStateException(
798                         "Can't get parameter key:" + key + " without a spatializer"));
799             case STATE_ENABLED_UNAVAILABLE:
800             case STATE_DISABLED_UNAVAILABLE:
801             case STATE_DISABLED_AVAILABLE:
802             case STATE_ENABLED_AVAILABLE:
803                 if (mSpat == null) {
804                     throw (new IllegalStateException(
805                             "null Spatializer for getParameter for key:" + key));
806                 }
807                 break;
808         }
809         // mSpat != null
810         try {
811             mSpat.getParameter(key, value);
812         } catch (RemoteException e) {
813             Log.e(TAG, "Error in getParameter for key:" + key, e);
814         }
815     }
816 
817     //------------------------------------------------------
818     // output
819 
820     /** @see Spatializer#getOutput */
getOutput()821     synchronized int getOutput() {
822         switch (mState) {
823             case STATE_UNINITIALIZED:
824             case STATE_NOT_SUPPORTED:
825                 throw (new IllegalStateException(
826                         "Can't get output without a spatializer"));
827             case STATE_ENABLED_UNAVAILABLE:
828             case STATE_DISABLED_UNAVAILABLE:
829             case STATE_DISABLED_AVAILABLE:
830             case STATE_ENABLED_AVAILABLE:
831                 if (mSpat == null) {
832                     throw (new IllegalStateException(
833                             "null Spatializer for getOutput"));
834                 }
835                 break;
836         }
837         // mSpat != null
838         try {
839             return mSpat.getOutput();
840         } catch (RemoteException e) {
841             Log.e(TAG, "Error in getOutput", e);
842             return 0;
843         }
844     }
845 
846     final RemoteCallbackList<ISpatializerOutputCallback> mOutputCallbacks =
847             new RemoteCallbackList<ISpatializerOutputCallback>();
848 
registerSpatializerOutputCallback( @onNull ISpatializerOutputCallback callback)849     synchronized void registerSpatializerOutputCallback(
850             @NonNull ISpatializerOutputCallback callback) {
851         mOutputCallbacks.register(callback);
852     }
853 
unregisterSpatializerOutputCallback( @onNull ISpatializerOutputCallback callback)854     synchronized void unregisterSpatializerOutputCallback(
855             @NonNull ISpatializerOutputCallback callback) {
856         mOutputCallbacks.unregister(callback);
857     }
858 
dispatchOutputUpdate(int output)859     private void dispatchOutputUpdate(int output) {
860         final int nbCallbacks = mOutputCallbacks.beginBroadcast();
861         for (int i = 0; i < nbCallbacks; i++) {
862             try {
863                 mOutputCallbacks.getBroadcastItem(i).dispatchSpatializerOutputChanged(output);
864             } catch (RemoteException e) {
865                 Log.e(TAG, "Error in dispatchOutputUpdate", e);
866             }
867         }
868         mOutputCallbacks.finishBroadcast();
869     }
870 
871     //------------------------------------------------------
872     // sensors
postInitSensors()873     private void postInitSensors() {
874         mAudioService.postInitSpatializerHeadTrackingSensors();
875     }
876 
onInitSensors()877     synchronized void onInitSensors() {
878         final boolean init = (mSpatLevel != SpatializationLevel.NONE);
879         final String action = init ? "initializing" : "releasing";
880         if (mSpat == null) {
881             Log.e(TAG, "not " + action + " sensors, null spatializer");
882             return;
883         }
884         try {
885             if (!mSpat.isHeadTrackingSupported()) {
886                 Log.e(TAG, "not " + action + " sensors, spatializer doesn't support headtracking");
887                 return;
888             }
889         } catch (RemoteException e) {
890             Log.e(TAG, "not " + action + " sensors, error querying headtracking", e);
891             return;
892         }
893         int headHandle = -1;
894         int screenHandle = -1;
895         if (init) {
896             if (mSensorManager == null) {
897                 try {
898                     mSensorManager = (SensorManager)
899                             mAudioService.mContext.getSystemService(Context.SENSOR_SERVICE);
900                     mDynSensorCallback = new HelperDynamicSensorCallback();
901                     mSensorManager.registerDynamicSensorCallback(mDynSensorCallback);
902                 } catch (Exception e) {
903                     Log.e(TAG, "Error with SensorManager, can't initialize sensors", e);
904                     mSensorManager = null;
905                     mDynSensorCallback = null;
906                     return;
907                 }
908             }
909             // initialize sensor handles
910             // TODO-HT update to non-private sensor once head tracker sensor is defined
911             for (Sensor sensor : mSensorManager.getDynamicSensorList(
912                     Sensor.TYPE_DEVICE_PRIVATE_BASE)) {
913                 if (sensor.getStringType().equals(HEADTRACKER_SENSOR)) {
914                     headHandle = sensor.getHandle();
915                     break;
916                 }
917             }
918             Sensor screenSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR);
919             screenHandle = screenSensor.getHandle();
920         } else {
921             if (mSensorManager != null && mDynSensorCallback != null) {
922                 mSensorManager.unregisterDynamicSensorCallback(mDynSensorCallback);
923                 mSensorManager = null;
924                 mDynSensorCallback = null;
925             }
926             // -1 is disable value for both screen and head tracker handles
927         }
928         try {
929             Log.i(TAG, "setScreenSensor:" + screenHandle);
930             mSpat.setScreenSensor(screenHandle);
931         } catch (Exception e) {
932             Log.e(TAG, "Error calling setScreenSensor:" + screenHandle, e);
933         }
934         try {
935             Log.i(TAG, "setHeadSensor:" + headHandle);
936             mSpat.setHeadSensor(headHandle);
937         } catch (Exception e) {
938             Log.e(TAG, "Error calling setHeadSensor:" + headHandle, e);
939         }
940     }
941 
942     //------------------------------------------------------
943     // SDK <-> AIDL converters
headTrackingModeTypeToSpatializerInt(byte mode)944     private static int headTrackingModeTypeToSpatializerInt(byte mode) {
945         switch (mode) {
946             case SpatializerHeadTrackingMode.OTHER:
947                 return Spatializer.HEAD_TRACKING_MODE_OTHER;
948             case SpatializerHeadTrackingMode.DISABLED:
949                 return Spatializer.HEAD_TRACKING_MODE_DISABLED;
950             case SpatializerHeadTrackingMode.RELATIVE_WORLD:
951                 return Spatializer.HEAD_TRACKING_MODE_RELATIVE_WORLD;
952             case SpatializerHeadTrackingMode.RELATIVE_SCREEN:
953                 return Spatializer.HEAD_TRACKING_MODE_RELATIVE_DEVICE;
954             default:
955                 throw(new IllegalArgumentException("Unexpected head tracking mode:" + mode));
956         }
957     }
958 
spatializerIntToHeadTrackingModeType(int sdkMode)959     private static byte spatializerIntToHeadTrackingModeType(int sdkMode) {
960         switch (sdkMode) {
961             case Spatializer.HEAD_TRACKING_MODE_OTHER:
962                 return SpatializerHeadTrackingMode.OTHER;
963             case Spatializer.HEAD_TRACKING_MODE_DISABLED:
964                 return SpatializerHeadTrackingMode.DISABLED;
965             case Spatializer.HEAD_TRACKING_MODE_RELATIVE_WORLD:
966                 return SpatializerHeadTrackingMode.RELATIVE_WORLD;
967             case Spatializer.HEAD_TRACKING_MODE_RELATIVE_DEVICE:
968                 return SpatializerHeadTrackingMode.RELATIVE_SCREEN;
969             default:
970                 throw(new IllegalArgumentException("Unexpected head tracking mode:" + sdkMode));
971         }
972     }
973 
spatializationLevelToSpatializerInt(byte level)974     private static int spatializationLevelToSpatializerInt(byte level) {
975         switch (level) {
976             case SpatializationLevel.NONE:
977                 return Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE;
978             case SpatializationLevel.SPATIALIZER_MULTICHANNEL:
979                 return Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_MULTICHANNEL;
980             case SpatializationLevel.SPATIALIZER_MCHAN_BED_PLUS_OBJECTS:
981                 return Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_MCHAN_BED_PLUS_OBJECTS;
982             default:
983                 throw(new IllegalArgumentException("Unexpected spatializer level:" + level));
984         }
985     }
986 }
987