Friend intent: Proper support for dataSet

- Introduce AccountTypeWithDataSet to encapsulate accountType + dataSet
  and use it instead of the "account type + '/' + dataset" string,
  for better type safety.

Bug 5162267

Change-Id: Id96aea69804bb1151b612838f3fdc24841e5f527
diff --git a/src/com/android/contacts/ContactLoader.java b/src/com/android/contacts/ContactLoader.java
index dbfe411..1107530 100644
--- a/src/com/android/contacts/ContactLoader.java
+++ b/src/com/android/contacts/ContactLoader.java
@@ -18,11 +18,13 @@
 
 import com.android.contacts.model.AccountType;
 import com.android.contacts.model.AccountTypeManager;
+import com.android.contacts.model.AccountTypeWithDataSet;
 import com.android.contacts.util.DataStatus;
 import com.android.contacts.util.StreamItemEntry;
 import com.android.contacts.util.StreamItemPhotoEntry;
 import com.google.android.collect.Lists;
 import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.Maps;
 import com.google.common.collect.Sets;
 
 import android.content.ContentResolver;
@@ -781,20 +783,21 @@
          * TODO Exclude the ones with no raw contacts in the database.
          */
         private void loadInvitableAccountTypes(Result contactData) {
-            Map<String, AccountType> allInvitables =
+            Map<AccountTypeWithDataSet, AccountType> allInvitables =
                     AccountTypeManager.getInstance(getContext()).getInvitableAccountTypes();
             if (allInvitables.isEmpty()) {
                 return;
             }
 
-            HashMap<String, AccountType> result = new HashMap<String, AccountType>(allInvitables);
+            HashMap<AccountTypeWithDataSet, AccountType> result = Maps.newHashMap(allInvitables);
 
             // Remove the ones that already have a raw contact in the current contact
             for (Entity entity : contactData.getEntities()) {
-                final String type = entity.getEntityValues().getAsString(RawContacts.ACCOUNT_TYPE);
-                if (!TextUtils.isEmpty(type)) {
-                    result.remove(type);
-                }
+                final ContentValues values = entity.getEntityValues();
+                final AccountTypeWithDataSet type = AccountTypeWithDataSet.get(
+                        values.getAsString(RawContacts.ACCOUNT_TYPE),
+                        values.getAsString(RawContacts.DATA_SET));
+                result.remove(type);
             }
 
             // Set to mInvitableAccountTypes
diff --git a/src/com/android/contacts/model/AccountType.java b/src/com/android/contacts/model/AccountType.java
index 95216e6..8ee1aa1 100644
--- a/src/com/android/contacts/model/AccountType.java
+++ b/src/com/android/contacts/model/AccountType.java
@@ -51,8 +51,6 @@
 public abstract class AccountType {
     private static final String TAG = "AccountType";
 
-    private static final String ACCOUNT_TYPE_DATA_SET_DELIMITER = "/";
-
     /**
      * The {@link RawContacts#ACCOUNT_TYPE} these constraints apply to.
      */
@@ -150,21 +148,10 @@
     }
 
     /**
-     * Returns the account type with the data set (if any) appended after a delimiter.
-     * If the data set is null, this will simply return the account type.
+     * Returns {@link AccountTypeWithDataSet} for this type.
      */
-    public String getAccountTypeAndDataSet() {
-        return getAccountTypeAndDataSet(accountType, dataSet);
-    }
-
-    /**
-     * Utility method to concatenate the given account type with a data set with a delimiter.
-     * If the data set is null, this will simply return the account type.
-     */
-    public static String getAccountTypeAndDataSet(String accountType, String dataSet) {
-        return dataSet == null
-                ? accountType
-                : accountType + ACCOUNT_TYPE_DATA_SET_DELIMITER + dataSet;
+    public AccountTypeWithDataSet getAccountTypeAndDataSet() {
+        return AccountTypeWithDataSet.get(accountType, dataSet);
     }
 
     /**
diff --git a/src/com/android/contacts/model/AccountTypeManager.java b/src/com/android/contacts/model/AccountTypeManager.java
index b517c2c..6d52e18 100644
--- a/src/com/android/contacts/model/AccountTypeManager.java
+++ b/src/com/android/contacts/model/AccountTypeManager.java
@@ -88,10 +88,10 @@
     public abstract AccountType getAccountType(String accountType, String dataSet);
 
     /**
-     * @return Unmodifiable map from account type strings to {@link AccountType}s which support
-     * the "invite" feature and have one or more account.
+     * @return Unmodifiable map from {@link AccountTypeWithDataSet}s to {@link AccountType}s
+     * which support the "invite" feature and have one or more account.
      */
-    public abstract Map<String, AccountType> getInvitableAccountTypes();
+    public abstract Map<AccountTypeWithDataSet, AccountType> getInvitableAccountTypes();
 
     /**
      * Find the best {@link DataKind} matching the requested
@@ -114,9 +114,9 @@
 
     private List<AccountWithDataSet> mAccounts = Lists.newArrayList();
     private List<AccountWithDataSet> mWritableAccounts = Lists.newArrayList();
-    private Map<String, AccountType> mAccountTypesWithDataSets = Maps.newHashMap();
-    private Map<String, AccountType> mInvitableAccountTypes = Collections.unmodifiableMap(
-            new HashMap<String, AccountType>());
+    private Map<AccountTypeWithDataSet, AccountType> mAccountTypesWithDataSets = Maps.newHashMap();
+    private Map<AccountTypeWithDataSet, AccountType> mInvitableAccountTypes =
+            Collections.unmodifiableMap(new HashMap<AccountTypeWithDataSet, AccountType>());
 
     private static final int MESSAGE_LOAD_DATA = 0;
     private static final int MESSAGE_PROCESS_BROADCAST_INTENT = 1;
@@ -266,7 +266,7 @@
         long startTime = SystemClock.currentThreadTimeMillis();
 
         // Account types, keyed off the account type and data set concatenation.
-        Map<String, AccountType> accountTypesByTypeAndDataSet = Maps.newHashMap();
+        Map<AccountTypeWithDataSet, AccountType> accountTypesByTypeAndDataSet = Maps.newHashMap();
 
         // The same AccountTypes, but keyed off {@link RawContacts#ACCOUNT_TYPE}.  Since there can
         // be multiple account types (with different data sets) for the same type of account, each
@@ -404,7 +404,7 @@
 
     // Bookkeeping method for tracking the known account types in the given maps.
     private void addAccountType(AccountType accountType,
-            Map<String, AccountType> accountTypesByTypeAndDataSet,
+            Map<AccountTypeWithDataSet, AccountType> accountTypesByTypeAndDataSet,
             Map<String, List<AccountType>> accountTypesByType) {
         accountTypesByTypeAndDataSet.put(accountType.getAccountTypeAndDataSet(), accountType);
         List<AccountType> accountsForType = accountTypesByType.get(accountType.accountType);
@@ -450,7 +450,7 @@
 
         // Try finding account type and kind matching request
         final AccountType type = mAccountTypesWithDataSets.get(
-                AccountType.getAccountTypeAndDataSet(accountType, dataSet));
+                AccountTypeWithDataSet.get(accountType, dataSet));
         if (type != null) {
             kind = type.getKindForMimetype(mimeType);
         }
@@ -475,13 +475,13 @@
         ensureAccountsLoaded();
         synchronized (this) {
             AccountType type = mAccountTypesWithDataSets.get(
-                    AccountType.getAccountTypeAndDataSet(accountType, dataSet));
+                    AccountTypeWithDataSet.get(accountType, dataSet));
             return type != null ? type : mFallbackAccountType;
         }
     }
 
     @Override
-    public Map<String, AccountType> getInvitableAccountTypes() {
+    public Map<AccountTypeWithDataSet, AccountType> getInvitableAccountTypes() {
         return mInvitableAccountTypes;
     }
 
@@ -490,14 +490,13 @@
      * its {@link AccountType#getInviteContactActivityClassName()} is not empty.
      */
     @VisibleForTesting
-    static Map<String, AccountType> findInvitableAccountTypes(Context context,
+    static Map<AccountTypeWithDataSet, AccountType> findInvitableAccountTypes(Context context,
             Collection<AccountWithDataSet> accounts,
-            Map<String, AccountType> accountTypesByTypeAndDataSet) {
-        HashMap<String, AccountType> result = Maps.newHashMap();
+            Map<AccountTypeWithDataSet, AccountType> accountTypesByTypeAndDataSet) {
+        HashMap<AccountTypeWithDataSet, AccountType> result = Maps.newHashMap();
         for (AccountWithDataSet account : accounts) {
-            String accountTypeWithDataSet = account.getAccountTypeWithDataSet();
-            AccountType type = accountTypesByTypeAndDataSet.get(
-                    account.getAccountTypeWithDataSet());
+            AccountTypeWithDataSet accountTypeWithDataSet = account.getAccountTypeAndWithDataSet();
+            AccountType type = accountTypesByTypeAndDataSet.get(accountTypeWithDataSet);
             if (type == null) continue; // just in case
             if (result.containsKey(accountTypeWithDataSet)) continue;
 
diff --git a/src/com/android/contacts/model/AccountTypeWithDataSet.java b/src/com/android/contacts/model/AccountTypeWithDataSet.java
new file mode 100644
index 0000000..d5cdbdd
--- /dev/null
+++ b/src/com/android/contacts/model/AccountTypeWithDataSet.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.contacts.model;
+
+import com.google.common.base.Objects;
+
+import android.text.TextUtils;
+
+
+/**
+ * Encapsulates an "account type" string and a "data set" string.
+ */
+public class AccountTypeWithDataSet {
+    /** account type will never be null. */
+    public final String accountType;
+
+    /** dataSet may be null, but never be "". */
+    public final String dataSet;
+
+    private AccountTypeWithDataSet(String accountType, String dataSet) {
+        if (accountType == null) throw new NullPointerException();
+
+        this.accountType = accountType;
+        this.dataSet = TextUtils.isEmpty(dataSet) ? null : dataSet;
+    }
+
+    public static AccountTypeWithDataSet get(String accountType, String dataSet) {
+        return new AccountTypeWithDataSet(accountType, dataSet);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (!(o instanceof AccountTypeWithDataSet)) return false;
+
+        AccountTypeWithDataSet other = (AccountTypeWithDataSet) o;
+        return Objects.equal(accountType, other.accountType)
+                && Objects.equal(dataSet, other.dataSet);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hashCode(accountType) ^ (dataSet == null ? 0 : Objects.hashCode(dataSet));
+    }
+
+    @Override
+    public String toString() {
+        return "[" + accountType + "/" + dataSet + "]";
+    }
+}
diff --git a/src/com/android/contacts/model/AccountWithDataSet.java b/src/com/android/contacts/model/AccountWithDataSet.java
index 1d97614..f607737 100644
--- a/src/com/android/contacts/model/AccountWithDataSet.java
+++ b/src/com/android/contacts/model/AccountWithDataSet.java
@@ -27,19 +27,22 @@
 public class AccountWithDataSet extends Account {
 
     public final String dataSet;
+    private final AccountTypeWithDataSet mAccountTypeWithDataSet;
 
     public AccountWithDataSet(String name, String type, String dataSet) {
         super(name, type);
         this.dataSet = dataSet;
+        mAccountTypeWithDataSet = AccountTypeWithDataSet.get(type, dataSet);
     }
 
     public AccountWithDataSet(Parcel in, String dataSet) {
         super(in);
         this.dataSet = dataSet;
+        mAccountTypeWithDataSet = AccountTypeWithDataSet.get(type, dataSet);
     }
 
-    public String getAccountTypeWithDataSet() {
-        return dataSet == null ? type : AccountType.getAccountTypeAndDataSet(type, dataSet);
+    public AccountTypeWithDataSet getAccountTypeAndWithDataSet() {
+        return mAccountTypeWithDataSet;
     }
 
     @Override