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