diff options
8 files changed, 467 insertions, 108 deletions
diff --git a/api/current.txt b/api/current.txt index 1fe625b..cca12be 100644 --- a/api/current.txt +++ b/api/current.txt @@ -15448,12 +15448,15 @@ package android.media.session { method public void disconnect(android.media.session.RouteInfo); method public android.media.session.SessionToken getSessionToken(); method public android.media.session.TransportPerformer getTransportPerformer(); - method public void publish(); + method public boolean isActive(); method public void release(); method public void removeCallback(android.media.session.Session.Callback); method public void sendEvent(java.lang.String, android.os.Bundle); + method public void setActive(boolean); + method public void setFlags(int); method public void setRouteOptions(java.util.List<android.media.session.RouteOptions>); - method public android.media.session.TransportPerformer setTransportPerformerEnabled(); + field public static final int FLAG_HANDLES_MEDIA_BUTTONS = 1; // 0x1 + field public static final int FLAG_HANDLES_TRANSPORT_CONTROLS = 2; // 0x2 } public static abstract class Session.Callback { diff --git a/media/java/android/media/session/ISession.aidl b/media/java/android/media/session/ISession.aidl index 7cab6b6..3ff07d9 100644 --- a/media/java/android/media/session/ISession.aidl +++ b/media/java/android/media/session/ISession.aidl @@ -31,9 +31,8 @@ import android.os.ResultReceiver; interface ISession { void sendEvent(String event, in Bundle data); ISessionController getController(); - void setTransportPerformerEnabled(); - void setFlags(long flags); - void publish(); + void setFlags(int flags); + void setActive(boolean active); void destroy(); // These commands are for setting up and communicating with routes diff --git a/media/java/android/media/session/MediaSessionLegacyHelper.java b/media/java/android/media/session/MediaSessionLegacyHelper.java index da0100a..c07229d 100644 --- a/media/java/android/media/session/MediaSessionLegacyHelper.java +++ b/media/java/android/media/session/MediaSessionLegacyHelper.java @@ -79,6 +79,8 @@ public class MediaSessionLegacyHelper { } performer.addListener(listener, mHandler); holder.mRccListener = listener; + holder.mFlags |= Session.FLAG_HANDLES_TRANSPORT_CONTROLS; + holder.mSession.setFlags(holder.mFlags); holder.update(); } @@ -87,6 +89,8 @@ public class MediaSessionLegacyHelper { if (holder != null && holder.mRccListener != null) { holder.mSession.getTransportPerformer().removeListener(holder.mRccListener); holder.mRccListener = null; + holder.mFlags &= ~Session.FLAG_HANDLES_TRANSPORT_CONTROLS; + holder.mSession.setFlags(holder.mFlags); holder.update(); } } @@ -99,6 +103,8 @@ public class MediaSessionLegacyHelper { return; } holder.mMediaButtonListener = new MediaButtonListener(pi, context); + holder.mFlags |= Session.FLAG_HANDLES_MEDIA_BUTTONS; + holder.mSession.setFlags(holder.mFlags); holder.mSession.getTransportPerformer().addListener(holder.mMediaButtonListener, mHandler); } @@ -106,6 +112,9 @@ public class MediaSessionLegacyHelper { SessionHolder holder = getHolder(pi, false); if (holder != null && holder.mMediaButtonListener != null) { holder.mSession.getTransportPerformer().removeListener(holder.mMediaButtonListener); + holder.mFlags &= ~Session.FLAG_HANDLES_MEDIA_BUTTONS; + holder.mSession.setFlags(holder.mFlags); + holder.mMediaButtonListener = null; holder.update(); } } @@ -114,8 +123,7 @@ public class MediaSessionLegacyHelper { SessionHolder holder = mSessions.get(pi); if (holder == null && createIfMissing) { Session session = mSessionManager.createSession(TAG); - session.setTransportPerformerEnabled(); - session.publish(); + session.setActive(true); holder = new SessionHolder(session, pi); mSessions.put(pi, holder); } @@ -194,6 +202,7 @@ public class MediaSessionLegacyHelper { public final PendingIntent mPi; public MediaButtonListener mMediaButtonListener; public TransportPerformer.Listener mRccListener; + public int mFlags; public SessionHolder(Session session, PendingIntent pi) { mSession = session; diff --git a/media/java/android/media/session/Session.java b/media/java/android/media/session/Session.java index 227175d..194679e7 100644 --- a/media/java/android/media/session/Session.java +++ b/media/java/android/media/session/Session.java @@ -65,13 +65,26 @@ public final class Session { private static final String TAG = "Session"; /** + * Set this flag on the session to indicate that it can handle media button + * events. + */ + public static final int FLAG_HANDLES_MEDIA_BUTTONS = 1 << 0; + + /** + * Set this flag on the session to indicate that it handles commands through + * the {@link TransportPerformer}. The performer can be retrieved by calling + * {@link #getTransportPerformer()}. + */ + public static final int FLAG_HANDLES_TRANSPORT_CONTROLS = 1 << 1; + + /** * System only flag for a session that needs to have priority over all other * sessions. This flag ensures this session will receive media button events * regardless of the current ordering in the system. * * @hide */ - public static final long FLAG_EXCLUSIVE_GLOBAL_PRIORITY = 1 << 32; + public static final int FLAG_EXCLUSIVE_GLOBAL_PRIORITY = 1 << 16; private static final int MSG_MEDIA_BUTTON = 1; private static final int MSG_COMMAND = 2; @@ -96,7 +109,7 @@ public final class Session { private TransportPerformer mPerformer; private Route mRoute; - private boolean mPublished = false;; + private boolean mActive = false;; /** * @hide @@ -111,6 +124,7 @@ public final class Session { throw new RuntimeException("Dead object in MediaSessionController constructor: ", e); } mSessionToken = new SessionToken(controllerBinder); + mPerformer = new TransportPerformer(mBinder); } /** @@ -158,52 +172,23 @@ public final class Session { } /** - * Start using a TransportPerformer with this media session. This must be - * called before calling publish and cannot be called more than once. - * Calling this will allow MediaControllers to retrieve a - * TransportController. + * Retrieves the {@link TransportPerformer} for this session. To receive + * commands through the performer you must also set the + * {@link #FLAG_HANDLES_TRANSPORT_CONTROLS} flag using + * {@link #setFlags(int)}. * - * @see TransportController - * @return The TransportPerformer created for this session - */ - public TransportPerformer setTransportPerformerEnabled() { - if (mPerformer != null) { - throw new IllegalStateException("setTransportPerformer can only be called once."); - } - if (mPublished) { - throw new IllegalStateException("setTransportPerformer cannot be called after publish"); - } - - mPerformer = new TransportPerformer(mBinder); - try { - mBinder.setTransportPerformerEnabled(); - } catch (RemoteException e) { - Log.wtf(TAG, "Failure in setTransportPerformerEnabled.", e); - } - return mPerformer; - } - - /** - * Retrieves the TransportPerformer used by this session. If called before - * {@link #setTransportPerformerEnabled} null will be returned. - * - * @return The TransportPerformer associated with this session or null + * @return The performer associated with this session. */ public TransportPerformer getTransportPerformer() { return mPerformer; } /** - * Set any flags for the session. This cannot be called after calling - * {@link #publish()}. + * Set any flags for the session. * * @param flags The flags to set for this session. - * @hide remove hide once we have non-system flags */ - public void setFlags(long flags) { - if (mPublished) { - throw new IllegalStateException("setFlags may not be called after publish"); - } + public void setFlags(int flags) { try { mBinder.setFlags(flags); } catch (RemoteException e) { @@ -212,20 +197,32 @@ public final class Session { } /** - * Call after you have finished setting up the session. This will make it - * available to listeners and begin pushing updates to MediaControllers. - * This can only be called once. + * Set if this session is currently active and ready to receive commands. If + * set to false your session's controller may not be discoverable. You must + * set the session to active before it can start receiving media button + * events or transport commands. + * + * @param active Whether this session is active or not. */ - public void publish() { - if (mPublished) { - throw new RuntimeException("publish() may only be called once."); + public void setActive(boolean active) { + if (mActive == active) { + return; } try { - mBinder.publish(); + mBinder.setActive(active); + mActive = active; } catch (RemoteException e) { - Log.wtf(TAG, "Failure in publish.", e); + Log.wtf(TAG, "Failure in setActive.", e); } - mPublished = true; + } + + /** + * Get the current active state of this session. + * + * @return True if the session is active, false otherwise. + */ + public boolean isActive() { + return mActive; } /** diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java index e4e5979..015032b 100644 --- a/services/core/java/com/android/server/media/MediaSessionRecord.java +++ b/services/core/java/com/android/server/media/MediaSessionRecord.java @@ -60,6 +60,24 @@ import java.util.UUID; public class MediaSessionRecord implements IBinder.DeathRecipient { private static final String TAG = "MediaSessionRecord"; + /** + * These are the playback states that count as currently active. + */ + private static final int[] ACTIVE_STATES = { + PlaybackState.PLAYSTATE_FAST_FORWARDING, + PlaybackState.PLAYSTATE_REWINDING, + PlaybackState.PLAYSTATE_SKIPPING_BACKWARDS, + PlaybackState.PLAYSTATE_SKIPPING_FORWARDS, + PlaybackState.PLAYSTATE_BUFFERING, + PlaybackState.PLAYSTATE_CONNECTING, + PlaybackState.PLAYSTATE_PLAYING }; + + /** + * The length of time a session will still be considered active after + * pausing in ms. + */ + private static final int ACTIVE_BUFFER = 30000; + private final MessageHandler mHandler; private final int mPid; @@ -75,7 +93,6 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { new ArrayList<ISessionControllerCallback>(); private final ArrayList<RouteRequest> mRequests = new ArrayList<RouteRequest>(); - private boolean mTransportPerformerEnabled = false; private RouteInfo mRoute; private RouteOptions mRequest; private RouteConnectionRecord mConnection; @@ -88,9 +105,10 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { private MediaMetadata mMetadata; private PlaybackState mPlaybackState; private int mRatingType; + private long mLastActiveTime; // End TransportPerformer fields - private boolean mIsPublished = false; + private boolean mIsActive = false; public MediaSessionRecord(int pid, String packageName, ISessionCallback cb, String tag, MediaSessionService service, Handler handler) { @@ -159,6 +177,16 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { } /** + * Check if this session has the specified flag. + * + * @param flag The flag to check. + * @return True if this session has that flag set, false otherwise. + */ + public boolean hasFlag(int flag) { + return (mFlags & flag) != 0; + } + + /** * Check if this session has system priorty and should receive media buttons * before any other sessions. * @@ -236,12 +264,36 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { } /** - * Check if this session has been published by the app yet. + * Check if this session has been set to active by the app. + * + * @return True if the session is active, false otherwise. + */ + public boolean isActive() { + return mIsActive; + } + + /** + * Check if the session is currently performing playback. This will also + * return true if the session was recently paused. * - * @return True if it has been published, false otherwise. + * @return True if the session is performing playback, false otherwise. */ - public boolean isPublished() { - return mIsPublished; + public boolean isPlaybackActive() { + int state = mPlaybackState == null ? 0 : mPlaybackState.getState(); + if (isActiveState(state)) { + return true; + } + if (state == mPlaybackState.PLAYSTATE_PAUSED) { + long inactiveTime = SystemClock.uptimeMillis() - mLastActiveTime; + if (inactiveTime < ACTIVE_BUFFER) { + return true; + } + } + return false; + } + + public boolean isTransportControlEnabled() { + return hasFlag(Session.FLAG_HANDLES_TRANSPORT_CONTROLS); } @Override @@ -255,11 +307,11 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { final String indent = prefix + " "; pw.println(indent + "pid=" + mPid); pw.println(indent + "info=" + mSessionInfo.toString()); - pw.println(indent + "published=" + mIsPublished); - pw.println(indent + "transport controls enabled=" + mTransportPerformerEnabled); + pw.println(indent + "published=" + mIsActive); + pw.println(indent + "flags=" + mFlags); pw.println(indent + "rating type=" + mRatingType); pw.println(indent + "controllers: " + mControllerCallbacks.size()); - pw.println(indent + "state=" + mPlaybackState.toString()); + pw.println(indent + "state=" + (mPlaybackState == null ? null : mPlaybackState.toString())); pw.println(indent + "metadata:" + getShortMetadataString()); pw.println(indent + "route requests {"); int size = mRequests.size(); @@ -272,6 +324,15 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { pw.println(indent + "params=" + (mRequest == null ? null : mRequest.toString())); } + private boolean isActiveState(int state) { + for (int i = 0; i < ACTIVE_STATES.length; i++) { + if (ACTIVE_STATES[i] == state) { + return true; + } + } + return false; + } + private String getShortMetadataString() { int fields = mMetadata == null ? 0 : mMetadata.size(); String title = mMetadata == null ? null : mMetadata @@ -414,26 +475,21 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { } @Override - public void publish() { - mIsPublished = true; - mService.publishSession(MediaSessionRecord.this); - } - @Override - public void setTransportPerformerEnabled() { - mTransportPerformerEnabled = true; + public void setActive(boolean active) { + mIsActive = active; + mService.updateSession(MediaSessionRecord.this); + mHandler.post(MessageHandler.MSG_UPDATE_SESSION_STATE); } @Override - public void setFlags(long flags) { + public void setFlags(int flags) { if ((flags & Session.FLAG_EXCLUSIVE_GLOBAL_PRIORITY) != 0) { int pid = getCallingPid(); int uid = getCallingUid(); mService.enforcePhoneStatePermission(pid, uid); } - if (mIsPublished) { - throw new IllegalStateException("Cannot set flags after publishing session."); - } mFlags = flags; + mHandler.post(MessageHandler.MSG_UPDATE_SESSION_STATE); } @Override @@ -444,7 +500,13 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { @Override public void setPlaybackState(PlaybackState state) { + int oldState = mPlaybackState == null ? 0 : mPlaybackState.getState(); + int newState = state == null ? 0 : state.getState(); + if (isActiveState(oldState) && newState == PlaybackState.PLAYSTATE_PAUSED) { + mLastActiveTime = SystemClock.elapsedRealtime(); + } mPlaybackState = state; + mService.onSessionPlaystateChange(MediaSessionRecord.this, oldState, newState); mHandler.post(MessageHandler.MSG_UPDATE_PLAYBACK_STATE); } @@ -708,7 +770,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { @Override public boolean isTransportControlEnabled() { - return mTransportPerformerEnabled; + return MediaSessionRecord.this.isTransportControlEnabled(); } @Override @@ -724,6 +786,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { private static final int MSG_SEND_EVENT = 4; private static final int MSG_UPDATE_ROUTE_FILTERS = 5; private static final int MSG_SEND_COMMAND = 6; + private static final int MSG_UPDATE_SESSION_STATE = 7; public MessageHandler(Looper looper) { super(looper); @@ -748,6 +811,9 @@ public class MediaSessionRecord implements IBinder.DeathRecipient { (Pair<RouteCommand, ResultReceiver>) msg.obj; pushRouteCommand(cmd.first, cmd.second); break; + case MSG_UPDATE_SESSION_STATE: + // TODO add session state + break; } } diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java index 3035521..fb858fc 100644 --- a/services/core/java/com/android/server/media/MediaSessionService.java +++ b/services/core/java/com/android/server/media/MediaSessionService.java @@ -26,6 +26,7 @@ import android.media.session.ISession; import android.media.session.ISessionCallback; import android.media.session.ISessionController; import android.media.session.ISessionManager; +import android.media.session.PlaybackState; import android.media.session.RouteInfo; import android.media.session.RouteOptions; import android.os.Binder; @@ -56,9 +57,9 @@ public class MediaSessionService extends SystemService implements Monitor { private final SessionManagerImpl mSessionManagerImpl; private final MediaRouteProviderWatcher mRouteProviderWatcher; + private final MediaSessionStack mPriorityStack; - private final ArrayList<MediaSessionRecord> mSessions - = new ArrayList<MediaSessionRecord>(); + private final ArrayList<MediaSessionRecord> mRecords = new ArrayList<MediaSessionRecord>(); private final ArrayList<MediaRouteProviderProxy> mProviders = new ArrayList<MediaRouteProviderProxy>(); private final Object mLock = new Object(); @@ -79,6 +80,7 @@ public class MediaSessionService extends SystemService implements Monitor { mSessionManagerImpl = new SessionManagerImpl(); mRouteProviderWatcher = new MediaRouteProviderWatcher(context, mProviderWatcherCallback, mHandler, context.getUserId()); + mPriorityStack = new MediaSessionStack(); } @Override @@ -131,17 +133,30 @@ public class MediaSessionService extends SystemService implements Monitor { } } - public void publishSession(MediaSessionRecord record) { + public void updateSession(MediaSessionRecord record) { synchronized (mLock) { + mPriorityStack.onSessionStateChange(record); if (record.isSystemPriority()) { - if (mPrioritySession != null) { - Log.w(TAG, "Replacing existing priority session with a new session"); + if (record.isActive()) { + if (mPrioritySession != null) { + Log.w(TAG, "Replacing existing priority session with a new session"); + } + mPrioritySession = record; + } else { + if (mPrioritySession == record) { + mPrioritySession = null; + } } - mPrioritySession = record; } } } + public void onSessionPlaystateChange(MediaSessionRecord record, int oldState, int newState) { + synchronized (mLock) { + mPriorityStack.onPlaystateChange(record, oldState, newState); + } + } + @Override public void monitor() { synchronized (mLock) { @@ -162,7 +177,8 @@ public class MediaSessionService extends SystemService implements Monitor { } private void destroySessionLocked(MediaSessionRecord session) { - mSessions.remove(session); + mRecords.remove(session); + mPriorityStack.removeSession(session); if (session == mPrioritySession) { mPrioritySession = null; } @@ -254,13 +270,24 @@ public class MediaSessionService extends SystemService implements Monitor { } catch (RemoteException e) { throw new RuntimeException("Media Session owner died prematurely.", e); } - mSessions.add(session); + mRecords.add(session); + mPriorityStack.addSession(session); if (DEBUG) { Log.d(TAG, "Created session for package " + packageName + " with tag " + tag); } return session; } + private int findIndexOfSessionForIdLocked(String sessionId) { + for (int i = mRecords.size() - 1; i >= 0; i--) { + MediaSessionRecord session = mRecords.get(i); + if (TextUtils.equals(session.getSessionInfo().getId(), sessionId)) { + return i; + } + } + return -1; + } + private MediaRouteProviderProxy getProviderLocked(String providerId) { for (int i = mProviders.size() - 1; i >= 0; i--) { MediaRouteProviderProxy provider = mProviders.get(i); @@ -271,19 +298,9 @@ public class MediaSessionService extends SystemService implements Monitor { return null; } - private int findIndexOfSessionForIdLocked(String sessionId) { - for (int i = mSessions.size() - 1; i >= 0; i--) { - MediaSessionRecord session = mSessions.get(i); - if (TextUtils.equals(session.getSessionInfo().getId(), sessionId)) { - return i; - } - } - return -1; - } - private boolean isSessionDiscoverable(MediaSessionRecord record) { // TODO probably want to check more than if it's published. - return record.isPublished(); + return record.isActive(); } private MediaRouteProviderWatcher.Callback mProviderWatcherCallback @@ -317,7 +334,7 @@ public class MediaSessionService extends SystemService implements Monitor { synchronized (mLock) { int index = findIndexOfSessionForIdLocked(sessionId); if (index != -1 && routes != null && routes.size() > 0) { - MediaSessionRecord record = mSessions.get(index); + MediaSessionRecord record = mRecords.get(index); record.selectRoute(routes.get(0)); } } @@ -329,7 +346,7 @@ public class MediaSessionService extends SystemService implements Monitor { synchronized (mLock) { int index = findIndexOfSessionForIdLocked(sessionId); if (index != -1) { - MediaSessionRecord session = mSessions.get(index); + MediaSessionRecord session = mRecords.get(index); session.setRouteConnected(route, options.getConnectionOptions(), connection); } } @@ -359,7 +376,6 @@ public class MediaSessionService extends SystemService implements Monitor { @Override public List<IBinder> getSessions(ComponentName componentName) { - final int pid = Binder.getCallingPid(); final int uid = Binder.getCallingUid(); final long token = Binder.clearCallingIdentity(); @@ -375,11 +391,11 @@ public class MediaSessionService extends SystemService implements Monitor { enforceMediaPermissions(componentName, pid, uid); ArrayList<IBinder> binders = new ArrayList<IBinder>(); synchronized (mLock) { - for (int i = mSessions.size() - 1; i >= 0; i--) { - MediaSessionRecord record = mSessions.get(i); - if (isSessionDiscoverable(record)) { - binders.add(record.getControllerBinder().asBinder()); - } + ArrayList<MediaSessionRecord> records = mPriorityStack + .getActiveSessions(); + int size = records.size(); + for (int i = 0; i < size; i++) { + binders.add(records.get(i).getControllerBinder().asBinder()); } } return binders; @@ -406,13 +422,14 @@ public class MediaSessionService extends SystemService implements Monitor { if (mPrioritySession != null) { mPrioritySession.dump(pw, ""); } - int count = mSessions.size(); - pw.println("Sessions - have " + count + " states:"); + int count = mRecords.size(); + pw.println(count + " Sessions:"); for (int i = 0; i < count; i++) { - MediaSessionRecord record = mSessions.get(i); + mRecords.get(i).dump(pw, ""); pw.println(); - record.dump(pw, ""); } + mPriorityStack.dumpLocked(pw, ""); + pw.println("Providers:"); count = mProviders.size(); for (int i = 0; i < count; i++) { diff --git a/services/core/java/com/android/server/media/MediaSessionStack.java b/services/core/java/com/android/server/media/MediaSessionStack.java new file mode 100644 index 0000000..f9f004d --- /dev/null +++ b/services/core/java/com/android/server/media/MediaSessionStack.java @@ -0,0 +1,267 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.media; + +import android.media.session.PlaybackState; +import android.media.session.Session; +import android.text.TextUtils; + +import java.io.PrintWriter; +import java.util.ArrayList; + +/** + * Keeps track of media sessions and their priority for notifications, media + * button routing, etc. + */ +public class MediaSessionStack { + /** + * These are states that usually indicate the user took an action and should + * bump priority regardless of the old state. + */ + private static final int[] ALWAYS_PRIORITY_STATES = { + PlaybackState.PLAYSTATE_FAST_FORWARDING, + PlaybackState.PLAYSTATE_REWINDING, + PlaybackState.PLAYSTATE_SKIPPING_BACKWARDS, + PlaybackState.PLAYSTATE_SKIPPING_FORWARDS }; + /** + * These are states that usually indicate the user took an action if they + * were entered from a non-priority state. + */ + private static final int[] TRANSITION_PRIORITY_STATES = { + PlaybackState.PLAYSTATE_BUFFERING, + PlaybackState.PLAYSTATE_CONNECTING, + PlaybackState.PLAYSTATE_PLAYING }; + + private final ArrayList<MediaSessionRecord> mSessions = new ArrayList<MediaSessionRecord>(); + + private MediaSessionRecord mCachedButtonReceiver; + private MediaSessionRecord mCachedDefault; + private ArrayList<MediaSessionRecord> mCachedActiveList; + private ArrayList<MediaSessionRecord> mCachedTransportControlList; + + /** + * Add a record to the priority tracker. + * + * @param record The record to add. + */ + public void addSession(MediaSessionRecord record) { + mSessions.add(record); + clearCache(); + } + + /** + * Remove a record from the priority tracker. + * + * @param record The record to remove. + */ + public void removeSession(MediaSessionRecord record) { + mSessions.remove(record); + clearCache(); + } + + /** + * Notify the priority tracker that a session's state changed. + * + * @param record The record that changed. + * @param oldState Its old playback state. + * @param newState Its new playback state. + */ + public void onPlaystateChange(MediaSessionRecord record, int oldState, int newState) { + if (shouldUpdatePriority(oldState, newState)) { + mSessions.remove(record); + mSessions.add(0, record); + clearCache(); + } + } + + /** + * Handle any stack changes that need to occur in response to a session + * state change. TODO add the old and new session state as params + * + * @param record The record that changed. + */ + public void onSessionStateChange(MediaSessionRecord record) { + // For now just clear the cache. Eventually we'll selectively clear + // depending on what changed. + clearCache(); + } + + /** + * Get the current priority sorted list of active sessions. The most + * important session is at index 0 and the least important at size - 1. + * + * @return All the active sessions in priority order. + */ + public ArrayList<MediaSessionRecord> getActiveSessions() { + if (mCachedActiveList == null) { + mCachedActiveList = getPriorityListLocked(true, 0); + } + return mCachedActiveList; + } + + /** + * Get the current priority sorted list of active sessions that use + * transport controls. The most important session is at index 0 and the + * least important at size -1. + * + * @return All the active sessions that handle transport controls in + * priority order. + */ + public ArrayList<MediaSessionRecord> getTransportControlSessions() { + if (mCachedTransportControlList == null) { + mCachedTransportControlList = getPriorityListLocked(true, + Session.FLAG_HANDLES_TRANSPORT_CONTROLS); + } + return mCachedTransportControlList; + } + + /** + * Get the highest priority active session. + * + * @return The current highest priority session or null. + */ + public MediaSessionRecord getDefaultSession() { + if (mCachedDefault != null) { + return mCachedDefault; + } + ArrayList<MediaSessionRecord> records = getPriorityListLocked(true, 0); + if (records.size() > 0) { + return records.get(0); + } + return null; + } + + /** + * Get the highest priority session that can handle media buttons. + * + * @return The default media button session or null. + */ + public MediaSessionRecord getDefaultMediaButtonSession() { + if (mCachedButtonReceiver != null) { + return mCachedButtonReceiver; + } + ArrayList<MediaSessionRecord> records = getPriorityListLocked(true, + Session.FLAG_HANDLES_MEDIA_BUTTONS); + if (records.size() > 0) { + mCachedButtonReceiver = records.get(0); + } + return mCachedButtonReceiver; + } + + public void dumpLocked(PrintWriter pw, String prefix) { + ArrayList<MediaSessionRecord> sortedSessions = getPriorityListLocked(false, 0); + int count = sortedSessions.size(); + pw.println(prefix + "Sessions Stack - have " + count + " sessions:"); + String indent = prefix + " "; + for (int i = 0; i < count; i++) { + MediaSessionRecord record = sortedSessions.get(i); + record.dump(pw, indent); + pw.println(); + } + } + + /** + * Get a priority sorted list of sessions. Can filter to only return active + * sessions or sessions with specific flags. + * + * @param activeOnly True to only return active sessions, false to return + * all sessions. + * @param withFlags Only return sessions with all the specified flags set. 0 + * returns all sessions. + * @return The priority sorted list of sessions. + */ + private ArrayList<MediaSessionRecord> getPriorityListLocked(boolean activeOnly, int withFlags) { + ArrayList<MediaSessionRecord> result = new ArrayList<MediaSessionRecord>(); + int lastLocalIndex = 0; + int lastActiveIndex = 0; + int lastPublishedIndex = 0; + + int size = mSessions.size(); + for (int i = 0; i < size; i++) { + final MediaSessionRecord session = mSessions.get(i); + + if ((session.getFlags() & withFlags) != withFlags) { + continue; + } + if (!session.isActive()) { + if (!activeOnly) { + // If we're getting unpublished as well always put them at + // the end + result.add(session); + } + continue; + } + + if (session.isSystemPriority()) { + // System priority sessions are special and always go at the + // front. We expect there to only be one of these at a time. + result.add(0, session); + lastLocalIndex++; + lastActiveIndex++; + lastPublishedIndex++; + } else if (session.isPlaybackActive()) { + // TODO replace getRoute() == null with real local route check + if(session.getRoute() == null) { + // Active local sessions get top priority + result.add(lastLocalIndex, session); + lastLocalIndex++; + lastActiveIndex++; + lastPublishedIndex++; + } else { + // Then active remote sessions + result.add(lastActiveIndex, session); + lastActiveIndex++; + lastPublishedIndex++; + } + } else { + // inactive sessions go at the end in order of whoever last did + // something. + result.add(lastPublishedIndex, session); + lastPublishedIndex++; + } + } + + return result; + } + + private boolean shouldUpdatePriority(int oldState, int newState) { + if (containsState(newState, ALWAYS_PRIORITY_STATES)) { + return true; + } + if (!containsState(oldState, TRANSITION_PRIORITY_STATES) + && containsState(newState, TRANSITION_PRIORITY_STATES)) { + return true; + } + return false; + } + + private boolean containsState(int state, int[] states) { + for (int i = 0; i < states.length; i++) { + if (states[i] == state) { + return true; + } + } + return false; + } + + private void clearCache() { + mCachedDefault = null; + mCachedButtonReceiver = null; + mCachedActiveList = null; + mCachedTransportControlList = null; + } +} diff --git a/tests/OneMedia/src/com/android/onemedia/PlayerSession.java b/tests/OneMedia/src/com/android/onemedia/PlayerSession.java index 2e029f0..b7dcef7 100644 --- a/tests/OneMedia/src/com/android/onemedia/PlayerSession.java +++ b/tests/OneMedia/src/com/android/onemedia/PlayerSession.java @@ -84,11 +84,12 @@ public class PlayerSession { Log.d(TAG, "Creating session for package " + mContext.getBasePackageName()); mSession = man.createSession("OneMedia"); mSession.addCallback(mCallback); - mPerformer = mSession.setTransportPerformerEnabled(); + mPerformer = mSession.getTransportPerformer(); mPerformer.addListener(new TransportListener()); mPerformer.setPlaybackState(mPlaybackState); + mSession.setFlags(Session.FLAG_HANDLES_TRANSPORT_CONTROLS); mSession.setRouteOptions(mRouteOptions); - mSession.publish(); + mSession.setActive(true); } public void onDestroy() { |