1 /* 2 * Copyright 2020 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.media.session; 18 19 import android.annotation.NonNull; 20 import android.os.Binder; 21 import android.os.IBinder; 22 import android.os.Parcel; 23 import android.os.Parcelable; 24 import android.os.RemoteException; 25 26 import com.android.internal.annotations.GuardedBy; 27 28 import java.util.ArrayList; 29 import java.util.List; 30 import java.util.function.Consumer; 31 32 /** 33 * Binder to receive a list that has a large number of {@link Parcelable} items. 34 * 35 * It's similar to {@link android.content.pm.ParceledListSlice}, but transactions are performed in 36 * the opposite direction. 37 * 38 * @param <T> the type of {@link Parcelable} 39 * @hide 40 */ 41 public class ParcelableListBinder<T extends Parcelable> extends Binder { 42 43 private static final int SUGGESTED_MAX_IPC_SIZE = IBinder.getSuggestedMaxIpcSizeBytes(); 44 45 private static final int END_OF_PARCEL = 0; 46 private static final int ITEM_CONTINUED = 1; 47 48 private final Class<T> mListElementsClass; 49 private final Consumer<List<T>> mConsumer; 50 51 private final Object mLock = new Object(); 52 53 @GuardedBy("mLock") 54 private final List<T> mList = new ArrayList<>(); 55 56 @GuardedBy("mLock") 57 private int mCount; 58 59 @GuardedBy("mLock") 60 private boolean mConsumed; 61 62 /** 63 * Creates an instance. 64 * 65 * @param listElementsClass the class of the list elements. 66 * @param consumer a consumer that consumes the list received 67 */ ParcelableListBinder(Class<T> listElementsClass, @NonNull Consumer<List<T>> consumer)68 public ParcelableListBinder(Class<T> listElementsClass, @NonNull Consumer<List<T>> consumer) { 69 mListElementsClass = listElementsClass; 70 mConsumer = consumer; 71 } 72 73 @Override onTransact(int code, Parcel data, Parcel reply, int flags)74 protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) 75 throws RemoteException { 76 if (code != FIRST_CALL_TRANSACTION) { 77 return super.onTransact(code, data, reply, flags); 78 } 79 List<T> listToBeConsumed; 80 synchronized (mLock) { 81 if (mConsumed) { 82 return false; 83 } 84 int i = mList.size(); 85 if (i == 0) { 86 mCount = data.readInt(); 87 } 88 while (i < mCount && data.readInt() != END_OF_PARCEL) { 89 Object object = data.readParcelable(null); 90 if (mListElementsClass.isAssignableFrom(object.getClass())) { 91 // Checking list items are of compaitible types to validate against malicious 92 // apps calling it directly via reflection with non compilable items. 93 // See b/317048338 for more details 94 mList.add((T) object); 95 } 96 i++; 97 } 98 if (i >= mCount) { 99 listToBeConsumed = mList; 100 mConsumed = true; 101 } else { 102 listToBeConsumed = null; 103 } 104 } 105 if (listToBeConsumed != null) { 106 mConsumer.accept(listToBeConsumed); 107 } 108 return true; 109 } 110 111 /** 112 * Sends a list of {@link Parcelable} to a binder. 113 * 114 * @param binder a binder interface backed by {@link ParcelableListBinder} 115 * @param list a list to send 116 */ send(@onNull IBinder binder, @NonNull List<T> list)117 public static <T extends Parcelable> void send(@NonNull IBinder binder, @NonNull List<T> list) 118 throws RemoteException { 119 int count = list.size(); 120 int i = 0; 121 do { 122 Parcel data = Parcel.obtain(); 123 Parcel reply = Parcel.obtain(); 124 if (i == 0) { 125 data.writeInt(count); 126 } 127 while (i < count && data.dataSize() < SUGGESTED_MAX_IPC_SIZE) { 128 data.writeInt(ITEM_CONTINUED); 129 data.writeParcelable(list.get(i), 0); 130 i++; 131 } 132 if (i < count) { 133 data.writeInt(END_OF_PARCEL); 134 } 135 binder.transact(FIRST_CALL_TRANSACTION, data, reply, 0); 136 reply.recycle(); 137 data.recycle(); 138 } while (i < count); 139 } 140 } 141