Merge "headphone volume limitation" into jb-mr1-dev

This commit is contained in:
Eric Laurent
2012-09-17 08:16:00 -07:00
committed by Android (Google) Code Review
5 changed files with 239 additions and 1 deletions

View File

@@ -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;
}
}

View File

@@ -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>

View File

@@ -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>

View File

@@ -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" />

View File

@@ -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);