1 /*
2  * Copyright (C) 2020 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 
17 package android.os;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.annotation.SystemApi;
22 import android.util.MathUtils;
23 
24 /**
25  * ParcelableHolder is a Parcelable which can contain another Parcelable.
26  * The main use case of ParcelableHolder is to make a Parcelable extensible.
27  * For example, an AOSP-defined Parcelable <code>AospDefinedParcelable</code>
28  * is expected to be extended by device implementers for their value-add features.
29  * Previously without ParcelableHolder, the device implementers had to
30  * directly modify the Parcelable to add more fields:
31  * <pre> {@code
32  * parcelable AospDefinedParcelable {
33  *   int a;
34  *   String b;
35  *   String x; // added by a device implementer
36  *   int[] y; // added by a device implementer
37  * }}</pre>
38  *
39  * This practice is very error-prone because the fields added by the device implementer
40  * might have a conflict when the Parcelable is revisioned in the next releases of Android.
41  *
42  * Using ParcelableHolder, one can define an extension point in a Parcelable.
43  * <pre> {@code
44  * parcelable AospDefinedParcelable {
45  *   int a;
46  *   String b;
47  *   ParcelableHolder extension;
48  * }}</pre>
49  * Then the device implementers can define their own Parcelable for their extension.
50  *
51  * <pre> {@code
52  * parcelable OemDefinedParcelable {
53  *   String x;
54  *   int[] y;
55  * }}</pre>
56  * Finally, the new Parcelable can be attached to the original Parcelable via
57  * the ParcelableHolder field.
58  *
59  * <pre> {@code
60  * AospDefinedParcelable ap = ...;
61  * OemDefinedParcelable op = new OemDefinedParcelable();
62  * op.x = ...;
63  * op.y = ...;
64  * ap.extension.setParcelable(op);}</pre>
65  *
66  * <p class="note">ParcelableHolder is <strong>not</strong> thread-safe.</p>
67  *
68  * @hide
69  */
70 @SystemApi
71 public final class ParcelableHolder implements Parcelable {
72     /**
73      * This is set by {@link #setParcelable}.
74      * {@link #mParcelable} and {@link #mParcel} are mutually exclusive
75      * if {@link ParcelableHolder} contains value, otherwise, both are null.
76      */
77     private Parcelable mParcelable;
78     /**
79      * This is set by {@link #readFromParcel}.
80      * {@link #mParcelable} and {@link #mParcel} are mutually exclusive
81      * if {@link ParcelableHolder} contains value, otherwise, both are null.
82      */
83     private Parcel mParcel;
84     private @Parcelable.Stability int mStability = Parcelable.PARCELABLE_STABILITY_LOCAL;
85 
ParcelableHolder(@arcelable.Stability int stability)86     public ParcelableHolder(@Parcelable.Stability int stability) {
87         mStability = stability;
88     }
89 
ParcelableHolder()90     private ParcelableHolder() {
91 
92     }
93 
94     /**
95      * {@link ParcelableHolder}'s stability is determined by the parcelable
96      * which contains this ParcelableHolder.
97      * For more detail refer to {@link Parcelable#getStability}.
98      */
99     @Override
getStability()100     public @Parcelable.Stability int getStability() {
101         return mStability;
102     }
103 
104     @NonNull
105     public static final Parcelable.Creator<ParcelableHolder> CREATOR =
106             new Parcelable.Creator<ParcelableHolder>() {
107                 @NonNull
108                 @Override
109                 public ParcelableHolder createFromParcel(@NonNull Parcel parcel) {
110                     ParcelableHolder parcelable = new ParcelableHolder();
111                     parcelable.readFromParcel(parcel);
112                     return parcelable;
113                 }
114 
115                 @NonNull
116                 @Override
117                 public ParcelableHolder[] newArray(int size) {
118                     return new ParcelableHolder[size];
119                 }
120             };
121 
122 
123     /**
124      * Write a parcelable into ParcelableHolder, the previous parcelable will be removed.
125      * (@link #setParcelable} and (@link #getParcelable} are not thread-safe.
126      * @throws BadParcelableException if the parcelable's stability is more unstable
127      *         ParcelableHolder.
128      */
setParcelable(@ullable Parcelable p)129     public void setParcelable(@Nullable Parcelable p) {
130         // A ParcelableHolder can only hold things at its stability or higher.
131         if (p != null && this.getStability() > p.getStability()) {
132             throw new BadParcelableException(
133                 "A ParcelableHolder can only hold things at its stability or higher. "
134                 + "The ParcelableHolder's stability is " + this.getStability()
135                 + ", but the parcelable's stability is " + p.getStability());
136         }
137         mParcelable = p;
138         if (mParcel != null) {
139             mParcel.recycle();
140             mParcel = null;
141         }
142     }
143 
144     /**
145      * Read a parcelable from ParcelableHolder.
146      * (@link #setParcelable} and (@link #getParcelable} are not thread-safe.
147      * @return the parcelable that was written by {@link #setParcelable} or {@link #readFromParcel},
148      *         or {@code null} if the parcelable has not been written.
149      * @throws BadParcelableException if T is different from the type written by
150      *         (@link #setParcelable}.
151      */
152     @Nullable
getParcelable(@onNull Class<T> clazz)153     public <T extends Parcelable> T getParcelable(@NonNull Class<T> clazz) {
154         if (mParcel == null) {
155             if (mParcelable != null && !clazz.isInstance(mParcelable)) {
156                 throw new BadParcelableException(
157                     "The ParcelableHolder has " + mParcelable.getClass().getName()
158                     + ", but the requested type is " + clazz.getName());
159             }
160             return (T) mParcelable;
161         }
162 
163         mParcel.setDataPosition(0);
164 
165         T parcelable = mParcel.readParcelable(clazz.getClassLoader());
166         if (parcelable != null && !clazz.isInstance(parcelable)) {
167             throw new BadParcelableException(
168                     "The ParcelableHolder has " + parcelable.getClass().getName()
169                     + ", but the requested type is " + clazz.getName());
170         }
171         mParcelable = parcelable;
172 
173         mParcel.recycle();
174         mParcel = null;
175         return parcelable;
176     }
177 
178     /**
179      * Read ParcelableHolder from a parcel.
180      */
readFromParcel(@onNull Parcel parcel)181     public void readFromParcel(@NonNull Parcel parcel) {
182         int wireStability = parcel.readInt();
183         if (this.mStability != wireStability) {
184             throw new IllegalArgumentException("Expected stability " + this.mStability
185                                                + " but got " + wireStability);
186         }
187 
188         mParcelable = null;
189 
190         int dataSize = parcel.readInt();
191         if (dataSize < 0) {
192             throw new IllegalArgumentException("dataSize from parcel is negative");
193         } else if (dataSize == 0) {
194             if (mParcel != null) {
195                 mParcel.recycle();
196                 mParcel = null;
197             }
198             return;
199         }
200         if (mParcel == null) {
201             mParcel = Parcel.obtain();
202         }
203         mParcel.setDataPosition(0);
204         mParcel.setDataSize(0);
205         int dataStartPos = parcel.dataPosition();
206 
207         mParcel.appendFrom(parcel, dataStartPos, dataSize);
208         parcel.setDataPosition(MathUtils.addOrThrow(dataStartPos, dataSize));
209     }
210 
211     @Override
writeToParcel(@onNull Parcel parcel, int flags)212     public void writeToParcel(@NonNull Parcel parcel, int flags) {
213         parcel.writeInt(this.mStability);
214 
215         if (mParcel != null) {
216             parcel.writeInt(mParcel.dataSize());
217             parcel.appendFrom(mParcel, 0, mParcel.dataSize());
218             return;
219         }
220 
221         if (mParcelable == null) {
222             parcel.writeInt(0);
223             return;
224         }
225 
226         int sizePos = parcel.dataPosition();
227         parcel.writeInt(0);
228         int dataStartPos = parcel.dataPosition();
229         parcel.writeParcelable(mParcelable, 0);
230         int dataSize = parcel.dataPosition() - dataStartPos;
231 
232         parcel.setDataPosition(sizePos);
233         parcel.writeInt(dataSize);
234         parcel.setDataPosition(MathUtils.addOrThrow(parcel.dataPosition(), dataSize));
235     }
236 
237     @Override
describeContents()238     public int describeContents() {
239         if (mParcel != null) {
240             return mParcel.hasFileDescriptors() ? Parcelable.CONTENTS_FILE_DESCRIPTOR : 0;
241         }
242         if (mParcelable != null) {
243             return mParcelable.describeContents();
244         }
245         return 0;
246     }
247 }
248