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

Commit 3114ce38 authored by Jean-Michel Trivi's avatar Jean-Michel Trivi
Browse files

Remote volume handling

Extend RemoteControlClient class to enable an applicaton to
 specify more information about how it's playing media, now covering
 usecases where media playback happens "remotely". This playback
 information can be used to set the volume and maximum volume
 used remotely.
Declare a new intent and associated extras in Intent,
 ACTION_VOLUME_UPDATE, so an application can be notified that
 the volume it handles should be updated. It can then use
 the new RemoteControlClient.setPlaybackInformation() method
 to notify AudioService what the volume is.
Extend AudioService to maintain playback information associated
 with the RemoteControlClient information in the stack of
 media button event receivers (mRCStack). The information
 about the active remote is cached so the stack doesn't have
 to be iterated over in order to retrieve remote playback info.
 Events to "adjust" the remote volume based on hardware key
 presses cause the client application to be notified of
 volume updates, and the volume panel to display the volume
 set by the app.
 Revise which stream type is controlled when none is specified
 according to latest guidelines for remote playback.
Update VolumePanel class to support a new pseudo stream type,
 AudioService.STREAM_REMOTE_MUSIC, that corresponds to the
 remote playback volume, and uses the new "media route" icon.
 Enable it to receive asynchronously new volume values for
 the remote that will be displayed if the UI is still up,
 and ignored otherwise.
 Now supports hiding/showing sliders dynamically so remote
 volume only appears when AudioService has a remote control
 client handling remote volume.
Define new java symbols for the two media route icons.
Modify lockscreen behavior: don't automatically control music
 volume when music is active, consider also remote playback.

Still to do:
- playback information set by RemoteControlClient should post
  a message for AudioService to update playback information
  instead of updating it synchronously

Change-Id: I557aa687239f9acfe33a609f05876c67fa7eb967
parent 4cb3b76c
Loading
Loading
Loading
Loading
+35 −0
Original line number Diff line number Diff line
@@ -27,6 +27,7 @@ import android.content.pm.ResolveInfo;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Rect;
import android.media.RemoteControlClient;
import android.net.Uri;
import android.os.Bundle;
import android.os.IBinder;
@@ -2151,6 +2152,19 @@ public class Intent implements Parcelable, Cloneable {
    public static final String ACTION_USB_AUDIO_DEVICE_PLUG =
            "android.intent.action.USB_AUDIO_DEVICE_PLUG";

    /**
     * @hide (to be un-hidden)
     * Broadcast Action: the volume handled by the receiver should be updated based on the
     * mutually exclusive extras, {@link #EXTRA_VOLUME_UPDATE_DIRECTION}
     * and {@link #EXTRA_VOLUME_UPDATE_VALUE}.
     *
     * @see #EXTRA_VOLUME_UPDATE_DIRECTION
     * @see #EXTRA_VOLUME_UPDATE_VALUE
     * @see android.media.RemoteControlClient
     */
    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
    public static final String ACTION_VOLUME_UPDATE = "android.intent.action.VOLUME_UPDATE";

    /**
     * <p>Broadcast Action: The user has switched on advanced settings in the settings app:</p>
     * <ul>
@@ -2839,6 +2853,27 @@ public class Intent implements Parcelable, Cloneable {
     */
    public static final String EXTRA_USERID =
            "android.intent.extra.user_id";

    /**
     * @hide (to be un-hidden)
     * An integer indicating whether the volume is to be increased (positive value) or decreased
     * (negative value). For bundled changes, the absolute value indicates the number of changes
     * in the same direction, e.g. +3 corresponds to three "volume up" changes.
     * @see #ACTION_VOLUME_UPDATE
     */
    public static final String EXTRA_VOLUME_UPDATE_DIRECTION =
            "android.intent.extra.VOLUME_UPDATE_DIRECTION";

    /**
     * @hide (to be un-hidden)
     * An integer indicating the new volume value, always between 0 and the value set for
     * {@link RemoteControlClient#PLAYBACKINFO_VOLUME_MAX} with
     * {@link RemoteControlClient#setPlaybackInformation(int, int)}
     * @see #ACTION_VOLUME_UPDATE
     */
    public static final String EXTRA_VOLUME_UPDATE_VALUE =
            "android.intent.extra.VOLUME_UPDATE_VALUE";

    // ---------------------------------------------------------------------
    // ---------------------------------------------------------------------
    // Intent flags (see mFlags variable).
+162 −10
Original line number Diff line number Diff line
@@ -34,10 +34,7 @@ import android.media.ToneGenerator;
import android.net.Uri;
import android.os.Handler;
import android.os.Message;
import android.os.RemoteException;
import android.os.Vibrator;
import android.provider.Settings;
import android.provider.Settings.System;
import android.util.Log;
import android.view.WindowManager.LayoutParams;
import android.widget.ImageView;
@@ -92,9 +89,13 @@ public class VolumePanel extends Handler implements OnSeekBarChangeListener, Vie
    private static final int MSG_TIMEOUT = 5;
    private static final int MSG_RINGER_MODE_CHANGED = 6;
    private static final int MSG_MUTE_CHANGED = 7;
    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;

    // Pseudo stream type for master volume
    private static final int STREAM_MASTER = -100;
    // Pseudo stream type for remote volume is defined in AudioService.STREAM_REMOTE_MUSIC

    protected Context mContext;
    private AudioManager mAudioManager;
@@ -155,10 +156,15 @@ public class VolumePanel extends Handler implements OnSeekBarChangeListener, Vie
                true),
        // for now, use media resources for master volume
        MasterStream(STREAM_MASTER,
                R.string.volume_icon_description_media,
                R.string.volume_icon_description_media, //FIXME should have its own description
                R.drawable.ic_audio_vol,
                R.drawable.ic_audio_vol_mute,
                false);
                false),
        RemoteStream(AudioService.STREAM_REMOTE_MUSIC,
                R.string.volume_icon_description_media, //FIXME should have its own description
                R.drawable.ic_media_route_on_holo_dark,
                R.drawable.ic_media_route_disabled_holo_dark,
                false);// will be dynamically updated

        int streamType;
        int descRes;
