diff options
| author | 2022-07-13 03:39:51 +0000 | |
|---|---|---|
| committer | 2022-07-27 19:02:25 +0000 | |
| commit | 118ca2fc2534195fa519399b2bd86220366155a4 (patch) | |
| tree | 3b3f81f07d8b3d96797d52924cd2105bee1255a4 | |
| parent | 77f3fe5709e63c09ed6241e15132a09332d8de53 (diff) | |
AudioDeviceVolumeManager: set volume per device
Add API in AudioDeviceVolumeManager that allows setting
volume for a specific audio device.
Bug: 236764441
Test: atest AudioDeviceVolumeManagerTest
Change-Id: Iebb277b35dbf5aa49c1fd4a8ec6dd904afa11ad9
7 files changed, 253 insertions, 13 deletions
diff --git a/media/java/android/media/AudioDeviceVolumeManager.java b/media/java/android/media/AudioDeviceVolumeManager.java index fe58cca9395f..c70887672f9e 100644 --- a/media/java/android/media/AudioDeviceVolumeManager.java +++ b/media/java/android/media/AudioDeviceVolumeManager.java @@ -61,10 +61,12 @@ public class AudioDeviceVolumeManager { private static IAudioService sService; - private final String mPackageName; + private final @NonNull String mPackageName; + private final @Nullable String mAttributionTag; public AudioDeviceVolumeManager(Context context) { mPackageName = context.getApplicationContext().getOpPackageName(); + mAttributionTag = context.getApplicationContext().getAttributionTag(); } /** @@ -287,7 +289,6 @@ public class AudioDeviceVolumeManager { * @hide * Removes a previously added listener of changes to device volume behavior. */ - @RequiresPermission(anyOf = { android.Manifest.permission.MODIFY_AUDIO_ROUTING, android.Manifest.permission.QUERY_AUDIO_STATE @@ -299,6 +300,21 @@ public class AudioDeviceVolumeManager { } /** + * @hide + * Sets the volume on the given audio device + * @param vi the volume information, only stream-based volumes are supported + * @param ada the device for which volume is to be modified + */ + @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + public void setDeviceVolume(@NonNull VolumeInfo vi, @NonNull AudioDeviceAttributes ada) { + try { + getService().setDeviceVolume(vi, ada, mPackageName, mAttributionTag); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } + + /** * Return human-readable name for volume behavior * @param behavior one of the volume behaviors defined in AudioManager * @return a string for the given behavior diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl index e28178a8d5d8..90eb9e644f26 100755 --- a/media/java/android/media/IAudioService.aidl +++ b/media/java/android/media/IAudioService.aidl @@ -98,6 +98,9 @@ interface IAudioService { void setStreamVolumeWithAttribution(int streamType, int index, int flags, in String callingPackage, in String attributionTag); + void setDeviceVolume(in VolumeInfo vi, in AudioDeviceAttributes ada, + in String callingPackage, in String attributionTag); + oneway void handleVolumeKey(in KeyEvent event, boolean isOnTv, String callingPackage, String caller); diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 3a869f859e52..a44a658359ff 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -175,6 +175,7 @@ import com.android.internal.util.Preconditions; import com.android.server.EventLogTags; import com.android.server.LocalServices; import com.android.server.SystemService; +import com.android.server.audio.AudioServiceEvents.DeviceVolumeEvent; import com.android.server.audio.AudioServiceEvents.PhoneStateEvent; import com.android.server.audio.AudioServiceEvents.VolumeEvent; import com.android.server.pm.UserManagerInternal; @@ -3584,7 +3585,8 @@ public class AudioService extends IAudioService.Stub + "), do not change associated stream volume"); continue; } - setStreamVolume(groupedStream, index, flags, callingPackage, callingPackage, + setStreamVolume(groupedStream, index, flags, /*device*/ null, + callingPackage, callingPackage, attributionTag, Binder.getCallingUid(), true /*hasModifyAudioSettings*/); } } @@ -3627,15 +3629,73 @@ public class AudioService extends IAudioService.Stub return AudioSystem.getMinVolumeIndexForAttributes(attr); } + /** @see AudioDeviceVolumeManager#setDeviceVolume(VolumeInfo, AudioDeviceAttributes) + * Part of service interface, check permissions and parameters here */ + public void setDeviceVolume(@NonNull VolumeInfo vi, @NonNull AudioDeviceAttributes ada, + @NonNull String callingPackage, @Nullable String attributionTag) { + enforceModifyAudioRoutingPermission(); + Objects.requireNonNull(vi); + Objects.requireNonNull(ada); + Objects.requireNonNull(callingPackage); + if (!vi.hasStreamType()) { + Log.e(TAG, "Unsupported non-stream type based VolumeInfo", new Exception()); + return; + } + int index = vi.getVolumeIndex(); + if (index == VolumeInfo.INDEX_NOT_SET) { + throw new IllegalArgumentException("changing device volume requires a volume index"); + } + + if (vi.getMinVolumeIndex() == VolumeInfo.INDEX_NOT_SET + || vi.getMaxVolumeIndex() == VolumeInfo.INDEX_NOT_SET) { + // assume index meant to be in stream type range, validate + if ((index * 10) < mStreamStates[vi.getStreamType()].getMinIndex() + || (index * 10) > mStreamStates[vi.getStreamType()].getMaxIndex()) { + throw new IllegalArgumentException("invalid volume index " + index + + " not between min/max for stream " + vi.getStreamType()); + } + } else { + // check if index needs to be rescaled + final int min = (mStreamStates[vi.getStreamType()].getMinIndex() + 5) / 10; + final int max = (mStreamStates[vi.getStreamType()].getMaxIndex() + 5) / 10; + if (vi.getMinVolumeIndex() != min || vi.getMaxVolumeIndex() != max) { + index = rescaleIndex(index, + /*srcMin*/ vi.getMinVolumeIndex(), /*srcMax*/ vi.getMaxVolumeIndex(), + /*dstMin*/ min, /*dstMax*/ max); + } + } + setStreamVolumeWithAttributionInt(vi.getStreamType(), index, /*flags*/ 0, + ada, callingPackage, attributionTag); + } + /** Retain API for unsupported app usage */ public void setStreamVolume(int streamType, int index, int flags, String callingPackage) { - setStreamVolumeWithAttribution(streamType, index, flags, callingPackage, null); + setStreamVolumeWithAttribution(streamType, index, flags, + callingPackage, /*attributionTag*/ null); } /** @see AudioManager#setStreamVolume(int, int, int) * Part of service interface, check permissions here */ public void setStreamVolumeWithAttribution(int streamType, int index, int flags, String callingPackage, String attributionTag) { + setStreamVolumeWithAttributionInt(streamType, index, flags, /*device*/ null, + callingPackage, attributionTag); + } + + /** + * Internal method for a stream type volume change. Can be used to change the volume on a + * given device only + * @param streamType the stream type whose volume is to be changed + * @param index the volume index + * @param flags options for volume handling + * @param device null when controlling volume for the current routing, otherwise the device + * for which volume is being changed + * @param callingPackage client side-provided package name of caller, not to be trusted + * @param attributionTag client side-provided attribution name, not to be trusted + */ + protected void setStreamVolumeWithAttributionInt(int streamType, int index, int flags, + @Nullable AudioDeviceAttributes device, + String callingPackage, String attributionTag) { if ((streamType == AudioManager.STREAM_ACCESSIBILITY) && !canChangeAccessibilityVolume()) { Log.w(TAG, "Trying to call setStreamVolume() for a11y without" + " CHANGE_ACCESSIBILITY_VOLUME callingPackage=" + callingPackage); @@ -3658,10 +3718,14 @@ public class AudioService extends IAudioService.Stub return; } - sVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_SET_STREAM_VOL, streamType, - index/*val1*/, flags/*val2*/, callingPackage)); - setStreamVolume(streamType, index, flags, callingPackage, callingPackage, - attributionTag, Binder.getCallingUid(), callingOrSelfHasAudioSettingsPermission()); + final AudioEventLogger.Event event = (device == null) + ? new VolumeEvent(VolumeEvent.VOL_SET_STREAM_VOL, streamType, + index/*val1*/, flags/*val2*/, callingPackage) + : new DeviceVolumeEvent(streamType, index, device, callingPackage); + sVolumeLogger.log(event); + setStreamVolume(streamType, index, flags, device, + callingPackage, callingPackage, attributionTag, + Binder.getCallingUid(), callingOrSelfHasAudioSettingsPermission()); } /** @see AudioManager#isUltrasoundSupported() */ @@ -3900,11 +3964,13 @@ public class AudioService extends IAudioService.Stub } } - private void setStreamVolume(int streamType, int index, int flags, String callingPackage, - String caller, String attributionTag, int uid, + private void setStreamVolume(int streamType, int index, int flags, + @Nullable AudioDeviceAttributes ada, + String callingPackage, String caller, String attributionTag, int uid, boolean hasModifyAudioSettings) { if (DEBUG_VOL) { Log.d(TAG, "setStreamVolume(stream=" + streamType+", index=" + index + + ", dev=" + ada + ", calling=" + callingPackage + ")"); } if (mUseFixedVolume) { @@ -3915,7 +3981,9 @@ public class AudioService extends IAudioService.Stub int streamTypeAlias = mStreamVolumeAlias[streamType]; VolumeStreamState streamState = mStreamStates[streamTypeAlias]; - final int device = getDeviceForStream(streamType); + final int device = (ada == null) + ? getDeviceForStream(streamType) + : ada.getInternalType(); int oldIndex; // skip a2dp absolute volume control request when the device @@ -5419,7 +5487,8 @@ public class AudioService extends IAudioService.Stub throw new SecurityException("Should only be called from system process"); } - setStreamVolume(streamType, index, flags, packageName, packageName, null, uid, + setStreamVolume(streamType, index, flags, /*device*/ null, + packageName, packageName, null, uid, hasAudioSettingsPermission(uid, pid)); } @@ -7562,7 +7631,7 @@ public class AudioService extends IAudioService.Stub && !isFullyMuted()) { index = 1; } - AudioSystem.setStreamVolumeIndexAS(mStreamType, index, device); + mAudioSystem.setStreamVolumeIndexAS(mStreamType, index, device); } // must be called while synchronized VolumeStreamState.class diff --git a/services/core/java/com/android/server/audio/AudioServiceEvents.java b/services/core/java/com/android/server/audio/AudioServiceEvents.java index 3225274a8a9b..b5835ce0c847 100644 --- a/services/core/java/com/android/server/audio/AudioServiceEvents.java +++ b/services/core/java/com/android/server/audio/AudioServiceEvents.java @@ -16,7 +16,9 @@ package com.android.server.audio; +import android.annotation.NonNull; import android.media.AudioAttributes; +import android.media.AudioDeviceAttributes; import android.media.AudioManager; import android.media.AudioSystem; import android.media.MediaMetrics; @@ -145,6 +147,43 @@ public class AudioServiceEvents { } } + static final class DeviceVolumeEvent extends AudioEventLogger.Event { + final int mStream; + final int mVolIndex; + final String mDeviceNativeType; + final String mDeviceAddress; + final String mCaller; + + DeviceVolumeEvent(int streamType, int index, @NonNull AudioDeviceAttributes device, + String callingPackage) { + mStream = streamType; + mVolIndex = index; + mDeviceNativeType = "0x" + Integer.toHexString(device.getInternalType()); + mDeviceAddress = device.getAddress(); + mCaller = callingPackage; + // log metrics + new MediaMetrics.Item(MediaMetrics.Name.AUDIO_VOLUME_EVENT) + .set(MediaMetrics.Property.EVENT, "setDeviceVolume") + .set(MediaMetrics.Property.STREAM_TYPE, + AudioSystem.streamToString(mStream)) + .set(MediaMetrics.Property.INDEX, mVolIndex) + .set(MediaMetrics.Property.DEVICE, mDeviceNativeType) + .set(MediaMetrics.Property.ADDRESS, mDeviceAddress) + .set(MediaMetrics.Property.CALLING_PACKAGE, mCaller) + .record(); + } + + @Override + public String eventToString() { + return new StringBuilder("setDeviceVolume(stream:") + .append(AudioSystem.streamToString(mStream)) + .append(" index:").append(mVolIndex) + .append(" device:").append(mDeviceNativeType) + .append(" addr:").append(mDeviceAddress) + .append(") from ").append(mCaller).toString(); + } + } + final static class VolumeEvent extends AudioEventLogger.Event { static final int VOL_ADJUST_SUGG_VOL = 0; static final int VOL_ADJUST_STREAM_VOL = 1; diff --git a/services/core/java/com/android/server/audio/AudioSystemAdapter.java b/services/core/java/com/android/server/audio/AudioSystemAdapter.java index 758a4ec96288..c3754eb3e44c 100644 --- a/services/core/java/com/android/server/audio/AudioSystemAdapter.java +++ b/services/core/java/com/android/server/audio/AudioSystemAdapter.java @@ -387,6 +387,17 @@ public class AudioSystemAdapter implements AudioSystem.RoutingUpdateCallback, } /** + * Same as {@link AudioSystem#setStreamVolumeIndexAS(int, int, int)} + * @param stream + * @param index + * @param device + * @return + */ + public int setStreamVolumeIndexAS(int stream, int index, int device) { + return AudioSystem.setStreamVolumeIndexAS(stream, index, device); + } + + /** * Same as {@link AudioSystem#setPhoneState(int, int)} * @param state * @param uid diff --git a/services/tests/servicestests/src/com/android/server/audio/AudioDeviceVolumeManagerTest.java b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceVolumeManagerTest.java new file mode 100644 index 000000000000..7acb6d66c834 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceVolumeManagerTest.java @@ -0,0 +1,97 @@ +/* + * 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 com.android.server.audio; + +import static org.mockito.Mockito.atLeast; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; + +import android.content.Context; +import android.media.AudioDeviceAttributes; +import android.media.AudioDeviceInfo; +import android.media.AudioManager; +import android.media.AudioSystem; +import android.media.VolumeInfo; +import android.os.test.TestLooper; + +import androidx.test.InstrumentationRegistry; + +import org.junit.Before; +import org.junit.Test; + +public class AudioDeviceVolumeManagerTest { + private static final String TAG = "AudioDeviceVolumeManagerTest"; + + private static final AudioDeviceAttributes DEVICE_SPEAKER_OUT = new AudioDeviceAttributes( + AudioDeviceAttributes.ROLE_OUTPUT, AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, ""); + + private Context mContext; + private String mPackageName; + private AudioSystemAdapter mSpyAudioSystem; + private SystemServerAdapter mSystemServer; + private SettingsAdapter mSettingsAdapter; + private TestLooper mTestLooper; + + private AudioService mAudioService; + + + @Before + public void setUp() throws Exception { + mTestLooper = new TestLooper(); + mContext = InstrumentationRegistry.getTargetContext(); + mPackageName = mContext.getOpPackageName(); + mSpyAudioSystem = spy(new NoOpAudioSystemAdapter()); + + mSystemServer = new NoOpSystemServerAdapter(); + mSettingsAdapter = new NoOpSettingsAdapter(); + mAudioService = new AudioService(mContext, mSpyAudioSystem, mSystemServer, + mSettingsAdapter, mTestLooper.getLooper()) { + @Override + public int getDeviceForStream(int stream) { + return AudioSystem.DEVICE_OUT_SPEAKER; + } + }; + + mTestLooper.dispatchAll(); + } + + @Test + public void testSetDeviceVolume() { + AudioManager am = mContext.getSystemService(AudioManager.class); + final int minIndex = am.getStreamMinVolume(AudioManager.STREAM_MUSIC); + final int maxIndex = am.getStreamMaxVolume(AudioManager.STREAM_MUSIC); + final int midIndex = (minIndex + maxIndex) / 2; + final VolumeInfo volMedia = new VolumeInfo.Builder(AudioManager.STREAM_MUSIC) + .setMinVolumeIndex(minIndex) + .setMaxVolumeIndex(maxIndex) + .build(); + final VolumeInfo volMin = new VolumeInfo.Builder(volMedia).setVolumeIndex(minIndex).build(); + final VolumeInfo volMid = new VolumeInfo.Builder(volMedia).setVolumeIndex(midIndex).build(); + final AudioDeviceAttributes usbDevice = new AudioDeviceAttributes( + /*native type*/ AudioSystem.DEVICE_OUT_USB_DEVICE, /*address*/ "bla"); + + mAudioService.setDeviceVolume(volMin, usbDevice, mPackageName, TAG); + mTestLooper.dispatchAll(); + verify(mSpyAudioSystem, atLeast(1)).setStreamVolumeIndexAS( + AudioManager.STREAM_MUSIC, minIndex, AudioSystem.DEVICE_OUT_USB_DEVICE); + + mAudioService.setDeviceVolume(volMid, usbDevice, mPackageName, TAG); + mTestLooper.dispatchAll(); + verify(mSpyAudioSystem, atLeast(1)).setStreamVolumeIndexAS( + AudioManager.STREAM_MUSIC, midIndex, AudioSystem.DEVICE_OUT_USB_DEVICE); + } +} diff --git a/services/tests/servicestests/src/com/android/server/audio/NoOpAudioSystemAdapter.java b/services/tests/servicestests/src/com/android/server/audio/NoOpAudioSystemAdapter.java index 09e5d4be14a4..ee9d59b11ed5 100644 --- a/services/tests/servicestests/src/com/android/server/audio/NoOpAudioSystemAdapter.java +++ b/services/tests/servicestests/src/com/android/server/audio/NoOpAudioSystemAdapter.java @@ -126,6 +126,11 @@ public class NoOpAudioSystemAdapter extends AudioSystemAdapter { } @Override + public int setStreamVolumeIndexAS(int stream, int index, int device) { + return AudioSystem.AUDIO_STATUS_OK; + } + + @Override @NonNull public ArrayList<AudioDeviceAttributes> getDevicesForAttributes( @NonNull AudioAttributes attributes, boolean forVolume) { |