OSDN Git Service

Add a class for managing Session priority
authorRoboErik <epastern@google.com>
Mon, 5 May 2014 21:23:49 +0000 (14:23 -0700)
committerRoboErik <epastern@google.com>
Wed, 7 May 2014 00:26:06 +0000 (17:26 -0700)
Priority is given first to the system priorty session, then to
active local sessions, then to active remote sessions, then to
the rest of the sessions. Ordering within categories is by whoever
last performed an action we associate with the user.

The stack has methods for getting filtered sets of this priority.

This also:
-Changes publish to setActive(boolean)
-Adds a flag for handling media buttons.
-Adds a flag for transport controls instead of enabling once.
-Unhides the setFlags API.
-Updates the legacy helper to use the flags.

Change-Id: I6ebeb27410de1b24149fd6e1785613ac444f0774

api/current.txt
media/java/android/media/session/ISession.aidl
media/java/android/media/session/MediaSessionLegacyHelper.java
media/java/android/media/session/Session.java
services/core/java/com/android/server/media/MediaSessionRecord.java
services/core/java/com/android/server/media/MediaSessionService.java
services/core/java/com/android/server/media/MediaSessionStack.java [new file with mode: 0644]
tests/OneMedia/src/com/android/onemedia/PlayerSession.java

index 1fe625b..cca12be 100644 (file)
@@ -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 {
index 7cab6b6..3ff07d9 100644 (file)
@@ -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
index da0100a..c07229d 100644 (file)
@@ -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;
index 227175d..194679e 100644 (file)
@@ -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;
     }
 
     /**
index e4e5979..015032b 100644 (file)
@@ -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;
             }
         }
 
index 3035521..fb858fc 100644 (file)
@@ -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 (file)
index 0000000..f9f004d
--- /dev/null
@@ -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;
+    }
+}
index 2e029f0..b7dcef7 100644 (file)
@@ -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() {