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         this.mStability = parcel.readInt();
183 
184         mParcelable = null;
185 
186         int dataSize = parcel.readInt();
187         if (dataSize < 0) {
188             throw new IllegalArgumentException("dataSize from parcel is negative");
189         } else if (dataSize == 0) {
190             if (mParcel != null) {
191                 mParcel.recycle();
192                 mParcel = null;
193             }
194             return;
195         }
196         if (mParcel == null) {
197             mParcel = Parcel.obtain();
198         }
199         mParcel.setDataPosition(0);
200         mParcel.setDataSize(0);
201         int dataStartPos = parcel.dataPosition();
202 
203         mParcel.appendFrom(parcel, dataStartPos, dataSize);
204         parcel.setDataPosition(MathUtils.addOrThrow(dataStartPos, dataSize));
205     }
206 
207     @Override
writeToParcel(@onNull Parcel parcel, int flags)208     public void writeToParcel(@NonNull Parcel parcel, int flags) {
209         parcel.writeInt(this.mStability);
210 
211         if (mParcel != null) {
212             parcel.writeInt(mParcel.dataSize());
213             parcel.appendFrom(mParcel, 0, mParcel.dataSize());
214             return;
215         }
216 
217         if (mParcelable == null) {
218             parcel.writeInt(0);
219             return;
220         }
221 
222         int sizePos = parcel.dataPosition();
223         parcel.writeInt(0);
224         int dataStartPos = parcel.dataPosition();
225         parcel.writeParcelable(mParcelable, 0);
226         int dataSize = parcel.dataPosition() - dataStartPos;
227 
228         parcel.setDataPosition(sizePos);
229         parcel.writeInt(dataSize);
230         parcel.setDataPosition(MathUtils.addOrThrow(parcel.dataPosition(), dataSize));
231     }
232 
233     @Override
describeContents()234     public int describeContents() {
235         if (mParcel != null) {
236             return mParcel.hasFileDescriptors() ? Parcelable.CONTENTS_FILE_DESCRIPTOR : 0;
237         }
238         if (mParcelable != null) {
239             return mParcelable.describeContents();
240         }
241         return 0;
242     }
243 }
244