ParcelImplListSlice.java
/*
* Copyright 2019 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 androidx.media2.common;
import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
import android.annotation.SuppressLint;
import android.os.Binder;
import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.RemoteException;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.RestrictTo;
import androidx.versionedparcelable.ParcelImpl;
import java.util.ArrayList;
import java.util.List;
/**
* Transfer a large list of {@link ParcelImpl} objects across an IPC. Splits into
* multiple transactions if needed.
*
* Note: Using this class causes synchronous binder calls in the opposite direction regardless of
* "oneway" property.
*
* @hide
*/
@RestrictTo(LIBRARY_GROUP)
@SuppressLint("BanParcelableUsage")
public class ParcelImplListSlice implements Parcelable {
private static final String TAG = "ParcelImplListSlice";
private static final boolean DEBUG = false;
private static final int MAX_IPC_SIZE = 64 * 1024; // IBinder.MAX_IPC_SIZE
private static final int INLINE_COUNT_LIMIT = 1;
final List<ParcelImpl> mList;
public ParcelImplListSlice(@NonNull List<ParcelImpl> list) {
if (list == null) {
throw new NullPointerException("list shouldn't be null");
}
mList = list;
}
ParcelImplListSlice(Parcel p) {
final int itemCount = p.readInt();
mList = new ArrayList<>(itemCount);
if (DEBUG) {
Log.d(TAG, "Retrieving " + itemCount + " items");
}
if (itemCount <= 0) {
return;
}
int i = 0;
while (i < itemCount) {
if (p.readInt() == 0) {
break;
}
final ParcelImpl parcelImpl = p.readParcelable(ParcelImpl.class.getClassLoader());
mList.add(parcelImpl);
if (DEBUG) {
Log.d(TAG, "Read inline #" + i + ": " + mList.get(mList.size() - 1));
}
i++;
}
if (i >= itemCount) {
return;
}
final IBinder retriever = p.readStrongBinder();
while (i < itemCount) {
if (DEBUG) {
Log.d(TAG, "Reading more @" + i + " of " + itemCount + ": retriever=" + retriever);
}
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
try {
data.writeInt(i);
try {
retriever.transact(IBinder.FIRST_CALL_TRANSACTION, data, reply, 0);
} catch (RemoteException e) {
Log.w(TAG, "Failure retrieving array; only received " + i + " of " + itemCount,
e);
return;
}
while (i < itemCount && reply.readInt() != 0) {
final ParcelImpl parcelImpl = reply.readParcelable(
ParcelImpl.class.getClassLoader());
mList.add(parcelImpl);
if (DEBUG) {
Log.d(TAG, "Read extra #" + i + ": " + mList.get(mList.size() - 1));
}
i++;
}
} finally {
reply.recycle();
data.recycle();
}
}
}
public @NonNull List<ParcelImpl> getList() {
return mList;
}
/**
* Write this to another Parcel. Note that this discards the internal Parcel
* and should not be used anymore. This is so we can pass this to a Binder
* where we won't have a chance to call recycle on this.
*/
@Override
public void writeToParcel(Parcel dest, int flags) {
final int itemCount = mList.size();
dest.writeInt(itemCount);
if (DEBUG) {
Log.d(TAG, "Writing " + itemCount + " items");
}
if (itemCount > 0) {
int i = 0;
while (i < itemCount && i < INLINE_COUNT_LIMIT && dest.dataSize() < MAX_IPC_SIZE) {
dest.writeInt(1);
final ParcelImpl parcelable = mList.get(i);
dest.writeParcelable(parcelable, flags);
if (DEBUG) {
Log.d(TAG, "Wrote inline #" + i + ": " + mList.get(i));
}
i++;
}
if (i < itemCount) {
dest.writeInt(0);
Binder retriever = new Binder() {
@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);
}
int i = data.readInt();
if (DEBUG) {
Log.d(TAG, "Writing more @" + i + " of " + itemCount);
}
while (i < itemCount && reply.dataSize() < MAX_IPC_SIZE) {
reply.writeInt(1);
final ParcelImpl parcelable = mList.get(i);
reply.writeParcelable(parcelable, flags);
if (DEBUG) {
Log.d(TAG, "Wrote extra #" + i + ": " + mList.get(i));
}
i++;
}
if (i < itemCount) {
if (DEBUG) {
Log.d(TAG, "Breaking @" + i + " of " + itemCount);
}
reply.writeInt(0);
}
return true;
}
};
if (DEBUG) {
Log.d(TAG, "Breaking @" + i + " of " + itemCount + ": retriever=" + retriever);
}
dest.writeStrongBinder(retriever);
}
}
}
@Override
public int describeContents() {
return 0;
}
public static final Parcelable.Creator<ParcelImplListSlice> CREATOR =
new Parcelable.Creator<ParcelImplListSlice>() {
@Override
public ParcelImplListSlice createFromParcel(Parcel in) {
return new ParcelImplListSlice(in);
}
@Override
public ParcelImplListSlice[] newArray(int size) {
return new ParcelImplListSlice[size];
}
};
}