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