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