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 @android.annotation.NonNull 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