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