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