diff options
4 files changed, 153 insertions, 5 deletions
diff --git a/media/java/android/media/session/ISession.aidl b/media/java/android/media/session/ISession.aidl index 21378c80fd56..77f7b54368f8 100644 --- a/media/java/android/media/session/ISession.aidl +++ b/media/java/android/media/session/ISession.aidl @@ -41,7 +41,8 @@ interface ISession { // These commands are for the TransportPerformer void setMetadata(in MediaMetadata metadata, long duration, String metadataDescription); void setPlaybackState(in PlaybackState state); - void setQueue(in ParceledListSlice queue); + void resetQueue(); + IBinder getBinderForSetQueue(); void setQueueTitle(CharSequence title); void setExtras(in Bundle extras); void setRatingType(int type); diff --git a/media/java/android/media/session/MediaSession.java b/media/java/android/media/session/MediaSession.java index 70bd1609ddbd..6c41f7bcd0bc 100644 --- a/media/java/android/media/session/MediaSession.java +++ b/media/java/android/media/session/MediaSession.java @@ -25,7 +25,6 @@ import android.app.PendingIntent; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.content.Intent; -import android.content.pm.ParceledListSlice; import android.media.AudioAttributes; import android.media.MediaDescription; import android.media.MediaMetadata; @@ -36,6 +35,7 @@ import android.net.Uri; import android.os.BadParcelableException; import android.os.Bundle; import android.os.Handler; +import android.os.IBinder; import android.os.Looper; import android.os.Message; import android.os.Parcel; @@ -491,7 +491,12 @@ public final class MediaSession { */ public void setQueue(@Nullable List<QueueItem> queue) { try { - mBinder.setQueue(queue == null ? null : new ParceledListSlice(queue)); + if (queue == null) { + mBinder.resetQueue(); + } else { + IBinder binder = mBinder.getBinderForSetQueue(); + ParcelableListBinder.send(binder, queue); + } } catch (RemoteException e) { Log.wtf("Dead object in setQueue.", e); } diff --git a/media/java/android/media/session/ParcelableListBinder.java b/media/java/android/media/session/ParcelableListBinder.java new file mode 100644 index 000000000000..a7aacf2d8fca --- /dev/null +++ b/media/java/android/media/session/ParcelableListBinder.java @@ -0,0 +1,131 @@ +/* + * Copyright 2020 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.session; + +import android.annotation.NonNull; +import android.os.Binder; +import android.os.IBinder; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.RemoteException; + +import com.android.internal.annotations.GuardedBy; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; + +/** + * Binder to receive a list that has a large number of {@link Parcelable} items. + * + * It's similar to {@link android.content.pm.ParceledListSlice}, but transactions are performed in + * the opposite direction. + * + * @param <T> the type of {@link Parcelable} + * @hide + */ +public class ParcelableListBinder<T extends Parcelable> extends Binder { + + private static final int SUGGESTED_MAX_IPC_SIZE = IBinder.getSuggestedMaxIpcSizeBytes(); + + private static final int END_OF_PARCEL = 0; + private static final int ITEM_CONTINUED = 1; + + private final Consumer<List<T>> mConsumer; + + private final Object mLock = new Object(); + + @GuardedBy("mLock") + private final List<T> mList = new ArrayList<>(); + + @GuardedBy("mLock") + private int mCount; + + @GuardedBy("mLock") + private boolean mConsumed; + + /** + * Creates an instance. + * + * @param consumer a consumer that consumes the list received + */ + public ParcelableListBinder(@NonNull Consumer<List<T>> consumer) { + mConsumer = consumer; + } + + @Override + protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) + throws RemoteException { + if (code != FIRST_CALL_TRANSACTION) { + return super.onTransact(code, data, reply, flags); + } + List<T> listToBeConsumed; + synchronized (mLock) { + if (mConsumed) { + return false; + } + int i = mList.size(); + if (i == 0) { + mCount = data.readInt(); + } + while (i < mCount && data.readInt() != END_OF_PARCEL) { + mList.add(data.readParcelable(null)); + i++; + } + if (i >= mCount) { + listToBeConsumed = mList; + mConsumed = true; + } else { + listToBeConsumed = null; + } + } + if (listToBeConsumed != null) { + mConsumer.accept(listToBeConsumed); + } + return true; + } + + /** + * Sends a list of {@link Parcelable} to a binder. + * + * @param binder a binder interface backed by {@link ParcelableListBinder} + * @param list a list to send + */ + public static <T extends Parcelable> void send(@NonNull IBinder binder, @NonNull List<T> list) + throws RemoteException { + int count = list.size(); + int i = 0; + while (i < count) { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + if (i == 0) { + data.writeInt(count); + } + while (i < count && data.dataSize() < SUGGESTED_MAX_IPC_SIZE) { + data.writeInt(ITEM_CONTINUED); + data.writeParcelable(list.get(i), 0); + i++; + } + if (i < count) { + data.writeInt(END_OF_PARCEL); + } + binder.transact(FIRST_CALL_TRANSACTION, data, reply, 0); + reply.recycle(); + data.recycle(); + } + } +} diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java index 8777ceacf884..1e02f49c43e4 100644 --- a/services/core/java/com/android/server/media/MediaSessionRecord.java +++ b/services/core/java/com/android/server/media/MediaSessionRecord.java @@ -36,6 +36,7 @@ import android.media.session.MediaController; import android.media.session.MediaController.PlaybackInfo; import android.media.session.MediaSession; import android.media.session.MediaSession.QueueItem; +import android.media.session.ParcelableListBinder; import android.media.session.PlaybackState; import android.net.Uri; import android.os.Binder; @@ -905,14 +906,24 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR } @Override - public void setQueue(ParceledListSlice queue) throws RemoteException { + public void resetQueue() throws RemoteException { synchronized (mLock) { - mQueue = queue == null ? null : (List<QueueItem>) queue.getList(); + mQueue = null; } mHandler.post(MessageHandler.MSG_UPDATE_QUEUE); } @Override + public IBinder getBinderForSetQueue() throws RemoteException { + return new ParcelableListBinder<QueueItem>((list) -> { + synchronized (mLock) { + mQueue = list; + } + mHandler.post(MessageHandler.MSG_UPDATE_QUEUE); + }); + } + + @Override public void setQueueTitle(CharSequence title) throws RemoteException { mQueueTitle = title; mHandler.post(MessageHandler.MSG_UPDATE_QUEUE_TITLE); |