summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--api/current.txt7
-rw-r--r--media/java/android/media/session/ISession.aidl5
-rw-r--r--media/java/android/media/session/MediaSessionLegacyHelper.java13
-rw-r--r--media/java/android/media/session/Session.java91
-rw-r--r--services/core/java/com/android/server/media/MediaSessionRecord.java108
-rw-r--r--services/core/java/com/android/server/media/MediaSessionService.java79
-rw-r--r--services/core/java/com/android/server/media/MediaSessionStack.java267
-rw-r--r--tests/OneMedia/src/com/android/onemedia/PlayerSession.java5
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() {