From 08635fe057050f19ee7290ecba1091887a6fd87b Mon Sep 17 00:00:00 2001 From: Nate Myren Date: Tue, 20 Apr 2021 12:04:39 -0700 Subject: [PATCH] AudioManager: add API for mode listener New API to add/remove a listener for changes to the audio mode (see existing AudioManager.getMode() API). Bug: 184891081 Test: atest android.media.cts.AudioModeListenerTest Change-Id: I0be07d577c7497066b61a1573752343227605fd7 --- core/api/current.txt | 6 + media/java/android/media/AudioManager.java | 153 ++++++++++++++++++ .../android/media/IAudioModeDispatcher.aidl | 28 ++++ media/java/android/media/IAudioService.aidl | 5 + .../android/server/audio/AudioService.java | 41 +++++ 5 files changed, 233 insertions(+) create mode 100644 media/java/android/media/IAudioModeDispatcher.aidl diff --git a/core/api/current.txt b/core/api/current.txt index 264c9d0134f43..0604472376388 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -20358,6 +20358,7 @@ package android.media { method @Deprecated public int abandonAudioFocus(android.media.AudioManager.OnAudioFocusChangeListener); method public int abandonAudioFocusRequest(@NonNull android.media.AudioFocusRequest); method public void addOnCommunicationDeviceChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.OnCommunicationDeviceChangedListener); + method public void addOnModeChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.OnModeChangedListener); method public void adjustStreamVolume(int, int, int); method public void adjustSuggestedStreamVolume(int, int, int); method public void adjustVolume(int, int); @@ -20408,6 +20409,7 @@ package android.media { method @Deprecated public void registerRemoteControlClient(android.media.RemoteControlClient); method @Deprecated public boolean registerRemoteController(android.media.RemoteController); method public void removeOnCommunicationDeviceChangedListener(@NonNull android.media.AudioManager.OnCommunicationDeviceChangedListener); + method public void removeOnModeChangedListener(@NonNull android.media.AudioManager.OnModeChangedListener); method @Deprecated public int requestAudioFocus(android.media.AudioManager.OnAudioFocusChangeListener, int, int); method public int requestAudioFocus(@NonNull android.media.AudioFocusRequest); method public void setAllowedCapturePolicy(int); @@ -20563,6 +20565,10 @@ package android.media { method public void onCommunicationDeviceChanged(@Nullable android.media.AudioDeviceInfo); } + public static interface AudioManager.OnModeChangedListener { + method public void onModeChanged(int); + } + public final class AudioMetadata { method @NonNull public static android.media.AudioMetadataMap createMap(); } diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index 4dc1cca9848d7..f2f9a26a84e96 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -2845,6 +2845,159 @@ public class AudioManager { } } + /** + * Interface definition of a callback that is notified when the audio mode changes + */ + public interface OnModeChangedListener { + /** + * Called on the listener to indicate that the audio mode has changed + * + * @param mode The current audio mode + */ + void onModeChanged(@AudioMode int mode); + } + + private final Object mModeListenerLock = new Object(); + /** + * List of listeners for audio mode and their associated Executor. + * List is lazy-initialized on first registration + */ + @GuardedBy("mModeListenerLock") + private @Nullable ArrayList mModeListeners; + + @GuardedBy("mModeListenerLock") + private ModeDispatcherStub mModeDispatcherStub; + + private final class ModeDispatcherStub + extends IAudioModeDispatcher.Stub { + + @Override + public void dispatchAudioModeChanged(int mode) { + // make a shallow copy of listeners so callback is not executed under lock + final ArrayList modeListeners; + synchronized (mModeListenerLock) { + if (mModeListeners == null || mModeListeners.size() == 0) { + return; + } + modeListeners = (ArrayList) mModeListeners.clone(); + } + final long ident = Binder.clearCallingIdentity(); + try { + for (ModeListenerInfo info : modeListeners) { + info.mExecutor.execute(() -> + info.mListener.onModeChanged(mode)); + } + } finally { + Binder.restoreCallingIdentity(ident); + } + } + } + + private static class ModeListenerInfo { + final @NonNull OnModeChangedListener mListener; + final @NonNull Executor mExecutor; + + ModeListenerInfo(OnModeChangedListener listener, Executor exe) { + mListener = listener; + mExecutor = exe; + } + } + + @GuardedBy("mModeListenerLock") + private boolean hasModeListener(OnModeChangedListener listener) { + return getModeListenerInfo(listener) != null; + } + + @GuardedBy("mModeListenerLock") + private @Nullable ModeListenerInfo getModeListenerInfo( + OnModeChangedListener listener) { + if (mModeListeners == null) { + return null; + } + for (ModeListenerInfo info : mModeListeners) { + if (info.mListener == listener) { + return info; + } + } + return null; + } + + + @GuardedBy("mModeListenerLock") + /** + * @return true if the listener was removed from the list + */ + private boolean removeModeListener(OnModeChangedListener listener) { + final ModeListenerInfo infoToRemove = getModeListenerInfo(listener); + if (infoToRemove != null) { + mModeListeners.remove(infoToRemove); + return true; + } + return false; + } + + /** + * Adds a listener to be notified of changes to the audio mode. + * See {@link #getMode()} + * @param executor + * @param listener + */ + public void addOnModeChangedListener( + @NonNull @CallbackExecutor Executor executor, + @NonNull OnModeChangedListener listener) { + Objects.requireNonNull(executor); + Objects.requireNonNull(listener); + synchronized (mModeListenerLock) { + if (hasModeListener(listener)) { + throw new IllegalArgumentException("attempt to call addOnModeChangedListener() " + + "on a previously registered listener"); + } + // lazy initialization of the list of strategy-preferred device listener + if (mModeListeners == null) { + mModeListeners = new ArrayList<>(); + } + final int oldCbCount = mModeListeners.size(); + mModeListeners.add(new ModeListenerInfo(listener, executor)); + if (oldCbCount == 0) { + // register binder for callbacks + if (mModeDispatcherStub == null) { + mModeDispatcherStub = new ModeDispatcherStub(); + } + try { + getService().registerModeDispatcher(mModeDispatcherStub); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + } + } + + /** + * Removes a previously added listener for changes to audio mode. + * See {@link #getMode()} + * @param listener + */ + public void removeOnModeChangedListener(@NonNull OnModeChangedListener listener) { + Objects.requireNonNull(listener); + synchronized (mModeListenerLock) { + if (!removeModeListener(listener)) { + throw new IllegalArgumentException("attempt to call removeOnModeChangedListener() " + + "on an unregistered listener"); + } + if (mModeListeners.size() == 0) { + // unregister binder for callbacks + try { + getService().unregisterModeDispatcher(mModeDispatcherStub); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } finally { + mModeDispatcherStub = null; + mModeListeners = null; + } + } + } + } + /** * Indicates if the platform supports a special call screening and call monitoring mode. *

diff --git a/media/java/android/media/IAudioModeDispatcher.aidl b/media/java/android/media/IAudioModeDispatcher.aidl new file mode 100644 index 0000000000000..6162541b67ad5 --- /dev/null +++ b/media/java/android/media/IAudioModeDispatcher.aidl @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2021 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 audio mode changes. + * + * {@hide} + */ +oneway interface IAudioModeDispatcher { + + void dispatchAudioModeChanged(int mode); + +} diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl index 7a6369cd7c6d6..c08c368af55aa 100755 --- a/media/java/android/media/IAudioService.aidl +++ b/media/java/android/media/IAudioService.aidl @@ -25,6 +25,7 @@ import android.media.AudioPlaybackConfiguration; import android.media.AudioRecordingConfiguration; import android.media.AudioRoutesInfo; import android.media.IAudioFocusDispatcher; +import android.media.IAudioModeDispatcher; import android.media.IAudioRoutesObserver; import android.media.IAudioServerStateDispatcher; import android.media.ICapturePresetDevicesRoleDispatcher; @@ -383,4 +384,8 @@ interface IAudioService { in AudioAttributes aa, in String callingPackageName); long getFadeOutDurationOnFocusLossMillis(in AudioAttributes aa); + + void registerModeDispatcher(IAudioModeDispatcher dispatcher); + + oneway void unregisterModeDispatcher(IAudioModeDispatcher dispatcher); } diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index edacd40049b4d..a55605ed3c15e 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -84,6 +84,7 @@ import android.media.AudioRecordingConfiguration; import android.media.AudioRoutesInfo; import android.media.AudioSystem; import android.media.IAudioFocusDispatcher; +import android.media.IAudioModeDispatcher; import android.media.IAudioRoutesObserver; import android.media.IAudioServerStateDispatcher; import android.media.IAudioService; @@ -120,6 +121,7 @@ import android.os.Looper; import android.os.Message; import android.os.PowerManager; import android.os.Process; +import android.os.RemoteCallbackList; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemClock; @@ -312,6 +314,7 @@ public class AudioService extends IAudioService.Stub private static final int MSG_RECORDING_CONFIG_CHANGE = 37; private static final int MSG_SET_A2DP_DEV_CONNECTION_STATE = 38; private static final int MSG_A2DP_DEV_CONFIG_CHANGE = 39; + private static final int MSG_DISPATCH_AUDIO_MODE = 40; // start of messages handled under wakelock // these messages can only be queued, i.e. sent with queueMsgUnderWakeLock(), @@ -4712,6 +4715,8 @@ public class AudioService extends IAudioService.Stub if (DEBUG_MODE) { Log.v(TAG, "onUpdateAudioMode: mode successfully set to " + mode); } + sendMsg(mAudioHandler, MSG_DISPATCH_AUDIO_MODE, SENDMSG_REPLACE, mode, 0, + /*obj*/ null, /*delay*/ 0); int previousMode = mMode.getAndSet(mode); // Note: newModeOwnerPid is always 0 when actualMode is MODE_NORMAL mModeLogger.log(new PhoneStateEvent(requesterPackage, requesterPid, @@ -4756,6 +4761,38 @@ public class AudioService extends IAudioService.Stub return mIsCallScreeningModeSupported; } + protected void dispatchMode(int mode) { + final int nbDispatchers = mModeDispatchers.beginBroadcast(); + for (int i = 0; i < nbDispatchers; i++) { + try { + mModeDispatchers.getBroadcastItem(i).dispatchAudioModeChanged(mode); + } catch (RemoteException e) { + } + } + mModeDispatchers.finishBroadcast(); + } + + final RemoteCallbackList mModeDispatchers = + new RemoteCallbackList(); + + /** + * @see {@link AudioManager#addOnModeChangedListener(Executor, AudioManager.OnModeChangedListener)} + * @param dispatcher + */ + public void registerModeDispatcher( + @NonNull IAudioModeDispatcher dispatcher) { + mModeDispatchers.register(dispatcher); + } + + /** + * @see {@link AudioManager#removeOnModeChangedListener(AudioManager.OnModeChangedListener)} + * @param dispatcher + */ + public void unregisterModeDispatcher( + @NonNull IAudioModeDispatcher dispatcher) { + mModeDispatchers.unregister(dispatcher); + } + /** @see AudioManager#setRttEnabled() */ @Override public void setRttEnabled(boolean rttEnabled) { @@ -7522,6 +7559,10 @@ public class AudioService extends IAudioService.Stub case MSG_A2DP_DEV_CONFIG_CHANGE: mDeviceBroker.postBluetoothA2dpDeviceConfigChange((BluetoothDevice) msg.obj); break; + + case MSG_DISPATCH_AUDIO_MODE: + dispatchMode(msg.arg1); + break; } } }