Make AccountWithDataSet null safe

Don't crash on null account names. Change AccountWithDataSet so that it
does not inherit from Account and allow name and type to be null.

Bug: 18959158
Change-Id: I6da8aa52d9c408288b2c85ac2c5a507ff42dc985
diff --git a/src/com/android/contacts/common/model/AccountTypeManager.java b/src/com/android/contacts/common/model/AccountTypeManager.java
index 6efe819..6677c84 100644
--- a/src/com/android/contacts/common/model/AccountTypeManager.java
+++ b/src/com/android/contacts/common/model/AccountTypeManager.java
@@ -126,7 +126,10 @@
     }
 
     public final AccountType getAccountTypeForAccount(AccountWithDataSet account) {
-        return getAccountType(account.getAccountTypeWithDataSet());
+        if (account != null) {
+            return getAccountType(account.getAccountTypeWithDataSet());
+        }
+        return getAccountType(null, null);
     }
 
     /**
@@ -243,20 +246,12 @@
     /* A latch that ensures that asynchronous initialization completes before data is used */
     private volatile CountDownLatch mInitializationLatch = new CountDownLatch(1);
 
-    private static final Comparator<Account> ACCOUNT_COMPARATOR = new Comparator<Account>() {
+    private static final Comparator<AccountWithDataSet> ACCOUNT_COMPARATOR =
+        new Comparator<AccountWithDataSet>() {
         @Override
-        public int compare(Account a, Account b) {
-            String aDataSet = null;
-            String bDataSet = null;
-            if (a instanceof AccountWithDataSet) {
-                aDataSet = ((AccountWithDataSet) a).dataSet;
-            }
-            if (b instanceof AccountWithDataSet) {
-                bDataSet = ((AccountWithDataSet) b).dataSet;
-            }
-
+        public int compare(AccountWithDataSet a, AccountWithDataSet b) {
             if (Objects.equal(a.name, b.name) && Objects.equal(a.type, b.type)
-                    && Objects.equal(aDataSet, bDataSet)) {
+                    && Objects.equal(a.dataSet, b.dataSet)) {
                 return 0;
             } else if (b.name == null || b.type == null) {
                 return -1;
@@ -273,8 +268,8 @@
                 }
 
                 // Accounts without data sets get sorted before those that have them.
-                if (aDataSet != null) {
-                    return bDataSet == null ? 1 : aDataSet.compareTo(bDataSet);
+                if (a.dataSet != null) {
+                    return b.dataSet == null ? 1 : a.dataSet.compareTo(b.dataSet);
                 } else {
                     return -1;
                 }
diff --git a/src/com/android/contacts/common/model/RawContact.java b/src/com/android/contacts/common/model/RawContact.java
index ab30f64..3d8db85 100644
--- a/src/com/android/contacts/common/model/RawContact.java
+++ b/src/com/android/contacts/common/model/RawContact.java
@@ -296,7 +296,12 @@
     }
 
     public void setAccount(AccountWithDataSet accountWithDataSet) {
-        setAccount(accountWithDataSet.name, accountWithDataSet.type, accountWithDataSet.dataSet);
+        if (accountWithDataSet != null) {
+            setAccount(accountWithDataSet.name, accountWithDataSet.type,
+                    accountWithDataSet.dataSet);
+        } else {
+            setAccount(null, null, null);
+        }
     }
 
     public void setAccountToLocal() {
diff --git a/src/com/android/contacts/common/model/account/AccountWithDataSet.java b/src/com/android/contacts/common/model/account/AccountWithDataSet.java
index dd31dbc..634b5d6 100644
--- a/src/com/android/contacts/common/model/account/AccountWithDataSet.java
+++ b/src/com/android/contacts/common/model/account/AccountWithDataSet.java
@@ -20,6 +20,7 @@
 import android.content.Context;
 import android.database.Cursor;
 import android.net.Uri;
+import android.os.Parcelable;
 import android.os.Parcel;
 import android.provider.BaseColumns;
 import android.provider.ContactsContract;
@@ -36,7 +37,7 @@
 /**
  * Wrapper for an account that includes a data set (which may be null).
  */
-public class AccountWithDataSet extends Account {
+public class AccountWithDataSet implements Parcelable {
     private static final String STRINGIFY_SEPARATOR = "\u0001";
     private static final String ARRAY_STRINGIFY_SEPARATOR = "\u0002";
 
@@ -45,6 +46,8 @@
     private static final Pattern ARRAY_STRINGIFY_SEPARATOR_PAT =
             Pattern.compile(Pattern.quote(ARRAY_STRINGIFY_SEPARATOR));
 
+    public final String name;
+    public final String type;
     public final String dataSet;
     private final AccountTypeWithDataSet mAccountTypeWithDataSet;
 
@@ -54,20 +57,41 @@
 
 
     public AccountWithDataSet(String name, String type, String dataSet) {
-        super(name, type);
-        this.dataSet = dataSet;
+        this.name = emptyToNull(name);
+        this.type = emptyToNull(type);
+        this.dataSet = emptyToNull(dataSet);
         mAccountTypeWithDataSet = AccountTypeWithDataSet.get(type, dataSet);
     }
 
+    private static final String emptyToNull(String text) {
+        return TextUtils.isEmpty(text) ? null : text;
+    }
+
     public AccountWithDataSet(Parcel in) {
-        super(in);
+        this.name = in.readString();
+        this.type = in.readString();
         this.dataSet = in.readString();
         mAccountTypeWithDataSet = AccountTypeWithDataSet.get(type, dataSet);
     }
 
-    @Override
+    public boolean isLocalAccount() {
+        return name == null && type == null;
+    }
+
+    public Account getAccountOrNull() {
+        if (name != null && type != null) {
+            return new Account(name, type);
+        }
+        return null;
+    }
+
+    public int describeContents() {
+        return 0;
+    }
+
     public void writeToParcel(Parcel dest, int flags) {
-        super.writeToParcel(dest, flags);
+        dest.writeString(name);
+        dest.writeString(type);
         dest.writeString(dataSet);
     }
 
@@ -113,27 +137,32 @@
         }
     }
 
-    @Override
-    public boolean equals(Object o) {
-        return (o instanceof AccountWithDataSet) && super.equals(o)
-                && Objects.equal(((AccountWithDataSet) o).dataSet, dataSet);
+    public boolean equals(Object obj) {
+        if (obj instanceof AccountWithDataSet) {
+            AccountWithDataSet other = (AccountWithDataSet) obj;
+            return Objects.equal(name, other.name)
+                    && Objects.equal(type, other.type)
+                    && Objects.equal(dataSet, other.dataSet);
+        }
+        return false;
     }
 
-    @Override
     public int hashCode() {
-        return 31 * super.hashCode()
-                + (dataSet == null ? 0 : dataSet.hashCode());
+        int result = 17;
+        result = 31 * result + (name != null ? name.hashCode() : 0);
+        result = 31 * result + (type != null ? type.hashCode() : 0);
+        result = 31 * result + (dataSet != null ? dataSet.hashCode() : 0);
+        return result;
     }
 
-    @Override
     public String toString() {
         return "AccountWithDataSet {name=" + name + ", type=" + type + ", dataSet=" + dataSet + "}";
     }
 
     private static StringBuilder addStringified(StringBuilder sb, AccountWithDataSet account) {
-        sb.append(account.name);
+        if (!TextUtils.isEmpty(account.name)) sb.append(account.name);
         sb.append(STRINGIFY_SEPARATOR);
-        sb.append(account.type);
+        if (!TextUtils.isEmpty(account.type)) sb.append(account.type);
         sb.append(STRINGIFY_SEPARATOR);
         if (!TextUtils.isEmpty(account.dataSet)) sb.append(account.dataSet);
 
diff --git a/src/com/android/contacts/common/vcard/ImportRequest.java b/src/com/android/contacts/common/vcard/ImportRequest.java
index 0fab8f5..32efb99 100644
--- a/src/com/android/contacts/common/vcard/ImportRequest.java
+++ b/src/com/android/contacts/common/vcard/ImportRequest.java
@@ -18,6 +18,7 @@
 import android.accounts.Account;
 import android.net.Uri;
 
+import com.android.contacts.common.model.account.AccountWithDataSet;
 import com.android.vcard.VCardSourceDetector;
 
 /**
@@ -95,10 +96,10 @@
      */
     public final int entryCount;
 
-    public ImportRequest(Account account,
+    public ImportRequest(AccountWithDataSet account,
             byte[] data, Uri uri, String displayName, int estimatedType, String estimatedCharset,
             int vcardVersion, int entryCount) {
-        this.account = account;
+        this.account = account != null ? account.getAccountOrNull() : null;
         this.data = data;
         this.uri = uri;
         this.displayName = displayName;