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