summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author TreeHugger Robot <treehugger-gerrit@google.com> 2018-01-27 08:05:20 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2018-01-27 08:05:20 +0000
commit2a28269a79692d9505deaa714dfa84f9cc37f5ea (patch)
tree93250fec2944e1dbd5819641e7c808861b89e29d
parent98c8ad92e5eb831602fd6e396530b75eeda7fcc6 (diff)
parent6a13e1b483434d1e5d7d529bcc989f3f3fe574bc (diff)
Merge changes from topics "sessionplayer2", "mediasession2_audio_api", "session2_playbacklistener", "session2_timing"
* changes: MediaSession2: Initial commit of SessionPlayer2 MediaSession2 API set for audio focus handling MediaSession2: Add/remove playback listeners MediaSession2: Fix timing issue
-rw-r--r--media/java/android/media/IMediaSession2.aidl33
-rw-r--r--media/java/android/media/MediaController2.java17
-rw-r--r--media/java/android/media/MediaItem2.java10
-rw-r--r--media/java/android/media/MediaPlayerInterface.java18
-rw-r--r--media/java/android/media/MediaSession2.java56
-rw-r--r--media/java/android/media/PlaybackState2.java3
-rw-r--r--media/java/android/media/SessionPlayer2.java142
-rw-r--r--media/java/android/media/SessionToken2.java171
-rw-r--r--media/java/android/media/session/ISessionManager.aidl3
-rw-r--r--media/java/android/media/session/MediaSessionManager.java37
-rw-r--r--media/java/android/media/update/MediaController2Provider.java2
-rw-r--r--media/java/android/media/update/MediaSession2Provider.java9
-rw-r--r--media/java/android/media/update/SessionPlayer2Provider.java53
-rw-r--r--media/java/android/media/update/SessionToken2Provider.java34
-rw-r--r--media/java/android/media/update/StaticProvider.java8
-rw-r--r--media/java/android/media/update/TransportControlProvider.java4
-rw-r--r--services/core/java/com/android/server/media/MediaSession2Record.java108
-rw-r--r--services/core/java/com/android/server/media/MediaSessionService.java122
-rw-r--r--services/core/java/com/android/server/media/MediaSessionService2Record.java65
19 files changed, 531 insertions, 364 deletions
diff --git a/media/java/android/media/IMediaSession2.aidl b/media/java/android/media/IMediaSession2.aidl
index f79711a389a7..3783e5fd94e4 100644
--- a/media/java/android/media/IMediaSession2.aidl
+++ b/media/java/android/media/IMediaSession2.aidl
@@ -24,9 +24,7 @@ import android.os.Bundle;
*
* @hide
*/
-// TODO(jaewan): Make this oneway interface.
-// Malicious app can fake session binder and holds commands from controller.
-interface IMediaSession2 {
+oneway interface IMediaSession2 {
// TODO(jaewan): add onCommand() to send private command
// TODO(jaewan): Due to the nature of oneway calls, APIs can be called in out of order
// Add id for individual calls to address this.
@@ -35,37 +33,18 @@ interface IMediaSession2 {
// not to expose other methods to the controller whose connection wasn't accepted.
// But this would be enough for now because it's the same as existing
// MediaBrowser and MediaBrowserService.
- oneway void connect(String callingPackage, IMediaSession2Callback callback);
- oneway void release(IMediaSession2Callback caller);
+ void connect(String callingPackage, IMediaSession2Callback callback);
+ void release(IMediaSession2Callback caller);
//////////////////////////////////////////////////////////////////////////////////////////////
// send command
//////////////////////////////////////////////////////////////////////////////////////////////
- oneway void sendCommand(IMediaSession2Callback caller, in Bundle command, in Bundle args);
- oneway void sendTransportControlCommand(IMediaSession2Callback caller,
+ void sendCommand(IMediaSession2Callback caller, in Bundle command, in Bundle args);
+ void sendTransportControlCommand(IMediaSession2Callback caller,
int commandCode, long arg);
- Bundle getPlaybackState();
-
//////////////////////////////////////////////////////////////////////////////////////////////
// Get library service specific
//////////////////////////////////////////////////////////////////////////////////////////////
- oneway void getBrowserRoot(IMediaSession2Callback callback, in Bundle rootHints);
-
- //////////////////////////////////////////////////////////////////////////////////////////////
- // Callbacks -- remove them
- //////////////////////////////////////////////////////////////////////////////////////////////
- /**
- * @param callbackBinder binder to be used to notify changes.
- * @param callbackFlag one of {@link MediaController2#FLAG_CALLBACK_PLAYBACK} or
- * {@link MediaController2#FLAG_CALLBACK_SESSION_ACTIVENESS}
- * @param requestCode If >= 0, this code will be called back by the callback after the callback
- * is registered.
- */
- // TODO(jaewan): Due to the nature of the binder, calls can be called out of order.
- // Need a way to ensure calling of unregisterCallback unregisters later
- // registerCallback.
- oneway void registerCallback(IMediaSession2Callback callbackBinder,
- int callbackFlag, int requestCode);
- oneway void unregisterCallback(IMediaSession2Callback callbackBinder, int callbackFlag);
+ void getBrowserRoot(IMediaSession2Callback callback, in Bundle rootHints);
}
diff --git a/media/java/android/media/MediaController2.java b/media/java/android/media/MediaController2.java
index e2d5c5de7c42..a8b2411a9013 100644
--- a/media/java/android/media/MediaController2.java
+++ b/media/java/android/media/MediaController2.java
@@ -133,9 +133,9 @@ public class MediaController2 implements AutoCloseable {
@NonNull List<MediaItem2> list, @NonNull PlaylistParams param) { }
/**
- * Called when the playback state is changed.
+ * Called when the playback state is changed, or connection success.
*
- * @param state
+ * @param state latest playback state
*/
public void onPlaybackStateChanged(@NonNull PlaybackState2 state) { }
@@ -254,12 +254,13 @@ public class MediaController2 implements AutoCloseable {
@NonNull @CallbackExecutor Executor executor, @NonNull ControllerCallback callback) {
super();
+ mProvider = createProvider(context, token, executor, callback);
// This also connects to the token.
// Explicit connect() isn't added on purpose because retrying connect() is impossible with
// session whose session binder is only valid while it's active.
// prevent a controller from reusable after the
// session is released and recreated.
- mProvider = createProvider(context, token, executor, callback);
+ mProvider.initialize();
}
MediaController2Provider createProvider(@NonNull Context context,
@@ -545,11 +546,15 @@ public class MediaController2 implements AutoCloseable {
}
/**
- * Get the latest {@link PlaybackState2} from the session.
+ * Get the lastly cached {@link PlaybackState2} from
+ * {@link ControllerCallback#onPlaybackStateChanged(PlaybackState2)}.
+ * <p>
+ * It may return {@code null} before the first callback or session has sent {@code null}
+ * playback state.
*
- * @return a playback state
+ * @return a playback state. Can be {@code null}
*/
- public PlaybackState2 getPlaybackState() {
+ public @Nullable PlaybackState2 getPlaybackState() {
return mProvider.getPlaybackState_impl();
}
diff --git a/media/java/android/media/MediaItem2.java b/media/java/android/media/MediaItem2.java
index 6a96faa6905e..f9711aa3b13d 100644
--- a/media/java/android/media/MediaItem2.java
+++ b/media/java/android/media/MediaItem2.java
@@ -36,9 +36,9 @@ import java.lang.annotation.RetentionPolicy;
* @hide
*/
public class MediaItem2 {
- // TODO(jaewan): Keep DataSourceDesc.
private final int mFlags;
private MediaMetadata2 mMetadata;
+ private DataSourceDesc mDataSourceDesc;
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@@ -64,8 +64,10 @@ public class MediaItem2 {
* @param metadata metadata with the media id.
* @param flags The flags for this item.
*/
- public MediaItem2(@NonNull MediaMetadata2 metadata, @Flags int flags) {
+ public MediaItem2(@Nullable MediaMetadata2 metadata,
+ @Nullable DataSourceDesc data, @Flags int flags) {
mFlags = flags;
+ mDataSourceDesc = data;
setMetadata(metadata);
}
@@ -139,4 +141,8 @@ public class MediaItem2 {
public @Nullable String getMediaId() {
return mMetadata.getMediaId();
}
+
+ public @Nullable DataSourceDesc getDataSourceDesc() {
+ return mDataSourceDesc;
+ }
}
diff --git a/media/java/android/media/MediaPlayerInterface.java b/media/java/android/media/MediaPlayerInterface.java
index 51bcd9b8a56a..f09fa909fab7 100644
--- a/media/java/android/media/MediaPlayerInterface.java
+++ b/media/java/android/media/MediaPlayerInterface.java
@@ -16,6 +16,8 @@
package android.media;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.media.MediaSession2.PlaylistParams;
import java.util.List;
@@ -48,9 +50,23 @@ public interface MediaPlayerInterface {
void rewind();
PlaybackState2 getPlaybackState();
+
+ /**
+ * Sets the {@link AudioAttributes} to be used during the playback of the media.
+ *
+ * @param attributes non-null <code>AudioAttributes</code>.
+ */
+ void setAudioAttributes(@NonNull AudioAttributes attributes);
+
+ /**
+ * Returns AudioAttributes that media player has.
+ */
+ @Nullable
AudioAttributes getAudioAttributes();
- void setPlaylist(List<MediaItem2> item, PlaylistParams param);
+ void setPlaylist(List<MediaItem2> list, PlaylistParams param);
+ List<MediaItem2> getPlaylist();
+
void setCurrentPlaylistItem(int index);
void setPlaylistParams(PlaylistParams params);
PlaylistParams getPlaylistParams();
diff --git a/media/java/android/media/MediaSession2.java b/media/java/android/media/MediaSession2.java
index 365c356e5e1d..245ba3b5f65d 100644
--- a/media/java/android/media/MediaSession2.java
+++ b/media/java/android/media/MediaSession2.java
@@ -83,6 +83,7 @@ public class MediaSession2 implements AutoCloseable {
// Note: Do not define IntDef because subclass can add more command code on top of these.
// TODO(jaewan): Shouldn't we pull out?
+ // TODO(jaewan): Should we also protect getPlaybackState()?
public static final int COMMAND_CODE_CUSTOM = 0;
public static final int COMMAND_CODE_PLAYBACK_START = 1;
public static final int COMMAND_CODE_PLAYBACK_PAUSE = 2;
@@ -199,7 +200,8 @@ public class MediaSession2 implements AutoCloseable {
@Override
public int hashCode() {
final int prime = 31;
- return ((mCustomCommand != null) ? mCustomCommand.hashCode() : 0) * prime + mCommandCode;
+ return ((mCustomCommand != null)
+ ? mCustomCommand.hashCode() : 0) * prime + mCommandCode;
}
}
@@ -999,14 +1001,13 @@ public class MediaSession2 implements AutoCloseable {
* framework had to add heuristics to figure out if an app is
* @hide
*/
-
MediaSession2(Context context, MediaPlayerInterface player, String id,
VolumeProvider volumeProvider, int ratingType, PendingIntent sessionActivity,
Executor callbackExecutor, SessionCallback callback) {
super();
mProvider = createProvider(context, player, id, volumeProvider, ratingType, sessionActivity,
- callbackExecutor, callback
- );
+ callbackExecutor, callback);
+ mProvider.initialize();
}
MediaSession2Provider createProvider(Context context, MediaPlayerInterface player, String id,
@@ -1079,15 +1080,6 @@ public class MediaSession2 implements AutoCloseable {
}
/**
- * Sets the {@link AudioAttributes} to be used during the playback of the video.
- *
- * @param attributes non-null <code>AudioAttributes</code>.
- */
- public void setAudioAttributes(@NonNull AudioAttributes attributes) {
- mProvider.setAudioAttributes_impl(attributes);
- }
-
- /**
* Sets which type of audio focus will be requested during the playback, or configures playback
* to not request audio focus. Valid values for focus requests are
* {@link AudioManager#AUDIOFOCUS_GAIN}, {@link AudioManager#AUDIOFOCUS_GAIN_TRANSIENT},
@@ -1270,6 +1262,10 @@ public class MediaSession2 implements AutoCloseable {
mProvider.setPlaylist_impl(playlist, param);
}
+ public List<MediaItem2> getPlaylist() {
+ return mProvider.getPlaylist_impl();
+ }
+
/**
* Sets the {@link PlaylistParams} for the current play list. Repeat/shuffle mode and metadata
* for the list can be set by calling this method.
@@ -1288,4 +1284,38 @@ public class MediaSession2 implements AutoCloseable {
public PlaylistParams getPlaylistParams() {
return mProvider.getPlaylistParams_impl();
}
+
+ /*
+ * Add a {@link PlaybackListener} to listen changes in the underlying
+ * {@link MediaPlayerInterface}. Listener will be called immediately to tell the current value.
+ * <p>
+ * Added listeners will be also called when the underlying player is changed.
+ *
+ * @param executor the call listener
+ * @param listener the listener that will be run
+ * @throws IllegalArgumentException when either the listener or handler is {@code null}.
+ */
+ public void addPlaybackListener(@NonNull @CallbackExecutor Executor executor,
+ @NonNull PlaybackListener listener) {
+ mProvider.addPlaybackListener_impl(executor, listener);
+ }
+
+ /**
+ * Remove previously added {@link PlaybackListener}.
+ *
+ * @param listener the listener to be removed
+ * @throws IllegalArgumentException if the listener is {@code null}.
+ */
+ public void removePlaybackListener(@NonNull PlaybackListener listener) {
+ mProvider.removePlaybackListener_impl(listener);
+ }
+
+ /**
+ * Return the {@link PlaybackState2} from the player.
+ *
+ * @return playback state
+ */
+ public PlaybackState2 getPlaybackState() {
+ return mProvider.getPlaybackState_impl();
+ }
}
diff --git a/media/java/android/media/PlaybackState2.java b/media/java/android/media/PlaybackState2.java
index 7688fbc9bb41..da776ebefa38 100644
--- a/media/java/android/media/PlaybackState2.java
+++ b/media/java/android/media/PlaybackState2.java
@@ -108,6 +108,9 @@ public final class PlaybackState2 {
private final long mActiveItemId;
private final CharSequence mErrorMessage;
+ // TODO(jaewan): Better error handling?
+ // E.g. media item at #2 has issue, but continue playing #3
+ // login error. fire intent xxx to login
public PlaybackState2(int state, long position, long updateTime, float speed,
long bufferedPosition, long activeItemId, CharSequence error) {
mState = state;
diff --git a/media/java/android/media/SessionPlayer2.java b/media/java/android/media/SessionPlayer2.java
new file mode 100644
index 000000000000..8e9ed23dab59
--- /dev/null
+++ b/media/java/android/media/SessionPlayer2.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright 2018 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 android.media;
+
+import android.annotation.SystemApi;
+import android.content.Context;
+import android.media.MediaSession2.PlaylistParams;
+import android.media.update.ApiLoader;
+import android.media.update.SessionPlayer2Provider;
+
+import java.util.List;
+import java.util.concurrent.Executor;
+
+/**
+ * Implementation of the {@link MediaPlayerInterface} which is backed by the {@link MediaPlayer2}
+ * @hide
+ */
+public class SessionPlayer2 implements MediaPlayerInterface {
+ private final SessionPlayer2Provider mProvider;
+
+ public SessionPlayer2(Context context) {
+ mProvider = ApiLoader.getProvider(context).createSessionPlayer2(context, this);
+ }
+
+ @Override
+ public void play() {
+ mProvider.play_impl();
+ }
+
+ @Override
+ public void prepare() {
+ mProvider.prepare_impl();
+ }
+
+ @Override
+ public void pause() {
+ mProvider.pause_impl();
+ }
+
+ @Override
+ public void stop() {
+ mProvider.stop_impl();
+ }
+
+ @Override
+ public void skipToPrevious() {
+ mProvider.skipToPrevious_impl();
+ }
+
+ @Override
+ public void skipToNext() {
+ mProvider.skipToNext_impl();
+ }
+
+ @Override
+ public void seekTo(long pos) {
+ mProvider.seekTo_impl(pos);
+ }
+
+ @Override
+ public void fastForward() {
+ mProvider.fastForward_impl();
+ }
+
+ @Override
+ public void rewind() {
+ mProvider.rewind_impl();
+ }
+
+ @Override
+ public PlaybackState2 getPlaybackState() {
+ return mProvider.getPlaybackState_impl();
+ }
+
+ @Override
+ public void setAudioAttributes(AudioAttributes attributes) {
+ mProvider.setAudioAttributes_impl(attributes);
+ }
+
+ @Override
+ public AudioAttributes getAudioAttributes() {
+ return mProvider.getAudioAttributes_impl();
+ }
+
+ @Override
+ public void setPlaylist(List<MediaItem2> list, PlaylistParams param) {
+ mProvider.setPlaylist_impl(list, param);
+ }
+
+ @Override
+ public List<MediaItem2> getPlaylist() {
+ return mProvider.getPlaylist_impl();
+ }
+
+ @Override
+ public void setCurrentPlaylistItem(int index) {
+ mProvider.setCurrentPlaylistItem_impl(index);
+ }
+
+ @Override
+ public void setPlaylistParams(PlaylistParams params) {
+ mProvider.setPlaylistParams_impl(params);
+ }
+
+ @Override
+ public PlaylistParams getPlaylistParams() {
+ return mProvider.getPlaylistParams_impl();
+ }
+
+ @Override
+ public void addPlaybackListener(Executor executor, PlaybackListener listener) {
+ mProvider.addPlaybackListener_impl(executor, listener);
+ }
+
+ @Override
+ public void removePlaybackListener(PlaybackListener listener) {
+ mProvider.removePlaybackListener_impl(listener);
+ }
+
+ public MediaPlayer2 getPlayer() {
+ return mProvider.getPlayer_impl();
+ }
+
+ @SystemApi
+ public SessionPlayer2Provider getProvider() {
+ return mProvider;
+ }
+}
diff --git a/media/java/android/media/SessionToken2.java b/media/java/android/media/SessionToken2.java
index 0abb85229ffc..7591eb3a25be 100644
--- a/media/java/android/media/SessionToken2.java
+++ b/media/java/android/media/SessionToken2.java
@@ -19,10 +19,13 @@ package android.media;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.content.Context;
import android.media.session.MediaSessionManager;
+import android.media.update.ApiLoader;
+import android.media.update.SessionToken2Provider;
import android.os.Bundle;
-import android.os.IBinder;
-import android.text.TextUtils;
+import android.os.IInterface;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -37,7 +40,6 @@ import java.lang.annotation.RetentionPolicy;
* It can be also obtained by {@link MediaSessionManager}.
* @hide
*/
-// TODO(jaewan): Move Token to updatable!
public final class SessionToken2 {
@Retention(RetentionPolicy.SOURCE)
@IntDef(value = {TYPE_SESSION, TYPE_SESSION_SERVICE, TYPE_LIBRARY_SERVICE})
@@ -48,91 +50,81 @@ public final class SessionToken2 {
public static final int TYPE_SESSION_SERVICE = 1;
public static final int TYPE_LIBRARY_SERVICE = 2;
- private static final String KEY_TYPE = "android.media.token.type";
- private static final String KEY_PACKAGE_NAME = "android.media.token.package_name";
- private static final String KEY_SERVICE_NAME = "android.media.token.service_name";
- private static final String KEY_ID = "android.media.token.id";
- private static final String KEY_SESSION_BINDER = "android.media.token.session_binder";
+ private final SessionToken2Provider mProvider;
- private final @TokenType int mType;
- private final String mPackageName;
- private final String mServiceName;
- private final String mId;
- private final IMediaSession2 mSessionBinder;
+ /**
+ * Constructor for the token. You can only create token for session service or library service
+ * to use by {@link MediaController2} or {@link MediaBrowser2}.
+ *
+ * @param context context
+ * @param type type
+ * @param packageName package name
+ * @param serviceName name of service. Can be {@code null} if it's not an service.
+ */
+ public SessionToken2(@NonNull Context context, @TokenType int type, @NonNull String packageName,
+ @NonNull String serviceName) {
+ this(context, -1, type, packageName, serviceName, null, null);
+ }
/**
* Constructor for the token.
*
- * @hide
+ * @param context context
+ * @param uid uid
* @param type type
* @param packageName package name
- * @param id id
* @param serviceName name of service. Can be {@code null} if it's not an service.
- * @param sessionBinder binder for this session. Can be {@code null} if it's service.
- * @hide
+ * @param id id. Can be {@code null} if serviceName is specified.
+ * @param sessionBinderInterface sessionBinder. Required for the session.
*/
- // TODO(jaewan): UID is also needed.
- // TODO(jaewan): Unhide
- public SessionToken2(@TokenType int type, @NonNull String packageName, @NonNull String id,
- @Nullable String serviceName, @Nullable IMediaSession2 sessionBinder) {
- // TODO(jaewan): Add sanity check.
- mType = type;
- mPackageName = packageName;
- mId = id;
- mServiceName = serviceName;
- mSessionBinder = sessionBinder;
+ @SystemApi
+ public SessionToken2(@NonNull Context context, int uid, @TokenType int type,
+ @NonNull String packageName, @Nullable String serviceName, @Nullable String id,
+ @Nullable IInterface sessionBinderInterface) {
+ mProvider = ApiLoader.getProvider(context)
+ .createSessionToken2(context, this, uid, type, packageName,
+ serviceName, id, sessionBinderInterface);
}
+ @Override
public int hashCode() {
- final int prime = 31;
- return mType
- + prime * (mPackageName.hashCode()
- + prime * (mId.hashCode()
- + prime * ((mServiceName != null ? mServiceName.hashCode() : 0)
- + prime * (mSessionBinder != null ? mSessionBinder.asBinder().hashCode() : 0))));
+ return mProvider.hashCode_impl();
}
@Override
public boolean equals(Object obj) {
- if (this == obj)
- return true;
- if (obj == null)
- return false;
- if (getClass() != obj.getClass())
- return false;
- SessionToken2 other = (SessionToken2) obj;
- if (!mPackageName.equals(other.getPackageName())
- || !mServiceName.equals(other.getServiceName())
- || !mId.equals(other.getId())
- || mType != other.getType()) {
- return false;
- }
- if (mSessionBinder == other.getSessionBinder()) {
- return true;
- } else if (mSessionBinder == null || other.getSessionBinder() == null) {
- return false;
- }
- return mSessionBinder.asBinder().equals(other.getSessionBinder().asBinder());
+ return mProvider.equals_impl(obj);
}
@Override
public String toString() {
- return "SessionToken {pkg=" + mPackageName + " id=" + mId + " type=" + mType
- + " service=" + mServiceName + " binder=" + mSessionBinder + "}";
+ return mProvider.toString_impl();
+ }
+
+ @SystemApi
+ public SessionToken2Provider getProvider() {
+ return mProvider;
+ }
+
+ /**
+ * @return uid of the session
+ */
+ public int getUid() {
+ return mProvider.getUid_impl();
}
/**
* @return package name
*/
public String getPackageName() {
- return mPackageName;
+ return mProvider.getPackageName_impl();
}
/**
* @return id
*/
public String getId() {
- return mId;
+ return mProvider.getId_imp();
}
/**
@@ -141,82 +133,23 @@ public final class SessionToken2 {
* @see #TYPE_SESSION_SERVICE
*/
public @TokenType int getType() {
- return mType;
- }
-
- /**
- * @return session binder.
- * @hide
- */
- public @Nullable IMediaSession2 getSessionBinder() {
- return mSessionBinder;
- }
-
- /**
- * @return service name if it's session service.
- * @hide
- */
- public @Nullable String getServiceName() {
- return mServiceName;
+ return mProvider.getType_impl();
}
/**
* Create a token from the bundle, exported by {@link #toBundle()}.
- *
* @param bundle
* @return
*/
- public static SessionToken2 fromBundle(@NonNull Bundle bundle) {
- if (bundle == null) {
- return null;
- }
- final @TokenType int type = bundle.getInt(KEY_TYPE, -1);
- final String packageName = bundle.getString(KEY_PACKAGE_NAME);
- final String serviceName = bundle.getString(KEY_SERVICE_NAME);
- final String id = bundle.getString(KEY_ID);
- final IBinder sessionBinder = bundle.getBinder(KEY_SESSION_BINDER);
-
- // Sanity check.
- switch (type) {
- case TYPE_SESSION:
- if (!(sessionBinder instanceof IMediaSession2)) {
- throw new IllegalArgumentException("Session needs sessionBinder");
- }
- break;
- case TYPE_SESSION_SERVICE:
- if (TextUtils.isEmpty(serviceName)) {
- throw new IllegalArgumentException("Session service needs service name");
- }
- if (sessionBinder != null && !(sessionBinder instanceof IMediaSession2)) {
- throw new IllegalArgumentException("Invalid session binder");
- }
- break;
- default:
- throw new IllegalArgumentException("Invalid type");
- }
- if (TextUtils.isEmpty(packageName) || id == null) {
- throw new IllegalArgumentException("Package name nor ID cannot be null.");
- }
- // TODO(jaewan): Revisit here when we add connection callback to the session for individual
- // controller's permission check. With it, sessionBinder should be available
- // if and only if for session, not session service.
- return new SessionToken2(type, packageName, id, serviceName,
- sessionBinder != null ? IMediaSession2.Stub.asInterface(sessionBinder) : null);
+ public static SessionToken2 fromBundle(@NonNull Context context, @NonNull Bundle bundle) {
+ return ApiLoader.getProvider(context).SessionToken2_fromBundle(context, bundle);
}
/**
* Create a {@link Bundle} from this token to share it across processes.
- *
* @return Bundle
*/
public Bundle toBundle() {
- Bundle bundle = new Bundle();
- bundle.putString(KEY_PACKAGE_NAME, mPackageName);
- bundle.putString(KEY_SERVICE_NAME, mServiceName);
- bundle.putString(KEY_ID, mId);
- bundle.putInt(KEY_TYPE, mType);
- bundle.putBinder(KEY_SESSION_BINDER,
- mSessionBinder != null ? mSessionBinder.asBinder() : null);
- return bundle;
+ return mProvider.toBundle_impl();
}
}
diff --git a/media/java/android/media/session/ISessionManager.aidl b/media/java/android/media/session/ISessionManager.aidl
index b8463ddba02e..8135106e51c9 100644
--- a/media/java/android/media/session/ISessionManager.aidl
+++ b/media/java/android/media/session/ISessionManager.aidl
@@ -52,6 +52,7 @@ interface ISessionManager {
void setOnMediaKeyListener(in IOnMediaKeyListener listener);
// MediaSession2
- Bundle createSessionToken(String callingPackage, String id, IMediaSession2 binder);
+ boolean onSessionCreated(in Bundle sessionToken);
+ void onSessionDestroyed(in Bundle sessionToken);
List<Bundle> getSessionTokens(boolean activeSessionOnly, boolean sessionServiceOnly);
}
diff --git a/media/java/android/media/session/MediaSessionManager.java b/media/java/android/media/session/MediaSessionManager.java
index 81b4603ebf93..1023a10e8dd3 100644
--- a/media/java/android/media/session/MediaSessionManager.java
+++ b/media/java/android/media/session/MediaSessionManager.java
@@ -337,19 +337,34 @@ public final class MediaSessionManager {
/**
* Called when a {@link MediaSession2} is created.
- *
* @hide
*/
// TODO(jaewan): System API
- public SessionToken2 createSessionToken(@NonNull String callingPackage, @NonNull String id,
- @NonNull IMediaSession2 binder) {
+ public boolean onSessionCreated(@NonNull SessionToken2 token) {
+ if (token == null) {
+ return false;
+ }
+ try {
+ return mService.onSessionCreated(token.toBundle());
+ } catch (RemoteException e) {
+ Log.wtf(TAG, "Cannot communicate with the service.", e);
+ }
+ return false;
+ }
+
+ /** Called when a {@link MediaSession2} is destroyed.
+ * @hide
+ */
+ // TODO(jaewan): System API
+ public void onSessionDestroyed(@NonNull SessionToken2 token) {
+ if (token == null) {
+ return;
+ }
try {
- Bundle bundle = mService.createSessionToken(callingPackage, id, binder);
- return SessionToken2.fromBundle(bundle);
+ mService.onSessionDestroyed(token.toBundle());
} catch (RemoteException e) {
Log.wtf(TAG, "Cannot communicate with the service.", e);
}
- return null;
}
/**
@@ -367,7 +382,7 @@ public final class MediaSessionManager {
try {
List<Bundle> bundles = mService.getSessionTokens(
/* activeSessionOnly */ true, /* sessionServiceOnly */ false);
- return toTokenList(bundles);
+ return toTokenList(mContext, bundles);
} catch (RemoteException e) {
Log.wtf(TAG, "Cannot communicate with the service.", e);
return Collections.emptyList();
@@ -387,7 +402,7 @@ public final class MediaSessionManager {
try {
List<Bundle> bundles = mService.getSessionTokens(
/* activeSessionOnly */ false, /* sessionServiceOnly */ true);
- return toTokenList(bundles);
+ return toTokenList(mContext, bundles);
} catch (RemoteException e) {
Log.wtf(TAG, "Cannot communicate with the service.", e);
return Collections.emptyList();
@@ -410,18 +425,18 @@ public final class MediaSessionManager {
try {
List<Bundle> bundles = mService.getSessionTokens(
/* activeSessionOnly */ false, /* sessionServiceOnly */ false);
- return toTokenList(bundles);
+ return toTokenList(mContext, bundles);
} catch (RemoteException e) {
Log.wtf(TAG, "Cannot communicate with the service.", e);
return Collections.emptyList();
}
}
- private static List<SessionToken2> toTokenList(List<Bundle> bundles) {
+ private static List<SessionToken2> toTokenList(Context context, List<Bundle> bundles) {
List<SessionToken2> tokens = new ArrayList<>();
if (bundles != null) {
for (int i = 0; i < bundles.size(); i++) {
- SessionToken2 token = SessionToken2.fromBundle(bundles.get(i));
+ SessionToken2 token = SessionToken2.fromBundle(context, bundles.get(i));
if (token != null) {
tokens.add(token);
}
diff --git a/media/java/android/media/update/MediaController2Provider.java b/media/java/android/media/update/MediaController2Provider.java
index cf35358c7841..b72fda94c688 100644
--- a/media/java/android/media/update/MediaController2Provider.java
+++ b/media/java/android/media/update/MediaController2Provider.java
@@ -35,6 +35,8 @@ import java.util.List;
* @hide
*/
public interface MediaController2Provider extends TransportControlProvider {
+ void initialize();
+
void close_impl();
SessionToken2 getSessionToken_impl();
boolean isConnected_impl();
diff --git a/media/java/android/media/update/MediaSession2Provider.java b/media/java/android/media/update/MediaSession2Provider.java
index 801bdeb68eef..abe3cae789c9 100644
--- a/media/java/android/media/update/MediaSession2Provider.java
+++ b/media/java/android/media/update/MediaSession2Provider.java
@@ -19,6 +19,7 @@ package android.media.update;
import android.media.AudioAttributes;
import android.media.MediaItem2;
import android.media.MediaPlayerInterface;
+import android.media.MediaPlayerInterface.PlaybackListener;
import android.media.MediaSession2.Command;
import android.media.MediaSession2.CommandButton;
import android.media.MediaSession2.CommandGroup;
@@ -30,11 +31,14 @@ import android.os.Bundle;
import android.os.ResultReceiver;
import java.util.List;
+import java.util.concurrent.Executor;
/**
* @hide
*/
public interface MediaSession2Provider extends TransportControlProvider {
+ void initialize();
+
void close_impl();
void setPlayer_impl(MediaPlayerInterface player);
void setPlayer_impl(MediaPlayerInterface player, VolumeProvider volumeProvider);
@@ -42,7 +46,6 @@ public interface MediaSession2Provider extends TransportControlProvider {
SessionToken2 getToken_impl();
List<ControllerInfo> getConnectedControllers_impl();
void setCustomLayout_impl(ControllerInfo controller, List<CommandButton> layout);
- void setAudioAttributes_impl(AudioAttributes attributes);
void setAudioFocusRequest_impl(int focusGain);
void setAllowedCommands_impl(ControllerInfo controller, CommandGroup commands);
@@ -51,9 +54,13 @@ public interface MediaSession2Provider extends TransportControlProvider {
ResultReceiver receiver);
void sendCustomCommand_impl(Command command, Bundle args);
void setPlaylist_impl(List<MediaItem2> playlist, PlaylistParams param);
+ List<MediaItem2> getPlaylist_impl();
void setPlaylistParams_impl(PlaylistParams params);
PlaylistParams getPlaylistParams_impl();
+ void addPlaybackListener_impl(Executor executor, PlaybackListener listener);
+ void removePlaybackListener_impl(PlaybackListener listener);
+
interface ControllerInfoProvider {
String getPackageName_impl();
int getUid_impl();
diff --git a/media/java/android/media/update/SessionPlayer2Provider.java b/media/java/android/media/update/SessionPlayer2Provider.java
new file mode 100644
index 000000000000..a084a59c5c06
--- /dev/null
+++ b/media/java/android/media/update/SessionPlayer2Provider.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2018 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 android.media.update;
+
+import android.media.AudioAttributes;
+import android.media.MediaItem2;
+import android.media.MediaPlayer2;
+import android.media.MediaPlayerInterface.PlaybackListener;
+import android.media.MediaSession2.PlaylistParams;
+import android.media.PlaybackState2;
+
+import java.util.List;
+import java.util.concurrent.Executor;
+
+/**
+ * @hide
+ */
+public interface SessionPlayer2Provider {
+ void play_impl();
+ void prepare_impl();
+ void pause_impl();
+ void stop_impl();
+ void skipToPrevious_impl();
+ void skipToNext_impl();
+ void seekTo_impl(long pos);
+ void fastForward_impl();
+ void rewind_impl();
+ PlaybackState2 getPlaybackState_impl();
+ void setAudioAttributes_impl(AudioAttributes attributes);
+ AudioAttributes getAudioAttributes_impl();
+ void setPlaylist_impl(List<MediaItem2> list, PlaylistParams param);
+ List<MediaItem2> getPlaylist_impl();
+ void setCurrentPlaylistItem_impl(int index);
+ void setPlaylistParams_impl(PlaylistParams params);
+ PlaylistParams getPlaylistParams_impl();
+ void addPlaybackListener_impl(Executor executor, PlaybackListener listener);
+ void removePlaybackListener_impl(PlaybackListener listener);
+ MediaPlayer2 getPlayer_impl();
+}
diff --git a/media/java/android/media/update/SessionToken2Provider.java b/media/java/android/media/update/SessionToken2Provider.java
new file mode 100644
index 000000000000..95d6ce07b8a8
--- /dev/null
+++ b/media/java/android/media/update/SessionToken2Provider.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2018 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 android.media.update;
+
+import android.os.Bundle;
+
+/**
+ * @hide
+ */
+public interface SessionToken2Provider {
+ String getPackageName_impl();
+ String getId_imp();
+ int getType_impl();
+ int getUid_impl();
+ Bundle toBundle_impl();
+
+ int hashCode_impl();
+ boolean equals_impl(Object obj);
+ String toString_impl();
+}
diff --git a/media/java/android/media/update/StaticProvider.java b/media/java/android/media/update/StaticProvider.java
index 7faac951ce98..963bc7475ceb 100644
--- a/media/java/android/media/update/StaticProvider.java
+++ b/media/java/android/media/update/StaticProvider.java
@@ -30,10 +30,12 @@ import android.media.MediaPlayerInterface;
import android.media.MediaSession2;
import android.media.MediaSession2.SessionCallback;
import android.media.MediaSessionService2;
+import android.media.SessionPlayer2;
import android.media.SessionToken2;
import android.media.VolumeProvider;
import android.media.update.MediaLibraryService2Provider.MediaLibrarySessionProvider;
import android.media.update.MediaSession2Provider.ControllerInfoProvider;
+import android.os.Bundle;
import android.os.IInterface;
import android.util.AttributeSet;
import android.widget.MediaControlView2;
@@ -71,4 +73,10 @@ public interface StaticProvider {
MediaLibrarySession instance, MediaPlayerInterface player, String id,
VolumeProvider volumeProvider, int ratingType, PendingIntent sessionActivity,
Executor executor, MediaLibrarySessionCallback callback);
+ SessionToken2Provider createSessionToken2(Context context, SessionToken2 instance,
+ int uid, int type, String packageName, String serviceName, String id,
+ IInterface sessionBinderInterface);
+ SessionToken2 SessionToken2_fromBundle(Context context, Bundle bundle);
+
+ SessionPlayer2Provider createSessionPlayer2(Context context, SessionPlayer2 instance);
}
diff --git a/media/java/android/media/update/TransportControlProvider.java b/media/java/android/media/update/TransportControlProvider.java
index 0c87063efb44..44f82b295629 100644
--- a/media/java/android/media/update/TransportControlProvider.java
+++ b/media/java/android/media/update/TransportControlProvider.java
@@ -16,6 +16,8 @@
package android.media.update;
+import android.media.PlaybackState2;
+
/**
* @hide
*/
@@ -31,4 +33,6 @@ public interface TransportControlProvider {
void rewind_impl();
void seekTo_impl(long pos);
void setCurrentPlaylistItem_impl(int index);
+
+ PlaybackState2 getPlaybackState_impl();
}
diff --git a/services/core/java/com/android/server/media/MediaSession2Record.java b/services/core/java/com/android/server/media/MediaSession2Record.java
index 4000a11dbf15..ffddf60817ee 100644
--- a/services/core/java/com/android/server/media/MediaSession2Record.java
+++ b/services/core/java/com/android/server/media/MediaSession2Record.java
@@ -44,138 +44,88 @@ class MediaSession2Record {
private static final boolean DEBUG = true; // TODO(jaewan): Change
private final Context mContext;
+ private final SessionToken2 mSessionToken;
private final SessionDestroyedListener mSessionDestroyedListener;
// TODO(jaewan): Replace these with the mContext.getMainExecutor()
- private final Handler mMainHandler;
private final Executor mMainExecutor;
private MediaController2 mController;
- private ControllerCallback mControllerCallback;
-
- private int mSessionPid;
/**
* Constructor
*/
- public MediaSession2Record(@NonNull Context context,
+ public MediaSession2Record(@NonNull Context context, @NonNull SessionToken2 token,
@NonNull SessionDestroyedListener listener) {
mContext = context;
+ mSessionToken = token;
mSessionDestroyedListener = listener;
-
- mMainHandler = new Handler(Looper.getMainLooper());
- mMainExecutor = (runnable) -> {
- mMainHandler.post(runnable);
- };
- }
-
- public int getSessionPid() {
- return mSessionPid;
+ mMainExecutor = (runnable) -> runnable.run();
}
public Context getContext() {
return mContext;
}
- @CallSuper
public void onSessionDestroyed() {
if (mController != null) {
- mControllerCallback.destroy();
mController.close();
+ // close() triggers ControllerCallback.onDisconnected() here already.
mController = null;
}
- mSessionPid = 0;
}
- /**
- * Create session token and tell server that session is now active.
- *
- * @param sessionPid session's pid
- * @return a token if successfully set, {@code null} if sanity check fails.
- */
- // TODO(jaewan): also add uid for multiuser support
- @CallSuper
- public @Nullable
- SessionToken2 createSessionToken(int sessionPid, String packageName, String id,
- IMediaSession2 sessionBinder) {
+ public boolean onSessionCreated(SessionToken2 token) {
if (mController != null) {
- if (mSessionPid != sessionPid) {
- // A package uses the same id for session across the different process.
- return null;
- }
- // If a session becomes inactive and then active again very quickly, previous 'inactive'
- // may not have delivered yet. Check if it's the case and destroy controller before
- // creating its session record to prevents getXXTokens() API from returning duplicated
- // tokens.
- // TODO(jaewan): Change this. If developer is really creating two sessions with the same
- // id, this will silently invalidate previous session and no way for
- // developers to know that.
- // Instead, keep the list of static session ids from our APIs.
- // Also change Controller2Impl.onConnectionChanged / getController.
- // Also clean up ControllerCallback#destroy().
- if (DEBUG) {
- Log.d(TAG, "Session is recreated almost immediately. " + this);
- }
- onSessionDestroyed();
+ // Disclaimer: This may fail if following happens for an app.
+ // Step 1) Create a session in the process #1
+ // Step 2) Process #1 is killed
+ // Step 3) Before the death of process #1 is delivered,
+ // (i.e. ControllerCallback#onDisconnected is called),
+ // new process is started and create another session with the same
+ // id in the new process.
+ // Step 4) fail!!! But this is tricky case that wouldn't happen in normal.
+ Log.w(TAG, "Cannot create a new session with the id=" + token.getId() + " in the"
+ + " pkg=" + token.getPackageName() + ". ID should be unique in a package");
+ return false;
}
- mController = onCreateMediaController(packageName, id, sessionBinder);
- mSessionPid = sessionPid;
- return mController.getSessionToken();
+ mController = new MediaController2(mContext, token, mMainExecutor,
+ new ControllerCallback());
+ return true;
}
/**
- * Called when session becomes active and needs controller to listen session's activeness.
- * <p>
- * Should be overridden by subclasses to create token with its own extra information.
+ * @return token
*/
- MediaController2 onCreateMediaController(
- String packageName, String id, IMediaSession2 sessionBinder) {
- SessionToken2 token = new SessionToken2(
- SessionToken2.TYPE_SESSION, packageName, id, null, sessionBinder);
- return createMediaController(token);
- }
-
- final MediaController2 createMediaController(SessionToken2 token) {
- mControllerCallback = new ControllerCallback();
- return new MediaController2(mContext, token, mMainExecutor, mControllerCallback);
+ public SessionToken2 getToken() {
+ return mSessionToken;
}
/**
- * @return controller. Note that framework can only call oneway calls.
+ * @return controller
*/
- public SessionToken2 getToken() {
- return mController == null ? null : mController.getSessionToken();
+ public MediaController2 getController() {
+ return mController;
}
@Override
public String toString() {
return getToken() == null
- ? "Token {null}"
- : "SessionRecord {pid=" + mSessionPid + ", " + getToken().toString() + "}";
+ ? "Token {null}" : "SessionRecord {" + getToken().toString() + "}";
}
private class ControllerCallback extends MediaController2.ControllerCallback {
- private final AtomicBoolean mIsActive = new AtomicBoolean(true);
-
- // This is called on the main thread with no lock. So place ensure followings.
+ // This is called on the random thread with no lock. So place ensure followings.
// 1. Don't touch anything in the parent class that needs synchronization.
// All other APIs in the MediaSession2Record assumes that server would use them with
// the lock hold.
- // 2. This can be called after the controller registered is released.
+ // 2. This can be called after the controller registered is closed.
@Override
public void onDisconnected() {
- if (!mIsActive.get()) {
- return;
- }
if (DEBUG) {
Log.d(TAG, "onDisconnected, token=" + getToken());
}
mSessionDestroyedListener.onSessionDestroyed(MediaSession2Record.this);
}
-
- // TODO(jaewan): Remove this API when we revisit createSessionToken()
- public void destroy() {
- mIsActive.set(false);
- }
};
}
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index c7f6014fa1b0..1d6d1ed46f54 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -16,6 +16,8 @@
package com.android.server.media;
+import static android.media.SessionToken2.TYPE_SESSION;
+
import android.app.ActivityManager;
import android.app.INotificationManager;
import android.app.KeyguardManager;
@@ -28,6 +30,7 @@ import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.content.pm.UserInfo;
@@ -130,15 +133,9 @@ public class MediaSessionService extends SystemService implements Monitor {
private final List<MediaSession2Record> mSessions = new ArrayList<>();
private final MediaSession2Record.SessionDestroyedListener mSessionDestroyedListener =
- (MediaSession2Record record) -> {
+ (record) -> {
synchronized (mLock) {
- if (DEBUG) {
- Log.d(TAG, record.toString() + " becomes inactive");
- }
- record.onSessionDestroyed();
- if (!(record instanceof MediaSessionService2Record)) {
- mSessions.remove(record);
- }
+ destroySessionLocked(record);
}
};
@@ -446,14 +443,16 @@ public class MediaSessionService extends SystemService implements Monitor {
}
// TODO(jaewan): Query per users.
+ // TODO(jaewan): Similar codes are also at the updatable. Can't we share codes?
+ PackageManager manager = getContext().getPackageManager();
List<ResolveInfo> services = new ArrayList<>();
// If multiple actions are declared for a service, browser gets higher priority.
- List<ResolveInfo> libraryServices = getContext().getPackageManager().queryIntentServices(
+ List<ResolveInfo> libraryServices = manager.queryIntentServices(
new Intent(MediaLibraryService2.SERVICE_INTERFACE), PackageManager.GET_META_DATA);
if (libraryServices != null) {
services.addAll(libraryServices);
}
- List<ResolveInfo> sessionServices = getContext().getPackageManager().queryIntentServices(
+ List<ResolveInfo> sessionServices = manager.queryIntentServices(
new Intent(MediaSessionService2.SERVICE_INTERFACE), PackageManager.GET_META_DATA);
if (sessionServices != null) {
services.addAll(sessionServices);
@@ -468,12 +467,19 @@ public class MediaSessionService extends SystemService implements Monitor {
continue;
}
ServiceInfo serviceInfo = services.get(i).serviceInfo;
+ int uid;
+ try {
+ uid = manager.getPackageUid(serviceInfo.packageName,
+ PackageManager.GET_META_DATA);
+ } catch (NameNotFoundException e) {
+ continue;
+ }
String id = (serviceInfo.metaData != null) ? serviceInfo.metaData.getString(
MediaSessionService2.SERVICE_META_DATA) : null;
// Do basic sanity check
// TODO(jaewan): also santity check if it's protected with the system|privileged
// permission
- boolean conflict = (getSessionRecordLocked(serviceInfo.name, id) != null);
+ boolean conflict = (getSessionRecordLocked(uid, serviceInfo.name, id) != null);
if (conflict) {
Log.w(TAG, serviceInfo.packageName + " contains multiple"
+ " MediaSessionService2s declared in the manifest with"
@@ -481,10 +487,12 @@ public class MediaSessionService extends SystemService implements Monitor {
+ serviceInfo.packageName + "/" + serviceInfo.name);
} else {
int type = (libraryServices.contains(services.get(i)))
- ? SessionToken2.TYPE_LIBRARY_SERVICE : SessionToken2.TYPE_SESSION_SERVICE;
- MediaSessionService2Record record =
- new MediaSessionService2Record(getContext(), mSessionDestroyedListener,
- type, serviceInfo.packageName, serviceInfo.name, id);
+ ? SessionToken2.TYPE_LIBRARY_SERVICE
+ : SessionToken2.TYPE_SESSION_SERVICE;
+ SessionToken2 token = new SessionToken2(getContext(), uid, type,
+ serviceInfo.packageName, serviceInfo.name, id, null);
+ MediaSession2Record record = new MediaSession2Record(getContext(),
+ token, mSessionDestroyedListener);
mSessions.add(record);
}
}
@@ -497,17 +505,27 @@ public class MediaSessionService extends SystemService implements Monitor {
}
}
- MediaSession2Record getSessionRecordLocked(String packageName, String id) {
+ private MediaSession2Record getSessionRecordLocked(int uid, String packageName, String id) {
for (int i = 0; i < mSessions.size(); i++) {
- MediaSession2Record record = mSessions.get(i);
- if (record.getToken().getPackageName().equals(packageName)
- && record.getToken().getId().equals(id)) {
- return record;
+ SessionToken2 token = mSessions.get(i).getToken();
+ if (token.getUid() == uid && token.getPackageName().equals(packageName)
+ && token.getId().equals(id)) {
+ return mSessions.get(i);
}
}
return null;
}
+ private void destroySessionLocked(MediaSession2Record record) {
+ if (DEBUG) {
+ Log.d(TAG, record.toString() + " becomes inactive");
+ }
+ record.onSessionDestroyed();
+ if (record.getToken().getType() == TYPE_SESSION) {
+ mSessions.remove(record);
+ }
+ }
+
private void enforcePackageName(String packageName, int uid) {
if (TextUtils.isEmpty(packageName)) {
throw new IllegalArgumentException("packageName may not be empty");
@@ -1410,29 +1428,55 @@ public class MediaSessionService extends SystemService implements Monitor {
}
@Override
- public Bundle createSessionToken(String sessionPackage, String id,
- IMediaSession2 sessionBinder) throws RemoteException {
- int uid = Binder.getCallingUid();
- int pid = Binder.getCallingPid();
-
- MediaSession2Record record;
- SessionToken2 token;
- // TODO(jaewan): Add sanity check for the token if calling package is from uid.
+ public boolean onSessionCreated(Bundle sessionToken) {
+ final int uid = Binder.getCallingUid();
+ final int pid = Binder.getCallingPid();
+ final SessionToken2 token = SessionToken2.fromBundle(getContext(), sessionToken);
+ if (token == null || token.getUid() != uid) {
+ Log.w(TAG, "onSessionCreated failed, expected caller uid=" + token.getUid()
+ + " but from uid=" + uid);
+ }
+ if (DEBUG) {
+ Log.d(TAG, "onSessionCreated " + token);
+ }
synchronized (mLock) {
- record = getSessionRecordLocked(sessionPackage, id);
- if (record == null) {
- record = new MediaSession2Record(getContext(), mSessionDestroyedListener);
+ MediaSession2Record record = getSessionRecordLocked(
+ uid, token.getPackageName(), token.getId());
+ if (record != null) {
+ return record.onSessionCreated(token);
+ } else {
+ record = new MediaSession2Record(
+ getContext(), token, mSessionDestroyedListener);
mSessions.add(record);
+ return record.onSessionCreated(token);
}
- token = record.createSessionToken(pid, sessionPackage, id, sessionBinder);
- if (token == null) {
- Log.d(TAG, "failed to create session token for " + sessionPackage
- + " from pid=" + pid + ". Previously " + record);
+ }
+ }
+
+ @Override
+ public void onSessionDestroyed(Bundle sessionToken) {
+ final int uid = Binder.getCallingUid();
+ final int pid = Binder.getCallingPid();
+ final SessionToken2 token = SessionToken2.fromBundle(getContext(), sessionToken);
+ if (token == null || token.getUid() != uid) {
+ Log.w(TAG, "onSessionDestroyed failed, expected caller uid=" + token.getUid()
+ + " but from uid=" + uid);
+ }
+ if (DEBUG) {
+ Log.d(TAG, "onSessionDestroyed " + token);
+ }
+ synchronized (mLock) {
+ MediaSession2Record record = getSessionRecordLocked(
+ uid, token.getPackageName(), token.getId());
+ if (record != null) {
+ record.onSessionDestroyed();
} else {
- Log.d(TAG, "session " + token + " is created");
+ if (DEBUG) {
+ Log.d(TAG, "Cannot find a session record to destroy. uid=" + uid
+ + ", pkg=" + token.getPackageName() + ", id=" + token.getId());
+ }
}
}
- return token == null ? null : token.toBundle();
}
// TODO(jaewan): Protect this API with permission
@@ -1444,8 +1488,8 @@ public class MediaSessionService extends SystemService implements Monitor {
synchronized (mLock) {
for (int i = 0; i < mSessions.size(); i++) {
MediaSession2Record record = mSessions.get(i);
- boolean isSessionService = (record instanceof MediaSessionService2Record);
- boolean isActive = record.getSessionPid() != 0;
+ boolean isSessionService = (record.getToken().getType() != TYPE_SESSION);
+ boolean isActive = record.getController() != null;
if ((!activeSessionOnly && isSessionService)
|| (!sessionServiceOnly && isActive)) {
SessionToken2 token = record.getToken();
diff --git a/services/core/java/com/android/server/media/MediaSessionService2Record.java b/services/core/java/com/android/server/media/MediaSessionService2Record.java
deleted file mode 100644
index d033f552124f..000000000000
--- a/services/core/java/com/android/server/media/MediaSessionService2Record.java
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright 2018 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.content.Context;
-import android.media.IMediaSession2;
-import android.media.MediaController2;
-import android.media.SessionToken2;
-import android.media.MediaSessionService2;
-
-/**
- * Records a {@link MediaSessionService2}.
- * <p>
- * Owner of this object should handle synchronization.
- */
-class MediaSessionService2Record extends MediaSession2Record {
- private static final boolean DEBUG = true; // TODO(jaewan): Modify
- private static final String TAG = "SessionService2Record";
-
- private final int mType;
- private final String mServiceName;
- private final SessionToken2 mToken;
-
- public MediaSessionService2Record(Context context,
- SessionDestroyedListener sessionDestroyedListener, int type,
- String packageName, String serviceName, String id) {
- super(context, sessionDestroyedListener);
- mType = type;
- mServiceName = serviceName;
- mToken = new SessionToken2(mType, packageName, id, mServiceName, null);
- }
-
- /**
- * Overriden to change behavior of
- * {@link #createSessionToken(int, String, String, IMediaSession2)}}.
- */
- @Override
- MediaController2 onCreateMediaController(
- String packageName, String id, IMediaSession2 sessionBinder) {
- SessionToken2 token = new SessionToken2(mType, packageName, id, mServiceName, sessionBinder);
- return createMediaController(token);
- }
-
- /**
- * @return token with no session binder information.
- */
- @Override
- public SessionToken2 getToken() {
- return mToken;
- }
-}