Merge "headphone volume limitation" into jb-mr1-dev
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user