1 /*
2  * Copyright (C) 2011 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.content.pm;
18 
19 import android.compat.annotation.UnsupportedAppUsage;
20 import android.os.BadParcelableException;
21 import android.os.Binder;
22 import android.os.Build;
23 import android.os.IBinder;
24 import android.os.Parcel;
25 import android.os.Parcelable;
26 import android.os.RemoteException;
27 import android.util.Log;
28 
29 import java.util.ArrayList;
30 import java.util.List;
31 
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 BaseParceledListSlice<T> implements Parcelable {
45     private static final String TAG = "ParceledListSlice";
46     private static final boolean DEBUG = false;
47 
48     private static final int MAX_IPC_SIZE = IBinder.getSuggestedMaxIpcSizeBytes();
49 
50     /**
51      * As of 2024 and for some time, max size has been 64KB. If a single
52      * element is too large, this class will write too big of Parcels,
53      * so log. 64KB/4 is 16KB is still pretty big for a single element
54      * (which could result in a ~64KB + 16KB = 80KB transaction). We may
55      * want to reduce the warning size just in case. Though, 64KB is
56      * already quite large for binder transactions, another strategy may
57      * be needed.
58      */
59     private static final int WARN_ELM_SIZE = MAX_IPC_SIZE / 4;
60 
61     private List<T> mList;
62 
63     private int mInlineCountLimit = Integer.MAX_VALUE;
64 
65     private boolean mHasBeenParceled = false;
66 
BaseParceledListSlice(List<T> list)67     public BaseParceledListSlice(List<T> list) {
68         mList = list;
69     }
70 
71     @SuppressWarnings("unchecked")
BaseParceledListSlice(Parcel p, ClassLoader loader)72     BaseParceledListSlice(Parcel p, ClassLoader loader) {
73         final int N = p.readInt();
74         mList = new ArrayList<T>(N);
75         if (DEBUG) Log.d(TAG, "Retrieving " + N + " items");
76         if (N <= 0) {
77             return;
78         }
79 
80         Parcelable.Creator<?> creator = readParcelableCreator(p, loader);
81         Class<?> listElementClass = null;
82 
83         int i = 0;
84         while (i < N) {
85             if (p.readInt() == 0) {
86                 break;
87             }
88             listElementClass = readVerifyAndAddElement(creator, p, loader, listElementClass);
89             if (DEBUG) Log.d(TAG, "Read inline #" + i + ": " + mList.get(mList.size()-1));
90             i++;
91         }
92         if (i >= N) {
93             return;
94         }
95         final IBinder retriever = p.readStrongBinder();
96         while (i < N) {
97             if (DEBUG) Log.d(TAG, "Reading more @" + i + " of " + N + ": retriever=" + retriever);
98             Parcel data = Parcel.obtain();
99             Parcel reply = Parcel.obtain();
100             data.writeInt(i);
101             try {
102                 retriever.transact(IBinder.FIRST_CALL_TRANSACTION, data, reply, 0);
103                 reply.readException();
104                 while (i < N && reply.readInt() != 0) {
105                     listElementClass = readVerifyAndAddElement(creator, reply, loader,
106                             listElementClass);
107                     if (DEBUG) Log.d(TAG, "Read extra #" + i + ": " + mList.get(mList.size()-1));
108                     i++;
109                 }
110             } catch (RemoteException e) {
111                 throw new BadParcelableException(
112                         "Failure retrieving array; only received " + i + " of " + N, e);
113             } finally {
114                 reply.recycle();
115                 data.recycle();
116             }
117         }
118     }
119 
readVerifyAndAddElement(Parcelable.Creator<?> creator, Parcel p, ClassLoader loader, Class<?> listElementClass)120     private Class<?> readVerifyAndAddElement(Parcelable.Creator<?> creator, Parcel p,
121             ClassLoader loader, Class<?> listElementClass) {
122         final T parcelable = readCreator(creator, p, loader);
123         if (listElementClass == null) {
124             listElementClass = parcelable.getClass();
125         } else {
126             verifySameType(listElementClass, parcelable.getClass());
127         }
128         mList.add(parcelable);
129         return listElementClass;
130     }
131 
readCreator(Parcelable.Creator<?> creator, Parcel p, ClassLoader loader)132     private T readCreator(Parcelable.Creator<?> creator, Parcel p, ClassLoader loader) {
133         if (creator instanceof Parcelable.ClassLoaderCreator<?>) {
134             Parcelable.ClassLoaderCreator<?> classLoaderCreator =
135                     (Parcelable.ClassLoaderCreator<?>) creator;
136             return (T) classLoaderCreator.createFromParcel(p, loader);
137         }
138         return (T) creator.createFromParcel(p);
139     }
140 
verifySameType(final Class<?> expected, final Class<?> actual)141     private static void verifySameType(final Class<?> expected, final Class<?> actual) {
142         if (!actual.equals(expected)) {
143             throw new IllegalArgumentException("Can't unparcel type "
144                     + (actual == null ? null : actual.getName()) + " in list of type "
145                     + (expected == null ? null : expected.getName()));
146         }
147     }
148 
149     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
getList()150     public List<T> getList() {
151         return mList;
152     }
153 
154     /**
155      * Set a limit on the maximum number of entries in the array that will be included
156      * inline in the initial parcelling of this object.
157      */
setInlineCountLimit(int maxCount)158     public void setInlineCountLimit(int maxCount) {
159         mInlineCountLimit = maxCount;
160     }
161 
162     /**
163      * Write this to another Parcel. Note that this discards the internal Parcel
164      * and should not be used anymore. This is so we can pass this to a Binder
165      * where we won't have a chance to call recycle on this.
166      *
167      * This method can only be called once per BaseParceledListSlice to ensure that
168      * the referenced list can be cleaned up before the recipient cleans up the
169      * Binder reference.
170      */
171     @Override
writeToParcel(Parcel dest, int flags)172     public void writeToParcel(Parcel dest, int flags) {
173         if (mHasBeenParceled) {
174             throw new IllegalStateException("Can't Parcel a ParceledListSlice more than once");
175         }
176         mHasBeenParceled = true;
177         final int N = mList.size();
178         final int callFlags = flags;
179         dest.writeInt(N);
180         if (DEBUG) Log.d(TAG, "Writing " + N + " items");
181         if (N > 0) {
182             final Class<?> listElementClass = mList.get(0).getClass();
183             writeParcelableCreator(mList.get(0), dest);
184             int i = 0;
185             while (i < N && i < mInlineCountLimit && dest.dataSize() < MAX_IPC_SIZE) {
186                 dest.writeInt(1);
187 
188                 final T parcelable = mList.get(i);
189                 verifySameType(listElementClass, parcelable.getClass());
190                 writeElement(parcelable, dest, callFlags);
191 
192                 if (DEBUG) Log.d(TAG, "Wrote inline #" + i + ": " + mList.get(i));
193                 i++;
194             }
195             if (i < N) {
196                 dest.writeInt(0);
197                 Binder retriever = new Binder() {
198                     @Override
199                     protected boolean onTransact(int code, Parcel data, Parcel reply, int flags)
200                             throws RemoteException {
201                         if (code != FIRST_CALL_TRANSACTION) {
202                             return super.onTransact(code, data, reply, flags);
203                         } else if (mList == null) {
204                             throw new IllegalArgumentException("Attempt to transfer null list, "
205                                     + "did transfer finish?");
206                         }
207                         int i = data.readInt();
208 
209                         if (DEBUG) {
210                             Log.d(TAG, "Writing more @" + i + " of " + N + " to "
211                                     + Binder.getCallingPid() + ", sender=" + this);
212                         }
213 
214                         try {
215                             reply.writeNoException();
216 
217                             // note: this logic ensures if there are enough elements in the list,
218                             // we will always write over the max IPC size. This is dangerous
219                             // when there are large elements.
220                             while (i < N && reply.dataSize() < MAX_IPC_SIZE) {
221                                 reply.writeInt(1);
222 
223                                 int preWriteSize = reply.dataSize();
224 
225                                 final T parcelable = mList.get(i);
226                                 verifySameType(listElementClass, parcelable.getClass());
227                                 writeElement(parcelable, reply, callFlags);
228 
229                                 int elmSize = reply.dataSize() - preWriteSize;
230                                 if (elmSize >= WARN_ELM_SIZE) {
231                                     Log.w(TAG, "Element #" + i + " is " + elmSize + " bytes.");
232                                 }
233 
234                                 if (DEBUG) Log.d(TAG, "Wrote extra #" + i + ": " + mList.get(i));
235                                 i++;
236                             }
237                             if (i < N) {
238                                 if (DEBUG) Log.d(TAG, "Breaking @" + i + " of " + N);
239                                 reply.writeInt(0);
240                             } else {
241                                 if (DEBUG) Log.d(TAG, "Transfer done, clearing mList reference");
242                                 mList = null;
243                             }
244                             if (reply.dataSize() >= MAX_IPC_SIZE + WARN_ELM_SIZE) {
245                                 Log.w(TAG, "Overly large reply size: " + reply.dataSize());
246                             }
247                         } catch (RuntimeException e) {
248                             if (DEBUG) Log.d(TAG, "Transfer failed, clearing mList reference");
249                             mList = null;
250                             throw e;
251                         }
252                         return true;
253                     }
254                 };
255                 if (DEBUG) Log.d(TAG, "Breaking @" + i + " of " + N + ": retriever=" + retriever);
256                 dest.writeStrongBinder(retriever);
257             }
258         }
259     }
260 
writeElement(T parcelable, Parcel reply, int callFlags)261     protected abstract void writeElement(T parcelable, Parcel reply, int callFlags);
262 
263     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
writeParcelableCreator(T parcelable, Parcel dest)264     protected abstract void writeParcelableCreator(T parcelable, Parcel dest);
265 
readParcelableCreator(Parcel from, ClassLoader loader)266     protected abstract Parcelable.Creator<?> readParcelableCreator(Parcel from, ClassLoader loader);
267 }
268