blob: a739ba3dd438ec9f5637bc3482fd848ce685c5b6 [file] [log] [blame]
/*
* 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 <code>AospDefinedParcelable</code>
* 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:
* <pre> {@code
* parcelable AospDefinedParcelable {
* int a;
* String b;
* String x; // added by a device implementer
* int[] y; // added by a device implementer
* }}</pre>
*
* 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.
* <pre> {@code
* parcelable AospDefinedParcelable {
* int a;
* String b;
* ParcelableHolder extension;
* }}</pre>
* Then the device implementers can define their own Parcelable for their extension.
*
* <pre> {@code
* parcelable OemDefinedParcelable {
* String x;
* int[] y;
* }}</pre>
* Finally, the new Parcelable can be attached to the original Parcelable via
* the ParcelableHolder field.
*
* <pre> {@code
* AospDefinedParcelable ap = ...;
* OemDefinedParcelable op = new OemDefinedParcelable();
* op.x = ...;
* op.y = ...;
* ap.extension.setParcelable(op);}</pre>
*
* <p class="note">ParcelableHolder is <strong>not</strong> thread-safe.</p>
*
* @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<ParcelableHolder> CREATOR =
new Parcelable.Creator<ParcelableHolder>() {
@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 extends Parcelable> T getParcelable(@NonNull Class<T> 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;
}
}