diff options
author | Eric Laurent <elaurent@google.com> | 2012-09-17 08:16:00 -0700 |
---|---|---|
committer | Android (Google) Code Review <android-gerrit@google.com> | 2012-09-17 08:16:01 -0700 |
commit | 39a37c3bb3d21c119cca536f85c298db805f86cd (patch) | |
tree | e8b04d981ae3fecbf73fca32e5f5a3c0cd78ef90 | |
parent | 89c82814dbdfd807b1611a8379be0421cd2f512d (diff) | |
parent | c34dcc1e1ebf152bb400abbb8bc25f7dc0c3ba97 (diff) | |
download | frameworks_base-39a37c3bb3d21c119cca536f85c298db805f86cd.zip frameworks_base-39a37c3bb3d21c119cca536f85c298db805f86cd.tar.gz frameworks_base-39a37c3bb3d21c119cca536f85c298db805f86cd.tar.bz2 |
Merge "headphone volume limitation" into jb-mr1-dev
-rw-r--r-- | core/java/android/view/VolumePanel.java | 61 | ||||
-rwxr-xr-x | core/res/res/values/config.xml | 6 | ||||
-rwxr-xr-x | core/res/res/values/strings.xml | 6 | ||||
-rw-r--r-- | core/res/res/values/symbols.xml | 2 | ||||
-rw-r--r-- | media/java/android/media/AudioService.java | 165 |
5 files changed, 239 insertions, 1 deletions
diff --git a/core/java/android/view/VolumePanel.java b/core/java/android/view/VolumePanel.java index 4d4e985c..c93da06 100644 --- a/core/java/android/view/VolumePanel.java +++ b/core/java/android/view/VolumePanel.java @@ -18,6 +18,7 @@ package android.view; import com.android.internal.R; +import android.app.AlertDialog; import android.app.Dialog; import android.content.DialogInterface.OnDismissListener; import android.content.BroadcastReceiver; @@ -92,6 +93,7 @@ public class VolumePanel extends Handler implements OnSeekBarChangeListener, Vie private static final int MSG_REMOTE_VOLUME_CHANGED = 8; private static final int MSG_REMOTE_VOLUME_UPDATE_IF_SHOWN = 9; private static final int MSG_SLIDER_VISIBILITY_CHANGED = 10; + private static final int MSG_DISPLAY_SAFE_VOLUME_WARNING = 11; // Pseudo stream type for master volume private static final int STREAM_MASTER = -100; @@ -211,6 +213,31 @@ public class VolumePanel extends Handler implements OnSeekBarChangeListener, Vie private ToneGenerator mToneGenerators[]; private Vibrator mVibrator; + private static AlertDialog sConfirmSafeVolumeDialog; + + private static class WarningDialogReceiver extends BroadcastReceiver + implements DialogInterface.OnDismissListener { + private Context mContext; + private Dialog mDialog; + + WarningDialogReceiver(Context context, Dialog dialog) { + mContext = context; + mDialog = dialog; + IntentFilter filter = new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); + context.registerReceiver(this, filter); + } + + @Override + public void onReceive(Context context, Intent intent) { + mDialog.cancel(); + } + + public void onDismiss(DialogInterface unused) { + mContext.unregisterReceiver(this); + } + } + + public VolumePanel(final Context context, AudioService volumeService) { mContext = context; mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); @@ -528,6 +555,10 @@ public class VolumePanel extends Handler implements OnSeekBarChangeListener, Vie postMuteChanged(STREAM_MASTER, flags); } + public void postDisplaySafeVolumeWarning() { + obtainMessage(MSG_DISPLAY_SAFE_VOLUME_WARNING, 0, 0).sendToTarget(); + } + /** * Override this if you have other work to do when the volume changes (for * example, vibrating, playing a sound, etc.). Make sure to call through to @@ -796,6 +827,32 @@ public class VolumePanel extends Handler implements OnSeekBarChangeListener, Vie } } + protected void onDisplaySafeVolumeWarning() { + if (sConfirmSafeVolumeDialog != null) { + sConfirmSafeVolumeDialog.dismiss(); + } + sConfirmSafeVolumeDialog = new AlertDialog.Builder(mContext) + .setTitle(android.R.string.dialog_alert_title) + .setMessage(com.android.internal.R.string.safe_media_volume_warning) + .setPositiveButton(com.android.internal.R.string.yes, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + mAudioService.disableSafeMediaVolume(); + } + }) + .setNegativeButton(com.android.internal.R.string.no, null) + .setIconAttribute(android.R.attr.alertDialogIcon) + .create(); + + final WarningDialogReceiver warning = new WarningDialogReceiver(mContext, + sConfirmSafeVolumeDialog); + + sConfirmSafeVolumeDialog.setOnDismissListener(warning); + sConfirmSafeVolumeDialog.getWindow().setType( + WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); + sConfirmSafeVolumeDialog.show(); + } + /** * Lock on this VolumePanel instance as long as you use the returned ToneGenerator. */ @@ -910,6 +967,10 @@ public class VolumePanel extends Handler implements OnSeekBarChangeListener, Vie case MSG_SLIDER_VISIBILITY_CHANGED: onSliderVisibilityChanged(msg.arg1, msg.arg2); break; + + case MSG_DISPLAY_SAFE_VOLUME_WARNING: + onDisplaySafeVolumeWarning(); + break; } } diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 93d7fcc..1a9a0a9 100755 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -937,4 +937,10 @@ larger than the minimum reported touchMajor/touchMinor values reported by the hardware. --> <dimen name="config_minScalingSpan">25mm</dimen> + + <!-- Safe headphone volume index. When music stream volume is below this index + the SPL on headphone output is compliant to EN 60950 requirements for portable music + players. --> + <integer name="config_safe_media_volume_index">10</integer> + </resources> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 73a61b6..56e5ce1 100755 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -3887,6 +3887,12 @@ Try again in <xliff:g id="number">%d</xliff:g> seconds. </string> + <!-- Message shown in dialog when user is attempting to set the music volume above the + recommended maximum level for headphones --> + <string name="safe_media_volume_warning" product="default"> + "Raise volume above the recommended level?" + </string> + <string name="kg_temp_back_string"> < </string> <!-- TODO: remove this --> </resources> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 2bfb06d..eb56d08 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -283,6 +283,7 @@ <java-symbol type="integer" name="config_soundEffectVolumeDb" /> <java-symbol type="integer" name="config_lockSoundVolumeDb" /> <java-symbol type="integer" name="config_multiuserMaximumUsers" /> + <java-symbol type="integer" name="config_safe_media_volume_index" /> <java-symbol type="color" name="tab_indicator_text_v4" /> @@ -814,6 +815,7 @@ <java-symbol type="string" name="default_audio_route_name_dock_speakers" /> <java-symbol type="string" name="default_audio_route_name_hdmi" /> <java-symbol type="string" name="default_audio_route_category_name" /> + <java-symbol type="string" name="safe_media_volume_warning" /> <java-symbol type="plurals" name="abbrev_in_num_days" /> <java-symbol type="plurals" name="abbrev_in_num_hours" /> 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); |