am 64d95bae: Merge "Utility methods for new contact editor flow" into ics-factoryrom
* commit '64d95bae817be522e576ece311fa17d88a4c627c':
Utility methods for new contact editor flow
diff --git a/src/com/android/contacts/editor/ContactEditorUtils.java b/src/com/android/contacts/editor/ContactEditorUtils.java
new file mode 100644
index 0000000..900e68a
--- /dev/null
+++ b/src/com/android/contacts/editor/ContactEditorUtils.java
@@ -0,0 +1,249 @@
+/*
+ * 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.editor;
+
+import com.android.contacts.model.AccountType;
+import com.android.contacts.model.AccountTypeManager;
+import com.android.contacts.model.AccountWithDataSet;
+import com.android.contacts.test.NeededForTesting;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Sets;
+
+import android.accounts.Account;
+import android.accounts.AccountManager;
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.preference.PreferenceManager;
+import android.text.TextUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Utility methods for the "account changed" notification in the new contact creation flow.
+ *
+ * TODO Remove all the "@VisibleForTesting"s once they're actually used in the app.
+ * (Until then we need them to avoid "no such method" in tests)
+ */
+public class ContactEditorUtils {
+ private static final String TAG = "ContactEditorUtils";
+
+ private static final String KEY_DEFAULT_ACCOUNT = "ContactEditorUtils_default_account";
+ private static final String KEY_KNOWN_ACCOUNTS = "ContactEditorUtils_known_accounts";
+ // Key to tell the first time launch.
+ private static final String KEY_ANYTHING_SAVED = "ContactEditorUtils_anything_saved";
+
+ private static final List<AccountWithDataSet> EMPTY_ACCOUNTS = ImmutableList.of();
+
+ private static ContactEditorUtils sInstance;
+
+ private final Context mContext;
+ private final SharedPreferences mPrefs;
+ private final AccountTypeManager mAccountTypes;
+
+ private ContactEditorUtils(Context context) {
+ this(context, AccountTypeManager.getInstance(context));
+ }
+
+ @VisibleForTesting
+ ContactEditorUtils(Context context, AccountTypeManager accountTypes) {
+ mContext = context;
+ mPrefs = PreferenceManager.getDefaultSharedPreferences(mContext);
+ mAccountTypes = accountTypes;
+ }
+
+ public static synchronized ContactEditorUtils getInstance(Context context) {
+ if (sInstance == null) {
+ sInstance = new ContactEditorUtils(context);
+ }
+ return sInstance;
+ }
+
+ void cleanupForTest() {
+ mPrefs.edit().remove(KEY_DEFAULT_ACCOUNT).remove(KEY_KNOWN_ACCOUNTS)
+ .remove(KEY_ANYTHING_SAVED).apply();
+ }
+
+ private List<AccountWithDataSet> getWritableAccounts() {
+ return mAccountTypes.getAccounts(true);
+ }
+
+ /**
+ * @return true if it's the first launch and {@link #saveDefaultAndAllAccounts} has never
+ * been called.
+ */
+ private boolean isFirstLaunch() {
+ return !mPrefs.getBoolean(KEY_ANYTHING_SAVED, false);
+ }
+
+ /**
+ * Saves all writable accounts and the default account, which can later be obtained
+ * with {@link #getDefaultAccount}.
+ *
+ * This should be called when saving a newly created contact.
+ *
+ * @param defaultAccount the account used to save a newly created contact. Or pass {@code null}
+ * If the user selected "local only".
+ */
+ @NeededForTesting
+ public void saveDefaultAndAllAccounts(AccountWithDataSet defaultAccount) {
+ mPrefs.edit()
+ .putBoolean(KEY_ANYTHING_SAVED, true)
+ .putString(
+ KEY_KNOWN_ACCOUNTS,AccountWithDataSet.stringifyList(getWritableAccounts()))
+ .putString(KEY_DEFAULT_ACCOUNT,
+ (defaultAccount == null) ? "" : defaultAccount.stringify())
+ .apply();
+ }
+
+ /**
+ * @return the default account saved with {@link #saveDefaultAndAllAccounts}.
+ *
+ * Note the {@code null} return value can mean either {@link #saveDefaultAndAllAccounts} has
+ * never been called, or {@code null} was passed to {@link #saveDefaultAndAllAccounts} --
+ * i.e. the user selected "local only".
+ *
+ * Also note that the returned account may have been removed already.
+ */
+ @NeededForTesting
+ public AccountWithDataSet getDefaultAccount() {
+ final String saved = mPrefs.getString(KEY_DEFAULT_ACCOUNT, null);
+ if (TextUtils.isEmpty(saved)) {
+ return null;
+ }
+ return AccountWithDataSet.unstringify(saved);
+ }
+
+ /**
+ * @return true if an account still exists. {@code null} is considered "local only" here,
+ * so it's valid too.
+ */
+ @VisibleForTesting
+ boolean isValidAccount(AccountWithDataSet account) {
+ if (account == null) {
+ return true; // It's "local only" account, which is valid.
+ }
+ return getWritableAccounts().contains(account);
+ }
+
+ /**
+ * @return saved known accounts, or an empty list if none has been saved yet.
+ */
+ @VisibleForTesting
+ List<AccountWithDataSet> getSavedAccounts() {
+ final String saved = mPrefs.getString(KEY_KNOWN_ACCOUNTS, null);
+ if (TextUtils.isEmpty(saved)) {
+ return EMPTY_ACCOUNTS;
+ }
+ return AccountWithDataSet.unstringifyList(saved);
+ }
+
+ /**
+ * @return true if the contact editor should show the "accounts changed" notification, that is:
+ * - If it's the first launch.
+ * - Or, if an account has been added.
+ * - Or, if the default account has been removed.
+ *
+ * Note if this method returns {@code false}, the caller can safely assume that
+ * {@link #getDefaultAccount} will return a valid account. (Either an account which still
+ * exists, or {@code null} which should be interpreted as "local only".)
+ */
+ @NeededForTesting
+ public boolean shouldShowAccountChangedNotification() {
+ if (isFirstLaunch()) {
+ return true;
+ }
+
+ // Account added?
+ final List<AccountWithDataSet> savedAccounts = getSavedAccounts();
+ for (AccountWithDataSet account : getWritableAccounts()) {
+ if (!savedAccounts.contains(account)) {
+ return true; // New account found.
+ }
+ }
+
+ // Does default account still exist?
+ if (!isValidAccount(getDefaultAccount())) {
+ return true;
+ }
+
+ // All good.
+ return false;
+ }
+
+ @VisibleForTesting
+ String[] getWritableAccountTypeStrings() {
+ final Set<String> types = Sets.newHashSet();
+ for (AccountType type : mAccountTypes.getAccountTypes(true)) {
+ types.add(type.accountType);
+ }
+ return types.toArray(new String[types.size()]);
+ }
+
+ /**
+ * Create an {@link Intent} to start "add new account" setup wizard. Selectable account
+ * types will be limited to ones that supports editing contacts.
+ *
+ * Use {@link Activity#startActivityForResult} or
+ * {@link android.app.Fragment#startActivityForResult} to start the wizard, and
+ * {@link Activity#onActivityResult} or {@link android.app.Fragment#onActivityResult} to
+ * get the result.
+ */
+ @NeededForTesting
+ public Intent createAddWritableAccountIntent() {
+ return AccountManager.newChooseAccountIntent(
+ null, // selectedAccount
+ new ArrayList<Account>(), // allowableAccounts
+ getWritableAccountTypeStrings(), // allowableAccountTypes
+ false, // alwaysPromptForAccount
+ null, // descriptionOverrideText
+ null, // addAccountAuthTokenType
+ null, // addAccountRequiredFeatures
+ null // addAccountOptions
+ );
+ }
+
+ /**
+ * Parses a result from {@link #createAddWritableAccountIntent} and returns the created
+ * {@link Account}, or null if the user has canceled the wizard. Pass the {@code resultCode}
+ * and {@code data} parameters passed to {@link Activity#onActivityResult} or
+ * {@link android.app.Fragment#onActivityResult}.
+ *
+ * Note although the return type is {@link AccountWithDataSet}, return values from this method
+ * will never have {@link AccountWithDataSet#dataSet} set, as there's no way to create an
+ * extension package account from setup wizard.
+ */
+ @NeededForTesting
+ public AccountWithDataSet getCreatedAccount(int resultCode, Intent resultData) {
+ // Javadoc doesn't say anything about resultCode but that the data intent will be non null
+ // on success.
+ if (resultData == null) return null;
+
+ final String accountType = resultData.getStringExtra(AccountManager.KEY_ACCOUNT_TYPE);
+ final String accountName = resultData.getStringExtra(AccountManager.KEY_ACCOUNT_NAME);
+
+ // Just in case
+ if (TextUtils.isEmpty(accountType) || TextUtils.isEmpty(accountName)) return null;
+
+ return new AccountWithDataSet(accountName, accountType, null);
+ }
+}
+
diff --git a/src/com/android/contacts/model/AccountTypeManager.java b/src/com/android/contacts/model/AccountTypeManager.java
index bdd8a50..dc2fb0d 100644
--- a/src/com/android/contacts/model/AccountTypeManager.java
+++ b/src/com/android/contacts/model/AccountTypeManager.java
@@ -112,6 +112,13 @@
final AccountType type = getAccountType(accountType, dataSet);
return type == null ? null : type.getKindForMimetype(mimeType);
}
+
+ /*
+ * Returns all registered {@link AccountType}s, including extension ones.
+ *
+ * @param contactWritableOnly if true, it only returns ones that support writing contacts.
+ */
+ public abstract List<AccountType> getAccountTypes(boolean contactWritableOnly);
}
class AccountTypeManagerImpl extends AccountTypeManager
@@ -539,4 +546,17 @@
}
return Collections.unmodifiableMap(result);
}
+
+ @Override
+ public List<AccountType> getAccountTypes(boolean contactWritableOnly) {
+ final List<AccountType> accountTypes = Lists.newArrayList();
+ synchronized (this) {
+ for (AccountType type : mAccountTypesWithDataSets.values()) {
+ if (!contactWritableOnly || type.areContactsWritable()) {
+ accountTypes.add(type);
+ }
+ }
+ }
+ return accountTypes;
+ }
}
diff --git a/src/com/android/contacts/model/AccountWithDataSet.java b/src/com/android/contacts/model/AccountWithDataSet.java
index 55af795..e379346 100644
--- a/src/com/android/contacts/model/AccountWithDataSet.java
+++ b/src/com/android/contacts/model/AccountWithDataSet.java
@@ -17,21 +17,34 @@
package com.android.contacts.model;
import com.android.internal.util.Objects;
+import com.google.common.collect.Lists;
import android.accounts.Account;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.os.Parcel;
+import android.os.Parcelable.Creator;
import android.provider.BaseColumns;
import android.provider.ContactsContract;
import android.provider.ContactsContract.RawContacts;
import android.text.TextUtils;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Pattern;
+
/**
* Wrapper for an account that includes a data set (which may be null).
*/
public class AccountWithDataSet extends Account {
+ private static final String STRINGIFY_SEPARATOR = "\u0001";
+ private static final String ARRAY_STRINGIFY_SEPARATOR = "\u0002";
+
+ private static final Pattern STRINGIFY_SEPARATOR_PAT =
+ Pattern.compile(Pattern.quote(STRINGIFY_SEPARATOR));
+ private static final Pattern ARRAY_STRINGIFY_SEPARATOR_PAT =
+ Pattern.compile(Pattern.quote(ARRAY_STRINGIFY_SEPARATOR));
public final String dataSet;
private final AccountTypeWithDataSet mAccountTypeWithDataSet;
@@ -47,12 +60,29 @@
mAccountTypeWithDataSet = AccountTypeWithDataSet.get(type, dataSet);
}
- public AccountWithDataSet(Parcel in, String dataSet) {
+ public AccountWithDataSet(Parcel in) {
super(in);
- this.dataSet = dataSet;
+ this.dataSet = in.readString();
mAccountTypeWithDataSet = AccountTypeWithDataSet.get(type, dataSet);
}
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ dest.writeString(dataSet);
+ }
+
+ // For Parcelable
+ public static final Creator<AccountWithDataSet> CREATOR = new Creator<AccountWithDataSet>() {
+ public AccountWithDataSet createFromParcel(Parcel source) {
+ return new AccountWithDataSet(source);
+ }
+
+ public AccountWithDataSet[] newArray(int size) {
+ return new AccountWithDataSet[size];
+ }
+ };
+
public AccountTypeWithDataSet getAccountTypeWithDataSet() {
return mAccountTypeWithDataSet;
}
@@ -100,4 +130,67 @@
public String toString() {
return "AccountWithDataSet {name=" + name + ", type=" + type + ", dataSet=" + dataSet + "}";
}
+
+ private static StringBuilder addStringified(StringBuilder sb, AccountWithDataSet account) {
+ sb.append(account.name);
+ sb.append(STRINGIFY_SEPARATOR);
+ sb.append(account.type);
+ sb.append(STRINGIFY_SEPARATOR);
+ if (!TextUtils.isEmpty(account.dataSet)) sb.append(account.dataSet);
+
+ return sb;
+ }
+
+ /**
+ * Pack the instance into a string.
+ */
+ public String stringify() {
+ return addStringified(new StringBuilder(), this).toString();
+ }
+
+ /**
+ * Unpack a string created by {@link #stringify}.
+ */
+ public static AccountWithDataSet unstringify(String s) {
+ final String[] array = STRINGIFY_SEPARATOR_PAT.split(s, 3);
+ if (array.length < 3) {
+ throw new IllegalArgumentException("Invalid string");
+ }
+ return new AccountWithDataSet(array[0], array[1],
+ TextUtils.isEmpty(array[2]) ? null : array[2]);
+ }
+
+ /**
+ * Pack a list of {@link AccountWithDataSet} into a string.
+ */
+ public static String stringifyList(List<AccountWithDataSet> accounts) {
+ final StringBuilder sb = new StringBuilder();
+
+ for (AccountWithDataSet account : accounts) {
+ if (sb.length() > 0) {
+ sb.append(ARRAY_STRINGIFY_SEPARATOR);
+ }
+ addStringified(sb, account);
+ }
+
+ return sb.toString();
+ }
+
+ /**
+ * Unpack a list of {@link AccountWithDataSet} into a string.
+ */
+ public static List<AccountWithDataSet> unstringifyList(String s) {
+ final ArrayList<AccountWithDataSet> ret = Lists.newArrayList();
+ if (TextUtils.isEmpty(s)) {
+ return ret;
+ }
+
+ final String[] array = ARRAY_STRINGIFY_SEPARATOR_PAT.split(s);
+
+ for (int i = 0; i < array.length; i++) {
+ ret.add(unstringify(array[i]));
+ }
+
+ return ret;
+ }
}
diff --git a/tests/src/com/android/contacts/editor/ContactEditorUtilsTest.java b/tests/src/com/android/contacts/editor/ContactEditorUtilsTest.java
new file mode 100644
index 0000000..9f4e487
--- /dev/null
+++ b/tests/src/com/android/contacts/editor/ContactEditorUtilsTest.java
@@ -0,0 +1,310 @@
+/*
+ * 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.editor;
+
+import com.android.contacts.editor.ContactEditorUtils;
+import com.android.contacts.model.AccountType;
+import com.android.contacts.model.AccountWithDataSet;
+import com.android.contacts.tests.mocks.MockAccountTypeManager;
+import com.google.android.collect.Sets;
+
+import android.content.Context;
+import android.test.AndroidTestCase;
+import android.test.MoreAsserts;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import java.util.Collection;
+import java.util.Set;
+
+/**
+ * Test case for {@link ContactEditorUtils}.
+ *
+ * adb shell am instrument -w -e class com.android.contacts.editor.ContactEditorUtilsTest \
+ com.android.contacts.tests/android.test.InstrumentationTestRunner
+ */
+@SmallTest
+public class ContactEditorUtilsTest extends AndroidTestCase {
+ private MockAccountTypeManager mAccountTypes;
+ private ContactEditorUtils mTarget;
+
+ private static final MockAccountType TYPE1 = new MockAccountType("type1", null, true);
+ private static final MockAccountType TYPE2 = new MockAccountType("type2", null, true);
+ private static final MockAccountType TYPE2EX = new MockAccountType("type2", "ext", true);
+
+ // Only type 3 is "readonly".
+ private static final MockAccountType TYPE3 = new MockAccountType("type3", null, false);
+
+ private static final AccountWithDataSet ACCOUNT_1_A = new AccountWithDataSet(
+ "a", TYPE1.accountType, TYPE1.dataSet);
+ private static final AccountWithDataSet ACCOUNT_1_B = new AccountWithDataSet(
+ "b", TYPE1.accountType, TYPE1.dataSet);
+
+ private static final AccountWithDataSet ACCOUNT_2_A = new AccountWithDataSet(
+ "a", TYPE2.accountType, TYPE2.dataSet);
+ private static final AccountWithDataSet ACCOUNT_2EX_A = new AccountWithDataSet(
+ "a", TYPE2EX.accountType, TYPE2EX.dataSet);
+
+ private static final AccountWithDataSet ACCOUNT_3_C = new AccountWithDataSet(
+ "c", TYPE3.accountType, TYPE3.dataSet);
+
+ @Override
+ protected void setUp() throws Exception {
+ // Initialize with 0 types, 0 accounts.
+ mAccountTypes = new MockAccountTypeManager(new AccountType[] {},
+ new AccountWithDataSet[] {});
+ mTarget = new ContactEditorUtils(getContext(), mAccountTypes);
+
+ // Clear the preferences.
+ mTarget.cleanupForTest();
+ }
+
+ private void setAccountTypes(AccountType... types) {
+ mAccountTypes.mTypes = types;
+ }
+
+ private void setAccounts(AccountWithDataSet... accounts) {
+ mAccountTypes.mAccounts = accounts;
+ }
+
+ public void testGetWritableAccountTypeStrings() {
+ String[] types;
+
+ // 0 writable types
+ setAccountTypes();
+
+ types = mTarget.getWritableAccountTypeStrings();
+ MoreAsserts.assertEquals(types, new String[0]);
+
+ // 1 writable type
+ setAccountTypes(TYPE1);
+
+ types = mTarget.getWritableAccountTypeStrings();
+ MoreAsserts.assertEquals(Sets.newHashSet(TYPE1.accountType), Sets.newHashSet(types));
+
+ // 2 writable types
+ setAccountTypes(TYPE1, TYPE2EX);
+
+ types = mTarget.getWritableAccountTypeStrings();
+ MoreAsserts.assertEquals(Sets.newHashSet(TYPE1.accountType, TYPE2EX.accountType),
+ Sets.newHashSet(types));
+
+ // 3 writable types + 1 readonly type
+ setAccountTypes(TYPE1, TYPE2, TYPE2EX, TYPE3);
+
+ types = mTarget.getWritableAccountTypeStrings();
+ MoreAsserts.assertEquals(
+ Sets.newHashSet(TYPE1.accountType, TYPE2.accountType, TYPE2EX.accountType),
+ Sets.newHashSet(types));
+ }
+
+ /**
+ * Test for
+ * - {@link ContactEditorUtils#saveDefaultAndAllAccounts}
+ * - {@link ContactEditorUtils#getDefaultAccount}
+ * - {@link ContactEditorUtils#getSavedAccounts()}
+ */
+ public void testSaveDefaultAndAllAccounts() {
+ // Use these account types here.
+ setAccountTypes(TYPE1, TYPE2);
+
+ // If none has been saved, it should return an empty list.
+ assertEquals(0, mTarget.getSavedAccounts().size());
+
+ // Save 0 accounts.
+ mAccountTypes.mAccounts = new AccountWithDataSet[]{};
+ mTarget.saveDefaultAndAllAccounts(null);
+ assertNull(mTarget.getDefaultAccount());
+ MoreAsserts.assertEquals(
+ Sets.newHashSet(mAccountTypes.mAccounts),
+ toSet(mTarget.getSavedAccounts()));
+
+
+ // 1 account
+ mAccountTypes.mAccounts = new AccountWithDataSet[]{ACCOUNT_1_A};
+ mTarget.saveDefaultAndAllAccounts(ACCOUNT_1_A);
+ assertEquals(ACCOUNT_1_A, mTarget.getDefaultAccount());
+ MoreAsserts.assertEquals(
+ Sets.newHashSet(mAccountTypes.mAccounts),
+ toSet(mTarget.getSavedAccounts()));
+
+ // 2 account
+ mAccountTypes.mAccounts = new AccountWithDataSet[]{ACCOUNT_1_A, ACCOUNT_1_B};
+ mTarget.saveDefaultAndAllAccounts(ACCOUNT_1_B);
+ assertEquals(ACCOUNT_1_B, mTarget.getDefaultAccount());
+ MoreAsserts.assertEquals(
+ Sets.newHashSet(mAccountTypes.mAccounts),
+ toSet(mTarget.getSavedAccounts()));
+ }
+
+ public void testIsAccountValid() {
+ // Use these account types here.
+ setAccountTypes(TYPE1, TYPE2);
+
+ // 0 accounts
+ mAccountTypes.mAccounts = new AccountWithDataSet[]{};
+ assertFalse(mTarget.isValidAccount(ACCOUNT_1_A));
+ assertTrue(mTarget.isValidAccount(null)); // null is always valid
+
+ // 2 accounts
+ mAccountTypes.mAccounts = new AccountWithDataSet[]{ACCOUNT_1_A, ACCOUNT_2_A};
+ assertTrue(mTarget.isValidAccount(ACCOUNT_1_A));
+ assertTrue(mTarget.isValidAccount(ACCOUNT_2_A));
+ assertFalse(mTarget.isValidAccount(ACCOUNT_2EX_A));
+ assertTrue(mTarget.isValidAccount(null)); // null is always valid
+ }
+
+ /**
+ * Tests for {@link ContactEditorUtils#shouldShowAccountChangedNotification()}, starting with
+ * 0 accounts.
+ */
+ public void testShouldShowAccountChangedNotification_0Accounts() {
+ // There's always at least one writable type...
+ setAccountTypes(TYPE1);
+
+ // First launch -- always true.
+ assertTrue(mTarget.shouldShowAccountChangedNotification());
+
+ // We show the notification here, and user clicked "add account"
+ setAccounts(ACCOUNT_1_A);
+
+ // Now we open the contact editor with the new account.
+
+ // When closing the editor, we save the default account.
+ mTarget.saveDefaultAndAllAccounts(ACCOUNT_1_A);
+
+ // Next time the user creates a contact, we don't show the notification.
+ assertFalse(mTarget.shouldShowAccountChangedNotification());
+
+ // User added a new writable account, ACCOUNT_1_B.
+ setAccounts(ACCOUNT_1_A, ACCOUNT_1_B);
+
+ // Now we show the notification again.
+ assertTrue(mTarget.shouldShowAccountChangedNotification());
+
+ // User saved a new contact. We update the account list and the default account.
+ mTarget.saveDefaultAndAllAccounts(ACCOUNT_1_B);
+
+ // User created another contact. Now we don't show the notification.
+ assertFalse(mTarget.shouldShowAccountChangedNotification());
+
+ // User installed a new contact sync adapter...
+
+ // Added a new account type: TYPE2, and the TYPE2EX extension.
+ setAccountTypes(TYPE1, TYPE2, TYPE2EX);
+ // Add new accounts: ACCOUNT_2_A, ACCOUNT_2EX_A.
+ setAccounts(ACCOUNT_1_A, ACCOUNT_1_B, ACCOUNT_2_A, ACCOUNT_2EX_A);
+
+ // New account means another notification.
+ assertTrue(mTarget.shouldShowAccountChangedNotification());
+
+ // User saves a new contact, with a different default account.
+ mTarget.saveDefaultAndAllAccounts(ACCOUNT_2_A);
+
+ // Next time user creates a contact, no notification.
+ assertFalse(mTarget.shouldShowAccountChangedNotification());
+
+ // Remove ACCOUNT_2EX_A.
+ setAccountTypes(TYPE1, TYPE2, TYPE2EX);
+ setAccounts(ACCOUNT_1_A, ACCOUNT_1_B, ACCOUNT_2_A);
+
+ // ACCOUNT_2EX_A was not default, so no notification either.
+ assertFalse(mTarget.shouldShowAccountChangedNotification());
+
+ // Remove ACCOUNT_1_B, which is default.
+ setAccountTypes(TYPE1, TYPE2, TYPE2EX);
+ setAccounts(ACCOUNT_1_A, ACCOUNT_1_B);
+
+ // Now we show the notification.
+ assertTrue(mTarget.shouldShowAccountChangedNotification());
+ }
+
+ /**
+ * Tests for {@link ContactEditorUtils#shouldShowAccountChangedNotification()}, starting with
+ * 1 accounts.
+ */
+ public void testShouldShowAccountChangedNotification_1Account() {
+ setAccountTypes(TYPE1, TYPE2);
+ setAccounts(ACCOUNT_1_A);
+
+ // First launch -- always true.
+ assertTrue(mTarget.shouldShowAccountChangedNotification());
+
+ // User saves a new contact.
+ mTarget.saveDefaultAndAllAccounts(ACCOUNT_1_A);
+
+ // Next time, no notification.
+ assertFalse(mTarget.shouldShowAccountChangedNotification());
+
+ // The rest is the same...
+ }
+
+ /**
+ * Tests for {@link ContactEditorUtils#shouldShowAccountChangedNotification()}, starting with
+ * 0 accounts, and the user selected "local only".
+ */
+ public void testShouldShowAccountChangedNotification_0Account_localOnly() {
+ // There's always at least one writable type...
+ setAccountTypes(TYPE1);
+
+ // First launch -- always true.
+ assertTrue(mTarget.shouldShowAccountChangedNotification());
+
+ // We show the notification here, and user clicked "keep local" and saved an contact.
+ mTarget.saveDefaultAndAllAccounts(null);
+
+ // Now there are no accounts, and default account is null.
+
+ // The user created another contact, but this we shouldn't show the notification.
+ assertFalse(mTarget.shouldShowAccountChangedNotification());
+ }
+
+ private static <T> Set<T> toSet(Collection<T> collection) {
+ Set<T> ret = Sets.newHashSet();
+ ret.addAll(collection);
+ return ret;
+ }
+
+ private static class MockAccountType extends AccountType {
+ private boolean mAreContactsWritable;
+
+ public MockAccountType(String accountType, String dataSet, boolean areContactsWritable) {
+ this.accountType = accountType;
+ this.dataSet = dataSet;
+ mAreContactsWritable = areContactsWritable;
+ }
+
+ @Override
+ public boolean areContactsWritable() {
+ return mAreContactsWritable;
+ }
+
+ @Override
+ public int getHeaderColor(Context context) {
+ return 0;
+ }
+
+ @Override
+ public int getSideBarColor(Context context) {
+ return 0;
+ }
+
+ @Override
+ public boolean isGroupMembershipEditable() {
+ return true;
+ }
+ }
+}
diff --git a/tests/src/com/android/contacts/model/AccountWithDataSetTest.java b/tests/src/com/android/contacts/model/AccountWithDataSetTest.java
new file mode 100644
index 0000000..27c106e
--- /dev/null
+++ b/tests/src/com/android/contacts/model/AccountWithDataSetTest.java
@@ -0,0 +1,122 @@
+/*
+ * 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.collect.Lists;
+
+import android.os.Bundle;
+import android.test.AndroidTestCase;
+import android.test.MoreAsserts;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import java.util.List;
+
+/**
+ * Test case for {@link AccountWithDataSet}.
+ *
+ * adb shell am instrument -w -e class com.android.contacts.model.AccountWithDataSetTest \
+ com.android.contacts.tests/android.test.InstrumentationTestRunner
+ */
+@SmallTest
+public class AccountWithDataSetTest extends AndroidTestCase {
+ public void testStringifyAndUnstringify() {
+ AccountWithDataSet a1 = new AccountWithDataSet("name1", "typeA", null);
+ AccountWithDataSet a2 = new AccountWithDataSet("name2", "typeB", null);
+ AccountWithDataSet a3 = new AccountWithDataSet("name3", "typeB", "dataset");
+
+ // stringify() & unstringify
+ AccountWithDataSet a1r = AccountWithDataSet.unstringify(a1.stringify());
+ AccountWithDataSet a2r = AccountWithDataSet.unstringify(a2.stringify());
+ AccountWithDataSet a3r = AccountWithDataSet.unstringify(a3.stringify());
+
+ assertEquals(a1, a1r);
+ assertEquals(a2, a2r);
+ assertEquals(a3, a3r);
+
+ MoreAsserts.assertNotEqual(a1, a2r);
+ MoreAsserts.assertNotEqual(a1, a3r);
+
+ MoreAsserts.assertNotEqual(a2, a1r);
+ MoreAsserts.assertNotEqual(a2, a3r);
+
+ MoreAsserts.assertNotEqual(a3, a1r);
+ MoreAsserts.assertNotEqual(a3, a2r);
+ }
+
+ public void testStringifyListAndUnstringify() {
+ AccountWithDataSet a1 = new AccountWithDataSet("name1", "typeA", null);
+ AccountWithDataSet a2 = new AccountWithDataSet("name2", "typeB", null);
+ AccountWithDataSet a3 = new AccountWithDataSet("name3", "typeB", "dataset");
+
+ // Empty list
+ assertEquals(0, stringifyListAndUnstringify().size());
+
+ // 1 element
+ final List<AccountWithDataSet> listA = stringifyListAndUnstringify(a1);
+ assertEquals(1, listA.size());
+ assertEquals(a1, listA.get(0));
+
+ // 2 elements
+ final List<AccountWithDataSet> listB = stringifyListAndUnstringify(a2, a1);
+ assertEquals(2, listB.size());
+ assertEquals(a2, listB.get(0));
+ assertEquals(a1, listB.get(1));
+
+ // 3 elements
+ final List<AccountWithDataSet> listC = stringifyListAndUnstringify(a3, a2, a1);
+ assertEquals(3, listC.size());
+ assertEquals(a3, listC.get(0));
+ assertEquals(a2, listC.get(1));
+ assertEquals(a1, listC.get(2));
+ }
+
+ private static List<AccountWithDataSet> stringifyListAndUnstringify(
+ AccountWithDataSet... accounts) {
+
+ List<AccountWithDataSet> list = Lists.newArrayList(accounts);
+ return AccountWithDataSet.unstringifyList(AccountWithDataSet.stringifyList(list));
+ }
+
+ public void testParcelable() {
+ AccountWithDataSet a1 = new AccountWithDataSet("name1", "typeA", null);
+ AccountWithDataSet a2 = new AccountWithDataSet("name2", "typeB", null);
+ AccountWithDataSet a3 = new AccountWithDataSet("name3", "typeB", "dataset");
+
+ // Parcel them & unpercel.
+ final Bundle b = new Bundle();
+ b.putParcelable("a1", a1);
+ b.putParcelable("a2", a2);
+ b.putParcelable("a3", a3);
+
+ AccountWithDataSet a1r = b.getParcelable("a1");
+ AccountWithDataSet a2r = b.getParcelable("a2");
+ AccountWithDataSet a3r = b.getParcelable("a3");
+
+ assertEquals(a1, a1r);
+ assertEquals(a2, a2r);
+ assertEquals(a3, a3r);
+
+ MoreAsserts.assertNotEqual(a1, a2r);
+ MoreAsserts.assertNotEqual(a1, a3r);
+
+ MoreAsserts.assertNotEqual(a2, a1r);
+ MoreAsserts.assertNotEqual(a2, a3r);
+
+ MoreAsserts.assertNotEqual(a3, a1r);
+ MoreAsserts.assertNotEqual(a3, a2r);
+ }
+}
diff --git a/tests/src/com/android/contacts/tests/mocks/MockAccountTypeManager.java b/tests/src/com/android/contacts/tests/mocks/MockAccountTypeManager.java
index 7a04ae3..3b712c7 100644
--- a/tests/src/com/android/contacts/tests/mocks/MockAccountTypeManager.java
+++ b/tests/src/com/android/contacts/tests/mocks/MockAccountTypeManager.java
@@ -19,9 +19,11 @@
import com.android.contacts.model.AccountTypeManager;
import com.android.contacts.model.AccountTypeWithDataSet;
import com.android.contacts.model.AccountWithDataSet;
+import com.google.android.collect.Lists;
import com.google.android.collect.Maps;
import java.util.Arrays;
+import java.util.Collection;
import java.util.List;
import java.util.Map;
@@ -32,8 +34,8 @@
*/
public class MockAccountTypeManager extends AccountTypeManager {
- private final AccountType[] mTypes;
- private AccountWithDataSet[] mAccounts;
+ public AccountType[] mTypes;
+ public AccountWithDataSet[] mAccounts;
public MockAccountTypeManager(AccountType[] types, AccountWithDataSet[] accounts) {
this.mTypes = types;
@@ -60,4 +62,17 @@
public Map<AccountTypeWithDataSet, AccountType> getInvitableAccountTypes() {
return Maps.newHashMap(); // Always returns empty
}
+
+ @Override
+ public List<AccountType> getAccountTypes(boolean writableOnly) {
+ final List<AccountType> ret = Lists.newArrayList();
+ synchronized (this) {
+ for (AccountType type : mTypes) {
+ if (!writableOnly || type.areContactsWritable()) {
+ ret.add(type);
+ }
+ }
+ }
+ return ret;
+ }
}