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