Properly decode a uid from the globalObjId in invites

* Meeting invitations in EAS include a globalObjId.  It turns out
  that this id is EITHER the actual uid (if Exchange created it)
  or a wrapper for the actual uid (if some other client created it)
* To find out which case we're dealing with, we have to look at
  the base64 decoded string for the magic "vCal-Uid" substring
* If it's there, we pull the real uid out of the decoded string
* Otherwise, we build a hex strong from the decoded bytes
* Write unit test for this process

Bug: 2598201
Change-Id: I1cc40af6d1e45be44c19465eb8a4c31851ec8157
diff --git a/src/com/android/exchange/adapter/EmailSyncAdapter.java b/src/com/android/exchange/adapter/EmailSyncAdapter.java
index 5cade38..dc4f204 100644
--- a/src/com/android/exchange/adapter/EmailSyncAdapter.java
+++ b/src/com/android/exchange/adapter/EmailSyncAdapter.java
@@ -45,7 +45,6 @@
 import android.database.Cursor;
 import android.net.Uri;
 import android.os.RemoteException;
-import android.util.Base64;
 import android.webkit.MimeTypeMap;
 
 import java.io.IOException;
@@ -215,16 +214,8 @@
                         packedString.put(MeetingInfo.MEETING_LOCATION, getValue());
                         break;
                     case Tags.EMAIL_GLOBAL_OBJID:
-                        // This is lovely; the unique id is a base64 encoded hex string
-                        String guid = getValue();
-                        StringBuilder sb = new StringBuilder();
-                        // First get the decoded base64
-                        byte[] bytes = Base64.decode(guid, Base64.DEFAULT);
-                        // Then go through the bytes and write out the hex values as characters
-                        for (byte b: bytes) {
-                            Utility.byteToHex(sb, b);
-                        }
-                        packedString.put(MeetingInfo.MEETING_UID, sb.toString());
+                        packedString.put(MeetingInfo.MEETING_UID,
+                                CalendarUtilities.getUidFromGlobalObjId(getValue()));
                         break;
                     case Tags.EMAIL_CATEGORIES:
                         nullParser();
diff --git a/src/com/android/exchange/utility/CalendarUtilities.java b/src/com/android/exchange/utility/CalendarUtilities.java
index 049ca30..35e42e1 100644
--- a/src/com/android/exchange/utility/CalendarUtilities.java
+++ b/src/com/android/exchange/utility/CalendarUtilities.java
@@ -18,6 +18,7 @@
 
 import com.android.email.Email;
 import com.android.email.R;
+import com.android.email.Utility;
 import com.android.email.mail.Address;
 import com.android.email.provider.EmailContent;
 import com.android.email.provider.EmailContent.Account;
@@ -1175,6 +1176,40 @@
         return -1;
     }
 
+    /**
+     * Return the uid for an event based on its globalObjId
+     * @param globalObjId the base64 encoded String provided by EAS
+     * @return the uid for the calendar event
+     */
+    static public String getUidFromGlobalObjId(String globalObjId) {
+        StringBuilder sb = new StringBuilder();
+        // First get the decoded base64
+        try {
+            byte[] idBytes = Base64.decode(globalObjId, Base64.DEFAULT);
+            String idString = new String(idBytes);
+            // If the base64 decoded string contains the magic substring: "vCal-Uid", then
+            // the actual uid is hidden within; the magic substring is never at the start of the
+            // decoded base64
+            int index = idString.indexOf("vCal-Uid");
+            if (index > 0) {
+                // The uid starts after "vCal-Uidxxxx", where xxxx are padding
+                // characters.  And it ends before the last character, which is ascii 0
+                return idString.substring(index + 12, idString.length() - 1);
+            } else {
+                // This is an EAS uid. Go through the bytes and write out the hex
+                // values as characters; this is what we'll need to pass back to EAS
+                // when responding to the invitation
+                for (byte b: idBytes) {
+                    Utility.byteToHex(sb, b);
+                }
+                return sb.toString();
+            }
+        } catch (RuntimeException e) {
+            // In the worst of cases (bad format, etc.), we can always return the input
+            return globalObjId;
+        }
+    }
+
     static public String buildMessageTextFromEntityValues(Context context,
             ContentValues entityValues, StringBuilder sb) {
         if (sb == null) {
diff --git a/tests/src/com/android/exchange/utility/CalendarUtilitiesTests.java b/tests/src/com/android/exchange/utility/CalendarUtilitiesTests.java
index 67172f3..20916e7 100644
--- a/tests/src/com/android/exchange/utility/CalendarUtilitiesTests.java
+++ b/tests/src/com/android/exchange/utility/CalendarUtilitiesTests.java
@@ -700,6 +700,20 @@
             Log.d("TimeZoneGeneration", "No rule: " + nr);
         }
     }
+
+    public void testGetUidFromGlobalObjId() {
+        // This is a "foreign" uid (from some vCalendar client)
+        String globalObjId = "BAAAAIIA4AB0xbcQGoLgCAAAAAAAAAAAAAAAAAAAAAAAAAAAMQAAA" +
+                "HZDYWwtVWlkAQAAADI3NjU1NmRkLTg1MzAtNGZiZS1iMzE0LThiM2JlYTYwMjE0OQA=";
+        String uid = CalendarUtilities.getUidFromGlobalObjId(globalObjId);
+        assertEquals(uid, "276556dd-8530-4fbe-b314-8b3bea602149");
+        // This is a native EAS uid
+        globalObjId =
+            "BAAAAIIA4AB0xbcQGoLgCAAAAADACTu7KbPKAQAAAAAAAAAAEAAAAObgsG6HVt1Fmy+7GlLbGhY=";
+        uid = CalendarUtilities.getUidFromGlobalObjId(globalObjId);
+        assertEquals(uid, "040000008200E00074C5B7101A82E00800000000C0093BBB29B3CA" +
+                "01000000000000000010000000E6E0B06E8756DD459B2FBB1A52DB1A16");
+    }
 }
 
     // TODO Planned unit tests