am bc43b4c2: RemoteControlClient can report current position, speed

* commit 'bc43b4c2f24fd03c0d0546895c97918c1736d9fb':
  RemoteControlClient can report current position, speed
This commit is contained in:
Jean-Michel Trivi
2013-03-29 09:54:15 -07:00
committed by Android Git Automerger
7 changed files with 260 additions and 39 deletions

View File

@ -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();

View File

@ -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,21 +6065,6 @@ public class AudioService extends IAudioService.Stub implements OnFinished {
case RemoteControlClient.PLAYBACKINFO_USES_STREAM:
rcse.mPlaybackStream = value;
break;
case RemoteControlClient.PLAYBACKINFO_PLAYSTATE:
rcse.mPlaybackState = value;
synchronized (mMainRemote) {
if (rccId == mMainRemote.mRccId) {
mMainRemoteIsActive = isPlaystateActive(value);
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)) {
postPromoteRcc(rccId);
}
break;
default:
Log.e(TAG, "unhandled key " + key + " for RCC " + rccId);
break;
@ -6031,7 +6074,45 @@ public class AudioService extends IAudioService.Stub implements OnFinished {
}//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 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(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(state)) {
postPromoteRcc(rccId);
}
}
}//for
} catch (ArrayIndexOutOfBoundsException e) {
// not expected to happen, indicates improper concurrent modification
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);

View File

@ -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);

View File

@ -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);

View File

@ -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);
}
}

View File

@ -132,7 +132,8 @@ public class KeyguardTransportControlView extends FrameLayout implements OnClick
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();

View File

@ -200,7 +200,8 @@ public class KeyguardUpdateMonitor {
private final IRemoteControlDisplay.Stub mRemoteControlDisplay =
new IRemoteControlDisplay.Stub() {
public void setPlaybackState(int generationId, int state, long stateChangeTimeMs) {
public void setPlaybackState(int generationId, int state, long stateChangeTimeMs,
long currentPosMs, float speed) {
Message msg = mHandler.obtainMessage(MSG_SET_PLAYBACK_STATE,
generationId, state, stateChangeTimeMs);
mHandler.sendMessage(msg);