diff --git a/core/api/current.txt b/core/api/current.txt index 3bd49b935d579..70b6c5cc0dc26 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -23548,17 +23548,24 @@ package android.media { } public class Spatializer { + method public void addOnHeadTrackerAvailableListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.Spatializer.OnHeadTrackerAvailableListener); method public void addOnSpatializerStateChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.Spatializer.OnSpatializerStateChangedListener); method public boolean canBeSpatialized(@NonNull android.media.AudioAttributes, @NonNull android.media.AudioFormat); method public int getImmersiveAudioLevel(); method public boolean isAvailable(); method public boolean isEnabled(); + method public boolean isHeadTrackerAvailable(); + method public void removeOnHeadTrackerAvailableListener(@NonNull android.media.Spatializer.OnHeadTrackerAvailableListener); method public void removeOnSpatializerStateChangedListener(@NonNull android.media.Spatializer.OnSpatializerStateChangedListener); field public static final int SPATIALIZER_IMMERSIVE_LEVEL_MULTICHANNEL = 1; // 0x1 field public static final int SPATIALIZER_IMMERSIVE_LEVEL_NONE = 0; // 0x0 field public static final int SPATIALIZER_IMMERSIVE_LEVEL_OTHER = -1; // 0xffffffff } + public static interface Spatializer.OnHeadTrackerAvailableListener { + method public void onHeadTrackerAvailableChanged(@NonNull android.media.Spatializer, boolean); + } + public static interface Spatializer.OnSpatializerStateChangedListener { method public void onSpatializerAvailableChanged(@NonNull android.media.Spatializer, boolean); method public void onSpatializerEnabledChanged(@NonNull android.media.Spatializer, boolean); diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl index bdbb740a849b7..c186700a43261 100755 --- a/media/java/android/media/IAudioService.aidl +++ b/media/java/android/media/IAudioService.aidl @@ -39,6 +39,7 @@ import android.media.IRecordingConfigDispatcher; import android.media.IRingtonePlayer; import android.media.IStrategyPreferredDevicesDispatcher; import android.media.ISpatializerCallback; +import android.media.ISpatializerHeadTrackerAvailableCallback; import android.media.ISpatializerHeadTrackingModeCallback; import android.media.ISpatializerHeadToSoundStagePoseCallback; import android.media.ISpatializerOutputCallback; @@ -414,6 +415,11 @@ interface IAudioService { boolean isHeadTrackerEnabled(in AudioDeviceAttributes device); + boolean isHeadTrackerAvailable(); + + void registerSpatializerHeadTrackerAvailableCallback( + in ISpatializerHeadTrackerAvailableCallback cb, boolean register); + void setSpatializerEnabled(boolean enabled); boolean canBeSpatialized(in AudioAttributes aa, in AudioFormat af); diff --git a/media/java/android/media/ISpatializerHeadTrackerAvailableCallback.aidl b/media/java/android/media/ISpatializerHeadTrackerAvailableCallback.aidl new file mode 100644 index 0000000000000..dc5ee1d4e9856 --- /dev/null +++ b/media/java/android/media/ISpatializerHeadTrackerAvailableCallback.aidl @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.media; + +/** + * AIDL for the AudioService to signal whether audio device used by Spatializer has head tracker. + * + * {@hide} + */ +oneway interface ISpatializerHeadTrackerAvailableCallback { + + void dispatchSpatializerHeadTrackerAvailable(boolean available); +} diff --git a/media/java/android/media/Spatializer.java b/media/java/android/media/Spatializer.java index be0ef37345ed3..8811e16b3317b 100644 --- a/media/java/android/media/Spatializer.java +++ b/media/java/android/media/Spatializer.java @@ -185,6 +185,45 @@ public class Spatializer { return false; } + /** + * Returns whether a head tracker is currently available for the audio device used by the + * spatializer effect. + * @return true if a head tracker is available and the effect is enabled, false otherwise. + * @see OnHeadTrackerAvailableListener + * @see #addOnHeadTrackerAvailableListener(Executor, OnHeadTrackerAvailableListener) + */ + public boolean isHeadTrackerAvailable() { + try { + return mAm.getService().isHeadTrackerAvailable(); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + return false; + } + + /** + * Adds a listener to be notified of changes to the availability of a head tracker. + * @param executor the {@code Executor} handling the callback + * @param listener the listener to receive availability updates + * @see #removeOnHeadTrackerAvailableListener(OnHeadTrackerAvailableListener) + */ + public void addOnHeadTrackerAvailableListener(@NonNull @CallbackExecutor Executor executor, + @NonNull OnHeadTrackerAvailableListener listener) { + mHeadTrackerListenerMgr.addListener(executor, listener, + "addOnHeadTrackerAvailableListener", + () -> new SpatializerHeadTrackerAvailableDispatcherStub()); + } + + /** + * Removes a previously registered listener for the availability of a head tracker. + * @param listener the listener previously registered with + * {@link #addOnHeadTrackerAvailableListener(Executor, OnHeadTrackerAvailableListener)} + */ + public void removeOnHeadTrackerAvailableListener( + @NonNull OnHeadTrackerAvailableListener listener) { + mHeadTrackerListenerMgr.removeListener(listener, "removeOnHeadTrackerAvailableListener"); + } + /** @hide */ @IntDef(flag = false, value = { SPATIALIZER_IMMERSIVE_LEVEL_OTHER, @@ -401,6 +440,22 @@ public class Spatializer { @HeadTrackingModeSet int mode); } + /** + * Interface to be notified of changes to the availability of a head tracker on the audio + * device to be used by the spatializer effect. + */ + public interface OnHeadTrackerAvailableListener { + /** + * Called when the availability of the head tracker changed. + * @param spatializer the {@code Spatializer} instance for which the head tracker + * availability was updated + * @param available true if the audio device that would output audio processed by + * the {@code Spatializer} has a head tracker associated with it, false + * otherwise. + */ + void onHeadTrackerAvailableChanged(@NonNull Spatializer spatializer, + boolean available); + } /** * @hide @@ -934,6 +989,36 @@ public class Spatializer { } } + //----------------------------------------------------------------------------- + // head tracker availability callback management and stub + /** + * manages the OnHeadTrackerAvailableListener listeners and the + * SpatializerHeadTrackerAvailableDispatcherStub + */ + private final CallbackUtil.LazyListenerManager + mHeadTrackerListenerMgr = new CallbackUtil.LazyListenerManager(); + + private final class SpatializerHeadTrackerAvailableDispatcherStub + extends ISpatializerHeadTrackerAvailableCallback.Stub + implements CallbackUtil.DispatcherStub { + @Override + public void register(boolean register) { + try { + mAm.getService().registerSpatializerHeadTrackerAvailableCallback(this, register); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } + + @Override + @SuppressLint("GuardedBy") // lock applied inside callListeners method + public void dispatchSpatializerHeadTrackerAvailable(boolean available) { + mHeadTrackerListenerMgr.callListeners( + (listener) -> listener.onHeadTrackerAvailableChanged( + Spatializer.this, available)); + } + } + //----------------------------------------------------------------------------- // head pose callback management and stub private final Object mPoseListenerLock = new Object(); diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index d01be5820d348..309a4ff16753f 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -102,6 +102,7 @@ import android.media.IRecordingConfigDispatcher; import android.media.IRingtonePlayer; import android.media.ISpatializerCallback; import android.media.ISpatializerHeadToSoundStagePoseCallback; +import android.media.ISpatializerHeadTrackerAvailableCallback; import android.media.ISpatializerHeadTrackingModeCallback; import android.media.ISpatializerOutputCallback; import android.media.IStrategyPreferredDevicesDispatcher; @@ -8723,6 +8724,11 @@ public class AudioService extends IAudioService.Stub return mSpatializerHelper.isHeadTrackerEnabled(Objects.requireNonNull(device)); } + /** @see Spatializer#isHeadTrackerAvailable() */ + public boolean isHeadTrackerAvailable() { + return mSpatializerHelper.isHeadTrackerAvailable(); + } + /** @see Spatializer#setSpatializerEnabled(boolean) */ public void setSpatializerEnabled(boolean enabled) { enforceModifyDefaultAudioEffectsPermission(); @@ -8767,6 +8773,13 @@ public class AudioService extends IAudioService.Stub mSpatializerHelper.unregisterHeadTrackingModeCallback(cb); } + /** @see Spatializer.SpatializerHeadTrackerAvailableDispatcherStub */ + public void registerSpatializerHeadTrackerAvailableCallback( + @NonNull ISpatializerHeadTrackerAvailableCallback cb, boolean register) { + Objects.requireNonNull(cb); + mSpatializerHelper.registerHeadTrackerAvailableCallback(cb, register); + } + /** @see Spatializer#setOnHeadToSoundstagePoseUpdatedListener */ public void registerHeadToSoundstagePoseCallback( @NonNull ISpatializerHeadToSoundStagePoseCallback cb) { diff --git a/services/core/java/com/android/server/audio/SpatializerHelper.java b/services/core/java/com/android/server/audio/SpatializerHelper.java index 5af73c9e46ea0..4a853e18b3a45 100644 --- a/services/core/java/com/android/server/audio/SpatializerHelper.java +++ b/services/core/java/com/android/server/audio/SpatializerHelper.java @@ -30,6 +30,7 @@ import android.media.INativeSpatializerCallback; import android.media.ISpatializer; import android.media.ISpatializerCallback; import android.media.ISpatializerHeadToSoundStagePoseCallback; +import android.media.ISpatializerHeadTrackerAvailableCallback; import android.media.ISpatializerHeadTrackingCallback; import android.media.ISpatializerHeadTrackingModeCallback; import android.media.ISpatializerOutputCallback; @@ -126,6 +127,7 @@ public class SpatializerHelper { private boolean mBinauralSupported = false; private int mActualHeadTrackingMode = Spatializer.HEAD_TRACKING_MODE_UNSUPPORTED; private int mDesiredHeadTrackingMode = Spatializer.HEAD_TRACKING_MODE_RELATIVE_WORLD; + private boolean mHeadTrackerAvailable = false; /** * The desired head tracking mode when enabling head tracking, tracks mDesiredHeadTrackingMode, * except when head tracking gets disabled through setting the desired mode to @@ -831,6 +833,7 @@ public class SpatializerHelper { mSpatCallback = null; try { mSpat.registerHeadTrackingCallback(null); + mHeadTrackerAvailable = false; mSpat.release(); } catch (RemoteException e) { Log.e(TAG, "Can't set release spatializer cleanly", e); @@ -890,6 +893,18 @@ public class SpatializerHelper { mHeadTrackingModeCallbacks.unregister(callback); } + final RemoteCallbackList mHeadTrackerCallbacks = + new RemoteCallbackList<>(); + + synchronized void registerHeadTrackerAvailableCallback( + @NonNull ISpatializerHeadTrackerAvailableCallback cb, boolean register) { + if (register) { + mHeadTrackerCallbacks.register(cb); + } else { + mHeadTrackerCallbacks.unregister(cb); + } + } + synchronized int[] getSupportedHeadTrackingModes() { switch (mState) { case STATE_UNINITIALIZED: @@ -1090,6 +1105,10 @@ public class SpatializerHelper { return false; } + synchronized boolean isHeadTrackerAvailable() { + return mHeadTrackerAvailable; + } + private boolean checkSpatForHeadTracking(String funcName) { switch (mState) { case STATE_UNINITIALIZED: @@ -1115,7 +1134,8 @@ public class SpatializerHelper { mHeadTrackingModeCallbacks.getBroadcastItem(i) .dispatchSpatializerActualHeadTrackingModeChanged(newMode); } catch (RemoteException e) { - Log.e(TAG, "Error in dispatchSpatializerActualHeadTrackingModeChanged", e); + Log.e(TAG, "Error in dispatchSpatializerActualHeadTrackingModeChanged(" + + newMode + ")", e); } } mHeadTrackingModeCallbacks.finishBroadcast(); @@ -1128,12 +1148,27 @@ public class SpatializerHelper { mHeadTrackingModeCallbacks.getBroadcastItem(i) .dispatchSpatializerDesiredHeadTrackingModeChanged(newMode); } catch (RemoteException e) { - Log.e(TAG, "Error in dispatchSpatializerDesiredHeadTrackingModeChanged", e); + Log.e(TAG, "Error in dispatchSpatializerDesiredHeadTrackingModeChanged(" + + newMode + ")", e); } } mHeadTrackingModeCallbacks.finishBroadcast(); } + private void dispatchHeadTrackerAvailable(boolean available) { + final int nbCallbacks = mHeadTrackerCallbacks.beginBroadcast(); + for (int i = 0; i < nbCallbacks; i++) { + try { + mHeadTrackerCallbacks.getBroadcastItem(i) + .dispatchSpatializerHeadTrackerAvailable(available); + } catch (RemoteException e) { + Log.e(TAG, "Error in dispatchSpatializerHeadTrackerAvailable(" + + available + ")", e); + } + } + mHeadTrackerCallbacks.finishBroadcast(); + } + //------------------------------------------------------ // head pose final RemoteCallbackList mHeadPoseCallbacks = @@ -1348,6 +1383,10 @@ public class SpatializerHelper { try { Log.i(TAG, "setHeadSensor:" + headHandle); mSpat.setHeadSensor(headHandle); + if (mHeadTrackerAvailable != (headHandle != -1)) { + mHeadTrackerAvailable = (headHandle != -1); + dispatchHeadTrackerAvailable(mHeadTrackerAvailable); + } } catch (Exception e) { Log.e(TAG, "Error calling setHeadSensor:" + headHandle, e); }