Add device and SIM to AccountTypeManager
This makes these account types available throughout the app rather than
just the Nav drawer.
Test:
Added unit tests for new classes; run with:
$ adb shell am instrument -w \
com.google.android.tests/android.test.InstrumentationTestRunner
Manually on Nexus 6, LG G5 and Samsung S7 with device and SIM contacts
in CP2 by verifying "Device" and "SIM" options were available in
* nav drawer
* account list accessed by Pressing FAB without default account set
* editor account dropdown for new contact
* editor account label when editing existing contact
* picker for default account in settings
* settings customize screen account list
Bug 30867780
Change-Id: I329381ccc58d59f2e27f65a3d9dc0164fb20c971
diff --git a/proguard.flags b/proguard.flags
index d9dad57..d6e3755 100644
--- a/proguard.flags
+++ b/proguard.flags
@@ -19,20 +19,23 @@
-keep class com.android.contacts.common.database.NoNullCursorAsyncQueryHandler { *; }
-keep class com.android.contacts.common.format.FormatUtils { *; }
-keep class com.android.contacts.common.format.TextHighlighter { *; }
--keep class com.android.contacts.common.list.ContactListFilter { *; }
-keep class com.android.contacts.common.list.ContactListItemView { *; }
-keep class com.android.contacts.common.list.ContactsSectionIndexer { *; }
-keep class com.android.contacts.common.location.CountryDetector { *; }
+-keep class com.android.contacts.common.model.account.AccountDisplayInfo { *; }
+-keep class com.android.contacts.common.model.account.AccountDisplayInfoFactory { *; }
-keep class com.android.contacts.common.model.account.AccountType { *; }
-keep class com.android.contacts.common.model.account.AccountType$* { *; }
-keep class com.android.contacts.common.model.account.AccountTypeWithDataSet { *; }
-keep class com.android.contacts.common.model.account.AccountWithDataSet { *; }
-keep class com.android.contacts.common.model.account.BaseAccountType { *; }
-keep class com.android.contacts.common.model.account.BaseAccountType$* { *; }
+-keep class com.android.contacts.common.model.account.DeviceLocalAccountType { *; }
-keep class com.android.contacts.common.model.account.ExchangeAccountType { *; }
-keep class com.android.contacts.common.model.account.ExternalAccountType { *; }
-keep class com.android.contacts.common.model.account.FallbackAccountType { *; }
-keep class com.android.contacts.common.model.account.GoogleAccountType { *; }
+-keep class com.android.contacts.common.model.account.SimAccountType { *; }
-keep class com.android.contacts.common.model.AccountTypeManager { *; }
-keep class com.android.contacts.common.model.AccountTypeManagerImpl { *; }
-keep class com.android.contacts.common.model.BuilderWrapper { *; }
@@ -53,6 +56,7 @@
-keep class com.android.contacts.common.model.dataitem.StructuredNameDataItem { *; }
-keep class com.android.contacts.common.model.dataitem.StructuredPostalDataItem { *; }
-keep class com.android.contacts.common.model.dataitem.WebsiteDataItem { *; }
+-keep class com.android.contacts.common.model.DeviceLocalAccountLocator { *; }
-keep class com.android.contacts.common.model.RawContact { *; }
-keep class com.android.contacts.common.model.RawContactDelta { *; }
-keep class com.android.contacts.common.model.RawContactDeltaList { *; }
@@ -64,14 +68,11 @@
-keep class com.android.contacts.common.util.BitmapUtil { *; }
-keep class com.android.contacts.common.util.ContactDisplayUtils { *; }
-keep class com.android.contacts.common.util.DateUtils { *; }
+-keep class com.android.contacts.common.util.DeviceLocalAccountTypeFactory { *; }
+-keep class com.android.contacts.common.util.DeviceLocalAccountTypeFactory$* { *; }
-keep class com.android.contacts.common.util.NameConverter { *; }
-keep class com.android.contacts.common.util.SearchUtil { *; }
-keep class com.android.contacts.common.util.SearchUtil$* { *; }
--keep class com.android.contacts.common.util.DeviceAccountFilter { *; }
--keep class com.android.contacts.common.util.DeviceAccountFilter$* { *; }
--keep class com.android.contacts.common.util.DeviceAccountPresentationValues { *; }
--keep class com.android.contacts.common.util.DeviceAccountPresentationValues$* { *; }
--keep class com.android.contacts.common.util.DeviceLocalContactsFilterProvider { *; }
-keep class com.android.contacts.ContactsApplication { *; }
-keep class com.android.contacts.ContactSaveService { *; }
-keep class com.android.contacts.ContactSaveService$* { *; }
diff --git a/res/drawable/ic_sim_card_black_24dp.xml b/res/drawable/ic_sim_card_tinted_24dp.xml
similarity index 92%
rename from res/drawable/ic_sim_card_black_24dp.xml
rename to res/drawable/ic_sim_card_tinted_24dp.xml
index 40ee84f..9a20abf 100644
--- a/res/drawable/ic_sim_card_black_24dp.xml
+++ b/res/drawable/ic_sim_card_tinted_24dp.xml
@@ -19,7 +19,8 @@
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
- android:viewportHeight="24.0">
+ android:viewportHeight="24.0"
+ android:tint="@color/device_account_tint_color">
<path
android:fillColor="#FF000000"
android:pathData="M19.99,4c0,-1.1 -0.89,-2 -1.99,-2h-8L4,8v12c0,1.1 0.9,2 2,2h12.01c1.1,0 1.99,-0.9 1.99,-2l-0.01,-16zM9,19L7,19v-2h2v2zM17,19h-2v-2h2v2zM9,15L7,15v-4h2v4zM13,19h-2v-4h2v4zM13,13h-2v-2h2v2zM17,15h-2v-4h2v4z"/>
diff --git a/res/values/colors.xml b/res/values/colors.xml
index e8d1f54..771ba44 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -254,4 +254,7 @@
<!-- Primary text color in Contacts app -->
<color name="contacts_text_color">#333333</color>
+
+ <!-- tint color for device account icons -->
+ <color name="device_account_tint_color">#7f7f7f</color>
</resources>
diff --git a/src-bind/com/android/contactsbind/ObjectFactory.java b/src-bind/com/android/contactsbind/ObjectFactory.java
index f6dc0c7..ecdb967 100644
--- a/src-bind/com/android/contactsbind/ObjectFactory.java
+++ b/src-bind/com/android/contactsbind/ObjectFactory.java
@@ -15,8 +15,7 @@
import com.android.contacts.common.logging.Logger;
import com.android.contacts.common.preference.PreferenceManager;
-import com.android.contacts.common.util.DeviceAccountFilter;
-import com.android.contacts.common.util.DeviceAccountPresentationValues;
+import com.android.contacts.common.util.DeviceLocalAccountTypeFactory;
import android.content.Context;
@@ -31,11 +30,7 @@
public static PreferenceManager getPreferenceManager(Context context) { return null; }
- public static DeviceAccountPresentationValues createDeviceAccountPresentationValues(Context context) {
- return new DeviceAccountPresentationValues.Default(context);
- }
-
- public static DeviceAccountFilter getDeviceAccountFilter(Context context) {
- return DeviceAccountFilter.ONLY_NULL;
+ public static DeviceLocalAccountTypeFactory getDeviceLocalAccountTypeFactory(Context context) {
+ return new DeviceLocalAccountTypeFactory.Default(context);
}
}
diff --git a/src/com/android/contacts/ContactsDrawerActivity.java b/src/com/android/contacts/ContactsDrawerActivity.java
index f976807..34c95c6 100644
--- a/src/com/android/contacts/ContactsDrawerActivity.java
+++ b/src/com/android/contacts/ContactsDrawerActivity.java
@@ -54,12 +54,10 @@
import com.android.contacts.common.list.ContactListFilter;
import com.android.contacts.common.list.ContactListFilterController;
import com.android.contacts.common.model.AccountTypeManager;
-import com.android.contacts.common.model.account.AccountType;
import com.android.contacts.common.model.account.AccountWithDataSet;
import com.android.contacts.common.preference.ContactsPreferenceActivity;
import com.android.contacts.common.util.AccountFilterUtil;
import com.android.contacts.common.util.AccountsListAdapter.AccountListFilter;
-import com.android.contacts.common.util.DeviceAccountPresentationValues;
import com.android.contacts.common.util.ImplicitIntentsUtil;
import com.android.contacts.common.util.ViewUtil;
import com.android.contacts.editor.ContactEditorFragment;
@@ -72,10 +70,11 @@
import com.android.contacts.interactions.AccountFiltersFragment;
import com.android.contacts.interactions.AccountFiltersFragment.AccountFiltersListener;
import com.android.contacts.quickcontact.QuickContactActivity;
+import com.android.contacts.common.model.account.AccountDisplayInfo;
+import com.android.contacts.common.model.account.AccountDisplayInfoFactory;
import com.android.contacts.util.SharedPreferenceUtil;
import com.android.contactsbind.Assistants;
import com.android.contactsbind.HelpUtils;
-import com.android.contactsbind.ObjectFactory;
import java.util.HashMap;
import java.util.Iterator;
@@ -173,7 +172,6 @@
// The account the new group will be created under.
private AccountWithDataSet mNewGroupAccount;
- private DeviceAccountPresentationValues mDeviceAccountPresentationValues;
private int mPositionOfLastGroup;
@@ -186,8 +184,6 @@
super.setContentView(R.layout.contacts_drawer_activity);
- mDeviceAccountPresentationValues = ObjectFactory.createDeviceAccountPresentationValues(this);
-
// Set up the action bar.
mToolbar = getView(R.id.toolbar);
setSupportActionBar(mToolbar);
@@ -461,6 +457,9 @@
@Override
public void onFiltersLoaded(List<ContactListFilter> accountFilterItems) {
+ final AccountDisplayInfoFactory accountDisplayFactory = AccountDisplayInfoFactory.
+ fromListFilters(this, accountFilterItems);
+
final Menu menu = mNavigationView.getMenu();
final MenuItem filtersMenuItem = menu.findItem(R.id.nav_filters);
final SubMenu subMenu = filtersMenuItem.getSubMenu();
@@ -473,12 +472,12 @@
int positionOfLastFilter = mPositionOfLastGroup + GAP_BETWEEN_TWO_MENU_GROUPS;
- mDeviceAccountPresentationValues.setFilters(accountFilterItems);
-
for (int i = 0; i < accountFilterItems.size(); i++) {
positionOfLastFilter++;
final ContactListFilter filter = accountFilterItems.get(i);
- final CharSequence menuName = mDeviceAccountPresentationValues.getLabel(i);
+ final AccountDisplayInfo displayableAccount =
+ accountDisplayFactory.getAccountDisplayInfoFor(filter);
+ final CharSequence menuName = displayableAccount.getNameLabel();
final MenuItem menuItem = subMenu.add(R.id.nav_filters_items, Menu.NONE,
positionOfLastFilter, menuName);
mFilterMenuMap.put(filter, menuItem);
@@ -502,17 +501,15 @@
return true;
}
});
- menuItem.setIcon(mDeviceAccountPresentationValues.getIcon(i));
+ menuItem.setIcon(displayableAccount.getIcon());
// Get rid of the default menu item overlay and show original account icons.
menuItem.getIcon().setColorFilter(Color.TRANSPARENT, PorterDuff.Mode.SRC_ATOP);
// Create a dummy action view to attach extra hidden content description to the menuItem
// for Talkback. We want Talkback to read out the account type but not have it be part
// of the menuItem title.
- final AccountType account = AccountTypeManager.getInstance(this)
- .getAccountType(filter.accountType, filter.dataSet);
LinearLayout view = (LinearLayout) LayoutInflater.from(this)
.inflate(R.layout.account_type_info, null);
- view.setContentDescription(account.getDisplayLabel(this));
+ view.setContentDescription(displayableAccount.getTypeLabel());
view.setVisibility(View.VISIBLE);
menuItem.setActionView(view);
}
diff --git a/src/com/android/contacts/activities/ContactEditorAccountsChangedActivity.java b/src/com/android/contacts/activities/ContactEditorAccountsChangedActivity.java
index c4c0e27..9b211ab 100644
--- a/src/com/android/contacts/activities/ContactEditorAccountsChangedActivity.java
+++ b/src/com/android/contacts/activities/ContactEditorAccountsChangedActivity.java
@@ -106,7 +106,7 @@
AccountListFilter.ACCOUNTS_CONTACT_WRITABLE);
accountListView.setAdapter(mAccountListAdapter);
accountListView.setOnItemClickListener(mAccountListItemClickListener);
- } else if (numAccounts == 1) {
+ } else if (numAccounts == 1 && !accounts.get(0).isLocalAccount()) {
// If the user has 1 writable account we will just show the user a message with 2
// possible action buttons.
view = View.inflate(this,
diff --git a/src/com/android/contacts/activities/ContactEditorBaseActivity.java b/src/com/android/contacts/activities/ContactEditorBaseActivity.java
index 8a8ce1d..55fcddb 100644
--- a/src/com/android/contacts/activities/ContactEditorBaseActivity.java
+++ b/src/com/android/contacts/activities/ContactEditorBaseActivity.java
@@ -71,6 +71,16 @@
// 3 used for ContactDeletionInteraction.RESULT_CODE_DELETED
public static final int RESULT_CODE_EDITED = 4;
+ /**
+ * The contact will be saved to the device local account when this is set for an insert. This
+ * is necessary because {@link android.accounts.Account} cannot be created with null values
+ * for the name and type and an Account is needed for
+ * {@link android.provider.ContactsContract.Intents.Insert#EXTRA_ACCOUNT}
+ */
+ public static final String EXTRA_SAVE_TO_DEVICE_FLAG =
+ "com.android.contacts.SAVE_TO_DEVICE_FLAG";
+
+
protected int mActionBarTitleResId;
/**
diff --git a/src/com/android/contacts/activities/PeopleActivity.java b/src/com/android/contacts/activities/PeopleActivity.java
index 492cd8b..7358fb7 100644
--- a/src/com/android/contacts/activities/PeopleActivity.java
+++ b/src/com/android/contacts/activities/PeopleActivity.java
@@ -37,7 +37,6 @@
import android.provider.ContactsContract.ProviderStatus;
import android.provider.ContactsContract.QuickContact;
import android.support.design.widget.CoordinatorLayout;
-import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.Snackbar;
import android.support.v13.app.FragmentPagerAdapter;
import android.support.v4.content.LocalBroadcastManager;
@@ -59,6 +58,8 @@
import android.widget.ImageButton;
import android.widget.Toast;
+import com.android.contacts.common.model.account.AccountDisplayInfo;
+import com.android.contacts.common.model.account.AccountDisplayInfoFactory;
import com.android.contacts.ContactSaveService;
import com.android.contacts.ContactsDrawerActivity;
import com.android.contacts.R;
@@ -78,7 +79,6 @@
import com.android.contacts.common.logging.ScreenEvent.ScreenType;
import com.android.contacts.common.model.AccountTypeManager;
import com.android.contacts.common.model.account.AccountWithDataSet;
-import com.android.contacts.common.model.account.GoogleAccountType;
import com.android.contacts.common.util.Constants;
import com.android.contacts.common.util.ImplicitIntentsUtil;
import com.android.contacts.common.widget.FloatingActionButtonController;
@@ -1441,19 +1441,24 @@
public void onFabClicked() {
final Intent intent = new Intent(Intent.ACTION_INSERT, Contacts.CONTENT_URI);
- final Bundle extras = getIntent().getExtras();
- if (extras != null) {
- final ContactListFilter filter = mContactListFilterController.getFilter();
- // If we are in account view, we pass the account explicitly in order to
- // create contact in the account. This will prevent the default account dialog
- // from being displayed.
- if (!isAllContactsFilter(filter) && !isDeviceContactsFilter(filter)) {
- final Account account = new Account(filter.accountName, filter.accountType);
- extras.putParcelable(Intents.Insert.EXTRA_ACCOUNT, account);
- extras.putString(Intents.Insert.EXTRA_DATA_SET, filter.dataSet);
- }
- intent.putExtras(extras);
+ // Copy our extras into the new intent.
+ intent.putExtras(getIntent());
+
+ final ContactListFilter filter = mContactListFilterController.getFilter();
+ // If we are in account view, we pass the account explicitly in order to
+ // create contact in the account. This will prevent the default account dialog
+ // from being displayed.
+ if (!isAllContactsFilter(filter) && filter.accountName != null &&
+ filter.accountType != null) {
+ final Account account = new Account(filter.accountName, filter.accountType);
+ intent.putExtra(Intents.Insert.EXTRA_ACCOUNT, account);
+ intent.putExtra(Intents.Insert.EXTRA_DATA_SET, filter.dataSet);
+ } else if (isDeviceContactsFilter(filter)) {
+ // It's OK to add this even though it's an implicit intent. If a different app
+ // receives the intent it should just ignore the flag.
+ intent.putExtra(CompactContactEditorActivity.EXTRA_SAVE_TO_DEVICE_FLAG, true);
}
+
try {
ImplicitIntentsUtil.startActivityInApp(PeopleActivity.this, intent);
} catch (ActivityNotFoundException ex) {
@@ -1502,10 +1507,15 @@
}
private String getActionBarTitleForAccount(ContactListFilter filter) {
- if (GoogleAccountType.ACCOUNT_TYPE.equals(filter.accountType)) {
+ final AccountDisplayInfoFactory factory =
+ AccountDisplayInfoFactory.forWritableAccounts(this);
+ final AccountDisplayInfo displayableAccount = factory.getAccountDisplayInfoFor(filter);
+ if (displayableAccount.hasGoogleAccountType()) {
return getString(R.string.title_from_google);
+ } else {
+ return displayableAccount.withFormattedName(this, R.string.title_from_other_accounts)
+ .getNameLabel().toString();
}
- return getString(R.string.title_from_other_accounts, filter.accountName);
}
// Persist filter only when it's of the type FILTER_TYPE_ALL_ACCOUNTS.
diff --git a/src/com/android/contacts/common/list/ContactListFilter.java b/src/com/android/contacts/common/list/ContactListFilter.java
index e99c374..2bae617 100644
--- a/src/com/android/contacts/common/list/ContactListFilter.java
+++ b/src/com/android/contacts/common/list/ContactListFilter.java
@@ -25,6 +25,7 @@
import android.text.TextUtils;
import com.android.contacts.common.logging.ListEvent;
+import com.android.contacts.common.model.account.AccountWithDataSet;
/**
* Contact list filter parameters.
@@ -330,6 +331,17 @@
return uriBuilder;
}
+ public AccountWithDataSet toAccountWithDataSet() {
+ if (filterType == FILTER_TYPE_ACCOUNT) {
+ return new AccountWithDataSet(accountName, accountType, dataSet);
+ } else if (filterType == FILTER_TYPE_DEVICE_CONTACTS) {
+ return AccountWithDataSet.getLocalAccount();
+ } else {
+ throw new IllegalStateException("Cannot create Account from filter type " +
+ filterTypeToString(filterType));
+ }
+ }
+
public String toDebugString() {
final StringBuilder builder = new StringBuilder();
builder.append("[filter type: " + filterType + " (" + filterTypeToString(filterType) + ")");
diff --git a/src/com/android/contacts/common/list/CustomContactListFilterActivity.java b/src/com/android/contacts/common/list/CustomContactListFilterActivity.java
index 74e8f84..4de57bb 100644
--- a/src/com/android/contacts/common/list/CustomContactListFilterActivity.java
+++ b/src/com/android/contacts/common/list/CustomContactListFilterActivity.java
@@ -56,6 +56,8 @@
import android.widget.ExpandableListView.ExpandableListContextMenuInfo;
import android.widget.TextView;
+import com.android.contacts.common.model.account.AccountDisplayInfo;
+import com.android.contacts.common.model.account.AccountDisplayInfoFactory;
import com.android.contacts.common.R;
import com.android.contacts.common.model.AccountTypeManager;
import com.android.contacts.common.model.ValuesDelta;
@@ -71,6 +73,7 @@
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
+import java.util.List;
/**
* Shows a list of all available {@link Groups} available, letting the user
@@ -139,7 +142,11 @@
final ContentResolver resolver = context.getContentResolver();
final AccountSet accounts = new AccountSet();
- for (AccountWithDataSet account : accountTypes.getAccounts(false)) {
+
+ final List<AccountWithDataSet> sourceAccounts = accountTypes.getAccounts(false);
+ final AccountDisplayInfoFactory displayableAccountFactory =
+ new AccountDisplayInfoFactory(context, sourceAccounts);
+ for (AccountWithDataSet account : sourceAccounts) {
final AccountType accountType = accountTypes.getAccountTypeForAccount(account);
if (accountType.isExtension() && !account.hasData(context)) {
// Extension with no data -- skip.
@@ -147,7 +154,8 @@
}
AccountDisplay accountDisplay =
- new AccountDisplay(resolver, account.name, account.type, account.dataSet);
+ new AccountDisplay(resolver, account.name, account.type, account.dataSet,
+ displayableAccountFactory.getAccountDisplayInfo(account));
final Uri.Builder groupsUri = Groups.CONTENT_URI.buildUpon()
.appendQueryParameter(Groups.ACCOUNT_NAME, account.name)
@@ -466,20 +474,30 @@
public final String mName;
public final String mType;
public final String mDataSet;
+ public final AccountDisplayInfo mAccountDisplayInfo;
public GroupDelta mUngrouped;
public ArrayList<GroupDelta> mSyncedGroups = Lists.newArrayList();
public ArrayList<GroupDelta> mUnsyncedGroups = Lists.newArrayList();
+ public GroupDelta getGroup(int position) {
+ if (position < mSyncedGroups.size()) {
+ return mSyncedGroups.get(position);
+ }
+ position -= mSyncedGroups.size();
+ return mUnsyncedGroups.get(position);
+ }
+
/**
* Build an {@link AccountDisplay} covering all {@link Groups} under the
* given {@link AccountWithDataSet}.
*/
public AccountDisplay(ContentResolver resolver, String accountName, String accountType,
- String dataSet) {
+ String dataSet, AccountDisplayInfo displayableInfo) {
mName = accountName;
mType = accountType;
mDataSet = dataSet;
+ mAccountDisplayInfo = displayableInfo;
}
/**
@@ -593,12 +611,11 @@
final AccountDisplay account = (AccountDisplay)this.getGroup(groupPosition);
- final AccountType accountType = mAccountTypes.getAccountType(
- account.mType, account.mDataSet);
-
- text1.setText(account.mName);
- text1.setVisibility(account.mName == null ? View.GONE : View.VISIBLE);
- text2.setText(accountType.getDisplayLabel(mContext));
+ text1.setText(account.mAccountDisplayInfo.getNameLabel());
+ text1.setVisibility(!account.mAccountDisplayInfo.isDeviceAccount()
+ || account.mAccountDisplayInfo.hasDistinctName()
+ ? View.VISIBLE : View.GONE);
+ text2.setText(account.mAccountDisplayInfo.getTypeLabel());
final int textColor = mContext.getResources().getColor(isExpanded
? R.color.dialtacts_theme_color
@@ -650,9 +667,10 @@
public Object getChild(int groupPosition, int childPosition) {
final AccountDisplay account = mAccounts.get(groupPosition);
final boolean validChild = childPosition >= 0
- && childPosition < account.mSyncedGroups.size();
+ && childPosition < account.mSyncedGroups.size()
+ + account.mUnsyncedGroups.size();
if (validChild) {
- return account.mSyncedGroups.get(childPosition);
+ return account.getGroup(childPosition);
} else {
return null;
}
@@ -673,8 +691,7 @@
public int getChildrenCount(int groupPosition) {
// Count is any synced groups, plus possible footer
final AccountDisplay account = mAccounts.get(groupPosition);
- final boolean anyHidden = account.mUnsyncedGroups.size() > 0;
- return account.mSyncedGroups.size() + (anyHidden ? 1 : 0);
+ return account.mSyncedGroups.size() + account.mUnsyncedGroups.size();
}
@Override
diff --git a/src/com/android/contacts/common/model/AccountTypeManager.java b/src/com/android/contacts/common/model/AccountTypeManager.java
index 35a7a3a..c9f68d4 100644
--- a/src/com/android/contacts/common/model/AccountTypeManager.java
+++ b/src/com/android/contacts/common/model/AccountTypeManager.java
@@ -29,6 +29,7 @@
import android.content.SyncStatusObserver;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
+import android.database.ContentObserver;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Handler;
@@ -37,6 +38,7 @@
import android.os.Message;
import android.os.SystemClock;
import android.provider.ContactsContract;
+import android.support.v4.content.ContextCompat;
import android.text.TextUtils;
import android.util.Log;
import android.util.TimingLogger;
@@ -53,7 +55,7 @@
import com.android.contacts.common.model.account.SamsungAccountType;
import com.android.contacts.common.model.dataitem.DataKind;
import com.android.contacts.common.util.Constants;
-import com.android.contacts.common.util.DeviceAccountFilter;
+import com.android.contacts.common.util.DeviceLocalAccountTypeFactory;
import com.android.contactsbind.ObjectFactory;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Objects;
@@ -65,13 +67,14 @@
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
-import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;
+import static com.android.contacts.common.util.DeviceLocalAccountTypeFactory.Util.isLocalAccountType;
+
/**
* Singleton holder for all parsed {@link AccountType} available on the
* system, typically filled through {@link PackageManager} queries.
@@ -87,11 +90,16 @@
* the available authenticators. This method can safely be called from the UI thread.
*/
public static AccountTypeManager getInstance(Context context) {
+ if (!hasRequiredPermissions(context)) {
+ // Hopefully any component that depends on the values returned by this class
+ // will be restarted if the permissions change.
+ return EMPTY;
+ }
synchronized (mInitializationLock) {
if (mAccountTypeManager == null) {
context = context.getApplicationContext();
mAccountTypeManager = new AccountTypeManagerImpl(context,
- ObjectFactory.getDeviceAccountFilter(context));
+ ObjectFactory.getDeviceLocalAccountTypeFactory(context));
}
}
return mAccountTypeManager;
@@ -110,6 +118,37 @@
}
}
+ private static final AccountTypeManager EMPTY = new AccountTypeManager() {
+ @Override
+ public List<AccountWithDataSet> getAccounts(boolean contactWritableOnly) {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public void sortAccounts(AccountWithDataSet defaultAccount) {
+ }
+
+ @Override
+ public List<AccountWithDataSet> getGroupWritableAccounts() {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public AccountType getAccountType(AccountTypeWithDataSet accountTypeWithDataSet) {
+ return null;
+ }
+
+ @Override
+ public Map<AccountTypeWithDataSet, AccountType> getUsableInvitableAccountTypes() {
+ return null;
+ }
+
+ @Override
+ public List<AccountType> getAccountTypes(boolean contactWritableOnly) {
+ return Collections.emptyList();
+ }
+ };
+
/**
* Returns the list of all accounts (if contactWritableOnly is false) or just the list of
* contact writable accounts (if contactWritableOnly is true).
@@ -183,6 +222,15 @@
}
return false;
}
+
+ private static boolean hasRequiredPermissions(Context context) {
+ final boolean canGetAccounts = ContextCompat.checkSelfPermission(context,
+ android.Manifest.permission.GET_ACCOUNTS) == PackageManager.PERMISSION_GRANTED;
+ final boolean canReadContacts = ContextCompat.checkSelfPermission(context,
+ android.Manifest.permission.READ_CONTACTS) == PackageManager.PERMISSION_GRANTED;
+ return canGetAccounts && canReadContacts;
+ }
+
}
class AccountComparator implements Comparator<AccountWithDataSet> {
@@ -251,7 +299,7 @@
private Context mContext;
private AccountManager mAccountManager;
- private DeviceAccountFilter mDeviceAccountFilter;
+ private DeviceLocalAccountTypeFactory mDeviceLocalAccountTypeFactory;
private AccountType mFallbackAccountType;
@@ -306,10 +354,11 @@
/**
* Internal constructor that only performs initial parsing.
*/
- public AccountTypeManagerImpl(Context context, DeviceAccountFilter deviceAccountFilter) {
+ public AccountTypeManagerImpl(Context context,
+ DeviceLocalAccountTypeFactory deviceLocalAccountTypeFactory) {
mContext = context;
mFallbackAccountType = new FallbackAccountType(context);
- mDeviceAccountFilter = deviceAccountFilter;
+ mDeviceLocalAccountTypeFactory = deviceLocalAccountTypeFactory;
mAccountManager = AccountManager.get(mContext);
@@ -351,6 +400,27 @@
ContentResolver.addStatusChangeListener(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS, this);
+ // Observe changes to RAW_CONTACTS so that we will update the list of "Device" accounts
+ // if a new device contact is added.
+ mContext.getContentResolver().registerContentObserver(
+ ContactsContract.RawContacts.CONTENT_URI, /* notifyDescendents */ true,
+ new ContentObserver(mListenerHandler) {
+ @Override
+ public boolean deliverSelfNotifications() {
+ return true;
+ }
+
+ @Override
+ public void onChange(boolean selfChange) {
+ mListenerHandler.sendEmptyMessage(MESSAGE_LOAD_DATA);
+ }
+
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ mListenerHandler.sendEmptyMessage(MESSAGE_LOAD_DATA);
+ }
+ });
+
mListenerHandler.sendEmptyMessage(MESSAGE_LOAD_DATA);
}
@@ -378,6 +448,7 @@
if (latch == null) {
return;
}
+
while (true) {
try {
latch.await();
@@ -443,8 +514,11 @@
} else if (SamsungAccountType.isSamsungAccountType(mContext, type,
auth.packageName)) {
accountType = new SamsungAccountType(mContext, auth.packageName, type);
- } else if (mDeviceAccountFilter.isDeviceAccountType(type)) {
- accountType = new FallbackAccountType(mContext);
+ } else if (!ExternalAccountType.hasContactsXml(mContext, auth.packageName)
+ && isLocalAccountType(mDeviceLocalAccountTypeFactory, type)) {
+ // This will be loaded by the DeviceLocalAccountLocator so don't try to create an
+ // ExternalAccountType for it.
+ continue;
} else {
Log.d(TAG, "Registering external account type=" + type
+ ", packageName=" + auth.packageName);
@@ -460,13 +534,7 @@
}
}
- // TODO: this is a hack. For FallbackAccountType we want to use a default icon and
- // label instead of what is pulled out of the authenticator
- if (!(accountType instanceof FallbackAccountType)) {
- accountType.accountType = auth.type;
- accountType.titleRes = auth.labelId;
- accountType.iconRes = auth.iconId;
- }
+ accountType.initializeFieldsFromAuthenticator(auth);
addAccountType(accountType, accountTypesByTypeAndDataSet, accountTypesByType);
@@ -505,6 +573,7 @@
}
timings.addSplit("Loaded account types");
+ boolean foundWritableGoogleAccount = false;
// Map in accounts to associate the account names with each account type entry.
Account[] accounts = mAccountManager.getAccounts();
for (Account account : accounts) {
@@ -522,6 +591,10 @@
allAccounts.add(accountWithDataSet);
if (accountType.areContactsWritable()) {
contactWritableAccounts.add(accountWithDataSet);
+ if (GoogleAccountType.ACCOUNT_TYPE.equals(account.type)
+ && accountWithDataSet.dataSet == null) {
+ foundWritableGoogleAccount = true;
+ }
}
if (accountType.isGroupMembershipEditable()) {
groupWritableAccounts.add(accountWithDataSet);
@@ -531,6 +604,40 @@
}
}
+ final DeviceLocalAccountLocator deviceAccounts =
+ new DeviceLocalAccountLocator(mContext.getContentResolver(),
+ mDeviceLocalAccountTypeFactory,
+ allAccounts);
+ final List<AccountWithDataSet> localAccounts = deviceAccounts.getDeviceLocalAccounts();
+ allAccounts.addAll(localAccounts);
+
+ for (AccountWithDataSet localAccount : localAccounts) {
+ // Prefer a known type if it exists. This covers the case that a local account has an
+ // authenticator with a valid contacts.xml
+ AccountType localAccountType = accountTypesByTypeAndDataSet.get(
+ localAccount.getAccountTypeWithDataSet());
+ if (localAccountType == null) {
+ localAccountType = mDeviceLocalAccountTypeFactory.getAccountType(localAccount.type);
+ }
+ accountTypesByTypeAndDataSet.put(localAccount.getAccountTypeWithDataSet(),
+ localAccountType);
+
+ // Skip the null account if there is a Google account available. This is done because
+ // the Google account's sync adapter will automatically move accounts in the "null"
+ // account. Hence, it would be confusing to still show it as an available writable
+ // account since contacts that were saved to it would magically change accounts when the
+ // sync adapter runs.
+ if (foundWritableGoogleAccount && localAccount.type == null) {
+ continue;
+ }
+ if (localAccountType.areContactsWritable()) {
+ contactWritableAccounts.add(localAccount);
+ }
+ if (localAccountType.isGroupMembershipEditable()) {
+ groupWritableAccounts.add(localAccount);
+ }
+ }
+
final AccountComparator accountComparator = new AccountComparator(null);
Collections.sort(allAccounts, accountComparator);
Collections.sort(contactWritableAccounts, accountComparator);
diff --git a/src/com/android/contacts/common/model/DeviceLocalAccountLocator.java b/src/com/android/contacts/common/model/DeviceLocalAccountLocator.java
new file mode 100644
index 0000000..8997ed4
--- /dev/null
+++ b/src/com/android/contacts/common/model/DeviceLocalAccountLocator.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2016 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.common.model;
+
+import android.accounts.AccountManager;
+import android.content.ContentResolver;
+import android.database.Cursor;
+import android.provider.ContactsContract;
+import android.support.annotation.VisibleForTesting;
+
+import com.android.contacts.common.model.account.AccountType;
+import com.android.contacts.common.model.account.AccountWithDataSet;
+import com.android.contacts.common.util.DeviceLocalAccountTypeFactory;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * DeviceLocalAccountLocator attempts to create accounts for "Device" contacts by querying
+ * CP2 for records with {@link android.provider.ContactsContract.RawContacts#ACCOUNT_TYPE} columns
+ * that do not exist for any account returned by {@link AccountManager#getAccounts()}
+ *
+ * This class should be used from a background thread since it does DB queries
+ */
+public class DeviceLocalAccountLocator {
+
+ @VisibleForTesting
+ static String[] PROJECTION = new String[] {
+ ContactsContract.RawContacts.ACCOUNT_NAME, ContactsContract.RawContacts.ACCOUNT_TYPE,
+ ContactsContract.RawContacts.DATA_SET
+ };
+
+ private static final int COL_NAME = 0;
+ private static final int COL_TYPE = 1;
+ private static final int COL_DATA_SET = 2;
+
+
+ private final ContentResolver mResolver;
+ private final DeviceLocalAccountTypeFactory mAccountTypeFactory;
+ private final Set<String> mKnownAccountTypes;
+
+
+ public DeviceLocalAccountLocator(ContentResolver contentResolver,
+ DeviceLocalAccountTypeFactory factory,
+ List<AccountWithDataSet> knownAccounts) {
+ mResolver = contentResolver;
+ mAccountTypeFactory = factory;
+ mKnownAccountTypes = new HashSet<>();
+ for (AccountWithDataSet account : knownAccounts) {
+ mKnownAccountTypes.add(account.type);
+ }
+ }
+
+ public List<AccountWithDataSet> getDeviceLocalAccounts() {
+ final String[] selectionArgs = getSelectionArgs();
+ final Cursor cursor = mResolver.query(ContactsContract.RawContacts.CONTENT_URI, PROJECTION,
+ getSelection(), selectionArgs, null);
+
+ final Set<AccountWithDataSet> localAccounts = new HashSet<>();
+ try {
+ while (cursor.moveToNext()) {
+ final String name = cursor.getString(COL_NAME);
+ final String type = cursor.getString(COL_TYPE);
+ final String dataSet = cursor.getString(COL_DATA_SET);
+
+ if (DeviceLocalAccountTypeFactory.Util.isLocalAccountType(
+ mAccountTypeFactory, type)) {
+ localAccounts.add(new AccountWithDataSet(name, type, dataSet));
+ }
+ }
+ } finally {
+ cursor.close();
+ }
+
+ return new ArrayList<>(localAccounts);
+ }
+
+ @VisibleForTesting
+ public String getSelection() {
+ final StringBuilder sb = new StringBuilder();
+ sb.append(ContactsContract.RawContacts.DELETED).append(" =0 AND (")
+ .append(ContactsContract.RawContacts.ACCOUNT_TYPE).append(" IS NULL");
+ if (mKnownAccountTypes.isEmpty()) {
+ return sb.append(')').toString();
+ }
+ sb.append(" OR ").append(ContactsContract.RawContacts.ACCOUNT_TYPE).append(" NOT IN (");
+ for (String ignored : mKnownAccountTypes) {
+ sb.append("?,");
+ }
+ // Remove trailing ','
+ sb.deleteCharAt(sb.length() - 1).append(')').append(')');
+
+ return sb.toString();
+ }
+
+ @VisibleForTesting
+ public String[] getSelectionArgs() {
+ return mKnownAccountTypes.toArray(new String[mKnownAccountTypes.size()]);
+ }
+}
diff --git a/src/com/android/contacts/common/model/account/AccountDisplayInfo.java b/src/com/android/contacts/common/model/account/AccountDisplayInfo.java
new file mode 100644
index 0000000..f68fdd5
--- /dev/null
+++ b/src/com/android/contacts/common/model/account/AccountDisplayInfo.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2016 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.common.model.account;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.support.annotation.StringRes;
+import android.text.TextUtils;
+
+/**
+ * Wrapper around AccountWithDataSet that contains user-friendly labels and an icon.
+ *
+ * The raw values for name and type in AccountWithDataSet are not always (or even usually)
+ * appropriate for direct display to the user.
+ */
+public class AccountDisplayInfo {
+ private final AccountWithDataSet mSource;
+
+ private final CharSequence mName;
+ private final CharSequence mType;
+ private final Drawable mIcon;
+
+ private final boolean mIsDeviceAccount;
+
+ public AccountDisplayInfo(AccountWithDataSet account, CharSequence name, CharSequence type,
+ Drawable icon, boolean isDeviceAccount) {
+ mSource = account;
+ mName = name;
+ mType = type;
+ mIcon = icon;
+ mIsDeviceAccount = isDeviceAccount;
+ }
+
+ public AccountWithDataSet getSource() {
+ return mSource;
+ }
+
+ public CharSequence getNameLabel() {
+ return mName;
+ }
+
+ public CharSequence getTypeLabel() {
+ return mType;
+ }
+
+ public Drawable getIcon() {
+ return mIcon;
+ }
+
+ public boolean hasGoogleAccountType() {
+ return GoogleAccountType.ACCOUNT_TYPE.equals(mSource.type);
+ }
+
+ public boolean isGoogleAccount() {
+ return GoogleAccountType.ACCOUNT_TYPE.equals(mSource.type) && mSource.dataSet == null;
+ }
+
+ public boolean isDeviceAccount() {
+ return mIsDeviceAccount;
+ }
+
+ public boolean hasDistinctName() {
+ return !TextUtils.equals(mName, mType);
+ }
+
+ public AccountDisplayInfo withName(CharSequence name) {
+ return withNameAndType(name, mType);
+ }
+
+ public AccountDisplayInfo withType(CharSequence type) {
+ return withNameAndType(mName, type);
+ }
+
+ public AccountDisplayInfo withNameAndType(CharSequence name, CharSequence type) {
+ return new AccountDisplayInfo(mSource, name, type, mIcon, mIsDeviceAccount);
+ }
+
+ public AccountDisplayInfo formatted(Context context, @StringRes int nameFormat,
+ @StringRes int typeFormat) {
+ return new AccountDisplayInfo(mSource, context.getString(nameFormat, mName),
+ context.getString(typeFormat, mType), mIcon, mIsDeviceAccount);
+ }
+
+ public AccountDisplayInfo withFormattedName(Context context, @StringRes int nameFormat) {
+ return withName(context.getString(nameFormat, mName));
+ }
+}
diff --git a/src/com/android/contacts/common/model/account/AccountDisplayInfoFactory.java b/src/com/android/contacts/common/model/account/AccountDisplayInfoFactory.java
new file mode 100644
index 0000000..aad1689
--- /dev/null
+++ b/src/com/android/contacts/common/model/account/AccountDisplayInfoFactory.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2016 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.common.model.account;
+
+import android.content.Context;
+
+import com.android.contacts.common.list.ContactListFilter;
+import com.android.contacts.common.model.AccountTypeManager;
+import com.android.contacts.common.model.RawContactDelta;
+import com.android.contacts.common.util.DeviceLocalAccountTypeFactory;
+import com.android.contactsbind.ObjectFactory;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Provides methods to get AccountDisplayInfo instances for available accounts.
+ *
+ * For most accounts the account name will be used for the label but device accounts and
+ * SIM accounts have friendly names associated with them unless there is more than one of these
+ * types of accounts present in the list.
+ */
+public class AccountDisplayInfoFactory {
+
+ private final Context mContext;
+ private final AccountTypeManager mAccountTypeManager;
+
+ private final DeviceLocalAccountTypeFactory mDeviceAccountTypeFactory;
+
+ private final int mDeviceAccountCount;
+ private final int mSimAccountCount;
+
+ public AccountDisplayInfoFactory(Context context, List<AccountWithDataSet> accounts) {
+ this(context, AccountTypeManager.getInstance(context),
+ ObjectFactory.getDeviceLocalAccountTypeFactory(context), accounts);
+ }
+
+ public AccountDisplayInfoFactory(Context context, AccountTypeManager accountTypeManager,
+ DeviceLocalAccountTypeFactory deviceAccountTypeFactory,
+ List<AccountWithDataSet> accounts) {
+ mContext = context;
+ mAccountTypeManager = accountTypeManager;
+ mDeviceAccountTypeFactory = deviceAccountTypeFactory;
+
+ mSimAccountCount = countOfType(DeviceLocalAccountTypeFactory.TYPE_SIM, accounts);
+ mDeviceAccountCount = countOfType(DeviceLocalAccountTypeFactory.TYPE_DEVICE, accounts);
+ }
+
+ public AccountDisplayInfo getAccountDisplayInfo(AccountWithDataSet account) {
+ final AccountType type = mAccountTypeManager.getAccountTypeForAccount(account);
+ final CharSequence name = shouldUseTypeLabelForName(account)
+ ? type.getDisplayLabel(mContext)
+ : account.name;
+ return new AccountDisplayInfo(account, name, type.getDisplayLabel(mContext),
+ type.getDisplayIcon(mContext),
+ DeviceLocalAccountTypeFactory.Util.isLocalAccountType(mDeviceAccountTypeFactory,
+ type.accountType));
+ }
+
+ public AccountDisplayInfo getAccountDisplayInfoFor(ContactListFilter filter) {
+ return getAccountDisplayInfo(filter.toAccountWithDataSet());
+ }
+
+ public AccountDisplayInfo getAccountDisplayInfoFor(RawContactDelta delta) {
+ final AccountWithDataSet account = new AccountWithDataSet(delta.getAccountName(),
+ delta.getAccountType(), delta.getDataSet());
+ return getAccountDisplayInfo(account);
+ }
+
+ public static AccountDisplayInfoFactory fromListFilters(Context context,
+ List<ContactListFilter> filters) {
+ final List<AccountWithDataSet> accounts = new ArrayList<>(filters.size());
+ for (ContactListFilter filter : filters) {
+ accounts.add(filter.toAccountWithDataSet());
+ }
+ return new AccountDisplayInfoFactory(context, accounts);
+ }
+
+ public static AccountDisplayInfoFactory forAllAccounts(Context context) {
+ final AccountTypeManager accountTypeManager = AccountTypeManager.getInstance(context);
+ final List<AccountWithDataSet> accounts = accountTypeManager.getAccounts(false);
+ return new AccountDisplayInfoFactory(context, accounts);
+ }
+
+ public static AccountDisplayInfoFactory forWritableAccounts(Context context) {
+ final AccountTypeManager accountTypeManager = AccountTypeManager.getInstance(context);
+ final List<AccountWithDataSet> accounts = accountTypeManager.getAccounts(true);
+ return new AccountDisplayInfoFactory(context, accounts);
+ }
+
+ private boolean shouldUseTypeLabelForName(AccountWithDataSet account) {
+ final int type = mDeviceAccountTypeFactory.classifyAccount(account.type);
+ return (type == DeviceLocalAccountTypeFactory.TYPE_SIM && mSimAccountCount == 1)
+ || (type == DeviceLocalAccountTypeFactory.TYPE_DEVICE && mDeviceAccountCount == 1)
+ || account.name == null;
+
+ }
+
+ private int countOfType(@DeviceLocalAccountTypeFactory.LocalAccountType int type,
+ List<AccountWithDataSet> accounts) {
+ int count = 0;
+ for (AccountWithDataSet account : accounts) {
+ if (mDeviceAccountTypeFactory.classifyAccount(account.type) == type) {
+ count++;
+ }
+ }
+ return count;
+ }
+}
diff --git a/src/com/android/contacts/common/model/account/AccountType.java b/src/com/android/contacts/common/model/account/AccountType.java
index 8b50d79..0943953 100644
--- a/src/com/android/contacts/common/model/account/AccountType.java
+++ b/src/com/android/contacts/common/model/account/AccountType.java
@@ -16,6 +16,7 @@
package com.android.contacts.common.model.account;
+import android.accounts.AuthenticatorDescription;
import android.content.ContentValues;
import android.content.Context;
import android.content.pm.PackageManager;
@@ -325,6 +326,12 @@
return this.mMimeKinds.get(mimeType);
}
+ public void initializeFieldsFromAuthenticator(AuthenticatorDescription authenticator) {
+ accountType = authenticator.type;
+ titleRes = authenticator.labelId;
+ iconRes = authenticator.iconId;
+ }
+
/**
* Add given {@link DataKind} to list of those provided by this source.
*/
diff --git a/src/com/android/contacts/common/model/account/AccountWithDataSet.java b/src/com/android/contacts/common/model/account/AccountWithDataSet.java
index 5947647..887cdd4 100644
--- a/src/com/android/contacts/common/model/account/AccountWithDataSet.java
+++ b/src/com/android/contacts/common/model/account/AccountWithDataSet.java
@@ -77,6 +77,8 @@
mAccountTypeWithDataSet = AccountTypeWithDataSet.get(type, dataSet);
}
+ // TODO: consider modifying or deleting this method. "local" accounts on some non-nexus devices
+ // have non-null values for name, type, and dataset
public boolean isLocalAccount() {
return name == null && type == null && dataSet == null;
}
diff --git a/src/com/android/contacts/common/model/account/DeviceLocalAccountType.java b/src/com/android/contacts/common/model/account/DeviceLocalAccountType.java
new file mode 100644
index 0000000..31452e6
--- /dev/null
+++ b/src/com/android/contacts/common/model/account/DeviceLocalAccountType.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2016 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.common.model.account;
+
+import android.content.Context;
+
+public class DeviceLocalAccountType extends FallbackAccountType {
+
+ private final boolean mGroupsEditable;
+
+ public DeviceLocalAccountType(Context context, boolean groupsEditable) {
+ super(context);
+ mGroupsEditable = groupsEditable;
+ }
+
+ public DeviceLocalAccountType(Context context) {
+ this(context, false);
+ }
+
+ @Override
+ public boolean isGroupMembershipEditable() {
+ return mGroupsEditable;
+ }
+}
diff --git a/src/com/android/contacts/common/model/account/FallbackAccountType.java b/src/com/android/contacts/common/model/account/FallbackAccountType.java
index 8a7b964..7c6d17c 100644
--- a/src/com/android/contacts/common/model/account/FallbackAccountType.java
+++ b/src/com/android/contacts/common/model/account/FallbackAccountType.java
@@ -16,6 +16,7 @@
package com.android.contacts.common.model.account;
+import android.accounts.AuthenticatorDescription;
import android.content.Context;
import android.util.Log;
@@ -71,6 +72,12 @@
}
@Override
+ public void initializeFieldsFromAuthenticator(AuthenticatorDescription authenticator) {
+ // Do nothing. For "Device" accounts we want to just display them using our own strings
+ // and icons.
+ }
+
+ @Override
public boolean areContactsWritable() {
return true;
}
diff --git a/src/com/android/contacts/common/model/account/SimAccountType.java b/src/com/android/contacts/common/model/account/SimAccountType.java
new file mode 100644
index 0000000..a2219cc
--- /dev/null
+++ b/src/com/android/contacts/common/model/account/SimAccountType.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2016 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.common.model.account;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+
+import com.android.contacts.R;
+
+/**
+ * Account type for SIM card contacts
+ *
+ * TODO: Right now this is the same as FallbackAccountType with a different icon and label.
+ * Instead it should setup it's own DataKinds that are known to work on SIM card.
+ */
+public class SimAccountType extends FallbackAccountType {
+
+ public SimAccountType(Context context) {
+ super(context);
+ this.titleRes = R.string.account_sim;
+ this.iconRes = R.drawable.ic_sim_card_tinted_24dp;
+
+ }
+
+ @Override
+ public boolean isGroupMembershipEditable() {
+ return false;
+ }
+}
diff --git a/src/com/android/contacts/common/preference/ContactsPreferences.java b/src/com/android/contacts/common/preference/ContactsPreferences.java
index a8a9089..f994d28 100644
--- a/src/com/android/contacts/common/preference/ContactsPreferences.java
+++ b/src/com/android/contacts/common/preference/ContactsPreferences.java
@@ -88,7 +88,7 @@
private final Context mContext;
private int mSortOrder = PREFERENCE_UNASSIGNED;
private int mDisplayOrder = PREFERENCE_UNASSIGNED;
- private String mDefaultAccount = null;
+ private AccountWithDataSet mDefaultAccount = null;
private ChangeListener mListener = null;
private Handler mHandler;
private final SharedPreferences mPreferences;
@@ -169,26 +169,24 @@
return mContext.getResources().getBoolean(R.bool.config_default_account_user_changeable);
}
- public String getDefaultAccount() {
+ public AccountWithDataSet getDefaultAccount() {
if (!isDefaultAccountUserChangeable()) {
return mDefaultAccount;
}
- if (TextUtils.isEmpty(mDefaultAccount)) {
+ if (mDefaultAccount == null) {
final String accountString = mPreferences
- .getString(mDefaultAccountKey, mDefaultAccount);
+ .getString(mDefaultAccountKey, null);
if (!TextUtils.isEmpty(accountString)) {
- final AccountWithDataSet accountWithDataSet = AccountWithDataSet.unstringify(
- accountString);
- mDefaultAccount = accountWithDataSet.name;
+ mDefaultAccount = AccountWithDataSet.unstringify(accountString);
}
}
return mDefaultAccount;
}
public void setDefaultAccount(AccountWithDataSet accountWithDataSet) {
- mDefaultAccount = accountWithDataSet == null ? null : accountWithDataSet.name;
+ mDefaultAccount = accountWithDataSet;
final Editor editor = mPreferences.edit();
- if (TextUtils.isEmpty(mDefaultAccount)) {
+ if (mDefaultAccount == null) {
editor.remove(mDefaultAccountKey);
} else {
editor.putString(mDefaultAccountKey, accountWithDataSet.stringify());
diff --git a/src/com/android/contacts/common/preference/DefaultAccountPreference.java b/src/com/android/contacts/common/preference/DefaultAccountPreference.java
index bcde371..3960e62 100644
--- a/src/com/android/contacts/common/preference/DefaultAccountPreference.java
+++ b/src/com/android/contacts/common/preference/DefaultAccountPreference.java
@@ -19,21 +19,19 @@
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
-import android.preference.ListPreference;
+import android.preference.DialogPreference;
import android.util.AttributeSet;
import android.view.View;
+import com.android.contacts.common.model.account.AccountDisplayInfoFactory;
import com.android.contacts.common.model.account.AccountWithDataSet;
import com.android.contacts.common.util.AccountsListAdapter;
-import java.util.HashMap;
-import java.util.Map;
-
-public class DefaultAccountPreference extends ListPreference {
+public class DefaultAccountPreference extends DialogPreference {
private ContactsPreferences mPreferences;
- private Map<String, AccountWithDataSet> mAccountMap;
- private int mClickedDialogEntryIndex;
private AccountsListAdapter mListAdapter;
+ private AccountDisplayInfoFactory mAccountDisplayInfoFactory;
+ private int mChosenIndex = -1;
public DefaultAccountPreference(Context context) {
super(context);
@@ -53,25 +51,9 @@
private void prepare() {
mPreferences = new ContactsPreferences(getContext());
- mAccountMap = new HashMap<>();
mListAdapter = new AccountsListAdapter(getContext(),
AccountsListAdapter.AccountListFilter.ACCOUNTS_CONTACT_WRITABLE);
- final String[] accountNamesArray = new String[mListAdapter.getCount()];
- for (int i = 0; i < mListAdapter.getCount(); i++) {
- final AccountWithDataSet account = mListAdapter.getItem(i);
- mAccountMap.put(account.name, account);
- accountNamesArray[i] = account.name;
- }
- setEntries(accountNamesArray);
- setEntryValues(accountNamesArray);
- final String defaultAccount = String.valueOf(mPreferences.getDefaultAccount());
- if (mListAdapter.getCount() == 1) {
- setValue(mListAdapter.getItem(0).name);
- } else if (mAccountMap.keySet().contains(defaultAccount)) {
- setValue(defaultAccount);
- } else {
- setValue(null);
- }
+ mAccountDisplayInfoFactory = AccountDisplayInfoFactory.forWritableAccounts(getContext());
}
@Override
@@ -81,48 +63,39 @@
@Override
public CharSequence getSummary() {
- return mPreferences.getDefaultAccount();
- }
-
- @Override
- protected boolean persistString(String value) {
- if (value == null && mPreferences.getDefaultAccount() == null) {
- return true;
- }
- if (value == null || mPreferences.getDefaultAccount() == null
- || !value.equals(mPreferences.getDefaultAccount())) {
- mPreferences.setDefaultAccount(mAccountMap.get(value));
- notifyChanged();
- }
- return true;
+ final AccountWithDataSet defaultAccount = mPreferences.getDefaultAccount();
+ return defaultAccount == null ? null : mAccountDisplayInfoFactory
+ .getAccountDisplayInfo(defaultAccount).getNameLabel();
}
@Override
protected void onPrepareDialogBuilder(AlertDialog.Builder builder) {
super.onPrepareDialogBuilder(builder);
- // UX recommendation is not to show cancel button on such lists.
+ // UX recommendation is not to show buttons on such lists.
builder.setNegativeButton(null, null);
- // Override and do everything ListPreference does except relative to our custom adapter.
- // onDialogClosed needs to be overridden as well since mClickedDialogEntryIndex is private
- // in ListPreference.
+ builder.setPositiveButton(null, null);
builder.setAdapter(mListAdapter, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
- mClickedDialogEntryIndex = which;
- // Clicking on an item simulates the positive button click,
- // and dismisses the dialog.
- DefaultAccountPreference.this.onClick(dialog, DialogInterface.BUTTON_POSITIVE);
- dialog.dismiss();
+ mChosenIndex = which;
}
});
}
@Override
protected void onDialogClosed(boolean positiveResult) {
- if (positiveResult && mClickedDialogEntryIndex >= 0 && getEntryValues() != null) {
- final String value = getEntryValues()[mClickedDialogEntryIndex].toString();
- if (callChangeListener(value)) {
- setValue(value);
+ final AccountWithDataSet currentDefault = mPreferences.getDefaultAccount();
+
+ if (mChosenIndex == -1) {
+ if (currentDefault != null) {
+ mPreferences.setDefaultAccount(null);
+ notifyChanged();
+ }
+ } else {
+ final AccountWithDataSet chosenAccount = mListAdapter.getItem(mChosenIndex);
+ if (!chosenAccount.equals(currentDefault)) {
+ mPreferences.setDefaultAccount(chosenAccount);
+ notifyChanged();
}
}
}
diff --git a/src/com/android/contacts/common/util/AccountsListAdapter.java b/src/com/android/contacts/common/util/AccountsListAdapter.java
index ef43a30..a437929 100644
--- a/src/com/android/contacts/common/util/AccountsListAdapter.java
+++ b/src/com/android/contacts/common/util/AccountsListAdapter.java
@@ -17,7 +17,6 @@
package com.android.contacts.common.util;
import android.content.Context;
-import android.text.TextUtils.TruncateAt;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -25,9 +24,10 @@
import android.widget.ImageView;
import android.widget.TextView;
+import com.android.contacts.common.model.account.AccountDisplayInfo;
+import com.android.contacts.common.model.account.AccountDisplayInfoFactory;
import com.android.contacts.common.R;
import com.android.contacts.common.model.AccountTypeManager;
-import com.android.contacts.common.model.account.AccountType;
import com.android.contacts.common.model.account.AccountWithDataSet;
import java.util.ArrayList;
@@ -38,8 +38,7 @@
*/
public final class AccountsListAdapter extends BaseAdapter {
private final LayoutInflater mInflater;
- private final List<AccountWithDataSet> mAccounts;
- private final AccountTypeManager mAccountTypes;
+ private final List<AccountDisplayInfo> mAccountDisplayInfoList;
private final Context mContext;
private int mCustomLayout = -1;
@@ -63,22 +62,29 @@
public AccountsListAdapter(Context context, AccountListFilter accountListFilter,
AccountWithDataSet currentAccount) {
mContext = context;
- mAccountTypes = AccountTypeManager.getInstance(context);
- mAccounts = getAccounts(accountListFilter);
+ final List<AccountWithDataSet> accounts = getAccounts(accountListFilter);
if (currentAccount != null
- && !mAccounts.isEmpty()
- && !mAccounts.get(0).equals(currentAccount)
- && mAccounts.remove(currentAccount)) {
- mAccounts.add(0, currentAccount);
+ && !accounts.isEmpty()
+ && !accounts.get(0).equals(currentAccount)
+ && accounts.remove(currentAccount)) {
+ accounts.add(0, currentAccount);
+ }
+
+ final AccountDisplayInfoFactory factory = new AccountDisplayInfoFactory(context,
+ accounts);
+ mAccountDisplayInfoList = new ArrayList<>(accounts.size());
+ for (AccountWithDataSet account : accounts) {
+ mAccountDisplayInfoList.add(factory.getAccountDisplayInfo(account));
}
mInflater = LayoutInflater.from(context);
}
private List<AccountWithDataSet> getAccounts(AccountListFilter accountListFilter) {
+ final AccountTypeManager typeManager = AccountTypeManager.getInstance(mContext);
if (accountListFilter == AccountListFilter.ACCOUNTS_GROUP_WRITABLE) {
- return new ArrayList<AccountWithDataSet>(mAccountTypes.getGroupWritableAccounts());
+ return new ArrayList<AccountWithDataSet>(typeManager.getGroupWritableAccounts());
}
- return new ArrayList<AccountWithDataSet>(mAccountTypes.getAccounts(
+ return new ArrayList<AccountWithDataSet>(typeManager.getAccounts(
accountListFilter == AccountListFilter.ACCOUNTS_CONTACT_WRITABLE));
}
@@ -96,25 +102,22 @@
final TextView text2 = (TextView) resultView.findViewById(android.R.id.text2);
final ImageView icon = (ImageView) resultView.findViewById(android.R.id.icon);
- final AccountWithDataSet account = mAccounts.get(position);
- final AccountType accountType = mAccountTypes.getAccountType(account.type, account.dataSet);
+ text1.setText(mAccountDisplayInfoList.get(position).getTypeLabel());
+ text2.setText(mAccountDisplayInfoList.get(position).getNameLabel());
- text1.setText(accountType.getDisplayLabel(mContext));
- text2.setText(account.name);
-
- icon.setImageDrawable(accountType.getDisplayIcon(mContext));
+ icon.setImageDrawable(mAccountDisplayInfoList.get(position).getIcon());
return resultView;
}
@Override
public int getCount() {
- return mAccounts.size();
+ return mAccountDisplayInfoList.size();
}
@Override
public AccountWithDataSet getItem(int position) {
- return mAccounts.get(position);
+ return mAccountDisplayInfoList.get(position).getSource();
}
@Override
diff --git a/src/com/android/contacts/common/util/DeviceAccountFilter.java b/src/com/android/contacts/common/util/DeviceAccountFilter.java
deleted file mode 100644
index 9dc98a5..0000000
--- a/src/com/android/contacts/common/util/DeviceAccountFilter.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2016 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.common.util;
-
-/**
- * Reports whether a value from RawContacts.ACCOUNT_TYPE should be considered a "Device"
- * account
- */
-public interface DeviceAccountFilter {
- boolean isDeviceAccountType(String accountType);
-
- public static DeviceAccountFilter ONLY_NULL = new DeviceAccountFilter() {
- @Override
- public boolean isDeviceAccountType(String accountType) {
- return accountType == null;
- }
- };
-}
diff --git a/src/com/android/contacts/common/util/DeviceAccountPresentationValues.java b/src/com/android/contacts/common/util/DeviceAccountPresentationValues.java
deleted file mode 100644
index dab81ed..0000000
--- a/src/com/android/contacts/common/util/DeviceAccountPresentationValues.java
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * Copyright (C) 2016 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.common.util;
-
-import android.content.Context;
-import android.graphics.drawable.Drawable;
-
-import com.android.contacts.common.list.ContactListFilter;
-
-import java.util.Collections;
-import java.util.List;
-
-/**
- * Supplies the label and icon that should be used for device accounts in the Nav Drawer.
- *
- * This operates on the list of filters to allow the implementation to choose better resources
- * in the case that there are multiple device accounts in the filter list.
- */
-public interface DeviceAccountPresentationValues {
- void setFilters(List<ContactListFilter> filters);
-
- CharSequence getLabel(int index);
-
- Drawable getIcon(int index);
-
- /**
- * The default implementation only returns a label and icon for a device filter that as null
- * values for the accountType and accountName
- */
- class Default implements DeviceAccountPresentationValues {
- private final Context mContext;
-
- private List<ContactListFilter> mFilters = null;
-
- public Default(Context context) {
- mContext = context;
- }
-
- @Override
- public CharSequence getLabel(int index) {
- assertFiltersInitialized();
-
- final ContactListFilter filter = mFilters.get(index);
- if (filter.filterType != ContactListFilter.FILTER_TYPE_DEVICE_CONTACTS) {
- return filter.accountName;
- }
- return filter.accountName != null ? filter.accountName :
- mContext.getString(com.android.contacts.common.R.string.account_phone);
- }
-
- @Override
- public Drawable getIcon(int index) {
- assertFiltersInitialized();
-
- final ContactListFilter filter = mFilters.get(index);
- if (filter.filterType != ContactListFilter.FILTER_TYPE_DEVICE_CONTACTS) {
- return filter.icon;
- }
- return mContext.getDrawable(com.android.contacts.common.R.drawable.ic_device);
- }
-
- @Override
- public void setFilters(List<ContactListFilter> filters) {
- if (filters == null) {
- mFilters = Collections.emptyList();
- } else {
- mFilters = filters;
- }
- }
-
- private void assertFiltersInitialized() {
- if (mFilters == null) {
- throw new IllegalStateException("setFilters must be called first.");
- }
- }
- }
-
-}
diff --git a/src/com/android/contacts/common/util/DeviceLocalAccountTypeFactory.java b/src/com/android/contacts/common/util/DeviceLocalAccountTypeFactory.java
new file mode 100644
index 0000000..040a6b4
--- /dev/null
+++ b/src/com/android/contacts/common/util/DeviceLocalAccountTypeFactory.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2016 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.common.util;
+
+import android.content.Context;
+import android.support.annotation.IntDef;
+
+import com.android.contacts.common.model.account.AccountType;
+import com.android.contacts.common.model.account.DeviceLocalAccountType;
+import com.android.contacts.common.model.account.FallbackAccountType;
+
+import java.lang.annotation.Retention;
+
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+/**
+ * Reports whether a value from RawContacts.ACCOUNT_TYPE should be considered a "Device"
+ * account
+ */
+public interface DeviceLocalAccountTypeFactory {
+
+ @Retention(SOURCE)
+ @IntDef({TYPE_OTHER, TYPE_DEVICE, TYPE_SIM})
+ @interface LocalAccountType {}
+ static final int TYPE_OTHER = 0;
+ static final int TYPE_DEVICE = 1;
+ static final int TYPE_SIM = 2;
+
+ @DeviceLocalAccountTypeFactory.LocalAccountType int classifyAccount(String accountType);
+
+ AccountType getAccountType(String accountType);
+
+ class Util {
+ private Util() { }
+
+ public static boolean isLocalAccountType(@LocalAccountType int type) {
+ return type == TYPE_SIM || type == TYPE_DEVICE;
+ }
+
+ public static boolean isLocalAccountType(DeviceLocalAccountTypeFactory factory,
+ String type) {
+
+ return isLocalAccountType(factory.classifyAccount(type));
+ }
+ }
+
+ class Default implements DeviceLocalAccountTypeFactory {
+ private Context mContext;
+
+ public Default(Context context) {
+ mContext = context;
+ }
+
+ @Override
+ public int classifyAccount(String accountType) {
+ return accountType == null ? TYPE_DEVICE : TYPE_OTHER;
+ }
+
+ @Override
+ public AccountType getAccountType(String accountType) {
+ if (accountType != null) {
+ throw new IllegalArgumentException(accountType + " is not a device account type.");
+ }
+ return new DeviceLocalAccountType(mContext);
+ }
+ }
+}
diff --git a/src/com/android/contacts/common/util/DeviceLocalContactsFilterProvider.java b/src/com/android/contacts/common/util/DeviceLocalContactsFilterProvider.java
deleted file mode 100644
index 1d06a43..0000000
--- a/src/com/android/contacts/common/util/DeviceLocalContactsFilterProvider.java
+++ /dev/null
@@ -1,173 +0,0 @@
-/*
- * Copyright (C) 2016 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.common.util;
-
-import android.accounts.Account;
-import android.accounts.AccountManager;
-import android.app.LoaderManager;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.CursorLoader;
-import android.content.Loader;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.Bundle;
-import android.provider.ContactsContract;
-import android.support.annotation.Keep;
-import android.support.annotation.VisibleForTesting;
-
-import com.android.contacts.common.list.ContactListFilter;
-import com.android.contacts.test.NeededForReflection;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
-/**
- * Get filters for device local accounts. These are "accounts" that have contacts associated
- * with them but are not returned by AccountManager. Any other account will be displayed
- * automatically so we don't worry about it.
- */
-public class DeviceLocalContactsFilterProvider
- implements LoaderManager.LoaderCallbacks<Cursor> {
-
- public static String[] PROJECTION = new String[] {
- ContactsContract.RawContacts.ACCOUNT_NAME, ContactsContract.RawContacts.ACCOUNT_TYPE
- };
-
- private static final int COL_NAME = 0;
- private static final int COL_TYPE = 1;
-
- private final Context mContext;
- private final DeviceAccountFilter mAccountTypeFilter;
-
- private String[] mKnownAccountTypes;
-
- private List<ContactListFilter> mDeviceFilters = Collections.emptyList();
-
- public DeviceLocalContactsFilterProvider(Context context,
- DeviceAccountFilter accountTypeFilter) {
- mContext = context;
- mAccountTypeFilter = accountTypeFilter;
- }
-
- private ContactListFilter createFilterForAccount(Account account) {
- return new ContactListFilter(ContactListFilter.FILTER_TYPE_DEVICE_CONTACTS,
- account.type, account.name, null, null);
- }
-
- public List<ContactListFilter> getListFilters() {
- return mDeviceFilters;
- }
-
- @Override
- public CursorLoader onCreateLoader(int i, Bundle bundle) {
- if (mKnownAccountTypes == null) {
- initKnownAccountTypes();
- }
- return new CursorLoader(mContext, getUri(), PROJECTION, getSelection(),
- getSelectionArgs(), null);
- }
-
-
- private List<ContactListFilter> createFiltersFromResults(Cursor cursor) {
- final Set<Account> accounts = new HashSet<>();
- boolean hasNullType = false;
-
- while (cursor.moveToNext()) {
- final String name = cursor.getString(COL_NAME);
- final String type = cursor.getString(COL_TYPE);
- // The case where where only one of the columns is null isn't handled specifically.
- if (mAccountTypeFilter.isDeviceAccountType(type)) {
- if (name != null && type != null) {
- accounts.add(new Account(name, type));
- } else {
- hasNullType = true;
- }
- }
- }
-
- final List<ContactListFilter> result = new ArrayList<>(accounts.size());
- if (hasNullType) {
- result.add(new ContactListFilter(ContactListFilter.FILTER_TYPE_DEVICE_CONTACTS,
- null, null, null, null));
- }
- for (Account account : accounts) {
- result.add(createFilterForAccount(account));
- }
- return result;
- }
-
- @Override
- public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
- if (cursor == null) return;
- mDeviceFilters = createFiltersFromResults(cursor);
- }
-
- @Override
- public void onLoaderReset(Loader<Cursor> loader) {
- }
-
- @Keep
- @VisibleForTesting
- public void setKnownAccountTypes(String... accountTypes) {
- mKnownAccountTypes = accountTypes;
- }
-
- private void initKnownAccountTypes() {
- final AccountManager accountManager = (AccountManager) mContext
- .getSystemService(Context.ACCOUNT_SERVICE);
- final Set<String> knownTypes = new HashSet<>();
- final Account[] accounts = accountManager.getAccounts();
- for (Account account : accounts) {
- if (ContentResolver.getIsSyncable(account, ContactsContract.AUTHORITY) > 0) {
- knownTypes.add(account.type);
- }
- }
- mKnownAccountTypes = knownTypes.toArray(new String[knownTypes.size()]);
- }
-
- private Uri getUri() {
- final Uri.Builder builder = ContactsContract.RawContacts.CONTENT_URI.buildUpon();
- if (mKnownAccountTypes == null || mKnownAccountTypes.length == 0) {
- builder.appendQueryParameter(ContactsContract.LIMIT_PARAM_KEY, "1");
- }
- return builder.build();
- }
-
- private String getSelection() {
- final StringBuilder sb = new StringBuilder();
- sb.append(ContactsContract.RawContacts.DELETED).append(" =0 AND (")
- .append(ContactsContract.RawContacts.ACCOUNT_TYPE).append(" IS NULL");
- if (mKnownAccountTypes == null || mKnownAccountTypes.length == 0) {
- return sb.append(')').toString();
- }
- sb.append(" OR ").append(ContactsContract.RawContacts.ACCOUNT_TYPE).append(" NOT IN (");
- for (String ignored : mKnownAccountTypes) {
- sb.append("?,");
- }
- // Remove trailing ','
- sb.deleteCharAt(sb.length() - 1).append(')').append(')');
-
- return sb.toString();
- }
-
- private String[] getSelectionArgs() {
- return mKnownAccountTypes;
- }
-}
diff --git a/src/com/android/contacts/editor/CompactRawContactsEditorView.java b/src/com/android/contacts/editor/CompactRawContactsEditorView.java
index d9cc58d..884cc51 100644
--- a/src/com/android/contacts/editor/CompactRawContactsEditorView.java
+++ b/src/com/android/contacts/editor/CompactRawContactsEditorView.java
@@ -16,6 +16,8 @@
package com.android.contacts.editor;
+import com.android.contacts.common.model.account.AccountDisplayInfo;
+import com.android.contacts.common.model.account.AccountDisplayInfoFactory;
import com.android.contacts.R;
import com.android.contacts.common.model.AccountTypeManager;
import com.android.contacts.common.model.RawContactDelta;
@@ -319,6 +321,7 @@
private CompactRawContactsEditorView.Listener mListener;
private AccountTypeManager mAccountTypeManager;
+ private AccountDisplayInfoFactory mAccountDisplayInfoFactory;
private LayoutInflater mLayoutInflater;
private ViewIdGenerator mViewIdGenerator;
@@ -372,6 +375,7 @@
super.onFinishInflate();
mAccountTypeManager = AccountTypeManager.getInstance(getContext());
+ mAccountDisplayInfoFactory = AccountDisplayInfoFactory.forWritableAccounts(getContext());
mLayoutInflater = (LayoutInflater)
getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
@@ -727,26 +731,25 @@
final RawContactDelta rawContactDelta =
mPrimaryNameKindSectionData.first.getRawContactDelta();
+ final AccountDisplayInfo account =
+ mAccountDisplayInfoFactory.getAccountDisplayInfoFor(rawContactDelta);
+
// Get the account information for the primary raw contact delta
- final Pair<String,String> accountInfo = mIsUserProfile
- ? EditorUiUtils.getLocalAccountInfo(getContext(),
- rawContactDelta.getAccountName(),
- rawContactDelta.getAccountType(mAccountTypeManager))
- : EditorUiUtils.getAccountInfo(getContext(),
- rawContactDelta.getAccountName(),
- rawContactDelta.getAccountType(mAccountTypeManager));
+ final String accountLabel = mIsUserProfile
+ ? EditorUiUtils.getAccountHeaderLabelForMyProfile(getContext(), account)
+ : account.getNameLabel().toString();
// Either the account header or selector should be shown, not both.
final List<AccountWithDataSet> accounts =
AccountTypeManager.getInstance(getContext()).getAccounts(true);
if (mHasNewContact && !mIsUserProfile) {
if (accounts.size() > 1) {
- addAccountSelector(accountInfo, rawContactDelta);
+ addAccountSelector(rawContactDelta, accountLabel);
} else {
- addAccountHeader(accountInfo);
+ addAccountHeader(accountLabel);
}
} else if (mIsUserProfile || !shouldHideAccountContainer(rawContactDeltas)) {
- addAccountHeader(accountInfo);
+ addAccountHeader(accountLabel);
}
// The raw contact selector should only display linked raw contacts that can be edited in
@@ -800,14 +803,12 @@
return (writable > 1 || (writable > 0 && readonly > 0));
}
- private void addAccountHeader(Pair<String,String> accountInfo) {
+ private void addAccountHeader(String accountLabel) {
mAccountHeaderContainer.setVisibility(View.VISIBLE);
// Set the account name
- final String accountName = TextUtils.isEmpty(accountInfo.first)
- ? accountInfo.second : accountInfo.first;
mAccountHeaderName.setVisibility(View.VISIBLE);
- mAccountHeaderName.setText(accountName);
+ mAccountHeaderName.setText(accountLabel);
// Set the account type
final String selectorTitle = getResources().getString(
@@ -827,13 +828,13 @@
// Set the content description
mAccountHeaderContainer.setContentDescription(
- EditorUiUtils.getAccountInfoContentDescription(accountName, selectorTitle));
+ EditorUiUtils.getAccountInfoContentDescription(accountLabel,
+ selectorTitle));
}
- private void addAccountSelector(Pair<String,String> accountInfo,
- final RawContactDelta rawContactDelta) {
+ private void addAccountSelector(final RawContactDelta rawContactDelta, CharSequence nameLabel) {
// Show save to default account.
- addAccountHeader(accountInfo);
+ addAccountHeader(nameLabel.toString());
// Add handlers for choosing another account to save to.
mAccountHeaderExpanderIcon.setVisibility(View.VISIBLE);
mAccountHeaderContainer.setOnClickListener(new View.OnClickListener() {
diff --git a/src/com/android/contacts/editor/ContactEditorBaseFragment.java b/src/com/android/contacts/editor/ContactEditorBaseFragment.java
index d8045cf..05f6c47 100644
--- a/src/com/android/contacts/editor/ContactEditorBaseFragment.java
+++ b/src/com/android/contacts/editor/ContactEditorBaseFragment.java
@@ -58,6 +58,7 @@
import com.android.contacts.ContactSaveService;
import com.android.contacts.GroupMetaDataLoader;
import com.android.contacts.R;
+import com.android.contacts.activities.CompactContactEditorActivity;
import com.android.contacts.activities.ContactEditorAccountsChangedActivity;
import com.android.contacts.activities.ContactEditorBaseActivity;
import com.android.contacts.activities.ContactEditorBaseActivity.ContactEditor;
@@ -580,6 +581,9 @@
mHasNewContact = true;
if (mAccountWithDataSet != null) {
createContact(mAccountWithDataSet);
+ } else if (mIntentExtras != null && mIntentExtras.getBoolean(
+ ContactEditorBaseActivity.EXTRA_SAVE_TO_DEVICE_FLAG, false)) {
+ createContact(null);
} else {
// No Account specified. Let the user choose
// Load Accounts async so that we can present them
diff --git a/src/com/android/contacts/editor/ContactEditorUtils.java b/src/com/android/contacts/editor/ContactEditorUtils.java
index 1b0da05..b976456 100644
--- a/src/com/android/contacts/editor/ContactEditorUtils.java
+++ b/src/com/android/contacts/editor/ContactEditorUtils.java
@@ -113,18 +113,13 @@
*
* 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".
+ * @param defaultAccount the account used to save a newly created contact.
*/
public void saveDefaultAndAllAccounts(AccountWithDataSet defaultAccount) {
final SharedPreferences.Editor editor = mPrefs.edit()
.putBoolean(mAnythingSavedKey, true);
- if (defaultAccount == null || defaultAccount.isLocalAccount()) {
- // If the default is "local only", there should be no writable accounts.
- // This should always be the case with our spec, but because we load the account list
- // asynchronously using a worker thread, it is possible that there are accounts at this
- // point. So if the default is null always clear the account list.
+ if (defaultAccount == null) {
editor.remove(KEY_KNOWN_ACCOUNTS);
editor.remove(mDefaultAccountKey);
} else {
@@ -212,7 +207,9 @@
final List<AccountWithDataSet> currentWritableAccounts = getWritableAccounts();
if (currentWritableAccounts.size() == 1) {
- return false;
+ // TODO: This will only work for devices that use a null device account but it should
+ // probably should notify for other OEM device account types as well.
+ return isFirstLaunch() && currentWritableAccounts.get(0).isLocalAccount();
}
if (isFirstLaunch()) {
@@ -226,12 +223,10 @@
return true;
}
- // If there is an inconsistent state in the preferences file - default account is null
- // ("local" account) while there are multiple accounts, then show the notification dialog.
- // This shouldn't ever happen, but this should allow the user can get back into a normal
- // state after they respond to the notification.
- if ((defaultAccount == null || defaultAccount.isLocalAccount())
- && currentWritableAccounts.size() > 0) {
+ // If there is an inconsistent state in the preferences file then show the notification
+ // dialog. This shouldn't ever happen, but this should allow the user can get back into a
+ // normal state after they respond to the notification.
+ if (defaultAccount == null) {
Log.e(TAG, "Preferences file in an inconsistent state, request that the default account"
+ " and current writable accounts be saved again");
return true;
diff --git a/src/com/android/contacts/editor/EditorUiUtils.java b/src/com/android/contacts/editor/EditorUiUtils.java
index 8772cbb..89d830b 100644
--- a/src/com/android/contacts/editor/EditorUiUtils.java
+++ b/src/com/android/contacts/editor/EditorUiUtils.java
@@ -16,15 +16,14 @@
package com.android.contacts.editor;
-import static android.provider.ContactsContract.CommonDataKinds.GroupMembership;
-import static android.provider.ContactsContract.CommonDataKinds.StructuredName;
-import static com.android.contacts.common.util.MaterialColorMapUtils.getDefaultPrimaryAndSecondaryColors;
-
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.drawable.Drawable;
+import android.media.RingtoneManager;
+import android.net.Uri;
+import android.os.Build;
import android.provider.ContactsContract.CommonDataKinds.Email;
import android.provider.ContactsContract.CommonDataKinds.Event;
import android.provider.ContactsContract.CommonDataKinds.Im;
@@ -36,11 +35,7 @@
import android.provider.ContactsContract.CommonDataKinds.SipAddress;
import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
import android.provider.ContactsContract.CommonDataKinds.Website;
-import android.media.RingtoneManager;
-import android.net.Uri;
-import android.os.Build;
import android.text.TextUtils;
-import android.util.Pair;
import android.widget.ImageView;
import com.android.contacts.R;
@@ -49,18 +44,20 @@
import com.android.contacts.common.ContactPhotoManager.DefaultImageRequest;
import com.android.contacts.common.ContactsUtils;
import com.android.contacts.common.model.ValuesDelta;
-import com.android.contacts.common.model.account.AccountType;
-import com.android.contacts.common.model.account.GoogleAccountType;
import com.android.contacts.common.model.dataitem.DataKind;
import com.android.contacts.common.util.MaterialColorMapUtils.MaterialPalette;
+import com.android.contacts.common.model.account.AccountDisplayInfo;
import com.android.contacts.util.ContactPhotoUtils;
import com.android.contacts.widget.QuickContactImageView;
-
import com.google.common.collect.Maps;
import java.io.FileNotFoundException;
import java.util.HashMap;
+import static android.provider.ContactsContract.CommonDataKinds.GroupMembership;
+import static android.provider.ContactsContract.CommonDataKinds.StructuredName;
+import static com.android.contacts.common.util.MaterialColorMapUtils.getDefaultPrimaryAndSecondaryColors;
+
/**
* Utility methods for creating contact editor.
*/
@@ -111,52 +108,34 @@
return id;
}
- /**
- * Returns the account name and account type labels to display for local accounts.
- */
- public static Pair<String,String> getLocalAccountInfo(Context context,
- String accountName, AccountType accountType) {
- if (TextUtils.isEmpty(accountName)) {
- return new Pair<>(
- /* accountName =*/ null,
- context.getString(R.string.local_profile_title));
+
+ public static String getAccountHeaderLabelForMyProfile(Context context,
+ AccountDisplayInfo displayableAccount) {
+ if (displayableAccount.isDeviceAccount()) {
+ return context.getString(R.string.local_profile_title);
+ } else {
+ return context.getString(R.string.external_profile_title,
+ displayableAccount.getTypeLabel());
}
- return new Pair<>(
- accountName,
- context.getString(R.string.external_profile_title,
- accountType.getDisplayLabel(context)));
}
- /**
- * Returns the account name and account type labels to display for the given account type.
- */
- public static Pair<String,String> getAccountInfo(Context context, String accountName,
- AccountType accountType) {
- CharSequence accountTypeDisplayLabel = accountType.getDisplayLabel(context);
- if (TextUtils.isEmpty(accountTypeDisplayLabel)
- || TextUtils.equals(
- context.getString(R.string.account_phone), accountTypeDisplayLabel)) {
- accountTypeDisplayLabel = context.getString(R.string.account_phone);
- } else if (GoogleAccountType.ACCOUNT_TYPE.equals(accountType.accountType)
- && accountType.dataSet == null){
- accountTypeDisplayLabel = context.getString(R.string.google_account_type_format,
- accountTypeDisplayLabel);
+ public static String getAccountTypeHeaderLabel(Context context, AccountDisplayInfo
+ displayableAccount) {
+ if (displayableAccount.isDeviceAccount()) {
+ // Do nothing. Type label should be "Device"
+ return displayableAccount.getTypeLabel().toString();
+ } else if (displayableAccount.isGoogleAccount()) {
+ return context.getString(R.string.google_account_type_format,
+ displayableAccount.getTypeLabel());
} else {
- accountTypeDisplayLabel = context.getString(R.string.account_type_format,
- accountTypeDisplayLabel);
+ return context.getString(R.string.account_type_format,
+ displayableAccount.getTypeLabel());
}
-
- if (TextUtils.isEmpty(accountName)) {
- return new Pair<>(/* accountName */ null, accountTypeDisplayLabel.toString());
- }
-
- return new Pair<>(context.getString(R.string.from_account_format, accountName),
- accountTypeDisplayLabel.toString());
}
/**
* Returns a content description String for the container of the account information
- * returned by {@link #getAccountInfo}.
+ * returned by {@link #getAccountTypeHeaderLabel(Context, AccountDisplayInfo)}.
*/
public static String getAccountInfoContentDescription(CharSequence accountName,
CharSequence accountType) {
diff --git a/src/com/android/contacts/editor/RawContactEditorView.java b/src/com/android/contacts/editor/RawContactEditorView.java
index 1e90e04..6d8c5ad 100644
--- a/src/com/android/contacts/editor/RawContactEditorView.java
+++ b/src/com/android/contacts/editor/RawContactEditorView.java
@@ -27,7 +27,6 @@
import android.provider.ContactsContract.Contacts;
import android.provider.ContactsContract.Data;
import android.util.AttributeSet;
-import android.util.Pair;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -35,6 +34,8 @@
import android.widget.LinearLayout;
import android.widget.TextView;
+import com.android.contacts.common.model.account.AccountDisplayInfo;
+import com.android.contacts.common.model.account.AccountDisplayInfoFactory;
import com.android.contacts.GroupMetaDataLoader;
import com.android.contacts.R;
import com.android.contacts.common.model.account.AccountType;
@@ -82,6 +83,8 @@
private DataKind mGroupMembershipKind;
private RawContactDelta mState;
+ private AccountDisplayInfoFactory mAccountDisplayInfoFactory;
+
public RawContactEditorView(Context context) {
super(context);
}
@@ -138,6 +141,8 @@
mAccountHeaderTypeTextView = (TextView) findViewById(R.id.account_type);
mAccountHeaderNameTextView = (TextView) findViewById(R.id.account_name);
mAccountIconImageView = (ImageView) findViewById(R.id.account_type_icon);
+
+ mAccountDisplayInfoFactory = AccountDisplayInfoFactory.forAllAccounts(getContext());
}
@Override
@@ -182,18 +187,28 @@
mRawContactId = state.getRawContactId();
- // Fill in the account info
- final Pair<String,String> accountInfo = isProfile
- ? EditorUiUtils.getLocalAccountInfo(getContext(), state.getAccountName(), type)
- : EditorUiUtils.getAccountInfo(getContext(), state.getAccountName(), type);
- if (accountInfo.first == null) {
- // Hide this view so the other text view will be centered vertically
+ final AccountDisplayInfo account = mAccountDisplayInfoFactory
+ .getAccountDisplayInfoFor(state);
+
+ final String accountTypeLabel;
+ final String accountNameLabel;
+ if (isProfile) {
+ accountTypeLabel = EditorUiUtils.getAccountHeaderLabelForMyProfile(
+ getContext(), account);
+ accountNameLabel = account.getNameLabel().toString();
+ } else {
+ accountTypeLabel = account.getTypeLabel().toString();
+ accountNameLabel = account.getNameLabel().toString();
+ }
+
+ if (!account.hasDistinctName()) {
+ // Hide this view so the other view will be centered vertically
mAccountHeaderNameTextView.setVisibility(View.GONE);
} else {
mAccountHeaderNameTextView.setVisibility(View.VISIBLE);
- mAccountHeaderNameTextView.setText(accountInfo.first);
+ mAccountHeaderNameTextView.setText(accountNameLabel);
}
- mAccountHeaderTypeTextView.setText(accountInfo.second);
+ mAccountHeaderTypeTextView.setText(accountTypeLabel);
updateAccountHeaderContentDescription();
mAccountIconImageView.setImageDrawable(state.getRawContactAccountType(getContext())
diff --git a/src/com/android/contacts/editor/RawContactReadOnlyEditorView.java b/src/com/android/contacts/editor/RawContactReadOnlyEditorView.java
index ad8504c..d55403b 100644
--- a/src/com/android/contacts/editor/RawContactReadOnlyEditorView.java
+++ b/src/com/android/contacts/editor/RawContactReadOnlyEditorView.java
@@ -27,7 +27,6 @@
import android.provider.ContactsContract.RawContacts;
import android.text.TextUtils;
import android.util.AttributeSet;
-import android.util.Pair;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
@@ -36,6 +35,8 @@
import android.widget.ImageView;
import android.widget.TextView;
+import com.android.contacts.common.model.account.AccountDisplayInfo;
+import com.android.contacts.common.model.account.AccountDisplayInfoFactory;
import com.android.contacts.R;
import com.android.contacts.common.GeoUtil;
import com.android.contacts.common.compat.PhoneNumberUtilsCompat;
@@ -55,6 +56,8 @@
implements OnClickListener {
private LayoutInflater mInflater;
+ private AccountDisplayInfoFactory mAccountDisplayInfoFactory;
+
private TextView mName;
private Button mEditExternallyButton;
private ViewGroup mGeneral;
@@ -93,6 +96,8 @@
mAccountHeaderTypeTextView = (TextView) findViewById(R.id.account_type);
mAccountHeaderNameTextView = (TextView) findViewById(R.id.account_name);
mAccountIconImageView = (ImageView) findViewById(R.id.account_type_icon);
+
+ mAccountDisplayInfoFactory = AccountDisplayInfoFactory.forAllAccounts(getContext());
}
/**
@@ -117,17 +122,29 @@
mAccountType = state.getAccountType();
mDataSet = state.getDataSet();
- final Pair<String,String> accountInfo = isProfile
- ? EditorUiUtils.getLocalAccountInfo(getContext(), state.getAccountName(), type)
- : EditorUiUtils.getAccountInfo(getContext(), state.getAccountName(), type);
- if (accountInfo.first == null) {
- // Hide this view so the other text view will be centered vertically
+
+ final AccountDisplayInfo account = mAccountDisplayInfoFactory
+ .getAccountDisplayInfoFor(state);
+
+ final String accountTypeLabel;
+ final String accountNameLabel;
+ if (isProfile) {
+ accountTypeLabel = EditorUiUtils.getAccountHeaderLabelForMyProfile(
+ getContext(), account);
+ accountNameLabel = account.getNameLabel().toString();
+ } else {
+ accountTypeLabel = account.getTypeLabel().toString();
+ accountNameLabel = account.getNameLabel().toString();
+ }
+
+ if (!account.hasDistinctName()) {
+ // Hide this view so the other view will be centered vertically
mAccountHeaderNameTextView.setVisibility(View.GONE);
} else {
mAccountHeaderNameTextView.setVisibility(View.VISIBLE);
- mAccountHeaderNameTextView.setText(accountInfo.first);
+ mAccountHeaderNameTextView.setText(accountNameLabel);
}
- mAccountHeaderTypeTextView.setText(accountInfo.second);
+ mAccountHeaderTypeTextView.setText(accountTypeLabel);
updateAccountHeaderContentDescription();
mAccountIconImageView.setImageDrawable(state.getRawContactAccountType(getContext())
diff --git a/src/com/android/contacts/group/GroupNameEditDialogFragment.java b/src/com/android/contacts/group/GroupNameEditDialogFragment.java
index 36a4710..544dd87 100644
--- a/src/com/android/contacts/group/GroupNameEditDialogFragment.java
+++ b/src/com/android/contacts/group/GroupNameEditDialogFragment.java
@@ -245,8 +245,7 @@
final String callbackAction = getArguments().getString(ARG_CALLBACK_ACTION);
final Intent serviceIntent;
if (mIsInsert) {
- serviceIntent = ContactSaveService.createNewGroupIntent(getActivity(),
- new AccountWithDataSet(mAccount.name, mAccount.type, mAccount.dataSet),
+ serviceIntent = ContactSaveService.createNewGroupIntent(getActivity(), mAccount,
name, null, getActivity().getClass(), callbackAction);
} else {
serviceIntent = ContactSaveService.createGroupRenameIntent(getActivity(), mGroupId,
diff --git a/src/com/android/contacts/interactions/AccountFiltersFragment.java b/src/com/android/contacts/interactions/AccountFiltersFragment.java
index b2b21f7..8d92327 100644
--- a/src/com/android/contacts/interactions/AccountFiltersFragment.java
+++ b/src/com/android/contacts/interactions/AccountFiltersFragment.java
@@ -19,16 +19,11 @@
import android.app.Fragment;
import android.app.LoaderManager;
import android.content.Loader;
-import android.database.Cursor;
import android.os.Bundle;
import com.android.contacts.common.list.ContactListFilter;
import com.android.contacts.common.util.AccountFilterUtil;
-import com.android.contacts.common.util.DeviceLocalContactsFilterProvider;
-import com.android.contactsbind.ObjectFactory;
-import java.util.ArrayList;
-import java.util.Collections;
import java.util.List;
/**
@@ -37,7 +32,6 @@
public class AccountFiltersFragment extends Fragment {
private static final int LOADER_FILTERS = 1;
- private static final int LOADER_DEVICE_LOCAL_CONTACTS = 3;
/**
* Callbacks for hosts of the {@link AccountFiltersFragment}.
@@ -50,8 +44,6 @@
void onFiltersLoaded(List<ContactListFilter> accountFilterItems);
}
- private LoaderManager.LoaderCallbacks<Cursor> mDeviceLocalLoaderListener;
-
private final LoaderManager.LoaderCallbacks<List<ContactListFilter>> mFiltersLoaderListener =
new LoaderManager.LoaderCallbacks<List<ContactListFilter>> () {
@Override
@@ -62,43 +54,25 @@
@Override
public void onLoadFinished(
Loader<List<ContactListFilter>> loader, List<ContactListFilter> data) {
- if (data == null) {
- mLoadedFilters = Collections.emptyList();
- } else {
- mLoadedFilters = data;
+ if (mListener != null && data != null) {
+ mListener.onFiltersLoaded(data);
}
- notifyWithCurrentFilters();
}
public void onLoaderReset(Loader<List<ContactListFilter>> loader) {
}
};
-
- private List<ContactListFilter> mLoadedFilters = null;
- private List<ContactListFilter> mDeviceLocalFilters = null;
private AccountFiltersListener mListener;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
-
- mDeviceLocalLoaderListener = new DeviceLocalContactsFilterProvider(getActivity(),
- ObjectFactory.getDeviceAccountFilter(getActivity())) {
- @Override
- public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
- super.onLoadFinished(loader, data);
- mDeviceLocalFilters = getListFilters();
- notifyWithCurrentFilters();
- }
- };
}
@Override
public void onStart() {
getLoaderManager().initLoader(LOADER_FILTERS, null, mFiltersLoaderListener);
- getLoaderManager().initLoader(LOADER_DEVICE_LOCAL_CONTACTS, null,
- mDeviceLocalLoaderListener);
super.onStart();
}
@@ -106,12 +80,4 @@
public void setListener(AccountFiltersListener listener) {
mListener = listener;
}
-
- private void notifyWithCurrentFilters() {
- if (mListener == null || mLoadedFilters == null || mDeviceLocalFilters == null) return;
-
- final List<ContactListFilter> result = new ArrayList<>(mLoadedFilters);
- result.addAll(mDeviceLocalFilters);
- mListener.onFiltersLoaded(result);
- }
}
diff --git a/tests/src/com/android/contacts/common/model/AccountWithDataSetTest.java b/tests/src/com/android/contacts/common/model/AccountWithDataSetTest.java
index e28f09e..7bfb922 100644
--- a/tests/src/com/android/contacts/common/model/AccountWithDataSetTest.java
+++ b/tests/src/com/android/contacts/common/model/AccountWithDataSetTest.java
@@ -58,6 +58,14 @@
MoreAsserts.assertNotEqual(a3, a2r);
}
+ public void testStringifyAndUnstringifyLocalAccount() {
+ final String stringified = AccountWithDataSet.getLocalAccount().stringify();
+
+ final AccountWithDataSet restored = AccountWithDataSet.unstringify(stringified);
+
+ assertEquals(AccountWithDataSet.getLocalAccount(), restored);
+ }
+
public void testStringifyListAndUnstringify() {
AccountWithDataSet a1 = new AccountWithDataSet("name1", "typeA", null);
AccountWithDataSet a2 = new AccountWithDataSet("name2", "typeB", null);
diff --git a/tests/src/com/android/contacts/common/model/DeviceLocalAccountLocatorTests.java b/tests/src/com/android/contacts/common/model/DeviceLocalAccountLocatorTests.java
new file mode 100644
index 0000000..e03c3e5
--- /dev/null
+++ b/tests/src/com/android/contacts/common/model/DeviceLocalAccountLocatorTests.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2016 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.common.model;
+
+import android.content.ContentResolver;
+import android.database.Cursor;
+import android.database.MatrixCursor;
+import android.net.Uri;
+import android.os.CancellationSignal;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.RawContacts;
+import android.support.annotation.Nullable;
+import android.test.AndroidTestCase;
+import android.test.mock.MockContentResolver;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.contacts.common.model.DeviceLocalAccountLocator;
+import com.android.contacts.common.model.account.AccountWithDataSet;
+import com.android.contacts.common.test.mocks.MockContentProvider;
+import com.android.contacts.common.util.DeviceLocalAccountTypeFactory;
+import com.android.contacts.tests.FakeDeviceAccountTypeFactory;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+@SmallTest
+public class DeviceLocalAccountLocatorTests extends AndroidTestCase {
+
+ // Basic smoke test that just checks that it doesn't throw when loading from CP2. We don't
+ // care what CP2 actually contains for this.
+ public void testShouldNotCrash() {
+ final DeviceLocalAccountLocator sut = new DeviceLocalAccountLocator(
+ getContext().getContentResolver(),
+ new DeviceLocalAccountTypeFactory.Default(getContext()),
+ Collections.<AccountWithDataSet>emptyList());
+ sut.getDeviceLocalAccounts();
+ // We didn't throw so it passed
+ }
+
+ public void test_getDeviceLocalAccounts_returnsEmptyListWhenNoRawContactsHaveDeviceType() {
+ final DeviceLocalAccountLocator sut = createWithQueryResult(queryResult(
+ "user", "com.example",
+ "user", "com.example",
+ "user", "com.example"));
+ assertTrue(sut.getDeviceLocalAccounts().isEmpty());
+ }
+
+ public void test_getDeviceLocalAccounts_returnsListWithItemForNullAccount() {
+ final DeviceLocalAccountLocator sut = createWithQueryResult(queryResult(
+ "user", "com.example",
+ null, null,
+ "user", "com.example",
+ null, null));
+
+ assertEquals(1, sut.getDeviceLocalAccounts().size());
+ }
+
+ public void test_getDeviceLocalAccounts_containsItemForEachDeviceAccount() {
+ final DeviceLocalAccountTypeFactory stubFactory = new FakeDeviceAccountTypeFactory()
+ .withDeviceTypes(null, "vnd.sec.contact.phone")
+ .withSimTypes("vnd.sec.contact.sim");
+ final DeviceLocalAccountLocator sut = new DeviceLocalAccountLocator(
+ createStubResolverWithContentQueryResult(queryResult(
+ "user", "com.example",
+ "user", "com.example",
+ "phone_account", "vnd.sec.contact.phone",
+ null, null,
+ "phone_account", "vnd.sec.contact.phone",
+ "user", "com.example",
+ null, null,
+ "sim_account", "vnd.sec.contact.sim",
+ "sim_account_2", "vnd.sec.contact.sim"
+ )), stubFactory,
+ Collections.<AccountWithDataSet>emptyList());
+
+ assertEquals(4, sut.getDeviceLocalAccounts().size());
+ }
+
+ public void test_getDeviceLocalAccounts_doesNotContainItemsForKnownAccounts() {
+ final DeviceLocalAccountLocator sut = new DeviceLocalAccountLocator(
+ getContext().getContentResolver(), new FakeDeviceAccountTypeFactory(),
+ Arrays.asList(new AccountWithDataSet("user", "com.example", null),
+ new AccountWithDataSet("user1", "com.example", null),
+ new AccountWithDataSet("user", "com.example.1", null)));
+
+ assertTrue("Selection should filter known accounts", sut.getSelection().contains("NOT IN (?,?)"));
+
+ final List<String> args = Arrays.asList(sut.getSelectionArgs());
+ assertEquals(2, args.size());
+ assertTrue("Selection args is missing an expected value", args.contains("com.example"));
+ assertTrue("Selection args is missing an expected value", args.contains("com.example.1"));
+ }
+
+ private DeviceLocalAccountLocator createWithQueryResult(
+ Cursor cursor) {
+ final DeviceLocalAccountLocator locator = new DeviceLocalAccountLocator(
+ createStubResolverWithContentQueryResult(cursor),
+ new DeviceLocalAccountTypeFactory.Default(getContext()),
+ Collections.<AccountWithDataSet>emptyList());
+ return locator;
+ }
+
+
+ private ContentResolver createStubResolverWithContentQueryResult(Cursor cursor) {
+ final MockContentResolver resolver = new MockContentResolver();
+ resolver.addProvider(ContactsContract.AUTHORITY, new FakeContactsProvider(cursor));
+ return resolver;
+ }
+
+ private Cursor queryResult(String... nameTypePairs) {
+ final MatrixCursor cursor = new MatrixCursor(new String[]
+ { RawContacts.ACCOUNT_NAME, RawContacts.ACCOUNT_TYPE, RawContacts.DATA_SET });
+ for (int i = 0; i < nameTypePairs.length; i+=2) {
+ cursor.newRow().add(nameTypePairs[i]).add(nameTypePairs[i+1])
+ .add(null);
+ }
+ return cursor;
+ }
+
+ private static class FakeContactsProvider extends MockContentProvider {
+ public Cursor mNextQueryResult;
+
+ public FakeContactsProvider() {}
+
+ public FakeContactsProvider(Cursor result) {
+ mNextQueryResult = result;
+ }
+
+ public void setNextQueryResult(Cursor result) {
+ mNextQueryResult = result;
+ }
+
+ @Override
+ public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
+ String sortOrder) {
+ return query(uri, projection, selection, selectionArgs, sortOrder, null);
+ }
+
+ @Nullable
+ @Override
+ public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
+ String sortOrder, CancellationSignal cancellationSignal) {
+ return mNextQueryResult;
+ }
+ }
+}
diff --git a/tests/src/com/android/contacts/common/preference/ContactsPreferencesTest.java b/tests/src/com/android/contacts/common/preference/ContactsPreferencesTest.java
index 26e811d..bbbd65c 100644
--- a/tests/src/com/android/contacts/common/preference/ContactsPreferencesTest.java
+++ b/tests/src/com/android/contacts/common/preference/ContactsPreferencesTest.java
@@ -142,9 +142,11 @@
.thenReturn(new AccountWithDataSet("name1", "type1", "dataset1").stringify(),
new AccountWithDataSet("name2", "type2", "dataset2").stringify());
- Assert.assertEquals("name1", mContactsPreferences.getDefaultAccount());
+ Assert.assertEquals(new AccountWithDataSet("name1", "type1", "dataset1"),
+ mContactsPreferences.getDefaultAccount());
mContactsPreferences.refreshValue(ACCOUNT_KEY);
- Assert.assertEquals("name2", mContactsPreferences.getDefaultAccount());
+ Assert.assertEquals(new AccountWithDataSet("name2", "type2", "dataset2"),
+ mContactsPreferences.getDefaultAccount());
}
}
diff --git a/tests/src/com/android/contacts/common/util/DeviceLocalContactsFilterProviderTests.java b/tests/src/com/android/contacts/common/util/DeviceLocalContactsFilterProviderTests.java
deleted file mode 100644
index d776ab8..0000000
--- a/tests/src/com/android/contacts/common/util/DeviceLocalContactsFilterProviderTests.java
+++ /dev/null
@@ -1,221 +0,0 @@
-/*
- * Copyright (C) 2016 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.common.util;
-
-import android.content.ContentProvider;
-import android.content.Context;
-import android.content.CursorLoader;
-import android.database.Cursor;
-import android.database.MatrixCursor;
-import android.net.Uri;
-import android.os.CancellationSignal;
-import android.provider.ContactsContract;
-import android.provider.ContactsContract.RawContacts;
-import android.support.annotation.Nullable;
-import android.test.LoaderTestCase;
-import android.test.mock.MockContentResolver;
-import android.test.mock.MockContext;
-import android.test.suitebuilder.annotation.SmallTest;
-
-import com.android.contacts.common.list.ContactListFilter;
-import com.android.contacts.common.test.mocks.MockContentProvider;
-
-import org.mockito.Mockito;
-
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.Set;
-
-import static org.mockito.Mockito.when;
-
-@SmallTest
-public class DeviceLocalContactsFilterProviderTests extends LoaderTestCase {
-
- // Basic smoke test that just checks that it doesn't throw when loading from CP2. We don't
- // care what CP2 actually contains for this.
- public void testShouldNotCrash() {
- final DeviceLocalContactsFilterProvider sut = new DeviceLocalContactsFilterProvider(
- getContext(), DeviceAccountFilter.ONLY_NULL);
- final CursorLoader loader = sut.onCreateLoader(0, null);
- getLoaderResultSynchronously(loader);
- // We didn't throw so it passed
- }
-
- public void testCreatesNoFiltersIfNoRawContactsHaveDeviceAccountType() {
- final DeviceLocalContactsFilterProvider sut = createWithFilterAndLoaderResult(
- DeviceAccountFilter.ONLY_NULL, queryResult(
- "user", "com.example",
- "user", "com.example",
- "user", "com.example"));
- sut.setKnownAccountTypes("com.example");
-
- doLoad(sut);
-
- assertEquals(0, sut.getListFilters().size());
- }
-
- public void testCreatesOneFilterForDeviceAccount() {
- final DeviceLocalContactsFilterProvider sut = createWithFilterAndLoaderResult(
- DeviceAccountFilter.ONLY_NULL, queryResult(
- "user", "com.example",
- "user", "com.example",
- null, null,
- "user", "com.example",
- null, null));
- sut.setKnownAccountTypes("com.example");
-
- doLoad(sut);
-
- assertEquals(1, sut.getListFilters().size());
- assertEquals(ContactListFilter.FILTER_TYPE_DEVICE_CONTACTS,
- sut.getListFilters().get(0).filterType);
- }
-
- public void testCreatesOneFilterForEachDeviceAccount() {
- final DeviceLocalContactsFilterProvider sut = createWithFilterAndLoaderResult(
- filterAllowing(null, "vnd.sec.contact.phone", "vnd.sec.contact.sim"), queryResult(
- "sim_account", "vnd.sec.contact.sim",
- "user", "com.example",
- "user", "com.example",
- "phone_account", "vnd.sec.contact.phone",
- null, null,
- "phone_account", "vnd.sec.contact.phone",
- "user", "com.example",
- null, null,
- "sim_account", "vnd.sec.contact.sim",
- "sim_account_2", "vnd.sec.contact.sim"
- ));
- sut.setKnownAccountTypes("com.example");
-
- doLoad(sut);
-
- assertEquals(4, sut.getListFilters().size());
- }
-
- public void testFilterIsUpdatedWhenLoaderReloads() {
- final FakeContactsProvider provider = new FakeContactsProvider();
- final DeviceLocalContactsFilterProvider sut = new DeviceLocalContactsFilterProvider(
- createStubContextWithContactsProvider(provider), DeviceAccountFilter.ONLY_NULL);
- sut.setKnownAccountTypes("com.example");
-
- provider.setNextQueryResult(queryResult(
- null, null,
- "user", "com.example",
- "user", "com.example"
- ));
- doLoad(sut);
-
- assertFalse(sut.getListFilters().isEmpty());
-
- provider.setNextQueryResult(queryResult(
- "user", "com.example",
- "user", "com.example"
- ));
- doLoad(sut);
-
- assertTrue(sut.getListFilters().isEmpty());
- }
-
- public void testDoesNotCreateFiltersForKnownAccounts() {
- final DeviceLocalContactsFilterProvider sut = new DeviceLocalContactsFilterProvider(
- getContext(), DeviceAccountFilter.ONLY_NULL);
- sut.setKnownAccountTypes("com.example", "maybe_syncable_device_account_type");
-
- final CursorLoader loader = sut.onCreateLoader(0, null);
-
- // The filtering is done at the DB level rather than in the code so just verify that
- // selection is about right.
- assertTrue("Loader selection is wrong", loader.getSelection().contains("NOT IN (?,?)"));
- assertEquals("com.example", loader.getSelectionArgs()[0]);
- assertEquals("maybe_syncable_device_account_type", loader.getSelectionArgs()[1]);
- }
-
- private void doLoad(DeviceLocalContactsFilterProvider loaderCallbacks) {
- final CursorLoader loader = loaderCallbacks.onCreateLoader(0, null);
- final Cursor cursor = getLoaderResultSynchronously(loader);
- loaderCallbacks.onLoadFinished(loader, cursor);
- }
-
- private DeviceLocalContactsFilterProvider createWithFilterAndLoaderResult(
- DeviceAccountFilter filter, Cursor cursor) {
- final DeviceLocalContactsFilterProvider result = new DeviceLocalContactsFilterProvider(
- createStubContextWithContentQueryResult(cursor), filter);
- return result;
- }
-
- private Context createStubContextWithContentQueryResult(final Cursor cursor) {
- return createStubContextWithContactsProvider(new FakeContactsProvider(cursor));
- }
-
- private Context createStubContextWithContactsProvider(ContentProvider contactsProvider) {
- final MockContentResolver resolver = new MockContentResolver();
- resolver.addProvider(ContactsContract.AUTHORITY, contactsProvider);
-
- final Context context = Mockito.mock(MockContext.class);
- when(context.getContentResolver()).thenReturn(resolver);
-
- // The loader pulls out the application context instead of usign the context directly
- when(context.getApplicationContext()).thenReturn(context);
-
- return context;
- }
-
- private Cursor queryResult(String... typeNamePairs) {
- final MatrixCursor cursor = new MatrixCursor(new String[]
- { RawContacts.ACCOUNT_NAME, RawContacts.ACCOUNT_TYPE });
- for (int i = 0; i < typeNamePairs.length; i += 2) {
- cursor.newRow().add(typeNamePairs[i]).add(typeNamePairs[i+1]);
- }
- return cursor;
- }
-
- private DeviceAccountFilter filterAllowing(String... accountTypes) {
- final Set<String> allowed = new HashSet<>(Arrays.asList(accountTypes));
- return new DeviceAccountFilter() {
- @Override
- public boolean isDeviceAccountType(String accountType) {
- return allowed.contains(accountType);
- }
- };
- }
-
- private static class FakeContactsProvider extends MockContentProvider {
- public Cursor mNextQueryResult;
-
- public FakeContactsProvider() {}
-
- public FakeContactsProvider(Cursor result) {
- mNextQueryResult = result;
- }
-
- public void setNextQueryResult(Cursor result) {
- mNextQueryResult = result;
- }
-
- @Override
- public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
- String sortOrder) {
- return query(uri, projection, selection, selectionArgs, sortOrder, null);
- }
-
- @Nullable
- @Override
- public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
- String sortOrder, CancellationSignal cancellationSignal) {
- return mNextQueryResult;
- }
- }
-}
diff --git a/tests/src/com/android/contacts/editor/ContactEditorUtilsTest.java b/tests/src/com/android/contacts/editor/ContactEditorUtilsTest.java
index 2e0306a..b5df8c9 100644
--- a/tests/src/com/android/contacts/editor/ContactEditorUtilsTest.java
+++ b/tests/src/com/android/contacts/editor/ContactEditorUtilsTest.java
@@ -272,7 +272,7 @@
assertTrue(mTarget.shouldShowAccountChangedNotification());
// We show the notification here, and user clicked "keep local" and saved an contact.
- mTarget.saveDefaultAndAllAccounts(null);
+ mTarget.saveDefaultAndAllAccounts(AccountWithDataSet.getLocalAccount());
// Now there are no accounts, and default account is null.
diff --git a/tests/src/com/android/contacts/editor/EditorUiUtilsTest.java b/tests/src/com/android/contacts/editor/EditorUiUtilsTest.java
index 19f28d3..e68511f 100644
--- a/tests/src/com/android/contacts/editor/EditorUiUtilsTest.java
+++ b/tests/src/com/android/contacts/editor/EditorUiUtilsTest.java
@@ -16,10 +16,6 @@
package com.android.contacts.editor;
-import com.android.contacts.R;
-import com.android.contacts.common.model.account.AccountType;
-import com.android.contacts.common.model.account.GoogleAccountType;
-
import android.content.Context;
import android.media.RingtoneManager;
import android.net.Uri;
@@ -27,7 +23,11 @@
import android.provider.Settings;
import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.SmallTest;
-import android.util.Pair;
+
+import com.android.contacts.common.model.account.AccountDisplayInfo;
+import com.android.contacts.R;
+import com.android.contacts.common.model.account.AccountType;
+import com.android.contacts.common.model.account.AccountWithDataSet;
/**
* Tests {@link EditorUiUtils}.
@@ -43,6 +43,11 @@
private static final String RINGTONE = "content://media/external/audio/media/31";
+ private static final AccountWithDataSet ACCOUNT =
+ new AccountWithDataSet(ACCOUNT_NAME, "some.account.type", null);
+ private static final AccountWithDataSet GOOGLE_ACCOUNT =
+ new AccountWithDataSet(ACCOUNT_NAME, "com.google", null);
+
private static final class MockAccountType extends AccountType {
private final String mDisplayLabel;
@@ -67,86 +72,65 @@
}
}
- public void testGetProfileAccountInfo_AccountName() {
- final Pair pair = EditorUiUtils.getLocalAccountInfo(getContext(),
- ACCOUNT_NAME, new MockAccountType(DISPLAY_LABEL));
+ public void testGetProfileAccountInfo_NonLocalAccount() {
+ final AccountDisplayInfo account = new AccountDisplayInfo(ACCOUNT, ACCOUNT_NAME,
+ DISPLAY_LABEL, /*icon*/ null, /*isDeviceAccount*/ false);
- assertNotNull(pair);
- assertEquals(ACCOUNT_NAME, pair.first);
- assertEquals(getContext().getString(R.string.external_profile_title, DISPLAY_LABEL),
- pair.second); // My LunkedIn profile
+ final String label = EditorUiUtils.getAccountHeaderLabelForMyProfile(getContext(),
+ account);
+
+ // My LunkedIn profile
+ final String expected = getContext()
+ .getString(R.string.external_profile_title, DISPLAY_LABEL);
+ assertEquals(expected, label);
}
- public void testGetProfileAccountInfo_NoAccountName() {
- final Pair pair = EditorUiUtils.getLocalAccountInfo(getContext(),
- /* accountName =*/ null, new MockAccountType(DISPLAY_LABEL));
- assertNotNull(pair);
- assertNull(pair.first);
- assertEquals(getContext().getString(R.string.local_profile_title),
- pair.second); // "My local profile
+ public void testGetProfileAccountInfo_DeviceLocalAccount() {
+ final AccountDisplayInfo account = new AccountDisplayInfo(ACCOUNT, "Device",
+ "Device", null, true);
+
+ final String label = EditorUiUtils.getAccountHeaderLabelForMyProfile(getContext(),
+ account);
+
+ // "My local profile"
+ final String expected = getContext().getString(R.string.local_profile_title);
+ assertEquals(expected, label);
}
- public void testGetAccountInfo_AccountName_DisplayLabel() {
- final Pair pair = EditorUiUtils.getAccountInfo(getContext(),
- ACCOUNT_NAME, new MockAccountType(DISPLAY_LABEL));
+ public void testGetAccountInfo_AccountType_NonGoogle() {
+ final AccountDisplayInfo account = new AccountDisplayInfo(ACCOUNT, ACCOUNT_NAME,
+ DISPLAY_LABEL, /*icon*/ null, /*isDeviceAccount*/ false);
- assertNotNull(pair);
- assertEquals(getContext().getString(R.string.from_account_format, ACCOUNT_NAME),
- pair.first); // somebody@lunkedin.com
- assertEquals(getContext().getString(R.string.account_type_format, DISPLAY_LABEL),
- pair.second); // LunkedIn Contact
+ final String label = EditorUiUtils.getAccountTypeHeaderLabel(getContext(), account);
+
+ // LunkedIn Contact
+ final String expected = getContext().getString(R.string.account_type_format, DISPLAY_LABEL);
+ assertEquals(expected, label);
}
- public void testGetAccountInfo_AccountName_DisplayLabel_GoogleAccountType() {
- final AccountType accountType = new MockAccountType(GOOGLE_DISPLAY_LABEL);
- accountType.accountType = GoogleAccountType.ACCOUNT_TYPE;
- final Pair pair = EditorUiUtils.getAccountInfo(getContext(),
- GOOGLE_ACCOUNT_NAME, accountType);
+ public void testGetAccountInfo_AccountType_Google() {
+ final AccountDisplayInfo account = new AccountDisplayInfo(GOOGLE_ACCOUNT, ACCOUNT_NAME,
+ GOOGLE_DISPLAY_LABEL, /*icon*/ null, /*isDeviceAccount*/ false);
- assertNotNull(pair);
- assertEquals(getContext().getString(R.string.from_account_format, GOOGLE_ACCOUNT_NAME),
- pair.first); // somebody@gmail.com
- assertEquals(
- getContext().getString(R.string.google_account_type_format, GOOGLE_DISPLAY_LABEL),
- pair.second); // Google Account
+ final String label = EditorUiUtils.getAccountTypeHeaderLabel(getContext(), account);
+
+ // Google Account
+ final String expected = getContext().getString(R.string.google_account_type_format,
+ GOOGLE_DISPLAY_LABEL);
+ assertEquals(expected, label);
}
- public void testGetAccountInfo_AccountName_NoDisplayLabel() {
- final Pair pair = EditorUiUtils.getAccountInfo(getContext(),
- ACCOUNT_NAME, new MockAccountType(/* displayLabel =*/ null));
+ public void testGetAccountInfo_AccountType_DeviceAccount() {
+ final AccountWithDataSet deviceAccount = AccountWithDataSet.getLocalAccount();
+ final AccountDisplayInfo account = new AccountDisplayInfo(deviceAccount, "Device",
+ "Device", /*icon*/ null, /*isDeviceAccount*/ true);
- assertNotNull(pair);
- assertEquals(getContext().getString(R.string.from_account_format, ACCOUNT_NAME),
- pair.first); // somebody@lunkedin.com
- assertEquals(getContext().getString(R.string.account_phone), pair.second); // Device
- }
+ final String label = EditorUiUtils.getAccountTypeHeaderLabel(getContext(), account);
- public void testGetAccountInfo_NoAccountName_DisplayLabel() {
- final Pair pair = EditorUiUtils.getAccountInfo(getContext(),
- /* accountName =*/ null, new MockAccountType(DISPLAY_LABEL));
-
- assertNotNull(pair);
- assertNull(pair.first);
- assertEquals(getContext().getString(R.string.account_type_format, DISPLAY_LABEL),
- pair.second); // LunkedIn contact
-
- final Pair pairDevice = EditorUiUtils.getAccountInfo(
- getContext(),
- /* accountName =*/ null,
- new MockAccountType(getContext().getString(R.string.account_phone)));
- assertNotNull(pairDevice);
- assertNull(pairDevice.first);
- assertEquals(getContext().getString(R.string.account_phone), pairDevice.second); // Device
- }
-
- public void testGetAccountInfo_NoAccountName_NoDisplayLabel() {
- final Pair pair = EditorUiUtils.getAccountInfo(getContext(),
- /* accountName =*/ null, new MockAccountType(/* displayLabel =*/ null));
-
- assertNotNull(pair);
- assertNull(pair.first);
- assertEquals(getContext().getString(R.string.account_phone), pair.second); // Device
+ // "Device"
+ final String expected = getContext().getString(R.string.account_phone);
+ assertEquals(expected, label);
}
public void testGetRingtongStrFromUri_lessThanOrEqualsToM() {
@@ -185,4 +169,8 @@
currentVersion));
}
+ private AccountDisplayInfo createDisplayableAccount() {
+ return new AccountDisplayInfo(ACCOUNT, ACCOUNT_NAME, DISPLAY_LABEL, null, false);
+ }
+
}
diff --git a/tests/src/com/android/contacts/tests/FakeAccountType.java b/tests/src/com/android/contacts/tests/FakeAccountType.java
new file mode 100644
index 0000000..59327d8
--- /dev/null
+++ b/tests/src/com/android/contacts/tests/FakeAccountType.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2016 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.tests;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.PixelFormat;
+import android.graphics.drawable.Drawable;
+
+import com.android.contacts.common.model.account.AccountType;
+import com.android.contacts.common.model.account.AccountWithDataSet;
+
+public class FakeAccountType extends AccountType {
+ public boolean areContactsWritable = false;
+ public boolean isGroupMembershipEditable = false;
+ public String displayLabel = "The Default Label";
+ public Drawable displayIcon = new Drawable() {
+ @Override
+ public void draw(Canvas canvas) {
+ }
+
+ @Override
+ public void setAlpha(int alpha) {
+ }
+
+ @Override
+ public void setColorFilter(ColorFilter colorFilter) {
+ }
+
+ @Override
+ public int getOpacity() {
+ return PixelFormat.OPAQUE;
+ }
+ };
+
+ public FakeAccountType() {
+ }
+
+ public FakeAccountType(String type) {
+ accountType = type;
+ }
+
+ @Override
+ public Drawable getDisplayIcon(Context context) {
+ return displayIcon;
+ }
+
+ @Override
+ public String getDisplayLabel(Context context) {
+ return displayLabel;
+ }
+
+ @Override
+ public boolean areContactsWritable() {
+ return areContactsWritable;
+ }
+
+ @Override
+ public boolean isGroupMembershipEditable() {
+ return isGroupMembershipEditable;
+ }
+
+ public static FakeAccountType create(String accountType, String label) {
+ final FakeAccountType result = new FakeAccountType();
+ result.accountType = accountType;
+ result.displayLabel = label;
+ return result;
+ }
+
+ public static FakeAccountType create(String accountType, String label, Drawable icon) {
+ final FakeAccountType result = new FakeAccountType();
+ result.accountType = accountType;
+ result.displayIcon = icon;
+ result.displayLabel = label;
+ return result;
+ }
+
+ public static AccountType create(AccountWithDataSet account, String label, Drawable icon) {
+ final FakeAccountType result = create(account.type, label, icon);
+ result.accountType = account.type;
+ result.dataSet = account.dataSet;
+ return result;
+ }
+}
diff --git a/tests/src/com/android/contacts/tests/FakeDeviceAccountTypeFactory.java b/tests/src/com/android/contacts/tests/FakeDeviceAccountTypeFactory.java
new file mode 100644
index 0000000..6a500ec
--- /dev/null
+++ b/tests/src/com/android/contacts/tests/FakeDeviceAccountTypeFactory.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2016 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.tests;
+
+import com.android.contacts.common.model.account.AccountType;
+import com.android.contacts.common.model.account.FallbackAccountType;
+import com.android.contacts.common.util.DeviceLocalAccountTypeFactory;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+public class FakeDeviceAccountTypeFactory implements DeviceLocalAccountTypeFactory {
+
+ private final Map<String, AccountType> mDeviceAccountTypes = new HashMap<>();
+ private final Map<String, AccountType> mSimAccountTypes = new HashMap<>();
+
+ @Override
+ public int classifyAccount(String accountType) {
+ if (mDeviceAccountTypes.containsKey(accountType)) {
+ return TYPE_DEVICE;
+ } else if (mSimAccountTypes.containsKey(accountType)) {
+ return TYPE_SIM;
+ } else {
+ return TYPE_OTHER;
+ }
+ }
+
+ @Override
+ public AccountType getAccountType(String accountType) {
+ final AccountType type = mDeviceAccountTypes.get(accountType);
+ return type == null ? mSimAccountTypes.get(accountType) : type;
+ }
+
+ public FakeDeviceAccountTypeFactory withSimTypes(String... types) {
+ for (String type : types) {
+ mSimAccountTypes.put(type, new FakeAccountType(type));
+ }
+ return this;
+ }
+
+ public FakeDeviceAccountTypeFactory withSimTypes(AccountType... types) {
+ for (AccountType type : types) {
+ mSimAccountTypes.put(type.accountType, type);
+ }
+ return this;
+ }
+
+ public FakeDeviceAccountTypeFactory withDeviceTypes(String... types) {
+ for (String type : types) {
+ mDeviceAccountTypes.put(type, new FakeAccountType(type));
+ }
+ return this;
+ }
+
+ public FakeDeviceAccountTypeFactory withDeviceTypes(AccountType... types) {
+ for (AccountType type : types) {
+ mDeviceAccountTypes.put(type.accountType, type);
+ }
+ return this;
+ }
+}
diff --git a/tests/src/com/android/contacts/util/AccountDisplayInfoFactoryTests.java b/tests/src/com/android/contacts/util/AccountDisplayInfoFactoryTests.java
new file mode 100644
index 0000000..875f40e
--- /dev/null
+++ b/tests/src/com/android/contacts/util/AccountDisplayInfoFactoryTests.java
@@ -0,0 +1,205 @@
+/*
+ * Copyright (C) 2016 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.util;
+
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.PixelFormat;
+import android.graphics.drawable.Drawable;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.contacts.common.model.AccountTypeManager;
+import com.android.contacts.common.model.account.AccountDisplayInfo;
+import com.android.contacts.common.model.account.AccountDisplayInfoFactory;
+import com.android.contacts.common.model.account.AccountType;
+import com.android.contacts.common.model.account.AccountWithDataSet;
+import com.android.contacts.common.test.mocks.MockAccountTypeManager;
+import com.android.contacts.common.util.DeviceLocalAccountTypeFactory;
+import com.android.contacts.tests.FakeAccountType;
+import com.android.contacts.tests.FakeDeviceAccountTypeFactory;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+
+@SmallTest
+public class AccountDisplayInfoFactoryTests extends AndroidTestCase {
+
+ private Map<AccountWithDataSet, AccountType> mKnownTypes;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mKnownTypes = new HashMap<>();
+ }
+
+ public void test_displayableAccount_hasIconFromAccountType() {
+ final Drawable comExampleIcon = someDrawable();
+
+ addTypeMapping(account("user", "com.example"), "title", comExampleIcon);
+ addTypeMapping(account(null, null), "device", someDrawable());
+ addTypeMapping(account("foo", "bar.type"), "bar", someDrawable());
+ addTypeMapping(account("user2", "com.example"), "title", comExampleIcon);
+
+ final AccountDisplayInfoFactory sut = createFactoryForKnownTypes();
+
+ final AccountDisplayInfo displayable = sut.getAccountDisplayInfo(
+ account("user", "com.example"));
+ assertEquals(comExampleIcon, displayable.getIcon());
+ }
+
+ public void test_displayableAccount_hasNameFromAccount() {
+ final Drawable comExampleIcon = someDrawable();
+
+ addTypeMapping(account("user@example.com", "com.example"), "title", comExampleIcon);
+ addTypeMapping(account(null, null), "device", someDrawable());
+ addTypeMapping(account("foo", "bar.type"), "bar", someDrawable());
+ addTypeMapping(account("user2@example.com", "com.example"), "title", comExampleIcon);
+
+ final AccountDisplayInfoFactory sut = createFactoryForKnownTypes();
+
+ final AccountDisplayInfo displayable = sut.getAccountDisplayInfo(
+ account("user@example.com", "com.example"));
+ assertEquals("user@example.com", displayable.getNameLabel());
+ }
+
+ public void test_displayableAccountForNullAccount_hasNameFromAccountType() {
+ addSomeKnownAccounts();
+ addTypeMapping(account(null, null), "Device Display Label", someDrawable());
+
+ final AccountDisplayInfoFactory sut = createFactoryForKnownTypes();
+
+ final AccountDisplayInfo displayable = sut.getAccountDisplayInfo(
+ account(null, null));
+ assertEquals("Device Display Label", displayable.getNameLabel());
+ }
+
+ public void test_displayableAccountForDeviceAccount_hasNameFromAccountType() {
+ addSomeKnownAccounts();
+ addTypeMapping(account("some.device.account.name", "device.account.type"), "Device Label",
+ someDrawable());
+
+ final AccountDisplayInfoFactory sut = createFactoryForKnownTypes(
+ new FakeDeviceAccountTypeFactory().withDeviceTypes("device.account.type"));
+
+ final AccountDisplayInfo displayable = sut.getAccountDisplayInfo(
+ account("some.device.account.name", "device.account.type"));
+ assertEquals("Device Label", displayable.getNameLabel());
+ }
+
+ public void test_displayableAccountForDeviceAccountWhenMultiple_hasNameFromAccount() {
+ addSomeKnownAccounts();
+ addTypeMapping(account("first.device.account.name", "a.device.account.type"),
+ "Device Display Label", someDrawable());
+ addTypeMapping(account("second.device.account.name", "b.device.account.type"),
+ "Device Display Label", someDrawable());
+ addTypeMapping(account("another.device.account.name", "a.device.account.type"),
+ "Device Display Label", someDrawable());
+
+ final AccountDisplayInfoFactory sut = createFactoryForKnownTypes(
+ new FakeDeviceAccountTypeFactory().withDeviceTypes("a.device.account.type",
+ "b.device.account.type"));
+
+ final AccountDisplayInfo displayable = sut.getAccountDisplayInfo(
+ account("first.device.account.name", "a.device.account.type"));
+ assertEquals("first.device.account.name", displayable.getNameLabel());
+
+ final AccountDisplayInfo displayable2 = sut.getAccountDisplayInfo(
+ account("second.device.account.name", "b.device.account.type"));
+ assertEquals("second.device.account.name", displayable2.getNameLabel());
+ }
+
+ public void test_displayableAccountForSimAccount_hasNameFromAccountType() {
+ addSomeKnownAccounts();
+ addTypeMapping(account("sim.account.name", "sim.account.type"), "SIM", someDrawable());
+
+ final AccountDisplayInfoFactory sut = createFactoryForKnownTypes(
+ new FakeDeviceAccountTypeFactory().withSimTypes("sim.account.type"));
+
+ final AccountDisplayInfo displayable = sut.getAccountDisplayInfo(
+ account("sim.account.name", "sim.account.type"));
+ assertEquals("SIM", displayable.getNameLabel());
+ }
+
+ public void test_displayableAccountForSimAccountWhenMultiple_hasNameFromAccount() {
+ addSomeKnownAccounts();
+ addTypeMapping(account("sim.account.name", "sim.account.type"), "SIM", someDrawable());
+ addTypeMapping(account("sim2.account.name", "sim.account.type"), "SIM", someDrawable());
+
+ final AccountDisplayInfoFactory sut = createFactoryForKnownTypes(
+ new FakeDeviceAccountTypeFactory().withSimTypes("sim.account.type"));
+
+ final AccountDisplayInfo displayable = sut.getAccountDisplayInfo(
+ account("sim.account.name", "sim.account.type"));
+ assertEquals("sim.account.name", displayable.getNameLabel());
+ }
+
+ private void addSomeKnownAccounts() {
+ final Drawable comExampleIcon = someDrawable();
+ addTypeMapping(account("user@example.com", "com.example"), "Example Title", comExampleIcon);
+ addTypeMapping(account("foo", "bar.type"), "Bar", someDrawable());
+ addTypeMapping(account("user2@example.com", "com.example"), "Example Title", comExampleIcon);
+ addTypeMapping(account("user", "com.example.two"), "Some Account", someDrawable());
+ }
+
+ private AccountDisplayInfoFactory createFactoryForKnownTypes() {
+ return createFactoryForKnownTypes(new DeviceLocalAccountTypeFactory.Default(getContext()));
+ }
+
+ private AccountDisplayInfoFactory createFactoryForKnownTypes(DeviceLocalAccountTypeFactory
+ typeFactory) {
+ return new AccountDisplayInfoFactory(getContext(),
+ createFakeAccountTypeManager(mKnownTypes), typeFactory,
+ new ArrayList<>(mKnownTypes.keySet()));
+ }
+
+ private AccountWithDataSet account(String name, String type) {
+ return new AccountWithDataSet(name, type, /* dataSet */ null);
+ }
+
+ private void addTypeMapping(AccountWithDataSet account, String label, Drawable icon) {
+ mKnownTypes.put(account, FakeAccountType.create(account, label, icon));
+ }
+
+ private AccountTypeManager createFakeAccountTypeManager(
+ final Map<AccountWithDataSet, AccountType> mapping) {
+ return new MockAccountTypeManager(mapping.values().toArray(new AccountType[mapping.size()]),
+ mapping.keySet().toArray(new AccountWithDataSet[mapping.size()]));
+ }
+
+ private Drawable someDrawable() {
+ return new Drawable() {
+ @Override
+ public void draw(Canvas canvas) {
+ }
+
+ @Override
+ public void setAlpha(int i) {
+ }
+
+ @Override
+ public void setColorFilter(ColorFilter colorFilter) {
+ }
+
+ @Override
+ public int getOpacity() {
+ return PixelFormat.OPAQUE;
+ }
+ };
+ }
+
+}