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