content: /*
   * 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);
      }
  }
  