Setup & maintain hierarchical names for EAS

TODO: More tests (when I can get tests running)

Change-Id: If15d93e760a3f23aa9a3b117ecb8a74d71999d9a
diff --git a/exchange2/src/com/android/exchange/adapter/FolderSyncParser.java b/exchange2/src/com/android/exchange/adapter/FolderSyncParser.java
index 814fafc..8eefe14 100644
--- a/exchange2/src/com/android/exchange/adapter/FolderSyncParser.java
+++ b/exchange2/src/com/android/exchange/adapter/FolderSyncParser.java
@@ -660,6 +660,8 @@
                 }
             }
 
+            MailboxUtilities.setupHierarchicalNames(mContext, mAccount.mId);
+
             // Signal completion of mailbox changes
             MailboxUtilities.endMailboxChanges(mContext, mAccount.mId);
         }
diff --git a/exchange2/src/com/android/exchange/provider/MailboxUtilities.java b/exchange2/src/com/android/exchange/provider/MailboxUtilities.java
index 093e3b8..c66ffa5 100644
--- a/exchange2/src/com/android/exchange/provider/MailboxUtilities.java
+++ b/exchange2/src/com/android/exchange/provider/MailboxUtilities.java
@@ -21,6 +21,8 @@
 import android.content.ContentValues;
 import android.content.Context;
 import android.database.Cursor;
+import android.os.Debug;
+import android.text.TextUtils;
 import android.util.Log;
 
 import com.android.emailcommon.Logging;
@@ -28,6 +30,8 @@
 import com.android.emailcommon.provider.EmailContent.MailboxColumns;
 import com.android.emailcommon.provider.Mailbox;
 
+import java.util.HashMap;
+
 public class MailboxUtilities {
     public static final String WHERE_PARENT_KEY_UNINITIALIZED =
         "(" + MailboxColumns.PARENT_KEY + " isnull OR " + MailboxColumns.PARENT_KEY + "=" +
@@ -210,4 +214,62 @@
             endMailboxChanges(context, accountId);
         }
     }
+
+    private static final String[] HIERARCHY_PROJECTION = new String[] {
+        MailboxColumns.ID, MailboxColumns.DISPLAY_NAME, MailboxColumns.PARENT_KEY,
+        MailboxColumns.HIERARCHICAL_NAME
+    };
+    private static final int HIERARCHY_ID = 0;
+    private static final int HIERARCHY_NAME = 1;
+    private static final int HIERARCHY_PARENT_KEY = 2;
+    private static final int HIERARCHY_HIERARCHICAL_NAME = 3;
+
+    private static String getHierarchicalName(Context context, long id, HashMap<Long, String> map,
+            String name, long parentId) {
+        String hierarchicalName;
+        if (map.containsKey(id)) {
+            return map.get(id);
+        } else if (parentId == Mailbox.NO_MAILBOX) {
+            hierarchicalName = name;
+        } else {
+            Mailbox parent = Mailbox.restoreMailboxWithId(context, parentId);
+            if (parent == null) return name + "/" + "??";
+            hierarchicalName = getHierarchicalName(context, parentId, map, parent.mDisplayName,
+                    parent.mParentKey) + "/" + name;
+        }
+        map.put(id, hierarchicalName);
+        return hierarchicalName;
+    }
+
+    public static void setupHierarchicalNames(Context context, long accountId) {
+        Account account = Account.restoreAccountWithId(context, accountId);
+        if (account == null) return;
+        // Start by clearing all names
+        ContentValues values = new ContentValues();
+        String accountSelector = Mailbox.ACCOUNT_KEY + "=" + account.mId;
+        ContentResolver resolver = context.getContentResolver();
+        HashMap<Long, String> nameMap = new HashMap<Long, String>();
+        Cursor c = resolver.query(Mailbox.CONTENT_URI, HIERARCHY_PROJECTION, accountSelector,
+                null, null);
+        try {
+            while(c.moveToNext()) {
+                long id = c.getLong(HIERARCHY_ID);
+                String displayName = c.getString(HIERARCHY_NAME);
+                String name = getHierarchicalName(context, id, nameMap, displayName,
+                        c.getLong(HIERARCHY_PARENT_KEY));
+                String oldHierarchicalName = c.getString(HIERARCHY_HIERARCHICAL_NAME);
+                // Don't write the name unless it has changed or we don't need one (it's top-level)
+                if (name.equals(oldHierarchicalName) ||
+                        ((name.equals(displayName)) && TextUtils.isEmpty(oldHierarchicalName))) {
+                    continue;
+                }
+                // If the name has changed, update it
+                values.put(MailboxColumns.HIERARCHICAL_NAME, name);
+                resolver.update(ContentUris.withAppendedId(Mailbox.CONTENT_URI, id), values, null,
+                        null);
+            }
+        } finally {
+            c.close();
+        }
+    }
 }
