blob: 6f1bf71f187b7c9d2f29802ca1a2d234a4e3945d [file] [log] [blame]
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.os;
import android.annotation.Nullable;
import android.util.ArrayMap;
import android.util.proto.ProtoOutputStream;
import com.android.internal.util.XmlUtils;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
import java.io.IOException;
import java.util.ArrayList;
/**
* A mapping from String keys to values of various types. The set of types
* supported by this class is purposefully restricted to simple objects that can
* safely be persisted to and restored from disk.
*
* @see Bundle
*/
public final class PersistableBundle extends BaseBundle implements Cloneable, Parcelable,
XmlUtils.WriteMapCallback {
private static final String TAG_PERSISTABLEMAP = "pbundle_as_map";
public static final PersistableBundle EMPTY;
static {
EMPTY = new PersistableBundle();
EMPTY.mMap = ArrayMap.EMPTY;
}
/** @hide */
public static boolean isValidType(Object value) {
return (value instanceof Integer) || (value instanceof Long) ||
(value instanceof Double) || (value instanceof String) ||
(value instanceof int[]) || (value instanceof long[]) ||
(value instanceof double[]) || (value instanceof String[]) ||
(value instanceof PersistableBundle) || (value == null) ||
(value instanceof Boolean) || (value instanceof boolean[]);
}
/**
* Constructs a new, empty PersistableBundle.
*/
public PersistableBundle() {
super();
mFlags = FLAG_DEFUSABLE;
}
/**
* Constructs a new, empty PersistableBundle sized to hold the given number of
* elements. The PersistableBundle will grow as needed.
*
* @param capacity the initial capacity of the PersistableBundle
*/
public PersistableBundle(int capacity) {
super(capacity);
mFlags = FLAG_DEFUSABLE;
}
/**
* Constructs a PersistableBundle containing a copy of the mappings from the given
* PersistableBundle. Does only a shallow copy of the original PersistableBundle -- see
* {@link #deepCopy()} if that is not what you want.
*
* @param b a PersistableBundle to be copied.
*
* @see #deepCopy()
*/
public PersistableBundle(PersistableBundle b) {
super(b);
mFlags = b.mFlags;
}
/**
* Constructs a PersistableBundle from a Bundle. Does only a shallow copy of the Bundle.
*
* @param b a Bundle to be copied.
*
* @throws IllegalArgumentException if any element of {@code b} cannot be persisted.
*
* @hide
*/
public PersistableBundle(Bundle b) {
this(b.getMap());
}
/**
* Constructs a PersistableBundle containing the mappings passed in.
*
* @param map a Map containing only those items that can be persisted.
* @throws IllegalArgumentException if any element of #map cannot be persisted.
*/
private PersistableBundle(ArrayMap<String, Object> map) {
super();
mFlags = FLAG_DEFUSABLE;
// First stuff everything in.
putAll(map);
// Now verify each item throwing an exception if there is a violation.
final int N = mMap.size();
for (int i=0; i<N; i++) {
Object value = mMap.valueAt(i);
if (value instanceof ArrayMap) {
// Fix up any Maps by replacing them with PersistableBundles.
mMap.setValueAt(i, new PersistableBundle((ArrayMap<String, Object>) value));
} else if (value instanceof Bundle) {
mMap.setValueAt(i, new PersistableBundle(((Bundle) value)));
} else if (!isValidType(value)) {
throw new IllegalArgumentException("Bad value in PersistableBundle key="
+ mMap.keyAt(i) + " value=" + value);
}
}
}
/* package */ PersistableBundle(Parcel parcelledData, int length) {
super(parcelledData, length);
mFlags = FLAG_DEFUSABLE;
}
/**
* Constructs a PersistableBundle without initializing it.
*/
PersistableBundle(boolean doInit) {
super(doInit);
}
/**
* Make a PersistableBundle for a single key/value pair.
*
* @hide
*/
public static PersistableBundle forPair(String key, String value) {
PersistableBundle b = new PersistableBundle(1);
b.putString(key, value);
return b;
}
/**
* Clones the current PersistableBundle. The internal map is cloned, but the keys and
* values to which it refers are copied by reference.
*/
@Override
public Object clone() {
return new PersistableBundle(this);
}
/**
* Make a deep copy of the given bundle. Traverses into inner containers and copies
* them as well, so they are not shared across bundles. Will traverse in to
* {@link Bundle}, {@link PersistableBundle}, {@link ArrayList}, and all types of
* primitive arrays. Other types of objects (such as Parcelable or Serializable)
* are referenced as-is and not copied in any way.
*/
public PersistableBundle deepCopy() {
PersistableBundle b = new PersistableBundle(false);
b.copyInternal(this, true);
return b;
}
/**
* Inserts a PersistableBundle value into the mapping of this Bundle, replacing
* any existing value for the given key. Either key or value may be null.
*
* @param key a String, or null
* @param value a Bundle object, or null
*/
public void putPersistableBundle(@Nullable String key, @Nullable PersistableBundle value) {
unparcel();
mMap.put(key, value);
}
/**
* Returns the value associated with the given key, or null if
* no mapping of the desired type exists for the given key or a null
* value is explicitly associated with the key.
*
* @param key a String, or null
* @return a Bundle value, or null
*/
@Nullable
public PersistableBundle getPersistableBundle(@Nullable String key) {
unparcel();
Object o = mMap.get(key);
if (o == null) {
return null;
}
try {
return (PersistableBundle) o;
} catch (ClassCastException e) {
typeWarning(key, o, "Bundle", e);
return null;
}
}
public static final @android.annotation.NonNull Parcelable.Creator<PersistableBundle> CREATOR =
new Parcelable.Creator<PersistableBundle>() {
@Override
public PersistableBundle createFromParcel(Parcel in) {
return in.readPersistableBundle();
}
@Override
public PersistableBundle[] newArray(int size) {
return new PersistableBundle[size];
}
};
/** @hide */
@Override
public void writeUnknownObject(Object v, String name, XmlSerializer out)
throws XmlPullParserException, IOException {
if (v instanceof PersistableBundle) {
out.startTag(null, TAG_PERSISTABLEMAP);
out.attribute(null, "name", name);
((PersistableBundle) v).saveToXml(out);
out.endTag(null, TAG_PERSISTABLEMAP);
} else {
throw new XmlPullParserException("Unknown Object o=" + v);
}
}
/** @hide */
public void saveToXml(XmlSerializer out) throws IOException, XmlPullParserException {
unparcel();
XmlUtils.writeMapXml(mMap, out, this);
}
/** @hide */
static class MyReadMapCallback implements XmlUtils.ReadMapCallback {
@Override
public Object readThisUnknownObjectXml(XmlPullParser in, String tag)
throws XmlPullParserException, IOException {
if (TAG_PERSISTABLEMAP.equals(tag)) {
return restoreFromXml(in);
}
throw new XmlPullParserException("Unknown tag=" + tag);
}
}
/**
* Report the nature of this Parcelable's contents
*/
@Override
public int describeContents() {
return 0;
}
/**
* Writes the PersistableBundle contents to a Parcel, typically in order for
* it to be passed through an IBinder connection.
* @param parcel The parcel to copy this bundle to.
*/
@Override
public void writeToParcel(Parcel parcel, int flags) {
final boolean oldAllowFds = parcel.pushAllowFds(false);
try {
writeToParcelInner(parcel, flags);
} finally {
parcel.restoreAllowFds(oldAllowFds);
}
}
/** @hide */
public static PersistableBundle restoreFromXml(XmlPullParser in) throws IOException,
XmlPullParserException {
final int outerDepth = in.getDepth();
final String startTag = in.getName();
final String[] tagName = new String[1];
int event;
while (((event = in.next()) != XmlPullParser.END_DOCUMENT) &&
(event != XmlPullParser.END_TAG || in.getDepth() < outerDepth)) {
if (event == XmlPullParser.START_TAG) {
return new PersistableBundle((ArrayMap<String, Object>)
XmlUtils.readThisArrayMapXml(in, startTag, tagName,
new MyReadMapCallback()));
}
}
return EMPTY;
}
@Override
synchronized public String toString() {
if (mParcelledData != null) {
if (isEmptyParcel()) {
return "PersistableBundle[EMPTY_PARCEL]";
} else {
return "PersistableBundle[mParcelledData.dataSize=" +
mParcelledData.dataSize() + "]";
}
}
return "PersistableBundle[" + mMap.toString() + "]";
}
/** @hide */
synchronized public String toShortString() {
if (mParcelledData != null) {
if (isEmptyParcel()) {
return "EMPTY_PARCEL";
} else {
return "mParcelledData.dataSize=" + mParcelledData.dataSize();
}
}
return mMap.toString();
}
/** @hide */
public void writeToProto(ProtoOutputStream proto, long fieldId) {
final long token = proto.start(fieldId);
if (mParcelledData != null) {
if (isEmptyParcel()) {
proto.write(PersistableBundleProto.PARCELLED_DATA_SIZE, 0);
} else {
proto.write(PersistableBundleProto.PARCELLED_DATA_SIZE, mParcelledData.dataSize());
}
} else {
proto.write(PersistableBundleProto.MAP_DATA, mMap.toString());
}
proto.end(token);
}
}