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