Home | History | Annotate | Download | only in os
      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 */
     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      */
     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      */
     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      */
     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      */
    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      */
    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 
    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      */
    143     PersistableBundle(boolean doInit) {
    144         super(doInit);
    145     }
    146 
    147     /**
    148      * Make a PersistableBundle for a single key/value pair.
    149      *
    150      * @hide
    151      */
    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
    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      */
    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      */
    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
    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
    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 */
    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
    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
    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
    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 */
    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
    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 */
    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 */
    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