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