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

Commit 35d7cd47 authored by Gyumin Sim's avatar Gyumin Sim
Browse files

Avoid blocking outgoing binder call for setQueue

There has been a blocking binder call out of system_server for
MediaSession#setQueue because ParceledListSlice internally creates
another binder on the caller side and calls the binder from callee
if the list is too large to fit in a single IPC transaction.

It introduces ParcelableListBinder to do multiple transactions in the
same direction, so that the binder call doesn't block system_server.

Bug: 147703076
Test: atest CtsMediaTestCases:android.media.cts.MediaSessionTest
Change-Id: I5623c20d615d25c88999d80615958d6cb6e51a77
parent 2503bf24
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;