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