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 * @hide
34 */
35public class ParceledListSlice<T extends Parcelable> implements Parcelable {
36    private static String TAG = "ParceledListSlice";
37    private static boolean DEBUG = false;
38
39    /*
40     * TODO get this number from somewhere else. For now set it to a quarter of
41     * the 1MB limit.
42     */
43    private static final int MAX_IPC_SIZE = 256 * 1024;
44    private static final int MAX_FIRST_IPC_SIZE = MAX_IPC_SIZE / 2;
45
46    private final List<T> mList;
47
48    public ParceledListSlice(List<T> list) {
49        mList = list;
50    }
51
52    private ParceledListSlice(Parcel p, ClassLoader loader) {
53        final int N = p.readInt();
54        mList = new ArrayList<T>(N);
55        if (DEBUG) Log.d(TAG, "Retrieving " + N + " items");
56        if (N <= 0) {
57            return;
58        }
59        Parcelable.Creator<T> creator = p.readParcelableCreator(loader);
60        int i = 0;
61        while (i < N) {
62            if (p.readInt() == 0) {
63                break;
64            }
65            mList.add(p.readCreator(creator, loader));
66            if (DEBUG) Log.d(TAG, "Read inline #" + i + ": " + mList.get(mList.size()-1));
67            i++;
68        }
69        if (i >= N) {
70            return;
71        }
72        final IBinder retriever = p.readStrongBinder();
73        while (i < N) {
74            if (DEBUG) Log.d(TAG, "Reading more @" + i + " of " + N + ": retriever=" + retriever);
75            Parcel data = Parcel.obtain();
76            Parcel reply = Parcel.obtain();
77            data.writeInt(i);
78            try {
79                retriever.transact(IBinder.FIRST_CALL_TRANSACTION, data, reply, 0);
80            } catch (RemoteException e) {
81                Log.w(TAG, "Failure retrieving array; only received " + i + " of " + N, e);
82                return;
83            }
84            while (i < N && reply.readInt() != 0) {
85                mList.add(reply.readCreator(creator, loader));
86                if (DEBUG) Log.d(TAG, "Read extra #" + i + ": " + mList.get(mList.size()-1));
87                i++;
88            }
89            reply.recycle();
90            data.recycle();
91        }
92    }
93
94    public List<T> getList() {
95        return mList;
96    }
97
98    @Override
99    public int describeContents() {
100        int contents = 0;
101        for (int i=0; i<mList.size(); i++) {
102            contents |= mList.get(i).describeContents();
103        }
104        return contents;
105    }
106
107    /**
108     * Write this to another Parcel. Note that this discards the internal Parcel
109     * and should not be used anymore. This is so we can pass this to a Binder
110     * where we won't have a chance to call recycle on this.
111     */
112    @Override
113    public void writeToParcel(Parcel dest, int flags) {
114        final int N = mList.size();
115        final int callFlags = flags;
116        dest.writeInt(N);
117        if (DEBUG) Log.d(TAG, "Writing " + N + " items");
118        if (N > 0) {
119            dest.writeParcelableCreator(mList.get(0));
120            int i = 0;
121            while (i < N && dest.dataSize() < MAX_FIRST_IPC_SIZE) {
122                dest.writeInt(1);
123                mList.get(i).writeToParcel(dest, callFlags);
124                if (DEBUG) Log.d(TAG, "Wrote inline #" + i + ": " + mList.get(i));
125                i++;
126            }
127            if (i < N) {
128                dest.writeInt(0);
129                Binder retriever = new Binder() {
130                    @Override
131                    protected boolean onTransact(int code, Parcel data, Parcel reply, int flags)
132                            throws RemoteException {
133                        if (code != FIRST_CALL_TRANSACTION) {
134                            return super.onTransact(code, data, reply, flags);
135                        }
136                        int i = data.readInt();
137                        if (DEBUG) Log.d(TAG, "Writing more @" + i + " of " + N);
138                        while (i < N && reply.dataSize() < MAX_IPC_SIZE) {
139                            reply.writeInt(1);
140                            mList.get(i).writeToParcel(reply, callFlags);
141                            if (DEBUG) Log.d(TAG, "Wrote extra #" + i + ": " + mList.get(i));
142                            i++;
143                        }
144                        if (i < N) {
145                            if (DEBUG) Log.d(TAG, "Breaking @" + i + " of " + N);
146                            reply.writeInt(0);
147                        }
148                        return true;
149                    }
150                };
151                if (DEBUG) Log.d(TAG, "Breaking @" + i + " of " + N + ": retriever=" + retriever);
152                dest.writeStrongBinder(retriever);
153            }
154        }
155    }
156
157    @SuppressWarnings("unchecked")
158    public static final Parcelable.ClassLoaderCreator<ParceledListSlice> CREATOR =
159            new Parcelable.ClassLoaderCreator<ParceledListSlice>() {
160        public ParceledListSlice createFromParcel(Parcel in) {
161            return new ParceledListSlice(in, null);
162        }
163
164        @Override
165        public ParceledListSlice createFromParcel(Parcel in, ClassLoader loader) {
166            return new ParceledListSlice(in, loader);
167        }
168
169        public ParceledListSlice[] newArray(int size) {
170            return new ParceledListSlice[size];
171        }
172    };
173}
174