PersistableBundle.java revision 0a8e160eb56f3b8f504b37349a79ec4edb7e5039
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                throw new IllegalArgumentException("Bad value in PersistableBundle key=" + key +
101                        " value=" + value);
102            }
103        }
104    }
105
106    /* package */ PersistableBundle(Parcel parcelledData, int length) {
107        super(parcelledData, length);
108    }
109
110    /**
111     * Make a PersistableBundle for a single key/value pair.
112     *
113     * @hide
114     */
115    public static PersistableBundle forPair(String key, String value) {
116        PersistableBundle b = new PersistableBundle(1);
117        b.putString(key, value);
118        return b;
119    }
120
121    /**
122     * Clones the current PersistableBundle. The internal map is cloned, but the keys and
123     * values to which it refers are copied by reference.
124     */
125    @Override
126    public Object clone() {
127        return new PersistableBundle(this);
128    }
129
130    /**
131     * Inserts a PersistableBundle value into the mapping of this Bundle, replacing
132     * any existing value for the given key.  Either key or value may be null.
133     *
134     * @param key a String, or null
135     * @param value a Bundle object, or null
136     */
137    public void putPersistableBundle(String key, PersistableBundle value) {
138        unparcel();
139        mMap.put(key, value);
140    }
141
142    /**
143     * Returns the value associated with the given key, or null if
144     * no mapping of the desired type exists for the given key or a null
145     * value is explicitly associated with the key.
146     *
147     * @param key a String, or null
148     * @return a Bundle value, or null
149     */
150    public PersistableBundle getPersistableBundle(String key) {
151        unparcel();
152        Object o = mMap.get(key);
153        if (o == null) {
154            return null;
155        }
156        try {
157            return (PersistableBundle) o;
158        } catch (ClassCastException e) {
159            typeWarning(key, o, "Bundle", e);
160            return null;
161        }
162    }
163
164    public static final Parcelable.Creator<PersistableBundle> CREATOR =
165            new Parcelable.Creator<PersistableBundle>() {
166                @Override
167                public PersistableBundle createFromParcel(Parcel in) {
168                    return in.readPersistableBundle();
169                }
170
171                @Override
172                public PersistableBundle[] newArray(int size) {
173                    return new PersistableBundle[size];
174                }
175            };
176
177    /** @hide */
178    @Override
179    public void writeUnknownObject(Object v, String name, XmlSerializer out)
180            throws XmlPullParserException, IOException {
181        if (v instanceof PersistableBundle) {
182            out.startTag(null, TAG_PERSISTABLEMAP);
183            out.attribute(null, "name", name);
184            ((PersistableBundle) v).saveToXml(out);
185            out.endTag(null, TAG_PERSISTABLEMAP);
186        } else {
187            throw new XmlPullParserException("Unknown Object o=" + v);
188        }
189    }
190
191    /** @hide */
192    public void saveToXml(XmlSerializer out) throws IOException, XmlPullParserException {
193        unparcel();
194        XmlUtils.writeMapXml(mMap, out, this);
195    }
196
197    /** @hide */
198    static class MyReadMapCallback implements  XmlUtils.ReadMapCallback {
199        @Override
200        public Object readThisUnknownObjectXml(XmlPullParser in, String tag)
201                throws XmlPullParserException, IOException {
202            if (TAG_PERSISTABLEMAP.equals(tag)) {
203                return restoreFromXml(in);
204            }
205            throw new XmlPullParserException("Unknown tag=" + tag);
206        }
207    }
208
209    /**
210     * Report the nature of this Parcelable's contents
211     */
212    @Override
213    public int describeContents() {
214        return 0;
215    }
216
217    /**
218     * Writes the PersistableBundle contents to a Parcel, typically in order for
219     * it to be passed through an IBinder connection.
220     * @param parcel The parcel to copy this bundle to.
221     */
222    @Override
223    public void writeToParcel(Parcel parcel, int flags) {
224        final boolean oldAllowFds = parcel.pushAllowFds(false);
225        try {
226            writeToParcelInner(parcel, flags);
227        } finally {
228            parcel.restoreAllowFds(oldAllowFds);
229        }
230    }
231
232    /** @hide */
233    public static PersistableBundle restoreFromXml(XmlPullParser in) throws IOException,
234            XmlPullParserException {
235        final int outerDepth = in.getDepth();
236        final String startTag = in.getName();
237        final String[] tagName = new String[1];
238        int event;
239        while (((event = in.next()) != XmlPullParser.END_DOCUMENT) &&
240                (event != XmlPullParser.END_TAG || in.getDepth() < outerDepth)) {
241            if (event == XmlPullParser.START_TAG) {
242                return new PersistableBundle((Map<String, Object>)
243                        XmlUtils.readThisMapXml(in, startTag, tagName, new MyReadMapCallback()));
244            }
245        }
246        return EMPTY;
247    }
248
249    @Override
250    synchronized public String toString() {
251        if (mParcelledData != null) {
252            if (mParcelledData == EMPTY_PARCEL) {
253                return "PersistableBundle[EMPTY_PARCEL]";
254            } else {
255                return "PersistableBundle[mParcelledData.dataSize=" +
256                        mParcelledData.dataSize() + "]";
257            }
258        }
259        return "PersistableBundle[" + mMap.toString() + "]";
260    }
261
262}
263