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; 18 19 import android.os.Binder; 20 import android.os.IBinder; 21 import android.os.Parcel; 22 import android.os.Parcelable; 23 import android.os.RemoteException; 24 import android.util.Log; 25 26 import java.util.ArrayList; 27 import java.util.List; 28 29 /** 30 * This is a copied version of BaseParceledListSlice in framework with hidden API usages 31 * removed. 32 * 33 * Transfer a large list of Parcelable objects across an IPC. Splits into 34 * multiple transactions if needed. 35 * 36 * Caveat: for efficiency and security, all elements must be the same concrete type. 37 * In order to avoid writing the class name of each object, we must ensure that 38 * each object is the same type, or else unparceling then reparceling the data may yield 39 * a different result if the class name encoded in the Parcelable is a Base type. 40 * See b/17671747. 41 * 42 * @hide 43 */ 44 abstract class BaseMediaParceledListSlice<T> implements Parcelable { 45 private static String TAG = "BaseMediaParceledListSlice"; 46 private static final boolean DEBUG = false; 47 48 /* 49 * TODO get this number from somewhere else. For now set it to a quarter of 50 * the 1MB limit. 51 */ 52 // private static final int MAX_IPC_SIZE = IBinder.getSuggestedMaxIpcSizeBytes(); 53 private static final int MAX_IPC_SIZE = 64 * 1024; 54 55 private final List<T> mList; 56 57 private int mInlineCountLimit = Integer.MAX_VALUE; 58 BaseMediaParceledListSlice(List<T> list)59 public BaseMediaParceledListSlice(List<T> list) { 60 mList = list; 61 } 62 63 @SuppressWarnings("unchecked") BaseMediaParceledListSlice(Parcel p, ClassLoader loader)64 BaseMediaParceledListSlice(Parcel p, ClassLoader loader) { 65 final int N = p.readInt(); 66 mList = new ArrayList<T>(N); 67 if (DEBUG) Log.d(TAG, "Retrieving " + N + " items"); 68 if (N <= 0) { 69 return; 70 } 71 72 Parcelable.Creator<?> creator = readParcelableCreator(p, loader); 73 Class<?> listElementClass = null; 74 75 int i = 0; 76 while (i < N) { 77 if (p.readInt() == 0) { 78 break; 79 } 80 81 final T parcelable = readCreator(creator, p, loader); 82 if (listElementClass == null) { 83 listElementClass = parcelable.getClass(); 84 } else { 85 verifySameType(listElementClass, parcelable.getClass()); 86 } 87 88 mList.add(parcelable); 89 90 if (DEBUG) Log.d(TAG, "Read inline #" + i + ": " + mList.get(mList.size()-1)); 91 i++; 92 } 93 if (i >= N) { 94 return; 95 } 96 final IBinder retriever = p.readStrongBinder(); 97 while (i < N) { 98 if (DEBUG) Log.d(TAG, "Reading more @" + i + " of " + N + ": retriever=" + retriever); 99 Parcel data = Parcel.obtain(); 100 Parcel reply = Parcel.obtain(); 101 data.writeInt(i); 102 try { 103 retriever.transact(IBinder.FIRST_CALL_TRANSACTION, data, reply, 0); 104 } catch (RemoteException e) { 105 Log.w(TAG, "Failure retrieving array; only received " + i + " of " + N, e); 106 return; 107 } 108 while (i < N && reply.readInt() != 0) { 109 final T parcelable = readCreator(creator, reply, loader); 110 if (listElementClass == null) { 111 listElementClass = parcelable.getClass(); 112 } else { 113 verifySameType(listElementClass, parcelable.getClass()); 114 } 115 116 mList.add(parcelable); 117 118 if (DEBUG) Log.d(TAG, "Read extra #" + i + ": " + mList.get(mList.size()-1)); 119 i++; 120 } 121 reply.recycle(); 122 data.recycle(); 123 } 124 } 125 readCreator(Parcelable.Creator<?> creator, Parcel p, ClassLoader loader)126 private T readCreator(Parcelable.Creator<?> creator, Parcel p, ClassLoader loader) { 127 if (creator instanceof Parcelable.ClassLoaderCreator<?>) { 128 Parcelable.ClassLoaderCreator<?> classLoaderCreator = 129 (Parcelable.ClassLoaderCreator<?>) creator; 130 return (T) classLoaderCreator.createFromParcel(p, loader); 131 } 132 return (T) creator.createFromParcel(p); 133 } 134 verifySameType(final Class<?> expected, final Class<?> actual)135 private static void verifySameType(final Class<?> expected, final Class<?> actual) { 136 if (!actual.equals(expected)) { 137 throw new IllegalArgumentException("Can't unparcel type " 138 + (actual == null ? null : actual.getName()) + " in list of type " 139 + (expected == null ? null : expected.getName())); 140 } 141 } 142 getList()143 public List<T> getList() { 144 return mList; 145 } 146 147 /** 148 * Set a limit on the maximum number of entries in the array that will be included 149 * inline in the initial parcelling of this object. 150 */ setInlineCountLimit(int maxCount)151 public void setInlineCountLimit(int maxCount) { 152 mInlineCountLimit = maxCount; 153 } 154 155 /** 156 * Write this to another Parcel. Note that this discards the internal Parcel 157 * and should not be used anymore. This is so we can pass this to a Binder 158 * where we won't have a chance to call recycle on this. 159 */ 160 @Override writeToParcel(Parcel dest, int flags)161 public void writeToParcel(Parcel dest, int flags) { 162 final int N = mList.size(); 163 final int callFlags = flags; 164 dest.writeInt(N); 165 if (DEBUG) Log.d(TAG, "Writing " + N + " items"); 166 if (N > 0) { 167 final Class<?> listElementClass = mList.get(0).getClass(); 168 writeParcelableCreator(mList.get(0), dest); 169 int i = 0; 170 while (i < N && i < mInlineCountLimit && dest.dataSize() < MAX_IPC_SIZE) { 171 dest.writeInt(1); 172 173 final T parcelable = mList.get(i); 174 verifySameType(listElementClass, parcelable.getClass()); 175 writeElement(parcelable, dest, callFlags); 176 177 if (DEBUG) Log.d(TAG, "Wrote inline #" + i + ": " + mList.get(i)); 178 i++; 179 } 180 if (i < N) { 181 dest.writeInt(0); 182 Binder retriever = new Binder() { 183 @Override 184 protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) 185 throws RemoteException { 186 if (code != FIRST_CALL_TRANSACTION) { 187 return super.onTransact(code, data, reply, flags); 188 } 189 int i = data.readInt(); 190 if (DEBUG) Log.d(TAG, "Writing more @" + i + " of " + N); 191 while (i < N && reply.dataSize() < MAX_IPC_SIZE) { 192 reply.writeInt(1); 193 194 final T parcelable = mList.get(i); 195 verifySameType(listElementClass, parcelable.getClass()); 196 writeElement(parcelable, reply, callFlags); 197 198 if (DEBUG) Log.d(TAG, "Wrote extra #" + i + ": " + mList.get(i)); 199 i++; 200 } 201 if (i < N) { 202 if (DEBUG) Log.d(TAG, "Breaking @" + i + " of " + N); 203 reply.writeInt(0); 204 } 205 return true; 206 } 207 }; 208 if (DEBUG) Log.d(TAG, "Breaking @" + i + " of " + N + ": retriever=" + retriever); 209 dest.writeStrongBinder(retriever); 210 } 211 } 212 } 213 writeElement(T parcelable, Parcel reply, int callFlags)214 abstract void writeElement(T parcelable, Parcel reply, int callFlags); 215 writeParcelableCreator(T parcelable, Parcel dest)216 abstract void writeParcelableCreator(T parcelable, Parcel dest); 217 readParcelableCreator(Parcel from, ClassLoader loader)218 abstract Parcelable.Creator<?> readParcelableCreator(Parcel from, ClassLoader loader); 219 } 220