1abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee/*
2abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee * Copyright (C) 2011 The Android Open Source Project
3abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee *
4abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee * Licensed under the Apache License, Version 2.0 (the "License");
5abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee * you may not use this file except in compliance with the License.
6abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee * You may obtain a copy of the License at
7abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee *
8abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee *      http://www.apache.org/licenses/LICENSE-2.0
9abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee *
10abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee * Unless required by applicable law or agreed to in writing, software
11abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee * distributed under the License is distributed on an "AS IS" BASIS,
12abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee * See the License for the specific language governing permissions and
14abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee * limitations under the License.
15abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee */
16abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee
17abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Leepackage android.content.pm;
18abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee
19abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Leeimport android.os.Binder;
20abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Leeimport android.os.IBinder;
21abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Leeimport android.os.Parcel;
22abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Leeimport android.os.Parcelable;
23abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Leeimport android.os.RemoteException;
24abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Leeimport android.util.Log;
25abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee
26abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Leeimport java.util.ArrayList;
27abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Leeimport java.util.List;
28abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee
29abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee/**
30abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee * Transfer a large list of Parcelable objects across an IPC.  Splits into
31abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee * multiple transactions if needed.
32abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee *
33abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee * Caveat: for efficiency and security, all elements must be the same concrete type.
34abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee * In order to avoid writing the class name of each object, we must ensure that
35abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee * each object is the same type, or else unparceling then reparceling the data may yield
36abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee * a different result if the class name encoded in the Parcelable is a Base type.
37abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee * See b/17671747.
38abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee *
39abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee * @hide
40abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee */
41abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Leeabstract class BaseParceledListSlice<T> implements Parcelable {
42abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee    private static String TAG = "ParceledListSlice";
43abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee    private static boolean DEBUG = false;
44abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee
45abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee    /*
46abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee     * TODO get this number from somewhere else. For now set it to a quarter of
47abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee     * the 1MB limit.
48abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee     */
49abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee    private static final int MAX_IPC_SIZE = IBinder.MAX_IPC_SIZE;
50abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee
51abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee    private final List<T> mList;
52abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee
533f7c9f2d0164f2b5826c194e9309791637f35c2cDianne Hackborn    private int mInlineCountLimit = Integer.MAX_VALUE;
543f7c9f2d0164f2b5826c194e9309791637f35c2cDianne Hackborn
55abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee    public BaseParceledListSlice(List<T> list) {
56abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee        mList = list;
57abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee    }
58abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee
59abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee    @SuppressWarnings("unchecked")
60abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee    BaseParceledListSlice(Parcel p, ClassLoader loader) {
61abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee        final int N = p.readInt();
62abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee        mList = new ArrayList<T>(N);
63abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee        if (DEBUG) Log.d(TAG, "Retrieving " + N + " items");
64abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee        if (N <= 0) {
65abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee            return;
66abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee        }
67abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee
68abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee        Parcelable.Creator<?> creator = readParcelableCreator(p, loader);
69abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee        Class<?> listElementClass = null;
70abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee
71abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee        int i = 0;
72abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee        while (i < N) {
73abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee            if (p.readInt() == 0) {
74abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee                break;
75abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee            }
76abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee
77abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee            final T parcelable = readCreator(creator, p, loader);
78abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee            if (listElementClass == null) {
79abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee                listElementClass = parcelable.getClass();
80abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee            } else {
81abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee                verifySameType(listElementClass, parcelable.getClass());
82abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee            }
83abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee
84abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee            mList.add(parcelable);
85abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee
86abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee            if (DEBUG) Log.d(TAG, "Read inline #" + i + ": " + mList.get(mList.size()-1));
87abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee            i++;
88abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee        }
89abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee        if (i >= N) {
90abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee            return;
91abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee        }
92abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee        final IBinder retriever = p.readStrongBinder();
93abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee        while (i < N) {
94abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee            if (DEBUG) Log.d(TAG, "Reading more @" + i + " of " + N + ": retriever=" + retriever);
95abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee            Parcel data = Parcel.obtain();
96abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee            Parcel reply = Parcel.obtain();
97abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee            data.writeInt(i);
98abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee            try {
99abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee                retriever.transact(IBinder.FIRST_CALL_TRANSACTION, data, reply, 0);
100abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee            } catch (RemoteException e) {
101abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee                Log.w(TAG, "Failure retrieving array; only received " + i + " of " + N, e);
102abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee                return;
103abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee            }
104abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee            while (i < N && reply.readInt() != 0) {
105abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee                final T parcelable = reply.readCreator(creator, loader);
106abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee                verifySameType(listElementClass, parcelable.getClass());
107abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee
108abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee                mList.add(parcelable);
109abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee
110abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee                if (DEBUG) Log.d(TAG, "Read extra #" + i + ": " + mList.get(mList.size()-1));
111abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee                i++;
112abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee            }
113abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee            reply.recycle();
114abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee            data.recycle();
115abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee        }
116abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee    }
117abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee
118abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee    private T readCreator(Parcelable.Creator<?> creator, Parcel p, ClassLoader loader) {
119abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee        if (creator instanceof Parcelable.ClassLoaderCreator<?>) {
120abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee            Parcelable.ClassLoaderCreator<?> classLoaderCreator =
121abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee                    (Parcelable.ClassLoaderCreator<?>) creator;
122abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee            return (T) classLoaderCreator.createFromParcel(p, loader);
123abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee        }
124abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee        return (T) creator.createFromParcel(p);
125abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee    }
126abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee
127abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee    private static void verifySameType(final Class<?> expected, final Class<?> actual) {
128abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee        if (!actual.equals(expected)) {
129abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee            throw new IllegalArgumentException("Can't unparcel type "
130abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee                    + actual.getName() + " in list of type "
131abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee                    + expected.getName());
132abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee        }
133abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee    }
134abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee
135abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee    public List<T> getList() {
136abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee        return mList;
137abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee    }
138abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee
139abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee    /**
1403f7c9f2d0164f2b5826c194e9309791637f35c2cDianne Hackborn     * Set a limit on the maximum number of entries in the array that will be included
1413f7c9f2d0164f2b5826c194e9309791637f35c2cDianne Hackborn     * inline in the initial parcelling of this object.
1423f7c9f2d0164f2b5826c194e9309791637f35c2cDianne Hackborn     */
1433f7c9f2d0164f2b5826c194e9309791637f35c2cDianne Hackborn    public void setInlineCountLimit(int maxCount) {
1443f7c9f2d0164f2b5826c194e9309791637f35c2cDianne Hackborn        mInlineCountLimit = maxCount;
1453f7c9f2d0164f2b5826c194e9309791637f35c2cDianne Hackborn    }
1463f7c9f2d0164f2b5826c194e9309791637f35c2cDianne Hackborn
1473f7c9f2d0164f2b5826c194e9309791637f35c2cDianne Hackborn    /**
148abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee     * Write this to another Parcel. Note that this discards the internal Parcel
149abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee     * and should not be used anymore. This is so we can pass this to a Binder
150abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee     * where we won't have a chance to call recycle on this.
151abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee     */
152abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee    @Override
153abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee    public void writeToParcel(Parcel dest, int flags) {
154abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee        final int N = mList.size();
155abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee        final int callFlags = flags;
156abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee        dest.writeInt(N);
157abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee        if (DEBUG) Log.d(TAG, "Writing " + N + " items");
158abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee        if (N > 0) {
159abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee            final Class<?> listElementClass = mList.get(0).getClass();
160abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee            writeParcelableCreator(mList.get(0), dest);
161abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee            int i = 0;
1623f7c9f2d0164f2b5826c194e9309791637f35c2cDianne Hackborn            while (i < N && i < mInlineCountLimit && dest.dataSize() < MAX_IPC_SIZE) {
163abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee                dest.writeInt(1);
164abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee
165abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee                final T parcelable = mList.get(i);
166abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee                verifySameType(listElementClass, parcelable.getClass());
167abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee                writeElement(parcelable, dest, callFlags);
168abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee
169abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee                if (DEBUG) Log.d(TAG, "Wrote inline #" + i + ": " + mList.get(i));
170abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee                i++;
171abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee            }
172abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee            if (i < N) {
173abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee                dest.writeInt(0);
174abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee                Binder retriever = new Binder() {
175abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee                    @Override
176abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee                    protected boolean onTransact(int code, Parcel data, Parcel reply, int flags)
177abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee                            throws RemoteException {
178abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee                        if (code != FIRST_CALL_TRANSACTION) {
179abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee                            return super.onTransact(code, data, reply, flags);
180abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee                        }
181abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee                        int i = data.readInt();
182abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee                        if (DEBUG) Log.d(TAG, "Writing more @" + i + " of " + N);
183abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee                        while (i < N && reply.dataSize() < MAX_IPC_SIZE) {
184abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee                            reply.writeInt(1);
185abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee
186abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee                            final T parcelable = mList.get(i);
187abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee                            verifySameType(listElementClass, parcelable.getClass());
188abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee                            writeElement(parcelable, reply, callFlags);
189abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee
190abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee                            if (DEBUG) Log.d(TAG, "Wrote extra #" + i + ": " + mList.get(i));
191abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee                            i++;
192abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee                        }
193abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee                        if (i < N) {
194abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee                            if (DEBUG) Log.d(TAG, "Breaking @" + i + " of " + N);
195abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee                            reply.writeInt(0);
196abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee                        }
197abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee                        return true;
198abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee                    }
199abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee                };
200abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee                if (DEBUG) Log.d(TAG, "Breaking @" + i + " of " + N + ": retriever=" + retriever);
201abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee                dest.writeStrongBinder(retriever);
202abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee            }
203abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee        }
204abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee    }
205abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee
206abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee    protected abstract void writeElement(T parcelable, Parcel reply, int callFlags);
207abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee
208abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee    protected abstract void writeParcelableCreator(T parcelable, Parcel dest);
209abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee
210abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee    protected abstract Parcelable.Creator<?> readParcelableCreator(Parcel from, ClassLoader loader);
211abaa0695c5361b36a7a2cdbe87c77bf60be20af7Robin Lee}
212