diff --git a/exchange2/tests/src/com/android/exchange/provider/MailboxUtilitiesTests.java b/exchange2/tests/src/com/android/exchange/provider/MailboxUtilitiesTests.java
index a7425d7..622eab5 100644
--- a/exchange2/tests/src/com/android/exchange/provider/MailboxUtilitiesTests.java
+++ b/exchange2/tests/src/com/android/exchange/provider/MailboxUtilitiesTests.java
@@ -623,7 +623,6 @@
         assertEquals(box2.mId, box3.mParentKey);
     }
 
-
     /**
      * Tests the proper separation of two accounts using the methodology from the previous test.
      * This test will fail if MailboxUtilities fails to distinguish between mailboxes in different
@@ -759,4 +758,62 @@
         assertEquals(CHILD_FLAGS, box6.mFlags);
         assertEquals(box5.mId, box6.mParentKey);
     }
+
+    /**
+     * Tests the proper separation of two accounts using the methodology from the previous test.
+     * This test will fail if MailboxUtilities fails to distinguish between mailboxes in different
+     * accounts that happen to have the same serverId
+     */
+    public void testSetupHierarchicalNames() {
+        // Set up account and mailboxes
+        mAccount = setupTestAccount("acct1", true);
+        long accountId = mAccount.mId;
+
+        // Box3 is in Box1
+        Mailbox box1 = EmailContentSetupUtils.setupMailbox(
+                "box1", accountId, false, mProviderContext, Mailbox.TYPE_MAIL);
+        box1.mServerId = "1:1";
+        box1.save(mProviderContext);
+        Mailbox box2 = EmailContentSetupUtils.setupMailbox(
+                "box2", accountId, false, mProviderContext, Mailbox.TYPE_MAIL);
+        box2.mServerId = "1:2";
+        box2.save(mProviderContext);
+        Mailbox box3 = EmailContentSetupUtils.setupMailbox(
+                "box3", accountId, false, mProviderContext, Mailbox.TYPE_MAIL, box1);
+        box3.mServerId = "1:3";
+        box3.save(mProviderContext);
+
+        // Box4 is in Box2; Box5 is in Box4; Box 6 is in Box5
+        Mailbox box4 = EmailContentSetupUtils.setupMailbox(
+                "box4", accountId, false, mProviderContext, Mailbox.TYPE_MAIL, box2);
+        box4.mServerId = "1:4";
+        box4.save(mProviderContext);
+        Mailbox box5 = EmailContentSetupUtils.setupMailbox(
+                "box5", accountId, false, mProviderContext, Mailbox.TYPE_MAIL, box4);
+        box5.mServerId = "1:5";
+        box5.save(mProviderContext);
+        Mailbox box6 = EmailContentSetupUtils.setupMailbox(
+                "box6", accountId, false, mProviderContext, Mailbox.TYPE_MAIL, box5);
+        box6.mServerId = "1:6";
+        box6.save(mProviderContext);
+
+        // Setup hierarchy
+        String accountSelector1 = MailboxColumns.ACCOUNT_KEY + " IN (" + accountId + ")";
+        MailboxUtilities.fixupUninitializedParentKeys(mProviderContext, accountSelector1);
+        // Setup hierarchy names
+        MailboxUtilities.setupHierarchicalNames(mProviderContext, accountId);
+        box1 = Mailbox.restoreMailboxWithId(mProviderContext, box1.mId);
+        box2 = Mailbox.restoreMailboxWithId(mProviderContext, box2.mId);
+        box3 = Mailbox.restoreMailboxWithId(mProviderContext, box3.mId);
+        box4 = Mailbox.restoreMailboxWithId(mProviderContext, box4.mId);
+        box5 = Mailbox.restoreMailboxWithId(mProviderContext, box5.mId);
+        box6 = Mailbox.restoreMailboxWithId(mProviderContext, box6.mId);
+
+        assertEquals("box1", box1.mHierarchicalName);
+        assertEquals("box2", box2.mHierarchicalName);
+        assertEquals("box1/box3", box3.mHierarchicalName);
+        assertEquals("box2/box4", box4.mHierarchicalName);
+        assertEquals("box2/box4/box5", box5.mHierarchicalName);
+        assertEquals("box2/box4/box5/box6", box6.mHierarchicalName);
+    }
 }