/* * Copyright (C) 2020 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.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; import android.util.MathUtils; /** * ParcelableHolder is a Parcelable which can contain another Parcelable. * The main use case of ParcelableHolder is to make a Parcelable extensible. * For example, an AOSP-defined Parcelable AospDefinedParcelable * is expected to be extended by device implementers for their value-add features. * Previously without ParcelableHolder, the device implementers had to * directly modify the Parcelable to add more fields: *
 {@code
 * parcelable AospDefinedParcelable {
 *   int a;
 *   String b;
 *   String x; // added by a device implementer
 *   int[] y; // added by a device implementer
 * }}
* * This practice is very error-prone because the fields added by the device implementer * might have a conflict when the Parcelable is revisioned in the next releases of Android. * * Using ParcelableHolder, one can define an extension point in a Parcelable. *
 {@code
 * parcelable AospDefinedParcelable {
 *   int a;
 *   String b;
 *   ParcelableHolder extension;
 * }}
* Then the device implementers can define their own Parcelable for their extension. * *
 {@code
 * parcelable OemDefinedParcelable {
 *   String x;
 *   int[] y;
 * }}
* Finally, the new Parcelable can be attached to the original Parcelable via * the ParcelableHolder field. * *
 {@code
 * AospDefinedParcelable ap = ...;
 * OemDefinedParcelable op = new OemDefinedParcelable();
 * op.x = ...;
 * op.y = ...;
 * ap.extension.setParcelable(op);}
* *

ParcelableHolder is not thread-safe.

* * @hide */ @SystemApi public final class ParcelableHolder implements Parcelable { /** * This is set by {@link #setParcelable}. * {@link #mParcelable} and {@link #mParcel} are mutually exclusive * if {@link ParcelableHolder} contains value, otherwise, both are null. */ private Parcelable mParcelable; /** * This is set by {@link #readFromParcel}. * {@link #mParcelable} and {@link #mParcel} are mutually exclusive * if {@link ParcelableHolder} contains value, otherwise, both are null. */ private Parcel mParcel; private @Parcelable.Stability int mStability = Parcelable.PARCELABLE_STABILITY_LOCAL; public ParcelableHolder(@Parcelable.Stability int stability) { mStability = stability; } private ParcelableHolder() { } /** * {@link ParcelableHolder}'s stability is determined by the parcelable * which contains this ParcelableHolder. * For more detail refer to {@link Parcelable#getStability}. */ @Override public @Parcelable.Stability int getStability() { return mStability; } @NonNull public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { @NonNull @Override public ParcelableHolder createFromParcel(@NonNull Parcel parcel) { ParcelableHolder parcelable = new ParcelableHolder(); parcelable.readFromParcel(parcel); return parcelable; } @NonNull @Override public ParcelableHolder[] newArray(int size) { return new ParcelableHolder[size]; } }; /** * Write a parcelable into ParcelableHolder, the previous parcelable will be removed. * (@link #setParcelable} and (@link #getParcelable} are not thread-safe. * @throws BadParcelableException if the parcelable's stability is more unstable * ParcelableHolder. */ public void setParcelable(@Nullable Parcelable p) { // A ParcelableHolder can only hold things at its stability or higher. if (p != null && this.getStability() > p.getStability()) { throw new BadParcelableException( "A ParcelableHolder can only hold things at its stability or higher. " + "The ParcelableHolder's stability is " + this.getStability() + ", but the parcelable's stability is " + p.getStability()); } mParcelable = p; if (mParcel != null) { mParcel.recycle(); mParcel = null; } } /** * Read a parcelable from ParcelableHolder. * (@link #setParcelable} and (@link #getParcelable} are not thread-safe. * @return the parcelable that was written by {@link #setParcelable} or {@link #readFromParcel}, * or {@code null} if the parcelable has not been written. * @throws BadParcelableException if T is different from the type written by * (@link #setParcelable}. */ @Nullable public T getParcelable(@NonNull Class clazz) { if (mParcel == null) { if (mParcelable != null && !clazz.isInstance(mParcelable)) { throw new BadParcelableException( "The ParcelableHolder has " + mParcelable.getClass().getName() + ", but the requested type is " + clazz.getName()); } return (T) mParcelable; } mParcel.setDataPosition(0); T parcelable = mParcel.readParcelable(clazz.getClassLoader()); if (parcelable != null && !clazz.isInstance(parcelable)) { throw new BadParcelableException( "The ParcelableHolder has " + parcelable.getClass().getName() + ", but the requested type is " + clazz.getName()); } mParcelable = parcelable; mParcel.recycle(); mParcel = null; return parcelable; } /** * Read ParcelableHolder from a parcel. */ public void readFromParcel(@NonNull Parcel parcel) { int wireStability = parcel.readInt(); if (this.mStability != wireStability) { throw new IllegalArgumentException("Expected stability " + this.mStability + " but got " + wireStability); } mParcelable = null; int dataSize = parcel.readInt(); if (dataSize < 0) { throw new IllegalArgumentException("dataSize from parcel is negative"); } else if (dataSize == 0) { if (mParcel != null) { mParcel.recycle(); mParcel = null; } return; } if (mParcel == null) { mParcel = Parcel.obtain(); } mParcel.setDataPosition(0); mParcel.setDataSize(0); int dataStartPos = parcel.dataPosition(); mParcel.appendFrom(parcel, dataStartPos, dataSize); parcel.setDataPosition(MathUtils.addOrThrow(dataStartPos, dataSize)); } @Override public void writeToParcel(@NonNull Parcel parcel, int flags) { parcel.writeInt(this.mStability); if (mParcel != null) { parcel.writeInt(mParcel.dataSize()); parcel.appendFrom(mParcel, 0, mParcel.dataSize()); return; } if (mParcelable == null) { parcel.writeInt(0); return; } int sizePos = parcel.dataPosition(); parcel.writeInt(0); int dataStartPos = parcel.dataPosition(); parcel.writeParcelable(mParcelable, 0); int dataSize = parcel.dataPosition() - dataStartPos; parcel.setDataPosition(sizePos); parcel.writeInt(dataSize); parcel.setDataPosition(MathUtils.addOrThrow(parcel.dataPosition(), dataSize)); } @Override public int describeContents() { if (mParcel != null) { return mParcel.hasFileDescriptors() ? Parcelable.CONTENTS_FILE_DESCRIPTOR : 0; } if (mParcelable != null) { return mParcelable.describeContents(); } return 0; } }