Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 45fb6fe0 authored by Gyumin Sim's avatar Gyumin Sim Committed by Android (Google) Code Review
Browse files

Merge "Avoid blocking outgoing binder call for setQueue"

parents de751cf9 35d7cd47
Loading
Loading
Loading
Loading
+2 −1
Original line number Diff line number Diff line
@@ -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);
+7 −2
Original line number Diff line number Diff line
@@ -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);
        }
+131 −0
Original line number Diff line number Diff line
/*
 * 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();
        }
    }
}
+13 −2
Original line number Diff line number Diff line
@@ -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,13 +906,23 @@ 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;