Extract logic for media safe hearing.
The SoundDoseHelper is responsible to select between the old standard
and the new sound dose approach. Extracted the previous logic for
showing a warning when the safe hearing level is exceeded into the
SoundDoseHelper
Test: manual and dumpsys audio
Bug: 257238734
Change-Id: I0af5c827f805ac9862693422399c49d1bf66fd73
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index aa8ee3d..21030a8 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -27,6 +27,7 @@
import static android.provider.Settings.Secure.VOLUME_HUSH_OFF;
import static android.provider.Settings.Secure.VOLUME_HUSH_VIBRATE;
+import static com.android.server.audio.SoundDoseHelper.ACTION_CHECK_MUSIC_ACTIVE;
import static com.android.server.utils.EventLogger.Event.ALOGE;
import static com.android.server.utils.EventLogger.Event.ALOGI;
import static com.android.server.utils.EventLogger.Event.ALOGW;
@@ -42,12 +43,10 @@
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.app.ActivityThread;
-import android.app.AlarmManager;
import android.app.AppGlobals;
import android.app.AppOpsManager;
import android.app.IUidObserver;
import android.app.NotificationManager;
-import android.app.PendingIntent;
import android.app.role.OnRoleHoldersChangedListener;
import android.app.role.RoleManager;
import android.bluetooth.BluetoothAdapter;
@@ -166,7 +165,6 @@
import android.util.ArraySet;
import android.util.IntArray;
import android.util.Log;
-import android.util.MathUtils;
import android.util.PrintWriterPrinter;
import android.util.Slog;
import android.util.SparseArray;
@@ -332,7 +330,7 @@
private static final int SENDMSG_QUEUE = 2;
// AudioHandler messages
- private static final int MSG_SET_DEVICE_VOLUME = 0;
+ /*package*/ 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;
@@ -342,13 +340,8 @@
private static final int MSG_SET_FORCE_USE = 8;
private static final int MSG_BT_HEADSET_CNCT_FAILED = 9;
private static final int MSG_SET_ALL_VOLUMES = 10;
- private static final int MSG_CHECK_MUSIC_ACTIVE = 11;
- private static final int MSG_CONFIGURE_SAFE_MEDIA_VOLUME = 12;
- private static final int MSG_CONFIGURE_SAFE_MEDIA_VOLUME_FORCED = 13;
- private static final int MSG_PERSIST_SAFE_VOLUME_STATE = 14;
private static final int MSG_UNLOAD_SOUND_EFFECTS = 15;
private static final int MSG_SYSTEM_READY = 16;
- private static final int MSG_PERSIST_MUSIC_ACTIVE_MS = 17;
private static final int MSG_UNMUTE_STREAM = 18;
private static final int MSG_DYN_POLICY_MIX_STATE_UPDATE = 19;
private static final int MSG_INDICATE_SYSTEM_READY = 20;
@@ -384,6 +377,9 @@
private static final int MSG_RESET_SPATIALIZER = 50;
private static final int MSG_NO_LOG_FOR_PLAYER_I = 51;
+ /** Messages handled by the {@link SoundDoseHelper}. */
+ /*package*/ static final int SAFE_MEDIA_VOLUME_MSG_START = 1000;
+
// start of messages handled under wakelock
// these messages can only be queued, i.e. sent with queueMsgUnderWakeLock(),
// and not with sendMsg(..., ..., SENDMSG_QUEUE, ...)
@@ -399,6 +395,9 @@
// List of empty UIDs used to reset the active assistant list
private static final int[] NO_ACTIVE_ASSISTANT_SERVICE_UIDS = new int[0];
+ // check playback or record activity every 6 seconds for UIDs owning mode IN_COMMUNICATION
+ private static final int CHECK_MODE_FOR_UID_PERIOD_MS = 6000;
+
/** @see AudioSystemThread */
private AudioSystemThread mAudioSystemThread;
/** @see AudioHandler */
@@ -410,6 +409,10 @@
return mStreamStates[stream].getIndex(device);
}
+ /*package*/ VolumeStreamState getVssVolumeForStream(int stream) {
+ return mStreamStates[stream];
+ }
+
/*package*/ int getMaxVssVolumeForStream(int stream) {
return mStreamStates[stream].getMaxIndex();
}
@@ -838,11 +841,6 @@
private int mDockState = Intent.EXTRA_DOCK_STATE_UNDOCKED;
- // Used when safe volume warning message display is requested by setStreamVolume(). In this
- // case, the new requested volume, stream type and device are stored in mPendingVolumeCommand
- // and used later when/if disableSafeMediaVolume() is called.
- private StreamVolumeCommand mPendingVolumeCommand;
-
private PowerManager.WakeLock mAudioEventWakeLock;
private final MediaFocusControl mMediaFocusControl;
@@ -894,6 +892,8 @@
private boolean mNavigationRepeatSoundEffectsEnabled;
private boolean mHomeSoundEffectEnabled;
+ private final SoundDoseHelper mSoundDoseHelper;
+
@GuardedBy("mSettingsLock")
private int mCurrentImeUid;
@@ -1184,6 +1184,9 @@
mAudioHandler = new AudioHandler(looper);
}
+ mSoundDoseHelper = new SoundDoseHelper(this, mContext, mAudioHandler, mSettings,
+ mVolumeController);
+
AudioSystem.setErrorCallback(mAudioSystemCallback);
updateAudioHalPids();
@@ -1199,16 +1202,6 @@
new String("AudioService ctor"),
0);
- mSafeMediaVolumeState = mSettings.getGlobalInt(mContentResolver,
- Settings.Global.AUDIO_SAFE_VOLUME_STATE,
- SAFE_MEDIA_VOLUME_NOT_CONFIGURED);
- // The default safe volume index read here will be replaced by the actual value when
- // the mcc is read by onConfigureSafeVolume()
- mSafeMediaVolumeIndex = mContext.getResources().getInteger(
- com.android.internal.R.integer.config_safe_media_volume_index) * 10;
-
- mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
-
mUseFixedVolume = mContext.getResources().getBoolean(
com.android.internal.R.bool.config_useFixedVolume);
@@ -1285,9 +1278,7 @@
// persistent data
initVolumeGroupStates();
- // mSafeUsbMediaVolumeIndex must be initialized after createStreamStates() because it
- // relies on audio policy having correct ranges for volume indexes.
- mSafeUsbMediaVolumeIndex = getSafeUsbMediaVolumeIndex();
+ mSoundDoseHelper.initSafeUsbMediaVolumeIndex();
// Call setRingerModeInt() to apply correct mute
// state on streams affected by ringer mode.
@@ -1442,14 +1433,7 @@
mNm = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
- sendMsg(mAudioHandler,
- MSG_CONFIGURE_SAFE_MEDIA_VOLUME_FORCED,
- SENDMSG_REPLACE,
- 0,
- 0,
- TAG,
- SystemProperties.getBoolean("audio.safemedia.bypass", false) ?
- 0 : SAFE_VOLUME_CONFIGURE_TIMEOUT_MS);
+ mSoundDoseHelper.configureSafeMediaVolume(/*forced=*/true, TAG);
initA11yMonitoring();
@@ -1987,8 +1971,8 @@
@AudioService.ConnectionState int state, String caller) {
if (state == AudioService.CONNECTION_STATE_CONNECTED) {
// DEVICE_OUT_HDMI is now connected
- if (mSafeMediaVolumeDevices.contains(AudioSystem.DEVICE_OUT_HDMI)) {
- scheduleMusicActiveCheck();
+ if (mSoundDoseHelper.safeDevicesContains(AudioSystem.DEVICE_OUT_HDMI)) {
+ mSoundDoseHelper.scheduleMusicActiveCheck();
}
if (isPlatformTelevision()) {
@@ -3283,10 +3267,7 @@
return;
}
- // reset any pending volume command
- synchronized (mSafeMediaVolumeStateLock) {
- mPendingVolumeCommand = null;
- }
+ mSoundDoseHelper.invalidatPendingVolumeCommand();
flags &= ~AudioManager.FLAG_FIXED_VOLUME;
if (streamTypeAlias == AudioSystem.STREAM_MUSIC && isFixedVolumeDevice(device)) {
@@ -3295,10 +3276,8 @@
// Always toggle between max safe volume and 0 for fixed volume devices where safe
// volume is enforced, and max and 0 for the others.
// This is simulated by stepping by the full allowed volume range
- if (mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_ACTIVE &&
- mSafeMediaVolumeDevices.contains(device)) {
- step = safeMediaVolumeIndex(device);
- } else {
+ step = mSoundDoseHelper.getSafeMediaVolumeIndex(device);
+ if (step < 0) {
step = streamState.getMaxIndex();
}
if (aliasIndex != 0) {
@@ -3371,10 +3350,10 @@
}
}
}
- } else if ((direction == AudioManager.ADJUST_RAISE) &&
- !checkSafeMediaVolume(streamTypeAlias, aliasIndex + step, device)) {
+ } else if ((direction == AudioManager.ADJUST_RAISE)
+ && mSoundDoseHelper.raiseVolumeDisplaySafeMediaVolume(streamTypeAlias,
+ aliasIndex + step, device, flags)) {
Log.e(TAG, "adjustStreamVolume() safe volume index = " + oldIndex);
- mVolumeController.postDisplaySafeVolumeWarning(flags);
} else if (!isFullVolumeDevice(device)
&& (streamState.adjustIndex(direction * step, device, caller,
hasModifyAudioSettings)
@@ -3545,29 +3524,6 @@
Binder.restoreCallingIdentity(identity);
}
- // StreamVolumeCommand contains the information needed to defer the process of
- // setStreamVolume() in case the user has to acknowledge the safe volume warning message.
- static class StreamVolumeCommand {
- public final int mStreamType;
- public final int mIndex;
- public final int mFlags;
- public final int mDevice;
-
- StreamVolumeCommand(int streamType, int index, int flags, int device) {
- mStreamType = streamType;
- mIndex = index;
- mFlags = flags;
- mDevice = device;
- }
-
- @Override
- public String toString() {
- return new StringBuilder().append("{streamType=").append(mStreamType).append(",index=")
- .append(mIndex).append(",flags=").append(mFlags).append(",device=")
- .append(mDevice).append('}').toString();
- }
- }
-
private int getNewRingerMode(int stream, int index, int flags) {
// setRingerMode does nothing if the device is single volume,so the value would be unchanged
if (mIsSingleVolume) {
@@ -3615,7 +3571,7 @@
return false;
}
- private void onSetStreamVolume(int streamType, int index, int flags, int device,
+ /*package*/ void onSetStreamVolume(int streamType, int index, int flags, int device,
String caller, boolean hasModifyAudioSettings) {
final int stream = mStreamVolumeAlias[streamType];
setStreamVolumeInt(stream, index, device, false, caller, hasModifyAudioSettings);
@@ -3959,7 +3915,7 @@
updateHearingAidVolumeOnVoiceActivityUpdate();
}
if (mMediaPlaybackActive.getAndSet(mediaActive) != mediaActive && mediaActive) {
- scheduleMusicActiveCheck();
+ mSoundDoseHelper.scheduleMusicActiveCheck();
}
// Update playback active state for all apps in audio mode stack.
// When the audio mode owner becomes active, replace any delayed MSG_UPDATE_AUDIO_MODE
@@ -4205,71 +4161,64 @@
return;
}
- synchronized (mSafeMediaVolumeStateLock) {
- // reset any pending volume command
- mPendingVolumeCommand = null;
+ mSoundDoseHelper.invalidatPendingVolumeCommand();
- oldIndex = streamState.getIndex(device);
+ oldIndex = streamState.getIndex(device);
- index = rescaleIndex(index * 10, streamType, streamTypeAlias);
+ index = rescaleIndex(index * 10, streamType, streamTypeAlias);
- if (streamTypeAlias == AudioSystem.STREAM_MUSIC
- && AudioSystem.DEVICE_OUT_ALL_A2DP_SET.contains(device)
- && (flags & AudioManager.FLAG_BLUETOOTH_ABS_VOLUME) == 0) {
- if (DEBUG_VOL) {
- Log.d(TAG, "setStreamVolume postSetAvrcpAbsoluteVolumeIndex index=" + index
- + "stream=" + streamType);
+ if (streamTypeAlias == AudioSystem.STREAM_MUSIC
+ && AudioSystem.DEVICE_OUT_ALL_A2DP_SET.contains(device)
+ && (flags & AudioManager.FLAG_BLUETOOTH_ABS_VOLUME) == 0) {
+ if (DEBUG_VOL) {
+ Log.d(TAG, "setStreamVolume postSetAvrcpAbsoluteVolumeIndex index=" + index
+ + "stream=" + streamType);
+ }
+ mDeviceBroker.postSetAvrcpAbsoluteVolumeIndex(index / 10);
+ } else if (isAbsoluteVolumeDevice(device)
+ && ((flags & AudioManager.FLAG_ABSOLUTE_VOLUME) == 0)) {
+ AbsoluteVolumeDeviceInfo info = mAbsoluteVolumeDeviceInfoMap.get(device);
+
+ dispatchAbsoluteVolumeChanged(streamType, info, index);
+ }
+
+ if (AudioSystem.isLeAudioDeviceType(device)
+ && streamType == getBluetoothContextualVolumeStream()
+ && (flags & AudioManager.FLAG_BLUETOOTH_ABS_VOLUME) == 0) {
+ if (DEBUG_VOL) {
+ Log.d(TAG, "adjustSreamVolume postSetLeAudioVolumeIndex index="
+ + index + " stream=" + streamType);
+ }
+ mDeviceBroker.postSetLeAudioVolumeIndex(index, mStreamStates[streamType].getMaxIndex(),
+ streamType);
+ }
+
+ if (device == AudioSystem.DEVICE_OUT_HEARING_AID
+ && streamType == getBluetoothContextualVolumeStream()) {
+ Log.i(TAG, "setStreamVolume postSetHearingAidVolumeIndex index=" + index
+ + " stream=" + streamType);
+ mDeviceBroker.postSetHearingAidVolumeIndex(index, streamType);
+ }
+
+ flags &= ~AudioManager.FLAG_FIXED_VOLUME;
+ if (streamTypeAlias == AudioSystem.STREAM_MUSIC && isFixedVolumeDevice(device)) {
+ flags |= AudioManager.FLAG_FIXED_VOLUME;
+
+ // volume is either 0 or max allowed for fixed volume devices
+ if (index != 0) {
+ index = mSoundDoseHelper.getSafeMediaVolumeIndex(device);
+ if (index < 0) {
+ index = streamState.getMaxIndex();
}
- mDeviceBroker.postSetAvrcpAbsoluteVolumeIndex(index / 10);
- } else if (isAbsoluteVolumeDevice(device)
- && ((flags & AudioManager.FLAG_ABSOLUTE_VOLUME) == 0)) {
- AbsoluteVolumeDeviceInfo info = mAbsoluteVolumeDeviceInfoMap.get(device);
-
- dispatchAbsoluteVolumeChanged(streamType, info, index);
- }
-
- if (AudioSystem.isLeAudioDeviceType(device)
- && streamType == getBluetoothContextualVolumeStream()
- && (flags & AudioManager.FLAG_BLUETOOTH_ABS_VOLUME) == 0) {
- if (DEBUG_VOL) {
- Log.d(TAG, "adjustSreamVolume postSetLeAudioVolumeIndex index="
- + index + " stream=" + streamType);
- }
- mDeviceBroker.postSetLeAudioVolumeIndex(index,
- mStreamStates[streamType].getMaxIndex(), streamType);
- }
-
- if (device == AudioSystem.DEVICE_OUT_HEARING_AID
- && streamType == getBluetoothContextualVolumeStream()) {
- Log.i(TAG, "setStreamVolume postSetHearingAidVolumeIndex index=" + index
- + " stream=" + streamType);
- mDeviceBroker.postSetHearingAidVolumeIndex(index, streamType);
- }
-
- flags &= ~AudioManager.FLAG_FIXED_VOLUME;
- if (streamTypeAlias == AudioSystem.STREAM_MUSIC && isFixedVolumeDevice(device)) {
- flags |= AudioManager.FLAG_FIXED_VOLUME;
-
- // volume is either 0 or max allowed for fixed volume devices
- if (index != 0) {
- if (mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_ACTIVE &&
- mSafeMediaVolumeDevices.contains(device)) {
- index = safeMediaVolumeIndex(device);
- } else {
- index = streamState.getMaxIndex();
- }
- }
- }
-
- if (!checkSafeMediaVolume(streamTypeAlias, index, device)) {
- mVolumeController.postDisplaySafeVolumeWarning(flags);
- mPendingVolumeCommand = new StreamVolumeCommand(
- streamType, index, flags, device);
- } else {
- onSetStreamVolume(streamType, index, flags, device, caller, hasModifyAudioSettings);
- index = mStreamStates[streamType].getIndex(device);
}
}
+
+ if (!mSoundDoseHelper.willDisplayWarningAfterCheckVolume(streamType, index, device,
+ flags)) {
+ onSetStreamVolume(streamType, index, flags, device, caller, hasModifyAudioSettings);
+ index = mStreamStates[streamType].getIndex(device);
+ }
+
synchronized (mHdmiClientLock) {
if (streamTypeAlias == AudioSystem.STREAM_MUSIC
&& (oldIndex != index)) {
@@ -5846,14 +5795,8 @@
checkAllAliasStreamVolumes();
checkMuteAffectedStreams();
- synchronized (mSafeMediaVolumeStateLock) {
- mMusicActiveMs = MathUtils.constrain(mSettings.getSecureIntForUser(mContentResolver,
- Settings.Secure.UNSAFE_VOLUME_MUSIC_ACTIVE_MS, 0, UserHandle.USER_CURRENT),
- 0, UNSAFE_VOLUME_MUSIC_ACTIVE_MS_MAX);
- if (mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_ACTIVE) {
- enforceSafeMediaVolume(TAG);
- }
- }
+ mSoundDoseHelper.restoreMusicActiveMs();
+ mSoundDoseHelper.enforceSafeMediaVolumeIfActive(TAG);
readVolumeGroupsSettings();
@@ -6189,139 +6132,6 @@
return mContentResolver;
}
- private void scheduleMusicActiveCheck() {
- synchronized (mSafeMediaVolumeStateLock) {
- cancelMusicActiveCheck();
- mMusicActiveIntent = PendingIntent.getBroadcast(mContext,
- REQUEST_CODE_CHECK_MUSIC_ACTIVE,
- new Intent(ACTION_CHECK_MUSIC_ACTIVE),
- PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
- mAlarmManager.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP,
- SystemClock.elapsedRealtime()
- + MUSIC_ACTIVE_POLL_PERIOD_MS, mMusicActiveIntent);
- }
- }
-
- private void cancelMusicActiveCheck() {
- synchronized (mSafeMediaVolumeStateLock) {
- if (mMusicActiveIntent != null) {
- mAlarmManager.cancel(mMusicActiveIntent);
- mMusicActiveIntent = null;
- }
- }
- }
- private void onCheckMusicActive(String caller) {
- synchronized (mSafeMediaVolumeStateLock) {
- if (mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_INACTIVE) {
- int device = getDeviceForStream(AudioSystem.STREAM_MUSIC);
- if (mSafeMediaVolumeDevices.contains(device)
- && mAudioSystem.isStreamActive(AudioSystem.STREAM_MUSIC, 0)) {
- scheduleMusicActiveCheck();
- int index = mStreamStates[AudioSystem.STREAM_MUSIC].getIndex(device);
- if (index > safeMediaVolumeIndex(device)) {
- // Approximate cumulative active music time
- long curTimeMs = SystemClock.elapsedRealtime();
- if (mLastMusicActiveTimeMs != 0) {
- mMusicActiveMs += (int) (curTimeMs - mLastMusicActiveTimeMs);
- }
- mLastMusicActiveTimeMs = curTimeMs;
- Log.i(TAG, "onCheckMusicActive() mMusicActiveMs: " + mMusicActiveMs);
- if (mMusicActiveMs > UNSAFE_VOLUME_MUSIC_ACTIVE_MS_MAX) {
- setSafeMediaVolumeEnabled(true, caller);
- mMusicActiveMs = 0;
- }
- saveMusicActiveMs();
- }
- } else {
- cancelMusicActiveCheck();
- mLastMusicActiveTimeMs = 0;
- }
- }
- }
- }
-
- private void saveMusicActiveMs() {
- mAudioHandler.obtainMessage(MSG_PERSIST_MUSIC_ACTIVE_MS, mMusicActiveMs, 0).sendToTarget();
- }
-
- private int getSafeUsbMediaVolumeIndex() {
- // determine UI volume index corresponding to the wanted safe gain in dBFS
- int min = MIN_STREAM_VOLUME[AudioSystem.STREAM_MUSIC];
- int max = MAX_STREAM_VOLUME[AudioSystem.STREAM_MUSIC];
-
- mSafeUsbMediaVolumeDbfs = mContext.getResources().getInteger(
- com.android.internal.R.integer.config_safe_media_volume_usb_mB) / 100.0f;
-
- while (Math.abs(max - min) > 1) {
- int index = (max + min) / 2;
- float gainDB = AudioSystem.getStreamVolumeDB(
- AudioSystem.STREAM_MUSIC, index, AudioSystem.DEVICE_OUT_USB_HEADSET);
- if (Float.isNaN(gainDB)) {
- //keep last min in case of read error
- break;
- } else if (gainDB == mSafeUsbMediaVolumeDbfs) {
- min = index;
- break;
- } else if (gainDB < mSafeUsbMediaVolumeDbfs) {
- min = index;
- } else {
- max = index;
- }
- }
- return min * 10;
- }
-
- private void onConfigureSafeVolume(boolean force, String caller) {
- synchronized (mSafeMediaVolumeStateLock) {
- int mcc = mContext.getResources().getConfiguration().mcc;
- if ((mMcc != mcc) || ((mMcc == 0) && force)) {
- mSafeMediaVolumeIndex = mContext.getResources().getInteger(
- com.android.internal.R.integer.config_safe_media_volume_index) * 10;
-
- mSafeUsbMediaVolumeIndex = getSafeUsbMediaVolumeIndex();
-
- boolean safeMediaVolumeEnabled =
- SystemProperties.getBoolean("audio.safemedia.force", false)
- || mContext.getResources().getBoolean(
- com.android.internal.R.bool.config_safe_media_volume_enabled);
-
- boolean safeMediaVolumeBypass =
- SystemProperties.getBoolean("audio.safemedia.bypass", false);
-
- // The persisted state is either "disabled" or "active": this is the state applied
- // next time we boot and cannot be "inactive"
- int persistedState;
- if (safeMediaVolumeEnabled && !safeMediaVolumeBypass) {
- persistedState = SAFE_MEDIA_VOLUME_ACTIVE;
- // The state can already be "inactive" here if the user has forced it before
- // the 30 seconds timeout for forced configuration. In this case we don't reset
- // it to "active".
- if (mSafeMediaVolumeState != SAFE_MEDIA_VOLUME_INACTIVE) {
- if (mMusicActiveMs == 0) {
- mSafeMediaVolumeState = SAFE_MEDIA_VOLUME_ACTIVE;
- enforceSafeMediaVolume(caller);
- } else {
- // We have existing playback time recorded, already confirmed.
- mSafeMediaVolumeState = SAFE_MEDIA_VOLUME_INACTIVE;
- mLastMusicActiveTimeMs = 0;
- }
- }
- } else {
- persistedState = SAFE_MEDIA_VOLUME_DISABLED;
- mSafeMediaVolumeState = SAFE_MEDIA_VOLUME_DISABLED;
- }
- mMcc = mcc;
- sendMsg(mAudioHandler,
- MSG_PERSIST_SAFE_VOLUME_STATE,
- SENDMSG_QUEUE,
- persistedState,
- 0,
- null,
- 0);
- }
- }
- }
-
///////////////////////////////////////////////////////////////////////////
// Internal methods
///////////////////////////////////////////////////////////////////////////
@@ -7711,7 +7521,7 @@
// 2 mSetModeLock
// 3 mSettingsLock
// 4 VolumeStreamState.class
- private class VolumeStreamState {
+ /*package*/ class VolumeStreamState {
private final int mStreamType;
private int mIndexMin;
// min index when user doesn't have permission to change audio settings
@@ -8364,8 +8174,8 @@
final VolumeStreamState streamState = mStreamStates[update.mStreamType];
if (update.hasVolumeIndex()) {
int index = update.getVolumeIndex();
- if (!checkSafeMediaVolume(update.mStreamType, index, update.mDevice)) {
- index = safeMediaVolumeIndex(update.mDevice);
+ if (!mSoundDoseHelper.checkSafeMediaVolume(update.mStreamType, index, update.mDevice)) {
+ index = mSoundDoseHelper.safeMediaVolumeIndex(update.mDevice);
}
streamState.setIndex(index, update.mDevice, update.mCaller,
// trusted as index is always validated before message is posted
@@ -8415,7 +8225,7 @@
}
/** Handles internal volume messages in separate volume thread. */
- private class AudioHandler extends Handler {
+ /*package*/ class AudioHandler extends Handler {
AudioHandler() {
super();
@@ -8462,12 +8272,6 @@
mSettings.putGlobalInt(mContentResolver, Settings.Global.MODE_RINGER, ringerMode);
}
- private void onPersistSafeVolumeState(int state) {
- mSettings.putGlobalInt(mContentResolver,
- Settings.Global.AUDIO_SAFE_VOLUME_STATE,
- state);
- }
-
private void onNotifyVolumeEvent(@NonNull IAudioPolicyCallback apc,
@AudioManager.VolumeAdjustment int direction) {
try {
@@ -8585,19 +8389,6 @@
mSpatializerHelper.reset(/* featureEnabled */ mHasSpatializerEffect);
break;
- case MSG_CHECK_MUSIC_ACTIVE:
- onCheckMusicActive((String) msg.obj);
- break;
-
- case MSG_CONFIGURE_SAFE_MEDIA_VOLUME_FORCED:
- case MSG_CONFIGURE_SAFE_MEDIA_VOLUME:
- onConfigureSafeVolume((msg.what == MSG_CONFIGURE_SAFE_MEDIA_VOLUME_FORCED),
- (String) msg.obj);
- break;
- case MSG_PERSIST_SAFE_VOLUME_STATE:
- onPersistSafeVolumeState(msg.arg1);
- break;
-
case MSG_SYSTEM_READY:
onSystemReady();
break;
@@ -8610,13 +8401,6 @@
onAccessoryPlugMediaUnmute(msg.arg1);
break;
- case MSG_PERSIST_MUSIC_ACTIVE_MS:
- final int musicActiveMs = msg.arg1;
- mSettings.putSecureIntForUser(mContentResolver,
- Settings.Secure.UNSAFE_VOLUME_MUSIC_ACTIVE_MS, musicActiveMs,
- UserHandle.USER_CURRENT);
- break;
-
case MSG_UNMUTE_STREAM:
onUnmuteStream(msg.arg1, msg.arg2);
break;
@@ -8746,6 +8530,12 @@
case MSG_NO_LOG_FOR_PLAYER_I:
mPlaybackMonitor.ignorePlayerIId(msg.arg1);
break;
+
+ default:
+ if (msg.what >= SAFE_MEDIA_VOLUME_MSG_START) {
+ // msg could be for the SoundDoseHelper
+ mSoundDoseHelper.handleMessage(msg);
+ }
}
}
}
@@ -8862,8 +8652,8 @@
/** only public for mocking/spying, do not call outside of AudioService */
@VisibleForTesting
public void checkMusicActive(int deviceType, String caller) {
- if (mSafeMediaVolumeDevices.contains(deviceType)) {
- scheduleMusicActiveCheck();
+ if (mSoundDoseHelper.safeDevicesContains(deviceType)) {
+ mSoundDoseHelper.scheduleMusicActiveCheck();
}
}
@@ -8997,7 +8787,8 @@
}
}
} else if (action.equals(ACTION_CHECK_MUSIC_ACTIVE)) {
- onCheckMusicActive(ACTION_CHECK_MUSIC_ACTIVE);
+ mSoundDoseHelper.onCheckMusicActive(ACTION_CHECK_MUSIC_ACTIVE,
+ mAudioSystem.isStreamActive(AudioSystem.STREAM_MUSIC, 0));
}
}
} // end class AudioServiceBroadcastReceiver
@@ -9883,13 +9674,7 @@
// reading new configuration "safely" (i.e. under try catch) in case anything
// goes wrong.
Configuration config = context.getResources().getConfiguration();
- sendMsg(mAudioHandler,
- MSG_CONFIGURE_SAFE_MEDIA_VOLUME,
- SENDMSG_REPLACE,
- 0,
- 0,
- TAG,
- 0);
+ mSoundDoseHelper.configureSafeMediaVolume(/*forced*/false, TAG);
boolean cameraSoundForced = readCameraSoundForced();
synchronized (mSettingsLock) {
@@ -9947,143 +9732,10 @@
return mDeviceBroker.startWatchingRoutes(observer);
}
-
- //==========================================================================================
- // Safe media volume management.
- // MUSIC stream volume level is limited when headphones are connected according to safety
- // regulation. When the user attempts to raise the volume above the limit, a warning is
- // displayed and the user has to acknowlegde before the volume is actually changed.
- // The volume index corresponding to the limit is stored in config_safe_media_volume_index
- // property. Platforms with a different limit must set this property accordingly in their
- // overlay.
- //==========================================================================================
-
- // mSafeMediaVolumeState indicates whether the media volume is limited over headphones.
- // It is SAFE_MEDIA_VOLUME_NOT_CONFIGURED at boot time until a network service is connected
- // or the configure time is elapsed. It is then set to SAFE_MEDIA_VOLUME_ACTIVE or
- // SAFE_MEDIA_VOLUME_DISABLED according to country option. If not SAFE_MEDIA_VOLUME_DISABLED, it
- // can be set to SAFE_MEDIA_VOLUME_INACTIVE by calling AudioService.disableSafeMediaVolume()
- // (when user opts out).
- private static final int SAFE_MEDIA_VOLUME_NOT_CONFIGURED = 0;
- private static final int SAFE_MEDIA_VOLUME_DISABLED = 1;
- private static final int SAFE_MEDIA_VOLUME_INACTIVE = 2; // confirmed
- private static final int SAFE_MEDIA_VOLUME_ACTIVE = 3; // unconfirmed
- private int mSafeMediaVolumeState;
- private final Object mSafeMediaVolumeStateLock = new Object();
-
- private int mMcc = 0;
- // mSafeMediaVolumeIndex is the cached value of config_safe_media_volume_index property
- private int mSafeMediaVolumeIndex;
- // mSafeUsbMediaVolumeDbfs is the cached value of the config_safe_media_volume_usb_mB
- // property, divided by 100.0.
- private float mSafeUsbMediaVolumeDbfs;
- // mSafeUsbMediaVolumeIndex is used for USB Headsets and is the music volume UI index
- // corresponding to a gain of mSafeUsbMediaVolumeDbfs (defaulting to -37dB) in audio
- // flinger mixer.
- // We remove -22 dBs from the theoretical -15dB to account for the EQ + bass boost
- // amplification when both effects are on with all band gains at maximum.
- // This level corresponds to a loudness of 85 dB SPL for the warning to be displayed when
- // the headset is compliant to EN 60950 with a max loudness of 100dB SPL.
- private int mSafeUsbMediaVolumeIndex;
- // mSafeMediaVolumeDevices lists the devices for which safe media volume is enforced,
- /*package*/ final Set<Integer> mSafeMediaVolumeDevices = new HashSet<>(
- Arrays.asList(AudioSystem.DEVICE_OUT_WIRED_HEADSET,
- AudioSystem.DEVICE_OUT_WIRED_HEADPHONE, AudioSystem.DEVICE_OUT_USB_HEADSET));
- // mMusicActiveMs is the cumulative time of music activity since safe volume was disabled.
- // When this time reaches UNSAFE_VOLUME_MUSIC_ACTIVE_MS_MAX, the safe media volume is re-enabled
- // automatically. mMusicActiveMs is rounded to a multiple of MUSIC_ACTIVE_POLL_PERIOD_MS.
- private int mMusicActiveMs;
- private long mLastMusicActiveTimeMs = 0;
- private PendingIntent mMusicActiveIntent = null;
- private AlarmManager mAlarmManager;
-
- private static final int UNSAFE_VOLUME_MUSIC_ACTIVE_MS_MAX = (20 * 3600 * 1000); // 20 hours
- private static final int MUSIC_ACTIVE_POLL_PERIOD_MS = 60000; // 1 minute polling interval
- private static final int SAFE_VOLUME_CONFIGURE_TIMEOUT_MS = 30000; // 30s after boot completed
- // check playback or record activity every 6 seconds for UIDs owning mode IN_COMMUNICATION
- private static final int CHECK_MODE_FOR_UID_PERIOD_MS = 6000;
-
- private static final String ACTION_CHECK_MUSIC_ACTIVE =
- AudioService.class.getSimpleName() + ".CHECK_MUSIC_ACTIVE";
- private static final int REQUEST_CODE_CHECK_MUSIC_ACTIVE = 1;
-
- private int safeMediaVolumeIndex(int device) {
- if (!mSafeMediaVolumeDevices.contains(device)) {
- return MAX_STREAM_VOLUME[AudioSystem.STREAM_MUSIC];
- }
- if (device == AudioSystem.DEVICE_OUT_USB_HEADSET) {
- return mSafeUsbMediaVolumeIndex;
- } else {
- return mSafeMediaVolumeIndex;
- }
- }
-
- private void setSafeMediaVolumeEnabled(boolean on, String caller) {
- synchronized (mSafeMediaVolumeStateLock) {
- if ((mSafeMediaVolumeState != SAFE_MEDIA_VOLUME_NOT_CONFIGURED) &&
- (mSafeMediaVolumeState != SAFE_MEDIA_VOLUME_DISABLED)) {
- if (on && (mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_INACTIVE)) {
- mSafeMediaVolumeState = SAFE_MEDIA_VOLUME_ACTIVE;
- enforceSafeMediaVolume(caller);
- } else if (!on && (mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_ACTIVE)) {
- mSafeMediaVolumeState = SAFE_MEDIA_VOLUME_INACTIVE;
- mMusicActiveMs = 1; // nonzero = confirmed
- mLastMusicActiveTimeMs = 0;
- saveMusicActiveMs();
- scheduleMusicActiveCheck();
- }
- }
- }
- }
-
- private void enforceSafeMediaVolume(String caller) {
- VolumeStreamState streamState = mStreamStates[AudioSystem.STREAM_MUSIC];
- Set<Integer> devices = mSafeMediaVolumeDevices;
-
- for (int device : devices) {
- int index = streamState.getIndex(device);
- if (index > safeMediaVolumeIndex(device)) {
- streamState.setIndex(safeMediaVolumeIndex(device), device, caller,
- true /*hasModifyAudioSettings*/);
- sendMsg(mAudioHandler,
- MSG_SET_DEVICE_VOLUME,
- SENDMSG_QUEUE,
- device,
- 0,
- streamState,
- 0);
- }
- }
- }
-
- private boolean checkSafeMediaVolume(int streamType, int index, int device) {
- synchronized (mSafeMediaVolumeStateLock) {
- if ((mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_ACTIVE)
- && (mStreamVolumeAlias[streamType] == AudioSystem.STREAM_MUSIC)
- && (mSafeMediaVolumeDevices.contains(device))
- && (index > safeMediaVolumeIndex(device))) {
- return false;
- }
- return true;
- }
- }
-
@Override
public void disableSafeMediaVolume(String callingPackage) {
enforceVolumeController("disable the safe media volume");
- synchronized (mSafeMediaVolumeStateLock) {
- final long identity = Binder.clearCallingIdentity();
- setSafeMediaVolumeEnabled(false, callingPackage);
- Binder.restoreCallingIdentity(identity);
- if (mPendingVolumeCommand != null) {
- onSetStreamVolume(mPendingVolumeCommand.mStreamType,
- mPendingVolumeCommand.mIndex,
- mPendingVolumeCommand.mFlags,
- mPendingVolumeCommand.mDevice,
- callingPackage, true /*hasModifyAudioSettings*/);
- mPendingVolumeCommand = null;
- }
- }
+ mSoundDoseHelper.disableSafeMediaVolume(callingPackage);
}
//==========================================================================================
@@ -10411,15 +10063,8 @@
pw.println("\nOther state:");
pw.print(" mVolumeController="); pw.println(mVolumeController);
- pw.print(" mSafeMediaVolumeState=");
- pw.println(safeMediaVolumeStateToString(mSafeMediaVolumeState));
- pw.print(" mSafeMediaVolumeIndex="); pw.println(mSafeMediaVolumeIndex);
- pw.print(" mSafeUsbMediaVolumeIndex="); pw.println(mSafeUsbMediaVolumeIndex);
- pw.print(" mSafeUsbMediaVolumeDbfs="); pw.println(mSafeUsbMediaVolumeDbfs);
+ mSoundDoseHelper.dump(pw);
pw.print(" sIndependentA11yVolume="); pw.println(sIndependentA11yVolume);
- pw.print(" mPendingVolumeCommand="); pw.println(mPendingVolumeCommand);
- pw.print(" mMusicActiveMs="); pw.println(mMusicActiveMs);
- pw.print(" mMcc="); pw.println(mMcc);
pw.print(" mCameraSoundForced="); pw.println(isCameraSoundForced());
pw.print(" mHasVibrator="); pw.println(mHasVibrator);
pw.print(" mVolumePolicy="); pw.println(mVolumePolicy);
@@ -10520,16 +10165,6 @@
private static final String mMetricsId = MediaMetrics.Name.AUDIO_SERVICE
+ MediaMetrics.SEPARATOR;
- private static String safeMediaVolumeStateToString(int state) {
- switch(state) {
- case SAFE_MEDIA_VOLUME_NOT_CONFIGURED: return "SAFE_MEDIA_VOLUME_NOT_CONFIGURED";
- case SAFE_MEDIA_VOLUME_DISABLED: return "SAFE_MEDIA_VOLUME_DISABLED";
- case SAFE_MEDIA_VOLUME_INACTIVE: return "SAFE_MEDIA_VOLUME_INACTIVE";
- case SAFE_MEDIA_VOLUME_ACTIVE: return "SAFE_MEDIA_VOLUME_ACTIVE";
- }
- return null;
- }
-
// Inform AudioFlinger of our device's low RAM attribute
private static void readAndSetLowRamDevice()
{
@@ -10609,7 +10244,14 @@
}
}
- public class VolumeController {
+ /** Interface used for enforcing the safe hearing standard. */
+ public interface ISafeHearingVolumeController {
+ /** Displays an instructional safeguard as required by the safe hearing standard. */
+ void postDisplaySafeVolumeWarning(int flags);
+ }
+
+ /** Wrapper which encapsulates the {@link IVolumeController} functionality. */
+ public class VolumeController implements ISafeHearingVolumeController {
private static final String TAG = "VolumeController";
private IVolumeController mController;
@@ -10696,6 +10338,7 @@
return "VolumeController(" + asBinder() + ",mVisible=" + mVisible + ")";
}
+ @Override
public void postDisplaySafeVolumeWarning(int flags) {
if (mController == null)
return;
diff --git a/services/core/java/com/android/server/audio/SoundDoseHelper.java b/services/core/java/com/android/server/audio/SoundDoseHelper.java
new file mode 100644
index 0000000..5188a53
--- /dev/null
+++ b/services/core/java/com/android/server/audio/SoundDoseHelper.java
@@ -0,0 +1,519 @@
+/*
+ * 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 com.android.server.audio.AudioService.MAX_STREAM_VOLUME;
+import static com.android.server.audio.AudioService.MIN_STREAM_VOLUME;
+import static com.android.server.audio.AudioService.MSG_SET_DEVICE_VOLUME;
+import static com.android.server.audio.AudioService.SAFE_MEDIA_VOLUME_MSG_START;
+
+import android.annotation.NonNull;
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.media.AudioSystem;
+import android.os.Binder;
+import android.os.Message;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.util.Log;
+import android.util.MathUtils;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.audio.AudioService.AudioHandler;
+import com.android.server.audio.AudioService.ISafeHearingVolumeController;
+
+import java.io.PrintWriter;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Safe media volume management.
+ * MUSIC stream volume level is limited when headphones are connected according to safety
+ * regulation. When the user attempts to raise the volume above the limit, a warning is
+ * displayed and the user has to acknowledge before the volume is actually changed.
+ * The volume index corresponding to the limit is stored in config_safe_media_volume_index
+ * property. Platforms with a different limit must set this property accordingly in their
+ * overlay.
+ */
+public class SoundDoseHelper {
+ private static final String TAG = "AS.SoundDoseHelper";
+
+ /*package*/ static final String ACTION_CHECK_MUSIC_ACTIVE =
+ AudioService.class.getSimpleName() + ".CHECK_MUSIC_ACTIVE";
+
+ /** Flag to enable/disable the sound dose computation. */
+ private static final boolean USE_CSD_FOR_SAFE_HEARING = false;
+
+ // mSafeMediaVolumeState indicates whether the media volume is limited over headphones.
+ // It is SAFE_MEDIA_VOLUME_NOT_CONFIGURED at boot time until a network service is connected
+ // or the configure time is elapsed. It is then set to SAFE_MEDIA_VOLUME_ACTIVE or
+ // SAFE_MEDIA_VOLUME_DISABLED according to country option. If not SAFE_MEDIA_VOLUME_DISABLED, it
+ // can be set to SAFE_MEDIA_VOLUME_INACTIVE by calling AudioService.disableSafeMediaVolume()
+ // (when user opts out).
+ private static final int SAFE_MEDIA_VOLUME_NOT_CONFIGURED = 0;
+ private static final int SAFE_MEDIA_VOLUME_DISABLED = 1;
+ private static final int SAFE_MEDIA_VOLUME_INACTIVE = 2; // confirmed
+ private static final int SAFE_MEDIA_VOLUME_ACTIVE = 3; // unconfirmed
+
+ private static final int MSG_CONFIGURE_SAFE_MEDIA_VOLUME = SAFE_MEDIA_VOLUME_MSG_START + 1;
+ private static final int MSG_PERSIST_SAFE_VOLUME_STATE = SAFE_MEDIA_VOLUME_MSG_START + 2;
+ private static final int MSG_PERSIST_MUSIC_ACTIVE_MS = SAFE_MEDIA_VOLUME_MSG_START + 3;
+
+ private static final int UNSAFE_VOLUME_MUSIC_ACTIVE_MS_MAX = (20 * 3600 * 1000); // 20 hours
+
+ // 30s after boot completed
+ private static final int SAFE_VOLUME_CONFIGURE_TIMEOUT_MS = 30000;
+
+ private static final int MUSIC_ACTIVE_POLL_PERIOD_MS = 60000; // 1 minute polling interval
+ private static final int REQUEST_CODE_CHECK_MUSIC_ACTIVE = 1;
+
+ private int mMcc = 0;
+
+ final Object mSafeMediaVolumeStateLock = new Object();
+ private int mSafeMediaVolumeState;
+
+ // Used when safe volume warning message display is requested by setStreamVolume(). In this
+ // case, the new requested volume, stream type and device are stored in mPendingVolumeCommand
+ // and used later when/if disableSafeMediaVolume() is called.
+ private StreamVolumeCommand mPendingVolumeCommand;
+
+ // mSafeMediaVolumeIndex is the cached value of config_safe_media_volume_index property
+ private int mSafeMediaVolumeIndex;
+ // mSafeUsbMediaVolumeDbfs is the cached value of the config_safe_media_volume_usb_mB
+ // property, divided by 100.0.
+ private float mSafeUsbMediaVolumeDbfs;
+
+ // mSafeUsbMediaVolumeIndex is used for USB Headsets and is the music volume UI index
+ // corresponding to a gain of mSafeUsbMediaVolumeDbfs (defaulting to -37dB) in audio
+ // flinger mixer.
+ // We remove -22 dBs from the theoretical -15dB to account for the EQ + bass boost
+ // amplification when both effects are on with all band gains at maximum.
+ // This level corresponds to a loudness of 85 dB SPL for the warning to be displayed when
+ // the headset is compliant to EN 60950 with a max loudness of 100dB SPL.
+ private int mSafeUsbMediaVolumeIndex;
+ // mSafeMediaVolumeDevices lists the devices for which safe media volume is enforced,
+ private final Set<Integer> mSafeMediaVolumeDevices = new HashSet<>(
+ Arrays.asList(AudioSystem.DEVICE_OUT_WIRED_HEADSET,
+ AudioSystem.DEVICE_OUT_WIRED_HEADPHONE, AudioSystem.DEVICE_OUT_USB_HEADSET));
+
+
+ // mMusicActiveMs is the cumulative time of music activity since safe volume was disabled.
+ // When this time reaches UNSAFE_VOLUME_MUSIC_ACTIVE_MS_MAX, the safe media volume is re-enabled
+ // automatically. mMusicActiveMs is rounded to a multiple of MUSIC_ACTIVE_POLL_PERIOD_MS.
+ private int mMusicActiveMs;
+ private long mLastMusicActiveTimeMs = 0;
+ private PendingIntent mMusicActiveIntent = null;
+ private final AlarmManager mAlarmManager;
+
+ @NonNull private final AudioService mAudioService;
+ @NonNull private final SettingsAdapter mSettings;
+ @NonNull private final AudioHandler mAudioHandler;
+ @NonNull private final ISafeHearingVolumeController mVolumeController;
+
+ private final Context mContext;
+
+ SoundDoseHelper(@NonNull AudioService audioService, Context context,
+ @NonNull AudioHandler audioHandler,
+ @NonNull SettingsAdapter settings,
+ @NonNull ISafeHearingVolumeController volumeController) {
+ mAudioService = audioService;
+ mAudioHandler = audioHandler;
+ mSettings = settings;
+ mVolumeController = volumeController;
+
+ mContext = context;
+
+ mSafeMediaVolumeState = mSettings.getGlobalInt(audioService.getContentResolver(),
+ Settings.Global.AUDIO_SAFE_VOLUME_STATE, 0);
+ if (USE_CSD_FOR_SAFE_HEARING) {
+ mSafeMediaVolumeState = SAFE_MEDIA_VOLUME_DISABLED;
+ }
+
+ // The default safe volume index read here will be replaced by the actual value when
+ // the mcc is read by onConfigureSafeVolume()
+ mSafeMediaVolumeIndex = mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_safe_media_volume_index) * 10;
+
+ mAlarmManager = (AlarmManager) mContext.getSystemService(
+ Context.ALARM_SERVICE);
+ }
+
+ /*package*/ int safeMediaVolumeIndex(int device) {
+ if (!mSafeMediaVolumeDevices.contains(device)) {
+ return MAX_STREAM_VOLUME[AudioSystem.STREAM_MUSIC];
+ }
+ if (device == AudioSystem.DEVICE_OUT_USB_HEADSET) {
+ return mSafeUsbMediaVolumeIndex;
+ } else {
+ return mSafeMediaVolumeIndex;
+ }
+ }
+
+ /*package*/ void restoreMusicActiveMs() {
+ synchronized (mSafeMediaVolumeStateLock) {
+ mMusicActiveMs = MathUtils.constrain(
+ mSettings.getSecureIntForUser(mAudioService.getContentResolver(),
+ Settings.Secure.UNSAFE_VOLUME_MUSIC_ACTIVE_MS, 0,
+ UserHandle.USER_CURRENT),
+ 0, UNSAFE_VOLUME_MUSIC_ACTIVE_MS_MAX);
+ }
+ }
+
+ /*package*/ void enforceSafeMediaVolumeIfActive(String caller) {
+ synchronized (mSafeMediaVolumeStateLock) {
+ if (mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_ACTIVE) {
+ enforceSafeMediaVolume(caller);
+ }
+ }
+ }
+
+ /*package*/ void enforceSafeMediaVolume(String caller) {
+ AudioService.VolumeStreamState streamState = mAudioService.getVssVolumeForStream(
+ AudioSystem.STREAM_MUSIC);
+ Set<Integer> devices = mSafeMediaVolumeDevices;
+
+ for (int device : devices) {
+ int index = streamState.getIndex(device);
+ int safeIndex = safeMediaVolumeIndex(device);
+ if (index > safeIndex) {
+ streamState.setIndex(safeIndex, device, caller, true /*hasModifyAudioSettings*/);
+ mAudioHandler.sendMessageAtTime(
+ mAudioHandler.obtainMessage(MSG_SET_DEVICE_VOLUME, device, /*arg2=*/0,
+ streamState), /*delay=*/0);
+ }
+ }
+ }
+
+ /*package*/ boolean checkSafeMediaVolume(int streamType, int index, int device) {
+ boolean result;
+ synchronized (mSafeMediaVolumeStateLock) {
+ result = checkSafeMediaVolume_l(streamType, index, device);
+ }
+ return result;
+ }
+
+ @GuardedBy("mSafeMediaVolumeStateLock")
+ private boolean checkSafeMediaVolume_l(int streamType, int index, int device) {
+ return (mSafeMediaVolumeState != SAFE_MEDIA_VOLUME_ACTIVE)
+ || (AudioService.mStreamVolumeAlias[streamType] != AudioSystem.STREAM_MUSIC)
+ || (!mSafeMediaVolumeDevices.contains(device))
+ || (index <= safeMediaVolumeIndex(device))
+ || USE_CSD_FOR_SAFE_HEARING;
+ }
+
+ /*package*/ boolean willDisplayWarningAfterCheckVolume(int streamType, int index, int device,
+ int flags) {
+ synchronized (mSafeMediaVolumeStateLock) {
+ if (!checkSafeMediaVolume_l(streamType, index, device)) {
+ mVolumeController.postDisplaySafeVolumeWarning(flags);
+ mPendingVolumeCommand = new StreamVolumeCommand(
+ streamType, index, flags, device);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /*package*/ void disableSafeMediaVolume(String callingPackage) {
+ synchronized (mSafeMediaVolumeStateLock) {
+ final long identity = Binder.clearCallingIdentity();
+ setSafeMediaVolumeEnabled(false, callingPackage);
+ Binder.restoreCallingIdentity(identity);
+
+ if (mPendingVolumeCommand != null) {
+ mAudioService.onSetStreamVolume(mPendingVolumeCommand.mStreamType,
+ mPendingVolumeCommand.mIndex,
+ mPendingVolumeCommand.mFlags,
+ mPendingVolumeCommand.mDevice,
+ callingPackage, true /*hasModifyAudioSettings*/);
+ mPendingVolumeCommand = null;
+ }
+ }
+ }
+
+ /*package*/ void scheduleMusicActiveCheck() {
+ synchronized (mSafeMediaVolumeStateLock) {
+ cancelMusicActiveCheck();
+ if (!USE_CSD_FOR_SAFE_HEARING) {
+ mMusicActiveIntent = PendingIntent.getBroadcast(mContext,
+ REQUEST_CODE_CHECK_MUSIC_ACTIVE,
+ new Intent(ACTION_CHECK_MUSIC_ACTIVE),
+ PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
+ mAlarmManager.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP,
+ SystemClock.elapsedRealtime()
+ + MUSIC_ACTIVE_POLL_PERIOD_MS, mMusicActiveIntent);
+ }
+ }
+ }
+
+ /*package*/ void onCheckMusicActive(String caller, boolean isStreamActive) {
+ synchronized (mSafeMediaVolumeStateLock) {
+ if (mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_INACTIVE) {
+ int device = mAudioService.getDeviceForStream(AudioSystem.STREAM_MUSIC);
+ if (mSafeMediaVolumeDevices.contains(device) && isStreamActive) {
+ scheduleMusicActiveCheck();
+ int index = mAudioService.getVssVolumeForDevice(AudioSystem.STREAM_MUSIC,
+ device);
+ if (index > safeMediaVolumeIndex(device)) {
+ // Approximate cumulative active music time
+ long curTimeMs = SystemClock.elapsedRealtime();
+ if (mLastMusicActiveTimeMs != 0) {
+ mMusicActiveMs += (int) (curTimeMs - mLastMusicActiveTimeMs);
+ }
+ mLastMusicActiveTimeMs = curTimeMs;
+ Log.i(TAG, "onCheckMusicActive() mMusicActiveMs: " + mMusicActiveMs);
+ if (mMusicActiveMs > UNSAFE_VOLUME_MUSIC_ACTIVE_MS_MAX) {
+ setSafeMediaVolumeEnabled(true, caller);
+ mMusicActiveMs = 0;
+ }
+ saveMusicActiveMs();
+ }
+ } else {
+ cancelMusicActiveCheck();
+ mLastMusicActiveTimeMs = 0;
+ }
+ }
+ }
+ }
+
+ /*package*/ void configureSafeMediaVolume(boolean forced, String caller) {
+ int msg = MSG_CONFIGURE_SAFE_MEDIA_VOLUME;
+ mAudioHandler.removeMessages(msg);
+
+ long time = 0;
+ if (forced) {
+ time = (SystemClock.uptimeMillis() + (SystemProperties.getBoolean(
+ "audio.safemedia.bypass", false) ? 0 : SAFE_VOLUME_CONFIGURE_TIMEOUT_MS));
+ }
+ mAudioHandler.sendMessageAtTime(
+ mAudioHandler.obtainMessage(msg, /*arg1=*/forced ? 1 : 0, /*arg2=*/0, caller),
+ time);
+ }
+
+ /*package*/ void initSafeUsbMediaVolumeIndex() {
+ // mSafeUsbMediaVolumeIndex must be initialized after createStreamStates() because it
+ // relies on audio policy having correct ranges for volume indexes.
+ mSafeUsbMediaVolumeIndex = getSafeUsbMediaVolumeIndex();
+ }
+
+ /*package*/ int getSafeMediaVolumeIndex(int device) {
+ if (mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_ACTIVE && mSafeMediaVolumeDevices.contains(
+ device)) {
+ return safeMediaVolumeIndex(device);
+ } else {
+ return -1;
+ }
+ }
+
+ /*package*/ boolean raiseVolumeDisplaySafeMediaVolume(int streamType, int index, int device,
+ int flags) {
+ if (checkSafeMediaVolume(streamType, index, device)) {
+ return false;
+ }
+
+ mVolumeController.postDisplaySafeVolumeWarning(flags);
+ return true;
+ }
+
+ /*package*/ boolean safeDevicesContains(int device) {
+ return mSafeMediaVolumeDevices.contains(device);
+ }
+
+ /*package*/ void invalidatPendingVolumeCommand() {
+ synchronized (mSafeMediaVolumeStateLock) {
+ mPendingVolumeCommand = null;
+ }
+ }
+
+ /*package*/ void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_CONFIGURE_SAFE_MEDIA_VOLUME:
+ onConfigureSafeVolume((msg.arg1 == 1), (String) msg.obj);
+ break;
+ case MSG_PERSIST_SAFE_VOLUME_STATE:
+ onPersistSafeVolumeState(msg.arg1);
+ break;
+ case MSG_PERSIST_MUSIC_ACTIVE_MS:
+ final int musicActiveMs = msg.arg1;
+ mSettings.putSecureIntForUser(mAudioService.getContentResolver(),
+ Settings.Secure.UNSAFE_VOLUME_MUSIC_ACTIVE_MS, musicActiveMs,
+ UserHandle.USER_CURRENT);
+ break;
+ }
+
+ }
+
+ /*package*/ void dump(PrintWriter pw) {
+ pw.print(" mSafeMediaVolumeState=");
+ pw.println(safeMediaVolumeStateToString(mSafeMediaVolumeState));
+ pw.print(" mSafeMediaVolumeIndex="); pw.println(mSafeMediaVolumeIndex);
+ pw.print(" mSafeUsbMediaVolumeIndex="); pw.println(mSafeUsbMediaVolumeIndex);
+ pw.print(" mSafeUsbMediaVolumeDbfs="); pw.println(mSafeUsbMediaVolumeDbfs);
+ pw.print(" mMusicActiveMs="); pw.println(mMusicActiveMs);
+ pw.print(" mMcc="); pw.println(mMcc);
+ pw.print(" mPendingVolumeCommand="); pw.println(mPendingVolumeCommand);
+ }
+
+ private void onConfigureSafeVolume(boolean force, String caller) {
+ synchronized (mSafeMediaVolumeStateLock) {
+ int mcc = mContext.getResources().getConfiguration().mcc;
+ if ((mMcc != mcc) || ((mMcc == 0) && force)) {
+ mSafeMediaVolumeIndex = mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_safe_media_volume_index) * 10;
+
+ mSafeUsbMediaVolumeIndex = getSafeUsbMediaVolumeIndex();
+
+ boolean safeMediaVolumeEnabled =
+ SystemProperties.getBoolean("audio.safemedia.force", false)
+ || mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_safe_media_volume_enabled);
+
+ boolean safeMediaVolumeBypass =
+ SystemProperties.getBoolean("audio.safemedia.bypass", false);
+
+ // The persisted state is either "disabled" or "active": this is the state applied
+ // next time we boot and cannot be "inactive"
+ int persistedState;
+ if (safeMediaVolumeEnabled && !safeMediaVolumeBypass && !USE_CSD_FOR_SAFE_HEARING) {
+ persistedState = SAFE_MEDIA_VOLUME_ACTIVE;
+ // The state can already be "inactive" here if the user has forced it before
+ // the 30 seconds timeout for forced configuration. In this case we don't reset
+ // it to "active".
+ if (mSafeMediaVolumeState != SAFE_MEDIA_VOLUME_INACTIVE) {
+ if (mMusicActiveMs == 0) {
+ mSafeMediaVolumeState = SAFE_MEDIA_VOLUME_ACTIVE;
+ enforceSafeMediaVolume(caller);
+ } else {
+ // We have existing playback time recorded, already confirmed.
+ mSafeMediaVolumeState = SAFE_MEDIA_VOLUME_INACTIVE;
+ mLastMusicActiveTimeMs = 0;
+ }
+ }
+ } else {
+ persistedState = SAFE_MEDIA_VOLUME_DISABLED;
+ mSafeMediaVolumeState = SAFE_MEDIA_VOLUME_DISABLED;
+ }
+ mMcc = mcc;
+ mAudioHandler.sendMessageAtTime(
+ mAudioHandler.obtainMessage(MSG_PERSIST_SAFE_VOLUME_STATE,
+ persistedState, /*arg2=*/0,
+ /*obj=*/null), /*delay=*/0);
+ }
+ }
+ }
+
+ @GuardedBy("mSafeMediaVolumeStateLock")
+ private void setSafeMediaVolumeEnabled(boolean on, String caller) {
+ if ((mSafeMediaVolumeState != SAFE_MEDIA_VOLUME_NOT_CONFIGURED) && (mSafeMediaVolumeState
+ != SAFE_MEDIA_VOLUME_DISABLED)) {
+ if (on && (mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_INACTIVE)) {
+ mSafeMediaVolumeState = SAFE_MEDIA_VOLUME_ACTIVE;
+ enforceSafeMediaVolume(caller);
+ } else if (!on && (mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_ACTIVE)) {
+ mSafeMediaVolumeState = SAFE_MEDIA_VOLUME_INACTIVE;
+ mMusicActiveMs = 1; // nonzero = confirmed
+ mLastMusicActiveTimeMs = 0;
+ saveMusicActiveMs();
+ scheduleMusicActiveCheck();
+ }
+ }
+ }
+
+ @GuardedBy("mSafeMediaVolumeStateLock")
+ private void cancelMusicActiveCheck() {
+ if (mMusicActiveIntent != null) {
+ mAlarmManager.cancel(mMusicActiveIntent);
+ mMusicActiveIntent = null;
+ }
+ }
+
+ @GuardedBy("mSafeMediaVolumeStateLock")
+ private void saveMusicActiveMs() {
+ mAudioHandler.obtainMessage(MSG_PERSIST_MUSIC_ACTIVE_MS, mMusicActiveMs, 0).sendToTarget();
+ }
+
+ private int getSafeUsbMediaVolumeIndex() {
+ // determine UI volume index corresponding to the wanted safe gain in dBFS
+ int min = MIN_STREAM_VOLUME[AudioSystem.STREAM_MUSIC];
+ int max = MAX_STREAM_VOLUME[AudioSystem.STREAM_MUSIC];
+
+ mSafeUsbMediaVolumeDbfs = mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_safe_media_volume_usb_mB) / 100.0f;
+
+ while (Math.abs(max - min) > 1) {
+ int index = (max + min) / 2;
+ float gainDB = AudioSystem.getStreamVolumeDB(
+ AudioSystem.STREAM_MUSIC, index, AudioSystem.DEVICE_OUT_USB_HEADSET);
+ if (Float.isNaN(gainDB)) {
+ //keep last min in case of read error
+ break;
+ } else if (gainDB == mSafeUsbMediaVolumeDbfs) {
+ min = index;
+ break;
+ } else if (gainDB < mSafeUsbMediaVolumeDbfs) {
+ min = index;
+ } else {
+ max = index;
+ }
+ }
+ return min * 10;
+ }
+
+ private void onPersistSafeVolumeState(int state) {
+ mSettings.putGlobalInt(mAudioService.getContentResolver(),
+ Settings.Global.AUDIO_SAFE_VOLUME_STATE,
+ state);
+ }
+
+ private static String safeMediaVolumeStateToString(int state) {
+ switch(state) {
+ case SAFE_MEDIA_VOLUME_NOT_CONFIGURED: return "SAFE_MEDIA_VOLUME_NOT_CONFIGURED";
+ case SAFE_MEDIA_VOLUME_DISABLED: return "SAFE_MEDIA_VOLUME_DISABLED";
+ case SAFE_MEDIA_VOLUME_INACTIVE: return "SAFE_MEDIA_VOLUME_INACTIVE";
+ case SAFE_MEDIA_VOLUME_ACTIVE: return "SAFE_MEDIA_VOLUME_ACTIVE";
+ }
+ return null;
+ }
+
+ // StreamVolumeCommand contains the information needed to defer the process of
+ // setStreamVolume() in case the user has to acknowledge the safe volume warning message.
+ private static class StreamVolumeCommand {
+ public final int mStreamType;
+ public final int mIndex;
+ public final int mFlags;
+ public final int mDevice;
+
+ StreamVolumeCommand(int streamType, int index, int flags, int device) {
+ mStreamType = streamType;
+ mIndex = index;
+ mFlags = flags;
+ mDevice = device;
+ }
+
+ @Override
+ public String toString() {
+ return new StringBuilder().append("{streamType=").append(mStreamType).append(",index=")
+ .append(mIndex).append(",flags=").append(mFlags).append(",device=")
+ .append(mDevice).append('}').toString();
+ }
+ }
+}