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