Loading media/java/android/media/Controller2Link.java +23 −5 Original line number Diff line number Diff line Loading @@ -25,8 +25,7 @@ import android.os.ResultReceiver; import java.util.Objects; /** * Handles incoming commands from {@link MediaSession2} and {@link MediaLibrarySession} * to both {@link MediaController2} and {@link MediaBrowser2}. * Handles incoming commands from {@link MediaSession2} to both {@link MediaController2}. * @hide */ // @SystemApi Loading Loading @@ -90,7 +89,7 @@ public final class Controller2Link implements Parcelable { try { mIController.notifyConnected(seq, connectionResult); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); throw new RuntimeException(e); } } Loading @@ -99,7 +98,7 @@ public final class Controller2Link implements Parcelable { try { mIController.notifyDisconnected(seq); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); throw new RuntimeException(e); } } Loading @@ -109,7 +108,16 @@ public final class Controller2Link implements Parcelable { try { mIController.sendSessionCommand(seq, command, args, resultReceiver); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); throw new RuntimeException(e); } } /** Interface method for IMediaController2.cancelSessionCommand */ public void cancelSessionCommand(int seq) { try { mIController.cancelSessionCommand(seq); } catch (RemoteException e) { throw new RuntimeException(e); } } Loading @@ -133,6 +141,11 @@ public final class Controller2Link implements Parcelable { mController.onSessionCommand(seq, command, args, resultReceiver); } /** Stub implementation for IMediaController2.cancelSessionCommand */ public void onCancelCommand(int seq) { mController.onCancelCommand(seq); } private class Controller2Stub extends IMediaController2.Stub { @Override public void notifyConnected(int seq, Bundle connectionResult) { Loading @@ -149,5 +162,10 @@ public final class Controller2Link implements Parcelable { ResultReceiver resultReceiver) { Controller2Link.this.onSessionCommand(seq, command, args, resultReceiver); } @Override public void cancelSessionCommand(int seq) { Controller2Link.this.onCancelCommand(seq); } } } media/java/android/media/IMediaController2.aidl +2 −0 Original line number Diff line number Diff line Loading @@ -33,4 +33,6 @@ oneway interface IMediaController2 { void notifyDisconnected(int seq) = 1; void sendSessionCommand(int seq, in Session2Command command, in Bundle args, in ResultReceiver resultReceiver) = 2; void cancelSessionCommand(int seq) = 3; // Next Id : 4 } media/java/android/media/IMediaSession2.aidl +2 −1 Original line number Diff line number Diff line Loading @@ -34,5 +34,6 @@ oneway interface IMediaSession2 { void disconnect(in Controller2Link caller, int seq) = 1; void sendSessionCommand(in Controller2Link caller, int seq, in Session2Command sessionCommand, in Bundle args, in ResultReceiver resultReceiver) = 2; // Next Id : 3 void cancelSessionCommand(in Controller2Link caller, int seq) = 3; // Next Id : 4 } media/java/android/media/MediaConstants.java +0 −1 Original line number Diff line number Diff line Loading @@ -16,7 +16,6 @@ package android.media; // Code for AML only class MediaConstants { // Bundle key for int static final String KEY_PID = "android.media.key.PID"; Loading media/java/android/media/MediaController2.java +93 −24 Original line number Diff line number Diff line Loading @@ -20,6 +20,8 @@ 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_SESSION2_STUB; import static android.media.Session2Command.RESULT_ERROR_UNKNOWN_ERROR; import static android.media.Session2Command.RESULT_INFO_SKIPPED; import static android.media.Session2Token.TYPE_SESSION; import android.annotation.NonNull; Loading @@ -31,6 +33,8 @@ import android.os.Handler; import android.os.IBinder; import android.os.Process; import android.os.ResultReceiver; import android.util.ArrayMap; import android.util.ArraySet; import android.util.Log; import java.util.concurrent.Executor; Loading Loading @@ -69,6 +73,21 @@ public class MediaController2 implements AutoCloseable { private Session2CommandGroup mAllowedCommands; //@GuardedBy("mLock") private Session2Token mConnectedToken; //@GuardedBy("mLock") private ArrayMap<ResultReceiver, Integer> mPendingCommands; //@GuardedBy("mLock") private ArraySet<Integer> mRequestedCommandSeqNumbers; /** * Create a {@link MediaController2} from the {@link Session2Token}. * This connects to the session and may wake up the service if it's not available. * * @param context Context * @param token token to connect to */ public MediaController2(@NonNull Context context, @NonNull Session2Token token) { this(context, token, context.getMainExecutor(), new ControllerCallback() {}); } /** * Create a {@link MediaController2} from the {@link Session2Token}. Loading @@ -77,31 +96,27 @@ public class MediaController2 implements AutoCloseable { * @param context Context * @param token token to connect to * @param executor executor to run callbacks on. * @param callback controller callback to receive changes in * @param callback controller callback to receive changes in. */ public MediaController2(@NonNull final Context context, @NonNull final Session2Token token, @NonNull final Executor executor, @NonNull final ControllerCallback callback) { public MediaController2(@NonNull Context context, @NonNull Session2Token token, @NonNull Executor executor, @NonNull ControllerCallback callback) { if (context == null) { throw new IllegalArgumentException("context shouldn't be null"); } if (token == null) { throw new IllegalArgumentException("token shouldn't be null"); } if (callback == null) { throw new IllegalArgumentException("callback shouldn't be null"); } if (executor == null) { throw new IllegalArgumentException("executor shouldn't be null"); } mContext = context; mSessionToken = token; mCallbackExecutor = executor; mCallback = callback; mCallbackExecutor = (executor == null) ? context.getMainExecutor() : executor; mCallback = (callback == null) ? new ControllerCallback() { } : callback; mControllerStub = new Controller2Link(this); // NOTE: mResultHandler uses main looper, so this MUST NOT be blocked. mResultHandler = new Handler(context.getMainLooper()); mNextSeqNumber = 0; mPendingCommands = new ArrayMap<>(); mRequestedCommandSeqNumbers = new ArraySet<>(); if (token.getType() == TYPE_SESSION) { connectToSession(); Loading @@ -116,11 +131,13 @@ public class MediaController2 implements AutoCloseable { if (mSessionBinder != null) { try { mSessionBinder.unlinkToDeath(mDeathRecipient, 0); mSessionBinder.disconnect(mControllerStub, mNextSeqNumber++); mSessionBinder.disconnect(mControllerStub, getNextSeqNumber()); } catch (RuntimeException e) { // No-op } } mPendingCommands.clear(); mRequestedCommandSeqNumbers.clear(); mCallbackExecutor.execute(() -> { mCallback.onDisconnected(MediaController2.this); }); Loading @@ -136,7 +153,6 @@ public class MediaController2 implements AutoCloseable { * @return a token which will be sent together in {@link ControllerCallback#onCommandResult} * when its result is received. */ // TODO: make cancelable. public Object sendSessionCommand(@NonNull Session2Command command, @Nullable Bundle args) { if (command == null) { throw new IllegalArgumentException("command shouldn't be null"); Loading @@ -144,26 +160,50 @@ public class MediaController2 implements AutoCloseable { ResultReceiver resultReceiver = new ResultReceiver(mResultHandler) { protected void onReceiveResult(int resultCode, Bundle resultData) { synchronized (mLock) { mPendingCommands.remove(this); } mCallbackExecutor.execute(() -> { mCallback.onCommandResult(MediaController2.this, this, command, resultData); command, new Session2Command.Result(resultCode, resultData)); }); } }; synchronized (mLock) { if (mSessionBinder != null) { int seq = getNextSeqNumber(); mPendingCommands.put(resultReceiver, seq); try { mSessionBinder.sendSessionCommand(mControllerStub, mNextSeqNumber++, command, args, resultReceiver); mSessionBinder.sendSessionCommand(mControllerStub, seq, command, args, resultReceiver); } catch (RuntimeException e) { // No-op mPendingCommands.remove(resultReceiver); resultReceiver.send(RESULT_ERROR_UNKNOWN_ERROR, null); } } } return resultReceiver; } /** * Cancels the session command previously sent. * * @param token the token which is returned from {@link #sendSessionCommand}. */ public void cancelSessionCommand(@NonNull Object token) { if (token == null) { throw new IllegalArgumentException("token shouldn't be null"); } synchronized (mLock) { if (mSessionBinder == null) return; Integer seq = mPendingCommands.remove(token); if (seq != null) { mSessionBinder.cancelSessionCommand(mControllerStub, seq); } } } // Called by Controller2Link.onConnected void onConnected(int seq, Bundle connectionResult) { final long token = Binder.clearCallingIdentity(); Loading Loading @@ -213,10 +253,26 @@ public class MediaController2 implements AutoCloseable { @Nullable ResultReceiver resultReceiver) { final long token = Binder.clearCallingIdentity(); try { synchronized (mLock) { mRequestedCommandSeqNumbers.add(seq); } mCallbackExecutor.execute(() -> { Bundle result = mCallback.onSessionCommand(MediaController2.this, command, args); 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) { resultReceiver.send(0, result); if (result == null) { throw new RuntimeException("onSessionCommand shouldn't return null"); } else { resultReceiver.send(result.getResultCode(), result.getResultData()); } } }); } finally { Loading @@ -224,6 +280,18 @@ public class MediaController2 implements AutoCloseable { } } // Called by Controller2Link.onSessionCommand void onCancelCommand(int seq) { final long token = Binder.clearCallingIdentity(); try { synchronized (mLock) { mRequestedCommandSeqNumbers.remove(seq); } } finally { Binder.restoreCallingIdentity(token); } } private int getNextSeqNumber() { synchronized (mLock) { return mNextSeqNumber++; Loading Loading @@ -278,9 +346,11 @@ public class MediaController2 implements AutoCloseable { * @param controller the controller for this event * @param command the session command * @param args optional arguments * @return the result for the session command * @return the result for the session command. A runtime exception will be thrown if null * is returned. */ public Bundle onSessionCommand(@NonNull MediaController2 controller, @NonNull public Session2Command.Result onSessionCommand(@NonNull MediaController2 controller, @NonNull Session2Command command, @Nullable Bundle args) { return null; } Loading @@ -294,7 +364,6 @@ public class MediaController2 implements AutoCloseable { * @param result the result of the session command */ public void onCommandResult(@NonNull MediaController2 controller, @NonNull Object token, @NonNull Session2Command command, @Nullable Bundle result) { } @NonNull Session2Command command, @NonNull Session2Command.Result result) { } } } Loading
media/java/android/media/Controller2Link.java +23 −5 Original line number Diff line number Diff line Loading @@ -25,8 +25,7 @@ import android.os.ResultReceiver; import java.util.Objects; /** * Handles incoming commands from {@link MediaSession2} and {@link MediaLibrarySession} * to both {@link MediaController2} and {@link MediaBrowser2}. * Handles incoming commands from {@link MediaSession2} to both {@link MediaController2}. * @hide */ // @SystemApi Loading Loading @@ -90,7 +89,7 @@ public final class Controller2Link implements Parcelable { try { mIController.notifyConnected(seq, connectionResult); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); throw new RuntimeException(e); } } Loading @@ -99,7 +98,7 @@ public final class Controller2Link implements Parcelable { try { mIController.notifyDisconnected(seq); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); throw new RuntimeException(e); } } Loading @@ -109,7 +108,16 @@ public final class Controller2Link implements Parcelable { try { mIController.sendSessionCommand(seq, command, args, resultReceiver); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); throw new RuntimeException(e); } } /** Interface method for IMediaController2.cancelSessionCommand */ public void cancelSessionCommand(int seq) { try { mIController.cancelSessionCommand(seq); } catch (RemoteException e) { throw new RuntimeException(e); } } Loading @@ -133,6 +141,11 @@ public final class Controller2Link implements Parcelable { mController.onSessionCommand(seq, command, args, resultReceiver); } /** Stub implementation for IMediaController2.cancelSessionCommand */ public void onCancelCommand(int seq) { mController.onCancelCommand(seq); } private class Controller2Stub extends IMediaController2.Stub { @Override public void notifyConnected(int seq, Bundle connectionResult) { Loading @@ -149,5 +162,10 @@ public final class Controller2Link implements Parcelable { ResultReceiver resultReceiver) { Controller2Link.this.onSessionCommand(seq, command, args, resultReceiver); } @Override public void cancelSessionCommand(int seq) { Controller2Link.this.onCancelCommand(seq); } } }
media/java/android/media/IMediaController2.aidl +2 −0 Original line number Diff line number Diff line Loading @@ -33,4 +33,6 @@ oneway interface IMediaController2 { void notifyDisconnected(int seq) = 1; void sendSessionCommand(int seq, in Session2Command command, in Bundle args, in ResultReceiver resultReceiver) = 2; void cancelSessionCommand(int seq) = 3; // Next Id : 4 }
media/java/android/media/IMediaSession2.aidl +2 −1 Original line number Diff line number Diff line Loading @@ -34,5 +34,6 @@ oneway interface IMediaSession2 { void disconnect(in Controller2Link caller, int seq) = 1; void sendSessionCommand(in Controller2Link caller, int seq, in Session2Command sessionCommand, in Bundle args, in ResultReceiver resultReceiver) = 2; // Next Id : 3 void cancelSessionCommand(in Controller2Link caller, int seq) = 3; // Next Id : 4 }
media/java/android/media/MediaConstants.java +0 −1 Original line number Diff line number Diff line Loading @@ -16,7 +16,6 @@ package android.media; // Code for AML only class MediaConstants { // Bundle key for int static final String KEY_PID = "android.media.key.PID"; Loading
media/java/android/media/MediaController2.java +93 −24 Original line number Diff line number Diff line Loading @@ -20,6 +20,8 @@ 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_SESSION2_STUB; import static android.media.Session2Command.RESULT_ERROR_UNKNOWN_ERROR; import static android.media.Session2Command.RESULT_INFO_SKIPPED; import static android.media.Session2Token.TYPE_SESSION; import android.annotation.NonNull; Loading @@ -31,6 +33,8 @@ import android.os.Handler; import android.os.IBinder; import android.os.Process; import android.os.ResultReceiver; import android.util.ArrayMap; import android.util.ArraySet; import android.util.Log; import java.util.concurrent.Executor; Loading Loading @@ -69,6 +73,21 @@ public class MediaController2 implements AutoCloseable { private Session2CommandGroup mAllowedCommands; //@GuardedBy("mLock") private Session2Token mConnectedToken; //@GuardedBy("mLock") private ArrayMap<ResultReceiver, Integer> mPendingCommands; //@GuardedBy("mLock") private ArraySet<Integer> mRequestedCommandSeqNumbers; /** * Create a {@link MediaController2} from the {@link Session2Token}. * This connects to the session and may wake up the service if it's not available. * * @param context Context * @param token token to connect to */ public MediaController2(@NonNull Context context, @NonNull Session2Token token) { this(context, token, context.getMainExecutor(), new ControllerCallback() {}); } /** * Create a {@link MediaController2} from the {@link Session2Token}. Loading @@ -77,31 +96,27 @@ public class MediaController2 implements AutoCloseable { * @param context Context * @param token token to connect to * @param executor executor to run callbacks on. * @param callback controller callback to receive changes in * @param callback controller callback to receive changes in. */ public MediaController2(@NonNull final Context context, @NonNull final Session2Token token, @NonNull final Executor executor, @NonNull final ControllerCallback callback) { public MediaController2(@NonNull Context context, @NonNull Session2Token token, @NonNull Executor executor, @NonNull ControllerCallback callback) { if (context == null) { throw new IllegalArgumentException("context shouldn't be null"); } if (token == null) { throw new IllegalArgumentException("token shouldn't be null"); } if (callback == null) { throw new IllegalArgumentException("callback shouldn't be null"); } if (executor == null) { throw new IllegalArgumentException("executor shouldn't be null"); } mContext = context; mSessionToken = token; mCallbackExecutor = executor; mCallback = callback; mCallbackExecutor = (executor == null) ? context.getMainExecutor() : executor; mCallback = (callback == null) ? new ControllerCallback() { } : callback; mControllerStub = new Controller2Link(this); // NOTE: mResultHandler uses main looper, so this MUST NOT be blocked. mResultHandler = new Handler(context.getMainLooper()); mNextSeqNumber = 0; mPendingCommands = new ArrayMap<>(); mRequestedCommandSeqNumbers = new ArraySet<>(); if (token.getType() == TYPE_SESSION) { connectToSession(); Loading @@ -116,11 +131,13 @@ public class MediaController2 implements AutoCloseable { if (mSessionBinder != null) { try { mSessionBinder.unlinkToDeath(mDeathRecipient, 0); mSessionBinder.disconnect(mControllerStub, mNextSeqNumber++); mSessionBinder.disconnect(mControllerStub, getNextSeqNumber()); } catch (RuntimeException e) { // No-op } } mPendingCommands.clear(); mRequestedCommandSeqNumbers.clear(); mCallbackExecutor.execute(() -> { mCallback.onDisconnected(MediaController2.this); }); Loading @@ -136,7 +153,6 @@ public class MediaController2 implements AutoCloseable { * @return a token which will be sent together in {@link ControllerCallback#onCommandResult} * when its result is received. */ // TODO: make cancelable. public Object sendSessionCommand(@NonNull Session2Command command, @Nullable Bundle args) { if (command == null) { throw new IllegalArgumentException("command shouldn't be null"); Loading @@ -144,26 +160,50 @@ public class MediaController2 implements AutoCloseable { ResultReceiver resultReceiver = new ResultReceiver(mResultHandler) { protected void onReceiveResult(int resultCode, Bundle resultData) { synchronized (mLock) { mPendingCommands.remove(this); } mCallbackExecutor.execute(() -> { mCallback.onCommandResult(MediaController2.this, this, command, resultData); command, new Session2Command.Result(resultCode, resultData)); }); } }; synchronized (mLock) { if (mSessionBinder != null) { int seq = getNextSeqNumber(); mPendingCommands.put(resultReceiver, seq); try { mSessionBinder.sendSessionCommand(mControllerStub, mNextSeqNumber++, command, args, resultReceiver); mSessionBinder.sendSessionCommand(mControllerStub, seq, command, args, resultReceiver); } catch (RuntimeException e) { // No-op mPendingCommands.remove(resultReceiver); resultReceiver.send(RESULT_ERROR_UNKNOWN_ERROR, null); } } } return resultReceiver; } /** * Cancels the session command previously sent. * * @param token the token which is returned from {@link #sendSessionCommand}. */ public void cancelSessionCommand(@NonNull Object token) { if (token == null) { throw new IllegalArgumentException("token shouldn't be null"); } synchronized (mLock) { if (mSessionBinder == null) return; Integer seq = mPendingCommands.remove(token); if (seq != null) { mSessionBinder.cancelSessionCommand(mControllerStub, seq); } } } // Called by Controller2Link.onConnected void onConnected(int seq, Bundle connectionResult) { final long token = Binder.clearCallingIdentity(); Loading Loading @@ -213,10 +253,26 @@ public class MediaController2 implements AutoCloseable { @Nullable ResultReceiver resultReceiver) { final long token = Binder.clearCallingIdentity(); try { synchronized (mLock) { mRequestedCommandSeqNumbers.add(seq); } mCallbackExecutor.execute(() -> { Bundle result = mCallback.onSessionCommand(MediaController2.this, command, args); 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) { resultReceiver.send(0, result); if (result == null) { throw new RuntimeException("onSessionCommand shouldn't return null"); } else { resultReceiver.send(result.getResultCode(), result.getResultData()); } } }); } finally { Loading @@ -224,6 +280,18 @@ public class MediaController2 implements AutoCloseable { } } // Called by Controller2Link.onSessionCommand void onCancelCommand(int seq) { final long token = Binder.clearCallingIdentity(); try { synchronized (mLock) { mRequestedCommandSeqNumbers.remove(seq); } } finally { Binder.restoreCallingIdentity(token); } } private int getNextSeqNumber() { synchronized (mLock) { return mNextSeqNumber++; Loading Loading @@ -278,9 +346,11 @@ public class MediaController2 implements AutoCloseable { * @param controller the controller for this event * @param command the session command * @param args optional arguments * @return the result for the session command * @return the result for the session command. A runtime exception will be thrown if null * is returned. */ public Bundle onSessionCommand(@NonNull MediaController2 controller, @NonNull public Session2Command.Result onSessionCommand(@NonNull MediaController2 controller, @NonNull Session2Command command, @Nullable Bundle args) { return null; } Loading @@ -294,7 +364,6 @@ public class MediaController2 implements AutoCloseable { * @param result the result of the session command */ public void onCommandResult(@NonNull MediaController2 controller, @NonNull Object token, @NonNull Session2Command command, @Nullable Bundle result) { } @NonNull Session2Command command, @NonNull Session2Command.Result result) { } } }