diff options
author | Eric Laurent <elaurent@google.com> | 2012-09-10 13:51:52 -0700 |
---|---|---|
committer | Eric Laurent <elaurent@google.com> | 2012-09-16 17:44:45 -0700 |
commit | c34dcc1e1ebf152bb400abbb8bc25f7dc0c3ba97 (patch) | |
tree | 04880091b91aefcb7517173d3fd28a9b7af6da3d /media/java | |
parent | b45965f519d41f138f0d0edfb587e4861e2199f4 (diff) | |
download | frameworks_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/java')
-rw-r--r-- | media/java/android/media/AudioService.java | 165 |
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); |