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.Collections;
28import 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 */
42public class ParceledListSlice<T extends Parcelable> 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    public static <T extends Parcelable> ParceledListSlice<T> emptyList() {
55        return new ParceledListSlice<T>(Collections.<T> emptyList());
56    }
57
58    public ParceledListSlice(List<T> list) {
59        mList = list;
60    }
61
62    @SuppressWarnings("unchecked")
63    private ParceledListSlice(Parcel p, ClassLoader loader) {
64        final int N = p.readInt();
65        mList = new ArrayList<T>(N);
66        if (DEBUG) Log.d(TAG, "Retrieving " + N + " items");
67        if (N <= 0) {
68            return;
69        }
70
71        Parcelable.Creator<?> creator = p.readParcelableCreator(loader);
72        Class<?> listElementClass = null;
73
74        int i = 0;
75        while (i < N) {
76            if (p.readInt() == 0) {
77                break;
78            }
79
80            final T parcelable = p.readCreator(creator, loader);
81            if (listElementClass == null) {
82                listElementClass = parcelable.getClass();
83            } else {
84                verifySameType(listElementClass, parcelable.getClass());
85            }
86
87            mList.add(parcelable);
88
89            if (DEBUG) Log.d(TAG, "Read inline #" + i + ": " + mList.get(mList.size()-1));
90            i++;
91        }
92        if (i >= N) {
93            return;
94        }
95        final IBinder retriever = p.readStrongBinder();
96        while (i < N) {
97            if (DEBUG) Log.d(TAG, "Reading more @" + i + " of " + N + ": retriever=" + retriever);
98            Parcel data = Parcel.obtain();
99            Parcel reply = Parcel.obtain();
100            data.writeInt(i);
101            try {
102                retriever.transact(IBinder.FIRST_CALL_TRANSACTION, data, reply, 0);
103            } catch (RemoteException e) {
104                Log.w(TAG, "Failure retrieving array; only received " + i + " of " + N, e);
105                return;
106            }
107            while (i < N && reply.readInt() != 0) {
108                final T parcelable = reply.readCreator(creator, loader);
109                verifySameType(listElementClass, parcelable.getClass());
110
111                mList.add(parcelable);
112
113                if (DEBUG) Log.d(TAG, "Read extra #" + i + ": " + mList.get(mList.size()-1));
114                i++;
115            }
116            reply.recycle();
117            data.recycle();
118        }
119    }
120
121    private static void verifySameType(final Class<?> expected, final Class<?> actual) {
122        if (!actual.equals(expected)) {
123            throw new IllegalArgumentException("Can't unparcel type "
124                    + actual.getName() + " in list of type "
125                    + expected.getName());
126        }
127    }
128
129    public List<T> getList() {
130        return mList;
131    }
132
133    @Override
134    public int describeContents() {
135        int contents = 0;
136        for (int i=0; i<mList.size(); i++) {
137            contents |= mList.get(i).describeContents();
138        }
139        return contents;
140    }
141
142    /**
143     * Write this to another Parcel. Note that this discards the internal Parcel
144     * and should not be used anymore. This is so we can pass this to a Binder
145     * where we won't have a chance to call recycle on this.
146     */
147    @Override
148    public void writeToParcel(Parcel dest, int flags) {
149        final int N = mList.size();
150        final int callFlags = flags;
151        dest.writeInt(N);
152        if (DEBUG) Log.d(TAG, "Writing " + N + " items");
153        if (N > 0) {
154            final Class<?> listElementClass = mList.get(0).getClass();
155            dest.writeParcelableCreator(mList.get(0));
156            int i = 0;
157            while (i < N && dest.dataSize() < MAX_IPC_SIZE) {
158                dest.writeInt(1);
159
160                final T parcelable = mList.get(i);
161                verifySameType(listElementClass, parcelable.getClass());
162                parcelable.writeToParcel(dest, callFlags);
163
164                if (DEBUG) Log.d(TAG, "Wrote inline #" + i + ": " + mList.get(i));
165                i++;
166            }
167            if (i < N) {
168                dest.writeInt(0);
169                Binder retriever = new Binder() {
170                    @Override
171                    protected boolean onTransact(int code, Parcel data, Parcel reply, int flags)
172                            throws RemoteException {
173                        if (code != FIRST_CALL_TRANSACTION) {
174                            return super.onTransact(code, data, reply, flags);
175                        }
176                        int i = data.readInt();
177                        if (DEBUG) Log.d(TAG, "Writing more @" + i + " of " + N);
178                        while (i < N && reply.dataSize() < MAX_IPC_SIZE) {
179                            reply.writeInt(1);
180
181                            final T parcelable = mList.get(i);
182                            verifySameType(listElementClass, parcelable.getClass());
183                            parcelable.writeToParcel(reply, callFlags);
184
185                            if (DEBUG) Log.d(TAG, "Wrote extra #" + i + ": " + mList.get(i));
186                            i++;
187                        }
188                        if (i < N) {
189                            if (DEBUG) Log.d(TAG, "Breaking @" + i + " of " + N);
190                            reply.writeInt(0);
191                        }
192                        return true;
193                    }
194                };
195                if (DEBUG) Log.d(TAG, "Breaking @" + i + " of " + N + ": retriever=" + retriever);
196                dest.writeStrongBinder(retriever);
197            }
198        }
199    }
200
201    @SuppressWarnings("unchecked")
202    public static final Parcelable.ClassLoaderCreator<ParceledListSlice> CREATOR =
203            new Parcelable.ClassLoaderCreator<ParceledListSlice>() {
204        public ParceledListSlice createFromParcel(Parcel in) {
205            return new ParceledListSlice(in, null);
206        }
207
208        @Override
209        public ParceledListSlice createFromParcel(Parcel in, ClassLoader loader) {
210            return new ParceledListSlice(in, loader);
211        }
212
213        public ParceledListSlice[] newArray(int size) {
214            return new ParceledListSlice[size];
215        }
216    };
217}
218