From 5017dd8480dfbc5fbefec880d4c3ac4880fe1f76 Mon Sep 17 00:00:00 2001 From: Jean-Michel Trivi Date: Thu, 19 Jan 2023 18:51:19 +0000 Subject: [PATCH] AudioManager: API to query independent/alias stream types Add getter to query the "independent" stream types, e.g. the ones that aren't aliased to any other. Add getter to query stream type aliases. Add listener add/remove to be notified of aliasing updates Add TestApi to see if a device uses stream types or volume groups to perform volume control. Bug: 265076070 Test: atest android.media.audio.cts.AudioManagerTest#testIndependentStreamTypes Change-Id: I14f9e5e7b22b3d6af51ef3b789b62ee99f7fccfa --- core/api/system-current.txt | 4 + core/api/test-current.txt | 2 + media/java/android/media/AudioManager.java | 134 ++++++++++++++++++ media/java/android/media/IAudioService.aidl | 16 +++ .../media/IStreamAliasingDispatcher.aidl | 27 ++++ .../android/server/audio/AudioService.java | 92 +++++++++++- 6 files changed, 274 insertions(+), 1 deletion(-) create mode 100644 media/java/android/media/IStreamAliasingDispatcher.aidl diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 0476d7916a38b..662fdec043e84 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -6681,6 +6681,7 @@ package android.media { method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void addOnPreferredDeviceForStrategyChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.OnPreferredDeviceForStrategyChangedListener) throws java.lang.SecurityException; method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void addOnPreferredDevicesForCapturePresetChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.OnPreferredDevicesForCapturePresetChangedListener) throws java.lang.SecurityException; method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void addOnPreferredDevicesForStrategyChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.OnPreferredDevicesForStrategyChangedListener) throws java.lang.SecurityException; + method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS) public void addOnStreamAliasingChangedListener(@NonNull java.util.concurrent.Executor, @NonNull Runnable); method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void cancelMuteAwaitConnection(@NonNull android.media.AudioDeviceAttributes) throws java.lang.IllegalStateException; method public void clearAudioServerStateCallback(); method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public boolean clearPreferredDevicesForCapturePreset(int); @@ -6694,6 +6695,7 @@ package android.media { method @NonNull @RequiresPermission(android.Manifest.permission.CALL_AUDIO_INTERCEPTION) public android.media.AudioTrack getCallUplinkInjectionAudioTrack(@NonNull android.media.AudioFormat); method @RequiresPermission(anyOf={android.Manifest.permission.MODIFY_AUDIO_ROUTING, "android.permission.QUERY_AUDIO_STATE", android.Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS}) public int getDeviceVolumeBehavior(@NonNull android.media.AudioDeviceAttributes); method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MODIFY_AUDIO_ROUTING, "android.permission.QUERY_AUDIO_STATE"}) public java.util.List getDevicesForAttributes(@NonNull android.media.AudioAttributes); + method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS) public java.util.List getIndependentStreamTypes(); method @RequiresPermission("android.permission.QUERY_AUDIO_STATE") public int getLastAudibleStreamVolume(int); method @IntRange(from=0) @RequiresPermission("android.permission.QUERY_AUDIO_STATE") public int getLastAudibleVolumeGroupVolume(int); method @IntRange(from=0) public long getMaxAdditionalOutputDeviceDelay(@NonNull android.media.AudioDeviceInfo); @@ -6704,6 +6706,7 @@ package android.media { method @Nullable @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public android.media.AudioDeviceAttributes getPreferredDeviceForStrategy(@NonNull android.media.audiopolicy.AudioProductStrategy); method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public java.util.List getPreferredDevicesForCapturePreset(int); method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public java.util.List getPreferredDevicesForStrategy(@NonNull android.media.audiopolicy.AudioProductStrategy); + method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS) public int getStreamTypeAlias(int); method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int[] getSupportedSystemUsages(); method @IntRange(from=0) @RequiresPermission(anyOf={android.Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS, android.Manifest.permission.MODIFY_AUDIO_ROUTING}) public int getVolumeGroupMaxVolumeIndex(int); method @IntRange(from=0) @RequiresPermission(anyOf={android.Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS, android.Manifest.permission.MODIFY_AUDIO_ROUTING}) public int getVolumeGroupMinVolumeIndex(int); @@ -6726,6 +6729,7 @@ package android.media { method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void removeOnPreferredDeviceForStrategyChangedListener(@NonNull android.media.AudioManager.OnPreferredDeviceForStrategyChangedListener); method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void removeOnPreferredDevicesForCapturePresetChangedListener(@NonNull android.media.AudioManager.OnPreferredDevicesForCapturePresetChangedListener); method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void removeOnPreferredDevicesForStrategyChangedListener(@NonNull android.media.AudioManager.OnPreferredDevicesForStrategyChangedListener); + method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS) public void removeOnStreamAliasingChangedListener(@NonNull Runnable); method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public boolean removePreferredDeviceForStrategy(@NonNull android.media.audiopolicy.AudioProductStrategy); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int requestAudioFocus(android.media.AudioManager.OnAudioFocusChangeListener, @NonNull android.media.AudioAttributes, int, int) throws java.lang.IllegalArgumentException; method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.MODIFY_PHONE_STATE, android.Manifest.permission.MODIFY_AUDIO_ROUTING}) public int requestAudioFocus(android.media.AudioManager.OnAudioFocusChangeListener, @NonNull android.media.AudioAttributes, int, int, android.media.audiopolicy.AudioPolicy) throws java.lang.IllegalArgumentException; diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 360113b73131a..1217411703ede 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -1683,8 +1683,10 @@ package android.media { method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS) public boolean isCsdEnabled(); method @RequiresPermission(anyOf={android.Manifest.permission.MODIFY_AUDIO_ROUTING, android.Manifest.permission.QUERY_AUDIO_STATE, android.Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS}) public boolean isFullVolumeDevice(); method @RequiresPermission(android.Manifest.permission.CALL_AUDIO_INTERCEPTION) public boolean isPstnCallAudioInterceptable(); + method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS) public boolean isVolumeControlUsingVolumeGroups(); method @RequiresPermission("android.permission.QUERY_AUDIO_STATE") public int requestAudioFocusForTest(@NonNull android.media.AudioFocusRequest, @NonNull String, int, int); method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS) public void setCsd(float); + method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS) public void setNotifAliasRingForTest(boolean); method public void setRampingRingerEnabled(boolean); method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS) public void setRs2Value(float); method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void setTestDeviceConnectionState(@NonNull android.media.AudioDeviceAttributes, boolean); diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index 063645152228b..a37428a23f69d 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -9387,6 +9387,140 @@ public class AudioManager { } } + //==================================================================== + // Stream aliasing changed listener, getter for stream alias or independent streams + + /** + * manages the stream aliasing listeners and StreamAliasingDispatcherStub + */ + private final CallbackUtil.LazyListenerManager mStreamAliasingListenerMgr = + new CallbackUtil.LazyListenerManager(); + + + final class StreamAliasingDispatcherStub extends IStreamAliasingDispatcher.Stub + implements CallbackUtil.DispatcherStub { + + @Override + public void register(boolean register) { + try { + getService().registerStreamAliasingDispatcher(this, register); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } + + @Override + public void dispatchStreamAliasingChanged() { + mStreamAliasingListenerMgr.callListeners((listener) -> listener.run()); + } + } + + /** + * @hide + * Adds a listener to be notified of changes to volume stream type aliasing. + * See {@link #getIndependentStreamTypes()} and {@link #getStreamTypeAlias(int)} + * @param executor the Executor running the listener + * @param onStreamAliasingChangedListener the listener to add for the aliasing changes + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS) + public void addOnStreamAliasingChangedListener( + @NonNull @CallbackExecutor Executor executor, + @NonNull Runnable onStreamAliasingChangedListener) { + mStreamAliasingListenerMgr.addListener(executor, onStreamAliasingChangedListener, + "addOnStreamAliasingChangedListener", + () -> new StreamAliasingDispatcherStub()); + } + + /** + * @hide + * Removes a previously added listener for changes to stream aliasing. + * See {@link #getIndependentStreamTypes()} and {@link #getStreamTypeAlias(int)} + * @see #addOnStreamAliasingChangedListener(Executor, Runnable) + * @param onStreamAliasingChangedListener the previously added listener of aliasing changes + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS) + public void removeOnStreamAliasingChangedListener( + @NonNull Runnable onStreamAliasingChangedListener) { + mStreamAliasingListenerMgr.removeListener(onStreamAliasingChangedListener, + "removeOnStreamAliasingChangedListener"); + } + + /** + * @hide + * Test method to temporarily override whether STREAM_NOTIFICATION is aliased to STREAM_RING, + * volumes will be updated in case of a change. + * @param isAliased if true, STREAM_NOTIFICATION is aliased to STREAM_RING + */ + @TestApi + @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS) + public void setNotifAliasRingForTest(boolean isAliased) { + final IAudioService service = getService(); + try { + service.setNotifAliasRingForTest(isAliased); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * @hide + * Return the list of independent stream types for volume control. + * A stream type is considered independent when the volume changes of that type do not + * affect any other independent volume control stream type. + * An independent stream type is its own alias when using {@link #getStreamTypeAlias(int)}. + * @return list of independent stream types. + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS) + public @NonNull List getIndependentStreamTypes() { + final IAudioService service = getService(); + try { + return service.getIndependentStreamTypes(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * @hide + * Return the stream type that a given stream is aliased to. + * A stream alias means that any change to the source stream will also be applied to the alias, + * and vice-versa. + * If a stream is independent (i.e. part of the stream types returned by + * {@link #getIndependentStreamTypes()}), its alias is itself. + * @param sourceStreamType the stream type to query for the alias. + * @return the stream type the source type is aliased to. + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS) + public @PublicStreamTypes int getStreamTypeAlias(@PublicStreamTypes int sourceStreamType) { + final IAudioService service = getService(); + try { + return service.getStreamTypeAlias(sourceStreamType); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * @hide + * Returns whether the system uses {@link AudioVolumeGroup} for volume control + * @return true when volume control is performed through volume groups, false if it uses + * stream types. + */ + @TestApi + @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS) + public boolean isVolumeControlUsingVolumeGroups() { + final IAudioService service = getService(); + try { + return service.isVolumeControlUsingVolumeGroups(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + //==================================================================== // Mute await connection diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl index 3e0356f5b6136..8e0c86f07470a 100644 --- a/media/java/android/media/IAudioService.aidl +++ b/media/java/android/media/IAudioService.aidl @@ -49,6 +49,7 @@ import android.media.ISpatializerHeadTrackerAvailableCallback; import android.media.ISpatializerHeadTrackingModeCallback; import android.media.ISpatializerHeadToSoundStagePoseCallback; import android.media.ISpatializerOutputCallback; +import android.media.IStreamAliasingDispatcher; import android.media.IVolumeController; import android.media.PlayerBase; import android.media.VolumeInfo; @@ -257,6 +258,21 @@ interface IAudioService { IRingtonePlayer getRingtonePlayer(); int getUiSoundsStreamType(); + @EnforcePermission("MODIFY_AUDIO_SYSTEM_SETTINGS") + List getIndependentStreamTypes(); + + @EnforcePermission("MODIFY_AUDIO_SYSTEM_SETTINGS") + int getStreamTypeAlias(int streamType); + + @EnforcePermission("MODIFY_AUDIO_SYSTEM_SETTINGS") + boolean isVolumeControlUsingVolumeGroups(); + + @EnforcePermission("MODIFY_AUDIO_SYSTEM_SETTINGS") + oneway void registerStreamAliasingDispatcher(IStreamAliasingDispatcher isad, boolean register); + + @EnforcePermission("MODIFY_AUDIO_SYSTEM_SETTINGS") + void setNotifAliasRingForTest(boolean alias); + @EnforcePermission("MODIFY_AUDIO_ROUTING") void setWiredDeviceConnectionState(in AudioDeviceAttributes aa, int state, String caller); diff --git a/media/java/android/media/IStreamAliasingDispatcher.aidl b/media/java/android/media/IStreamAliasingDispatcher.aidl new file mode 100644 index 0000000000000..1ef63c3b5f845 --- /dev/null +++ b/media/java/android/media/IStreamAliasingDispatcher.aidl @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2023 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 AudioService to signal stream aliasing update. + * + * {@hide} + */ +oneway interface IStreamAliasingDispatcher { + + void dispatchStreamAliasingChanged(); +} diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index c01424d94052a..3613d04fcf0c7 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -119,6 +119,7 @@ import android.media.ISpatializerHeadTrackingModeCallback; import android.media.ISpatializerOutputCallback; import android.media.IStrategyNonDefaultDevicesDispatcher; import android.media.IStrategyPreferredDevicesDispatcher; +import android.media.IStreamAliasingDispatcher; import android.media.IVolumeController; import android.media.MediaMetrics; import android.media.MediaRecorder.AudioSource; @@ -310,7 +311,9 @@ public class AudioService extends IAudioService.Stub * volumes will be updated in case of a change. * @param alias if true, STREAM_NOTIFICATION is aliased to STREAM_RING */ - /*package*/ void setNotifAliasRingForTest(boolean alias) { + @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS) + public void setNotifAliasRingForTest(boolean alias) { + super.setNotifAliasRingForTest_enforcePermission(); boolean update = (mNotifAliasRing != alias); mNotifAliasRing = alias; if (update) { @@ -2275,6 +2278,7 @@ public class AudioService extends IAudioService.Stub 0, mStreamStates[AudioSystem.STREAM_ACCESSIBILITY], 0); } + dispatchStreamAliasingUpdate(); } private void readDockAudioSettings(ContentResolver cr) @@ -5020,6 +5024,91 @@ public class AudioService extends IAudioService.Stub return sDefaultVolumeInfo; } + /** + * list of callback dispatchers for stream aliasing updates + */ + final RemoteCallbackList mStreamAliasingDispatchers = + new RemoteCallbackList(); + + /** + * Register/unregister a callback for stream aliasing updates + * @param isad the callback dispatcher + * @param register whether this for a registration or unregistration + * @see AudioManager#addOnStreamAliasingChangedListener(Executor, Runnable) + * @see AudioManager#removeOnStreamAliasingChangedListener(Runnable) + */ + @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS) + public void registerStreamAliasingDispatcher(IStreamAliasingDispatcher isad, boolean register) { + super.registerStreamAliasingDispatcher_enforcePermission(); + Objects.requireNonNull(isad); + + if (register) { + mStreamAliasingDispatchers.register(isad); + } else { + mStreamAliasingDispatchers.unregister(isad); + } + } + + protected void dispatchStreamAliasingUpdate() { + final int nbDispatchers = mStreamAliasingDispatchers.beginBroadcast(); + for (int i = 0; i < nbDispatchers; i++) { + try { + mStreamAliasingDispatchers.getBroadcastItem(i).dispatchStreamAliasingChanged(); + } catch (RemoteException e) { + Log.e(TAG, "Error on stream alias update dispatch", e); + } + } + mStreamAliasingDispatchers.finishBroadcast(); + } + + /** + * @see AudioManager#getIndependentStreamTypes() + * @return the list of non-aliased stream types + */ + @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS) + public ArrayList getIndependentStreamTypes() { + super.getIndependentStreamTypes_enforcePermission(); + + if (mUseVolumeGroupAliases) { + return new ArrayList<>(Arrays.stream(AudioManager.getPublicStreamTypes()) + .boxed().toList()); + } + ArrayList res = new ArrayList(1); + for (int stream : mStreamVolumeAlias) { + if (!res.contains(stream)) { + res.add(stream); + } + } + return res; + } + + /** + * @see AudioManager#getStreamTypeAlias(int) + * @param sourceStreamType the stream type for which the alias is queried + * @return the stream alias + */ + @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS) + public @AudioManager.PublicStreamTypes + int getStreamTypeAlias(@AudioManager.PublicStreamTypes int sourceStreamType) { + super.getStreamTypeAlias_enforcePermission(); + // verify parameters + ensureValidStreamType(sourceStreamType); + + return mStreamVolumeAlias[sourceStreamType]; + } + + /** + * @see AudioManager#isVolumeControlUsingVolumeGroups() + * @return true when volume control is performed through volume groups, false if it uses + * stream types. + */ + @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS) + public boolean isVolumeControlUsingVolumeGroups() { + super.isVolumeControlUsingVolumeGroups_enforcePermission(); + + return mUseVolumeGroupAliases; + } + /** @see AudioManager#getUiSoundsStreamType() * TODO(b/181140246): when using VolumeGroup alias, we are lacking configurability for * UI Sounds identification. @@ -10715,6 +10804,7 @@ public class AudioService extends IAudioService.Stub pw.print(" mBluetoothName="); pw.println(mDeviceBroker.getCurAudioRoutes().bluetoothName); pw.println("\nOther state:"); + pw.print(" mUseVolumeGroupAliases="); pw.println(mUseVolumeGroupAliases); pw.print(" mVolumeController="); pw.println(mVolumeController); mSoundDoseHelper.dump(pw); pw.print(" sIndependentA11yVolume="); pw.println(sIndependentA11yVolume);