summaryrefslogtreecommitdiff
path: root/media
diff options
context:
space:
mode:
author Francois Gaffie <francois.gaffie@renault.com> 2021-06-22 10:19:41 +0200
committer Oscar Azucena <oscarazu@google.com> 2025-03-17 11:00:55 -0700
commit1c11482afa783b3f65391dcb99c56d17f9d976cb (patch)
tree6ef5315bd63d97c41921a16a64c5c4db0d46e698 /media
parent405929567b67839ec13c2caf644b6472c9f714d9 (diff)
AudioService: move volume callback on AudioService
Robustness to crash of AudioServer. Take benefit of future permission enforcement. Bug: 293236285 Flag: EXEMPT refactor Test: atest com.android.server.audio Signed-off-by: Francois Gaffie <francois.gaffie@renault.com> Change-Id: If556a668f45d608b98b6e8073426f5bb8128c7a2
Diffstat (limited to 'media')
-rw-r--r--media/java/android/media/AudioManager.java80
-rw-r--r--media/java/android/media/AudioSystem.java21
-rw-r--r--media/java/android/media/IAudioService.aidl5
-rw-r--r--media/java/android/media/audiopolicy/AudioVolumeGroupChangeHandler.java166
-rw-r--r--media/java/android/media/audiopolicy/IAudioVolumeChangeDispatcher.aidl31
-rw-r--r--media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioVolumeGroupChangeHandlerTest.java211
6 files changed, 112 insertions, 402 deletions
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 4aba491c291e..f0890d11c95e 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -65,7 +65,7 @@ import android.media.audiopolicy.AudioPolicy;
import android.media.audiopolicy.AudioPolicy.AudioPolicyFocusListener;
import android.media.audiopolicy.AudioProductStrategy;
import android.media.audiopolicy.AudioVolumeGroup;
-import android.media.audiopolicy.AudioVolumeGroupChangeHandler;
+import android.media.audiopolicy.IAudioVolumeChangeDispatcher;
import android.media.projection.MediaProjection;
import android.media.session.MediaController;
import android.media.session.MediaSession;
@@ -128,8 +128,6 @@ public class AudioManager {
private static final String TAG = "AudioManager";
private static final boolean DEBUG = false;
private static final AudioPortEventHandler sAudioPortEventHandler = new AudioPortEventHandler();
- private static final AudioVolumeGroupChangeHandler sAudioAudioVolumeGroupChangedHandler =
- new AudioVolumeGroupChangeHandler();
private static WeakReference<Context> sContext;
@@ -8761,9 +8759,13 @@ public class AudioManager {
}
}
+ //====================================================================
+ // Notification of volume group changes
/**
+ * Callback to receive updates on volume group changes, register using
+ * {@link AudioManager#registerVolumeGroupCallback(Executor, AudioVolumeCallback)}.
+ *
* @hide
- * Callback registered by client to be notified upon volume group change.
*/
@SystemApi
public abstract static class VolumeGroupCallback {
@@ -8774,35 +8776,63 @@ public class AudioManager {
public void onAudioVolumeGroupChanged(int group, int flags) {}
}
- /**
- * @hide
- * Register an audio volume group change listener.
- * @param callback the {@link VolumeGroupCallback} to register
- */
+ /**
+ * @hide
+ * Register an audio volume group change listener.
+ *
+ * @param executor {@link Executor} to handle the callbacks
+ * @param callback the callback to receive the audio volume group changes
+ * @throws SecurityException if the caller doesn't have the required permission.
+ */
@SystemApi
- public void registerVolumeGroupCallback(
- @NonNull Executor executor,
+ public void registerVolumeGroupCallback(@NonNull Executor executor,
@NonNull VolumeGroupCallback callback) {
- Preconditions.checkNotNull(executor, "executor must not be null");
- Preconditions.checkNotNull(callback, "volume group change cb must not be null");
- sAudioAudioVolumeGroupChangedHandler.init();
- // TODO: make use of executor
- sAudioAudioVolumeGroupChangedHandler.registerListener(callback);
+ mVolumeChangedListenerMgr.addListener(executor, callback, "registerVolumeGroupCallback",
+ () -> new AudioVolumeChangeDispatcherStub());
}
- /**
- * @hide
- * Unregister an audio volume group change listener.
- * @param callback the {@link VolumeGroupCallback} to unregister
- */
+ /**
+ * @hide
+ * Unregister an audio volume group change listener.
+ * @param callback the {@link VolumeGroupCallback} to unregister
+ */
@SystemApi
- public void unregisterVolumeGroupCallback(
- @NonNull VolumeGroupCallback callback) {
- Preconditions.checkNotNull(callback, "volume group change cb must not be null");
- sAudioAudioVolumeGroupChangedHandler.unregisterListener(callback);
+ public void unregisterVolumeGroupCallback(@NonNull VolumeGroupCallback callback) {
+ mVolumeChangedListenerMgr.removeListener(callback, "unregisterVolumeGroupCallback");
}
/**
+ * Manages the VolumeGroupCallback listeners and the AudioVolumeChangeDispatcherStub
+ */
+ private final CallbackUtil.LazyListenerManager<VolumeGroupCallback> mVolumeChangedListenerMgr =
+ new CallbackUtil.LazyListenerManager();
+
+ final class AudioVolumeChangeDispatcherStub extends IAudioVolumeChangeDispatcher.Stub
+ implements CallbackUtil.DispatcherStub {
+
+ @Override
+ public void register(boolean register) {
+ try {
+ if (register) {
+ getService().registerAudioVolumeCallback(this);
+ } else {
+ getService().unregisterAudioVolumeCallback(this);
+ }
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ @Override
+ public void onAudioVolumeGroupChanged(int group, int flags) {
+ mVolumeChangedListenerMgr.callListeners((listener) ->
+ listener.onAudioVolumeGroupChanged(group, flags));
+ }
+ }
+
+ //====================================================================
+
+ /**
* Return if an asset contains haptic channels or not.
*
* @param context the {@link Context} to resolve the uri.
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index ad6f2e52fd97..4906cd3fb1e5 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -2732,4 +2732,25 @@ public class AudioSystem
* @hide
*/
public static native void triggerSystemPropertyUpdate(long handle);
+
+ /**
+ * Registers the given {@link INativeAudioVolumeGroupCallback} to native audioserver.
+ * @param callback to register
+ * @return {@link #SUCCESS} if successfully registered.
+ *
+ * @hide
+ */
+ public static native int registerAudioVolumeGroupCallback(
+ INativeAudioVolumeGroupCallback callback);
+
+ /**
+ * Unegisters the given {@link INativeAudioVolumeGroupCallback} from native audioserver
+ * previously registered via {@link #registerAudioVolumeGroupCallback}.
+ * @param callback to register
+ * @return {@link #SUCCESS} if successfully registered.
+ *
+ * @hide
+ */
+ public static native int unregisterAudioVolumeGroupCallback(
+ INativeAudioVolumeGroupCallback callback);
}
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index 8aadb418cf5a..b97b943113b6 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -65,6 +65,7 @@ import android.media.audiopolicy.AudioPolicyConfig;
import android.media.audiopolicy.AudioProductStrategy;
import android.media.audiopolicy.AudioVolumeGroup;
import android.media.audiopolicy.IAudioPolicyCallback;
+import android.media.audiopolicy.IAudioVolumeChangeDispatcher;
import android.media.projection.IMediaProjection;
import android.net.Uri;
import android.os.PersistableBundle;
@@ -446,6 +447,10 @@ interface IAudioService {
boolean isAudioServerRunning();
+ void registerAudioVolumeCallback(IAudioVolumeChangeDispatcher avc);
+
+ oneway void unregisterAudioVolumeCallback(IAudioVolumeChangeDispatcher avc);
+
int setUidDeviceAffinity(in IAudioPolicyCallback pcb, in int uid, in int[] deviceTypes,
in String[] deviceAddresses);
diff --git a/media/java/android/media/audiopolicy/AudioVolumeGroupChangeHandler.java b/media/java/android/media/audiopolicy/AudioVolumeGroupChangeHandler.java
deleted file mode 100644
index 022cfeeb4e43..000000000000
--- a/media/java/android/media/audiopolicy/AudioVolumeGroupChangeHandler.java
+++ /dev/null
@@ -1,166 +0,0 @@
-/*
- * Copyright (C) 2018 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.audiopolicy;
-
-import android.annotation.NonNull;
-import android.media.AudioManager;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Message;
-
-import com.android.internal.util.Preconditions;
-
-import java.lang.ref.WeakReference;
-import java.util.ArrayList;
-
-/**
- * The AudioVolumeGroupChangeHandler handles AudioManager.OnAudioVolumeGroupChangedListener
- * callbacks posted from JNI
- *
- * TODO: Make use of Executor of callbacks.
- * @hide
- */
-public class AudioVolumeGroupChangeHandler {
- private Handler mHandler;
- private HandlerThread mHandlerThread;
- private final ArrayList<AudioManager.VolumeGroupCallback> mListeners =
- new ArrayList<AudioManager.VolumeGroupCallback>();
-
- private static final String TAG = "AudioVolumeGroupChangeHandler";
-
- private static final int AUDIOVOLUMEGROUP_EVENT_VOLUME_CHANGED = 1000;
- private static final int AUDIOVOLUMEGROUP_EVENT_NEW_LISTENER = 4;
-
- /**
- * Accessed by native methods: JNI Callback context.
- */
- @SuppressWarnings("unused")
- private long mJniCallback;
-
- /**
- * Initialization
- */
- public void init() {
- synchronized (this) {
- if (mHandler != null) {
- return;
- }
- // create a new thread for our new event handler
- mHandlerThread = new HandlerThread(TAG);
- mHandlerThread.start();
-
- if (mHandlerThread.getLooper() == null) {
- mHandler = null;
- return;
- }
- mHandler = new Handler(mHandlerThread.getLooper()) {
- @Override
- public void handleMessage(Message msg) {
- ArrayList<AudioManager.VolumeGroupCallback> listeners;
- synchronized (this) {
- if (msg.what == AUDIOVOLUMEGROUP_EVENT_NEW_LISTENER) {
- listeners =
- new ArrayList<AudioManager.VolumeGroupCallback>();
- if (mListeners.contains(msg.obj)) {
- listeners.add(
- (AudioManager.VolumeGroupCallback) msg.obj);
- }
- } else {
- listeners = (ArrayList<AudioManager.VolumeGroupCallback>)
- mListeners.clone();
- }
- }
- if (listeners.isEmpty()) {
- return;
- }
-
- switch (msg.what) {
- case AUDIOVOLUMEGROUP_EVENT_VOLUME_CHANGED:
- for (int i = 0; i < listeners.size(); i++) {
- listeners.get(i).onAudioVolumeGroupChanged((int) msg.arg1,
- (int) msg.arg2);
- }
- break;
-
- default:
- break;
- }
- }
- };
- native_setup(new WeakReference<AudioVolumeGroupChangeHandler>(this));
- }
- }
-
- private native void native_setup(Object moduleThis);
-
- @Override
- protected void finalize() {
- native_finalize();
- if (mHandlerThread.isAlive()) {
- mHandlerThread.quit();
- }
- }
- private native void native_finalize();
-
- /**
- * @param cb the {@link AudioManager.VolumeGroupCallback} to register
- */
- public void registerListener(@NonNull AudioManager.VolumeGroupCallback cb) {
- Preconditions.checkNotNull(cb, "volume group callback shall not be null");
- synchronized (this) {
- mListeners.add(cb);
- }
- if (mHandler != null) {
- Message m = mHandler.obtainMessage(
- AUDIOVOLUMEGROUP_EVENT_NEW_LISTENER, 0, 0, cb);
- mHandler.sendMessage(m);
- }
- }
-
- /**
- * @param cb the {@link AudioManager.VolumeGroupCallback} to unregister
- */
- public void unregisterListener(@NonNull AudioManager.VolumeGroupCallback cb) {
- Preconditions.checkNotNull(cb, "volume group callback shall not be null");
- synchronized (this) {
- mListeners.remove(cb);
- }
- }
-
- Handler handler() {
- return mHandler;
- }
-
- @SuppressWarnings("unused")
- private static void postEventFromNative(Object moduleRef,
- int what, int arg1, int arg2, Object obj) {
- AudioVolumeGroupChangeHandler eventHandler =
- (AudioVolumeGroupChangeHandler) ((WeakReference) moduleRef).get();
- if (eventHandler == null) {
- return;
- }
-
- if (eventHandler != null) {
- Handler handler = eventHandler.handler();
- if (handler != null) {
- Message m = handler.obtainMessage(what, arg1, arg2, obj);
- // Do not remove previous messages, as we would lose notification of group changes
- handler.sendMessage(m);
- }
- }
- }
-}
diff --git a/media/java/android/media/audiopolicy/IAudioVolumeChangeDispatcher.aidl b/media/java/android/media/audiopolicy/IAudioVolumeChangeDispatcher.aidl
new file mode 100644
index 000000000000..e6f9024cfd1e
--- /dev/null
+++ b/media/java/android/media/audiopolicy/IAudioVolumeChangeDispatcher.aidl
@@ -0,0 +1,31 @@
+/* Copyright (C) 2025 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.audiopolicy;
+
+/**
+ * AIDL for the AudioService to signal audio volume groups changes
+ *
+ * {@hide}
+ */
+oneway interface IAudioVolumeChangeDispatcher {
+
+ /**
+ * Called when a volume group has been changed
+ * @param group id of the volume group that has changed.
+ * @param flags one or more flags to describe the volume change.
+ */
+ void onAudioVolumeGroupChanged(int group, int flags);
+}
diff --git a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioVolumeGroupChangeHandlerTest.java b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioVolumeGroupChangeHandlerTest.java
deleted file mode 100644
index 82394a2eb420..000000000000
--- a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioVolumeGroupChangeHandlerTest.java
+++ /dev/null
@@ -1,211 +0,0 @@
-/*
- * Copyright (C) 2020 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 com.android.audiopolicytest;
-
-import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
-
-import static com.android.audiopolicytest.AudioVolumeTestUtil.DEFAULT_ATTRIBUTES;
-import static com.android.audiopolicytest.AudioVolumeTestUtil.incrementVolumeIndex;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertThrows;
-import static org.junit.Assert.assertTrue;
-
-import android.media.AudioAttributes;
-import android.media.AudioManager;
-import android.media.audiopolicy.AudioVolumeGroup;
-import android.media.audiopolicy.AudioVolumeGroupChangeHandler;
-import android.platform.test.annotations.Presubmit;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.ArrayList;
-import java.util.List;
-
-@Presubmit
-@RunWith(AndroidJUnit4.class)
-public class AudioVolumeGroupChangeHandlerTest {
- private static final String TAG = "AudioVolumeGroupChangeHandlerTest";
-
- @Rule
- public final AudioVolumesTestRule rule = new AudioVolumesTestRule();
-
- private AudioManager mAudioManager;
-
- @Before
- public void setUp() {
- mAudioManager = getApplicationContext().getSystemService(AudioManager.class);
- }
-
- @Test
- public void testRegisterInvalidCallback() {
- final AudioVolumeGroupChangeHandler audioAudioVolumeGroupChangedHandler =
- new AudioVolumeGroupChangeHandler();
-
- audioAudioVolumeGroupChangedHandler.init();
-
- assertThrows(NullPointerException.class, () -> {
- AudioManager.VolumeGroupCallback nullCb = null;
- audioAudioVolumeGroupChangedHandler.registerListener(nullCb);
- });
- }
-
- @Test
- public void testUnregisterInvalidCallback() {
- final AudioVolumeGroupChangeHandler audioAudioVolumeGroupChangedHandler =
- new AudioVolumeGroupChangeHandler();
-
- audioAudioVolumeGroupChangedHandler.init();
-
- final AudioVolumeGroupCallbackHelper cb = new AudioVolumeGroupCallbackHelper();
- audioAudioVolumeGroupChangedHandler.registerListener(cb);
-
- assertThrows(NullPointerException.class, () -> {
- AudioManager.VolumeGroupCallback nullCb = null;
- audioAudioVolumeGroupChangedHandler.unregisterListener(nullCb);
- });
- audioAudioVolumeGroupChangedHandler.unregisterListener(cb);
- }
-
- @Test
- public void testRegisterUnregisterCallback() {
- final AudioVolumeGroupChangeHandler audioAudioVolumeGroupChangedHandler =
- new AudioVolumeGroupChangeHandler();
-
- audioAudioVolumeGroupChangedHandler.init();
- final AudioVolumeGroupCallbackHelper validCb = new AudioVolumeGroupCallbackHelper();
-
- // Should not assert, otherwise test will fail
- audioAudioVolumeGroupChangedHandler.registerListener(validCb);
-
- // Should not assert, otherwise test will fail
- audioAudioVolumeGroupChangedHandler.unregisterListener(validCb);
- }
-
- @Test
- public void testCallbackReceived() {
- final AudioVolumeGroupChangeHandler audioAudioVolumeGroupChangedHandler =
- new AudioVolumeGroupChangeHandler();
-
- audioAudioVolumeGroupChangedHandler.init();
-
- final AudioVolumeGroupCallbackHelper validCb = new AudioVolumeGroupCallbackHelper();
- audioAudioVolumeGroupChangedHandler.registerListener(validCb);
-
- List<AudioVolumeGroup> audioVolumeGroups = mAudioManager.getAudioVolumeGroups();
- assertTrue(audioVolumeGroups.size() > 0);
-
- try {
- for (final AudioVolumeGroup audioVolumeGroup : audioVolumeGroups) {
- int volumeGroupId = audioVolumeGroup.getId();
-
- List<AudioAttributes> avgAttributes = audioVolumeGroup.getAudioAttributes();
- // Set the volume per attributes (if valid) and wait the callback
- if (avgAttributes.size() == 0 || avgAttributes.get(0).equals(DEFAULT_ATTRIBUTES)) {
- // Some volume groups may not have valid attributes, used for internal
- // volume management like patch/rerouting
- // so bailing out strategy retrieval from attributes
- continue;
- }
- final AudioAttributes aa = avgAttributes.get(0);
-
- int index = mAudioManager.getVolumeIndexForAttributes(aa);
- int indexMax = mAudioManager.getMaxVolumeIndexForAttributes(aa);
- int indexMin = mAudioManager.getMinVolumeIndexForAttributes(aa);
-
- final int indexForAa = incrementVolumeIndex(index, indexMin, indexMax);
-
- // Set the receiver to filter only the current group callback
- validCb.setExpectedVolumeGroup(volumeGroupId);
- mAudioManager.setVolumeIndexForAttributes(aa, indexForAa, 0/*flags*/);
- assertTrue(validCb.waitForExpectedVolumeGroupChanged(
- AudioVolumeGroupCallbackHelper.ASYNC_TIMEOUT_MS));
-
- final int readIndex = mAudioManager.getVolumeIndexForAttributes(aa);
- assertEquals(readIndex, indexForAa);
- }
- } finally {
- audioAudioVolumeGroupChangedHandler.unregisterListener(validCb);
- }
- }
-
- @Test
- public void testMultipleCallbackReceived() {
-
- final AudioVolumeGroupChangeHandler audioAudioVolumeGroupChangedHandler =
- new AudioVolumeGroupChangeHandler();
-
- audioAudioVolumeGroupChangedHandler.init();
-
- final int callbackCount = 10;
- final List<AudioVolumeGroupCallbackHelper> validCbs =
- new ArrayList<AudioVolumeGroupCallbackHelper>();
- for (int i = 0; i < callbackCount; i++) {
- validCbs.add(new AudioVolumeGroupCallbackHelper());
- }
- for (final AudioVolumeGroupCallbackHelper cb : validCbs) {
- audioAudioVolumeGroupChangedHandler.registerListener(cb);
- }
-
- List<AudioVolumeGroup> audioVolumeGroups = mAudioManager.getAudioVolumeGroups();
- assertTrue(audioVolumeGroups.size() > 0);
-
- try {
- for (final AudioVolumeGroup audioVolumeGroup : audioVolumeGroups) {
- int volumeGroupId = audioVolumeGroup.getId();
-
- List<AudioAttributes> avgAttributes = audioVolumeGroup.getAudioAttributes();
- // Set the volume per attributes (if valid) and wait the callback
- if (avgAttributes.size() == 0 || avgAttributes.get(0).equals(DEFAULT_ATTRIBUTES)) {
- // Some volume groups may not have valid attributes, used for internal
- // volume management like patch/rerouting
- // so bailing out strategy retrieval from attributes
- continue;
- }
- AudioAttributes aa = avgAttributes.get(0);
-
- int index = mAudioManager.getVolumeIndexForAttributes(aa);
- int indexMax = mAudioManager.getMaxVolumeIndexForAttributes(aa);
- int indexMin = mAudioManager.getMinVolumeIndexForAttributes(aa);
-
- final int indexForAa = incrementVolumeIndex(index, indexMin, indexMax);
-
- // Set the receiver to filter only the current group callback
- for (final AudioVolumeGroupCallbackHelper cb : validCbs) {
- cb.setExpectedVolumeGroup(volumeGroupId);
- }
- mAudioManager.setVolumeIndexForAttributes(aa, indexForAa, 0/*flags*/);
-
- for (final AudioVolumeGroupCallbackHelper cb : validCbs) {
- assertTrue(cb.waitForExpectedVolumeGroupChanged(
- AudioVolumeGroupCallbackHelper.ASYNC_TIMEOUT_MS));
- }
- int readIndex = mAudioManager.getVolumeIndexForAttributes(aa);
- assertEquals(readIndex, indexForAa);
- }
- } finally {
- for (final AudioVolumeGroupCallbackHelper cb : validCbs) {
- audioAudioVolumeGroupChangedHandler.unregisterListener(cb);
- }
- }
- }
-}