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
This commit is contained in:
@@ -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
|
||||
@@ -298,6 +299,21 @@ public class AudioDeviceVolumeManager {
|
||||
"removeOnDeviceVolumeBehaviorChangedListener");
|
||||
}
|
||||
|
||||
/**
|
||||
* @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
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -386,6 +386,17 @@ public class AudioSystemAdapter implements AudioSystem.RoutingUpdateCallback,
|
||||
return AudioSystem.isStreamActiveRemotely(stream, inPastMs);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -125,6 +125,11 @@ public class NoOpAudioSystemAdapter extends AudioSystemAdapter {
|
||||
return mIsStreamActive;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int setStreamVolumeIndexAS(int stream, int index, int device) {
|
||||
return AudioSystem.AUDIO_STATUS_OK;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public ArrayList<AudioDeviceAttributes> getDevicesForAttributes(
|
||||
|
||||
Reference in New Issue
Block a user