summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorEric Laurent <elaurent@google.com>2012-09-17 08:16:00 -0700
committerAndroid (Google) Code Review <android-gerrit@google.com>2012-09-17 08:16:01 -0700
commit39a37c3bb3d21c119cca536f85c298db805f86cd (patch)
treee8b04d981ae3fecbf73fca32e5f5a3c0cd78ef90
parent89c82814dbdfd807b1611a8379be0421cd2f512d (diff)
parentc34dcc1e1ebf152bb400abbb8bc25f7dc0c3ba97 (diff)
downloadframeworks_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.java61
-rwxr-xr-xcore/res/res/values/config.xml6
-rwxr-xr-xcore/res/res/values/strings.xml6
-rw-r--r--core/res/res/values/symbols.xml2
-rw-r--r--media/java/android/media/AudioService.java165
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"> &lt; </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);