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

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

RemoteControlClient can report current position, speed

Extend RemoteControlClient class to support reporting the
 current playback position, and the playback speed.
Define listener for an application to receive new playback
 position to seek to (use of listener to be implemented).
Update IRemoteControlDisplay implementations to new interface.

bug 8120740

Change-Id: I2654daeca1ac49713d325df8226dceb85943c020
parent a4b68908
Loading
Loading
Loading
Loading
+2 −1
Original line number Diff line number Diff line
@@ -143,7 +143,8 @@ public class TransportControlView extends FrameLayout implements OnClickListener
            mLocalHandler = new WeakReference<Handler>(handler);
        }

        public void setPlaybackState(int generationId, int state, long stateChangeTimeMs) {
        public void setPlaybackState(int generationId, int state, long stateChangeTimeMs,
                long currentPosMs, float speed) {
            Handler handler = mLocalHandler.get();
            if (handler != null) {
                handler.obtainMessage(MSG_UPDATE_STATE, generationId, state).sendToTarget();
+100 −19
Original line number Diff line number Diff line
@@ -166,6 +166,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished {
    private static final int MSG_PROMOTE_RCC = 29;
    private static final int MSG_BROADCAST_BT_CONNECTION_STATE = 30;
    private static final int MSG_UNLOAD_SOUND_EFFECTS = 31;
    private static final int MSG_RCC_NEW_PLAYBACK_STATE = 32;


    // flags for MSG_PERSIST_VOLUME indicating if current and/or last audible volume should be
@@ -3741,6 +3742,10 @@ public class AudioService extends IAudioService.Stub implements OnFinished {
                    onRegisterVolumeObserverForRcc(msg.arg1 /* rccId */,
                            (IRemoteVolumeObserver)msg.obj /* rvo */);
                    break;
                case MSG_RCC_NEW_PLAYBACK_STATE:
                    onNewPlaybackStateForRcc(msg.arg1 /* rccId */, msg.arg2 /* state */,
                            (RccPlaybackState)msg.obj /* newState */);
                    break;

                case MSG_SET_RSX_CONNECTION_STATE:
                    onSetRsxConnectionState(msg.arg1/*available*/, msg.arg2/*address*/);
@@ -5001,6 +5006,59 @@ public class AudioService extends IAudioService.Stub implements OnFinished {
     */
    private boolean mHasRemotePlayback;

    private static class RccPlaybackState {
        public int mState;
        public long mPositionMs;
        public float mSpeed;

        public RccPlaybackState(int state, long positionMs, float speed) {
            mState = state;
            mPositionMs = positionMs;
            mSpeed = speed;
        }

        public void reset() {
            mState = RemoteControlClient.PLAYSTATE_STOPPED;
            mPositionMs = RemoteControlClient.PLAYBACK_POSITION_INVALID;
            mSpeed = RemoteControlClient.PLAYBACK_SPEED_1X;
        }

        @Override
        public String toString() {
            return stateToString() + ", "
                    + ((mPositionMs == RemoteControlClient.PLAYBACK_POSITION_INVALID) ?
                            "PLAYBACK_POSITION_INVALID ," : String.valueOf(mPositionMs)) + "ms ,"
                    + mSpeed + "X";
        }

        private String stateToString() {
            switch (mState) {
                case RemoteControlClient.PLAYSTATE_NONE:
                    return "PLAYSTATE_NONE";
                case RemoteControlClient.PLAYSTATE_STOPPED:
                    return "PLAYSTATE_STOPPED";
                case RemoteControlClient.PLAYSTATE_PAUSED:
                    return "PLAYSTATE_PAUSED";
                case RemoteControlClient.PLAYSTATE_PLAYING:
                    return "PLAYSTATE_PLAYING";
                case RemoteControlClient.PLAYSTATE_FAST_FORWARDING:
                    return "PLAYSTATE_FAST_FORWARDING";
                case RemoteControlClient.PLAYSTATE_REWINDING:
                    return "PLAYSTATE_REWINDING";
                case RemoteControlClient.PLAYSTATE_SKIPPING_FORWARDS:
                    return "PLAYSTATE_SKIPPING_FORWARDS";
                case RemoteControlClient.PLAYSTATE_SKIPPING_BACKWARDS:
                    return "PLAYSTATE_SKIPPING_BACKWARDS";
                case RemoteControlClient.PLAYSTATE_BUFFERING:
                    return "PLAYSTATE_BUFFERING";
                case RemoteControlClient.PLAYSTATE_ERROR:
                    return "PLAYSTATE_ERROR";
                default:
                    return "[invalid playstate]";
            }
        }
    }

    private static class RemoteControlStackEntry {
        public int mRccId = RemoteControlClient.RCSE_ID_UNREGISTERED;
        /**
@@ -5029,7 +5087,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished {
        public int mPlaybackVolumeMax;
        public int mPlaybackVolumeHandling;
        public int mPlaybackStream;
        public int mPlaybackState;
        public RccPlaybackState mPlaybackState;
        public IRemoteVolumeObserver mRemoteVolumeObs;

        public void resetPlaybackInfo() {
@@ -5038,7 +5096,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished {
            mPlaybackVolumeMax = RemoteControlClient.DEFAULT_PLAYBACK_VOLUME;
            mPlaybackVolumeHandling = RemoteControlClient.DEFAULT_PLAYBACK_VOLUME_HANDLING;
            mPlaybackStream = AudioManager.STREAM_MUSIC;
            mPlaybackState = RemoteControlClient.PLAYSTATE_STOPPED;
            mPlaybackState.reset();
            mRemoteVolumeObs = null;
        }

@@ -6007,31 +6065,54 @@ public class AudioService extends IAudioService.Stub implements OnFinished {
                            case RemoteControlClient.PLAYBACKINFO_USES_STREAM:
                                rcse.mPlaybackStream = value;
                                break;
                            case RemoteControlClient.PLAYBACKINFO_PLAYSTATE:
                                rcse.mPlaybackState = value;
                            default:
                                Log.e(TAG, "unhandled key " + key + " for RCC " + rccId);
                                break;
                        }
                        return;
                    }
                }//for
            } catch (ArrayIndexOutOfBoundsException e) {
                // not expected to happen, indicates improper concurrent modification
                Log.e(TAG, "Wrong index mRCStack on onNewPlaybackInfoForRcc, lock error? ", e);
            }
        }
    }

    public void setPlaybackStateForRcc(int rccId, int state, long timeMs, float speed) {
        sendMsg(mAudioHandler, MSG_RCC_NEW_PLAYBACK_STATE, SENDMSG_QUEUE,
                rccId /* arg1 */, state /* arg2 */,
                new RccPlaybackState(state, timeMs, speed) /* obj */, 0 /* delay */);
    }

    public void onNewPlaybackStateForRcc(int rccId, int state, RccPlaybackState newState) {
        if(DEBUG_RC) Log.d(TAG, "onNewPlaybackStateForRcc(id=" + rccId + ", state=" + state
                + ", time=" + newState.mPositionMs + ", speed=" + newState.mSpeed + ")");
        synchronized(mRCStack) {
            // iterating from top of stack as playback information changes are more likely
            //   on entries at the top of the remote control stack
            try {
                for (int index = mRCStack.size()-1; index >= 0; index--) {
                    final RemoteControlStackEntry rcse = mRCStack.elementAt(index);
                    if (rcse.mRccId == rccId) {
                        rcse.mPlaybackState = newState;
                        synchronized (mMainRemote) {
                            if (rccId == mMainRemote.mRccId) {
                                        mMainRemoteIsActive = isPlaystateActive(value);
                                mMainRemoteIsActive = isPlaystateActive(state);
                                postReevaluateRemote();
                            }
                        }
                        // an RCC moving to a "playing" state should become the media button
                        //   event receiver so it can be controlled, without requiring the
                        //   app to re-register its receiver
                                if (isPlaystateActive(value)) {
                        if (isPlaystateActive(state)) {
                            postPromoteRcc(rccId);
                        }
                                break;
                            default:
                                Log.e(TAG, "unhandled key " + key + " for RCC " + rccId);
                                break;
                        }
                        return;
                    }
                }//for
            } catch (ArrayIndexOutOfBoundsException e) {
                // not expected to happen, indicates improper concurrent modification
                Log.e(TAG, "Wrong index accessing RC stack, lock error? ", e);
                Log.e(TAG, "Wrong index on mRCStack in onNewPlaybackStateForRcc, lock error? ", e);
            }
        }
    }
@@ -6075,7 +6156,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished {
                for (int index = mRCStack.size()-1; index >= 0; index--) {
                    final RemoteControlStackEntry rcse = mRCStack.elementAt(index);
                    if ((rcse.mPlaybackType == RemoteControlClient.PLAYBACK_TYPE_REMOTE)
                            && isPlaystateActive(rcse.mPlaybackState)
                            && isPlaystateActive(rcse.mPlaybackState.mState)
                            && (rcse.mPlaybackStream == streamType)) {
                        if (DEBUG_RC) Log.d(TAG, "remote playback active on stream " + streamType
                                + ", vol =" + rcse.mPlaybackVolume);
+1 −0
Original line number Diff line number Diff line
@@ -159,6 +159,7 @@ interface IAudioService {
    oneway void remoteControlDisplayUsesBitmapSize(in IRemoteControlDisplay rcd, int w, int h);

    oneway void setPlaybackInfoForRcc(int rccId, int what, int value);
           void setPlaybackStateForRcc(int rccId, int state, long timeMs, float speed);
           int  getRemoteStreamMaxVolume();
           int  getRemoteStreamVolume();
    oneway void registerRemoteVolumeObserverForRcc(int rccId, in IRemoteVolumeObserver rvo);
+2 −1
Original line number Diff line number Diff line
@@ -40,7 +40,8 @@ oneway interface IRemoteControlDisplay
    void setCurrentClientId(int clientGeneration, in PendingIntent clientMediaIntent,
            boolean clearing);

    void setPlaybackState(int generationId, int state, long stateChangeTimeMs);
    void setPlaybackState(int generationId, int state, long stateChangeTimeMs, long currentPosMs,
            float speed);

    void setTransportControlFlags(int generationId, int transportControlFlags);

+151 −16
Original line number Diff line number Diff line
@@ -172,6 +172,17 @@ public class RemoteControlClient
     */
    public final static int PLAYBACKINFO_INVALID_VALUE = Integer.MIN_VALUE;

    /**
     * @hide
     * An unknown or invalid playback position value.
     */
    public final static long PLAYBACK_POSITION_INVALID = -1;
    /**
     * @hide
     * The default playback speed, 1x.
     */
    public final static float PLAYBACK_SPEED_1X = 1.0f;

    //==========================================
    // Public keys for playback information
    /**
@@ -208,15 +219,7 @@ public class RemoteControlClient
    public final static int PLAYBACKINFO_USES_STREAM = 5;

    //==========================================
    // Private keys for playback information
    /**
     * @hide
     * Used internally to relay playback state (set by the application with
     * {@link #setPlaybackState(int)}) to AudioService
     */
    public final static int PLAYBACKINFO_PLAYSTATE = 255;


    // Public flags for the supported transport control capabililities
    /**
     * Flag indicating a RemoteControlClient makes use of the "previous" media key.
     *
@@ -273,6 +276,15 @@ public class RemoteControlClient
     * @see android.view.KeyEvent#KEYCODE_MEDIA_NEXT
     */
    public final static int FLAG_KEY_MEDIA_NEXT = 1 << 7;
    /**
     * @hide
     * (to be un-hidden and added in javadoc of setTransportControlFlags(int))
     * Flag indicating a RemoteControlClient can receive changes in the media playback position
     * through the {@link #OnPlaybackPositionUpdateListener} interface.
     *
     * @see #setTransportControlFlags(int)
     */
    public final static int FLAG_KEY_MEDIA_POSITION_UPDATE = 1 << 8;

    /**
     * @hide
@@ -588,17 +600,54 @@ public class RemoteControlClient
     *       {@link #PLAYSTATE_ERROR}.
     */
    public void setPlaybackState(int state) {
        setPlaybackState(state, PLAYBACK_POSITION_INVALID, PLAYBACK_SPEED_1X);
    }

    /**
     * @hide
     * (to be un-hidden)
     * Sets the current playback state and the matching media position for the current playback
     *   speed.
     * @param state The current playback state, one of the following values:
     *       {@link #PLAYSTATE_STOPPED},
     *       {@link #PLAYSTATE_PAUSED},
     *       {@link #PLAYSTATE_PLAYING},
     *       {@link #PLAYSTATE_FAST_FORWARDING},
     *       {@link #PLAYSTATE_REWINDING},
     *       {@link #PLAYSTATE_SKIPPING_FORWARDS},
     *       {@link #PLAYSTATE_SKIPPING_BACKWARDS},
     *       {@link #PLAYSTATE_BUFFERING},
     *       {@link #PLAYSTATE_ERROR}.
     * @param timeInMs a 0 or positive value for the current media position expressed in ms
     *    (same unit as for when sending the media duration, if applicable, with
     *    {@link android.media.MediaMetadataRetriever#METADATA_KEY_DURATION} in the
     *    {@link RemoteControlClient.MetadataEditor}). Negative values imply that position is not
     *    known (e.g. listening to a live stream of a radio) or not applicable (e.g. when state
     *    is {@link #PLAYSTATE_BUFFERING} and nothing had played yet).
     * @param playbackSpeed a value expressed as a ratio of 1x playback: 1.0f is normal playback,
     *    2.0f is 2x, 0.5f is half-speed, -2.0f is rewind at 2x speed. 0.0f means nothing is
     *    playing (e.g. when state is {@link #PLAYSTATE_ERROR}).
     */
    public void setPlaybackState(int state, long timeInMs, float playbackSpeed) {
        synchronized(mCacheLock) {
            if (mPlaybackState != state) {
            if (timeInMs != PLAYBACK_POSITION_INVALID) {
                mPlaybackPositionCapabilities |= MEDIA_POSITION_READABLE;
            } else {
                mPlaybackPositionCapabilities &= ~MEDIA_POSITION_READABLE;
            }
            if ((mPlaybackState != state) || (mPlaybackPositionMs != timeInMs)
                    || (mPlaybackSpeed != playbackSpeed)) {
                // store locally
                mPlaybackState = state;
                mPlaybackPositionMs = timeInMs;
                mPlaybackSpeed = playbackSpeed;
                // keep track of when the state change occurred
                mPlaybackStateChangeTimeMs = SystemClock.elapsedRealtime();

                // send to remote control display if conditions are met
                sendPlaybackState_syncCacheLock();
                // update AudioService
                sendAudioServiceNewPlaybackInfo_syncCacheLock(PLAYBACKINFO_PLAYSTATE, state);
                sendAudioServiceNewPlaybackState_syncCacheLock();
            }
        }
    }
@@ -625,6 +674,65 @@ public class RemoteControlClient
        }
    }

    /**
     * @hide
     * (to be un-hidden)
     * Interface definition for a callback to be invoked when the media playback position is
     * requested to be updated.
     */
    public interface OnPlaybackPositionUpdateListener {
        /**
         * Called on the listener to notify it that the playback head should be set at the given
         * position. If the position can be changed from its current value, the implementor of
         * the interface should also update the playback position using
         * {@link RemoteControlClient#setPlaybackState(int, long, int)} to reflect the actual new
         * position being used, regardless of whether it differs from the requested position.
         * @param newPositionMs the new requested position in the current media, expressed in ms.
         */
        void onPlaybackPositionUpdate(long newPositionMs);
    }

    /**
     * @hide
     * (to be un-hidden)
     * Sets the listener RemoteControlClient calls whenever the media playback position is requested
     * to be updated.
     * Notifications will be received in the same thread as the one in which RemoteControlClient
     * was created.
     * @param l
     */
    public void setPlaybackPositionUpdateListener(OnPlaybackPositionUpdateListener l) {
        synchronized(mCacheLock) {
            if ((mPositionUpdateListener == null) && (l != null)) {
                mPlaybackPositionCapabilities |= MEDIA_POSITION_WRITABLE;
                // tell RCDs and AudioService this RCC accepts position updates
                // TODO implement
            } else if ((mPositionUpdateListener != null) && (l == null)) {
                mPlaybackPositionCapabilities &= ~MEDIA_POSITION_WRITABLE;
                // tell RCDs and AudioService this RCC doesn't handle position updates
                // TODO implement
            }
            mPositionUpdateListener = l;
        }
    }

    /**
     * @hide
     * Flag to reflect that the application controlling this RemoteControlClient sends playback
     * position updates. The playback position being "readable" is considered from the application's
     * point of view.
     */
    public static int MEDIA_POSITION_READABLE = 1 << 0;
    /**
     * @hide
     * Flag to reflect that the application controlling this RemoteControlClient can receive
     * playback position updates. The playback position being "writable"
     * is considered from the application's point of view.
     */
    public static int MEDIA_POSITION_WRITABLE = 1 << 1;

    private int mPlaybackPositionCapabilities = 0;

    /** @hide */
    public final static int DEFAULT_PLAYBACK_VOLUME_HANDLING = PLAYBACK_VOLUME_VARIABLE;
    /** @hide */
@@ -755,6 +863,14 @@ public class RemoteControlClient
     * Access synchronized on mCacheLock
     */
    private long mPlaybackStateChangeTimeMs = 0;
    /**
     * Last playback position in ms reported by the user
     */
    private long mPlaybackPositionMs = PLAYBACK_POSITION_INVALID;
    /**
     * Last playback speed reported by the user
     */
    private float mPlaybackSpeed = PLAYBACK_SPEED_1X;
    /**
     * Cache for the artwork bitmap.
     * Access synchronized on mCacheLock
@@ -774,9 +890,13 @@ public class RemoteControlClient
     * This is re-initialized in apply() and so cannot be final.
     */
    private Bundle mMetadata = new Bundle();

    /**
     * The current remote control client generation ID across the system
     * Listener registered by user of RemoteControlClient to receive requests for playback position
     * update requests.
     */
    private OnPlaybackPositionUpdateListener mPositionUpdateListener;
    /**
     * The current remote control client generation ID across the system, as known by this object
     */
    private int mCurrentClientGenId = -1;
    /**
@@ -789,7 +909,8 @@ public class RemoteControlClient

    /**
     * The media button intent description associated with this remote control client
     * (can / should include target component for intent handling)
     * (can / should include target component for intent handling, used when persisting media
     *    button event receiver across reboots).
     */
    private final PendingIntent mRcMediaIntent;

@@ -990,7 +1111,8 @@ public class RemoteControlClient
                final DisplayInfoForClient di = (DisplayInfoForClient) displayIterator.next();
                try {
                    di.mRcDisplay.setPlaybackState(mInternalClientGenId,
                            mPlaybackState, mPlaybackStateChangeTimeMs);
                            mPlaybackState, mPlaybackStateChangeTimeMs, mPlaybackPositionMs,
                            mPlaybackSpeed);
                } catch (RemoteException e) {
                    Log.e(TAG, "Error in setPlaybackState(), dead display " + di.mRcDisplay, e);
                    displayIterator.remove();
@@ -1109,7 +1231,20 @@ public class RemoteControlClient
        try {
            service.setPlaybackInfoForRcc(mRcseId, what, value);
        } catch (RemoteException e) {
            Log.e(TAG, "Dead object in sendAudioServiceNewPlaybackInfo_syncCacheLock", e);
            Log.e(TAG, "Dead object in setPlaybackInfoForRcc", e);
        }
    }

    private void sendAudioServiceNewPlaybackState_syncCacheLock() {
        if (mRcseId == RCSE_ID_UNREGISTERED) {
            return;
        }
        IAudioService service = getService();
        try {
            service.setPlaybackStateForRcc(mRcseId,
                    mPlaybackState, mPlaybackPositionMs, mPlaybackSpeed);
        } catch (RemoteException e) {
            Log.e(TAG, "Dead object in setPlaybackStateForRcc", e);
        }
    }

Loading