@@ -184,7 +190,8 @@ public class VolumePanel extends Handler implements OnSeekBarChangeListener, Vie
        StreamResources.MediaStream,
        StreamResources.NotificationStream,
        StreamResources.AlarmStream,
        StreamResources.MasterStream
        StreamResources.MasterStream,
        StreamResources.RemoteStream
    };

    /** Object that contains data for each slider */
@@ -297,6 +304,8 @@ public class VolumePanel extends Handler implements OnSeekBarChangeListener, Vie
    private boolean isMuted(int streamType) {
        if (streamType == STREAM_MASTER) {
            return mAudioManager.isMasterMute();
        } else if (streamType == AudioService.STREAM_REMOTE_MUSIC) {
            return (mAudioService.getRemoteStreamVolume() <= 0);
        } else {
            return mAudioManager.isStreamMute(streamType);
        }
@@ -305,6 +314,8 @@ public class VolumePanel extends Handler implements OnSeekBarChangeListener, Vie
    private int getStreamMaxVolume(int streamType) {
        if (streamType == STREAM_MASTER) {
            return mAudioManager.getMasterMaxVolume();
        } else if (streamType == AudioService.STREAM_REMOTE_MUSIC) {
            return mAudioService.getRemoteStreamMaxVolume();
        } else {
            return mAudioManager.getStreamMaxVolume(streamType);
        }
@@ -313,6 +324,8 @@ public class VolumePanel extends Handler implements OnSeekBarChangeListener, Vie
    private int getStreamVolume(int streamType) {
        if (streamType == STREAM_MASTER) {
            return mAudioManager.getMasterVolume();
        } else if (streamType == AudioService.STREAM_REMOTE_MUSIC) {
            return mAudioService.getRemoteStreamVolume();
        } else {
            return mAudioManager.getStreamVolume(streamType);
        }
@@ -321,6 +334,8 @@ public class VolumePanel extends Handler implements OnSeekBarChangeListener, Vie
    private void setStreamVolume(int streamType, int index, int flags) {
        if (streamType == STREAM_MASTER) {
            mAudioManager.setMasterVolume(index, flags);
        } else if (streamType == AudioService.STREAM_REMOTE_MUSIC) {
            mAudioService.setRemoteStreamVolume(index);
        } else {
            mAudioManager.setStreamVolume(streamType, index, flags);
        }
@@ -398,7 +413,11 @@ public class VolumePanel extends Handler implements OnSeekBarChangeListener, Vie
                mAudioManager.getRingerMode() == AudioManager.RINGER_MODE_VIBRATE) {
            sc.icon.setImageResource(R.drawable.ic_audio_ring_notif_vibrate);
        }
        if (sc.streamType != mAudioManager.getMasterStreamType() && muted) {
        if (sc.streamType == AudioService.STREAM_REMOTE_MUSIC) {
            // never disable touch interactions for remote playback, the muting is not tied to
            // the state of the phone.
            sc.seekbarView.setEnabled(true);
        } else if (sc.streamType != mAudioManager.getMasterStreamType() && muted) {
            sc.seekbarView.setEnabled(false);
        } else {
            sc.seekbarView.setEnabled(true);
@@ -446,6 +465,40 @@ public class VolumePanel extends Handler implements OnSeekBarChangeListener, Vie
        obtainMessage(MSG_VOLUME_CHANGED, streamType, flags).sendToTarget();
    }

    public void postRemoteVolumeChanged(int streamType, int flags) {
        if (hasMessages(MSG_REMOTE_VOLUME_CHANGED)) return;
        synchronized (this) {
            if (mStreamControls == null) {
                createSliders();
            }
        }
        removeMessages(MSG_FREE_RESOURCES);
        obtainMessage(MSG_REMOTE_VOLUME_CHANGED, streamType, flags).sendToTarget();
    }

    public void postRemoteSliderVisibility(boolean visible) {
        obtainMessage(MSG_SLIDER_VISIBILITY_CHANGED,
                AudioService.STREAM_REMOTE_MUSIC, visible ? 1 : 0).sendToTarget();
    }

    /**
     * Called by AudioService when it has received new remote playback information that
     * would affect the VolumePanel display (mainly volumes). The difference with
     * {@link #postRemoteVolumeChanged(int, int)} is that the handling of the posted message
     * (MSG_REMOTE_VOLUME_UPDATE_IF_SHOWN) will only update the volume slider if it is being
     * displayed.
     * This special code path is due to the fact that remote volume updates arrive to AudioService
     * asynchronously. So after AudioService has sent the volume update (which should be treated
     * as a request to update the volume), the application will likely set a new volume. If the UI
     * is still up, we need to refresh the display to show this new value.
     */
    public void postHasNewRemotePlaybackInfo() {
        if (hasMessages(MSG_REMOTE_VOLUME_UPDATE_IF_SHOWN)) return;
        // don't create or prevent resources to be freed, if they disappear, this update came too
        //   late and shouldn't warrant the panel to be displayed longer
        obtainMessage(MSG_REMOTE_VOLUME_UPDATE_IF_SHOWN).sendToTarget();
    }

    public void postMasterVolumeChanged(int flags) {
        postVolumeChanged(STREAM_MASTER, flags);
    }
@@ -585,6 +638,11 @@ public class VolumePanel extends Handler implements OnSeekBarChangeListener, Vie
                max++;
                break;
            }

            case AudioService.STREAM_REMOTE_MUSIC: {
                if (LOGD) { Log.d(TAG, "showing remote volume "+index+" over "+ max); }
                break;
            }
        }

        StreamControl sc = mStreamControls.get(streamType);
@@ -593,7 +651,8 @@ public class VolumePanel extends Handler implements OnSeekBarChangeListener, Vie
                sc.seekbarView.setMax(max);
            }
            sc.seekbarView.setProgress(index);
            if (streamType != mAudioManager.getMasterStreamType() && isMuted(streamType)) {
            if (streamType != mAudioManager.getMasterStreamType()
                    && streamType != AudioService.STREAM_REMOTE_MUSIC && isMuted(streamType)) {
                sc.seekbarView.setEnabled(false);
            } else {
                sc.seekbarView.setEnabled(true);
@@ -601,7 +660,9 @@ public class VolumePanel extends Handler implements OnSeekBarChangeListener, Vie
        }

        if (!mDialog.isShowing()) {
            mAudioManager.forceVolumeControlStream(streamType);
            int stream = (streamType == AudioService.STREAM_REMOTE_MUSIC) ? -1 : streamType;
            // when the stream is for remote playback, use -1 to reset the stream type evaluation
            mAudioManager.forceVolumeControlStream(stream);
            mDialog.setContentView(mView);
            // Showing dialog - use collapsed state
            if (mShowCombinedVolumes) {
@@ -611,7 +672,8 @@ public class VolumePanel extends Handler implements OnSeekBarChangeListener, Vie
        }

        // Do a little vibrate if applicable (only when going into vibrate mode)
        if ((flags & AudioManager.FLAG_VIBRATE) != 0 &&
        if ((streamType != AudioService.STREAM_REMOTE_MUSIC) &&
                ((flags & AudioManager.FLAG_VIBRATE) != 0) &&
                mAudioService.isStreamAffectedByRingerMode(streamType) &&
                mAudioManager.getRingerMode() == AudioManager.RINGER_MODE_VIBRATE) {
            sendMessageDelayed(obtainMessage(MSG_VIBRATE), VIBRATE_DELAY);
@@ -658,6 +720,72 @@ public class VolumePanel extends Handler implements OnSeekBarChangeListener, Vie
        mVibrator.vibrate(VIBRATE_DURATION);
    }

    protected void onRemoteVolumeChanged(int streamType, int flags) {
        // streamType is the real stream type being affected, but for the UI sliders, we
        // refer to AudioService.STREAM_REMOTE_MUSIC. We still play the beeps on the real
        // stream type.
        if (LOGD) Log.d(TAG, "onRemoteVolumeChanged(stream:"+streamType+", flags: " + flags + ")");

        if (((flags & AudioManager.FLAG_SHOW_UI) != 0) || mDialog.isShowing()) {
            synchronized (this) {
                if (mActiveStreamType != AudioService.STREAM_REMOTE_MUSIC) {
                    reorderSliders(AudioService.STREAM_REMOTE_MUSIC);
                }
                onShowVolumeChanged(AudioService.STREAM_REMOTE_MUSIC, flags);
            }
        } else {
            if (LOGD) Log.d(TAG, "not calling onShowVolumeChanged(), no FLAG_SHOW_UI or no UI");
        }

        if ((flags & AudioManager.FLAG_PLAY_SOUND) != 0 && ! mRingIsSilent) {
            removeMessages(MSG_PLAY_SOUND);
            sendMessageDelayed(obtainMessage(MSG_PLAY_SOUND, streamType, flags), PLAY_SOUND_DELAY);
        }

        if ((flags & AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE) != 0) {
            removeMessages(MSG_PLAY_SOUND);
            removeMessages(MSG_VIBRATE);
            onStopSounds();
        }

        removeMessages(MSG_FREE_RESOURCES);
        sendMessageDelayed(obtainMessage(MSG_FREE_RESOURCES), FREE_DELAY);

        resetTimeout();
    }

    protected void onRemoteVolumeUpdateIfShown() {
        if (LOGD) Log.d(TAG, "onRemoteVolumeUpdateIfShown()");
        if (mDialog.isShowing()
                && (mActiveStreamType == AudioService.STREAM_REMOTE_MUSIC)
                && (mStreamControls != null)) {
            onShowVolumeChanged(AudioService.STREAM_REMOTE_MUSIC, 0);
        }
    }


    /**
     * Handler for MSG_SLIDER_VISIBILITY_CHANGED
     * Hide or show a slider
     * @param streamType can be a valid stream type value, or VolumePanel.STREAM_MASTER,
     *                   or AudioService.STREAM_REMOTE_MUSIC
     * @param visible
     */
    synchronized protected void onSliderVisibilityChanged(int streamType, int visible) {
        if (LOGD) Log.d(TAG, "onSliderVisibilityChanged(stream="+streamType+", visi="+visible+")");
        boolean isVisible = (visible == 1);
        for (int i = STREAMS.length - 1 ; i >= 0 ; i--) {
            StreamResources streamRes = STREAMS[i];
            if (streamRes.streamType == streamType) {
                streamRes.show = isVisible;
                if (!isVisible && (mActiveStreamType == streamType)) {
                    mActiveStreamType = -1;
                }
                break;
            }
        }
    }

    /**
     * Lock on this VolumePanel instance as long as you use the returned ToneGenerator.
     */
@@ -750,6 +878,19 @@ public class VolumePanel extends Handler implements OnSeekBarChangeListener, Vie
                }
                break;
            }

            case MSG_REMOTE_VOLUME_CHANGED: {
                onRemoteVolumeChanged(msg.arg1, msg.arg2);
                break;
            }

            case MSG_REMOTE_VOLUME_UPDATE_IF_SHOWN:
                onRemoteVolumeUpdateIfShown();
                break;

            case MSG_SLIDER_VISIBILITY_CHANGED:
                onSliderVisibilityChanged(msg.arg1, msg.arg2);
                break;
        }
    }

