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