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