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