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
17package android.content.pm;
18
19import android.os.Binder;
20import android.os.IBinder;
21import android.os.Parcel;
22import android.os.Parcelable;
23import android.os.RemoteException;
24import android.util.Log;
25
26import java.util.ArrayList;
27import 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 */
41abstract 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
55    public BaseParceledListSlice(List<T> list) {
56        mList = list;
57    }
58
59    @SuppressWarnings("unchecked")
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
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
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
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     */
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
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
206    protected abstract void writeElement(T parcelable, Parcel reply, int callFlags);
207
208    protected abstract void writeParcelableCreator(T parcelable, Parcel dest);
209
210    protected abstract Parcelable.Creator<?> readParcelableCreator(Parcel from, ClassLoader loader);
211}
212