Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 39a37c3b authored by Eric Laurent's avatar Eric Laurent Committed by Android (Google) Code Review
Browse files

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

parents 89c82814 c34dcc1e
Loading
Loading
Loading
Loading
+61 −0
Original line number Diff line number Diff line
@@ -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;
        }
    }

+6 −0
Original line number Diff line number Diff line
@@ -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>
+6 −0
Original line number Diff line number Diff line
@@ -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>
+2 −0
Original line number Diff line number Diff line
@@ -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" />
+164 −1
Original line number Diff line number Diff line
@@ -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;
            }
        }
    }
@@ -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);