Skip to content

Commit 39a37c3

Browse files
Eric LaurentAndroid (Google) Code Review
authored andcommitted
Merge "headphone volume limitation" into jb-mr1-dev
2 parents 89c8281 + c34dcc1 commit 39a37c3

File tree

5 files changed

+239
-1
lines changed

5 files changed

+239
-1
lines changed

core/java/android/view/VolumePanel.java

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import com.android.internal.R;
2020

21+
import android.app.AlertDialog;
2122
import android.app.Dialog;
2223
import android.content.DialogInterface.OnDismissListener;
2324
import android.content.BroadcastReceiver;
@@ -92,6 +93,7 @@ public class VolumePanel extends Handler implements OnSeekBarChangeListener, Vie
9293
private static final int MSG_REMOTE_VOLUME_CHANGED = 8;
9394
private static final int MSG_REMOTE_VOLUME_UPDATE_IF_SHOWN = 9;
9495
private static final int MSG_SLIDER_VISIBILITY_CHANGED = 10;
96+
private static final int MSG_DISPLAY_SAFE_VOLUME_WARNING = 11;
9597

9698
// Pseudo stream type for master volume
9799
private static final int STREAM_MASTER = -100;
@@ -211,6 +213,31 @@ private class StreamControl {
211213
private ToneGenerator mToneGenerators[];
212214
private Vibrator mVibrator;
213215

216+
private static AlertDialog sConfirmSafeVolumeDialog;
217+
218+
private static class WarningDialogReceiver extends BroadcastReceiver
219+
implements DialogInterface.OnDismissListener {
220+
private Context mContext;
221+
private Dialog mDialog;
222+
223+
WarningDialogReceiver(Context context, Dialog dialog) {
224+
mContext = context;
225+
mDialog = dialog;
226+
IntentFilter filter = new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
227+
context.registerReceiver(this, filter);
228+
}
229+
230+
@Override
231+
public void onReceive(Context context, Intent intent) {
232+
mDialog.cancel();
233+
}
234+
235+
public void onDismiss(DialogInterface unused) {
236+
mContext.unregisterReceiver(this);
237+
}
238+
}
239+
240+
214241
public VolumePanel(final Context context, AudioService volumeService) {
215242
mContext = context;
216243
mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
@@ -528,6 +555,10 @@ public void postMasterMuteChanged(int flags) {
528555
postMuteChanged(STREAM_MASTER, flags);
529556
}
530557

558+
public void postDisplaySafeVolumeWarning() {
559+
obtainMessage(MSG_DISPLAY_SAFE_VOLUME_WARNING, 0, 0).sendToTarget();
560+
}
561+
531562
/**
532563
* Override this if you have other work to do when the volume changes (for
533564
* example, vibrating, playing a sound, etc.). Make sure to call through to
@@ -796,6 +827,32 @@ synchronized protected void onSliderVisibilityChanged(int streamType, int visibl
796827
}
797828
}
798829

830+
protected void onDisplaySafeVolumeWarning() {
831+
if (sConfirmSafeVolumeDialog != null) {
832+
sConfirmSafeVolumeDialog.dismiss();
833+
}
834+
sConfirmSafeVolumeDialog = new AlertDialog.Builder(mContext)
835+
.setTitle(android.R.string.dialog_alert_title)
836+
.setMessage(com.android.internal.R.string.safe_media_volume_warning)
837+
.setPositiveButton(com.android.internal.R.string.yes,
838+
new DialogInterface.OnClickListener() {
839+
public void onClick(DialogInterface dialog, int which) {
840+
mAudioService.disableSafeMediaVolume();
841+
}
842+
})
843+
.setNegativeButton(com.android.internal.R.string.no, null)
844+
.setIconAttribute(android.R.attr.alertDialogIcon)
845+
.create();
846+
847+
final WarningDialogReceiver warning = new WarningDialogReceiver(mContext,
848+
sConfirmSafeVolumeDialog);
849+
850+
sConfirmSafeVolumeDialog.setOnDismissListener(warning);
851+
sConfirmSafeVolumeDialog.getWindow().setType(
852+
WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
853+
sConfirmSafeVolumeDialog.show();
854+
}
855+
799856
/**
800857
* Lock on this VolumePanel instance as long as you use the returned ToneGenerator.
801858
*/
@@ -910,6 +967,10 @@ public void handleMessage(Message msg) {
910967
case MSG_SLIDER_VISIBILITY_CHANGED:
911968
onSliderVisibilityChanged(msg.arg1, msg.arg2);
912969
break;
970+
971+
case MSG_DISPLAY_SAFE_VOLUME_WARNING:
972+
onDisplaySafeVolumeWarning();
973+
break;
913974
}
914975
}
915976

core/res/res/values/config.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -937,4 +937,10 @@
937937
larger than the minimum reported touchMajor/touchMinor values
938938
reported by the hardware. -->
939939
<dimen name="config_minScalingSpan">25mm</dimen>
940+
941+
<!-- Safe headphone volume index. When music stream volume is below this index
942+
the SPL on headphone output is compliant to EN 60950 requirements for portable music
943+
players. -->
944+
<integer name="config_safe_media_volume_index">10</integer>
945+
940946
</resources>

core/res/res/values/strings.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3887,6 +3887,12 @@
38873887
Try again in <xliff:g id="number">%d</xliff:g> seconds.
38883888
</string>
38893889

3890+
<!-- Message shown in dialog when user is attempting to set the music volume above the
3891+
recommended maximum level for headphones -->
3892+
<string name="safe_media_volume_warning" product="default">
3893+
"Raise volume above the recommended level?"
3894+
</string>
3895+
38903896
<string name="kg_temp_back_string"> &lt; </string> <!-- TODO: remove this -->
38913897

38923898
</resources>

core/res/res/values/symbols.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,7 @@
283283
<java-symbol type="integer" name="config_soundEffectVolumeDb" />
284284
<java-symbol type="integer" name="config_lockSoundVolumeDb" />
285285
<java-symbol type="integer" name="config_multiuserMaximumUsers" />
286+
<java-symbol type="integer" name="config_safe_media_volume_index" />
286287

287288
<java-symbol type="color" name="tab_indicator_text_v4" />
288289

@@ -814,6 +815,7 @@
814815
<java-symbol type="string" name="default_audio_route_name_dock_speakers" />
815816
<java-symbol type="string" name="default_audio_route_name_hdmi" />
816817
<java-symbol type="string" name="default_audio_route_category_name" />
818+
<java-symbol type="string" name="safe_media_volume_warning" />
817819

818820
<java-symbol type="plurals" name="abbrev_in_num_days" />
819821
<java-symbol type="plurals" name="abbrev_in_num_hours" />

media/java/android/media/AudioService.java

Lines changed: 164 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished {
153153
// end of messages handled under wakelock
154154
private static final int MSG_SET_RSX_CONNECTION_STATE = 23; // change remote submix connection
155155
private static final int MSG_SET_FORCE_RSX_USE = 24; // force remote submix audio routing
156+
private static final int MSG_CHECK_MUSIC_ACTIVE = 25;
156157

157158
// flags for MSG_PERSIST_VOLUME indicating if current and/or last audible volume should be
158159
// persisted
@@ -430,6 +431,8 @@ public AudioService(Context context) {
430431
mContentResolver = context.getContentResolver();
431432
mVoiceCapable = mContext.getResources().getBoolean(
432433
com.android.internal.R.bool.config_voice_capable);
434+
mSafeMediaVolumeIndex = mContext.getResources().getInteger(
435+
com.android.internal.R.integer.config_safe_media_volume_index) * 10;
433436

434437
PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
435438
mMediaEventWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "handleMediaEvent");
@@ -454,6 +457,10 @@ public AudioService(Context context) {
454457
updateStreamVolumeAlias(false /*updateVolumes*/);
455458
createStreamStates();
456459

460+
synchronized (mSafeMediaVolumeEnabled) {
461+
enforceSafeMediaVolume();
462+
}
463+
457464
mMediaServerOk = true;
458465

459466
// Call setRingerModeInt() to apply correct mute
@@ -738,6 +745,11 @@ public void adjustStreamVolume(int streamType, int direction, int flags) {
738745
// convert one UI step (+/-1) into a number of internal units on the stream alias
739746
int step = rescaleIndex(10, streamType, streamTypeAlias);
740747

748+
if ((direction == AudioManager.ADJUST_RAISE) &&
749+
!checkSafeMediaVolume(streamTypeAlias, aliasIndex + step, device)) {
750+
return;
751+
}
752+
741753
// If either the client forces allowing ringer modes for this adjustment,
742754
// or the stream type is one that is affected by ringer modes
743755
if (((flags & AudioManager.FLAG_ALLOW_RINGER_MODES) != 0) ||
@@ -815,12 +827,17 @@ public void setStreamVolume(int streamType, int index, int flags) {
815827
VolumeStreamState streamState = mStreamStates[mStreamVolumeAlias[streamType]];
816828

817829
final int device = getDeviceForStream(streamType);
830+
818831
// get last audible index if stream is muted, current index otherwise
819832
final int oldIndex = streamState.getIndex(device,
820833
(streamState.muteCount() != 0) /* lastAudible */);
821834

822835
index = rescaleIndex(index * 10, streamType, mStreamVolumeAlias[streamType]);
823836

837+
if (!checkSafeMediaVolume(mStreamVolumeAlias[streamType], index, device)) {
838+
return;
839+
}
840+
824841
// setting volume on master stream type also controls silent mode
825842
if (((flags & AudioManager.FLAG_ALLOW_RINGER_MODES) != 0) ||
826843
(mStreamVolumeAlias[streamType] == getMasterStreamType())) {
@@ -1681,6 +1698,10 @@ public void reloadAudioSettings() {
16811698

16821699
checkAllAliasStreamVolumes();
16831700

1701+
synchronized (mSafeMediaVolumeEnabled) {
1702+
enforceSafeMediaVolume();
1703+
}
1704+
16841705
// apply new ringer mode
16851706
setRingerModeInt(getRingerMode(), false);
16861707
}
@@ -2138,6 +2159,33 @@ private void onSetRsxConnectionState(int available, int address) {
21382159
String.valueOf(address) /*device_address*/);
21392160
}
21402161

2162+
private void onCheckMusicActive() {
2163+
synchronized (mSafeMediaVolumeEnabled) {
2164+
if (!mSafeMediaVolumeEnabled) {
2165+
int device = getDeviceForStream(AudioSystem.STREAM_MUSIC);
2166+
2167+
if ((device & mSafeMediaVolumeDevices) != 0) {
2168+
sendMsg(mAudioHandler,
2169+
MSG_CHECK_MUSIC_ACTIVE,
2170+
SENDMSG_REPLACE,
2171+
device,
2172+
0,
2173+
null,
2174+
MUSIC_ACTIVE_POLL_PERIOD_MS);
2175+
if (AudioSystem.isStreamActive(AudioSystem.STREAM_MUSIC, 0)) {
2176+
// Approximate cumulative active music time
2177+
mMusicActiveMs += MUSIC_ACTIVE_POLL_PERIOD_MS;
2178+
if (mMusicActiveMs > UNSAFE_VOLUME_MUSIC_ACTIVE_MS_MAX) {
2179+
setSafeMediaVolumeEnabled(true);
2180+
mMusicActiveMs = 0;
2181+
mVolumePanel.postDisplaySafeVolumeWarning();
2182+
}
2183+
}
2184+
}
2185+
}
2186+
}
2187+
}
2188+
21412189
///////////////////////////////////////////////////////////////////////////
21422190
// Internal methods
21432191
///////////////////////////////////////////////////////////////////////////
@@ -2397,6 +2445,14 @@ private int getDeviceForStream(int stream) {
23972445
public void setWiredDeviceConnectionState(int device, int state, String name) {
23982446
synchronized (mConnectedDevices) {
23992447
int delay = checkSendBecomingNoisyIntent(device, state);
2448+
if ((device & mSafeMediaVolumeDevices) != 0) {
2449+
setSafeMediaVolumeEnabled(state != 0);
2450+
// insert delay to allow new volume to apply before switching to headphones
2451+
if ((delay < SAFE_VOLUME_DELAY_MS) && (state != 0)) {
2452+
delay = SAFE_VOLUME_DELAY_MS;
2453+
}
2454+
}
2455+
24002456
queueMsgUnderWakeLock(mAudioHandler,
24012457
MSG_SET_WIRED_DEVICE_CONNECTION_STATE,
24022458
device,
@@ -3168,6 +3224,10 @@ public void handleMessage(Message msg) {
31683224
case MSG_SET_RSX_CONNECTION_STATE:
31693225
onSetRsxConnectionState(msg.arg1/*available*/, msg.arg2/*address*/);
31703226
break;
3227+
3228+
case MSG_CHECK_MUSIC_ACTIVE:
3229+
onCheckMusicActive();
3230+
break;
31713231
}
31723232
}
31733233
}
@@ -4426,7 +4486,7 @@ private void dumpRCCStack(PrintWriter pw) {
44264486
" -- vol: " + rcse.mPlaybackVolume +
44274487
" -- volMax: " + rcse.mPlaybackVolumeMax +
44284488
" -- volObs: " + rcse.mRemoteVolumeObs);
4429-
4489+
44304490
}
44314491
}
44324492
synchronized (mMainRemote) {
@@ -5415,6 +5475,109 @@ public AudioRoutesInfo startWatchingRoutes(IAudioRoutesObserver observer) {
54155475
}
54165476
}
54175477

5478+
5479+
//==========================================================================================
5480+
// Safe media volume management.
5481+
// MUSIC stream volume level is limited when headphones are connected according to safety
5482+
// regulation. When the user attempts to raise the volume above the limit, a warning is
5483+
// displayed and the user has to acknowlegde before the volume is actually changed.
5484+
// The volume index corresponding to the limit is stored in config_safe_media_volume_index
5485+
// property. Platforms with a different limit must set this property accordingly in their
5486+
// overlay.
5487+
//==========================================================================================
5488+
5489+
// mSafeMediaVolumeEnabled indicates whether the media volume is limited over headphones.
5490+
// It is true by default when headphones or a headset are inserted and can be overriden by
5491+
// calling AudioService.disableSafeMediaVolume() (when user opts out).
5492+
private Boolean mSafeMediaVolumeEnabled = new Boolean(false);
5493+
// mSafeMediaVolumeIndex is the cached value of config_safe_media_volume_index property
5494+
private final int mSafeMediaVolumeIndex;
5495+
// mSafeMediaVolumeDevices lists the devices for which safe media volume is enforced,
5496+
private final int mSafeMediaVolumeDevices = AudioSystem.DEVICE_OUT_WIRED_HEADSET |
5497+
AudioSystem.DEVICE_OUT_WIRED_HEADPHONE;
5498+
// mMusicActiveMs is the cumulative time of music activity since safe volume was disabled.
5499+
// When this time reaches UNSAFE_VOLUME_MUSIC_ACTIVE_MS_MAX, the safe media volume is re-enabled
5500+
// automatically. mMusicActiveMs is rounded to a multiple of MUSIC_ACTIVE_POLL_PERIOD_MS.
5501+
private int mMusicActiveMs;
5502+
private static final int UNSAFE_VOLUME_MUSIC_ACTIVE_MS_MAX = (20 * 3600 * 1000); // 20 hours
5503+
private static final int MUSIC_ACTIVE_POLL_PERIOD_MS = 60000; // 1 minute polling interval
5504+
private static final int SAFE_VOLUME_DELAY_MS = 500; // 500ms before switching to headphones
5505+
5506+
private void setSafeMediaVolumeEnabled(boolean on) {
5507+
synchronized (mSafeMediaVolumeEnabled) {
5508+
if (on && !mSafeMediaVolumeEnabled) {
5509+
enforceSafeMediaVolume();
5510+
} else if (!on && mSafeMediaVolumeEnabled) {
5511+
mMusicActiveMs = 0;
5512+
sendMsg(mAudioHandler,
5513+
MSG_CHECK_MUSIC_ACTIVE,
5514+
SENDMSG_REPLACE,
5515+
0,
5516+
0,
5517+
null,
5518+
MUSIC_ACTIVE_POLL_PERIOD_MS);
5519+
}
5520+
mSafeMediaVolumeEnabled = on;
5521+
}
5522+
}
5523+
5524+
private void enforceSafeMediaVolume() {
5525+
VolumeStreamState streamState = mStreamStates[AudioSystem.STREAM_MUSIC];
5526+
boolean lastAudible = (streamState.muteCount() != 0);
5527+
int devices = mSafeMediaVolumeDevices;
5528+
int i = 0;
5529+
5530+
while (devices != 0) {
5531+
int device = 1 << i++;
5532+
if ((device & devices) == 0) {
5533+
continue;
5534+
}
5535+
int index = streamState.getIndex(device, lastAudible);
5536+
if (index > mSafeMediaVolumeIndex) {
5537+
if (lastAudible) {
5538+
streamState.setLastAudibleIndex(mSafeMediaVolumeIndex, device);
5539+
sendMsg(mAudioHandler,
5540+
MSG_PERSIST_VOLUME,
5541+
SENDMSG_QUEUE,
5542+
PERSIST_LAST_AUDIBLE,
5543+
device,
5544+
streamState,
5545+
PERSIST_DELAY);
5546+
} else {
5547+
streamState.setIndex(mSafeMediaVolumeIndex, device, true);
5548+
sendMsg(mAudioHandler,
5549+
MSG_SET_DEVICE_VOLUME,
5550+
SENDMSG_QUEUE,
5551+
device,
5552+
0,
5553+
streamState,
5554+
0);
5555+
}
5556+
}
5557+
devices &= ~device;
5558+
}
5559+
}
5560+
5561+
private boolean checkSafeMediaVolume(int streamType, int index, int device) {
5562+
synchronized (mSafeMediaVolumeEnabled) {
5563+
if (mSafeMediaVolumeEnabled &&
5564+
(mStreamVolumeAlias[streamType] == AudioSystem.STREAM_MUSIC) &&
5565+
((device & mSafeMediaVolumeDevices) != 0) &&
5566+
(index > mSafeMediaVolumeIndex)) {
5567+
mVolumePanel.postDisplaySafeVolumeWarning();
5568+
return false;
5569+
}
5570+
return true;
5571+
}
5572+
}
5573+
5574+
public void disableSafeMediaVolume() {
5575+
synchronized (mSafeMediaVolumeEnabled) {
5576+
setSafeMediaVolumeEnabled(false);
5577+
}
5578+
}
5579+
5580+
54185581
@Override
54195582
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
54205583
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG);

0 commit comments

Comments
 (0)