summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--services/core/java/com/android/server/audio/AudioService.java353
-rw-r--r--services/core/java/com/android/server/audio/AudioServiceEvents.java33
2 files changed, 371 insertions, 15 deletions
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 88a63f1aa06d..0cfa9fd4b39b 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -124,6 +124,7 @@ import android.util.Log;
import android.util.MathUtils;
import android.util.PrintWriterPrinter;
import android.util.Slog;
+import android.util.SparseArray;
import android.util.SparseIntArray;
import android.view.KeyEvent;
import android.view.accessibility.AccessibilityManager;
@@ -243,6 +244,7 @@ public class AudioService extends IAudioService.Stub
// AudioHandler messages
private static final int MSG_SET_DEVICE_VOLUME = 0;
private static final int MSG_PERSIST_VOLUME = 1;
+ private static final int MSG_PERSIST_VOLUME_GROUP = 2;
private static final int MSG_PERSIST_RINGER_MODE = 3;
private static final int MSG_AUDIO_SERVER_DIED = 4;
private static final int MSG_PLAY_SOUND_EFFECT = 5;
@@ -761,6 +763,10 @@ public class AudioService extends IAudioService.Stub
mSettingsObserver = new SettingsObserver();
createStreamStates();
+ // must be called after createStreamStates() as it uses MUSIC volume as default if no
+ // persistent data
+ initVolumeGroupStates();
+
// mSafeUsbMediaVolumeIndex must be initialized after createStreamStates() because it
// relies on audio policy having correct ranges for volume indexes.
mSafeUsbMediaVolumeIndex = getSafeUsbMediaVolumeIndex();
@@ -995,6 +1001,9 @@ public class AudioService extends IAudioService.Stub
streamState.applyAllVolumes();
}
+ // Restore audio volume groups
+ restoreVolumeGroups();
+
// Restore mono mode
updateMasterMono(mContentResolver);
@@ -2112,20 +2121,20 @@ public class AudioService extends IAudioService.Stub
String callingPackage) {
enforceModifyAudioRoutingPermission();
Preconditions.checkNotNull(attr, "attr must not be null");
- // @todo not hold the caller context, post message
- int stream = AudioProductStrategy.getLegacyStreamTypeForStrategyWithAudioAttributes(attr);
- final int device = getDeviceForStream(stream);
-
- int oldIndex = AudioSystem.getVolumeIndexForAttributes(attr, device);
-
- AudioSystem.setVolumeIndexForAttributes(attr, index, device);
-
final int volumeGroup = getVolumeGroupIdForAttributes(attr);
- final AudioVolumeGroup avg = getAudioVolumeGroupById(volumeGroup);
- if (avg == null) {
+ if (sVolumeGroupStates.indexOfKey(volumeGroup) < 0) {
+ Log.e(TAG, ": no volume group found for attributes " + attr.toString());
return;
}
- for (final int groupedStream : avg.getLegacyStreamTypes()) {
+ final VolumeGroupState vgs = sVolumeGroupStates.get(volumeGroup);
+
+ sVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_SET_GROUP_VOL, attr, vgs.name(),
+ index/*val1*/, flags/*val2*/, callingPackage));
+
+ vgs.setVolumeIndex(index, flags);
+
+ // For legacy reason, propagate to all streams associated to this volume group
+ for (final int groupedStream : vgs.getLegacyStreamTypes()) {
setStreamVolume(groupedStream, index, flags, callingPackage, callingPackage,
Binder.getCallingUid());
}
@@ -2147,10 +2156,12 @@ public class AudioService extends IAudioService.Stub
public int getVolumeIndexForAttributes(@NonNull AudioAttributes attr) {
enforceModifyAudioRoutingPermission();
Preconditions.checkNotNull(attr, "attr must not be null");
- int stream = AudioProductStrategy.getLegacyStreamTypeForStrategyWithAudioAttributes(attr);
- final int device = getDeviceForStream(stream);
-
- return AudioSystem.getVolumeIndexForAttributes(attr, device);
+ final int volumeGroup = getVolumeGroupIdForAttributes(attr);
+ if (sVolumeGroupStates.indexOfKey(volumeGroup) < 0) {
+ throw new IllegalArgumentException("No volume group for attributes " + attr);
+ }
+ final VolumeGroupState vgs = sVolumeGroupStates.get(volumeGroup);
+ return vgs.getVolumeIndex();
}
/** @see AudioManager#getMaxVolumeIndexForAttributes(attr) */
@@ -3549,6 +3560,8 @@ public class AudioService extends IAudioService.Stub
enforceSafeMediaVolume(TAG);
}
}
+
+ readVolumeGroupsSettings();
}
/** @see AudioManager#setSpeakerphoneOn(boolean) */
@@ -4437,6 +4450,310 @@ public class AudioService extends IAudioService.Stub
///////////////////////////////////////////////////////////////////////////
// Inner classes
///////////////////////////////////////////////////////////////////////////
+ /**
+ * Key is the AudioManager VolumeGroupId
+ * Value is the VolumeGroupState
+ */
+ private static final SparseArray<VolumeGroupState> sVolumeGroupStates = new SparseArray<>();
+
+ private void initVolumeGroupStates() {
+ for (final AudioVolumeGroup avg : getAudioVolumeGroups()) {
+ try {
+ // if no valid attributes, this volume group is not controllable, throw exception
+ ensureValidAttributes(avg);
+ } catch (IllegalArgumentException e) {
+ // Volume Groups without attributes are not controllable through set/get volume
+ // using attributes. Do not append them.
+ Log.d(TAG, "volume group " + avg.name() + " for internal policy needs");
+ continue;
+ }
+ sVolumeGroupStates.append(avg.getId(), new VolumeGroupState(avg));
+ }
+ for (int i = 0; i < sVolumeGroupStates.size(); i++) {
+ final VolumeGroupState vgs = sVolumeGroupStates.valueAt(i);
+ vgs.applyAllVolumes();
+ }
+ }
+
+ private void ensureValidAttributes(AudioVolumeGroup avg) {
+ boolean hasAtLeastOneValidAudioAttributes = avg.getAudioAttributes().stream()
+ .anyMatch(aa -> !aa.equals(AudioProductStrategy.sDefaultAttributes));
+ if (!hasAtLeastOneValidAudioAttributes) {
+ throw new IllegalArgumentException("Volume Group " + avg.name()
+ + " has no valid audio attributes");
+ }
+ }
+
+ private void readVolumeGroupsSettings() {
+ Log.v(TAG, "readVolumeGroupsSettings");
+ for (int i = 0; i < sVolumeGroupStates.size(); i++) {
+ final VolumeGroupState vgs = sVolumeGroupStates.valueAt(i);
+ vgs.readSettings();
+ vgs.applyAllVolumes();
+ }
+ }
+
+ // Called upon crash of AudioServer
+ private void restoreVolumeGroups() {
+ Log.v(TAG, "restoreVolumeGroups");
+ for (int i = 0; i < sVolumeGroupStates.size(); i++) {
+ final VolumeGroupState vgs = sVolumeGroupStates.valueAt(i);
+ vgs.applyAllVolumes();
+ }
+ }
+
+ private void dumpVolumeGroups(PrintWriter pw) {
+ pw.println("\nVolume Groups (device: index)");
+ for (int i = 0; i < sVolumeGroupStates.size(); i++) {
+ final VolumeGroupState vgs = sVolumeGroupStates.valueAt(i);
+ vgs.dump(pw);
+ pw.println("");
+ }
+ }
+
+ // NOTE: Locking order for synchronized objects related to volume management:
+ // 1 mSettingsLock
+ // 2 VolumeGroupState.class
+ private class VolumeGroupState {
+ private final AudioVolumeGroup mAudioVolumeGroup;
+ private final SparseIntArray mIndexMap = new SparseIntArray(8);
+ private int mIndexMin;
+ private int mIndexMax;
+ private int mLegacyStreamType = AudioSystem.STREAM_DEFAULT;
+ private int mPublicStreamType = AudioSystem.STREAM_MUSIC;
+ private AudioAttributes mAudioAttributes = AudioProductStrategy.sDefaultAttributes;
+
+ // No API in AudioSystem to get a device from strategy or from attributes.
+ // Need a valid public stream type to use current API getDeviceForStream
+ private int getDeviceForVolume() {
+ return getDeviceForStream(mPublicStreamType);
+ }
+
+ private VolumeGroupState(AudioVolumeGroup avg) {
+ mAudioVolumeGroup = avg;
+ Log.v(TAG, "VolumeGroupState for " + avg.toString());
+ for (final AudioAttributes aa : avg.getAudioAttributes()) {
+ if (!aa.equals(AudioProductStrategy.sDefaultAttributes)) {
+ mAudioAttributes = aa;
+ break;
+ }
+ }
+ final int[] streamTypes = mAudioVolumeGroup.getLegacyStreamTypes();
+ if (streamTypes.length != 0) {
+ // Uses already initialized MIN / MAX if a stream type is attached to group
+ mLegacyStreamType = streamTypes[0];
+ for (final int streamType : streamTypes) {
+ if (streamType != AudioSystem.STREAM_DEFAULT
+ && streamType < AudioSystem.getNumStreamTypes()) {
+ mPublicStreamType = streamType;
+ break;
+ }
+ }
+ mIndexMin = MIN_STREAM_VOLUME[mPublicStreamType];
+ mIndexMax = MAX_STREAM_VOLUME[mPublicStreamType];
+ } else if (!avg.getAudioAttributes().isEmpty()) {
+ mIndexMin = AudioSystem.getMinVolumeIndexForAttributes(mAudioAttributes);
+ mIndexMax = AudioSystem.getMaxVolumeIndexForAttributes(mAudioAttributes);
+ } else {
+ Log.e(TAG, "volume group: " + mAudioVolumeGroup.name()
+ + " has neither valid attributes nor valid stream types assigned");
+ return;
+ }
+ // Load volume indexes from data base
+ readSettings();
+ }
+
+ public @NonNull int[] getLegacyStreamTypes() {
+ return mAudioVolumeGroup.getLegacyStreamTypes();
+ }
+
+ public String name() {
+ return mAudioVolumeGroup.name();
+ }
+
+ public int getVolumeIndex() {
+ return getIndex(getDeviceForVolume());
+ }
+
+ public void setVolumeIndex(int index, int flags) {
+ if (mUseFixedVolume) {
+ return;
+ }
+ setVolumeIndex(index, getDeviceForVolume(), flags);
+ }
+
+ private void setVolumeIndex(int index, int device, int flags) {
+ // Set the volume index
+ setVolumeIndexInt(index, device, flags);
+
+ // Update local cache
+ mIndexMap.put(device, index);
+
+ // update data base - post a persist volume group msg
+ sendMsg(mAudioHandler,
+ MSG_PERSIST_VOLUME_GROUP,
+ SENDMSG_QUEUE,
+ device,
+ 0,
+ this,
+ PERSIST_DELAY);
+ }
+
+ private void setVolumeIndexInt(int index, int device, int flags) {
+ // Set the volume index
+ AudioSystem.setVolumeIndexForAttributes(mAudioAttributes, index, device);
+ }
+
+ public int getIndex(int device) {
+ synchronized (VolumeGroupState.class) {
+ int index = mIndexMap.get(device, -1);
+ // there is always an entry for AudioSystem.DEVICE_OUT_DEFAULT
+ return (index != -1) ? index : mIndexMap.get(AudioSystem.DEVICE_OUT_DEFAULT);
+ }
+ }
+
+ public boolean hasIndexForDevice(int device) {
+ synchronized (VolumeGroupState.class) {
+ return (mIndexMap.get(device, -1) != -1);
+ }
+ }
+
+ public int getMaxIndex() {
+ return mIndexMax;
+ }
+
+ public int getMinIndex() {
+ return mIndexMin;
+ }
+
+ public void applyAllVolumes() {
+ synchronized (VolumeGroupState.class) {
+ if (mLegacyStreamType != AudioSystem.STREAM_DEFAULT) {
+ // No-op to avoid regression with stream based volume management
+ return;
+ }
+ // apply device specific volumes first
+ int index;
+ for (int i = 0; i < mIndexMap.size(); i++) {
+ final int device = mIndexMap.keyAt(i);
+ if (device != AudioSystem.DEVICE_OUT_DEFAULT) {
+ index = mIndexMap.valueAt(i);
+ Log.v(TAG, "applyAllVolumes: restore index " + index + " for group "
+ + mAudioVolumeGroup.name() + " and device "
+ + AudioSystem.getOutputDeviceName(device));
+ setVolumeIndexInt(index, device, 0 /*flags*/);
+ }
+ }
+ // apply default volume last: by convention , default device volume will be used
+ index = getIndex(AudioSystem.DEVICE_OUT_DEFAULT);
+ Log.v(TAG, "applyAllVolumes: restore default device index " + index + " for group "
+ + mAudioVolumeGroup.name());
+ setVolumeIndexInt(index, AudioSystem.DEVICE_OUT_DEFAULT, 0 /*flags*/);
+ }
+ }
+
+ private void persistVolumeGroup(int device) {
+ if (mUseFixedVolume) {
+ return;
+ }
+ Log.v(TAG, "persistVolumeGroup: storing index " + getIndex(device) + " for group "
+ + mAudioVolumeGroup.name() + " and device "
+ + AudioSystem.getOutputDeviceName(device));
+ boolean success = Settings.System.putIntForUser(mContentResolver,
+ getSettingNameForDevice(device),
+ getIndex(device),
+ UserHandle.USER_CURRENT);
+ if (!success) {
+ Log.e(TAG, "persistVolumeGroup failed for group " + mAudioVolumeGroup.name());
+ }
+ }
+
+ public void readSettings() {
+ synchronized (VolumeGroupState.class) {
+ // First clear previously loaded (previous user?) settings
+ mIndexMap.clear();
+ // force maximum volume on all streams if fixed volume property is set
+ if (mUseFixedVolume) {
+ mIndexMap.put(AudioSystem.DEVICE_OUT_DEFAULT, mIndexMax);
+ return;
+ }
+ for (int device : AudioSystem.DEVICE_OUT_ALL_SET) {
+ // retrieve current volume for device
+ // if no volume stored for current volume group and device, use default volume
+ // if default device, continue otherwise
+ int defaultIndex = (device == AudioSystem.DEVICE_OUT_DEFAULT)
+ ? AudioSystem.DEFAULT_STREAM_VOLUME[mPublicStreamType] : -1;
+ int index;
+ String name = getSettingNameForDevice(device);
+ index = Settings.System.getIntForUser(
+ mContentResolver, name, defaultIndex, UserHandle.USER_CURRENT);
+ if (index == -1) {
+ Log.e(TAG, "readSettings: No index stored for group "
+ + mAudioVolumeGroup.name() + ", device " + name);
+ continue;
+ }
+ Log.v(TAG, "readSettings: found stored index " + getValidIndex(index)
+ + " for group " + mAudioVolumeGroup.name() + ", device: " + name);
+ mIndexMap.put(device, getValidIndex(index));
+ }
+ }
+ }
+
+ private int getValidIndex(int index) {
+ if (index < mIndexMin) {
+ return mIndexMin;
+ } else if (mUseFixedVolume || index > mIndexMax) {
+ return mIndexMax;
+ }
+ return index;
+ }
+
+ public @NonNull String getSettingNameForDevice(int device) {
+ final String suffix = AudioSystem.getOutputDeviceName(device);
+ if (suffix.isEmpty()) {
+ return mAudioVolumeGroup.name();
+ }
+ return mAudioVolumeGroup.name() + "_" + AudioSystem.getOutputDeviceName(device);
+ }
+
+ private void dump(PrintWriter pw) {
+ pw.println("- VOLUME GROUP " + mAudioVolumeGroup.name() + ":");
+ pw.print(" Min: ");
+ pw.println(mIndexMin);
+ pw.print(" Max: ");
+ pw.println(mIndexMax);
+ pw.print(" Current: ");
+ for (int i = 0; i < mIndexMap.size(); i++) {
+ if (i > 0) {
+ pw.print(", ");
+ }
+ final int device = mIndexMap.keyAt(i);
+ pw.print(Integer.toHexString(device));
+ final String deviceName = device == AudioSystem.DEVICE_OUT_DEFAULT ? "default"
+ : AudioSystem.getOutputDeviceName(device);
+ if (!deviceName.isEmpty()) {
+ pw.print(" (");
+ pw.print(deviceName);
+ pw.print(")");
+ }
+ pw.print(": ");
+ pw.print(mIndexMap.valueAt(i));
+ }
+ pw.println();
+ pw.print(" Devices: ");
+ int n = 0;
+ final int devices = getDeviceForVolume();
+ for (int device : AudioSystem.DEVICE_OUT_ALL_SET) {
+ if ((devices & device) == device) {
+ if (n++ > 0) {
+ pw.print(", ");
+ }
+ pw.print(AudioSystem.getOutputDeviceName(device));
+ }
+ }
+ }
+ }
+
// NOTE: Locking order for synchronized objects related to volume or ringer mode management:
// 1 mScoclient OR mSafeMediaVolumeState
@@ -5081,6 +5398,11 @@ public class AudioService extends IAudioService.Stub
persistVolume((VolumeStreamState) msg.obj, msg.arg1);
break;
+ case MSG_PERSIST_VOLUME_GROUP:
+ final VolumeGroupState vgs = (VolumeGroupState) msg.obj;
+ vgs.persistVolumeGroup(msg.arg1);
+ break;
+
case MSG_PERSIST_RINGER_MODE:
// note that the value persisted is the current ringer mode, not the
// value of ringer mode as of the time the request was made to persist
@@ -6128,6 +6450,7 @@ public class AudioService extends IAudioService.Stub
}
mMediaFocusControl.dump(pw);
dumpStreamStates(pw);
+ dumpVolumeGroups(pw);
dumpRingerMode(pw);
pw.println("\nAudio routes:");
pw.print(" mMainType=0x"); pw.println(Integer.toHexString(
diff --git a/services/core/java/com/android/server/audio/AudioServiceEvents.java b/services/core/java/com/android/server/audio/AudioServiceEvents.java
index fcd8701f4ccf..add620e74df0 100644
--- a/services/core/java/com/android/server/audio/AudioServiceEvents.java
+++ b/services/core/java/com/android/server/audio/AudioServiceEvents.java
@@ -16,6 +16,7 @@
package com.android.server.audio;
+import android.media.AudioAttributes;
import android.media.AudioManager;
import android.media.AudioSystem;
@@ -97,12 +98,15 @@ public class AudioServiceEvents {
static final int VOL_ADJUST_VOL_UID = 5;
static final int VOL_VOICE_ACTIVITY_HEARING_AID = 6;
static final int VOL_MODE_CHANGE_HEARING_AID = 7;
+ static final int VOL_SET_GROUP_VOL = 8;
final int mOp;
final int mStream;
final int mVal1;
final int mVal2;
final String mCaller;
+ final String mGroupName;
+ final AudioAttributes mAudioAttributes;
/** used for VOL_ADJUST_VOL_UID,
* VOL_ADJUST_SUGG_VOL,
@@ -114,6 +118,8 @@ public class AudioServiceEvents {
mVal1 = val1;
mVal2 = val2;
mCaller = caller;
+ mGroupName = null;
+ mAudioAttributes = null;
}
/** used for VOL_SET_HEARING_AID_VOL*/
@@ -124,6 +130,8 @@ public class AudioServiceEvents {
// unused
mStream = -1;
mCaller = null;
+ mGroupName = null;
+ mAudioAttributes = null;
}
/** used for VOL_SET_AVRCP_VOL */
@@ -134,6 +142,8 @@ public class AudioServiceEvents {
mVal2 = 0;
mStream = -1;
mCaller = null;
+ mGroupName = null;
+ mAudioAttributes = null;
}
/** used for VOL_VOICE_ACTIVITY_HEARING_AID */
@@ -144,6 +154,8 @@ public class AudioServiceEvents {
mVal2 = voiceActive ? 1 : 0;
// unused
mCaller = null;
+ mGroupName = null;
+ mAudioAttributes = null;
}
/** used for VOL_MODE_CHANGE_HEARING_AID */
@@ -154,6 +166,19 @@ public class AudioServiceEvents {
mVal2 = mode;
// unused
mCaller = null;
+ mGroupName = null;
+ mAudioAttributes = null;
+ }
+
+ /** used for VOL_SET_GROUP_VOL */
+ VolumeEvent(int op, AudioAttributes aa, String group, int index, int flags, String caller) {
+ mOp = op;
+ mStream = -1;
+ mVal1 = index;
+ mVal2 = flags;
+ mCaller = caller;
+ mGroupName = group;
+ mAudioAttributes = aa;
}
@Override
@@ -208,6 +233,14 @@ public class AudioServiceEvents {
.append(") causes setting HEARING_AID volume to idx:").append(mVal1)
.append(" stream:").append(AudioSystem.streamToString(mStream))
.toString();
+ case VOL_SET_GROUP_VOL:
+ return new StringBuilder("setVolumeIndexForAttributes(attr:")
+ .append(mAudioAttributes.toString())
+ .append(" group: ").append(mGroupName)
+ .append(" index:").append(mVal1)
+ .append(" flags:0x").append(Integer.toHexString(mVal2))
+ .append(") from ").append(mCaller)
+ .toString();
default: return new StringBuilder("FIXME invalid op:").append(mOp).toString();
}
}