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 String TAG = "ParceledListSlice";
46     private static 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 
54     private List<T> mList;
55 
56     private int mInlineCountLimit = Integer.MAX_VALUE;
57 
58     private boolean mHasBeenParceled = false;
59 
BaseParceledListSlice(List<T> list)60     public BaseParceledListSlice(List<T> list) {
61         mList = list;
62     }
63 
64     @SuppressWarnings("unchecked")
BaseParceledListSlice(Parcel p, ClassLoader loader)65     BaseParceledListSlice(Parcel p, ClassLoader loader) {
66         final int N = p.readInt();
67         mList = new ArrayList<T>(N);
68         if (DEBUG) Log.d(TAG, "Retrieving " + N + " items");
69         if (N <= 0) {
70             return;
71         }
72 
73         Parcelable.Creator<?> creator = readParcelableCreator(p, loader);
74         Class<?> listElementClass = null;
75 
76         int i = 0;
77         while (i < N) {
78             if (p.readInt() == 0) {
79                 break;
80             }
81             listElementClass = readVerifyAndAddElement(creator, p, loader, listElementClass);
82             if (DEBUG) Log.d(TAG, "Read inline #" + i + ": " + mList.get(mList.size()-1));
83             i++;
84         }
85         if (i >= N) {
86             return;
87         }
88         final IBinder retriever = p.readStrongBinder();
89         while (i < N) {
90             if (DEBUG) Log.d(TAG, "Reading more @" + i + " of " + N + ": retriever=" + retriever);
91             Parcel data = Parcel.obtain();
92             Parcel reply = Parcel.obtain();
93             data.writeInt(i);
94             try {
95                 retriever.transact(IBinder.FIRST_CALL_TRANSACTION, data, reply, 0);
96                 reply.readException();
97                 while (i < N && reply.readInt() != 0) {
98                     listElementClass = readVerifyAndAddElement(creator, reply, loader,
99                             listElementClass);
100                     if (DEBUG) Log.d(TAG, "Read extra #" + i + ": " + mList.get(mList.size()-1));
101                     i++;
102                 }
103             } catch (RemoteException e) {
104                 throw new BadParcelableException(
105                         "Failure retrieving array; only received " + i + " of " + N, e);
106             } finally {
107                 reply.recycle();
108                 data.recycle();
109             }
110         }
111     }
112 
readVerifyAndAddElement(Parcelable.Creator<?> creator, Parcel p, ClassLoader loader, Class<?> listElementClass)113     private Class<?> readVerifyAndAddElement(Parcelable.Creator<?> creator, Parcel p,
114             ClassLoader loader, Class<?> listElementClass) {
115         final T parcelable = readCreator(creator, p, loader);
116         if (listElementClass == null) {
117             listElementClass = parcelable.getClass();
118         } else {
119             verifySameType(listElementClass, parcelable.getClass());
120         }
121         mList.add(parcelable);
122         return listElementClass;
123     }
124 
readCreator(Parcelable.Creator<?> creator, Parcel p, ClassLoader loader)125     private T readCreator(Parcelable.Creator<?> creator, Parcel p, ClassLoader loader) {
126         if (creator instanceof Parcelable.ClassLoaderCreator<?>) {
127             Parcelable.ClassLoaderCreator<?> classLoaderCreator =
128                     (Parcelable.ClassLoaderCreator<?>) creator;
129             return (T) classLoaderCreator.createFromParcel(p, loader);
130         }
131         return (T) creator.createFromParcel(p);
132     }
133 
verifySameType(final Class<?> expected, final Class<?> actual)134     private static void verifySameType(final Class<?> expected, final Class<?> actual) {
135         if (!actual.equals(expected)) {
136             throw new IllegalArgumentException("Can't unparcel type "
137                     + (actual == null ? null : actual.getName()) + " in list of type "
138                     + (expected == null ? null : expected.getName()));
139         }
140     }
141 
142     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
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      * This method can only be called once per BaseParceledListSlice to ensure that
161      * the referenced list can be cleaned up before the recipient cleans up the
162      * Binder reference.
163      */
164     @Override
writeToParcel(Parcel dest, int flags)165     public void writeToParcel(Parcel dest, int flags) {
166         if (mHasBeenParceled) {
167             throw new IllegalStateException("Can't Parcel a ParceledListSlice more than once");
168         }
169         mHasBeenParceled = true;
170         final int N = mList.size();
171         final int callFlags = flags;
172         dest.writeInt(N);
173         if (DEBUG) Log.d(TAG, "Writing " + N + " items");
174         if (N > 0) {
175             final Class<?> listElementClass = mList.get(0).getClass();
176             writeParcelableCreator(mList.get(0), dest);
177             int i = 0;
178             while (i < N && i < mInlineCountLimit && dest.dataSize() < MAX_IPC_SIZE) {
179                 dest.writeInt(1);
180 
181                 final T parcelable = mList.get(i);
182                 verifySameType(listElementClass, parcelable.getClass());
183                 writeElement(parcelable, dest, callFlags);
184 
185                 if (DEBUG) Log.d(TAG, "Wrote inline #" + i + ": " + mList.get(i));
186                 i++;
187             }
188             if (i < N) {
189                 dest.writeInt(0);
190                 Binder retriever = new Binder() {
191                     @Override
192                     protected boolean onTransact(int code, Parcel data, Parcel reply, int flags)
193                             throws RemoteException {
194                         if (code != FIRST_CALL_TRANSACTION) {
195                             return super.onTransact(code, data, reply, flags);
196                         } else if (mList == null) {
197                             throw new IllegalArgumentException("Attempt to transfer null list, "
198                                     + "did transfer finish?");
199                         }
200                         int i = data.readInt();
201 
202                         if (DEBUG) {
203                             Log.d(TAG, "Writing more @" + i + " of " + N + " to "
204                                     + Binder.getCallingPid() + ", sender=" + this);
205                         }
206 
207                         try {
208                             reply.writeNoException();
209                             while (i < N && reply.dataSize() < MAX_IPC_SIZE) {
210                                 reply.writeInt(1);
211 
212                                 final T parcelable = mList.get(i);
213                                 verifySameType(listElementClass, parcelable.getClass());
214                                 writeElement(parcelable, reply, callFlags);
215 
216                                 if (DEBUG) Log.d(TAG, "Wrote extra #" + i + ": " + mList.get(i));
217                                 i++;
218                             }
219                             if (i < N) {
220                                 if (DEBUG) Log.d(TAG, "Breaking @" + i + " of " + N);
221                                 reply.writeInt(0);
222                             } else {
223                                 if (DEBUG) Log.d(TAG, "Transfer done, clearing mList reference");
224                                 mList = null;
225                             }
226                         } catch (RuntimeException e) {
227                             if (DEBUG) Log.d(TAG, "Transfer failed, clearing mList reference");
228                             mList = null;
229                             throw e;
230                         }
231                         return true;
232                     }
233                 };
234                 if (DEBUG) Log.d(TAG, "Breaking @" + i + " of " + N + ": retriever=" + retriever);
235                 dest.writeStrongBinder(retriever);
236             }
237         }
238     }
239 
writeElement(T parcelable, Parcel reply, int callFlags)240     protected abstract void writeElement(T parcelable, Parcel reply, int callFlags);
241 
242     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
writeParcelableCreator(T parcelable, Parcel dest)243     protected abstract void writeParcelableCreator(T parcelable, Parcel dest);
244 
readParcelableCreator(Parcel from, ClassLoader loader)245     protected abstract Parcelable.Creator<?> readParcelableCreator(Parcel from, ClassLoader loader);
246 }
247