PersistableBundle.java revision d136e51a99df5275eaafdde407e89e78c02b829b
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;
21
22import com.android.internal.util.XmlUtils;
23
24import org.xmlpull.v1.XmlPullParser;
25import org.xmlpull.v1.XmlPullParserException;
26import org.xmlpull.v1.XmlSerializer;
27
28import java.io.IOException;
29
30/**
31 * A mapping from String keys to values of various types. The set of types
32 * supported by this class is purposefully restricted to simple objects that can
33 * safely be persisted to and restored from disk.
34 *
35 * @see Bundle
36 */
37public final class PersistableBundle extends BaseBundle implements Cloneable, Parcelable,
38        XmlUtils.WriteMapCallback {
39    private static final String TAG_PERSISTABLEMAP = "pbundle_as_map";
40    public static final PersistableBundle EMPTY;
41    static final Parcel EMPTY_PARCEL;
42
43    static {
44        EMPTY = new PersistableBundle();
45        EMPTY.mMap = ArrayMap.EMPTY;
46        EMPTY_PARCEL = BaseBundle.EMPTY_PARCEL;
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.
81     *
82     * @param b a PersistableBundle to be copied.
83     */
84    public PersistableBundle(PersistableBundle b) {
85        super(b);
86        mFlags = b.mFlags;
87    }
88
89
90    /**
91     * Constructs a PersistableBundle from a Bundle.
92     *
93     * @param b a Bundle to be copied.
94     *
95     * @throws IllegalArgumentException if any element of {@code b} cannot be persisted.
96     *
97     * @hide
98     */
99    public PersistableBundle(Bundle b) {
100        this(b.getMap());
101    }
102
103    /**
104     * Constructs a PersistableBundle containing the mappings passed in.
105     *
106     * @param map a Map containing only those items that can be persisted.
107     * @throws IllegalArgumentException if any element of #map cannot be persisted.
108     */
109    private PersistableBundle(ArrayMap<String, Object> map) {
110        super();
111        mFlags = FLAG_DEFUSABLE;
112
113        // First stuff everything in.
114        putAll(map);
115
116        // Now verify each item throwing an exception if there is a violation.
117        final int N = mMap.size();
118        for (int i=0; i<N; i++) {
119            Object value = mMap.valueAt(i);
120            if (value instanceof ArrayMap) {
121                // Fix up any Maps by replacing them with PersistableBundles.
122                mMap.setValueAt(i, new PersistableBundle((ArrayMap<String, Object>) value));
123            } else if (value instanceof Bundle) {
124                mMap.setValueAt(i, new PersistableBundle(((Bundle) value)));
125            } else if (!isValidType(value)) {
126                throw new IllegalArgumentException("Bad value in PersistableBundle key="
127                        + mMap.keyAt(i) + " value=" + value);
128            }
129        }
130    }
131
132    /* package */ PersistableBundle(Parcel parcelledData, int length) {
133        super(parcelledData, length);
134        mFlags = FLAG_DEFUSABLE;
135    }
136
137    /**
138     * Make a PersistableBundle for a single key/value pair.
139     *
140     * @hide
141     */
142    public static PersistableBundle forPair(String key, String value) {
143        PersistableBundle b = new PersistableBundle(1);
144        b.putString(key, value);
145        return b;
146    }
147
148    /**
149     * Clones the current PersistableBundle. The internal map is cloned, but the keys and
150     * values to which it refers are copied by reference.
151     */
152    @Override
153    public Object clone() {
154        return new PersistableBundle(this);
155    }
156
157    /**
158     * Inserts a PersistableBundle value into the mapping of this Bundle, replacing
159     * any existing value for the given key.  Either key or value may be null.
160     *
161     * @param key a String, or null
162     * @param value a Bundle object, or null
163     */
164    public void putPersistableBundle(@Nullable String key, @Nullable PersistableBundle value) {
165        unparcel();
166        mMap.put(key, value);
167    }
168
169    /**
170     * Returns the value associated with the given key, or null if
171     * no mapping of the desired type exists for the given key or a null
172     * value is explicitly associated with the key.
173     *
174     * @param key a String, or null
175     * @return a Bundle value, or null
176     */
177    @Nullable
178    public PersistableBundle getPersistableBundle(@Nullable String key) {
179        unparcel();
180        Object o = mMap.get(key);
181        if (o == null) {
182            return null;
183        }
184        try {
185            return (PersistableBundle) o;
186        } catch (ClassCastException e) {
187            typeWarning(key, o, "Bundle", e);
188            return null;
189        }
190    }
191
192    public static final Parcelable.Creator<PersistableBundle> CREATOR =
193            new Parcelable.Creator<PersistableBundle>() {
194                @Override
195                public PersistableBundle createFromParcel(Parcel in) {
196                    return in.readPersistableBundle();
197                }
198
199                @Override
200                public PersistableBundle[] newArray(int size) {
201                    return new PersistableBundle[size];
202                }
203            };
204
205    /** @hide */
206    @Override
207    public void writeUnknownObject(Object v, String name, XmlSerializer out)
208            throws XmlPullParserException, IOException {
209        if (v instanceof PersistableBundle) {
210            out.startTag(null, TAG_PERSISTABLEMAP);
211            out.attribute(null, "name", name);
212            ((PersistableBundle) v).saveToXml(out);
213            out.endTag(null, TAG_PERSISTABLEMAP);
214        } else {
215            throw new XmlPullParserException("Unknown Object o=" + v);
216        }
217    }
218
219    /** @hide */
220    public void saveToXml(XmlSerializer out) throws IOException, XmlPullParserException {
221        unparcel();
222        XmlUtils.writeMapXml(mMap, out, this);
223    }
224
225    /** @hide */
226    static class MyReadMapCallback implements  XmlUtils.ReadMapCallback {
227        @Override
228        public Object readThisUnknownObjectXml(XmlPullParser in, String tag)
229                throws XmlPullParserException, IOException {
230            if (TAG_PERSISTABLEMAP.equals(tag)) {
231                return restoreFromXml(in);
232            }
233            throw new XmlPullParserException("Unknown tag=" + tag);
234        }
235    }
236
237    /**
238     * Report the nature of this Parcelable's contents
239     */
240    @Override
241    public int describeContents() {
242        return 0;
243    }
244
245    /**
246     * Writes the PersistableBundle contents to a Parcel, typically in order for
247     * it to be passed through an IBinder connection.
248     * @param parcel The parcel to copy this bundle to.
249     */
250    @Override
251    public void writeToParcel(Parcel parcel, int flags) {
252        final boolean oldAllowFds = parcel.pushAllowFds(false);
253        try {
254            writeToParcelInner(parcel, flags);
255        } finally {
256            parcel.restoreAllowFds(oldAllowFds);
257        }
258    }
259
260    /** @hide */
261    public static PersistableBundle restoreFromXml(XmlPullParser in) throws IOException,
262            XmlPullParserException {
263        final int outerDepth = in.getDepth();
264        final String startTag = in.getName();
265        final String[] tagName = new String[1];
266        int event;
267        while (((event = in.next()) != XmlPullParser.END_DOCUMENT) &&
268                (event != XmlPullParser.END_TAG || in.getDepth() < outerDepth)) {
269            if (event == XmlPullParser.START_TAG) {
270                return new PersistableBundle((ArrayMap<String, Object>)
271                        XmlUtils.readThisArrayMapXml(in, startTag, tagName,
272                        new MyReadMapCallback()));
273            }
274        }
275        return EMPTY;
276    }
277
278    @Override
279    synchronized public String toString() {
280        if (mParcelledData != null) {
281            if (mParcelledData == EMPTY_PARCEL) {
282                return "PersistableBundle[EMPTY_PARCEL]";
283            } else {
284                return "PersistableBundle[mParcelledData.dataSize=" +
285                        mParcelledData.dataSize() + "]";
286            }
287        }
288        return "PersistableBundle[" + mMap.toString() + "]";
289    }
290}
291