1 /*
2  * Copyright (C) 2015 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.support.car.os;
18 
19 import android.os.Parcel;
20 import android.os.Parcelable;
21 import android.support.car.annotation.VersionDef;
22 
23 /**
24  * ExtendableParcelable helps extending Parcelable in future in safe way.
25  * Basic idea is to write version code and payload length when writing to Parcel.
26  * Reader side reads version and do following actions:
27  *   - if writer version <= reader version: Reader should know up to which element is supported by
28  *     writer and read only up to that portion.
29  *   - if writer version > reader version: Reader reads all members it knows and throw away remaing
30  *     data so that additional Parcel reading can be done safely.
31  *
32  * For reader side:
33  *  - In constructor with Parcel, call super(Parcel) first so that version number and payload length
34  *    is read properly.
35  *  - Call {@link #readHeader(Parcel)}.
36  *  - After finishing all recognized or available members, call
37  *    {@link #completeReading(Parcel, int)} with second argument set to return value from
38  *    {@link #readHeader(Parcel)}. This will throw away any unread data.
39  *
40  * For writer side, when implementing writeToParcel:
41  *   - call {@link #writeHeader(Parcel)} before writing anything else.
42  *   - call {@link #completeWriting(Parcel, int)} with second argument set to return value from
43  *     {@link #writeHeader(Parcel)}.
44  */
45 public abstract class ExtendableParcelable implements Parcelable {
46     /**
47      * Version of this Parcelable. Reader side needs to read only up to the version written.
48      * This does not represent class version but contents version. For example, original
49      * Parcelable (=V1) passed to V2 supporting Parcelable will have version 1 even if
50      * V2 supporting Parcelable may have additional member variables added in V2.
51      */
52     @VersionDef(version = 1)
53     public final int version;
54 
55     /**
56      * Constructor for reading parcel. Always call this first before reading anything else.
57      * @param in
58      * @param maxVersion
59      */
ExtendableParcelable(Parcel in, int version)60     protected ExtendableParcelable(Parcel in, int version) {
61         int writerVersion = in.readInt();
62         if (version < writerVersion) { // version limited by reader
63             this.version = version;
64         } else { // version limited by writer
65             this.version = writerVersion;
66         }
67     }
68 
69     /**
70      * Constructor for writer side. Version should be always set.
71      * @param version
72      */
ExtendableParcelable(int version)73     protected ExtendableParcelable(int version) {
74         this.version = version;
75     }
76 
77     /**
78      * Read header of Parcelable from Pacel. This should be done after super(Parcel, int) and
79      * before reading any Parcel. After all reading is done, {@link #completeReading(Parcel, int)}
80      * should be called.
81      *
82      * @param in
83      * @return last position. This should be passed to {@link #completeReading(Parcel, int)}.
84      */
readHeader(Parcel in)85     protected int readHeader(Parcel in) {
86         int payloadLength = in.readInt();
87         int startingPosition = in.dataPosition();
88         return startingPosition + payloadLength;
89     }
90 
91     /**
92      * Complete reading and safely throw away any unread data.
93      * @param in
94      * @param lastPosition Last position of of this Parcelable in the passed Parcel.
95      *                     The value is passed from {@link #readHeader(Parcel)}.
96      */
completeReading(Parcel in, int lastPosition)97     protected void completeReading(Parcel in, int lastPosition) {
98         in.setDataPosition(lastPosition);
99     }
100 
101     /**
102      * Write header for writing to Parcel. This should be done before writing anything else.
103      * Code to use ths can look like:
104      *   int pos = writeHeader(dest);
105      *   dest.writeInt(0); // whatever relevant data
106      *   ...
107      *   completeWrite(dest, pos);
108      *
109      * @param dest
110      * @return startingPosition which should be passed when calling completeWrite.
111      */
writeHeader(Parcel dest)112     protected int writeHeader(Parcel dest) {
113         dest.writeInt(version);
114         // temporary value for playload length. will be replaced in completeWrite
115         dest.writeInt(0);
116         // previous int is 4 bytes before this.
117         return dest.dataPosition();
118     }
119 
120     /**
121      * Complete writing the current Parcelable. No more write to Parcel should be done after
122      * this call.
123      * @param dest
124      * @param startingPosition startingPosition returned from writeHeader
125      */
completeWriting(Parcel dest, int startingPosition)126     protected void completeWriting(Parcel dest, int startingPosition) {
127         int currentPosition = dest.dataPosition();
128         dest.setDataPosition(startingPosition - 4);
129         int payloadLength = currentPosition - startingPosition;
130         dest.writeInt(payloadLength);
131         dest.setDataPosition(currentPosition);
132     }
133 }
134