cmsdk: Declare a parcelable header.

  - This change forces that a parcelable header is written as the
  first data positions in a parcel and defines a means to distinguish
  between old sdk class versions vs new ones to do proper unraveling
  of parcels.

Ticket: BAMBOO-152
Change-Id: I9cc762fe8a51cc527e85be7fe5de57e4613be019
diff --git a/src/java/cyanogenmod/app/CustomTile.java b/src/java/cyanogenmod/app/CustomTile.java
index 8f2f4bd..dc10d4a 100644
--- a/src/java/cyanogenmod/app/CustomTile.java
+++ b/src/java/cyanogenmod/app/CustomTile.java
@@ -23,6 +23,7 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.text.TextUtils;
+import cyanogenmod.os.Build;
 
 import java.util.ArrayList;
 
@@ -83,25 +84,38 @@
      * Unflatten the CustomTile from a parcel.
      */
     public CustomTile(Parcel parcel) {
-        if (parcel.readInt() != 0) {
-            this.onClick = PendingIntent.CREATOR.createFromParcel(parcel);
+        // Read parcelable version, make sure to define explicit changes
+        // within {@link Build.PARCELABLE_VERSION);
+        int parcelableVersion = parcel.readInt();
+        int parcelableSize = parcel.readInt();
+        int startPosition = parcel.dataPosition();
+
+        // Pattern here is that all new members should be added to the end of
+        // the writeToParcel method. Then we step through each version, until the latest
+        // API release to help unravel this parcel
+        if (parcelableVersion >= Build.CM_VERSION_CODES.APRICOT) {
+            if (parcel.readInt() != 0) {
+                this.onClick = PendingIntent.CREATOR.createFromParcel(parcel);
+            }
+            if (parcel.readInt() != 0) {
+                this.onSettingsClick = Intent.CREATOR.createFromParcel(parcel);
+            }
+            if (parcel.readInt() != 0) {
+                this.onClickUri = Uri.CREATOR.createFromParcel(parcel);
+            }
+            if (parcel.readInt() != 0) {
+                this.label = parcel.readString();
+            }
+            if (parcel.readInt() != 0) {
+                this.contentDescription = parcel.readString();
+            }
+            if (parcel.readInt() != 0) {
+                this.expandedStyle = ExpandedStyle.CREATOR.createFromParcel(parcel);
+            }
+            this.icon = parcel.readInt();
         }
-        if (parcel.readInt() != 0) {
-            this.onSettingsClick = Intent.CREATOR.createFromParcel(parcel);
-        }
-        if (parcel.readInt() != 0) {
-            this.onClickUri = Uri.CREATOR.createFromParcel(parcel);
-        }
-        if (parcel.readInt() != 0) {
-            this.label = parcel.readString();
-        }
-        if (parcel.readInt() != 0) {
-            this.contentDescription = parcel.readString();
-        }
-        if (parcel.readInt() != 0) {
-            this.expandedStyle = ExpandedStyle.CREATOR.createFromParcel(parcel);
-        }
-        this.icon = parcel.readInt();
+
+        parcel.setDataPosition(startPosition + parcelableSize);
     }
 
     /**
@@ -167,6 +181,17 @@
 
     @Override
     public void writeToParcel(Parcel out, int flags) {
+        // Write parcelable version, make sure to define explicit changes
+        // within {@link Build.PARCELABLE_VERSION);
+        out.writeInt(Build.PARCELABLE_VERSION);
+
+        // Inject a placeholder that will store the parcel size from this point on
+        // (not including the size itself).
+        int sizePosition = out.dataPosition();
+        out.writeInt(0);
+        int startPosition = out.dataPosition();
+
+        // ==== APRICOT =====
         if (onClick != null) {
             out.writeInt(1);
             onClick.writeToParcel(out, 0);
@@ -204,6 +229,12 @@
             out.writeInt(0);
         }
         out.writeInt(icon);
+
+        // Go back and write size
+        int parcelableSize = out.dataPosition() - startPosition;
+        out.setDataPosition(sizePosition);
+        out.writeInt(parcelableSize);
+        out.setDataPosition(startPosition + parcelableSize);
     }
 
     /**
@@ -234,10 +265,23 @@
         private int styleId;
 
         private ExpandedStyle(Parcel parcel) {
-            if (parcel.readInt() != 0) {
-                expandedItems = parcel.createTypedArray(ExpandedItem.CREATOR);
+            // Read parcelable version, make sure to define explicit changes
+            // within {@link Build.PARCELABLE_VERSION);
+            int parcelableVersion = parcel.readInt();
+            int parcelableSize = parcel.readInt();
+            int startPosition = parcel.dataPosition();
+
+            // Pattern here is that all new members should be added to the end of
+            // the writeToParcel method. Then we step through each version, until the latest
+            // API release to help unravel this parcel
+            if (parcelableVersion >= Build.CM_VERSION_CODES.APRICOT) {
+                if (parcel.readInt() != 0) {
+                    expandedItems = parcel.createTypedArray(ExpandedItem.CREATOR);
+                }
+                styleId = parcel.readInt();
             }
-            styleId = parcel.readInt();
+
+            parcel.setDataPosition(startPosition + parcelableSize);
         }
 
         /**
@@ -287,6 +331,17 @@
 
         @Override
         public void writeToParcel(Parcel parcel, int i) {
+            // Write parcelable version, make sure to define explicit changes
+            // within {@link Build.PARCELABLE_VERSION);
+            parcel.writeInt(Build.PARCELABLE_VERSION);
+
+            // Inject a placeholder that will store the parcel size from this point on
+            // (not including the size itself).
+            int sizePosition = parcel.dataPosition();
+            parcel.writeInt(0);
+            int startPosition = parcel.dataPosition();
+
+            // ==== APRICOT ====
             if (expandedItems != null) {
                 parcel.writeInt(1);
                 parcel.writeTypedArray(expandedItems, 0);
@@ -294,6 +349,12 @@
                 parcel.writeInt(0);
             }
             parcel.writeInt(styleId);
+
+            // Go back and write size
+            int parcelableSize = parcel.dataPosition() - startPosition;
+            parcel.setDataPosition(sizePosition);
+            parcel.writeInt(parcelableSize);
+            parcel.setDataPosition(startPosition + parcelableSize);
         }
 
         @Override
@@ -433,16 +494,29 @@
          * Unflatten the ExpandedItem from a parcel.
          */
         protected ExpandedItem(Parcel parcel) {
-            if (parcel.readInt() != 0) {
-                onClickPendingIntent = PendingIntent.CREATOR.createFromParcel(parcel);
+            // Read parcelable version, make sure to define explicit changes
+            // within {@link Build.PARCELABLE_VERSION);
+            int parcelableVersion = parcel.readInt();
+            int parcelableSize = parcel.readInt();
+            int startPosition = parcel.dataPosition();
+
+            // Pattern here is that all new members should be added to the end of
+            // the writeToParcel method. Then we step through each version, until the latest
+            // API release to help unravel this parcel
+            if (parcelableVersion >= Build.CM_VERSION_CODES.APRICOT) {
+                if (parcel.readInt() != 0) {
+                    onClickPendingIntent = PendingIntent.CREATOR.createFromParcel(parcel);
+                }
+                if (parcel.readInt() != 0) {
+                    itemTitle = parcel.readString();
+                }
+                if (parcel.readInt() != 0) {
+                    itemSummary = parcel.readString();
+                }
+                itemDrawableResourceId = parcel.readInt();
             }
-            if (parcel.readInt() != 0) {
-                itemTitle = parcel.readString();
-            }
-            if (parcel.readInt() != 0) {
-                itemSummary = parcel.readString();
-            }
-            itemDrawableResourceId = parcel.readInt();
+
+            parcel.setDataPosition(startPosition + parcelableSize);
         }
 
         @Override
@@ -452,6 +526,16 @@
 
         @Override
         public void writeToParcel(Parcel out, int flags) {
+            // Write parcelable version, make sure to define explicit changes
+            // within {@link Build.PARCELABLE_VERSION);
+            out.writeInt(Build.PARCELABLE_VERSION);
+
+            // Inject a placeholder that will store the parcel size from this point on
+            // (not including the size itself).
+            int sizePosition = out.dataPosition();
+            out.writeInt(0);
+            int startPosition = out.dataPosition();
+
             if (onClickPendingIntent != null) {
                 out.writeInt(1);
                 onClickPendingIntent.writeToParcel(out, 0);
@@ -471,6 +555,12 @@
                 out.writeInt(0);
             }
             out.writeInt(itemDrawableResourceId);
+
+            // Go back and write size
+            int parcelableSize = out.dataPosition() - startPosition;
+            out.setDataPosition(sizePosition);
+            out.writeInt(parcelableSize);
+            out.setDataPosition(startPosition + parcelableSize);
         }
 
         @Override
diff --git a/src/java/cyanogenmod/app/StatusBarPanelCustomTile.java b/src/java/cyanogenmod/app/StatusBarPanelCustomTile.java
index 27d8151..cfdc153 100644
--- a/src/java/cyanogenmod/app/StatusBarPanelCustomTile.java
+++ b/src/java/cyanogenmod/app/StatusBarPanelCustomTile.java
@@ -19,6 +19,7 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.UserHandle;
+import cyanogenmod.os.Build;
 
 /**
  * Class encapsulating a Custom Tile. Sent by the StatusBarManagerService to clients including
@@ -64,20 +65,56 @@
 
 
     public StatusBarPanelCustomTile(Parcel in) {
-        this.pkg = in.readString();
-        this.opPkg = in.readString();
-        this.id = in.readInt();
-        if (in.readInt() != 0) {
-            this.tag = in.readString();
-        } else {
-            this.tag = null;
+        // Read parcelable version, make sure to define explicit changes
+        // within {@link Build.PARCELABLE_VERSION);
+        int parcelableVersion = in.readInt();
+        int parcelableSize = in.readInt();
+        int startPosition = in.dataPosition();
+
+        // tmp variables for final
+        String tmpPkg = null;
+        String tmpOpPkg = null;
+        int tmpId = -1;
+        String tmpTag = null;
+        int tmpUid = -1;
+        int tmpPid = -1;
+        CustomTile tmpCustomTile = null;
+        UserHandle tmpUser = null;
+        long tmpPostTime = -1;
+
+        // Pattern here is that all new members should be added to the end of
+        // the writeToParcel method. Then we step through each version, until the latest
+        // API release to help unravel this parcel
+        if (parcelableVersion >= Build.CM_VERSION_CODES.APRICOT) {
+            // default
+            tmpPkg = in.readString();
+            tmpOpPkg = in.readString();
+            tmpId = in.readInt();
+            if (in.readInt() != 0) {
+                tmpTag = in.readString();
+            } else {
+                tmpTag = null;
+            }
+            tmpUid = in.readInt();
+            tmpPid = in.readInt();
+            tmpCustomTile = new CustomTile(in);
+            tmpUser = UserHandle.readFromParcel(in);
+            tmpPostTime = in.readLong();
         }
-        this.uid = in.readInt();
-        this.initialPid = in.readInt();
-        this.customTile = new CustomTile(in);
-        this.user = UserHandle.readFromParcel(in);
-        this.postTime = in.readLong();
+
+        // Assign finals
+        this.pkg = tmpPkg;
+        this.opPkg = tmpOpPkg;
+        this.id = tmpId;
+        this.tag = tmpTag;
+        this.uid = tmpUid;
+        this.initialPid = tmpPid;
+        this.customTile = tmpCustomTile;
+        this.user = tmpUser;
+        this.postTime = tmpPostTime;
         this.key = key();
+
+        in.setDataPosition(startPosition + parcelableSize);
     }
 
     private String key() {
@@ -112,6 +149,17 @@
 
     @Override
     public void writeToParcel(Parcel out, int flags) {
+        // Write parcelable version, make sure to define explicit changes
+        // within {@link Build.PARCELABLE_VERSION);
+        out.writeInt(Build.PARCELABLE_VERSION);
+
+        // Inject a placeholder that will store the parcel size from this point on
+        // (not including the size itself).
+        int sizePosition = out.dataPosition();
+        out.writeInt(0);
+        int startPosition = out.dataPosition();
+
+        // ==== APRICOT ===
         out.writeString(this.pkg);
         out.writeString(this.opPkg);
         out.writeInt(this.id);
@@ -126,6 +174,12 @@
         this.customTile.writeToParcel(out, flags);
         user.writeToParcel(out, flags);
         out.writeLong(this.postTime);
+
+        // Go back and write size
+        int parcelableSize = out.dataPosition() - startPosition;
+        out.setDataPosition(sizePosition);
+        out.writeInt(parcelableSize);
+        out.setDataPosition(startPosition + parcelableSize);
     }
 
     @Override
diff --git a/src/java/cyanogenmod/os/Build.java b/src/java/cyanogenmod/os/Build.java
index 8fde45f..5ae1d97 100644
--- a/src/java/cyanogenmod/os/Build.java
+++ b/src/java/cyanogenmod/os/Build.java
@@ -26,6 +26,16 @@
     /** Value used for when a build property is unknown. */
     public static final String UNKNOWN = "unknown";
 
+    /**
+     * Since there might be a case where new versions of the cm framework use applications running
+     * old versions of the protocol (and thus old versions of this class), we need a versioning
+     * system for the parcels sent between the core framework and its sdk users.
+     *
+     * This parcelable version should be the latest version API version listed in
+     * {@link CM_VERSION_CODES}
+     */
+    public static final int PARCELABLE_VERSION = CM_VERSION_CODES.APRICOT;
+
     private static final SparseArray<String> sdkMap;
     static
     {