1/*
2 * Copyright (C) 2014 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.os;
18
19import android.annotation.Nullable;
20import android.util.ArrayMap;
21import android.util.proto.ProtoOutputStream;
22
23import com.android.internal.util.XmlUtils;
24
25import org.xmlpull.v1.XmlPullParser;
26import org.xmlpull.v1.XmlPullParserException;
27import org.xmlpull.v1.XmlSerializer;
28
29import java.io.IOException;
30import java.util.ArrayList;
31
32/**
33 * A mapping from String keys to values of various types. The set of types
34 * supported by this class is purposefully restricted to simple objects that can
35 * safely be persisted to and restored from disk.
36 *
37 * @see Bundle
38 */
39public final class PersistableBundle extends BaseBundle implements Cloneable, Parcelable,
40        XmlUtils.WriteMapCallback {
41    private static final String TAG_PERSISTABLEMAP = "pbundle_as_map";
42    public static final PersistableBundle EMPTY;
43
44    static {
45        EMPTY = new PersistableBundle();
46        EMPTY.mMap = ArrayMap.EMPTY;
47    }
48
49    /** @hide */
50    public static boolean isValidType(Object value) {
51        return (value instanceof Integer) || (value instanceof Long) ||
52                (value instanceof Double) || (value instanceof String) ||
53                (value instanceof int[]) || (value instanceof long[]) ||
54                (value instanceof double[]) || (value instanceof String[]) ||
55                (value instanceof PersistableBundle) || (value == null) ||
56                (value instanceof Boolean) || (value instanceof boolean[]);
57    }
58
59    /**
60     * Constructs a new, empty PersistableBundle.
61     */
62    public PersistableBundle() {
63        super();
64        mFlags = FLAG_DEFUSABLE;
65    }
66
67    /**
68     * Constructs a new, empty PersistableBundle sized to hold the given number of
69     * elements. The PersistableBundle will grow as needed.
70     *
71     * @param capacity the initial capacity of the PersistableBundle
72     */
73    public PersistableBundle(int capacity) {
74        super(capacity);
75        mFlags = FLAG_DEFUSABLE;
76    }
77
78    /**
79     * Constructs a PersistableBundle containing a copy of the mappings from the given
80     * PersistableBundle.  Does only a shallow copy of the original PersistableBundle -- see
81     * {@link #deepCopy()} if that is not what you want.
82     *
83     * @param b a PersistableBundle to be copied.
84     *
85     * @see #deepCopy()
86     */
87    public PersistableBundle(PersistableBundle b) {
88        super(b);
89        mFlags = b.mFlags;
90    }
91
92
93    /**
94     * Constructs a PersistableBundle from a Bundle.  Does only a shallow copy of the Bundle.
95     *
96     * @param b a Bundle to be copied.
97     *
98     * @throws IllegalArgumentException if any element of {@code b} cannot be persisted.
99     *
100     * @hide
101     */
102    public PersistableBundle(Bundle b) {
103        this(b.getMap());
104    }
105
106    /**
107     * Constructs a PersistableBundle containing the mappings passed in.
108     *
109     * @param map a Map containing only those items that can be persisted.
110     * @throws IllegalArgumentException if any element of #map cannot be persisted.
111     */
112    private PersistableBundle(ArrayMap<String, Object> map) {
113        super();
114        mFlags = FLAG_DEFUSABLE;
115
116        // First stuff everything in.
117        putAll(map);
118
119        // Now verify each item throwing an exception if there is a violation.
120        final int N = mMap.size();
121        for (int i=0; i<N; i++) {
122            Object value = mMap.valueAt(i);
123            if (value instanceof ArrayMap) {
124                // Fix up any Maps by replacing them with PersistableBundles.
125                mMap.setValueAt(i, new PersistableBundle((ArrayMap<String, Object>) value));
126            } else if (value instanceof Bundle) {
127                mMap.setValueAt(i, new PersistableBundle(((Bundle) value)));
128            } else if (!isValidType(value)) {
129                throw new IllegalArgumentException("Bad value in PersistableBundle key="
130                        + mMap.keyAt(i) + " value=" + value);
131            }
132        }
133    }
134
135    /* package */ PersistableBundle(Parcel parcelledData, int length) {
136        super(parcelledData, length);
137        mFlags = FLAG_DEFUSABLE;
138    }
139
140    /**
141     * Constructs a PersistableBundle without initializing it.
142     */
143    PersistableBundle(boolean doInit) {
144        super(doInit);
145    }
146
147    /**
148     * Make a PersistableBundle for a single key/value pair.
149     *
150     * @hide
151     */
152    public static PersistableBundle forPair(String key, String value) {
153        PersistableBundle b = new PersistableBundle(1);
154        b.putString(key, value);
155        return b;
156    }
157
158    /**
159     * Clones the current PersistableBundle. The internal map is cloned, but the keys and
160     * values to which it refers are copied by reference.
161     */
162    @Override
163    public Object clone() {
164        return new PersistableBundle(this);
165    }
166
167    /**
168     * Make a deep copy of the given bundle.  Traverses into inner containers and copies
169     * them as well, so they are not shared across bundles.  Will traverse in to
170     * {@link Bundle}, {@link PersistableBundle}, {@link ArrayList}, and all types of
171     * primitive arrays.  Other types of objects (such as Parcelable or Serializable)
172     * are referenced as-is and not copied in any way.
173     */
174    public PersistableBundle deepCopy() {
175        PersistableBundle b = new PersistableBundle(false);
176        b.copyInternal(this, true);
177        return b;
178    }
179
180    /**
181     * Inserts a PersistableBundle value into the mapping of this Bundle, replacing
182     * any existing value for the given key.  Either key or value may be null.
183     *
184     * @param key a String, or null
185     * @param value a Bundle object, or null
186     */
187    public void putPersistableBundle(@Nullable String key, @Nullable PersistableBundle value) {
188        unparcel();
189        mMap.put(key, value);
190    }
191
192    /**
193     * Returns the value associated with the given key, or null if
194     * no mapping of the desired type exists for the given key or a null
195     * value is explicitly associated with the key.
196     *
197     * @param key a String, or null
198     * @return a Bundle value, or null
199     */
200    @Nullable
201    public PersistableBundle getPersistableBundle(@Nullable String key) {
202        unparcel();
203        Object o = mMap.get(key);
204        if (o == null) {
205            return null;
206        }
207        try {
208            return (PersistableBundle) o;
209        } catch (ClassCastException e) {
210            typeWarning(key, o, "Bundle", e);
211            return null;
212        }
213    }
214
215    public static final Parcelable.Creator<PersistableBundle> CREATOR =
216            new Parcelable.Creator<PersistableBundle>() {
217                @Override
218                public PersistableBundle createFromParcel(Parcel in) {
219                    return in.readPersistableBundle();
220                }
221
222                @Override
223                public PersistableBundle[] newArray(int size) {
224                    return new PersistableBundle[size];
225                }
226            };
227
228    /** @hide */
229    @Override
230    public void writeUnknownObject(Object v, String name, XmlSerializer out)
231            throws XmlPullParserException, IOException {
232        if (v instanceof PersistableBundle) {
233            out.startTag(null, TAG_PERSISTABLEMAP);
234            out.attribute(null, "name", name);
235            ((PersistableBundle) v).saveToXml(out);
236            out.endTag(null, TAG_PERSISTABLEMAP);
237        } else {
238            throw new XmlPullParserException("Unknown Object o=" + v);
239        }
240    }
241
242    /** @hide */
243    public void saveToXml(XmlSerializer out) throws IOException, XmlPullParserException {
244        unparcel();
245        XmlUtils.writeMapXml(mMap, out, this);
246    }
247
248    /** @hide */
249    static class MyReadMapCallback implements  XmlUtils.ReadMapCallback {
250        @Override
251        public Object readThisUnknownObjectXml(XmlPullParser in, String tag)
252                throws XmlPullParserException, IOException {
253            if (TAG_PERSISTABLEMAP.equals(tag)) {
254                return restoreFromXml(in);
255            }
256            throw new XmlPullParserException("Unknown tag=" + tag);
257        }
258    }
259
260    /**
261     * Report the nature of this Parcelable's contents
262     */
263    @Override
264    public int describeContents() {
265        return 0;
266    }
267
268    /**
269     * Writes the PersistableBundle contents to a Parcel, typically in order for
270     * it to be passed through an IBinder connection.
271     * @param parcel The parcel to copy this bundle to.
272     */
273    @Override
274    public void writeToParcel(Parcel parcel, int flags) {
275        final boolean oldAllowFds = parcel.pushAllowFds(false);
276        try {
277            writeToParcelInner(parcel, flags);
278        } finally {
279            parcel.restoreAllowFds(oldAllowFds);
280        }
281    }
282
283    /** @hide */
284    public static PersistableBundle restoreFromXml(XmlPullParser in) throws IOException,
285            XmlPullParserException {
286        final int outerDepth = in.getDepth();
287        final String startTag = in.getName();
288        final String[] tagName = new String[1];
289        int event;
290        while (((event = in.next()) != XmlPullParser.END_DOCUMENT) &&
291                (event != XmlPullParser.END_TAG || in.getDepth() < outerDepth)) {
292            if (event == XmlPullParser.START_TAG) {
293                return new PersistableBundle((ArrayMap<String, Object>)
294                        XmlUtils.readThisArrayMapXml(in, startTag, tagName,
295                        new MyReadMapCallback()));
296            }
297        }
298        return EMPTY;
299    }
300
301    @Override
302    synchronized public String toString() {
303        if (mParcelledData != null) {
304            if (isEmptyParcel()) {
305                return "PersistableBundle[EMPTY_PARCEL]";
306            } else {
307                return "PersistableBundle[mParcelledData.dataSize=" +
308                        mParcelledData.dataSize() + "]";
309            }
310        }
311        return "PersistableBundle[" + mMap.toString() + "]";
312    }
313
314    /** @hide */
315    synchronized public String toShortString() {
316        if (mParcelledData != null) {
317            if (isEmptyParcel()) {
318                return "EMPTY_PARCEL";
319            } else {
320                return "mParcelledData.dataSize=" + mParcelledData.dataSize();
321            }
322        }
323        return mMap.toString();
324    }
325
326    /** @hide */
327    public void writeToProto(ProtoOutputStream proto, long fieldId) {
328        final long token = proto.start(fieldId);
329
330        if (mParcelledData != null) {
331            if (isEmptyParcel()) {
332                proto.write(PersistableBundleProto.PARCELLED_DATA_SIZE, 0);
333            } else {
334                proto.write(PersistableBundleProto.PARCELLED_DATA_SIZE, mParcelledData.dataSize());
335            }
336        } else {
337            proto.write(PersistableBundleProto.MAP_DATA, mMap.toString());
338        }
339
340        proto.end(token);
341    }
342}
343