@@ -779,6 +920,17 @@ public class VolumePanel extends Handler implements OnSeekBarChangeListener, Vie
    }

    public void onStopTrackingTouch(SeekBar seekBar) {
        final Object tag = seekBar.getTag();
        if (tag instanceof StreamControl) {
            StreamControl sc = (StreamControl) tag;
            // because remote volume updates are asynchronous, AudioService might have received
            // a new remote volume value since the finger adjusted the slider. So when the
            // progress of the slider isn't being tracked anymore, adjust the slider to the last
            // "published" remote volume value, so the UI reflects the actual volume.
            if (sc.streamType == AudioService.STREAM_REMOTE_MUSIC) {
                seekBar.setProgress(getStreamVolume(AudioService.STREAM_REMOTE_MUSIC));
            }
        }
    }

    public void onClick(View v) {
+2 −0
Original line number Diff line number Diff line
@@ -1024,6 +1024,8 @@
  <java-symbol type="drawable" name="notification_template_icon_bg" />
  <java-symbol type="drawable" name="notification_template_icon_low_bg" />
  <java-symbol type="drawable" name="ic_lockscreen_unlock_phantom" />
  <java-symbol type="drawable" name="ic_media_route_on_holo_dark" />
  <java-symbol type="drawable" name="ic_media_route_disabled_holo_dark" />

  <java-symbol type="layout" name="action_bar_home" />
  <java-symbol type="layout" name="action_bar_title_item" />
+27 −4
Original line number Diff line number Diff line
@@ -98,7 +98,10 @@ public class AudioManager {

    /**
     * @hide Broadcast intent when the volume for a particular stream type changes.
     * Includes the stream, the new volume and previous volumes
     * Includes the stream, the new volume and previous volumes.
     * Notes:
     *  - for internal platform use only, do not make public,
     *  - never used for "remote" volume changes
     *
     * @see #EXTRA_VOLUME_STREAM_TYPE
     * @see #EXTRA_VOLUME_STREAM_VALUE
@@ -1498,6 +1501,24 @@ public class AudioManager {
        return AudioSystem.isStreamActive(STREAM_MUSIC, 0);
    }

    /**
     * @hide
     * If the stream is active locally or remotely, adjust its volume according to the enforced
     * priority rules.
     * Note: only AudioManager.STREAM_MUSIC is supported at the moment
     */
    public void adjustLocalOrRemoteStreamVolume(int streamType, int direction) {
        if (streamType != STREAM_MUSIC) {
            Log.w(TAG, "adjustLocalOrRemoteStreamVolume() doesn't support stream " + streamType);
        }
        IAudioService service = getService();
        try {
            service.adjustLocalOrRemoteStreamVolume(streamType, direction);
        } catch (RemoteException e) {
            Log.e(TAG, "Dead object in adjustLocalOrRemoteStreamVolume", e);
        }
    }

    /*
     * Sets a generic audio configuration parameter. The use of these parameters
     * are platform dependant, see libaudio
@@ -2074,10 +2095,12 @@ public class AudioManager {
        }
        IAudioService service = getService();
        try {
            service.registerRemoteControlClient(rcClient.getRcMediaIntent(),   /* mediaIntent   */
            int rcseId = service.registerRemoteControlClient(
                    rcClient.getRcMediaIntent(),       /* mediaIntent   */
                    rcClient.getIRemoteControlClient(),/* rcClient      */
                    // used to match media button event receiver and audio focus
                    mContext.getPackageName());        /* packageName   */
            rcClient.setRcseId(rcseId);
        } catch (RemoteException e) {
            Log.e(TAG, "Dead object in registerRemoteControlClient"+e);
        }
+436 −28

File changed.

Preview size limit exceeded, changes collapsed.

Loading