summaryrefslogtreecommitdiffstats
path: root/media
diff options
context:
space:
mode:
authorEric Laurent <elaurent@google.com>2012-09-10 13:51:52 -0700
committerEric Laurent <elaurent@google.com>2012-09-16 17:44:45 -0700
commitc34dcc1e1ebf152bb400abbb8bc25f7dc0c3ba97 (patch)
tree04880091b91aefcb7517173d3fd28a9b7af6da3d /media
parentb45965f519d41f138f0d0edfb587e4861e2199f4 (diff)
downloadframeworks_base-c34dcc1e1ebf152bb400abbb8bc25f7dc0c3ba97.zip
frameworks_base-c34dcc1e1ebf152bb400abbb8bc25f7dc0c3ba97.tar.gz
frameworks_base-c34dcc1e1ebf152bb400abbb8bc25f7dc0c3ba97.tar.bz2
headphone volume limitation
Limit music volume when headphones or headset are inserted. Display warning message when user wants to increase the volume above a platform specific volume and request user acknowledgement before proceeding. TODO: exact wording of the warning message must be defined by UX. Change-Id: I00f429f602534c6d8783126b929371c4d432e6e2
Diffstat (limited to 'media')
-rw-r--r--media/java/android/media/AudioService.java165
1 files changed, 164 insertions, 1 deletions
diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java
index e5c2a4d..4459d03 100644
--- a/media/java/android/media/AudioService.java
+++ b/media/java/android/media/AudioService.java
@@ -153,6 +153,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished {
// end of messages handled under wakelock
private static final int MSG_SET_RSX_CONNECTION_STATE = 23; // change remote submix connection
private static final int MSG_SET_FORCE_RSX_USE = 24; // force remote submix audio routing
+ private static final int MSG_CHECK_MUSIC_ACTIVE = 25;
// flags for MSG_PERSIST_VOLUME indicating if current and/or last audible volume should be
// persisted
@@ -430,6 +431,8 @@ public class AudioService extends IAudioService.Stub implements OnFinished {
mContentResolver = context.getContentResolver();
mVoiceCapable = mContext.getResources().getBoolean(
com.android.internal.R.bool.config_voice_capable);
+ mSafeMediaVolumeIndex = mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_safe_media_volume_index) * 10;
PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
mMediaEventWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "handleMediaEvent");
@@ -454,6 +457,10 @@ public class AudioService extends IAudioService.Stub implements OnFinished {
updateStreamVolumeAlias(false /*updateVolumes*/);
createStreamStates();
+ synchronized (mSafeMediaVolumeEnabled) {
+ enforceSafeMediaVolume();
+ }
+
mMediaServerOk = true;
// Call setRingerModeInt() to apply correct mute
@@ -738,6 +745,11 @@ public class AudioService extends IAudioService.Stub implements OnFinished {
// convert one UI step (+/-1) into a number of internal units on the stream alias
int step = rescaleIndex(10, streamType, streamTypeAlias);
+ if ((direction == AudioManager.ADJUST_RAISE) &&
+ !checkSafeMediaVolume(streamTypeAlias, aliasIndex + step, device)) {
+ return;
+ }
+
// If either the client forces allowing ringer modes for this adjustment,
// or the stream type is one that is affected by ringer modes
if (((flags & AudioManager.FLAG_ALLOW_RINGER_MODES) != 0) ||
@@ -815,12 +827,17 @@ public class AudioService extends IAudioService.Stub implements OnFinished {
VolumeStreamState streamState = mStreamStates[mStreamVolumeAlias[streamType]];
final int device = getDeviceForStream(streamType);
+
// get last audible index if stream is muted, current index otherwise
final int oldIndex = streamState.getIndex(device,
(streamState.muteCount() != 0) /* lastAudible */);
index = rescaleIndex(index * 10, streamType, mStreamVolumeAlias[streamType]);
+ if (!checkSafeMediaVolume(mStreamVolumeAlias[streamType], index, device)) {
+ return;
+ }
+
// setting volume on master stream type also controls silent mode
if (((flags & AudioManager.FLAG_ALLOW_RINGER_MODES) != 0) ||
(mStreamVolumeAlias[streamType] == getMasterStreamType())) {
@@ -1681,6 +1698,10 @@ public class AudioService extends IAudioService.Stub implements OnFinished {
checkAllAliasStreamVolumes();
+ synchronized (mSafeMediaVolumeEnabled) {
+ enforceSafeMediaVolume();
+ }
+
// apply new ringer mode
setRingerModeInt(getRingerMode(), false);
}
@@ -2138,6 +2159,33 @@ public class AudioService extends IAudioService.Stub implements OnFinished {
String.valueOf(address) /*device_address*/);
}
+ private void onCheckMusicActive() {
+ synchronized (mSafeMediaVolumeEnabled) {
+ if (!mSafeMediaVolumeEnabled) {
+ int device = getDeviceForStream(AudioSystem.STREAM_MUSIC);
+
+ if ((device & mSafeMediaVolumeDevices) != 0) {
+ sendMsg(mAudioHandler,
+ MSG_CHECK_MUSIC_ACTIVE,
+ SENDMSG_REPLACE,
+ device,
+ 0,
+ null,
+ MUSIC_ACTIVE_POLL_PERIOD_MS);
+ if (AudioSystem.isStreamActive(AudioSystem.STREAM_MUSIC, 0)) {
+ // Approximate cumulative active music time
+ mMusicActiveMs += MUSIC_ACTIVE_POLL_PERIOD_MS;
+ if (mMusicActiveMs > UNSAFE_VOLUME_MUSIC_ACTIVE_MS_MAX) {
+ setSafeMediaVolumeEnabled(true);
+ mMusicActiveMs = 0;
+ mVolumePanel.postDisplaySafeVolumeWarning();
+ }
+ }
+ }
+ }
+ }
+ }
+
///////////////////////////////////////////////////////////////////////////
// Internal methods
///////////////////////////////////////////////////////////////////////////
@@ -2397,6 +2445,14 @@ public class AudioService extends IAudioService.Stub implements OnFinished {
public void setWiredDeviceConnectionState(int device, int state, String name) {
synchronized (mConnectedDevices) {
int delay = checkSendBecomingNoisyIntent(device, state);
+ if ((device & mSafeMediaVolumeDevices) != 0) {
+ setSafeMediaVolumeEnabled(state != 0);
+ // insert delay to allow new volume to apply before switching to headphones
+ if ((delay < SAFE_VOLUME_DELAY_MS) && (state != 0)) {
+ delay = SAFE_VOLUME_DELAY_MS;
+ }
+ }
+
queueMsgUnderWakeLock(mAudioHandler,
MSG_SET_WIRED_DEVICE_CONNECTION_STATE,
device,
@@ -3168,6 +3224,10 @@ public class AudioService extends IAudioService.Stub implements OnFinished {
case MSG_SET_RSX_CONNECTION_STATE:
onSetRsxConnectionState(msg.arg1/*available*/, msg.arg2/*address*/);
break;
+
+ case MSG_CHECK_MUSIC_ACTIVE:
+ onCheckMusicActive();
+ break;
}
}
}
@@ -4426,7 +4486,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished {
" -- vol: " + rcse.mPlaybackVolume +
" -- volMax: " + rcse.mPlaybackVolumeMax +
" -- volObs: " + rcse.mRemoteVolumeObs);
-
+
}
}
synchronized (mMainRemote) {
@@ -5415,6 +5475,109 @@ public class AudioService extends IAudioService.Stub implements OnFinished {
}
}
+
+ //==========================================================================================
+ // 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.
+ //==========================================================================================
+
+ // mSafeMediaVolumeEnabled indicates whether the media volume is limited over headphones.
+ // It is true by default when headphones or a headset are inserted and can be overriden by
+ // calling AudioService.disableSafeMediaVolume() (when user opts out).
+ private Boolean mSafeMediaVolumeEnabled = new Boolean(false);
+ // mSafeMediaVolumeIndex is the cached value of config_safe_media_volume_index property
+ private final int mSafeMediaVolumeIndex;
+ // mSafeMediaVolumeDevices lists the devices for which safe media volume is enforced,
+ private final int mSafeMediaVolumeDevices = AudioSystem.DEVICE_OUT_WIRED_HEADSET |
+ AudioSystem.DEVICE_OUT_WIRED_HEADPHONE;
+ // 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 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_DELAY_MS = 500; // 500ms before switching to headphones
+
+ private void setSafeMediaVolumeEnabled(boolean on) {
+ synchronized (mSafeMediaVolumeEnabled) {
+ if (on && !mSafeMediaVolumeEnabled) {
+ enforceSafeMediaVolume();
+ } else if (!on && mSafeMediaVolumeEnabled) {
+ mMusicActiveMs = 0;
+ sendMsg(mAudioHandler,
+ MSG_CHECK_MUSIC_ACTIVE,
+ SENDMSG_REPLACE,
+ 0,
+ 0,
+ null,
+ MUSIC_ACTIVE_POLL_PERIOD_MS);
+ }
+ mSafeMediaVolumeEnabled = on;
+ }
+ }
+
+ private void enforceSafeMediaVolume() {
+ VolumeStreamState streamState = mStreamStates[AudioSystem.STREAM_MUSIC];
+ boolean lastAudible = (streamState.muteCount() != 0);
+ int devices = mSafeMediaVolumeDevices;
+ int i = 0;
+
+ while (devices != 0) {
+ int device = 1 << i++;
+ if ((device & devices) == 0) {
+ continue;
+ }
+ int index = streamState.getIndex(device, lastAudible);
+ if (index > mSafeMediaVolumeIndex) {
+ if (lastAudible) {
+ streamState.setLastAudibleIndex(mSafeMediaVolumeIndex, device);
+ sendMsg(mAudioHandler,
+ MSG_PERSIST_VOLUME,
+ SENDMSG_QUEUE,
+ PERSIST_LAST_AUDIBLE,
+ device,
+ streamState,
+ PERSIST_DELAY);
+ } else {
+ streamState.setIndex(mSafeMediaVolumeIndex, device, true);
+ sendMsg(mAudioHandler,
+ MSG_SET_DEVICE_VOLUME,
+ SENDMSG_QUEUE,
+ device,
+ 0,
+ streamState,
+ 0);
+ }
+ }
+ devices &= ~device;
+ }
+ }
+
+ private boolean checkSafeMediaVolume(int streamType, int index, int device) {
+ synchronized (mSafeMediaVolumeEnabled) {
+ if (mSafeMediaVolumeEnabled &&
+ (mStreamVolumeAlias[streamType] == AudioSystem.STREAM_MUSIC) &&
+ ((device & mSafeMediaVolumeDevices) != 0) &&
+ (index > mSafeMediaVolumeIndex)) {
+ mVolumePanel.postDisplaySafeVolumeWarning();
+ return false;
+ }
+ return true;
+ }
+ }
+
+ public void disableSafeMediaVolume() {
+ synchronized (mSafeMediaVolumeEnabled) {
+ setSafeMediaVolumeEnabled(false);
+ }
+ }
+
+
@Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG);