diff options
| -rw-r--r-- | api/current.txt | 4 | ||||
| -rw-r--r-- | media/java/android/media/Controller2Link.java | 53 | ||||
| -rw-r--r-- | media/java/android/media/IMediaController2.aidl | 7 | ||||
| -rw-r--r-- | media/java/android/media/MediaConstants.java | 1 | ||||
| -rw-r--r-- | media/java/android/media/MediaController2.java | 150 | ||||
| -rw-r--r-- | media/java/android/media/MediaSession2.java | 43 |
6 files changed, 185 insertions, 73 deletions
diff --git a/api/current.txt b/api/current.txt index 40264045d4d7..ea132de14292 100644 --- a/api/current.txt +++ b/api/current.txt @@ -24566,6 +24566,7 @@ package android.media { ctor public MediaController2(android.content.Context, android.media.Session2Token, java.util.concurrent.Executor, android.media.MediaController2.ControllerCallback); method public void cancelSessionCommand(java.lang.Object); method public void close(); + method public boolean isPlaybackActive(); method public java.lang.Object sendSessionCommand(android.media.Session2Command, android.os.Bundle); } @@ -24574,6 +24575,7 @@ package android.media { method public void onCommandResult(android.media.MediaController2, java.lang.Object, android.media.Session2Command, android.media.Session2Command.Result); method public void onConnected(android.media.MediaController2, android.media.Session2CommandGroup); method public void onDisconnected(android.media.MediaController2); + method public void onPlaybackActiveChanged(android.media.MediaController2, boolean); method public android.media.Session2Command.Result onSessionCommand(android.media.MediaController2, android.media.Session2Command, android.os.Bundle); } @@ -25915,7 +25917,9 @@ package android.media { method public void close(); method public java.lang.String getSessionId(); method public android.media.Session2Token getSessionToken(); + method public boolean isPlaybackActive(); method public java.lang.Object sendSessionCommand(android.media.MediaSession2.ControllerInfo, android.media.Session2Command, android.os.Bundle); + method public void setPlaybackActive(boolean); } public static final class MediaSession2.Builder { diff --git a/media/java/android/media/Controller2Link.java b/media/java/android/media/Controller2Link.java index a62db5f1fb20..d11f7769ee5e 100644 --- a/media/java/android/media/Controller2Link.java +++ b/media/java/android/media/Controller2Link.java @@ -16,6 +16,7 @@ package android.media; +import android.os.Binder; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; @@ -102,6 +103,15 @@ public final class Controller2Link implements Parcelable { } } + /** Interface method for IMediaController2.notifyPlaybackActiveChanged */ + public void notifyPlaybackActiveChanged(int seq, boolean playbackActive) { + try { + mIController.notifyPlaybackActiveChanged(seq, playbackActive); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + /** Interface method for IMediaController2.sendSessionCommand */ public void sendSessionCommand(int seq, Session2Command command, Bundle args, ResultReceiver resultReceiver) { @@ -135,6 +145,11 @@ public final class Controller2Link implements Parcelable { mController.onDisconnected(seq); } + /** Stub implementation for IMediaController2.notifyPlaybackActiveChanged */ + public void onPlaybackActiveChanged(int seq, boolean playbackActive) { + mController.onPlaybackActiveChanged(seq, playbackActive); + } + /** Stub implementation for IMediaController2.sendSessionCommand */ public void onSessionCommand(int seq, Session2Command command, Bundle args, ResultReceiver resultReceiver) { @@ -149,23 +164,53 @@ public final class Controller2Link implements Parcelable { private class Controller2Stub extends IMediaController2.Stub { @Override public void notifyConnected(int seq, Bundle connectionResult) { - Controller2Link.this.onConnected(seq, connectionResult); + final long token = Binder.clearCallingIdentity(); + try { + Controller2Link.this.onConnected(seq, connectionResult); + } finally { + Binder.restoreCallingIdentity(token); + } } @Override public void notifyDisconnected(int seq) { - Controller2Link.this.onDisconnected(seq); + final long token = Binder.clearCallingIdentity(); + try { + Controller2Link.this.onDisconnected(seq); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override + public void notifyPlaybackActiveChanged(int seq, boolean playbackActive) { + final long token = Binder.clearCallingIdentity(); + try { + Controller2Link.this.onPlaybackActiveChanged(seq, playbackActive); + } finally { + Binder.restoreCallingIdentity(token); + } } @Override public void sendSessionCommand(int seq, Session2Command command, Bundle args, ResultReceiver resultReceiver) { - Controller2Link.this.onSessionCommand(seq, command, args, resultReceiver); + final long token = Binder.clearCallingIdentity(); + try { + Controller2Link.this.onSessionCommand(seq, command, args, resultReceiver); + } finally { + Binder.restoreCallingIdentity(token); + } } @Override public void cancelSessionCommand(int seq) { - Controller2Link.this.onCancelCommand(seq); + final long token = Binder.clearCallingIdentity(); + try { + Controller2Link.this.onCancelCommand(seq); + } finally { + Binder.restoreCallingIdentity(token); + } } } } diff --git a/media/java/android/media/IMediaController2.aidl b/media/java/android/media/IMediaController2.aidl index ca5394f504cb..42c6e70529ec 100644 --- a/media/java/android/media/IMediaController2.aidl +++ b/media/java/android/media/IMediaController2.aidl @@ -31,8 +31,9 @@ import android.media.Session2Command; oneway interface IMediaController2 { void notifyConnected(int seq, in Bundle connectionResult) = 0; void notifyDisconnected(int seq) = 1; + void notifyPlaybackActiveChanged(int seq, boolean playbackActive) = 2; void sendSessionCommand(int seq, in Session2Command command, in Bundle args, - in ResultReceiver resultReceiver) = 2; - void cancelSessionCommand(int seq) = 3; - // Next Id : 4 + in ResultReceiver resultReceiver) = 3; + void cancelSessionCommand(int seq) = 4; + // Next Id : 5 } diff --git a/media/java/android/media/MediaConstants.java b/media/java/android/media/MediaConstants.java index 5a5747ae4bab..65b6f55a068a 100644 --- a/media/java/android/media/MediaConstants.java +++ b/media/java/android/media/MediaConstants.java @@ -26,6 +26,7 @@ class MediaConstants { // Bundle key for Parcelable static final String KEY_SESSION2LINK = "android.media.key.SESSION2LINK"; static final String KEY_ALLOWED_COMMANDS = "android.media.key.ALLOWED_COMMANDS"; + static final String KEY_PLAYBACK_ACTIVE = "android.media.key.PLAYBACK_ACTIVE"; private MediaConstants() { } diff --git a/media/java/android/media/MediaController2.java b/media/java/android/media/MediaController2.java index 165ea412333e..7c5335b71627 100644 --- a/media/java/android/media/MediaController2.java +++ b/media/java/android/media/MediaController2.java @@ -19,6 +19,7 @@ package android.media; import static android.media.MediaConstants.KEY_ALLOWED_COMMANDS; import static android.media.MediaConstants.KEY_PACKAGE_NAME; import static android.media.MediaConstants.KEY_PID; +import static android.media.MediaConstants.KEY_PLAYBACK_ACTIVE; import static android.media.MediaConstants.KEY_SESSION2LINK; import static android.media.Session2Command.RESULT_ERROR_UNKNOWN_ERROR; import static android.media.Session2Command.RESULT_INFO_SKIPPED; @@ -30,7 +31,6 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; -import android.os.Binder; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; @@ -82,6 +82,8 @@ public class MediaController2 implements AutoCloseable { private ArrayMap<ResultReceiver, Integer> mPendingCommands; //@GuardedBy("mLock") private ArraySet<Integer> mRequestedCommandSeqNumbers; + //@GuardedBy("mLock") + private boolean mPlaybackActive; /** * Create a {@link MediaController2} from the {@link Session2Token}. @@ -160,6 +162,18 @@ public class MediaController2 implements AutoCloseable { } /** + * Returns whether the session's playback is active. + * + * @return {@code true} if playback active. {@code false} otherwise. + * @see ControllerCallback#onPlaybackActiveChanged(MediaController2, boolean) + */ + public boolean isPlaybackActive() { + synchronized (mLock) { + return mPlaybackActive; + } + } + + /** * Sends a session command to the session * <p> * @param command the session command @@ -221,89 +235,82 @@ public class MediaController2 implements AutoCloseable { // Called by Controller2Link.onConnected void onConnected(int seq, Bundle connectionResult) { - final long token = Binder.clearCallingIdentity(); - try { - Session2Link sessionBinder = connectionResult.getParcelable(KEY_SESSION2LINK); - Session2CommandGroup allowedCommands = - connectionResult.getParcelable(KEY_ALLOWED_COMMANDS); - if (DEBUG) { - Log.d(TAG, "notifyConnected sessionBinder=" + sessionBinder - + ", allowedCommands=" + allowedCommands); - } - if (sessionBinder == null || allowedCommands == null) { - // Connection rejected. - close(); - return; - } - synchronized (mLock) { - mSessionBinder = sessionBinder; - mAllowedCommands = allowedCommands; - // Implementation for the local binder is no-op, - // so can be used without worrying about deadlock. - sessionBinder.linkToDeath(mDeathRecipient, 0); - mConnectedToken = new Session2Token(mSessionToken.getUid(), TYPE_SESSION, - mSessionToken.getPackageName(), sessionBinder); - } - mCallbackExecutor.execute(() -> { - mCallback.onConnected(MediaController2.this, allowedCommands); - }); - } finally { - Binder.restoreCallingIdentity(token); + Session2Link sessionBinder = connectionResult.getParcelable(KEY_SESSION2LINK); + Session2CommandGroup allowedCommands = + connectionResult.getParcelable(KEY_ALLOWED_COMMANDS); + boolean playbackActive = connectionResult.getBoolean(KEY_PLAYBACK_ACTIVE); + if (DEBUG) { + Log.d(TAG, "notifyConnected sessionBinder=" + sessionBinder + + ", allowedCommands=" + allowedCommands); + } + if (sessionBinder == null || allowedCommands == null) { + // Connection rejected. + close(); + return; + } + synchronized (mLock) { + mSessionBinder = sessionBinder; + mAllowedCommands = allowedCommands; + mPlaybackActive = playbackActive; + + // Implementation for the local binder is no-op, + // so can be used without worrying about deadlock. + sessionBinder.linkToDeath(mDeathRecipient, 0); + mConnectedToken = new Session2Token(mSessionToken.getUid(), TYPE_SESSION, + mSessionToken.getPackageName(), sessionBinder); } + mCallbackExecutor.execute(() -> { + mCallback.onConnected(MediaController2.this, allowedCommands); + }); } // Called by Controller2Link.onDisconnected void onDisconnected(int seq) { - final long token = Binder.clearCallingIdentity(); - try { - // close() will call mCallback.onDisconnected - close(); - } finally { - Binder.restoreCallingIdentity(token); + // close() will call mCallback.onDisconnected + close(); + } + + // Called by Controller2Link.onPlaybackActiveChanged + void onPlaybackActiveChanged(int seq, boolean playbackActive) { + synchronized (mLock) { + mPlaybackActive = playbackActive; } + mCallbackExecutor.execute(() -> { + mCallback.onPlaybackActiveChanged(MediaController2.this, playbackActive); + }); } // Called by Controller2Link.onSessionCommand void onSessionCommand(int seq, Session2Command command, Bundle args, @Nullable ResultReceiver resultReceiver) { - final long token = Binder.clearCallingIdentity(); - try { + synchronized (mLock) { + mRequestedCommandSeqNumbers.add(seq); + } + mCallbackExecutor.execute(() -> { + boolean isCanceled; synchronized (mLock) { - mRequestedCommandSeqNumbers.add(seq); + isCanceled = !mRequestedCommandSeqNumbers.remove(seq); } - mCallbackExecutor.execute(() -> { - boolean isCanceled; - synchronized (mLock) { - isCanceled = !mRequestedCommandSeqNumbers.remove(seq); - } - if (isCanceled) { - resultReceiver.send(RESULT_INFO_SKIPPED, null); - return; - } - Session2Command.Result result = mCallback.onSessionCommand( - MediaController2.this, command, args); - if (resultReceiver != null) { - if (result == null) { - throw new RuntimeException("onSessionCommand shouldn't return null"); - } else { - resultReceiver.send(result.getResultCode(), result.getResultData()); - } + if (isCanceled) { + resultReceiver.send(RESULT_INFO_SKIPPED, null); + return; + } + Session2Command.Result result = mCallback.onSessionCommand( + MediaController2.this, command, args); + if (resultReceiver != null) { + if (result == null) { + throw new RuntimeException("onSessionCommand shouldn't return null"); + } else { + resultReceiver.send(result.getResultCode(), result.getResultData()); } - }); - } finally { - Binder.restoreCallingIdentity(token); - } + } + }); } // Called by Controller2Link.onSessionCommand void onCancelCommand(int seq) { - final long token = Binder.clearCallingIdentity(); - try { - synchronized (mLock) { - mRequestedCommandSeqNumbers.remove(seq); - } - } finally { - Binder.restoreCallingIdentity(token); + synchronized (mLock) { + mRequestedCommandSeqNumbers.remove(seq); } } @@ -393,6 +400,17 @@ public class MediaController2 implements AutoCloseable { public void onDisconnected(@NonNull MediaController2 controller) {} /** + * Called when the playback of the session's playback activeness is changed. + * + * @param controller the controller for this event + * @param playbackActive {@code true} if the session's playback is active. + * {@code false} otherwise. + * @see MediaController2#isPlaybackActive() + */ + public void onPlaybackActiveChanged(@NonNull MediaController2 controller, + boolean playbackActive) {} + + /** * Called when the connected session sent a session command. * * @param controller the controller for this event diff --git a/media/java/android/media/MediaSession2.java b/media/java/android/media/MediaSession2.java index 04999cace5e0..3adac7295fff 100644 --- a/media/java/android/media/MediaSession2.java +++ b/media/java/android/media/MediaSession2.java @@ -19,6 +19,7 @@ package android.media; import static android.media.MediaConstants.KEY_ALLOWED_COMMANDS; import static android.media.MediaConstants.KEY_PACKAGE_NAME; import static android.media.MediaConstants.KEY_PID; +import static android.media.MediaConstants.KEY_PLAYBACK_ACTIVE; import static android.media.MediaConstants.KEY_SESSION2LINK; import static android.media.Session2Command.RESULT_ERROR_UNKNOWN_ERROR; import static android.media.Session2Command.RESULT_INFO_SKIPPED; @@ -87,6 +88,8 @@ public class MediaSession2 implements AutoCloseable { //@GuardedBy("mLock") private boolean mClosed; + //@GuardedBy("mLock") + private boolean mPlaybackActive; MediaSession2(@NonNull Context context, @NonNull String id, PendingIntent sessionActivity, @NonNull Executor callbackExecutor, @NonNull SessionCallback callback) { @@ -215,6 +218,35 @@ public class MediaSession2 implements AutoCloseable { controller.cancelSessionCommand(token); } + /** + * Sets whether the playback is active (i.e. playing something) + * + * @param playbackActive {@code true} if the playback active, {@code false} otherwise. + **/ + public void setPlaybackActive(boolean playbackActive) { + synchronized (mLock) { + if (mPlaybackActive == playbackActive) { + return; + } + mPlaybackActive = playbackActive; + } + List<ControllerInfo> controllerInfos = getConnectedControllers(); + for (ControllerInfo controller : controllerInfos) { + controller.notifyPlaybackActiveChanged(playbackActive); + } + } + + /** + * Returns whehther the playback is active (i.e. playing something) + * + * @return {@code true} if the playback active, {@code false} otherwise. + */ + public boolean isPlaybackActive() { + synchronized (mLock) { + return mPlaybackActive; + } + } + boolean isClosed() { synchronized (mLock) { return mClosed; @@ -275,6 +307,7 @@ public class MediaSession2 implements AutoCloseable { connectionResult.putParcelable(KEY_SESSION2LINK, mSessionStub); connectionResult.putParcelable(KEY_ALLOWED_COMMANDS, controllerInfo.mAllowedCommands); + connectionResult.putBoolean(KEY_PLAYBACK_ACTIVE, isPlaybackActive()); // Double check if session is still there, because close() can be called in // another thread. @@ -591,6 +624,16 @@ public class MediaSession2 implements AutoCloseable { } } + void notifyPlaybackActiveChanged(boolean playbackActive) { + if (mControllerBinder == null) return; + + try { + mControllerBinder.notifyPlaybackActiveChanged(getNextSeqNumber(), playbackActive); + } catch (RuntimeException e) { + // Controller may be died prematurely. + } + } + void sendSessionCommand(Session2Command command, Bundle args, ResultReceiver resultReceiver) { if (mControllerBinder == null) return; |