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