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