Swtich to use DefaultAccount related API to get and set default account for handling contacts.

Test: atest SettingsRoboTests:com.android.settings.applications.contacts.ContactsStorageSettingsTest
atest SettingsRoboTests:com.android.settings.applications.contacts.ContactsStoragePreferenceControllerTest
Bug: 368641291
Flag: com.android.settings.flags.enable_contacts_default_account_in_settings
Change-Id: I1164c120881714d051a6acc34e75d2b5de0f01b5
This commit is contained in:
Dongzhuo Zhang
2024-10-18 00:55:34 +00:00
parent 286dcff126
commit 8e3e3ea973
5 changed files with 657 additions and 424 deletions

View File

@@ -3324,27 +3324,27 @@
<!-- Dialog body informing user about an unmountable storage device. [CHAR LIMIT=NONE]--> <!-- Dialog body informing user about an unmountable storage device. [CHAR LIMIT=NONE]-->
<string name="storage_dialog_unmountable">This <xliff:g id="name" example="SD card">^1</xliff:g> is corrupted. <string name="storage_dialog_unmountable">This <xliff:g id="name" example="SD card">^1</xliff:g> is corrupted.
\n\nTo use this <xliff:g id="name" example="SD card">^1</xliff:g>, you have to set it up first.</string> \n\nTo use this <xliff:g id="name" example="SD card">^1</xliff:g>, you have to set it up first.</string>
<!-- Body of dialog informing user about consequences of formatting an internal storage device [CHAR LIMIT=NONE]--> <!-- Body of dialog informing user about consequences of formatting an internal storage device [CHAR LIMIT=NONE]-->
<string name="storage_internal_format_details">You can format this SD card to store photos, videos, music, <string name="storage_internal_format_details">You can format this SD card to store photos, videos, music,
and more and access them on other devices. and more and access them on other devices.
\n\n<b>All data on this SD card will be erased.</b> \n\n<b>All data on this SD card will be erased.</b>
\n\n<b>Before formatting</b> \n\n<b>Before formatting</b>
\n\n<b>Back up photos &amp; other media</b> \n\n<b>Back up photos &amp; other media</b>
\nMove your media files to alternative storage on this device, or transfer them to a computer using a USB cable. \nMove your media files to alternative storage on this device, or transfer them to a computer using a USB cable.
\n\n<b>Back up apps</b> \n\n<b>Back up apps</b>
\nAll apps stored on this <xliff:g id="name" example="SD card">^1</xliff:g> will be uninstalled and their data will be erased. To keep these apps, move them to alternative storage on this device.</string> \nAll apps stored on this <xliff:g id="name" example="SD card">^1</xliff:g> will be uninstalled and their data will be erased. To keep these apps, move them to alternative storage on this device.</string>
<!-- Body of dialog informing user about consequences of ejecting an internal storage device [CHAR LIMIT=NONE]--> <!-- Body of dialog informing user about consequences of ejecting an internal storage device [CHAR LIMIT=NONE]-->
<string name="storage_internal_unmount_details"><b>When you eject this <xliff:g id="name" example="SD card">^1</xliff:g>, apps stored on it will stop working, and media files stored on it will not be available until it is reinserted.</b> <string name="storage_internal_unmount_details"><b>When you eject this <xliff:g id="name" example="SD card">^1</xliff:g>, apps stored on it will stop working, and media files stored on it will not be available until it is reinserted.</b>
\n\nThis <xliff:g id="name" example="SD card">^1</xliff:g> is formatted to work on this device only. It won\u2019t work on any others.</string> \n\nThis <xliff:g id="name" example="SD card">^1</xliff:g> is formatted to work on this device only. It won\u2019t work on any others.</string>
<!-- Body of dialog informing user about consequences of forgetting an internal storage device [CHAR LIMIT=NONE]--> <!-- Body of dialog informing user about consequences of forgetting an internal storage device [CHAR LIMIT=NONE]-->
<string name="storage_internal_forget_details">To use the apps, photos, or data this <xliff:g id="name" example="SD card">^1</xliff:g> contains, reinsert it. <string name="storage_internal_forget_details">To use the apps, photos, or data this <xliff:g id="name" example="SD card">^1</xliff:g> contains, reinsert it.
\n\nAlternatively, you can choose to forget this storage if the device isn\u2019t available. \n\nAlternatively, you can choose to forget this storage if the device isn\u2019t available.
\n\nIf you choose to forget, all the data the device contains will be lost forever. \n\nIf you choose to forget, all the data the device contains will be lost forever.
\n\nYou can reinstall the apps later, but their data stored on this device will be lost.</string> \n\nYou can reinstall the apps later, but their data stored on this device will be lost.</string>
<!-- Title of dialog confirming that user wants to forget an internal storage device [CHAR LIMIT=32]--> <!-- Title of dialog confirming that user wants to forget an internal storage device [CHAR LIMIT=32]-->
<string name="storage_internal_forget_confirm_title">Forget <xliff:g id="name" example="SD card">^1</xliff:g>?</string> <string name="storage_internal_forget_confirm_title">Forget <xliff:g id="name" example="SD card">^1</xliff:g>?</string>
@@ -3378,7 +3378,7 @@
<string name="storage_wizard_move_confirm_title">Move <xliff:g id="app" example="Calculator">^1</xliff:g></string> <string name="storage_wizard_move_confirm_title">Move <xliff:g id="app" example="Calculator">^1</xliff:g></string>
<!-- Body of wizard step prompting user to move an app [CHAR LIMIT=NONE] --> <!-- Body of wizard step prompting user to move an app [CHAR LIMIT=NONE] -->
<string name="storage_wizard_move_confirm_body">Moving <xliff:g id="app" example="Calculator">^1</xliff:g> and its data to <xliff:g id="name" example="SD card">^2</xliff:g> will take only a few moments. You won\u2019t be able to use the app until the move is complete. <string name="storage_wizard_move_confirm_body">Moving <xliff:g id="app" example="Calculator">^1</xliff:g> and its data to <xliff:g id="name" example="SD card">^2</xliff:g> will take only a few moments. You won\u2019t be able to use the app until the move is complete.
\n\nDon\u2019t remove the <xliff:g id="name" example="SD card">^2</xliff:g> during the move. \n\nDon\u2019t remove the <xliff:g id="name" example="SD card">^2</xliff:g> during the move.
</string> </string>
<!-- Body of lock screen challenge message explaining that the given user must be unlocked before data can be moved [CHAR LIMIT=64] --> <!-- Body of lock screen challenge message explaining that the given user must be unlocked before data can be moved [CHAR LIMIT=64] -->
@@ -3388,7 +3388,7 @@
<string name="storage_wizard_move_progress_title">Moving <xliff:g id="app" example="Calculator">^1</xliff:g>\u2026</string> <string name="storage_wizard_move_progress_title">Moving <xliff:g id="app" example="Calculator">^1</xliff:g>\u2026</string>
<!-- Body of wizard step showing app move progress [CHAR LIMIT=NONE] --> <!-- Body of wizard step showing app move progress [CHAR LIMIT=NONE] -->
<string name="storage_wizard_move_progress_body">Don\u2019t remove the <xliff:g id="name" example="SD card">^1</xliff:g> during the move. <string name="storage_wizard_move_progress_body">Don\u2019t remove the <xliff:g id="name" example="SD card">^1</xliff:g> during the move.
\n\nThe <xliff:g id="app" example="Calculator">^2</xliff:g> app on this device won\u2019t be available until the move is complete. \n\nThe <xliff:g id="app" example="Calculator">^2</xliff:g> app on this device won\u2019t be available until the move is complete.
</string> </string>
<!-- This is the title of a full-screen message. After this question, the user will get to choose how they want to use the storage device that they have in their phone. The placeholder is for the specific device (e.g. SD card, USB drive, etc.). [CHAR LIMIT=32] --> <!-- This is the title of a full-screen message. After this question, the user will get to choose how they want to use the storage device that they have in their phone. The placeholder is for the specific device (e.g. SD card, USB drive, etc.). [CHAR LIMIT=32] -->
@@ -3408,7 +3408,7 @@
<string name="storage_wizard_format_confirm_v2_title">Format this <xliff:g id="name" example="SD card">^1</xliff:g>?</string> <string name="storage_wizard_format_confirm_v2_title">Format this <xliff:g id="name" example="SD card">^1</xliff:g>?</string>
<!-- Body of a dialog. This text is confirming that the user wants to use their SD card as extra phone storage, but the formatting process will erase existing content on the card. The first placeholder is for the name of the device (e.g. a brand name of the SD card or USB drive). The second and third placeholders are for the general references (e.g. SD card, USB drive). [CHAR LIMIT=NONE] --> <!-- Body of a dialog. This text is confirming that the user wants to use their SD card as extra phone storage, but the formatting process will erase existing content on the card. The first placeholder is for the name of the device (e.g. a brand name of the SD card or USB drive). The second and third placeholders are for the general references (e.g. SD card, USB drive). [CHAR LIMIT=NONE] -->
<string name="storage_wizard_format_confirm_v2_body">This <xliff:g id="name" example="SanDisk SD card">^1</xliff:g> needs to be formatted to store apps, files, and media. <string name="storage_wizard_format_confirm_v2_body">This <xliff:g id="name" example="SanDisk SD card">^1</xliff:g> needs to be formatted to store apps, files, and media.
\n\nFormatting will erase existing content on the <xliff:g id="name" example="SD card">^2</xliff:g>. To avoid losing content, back it up to another <xliff:g id="name" example="SD card">^3</xliff:g> or device.</string> \n\nFormatting will erase existing content on the <xliff:g id="name" example="SD card">^2</xliff:g>. To avoid losing content, back it up to another <xliff:g id="name" example="SD card">^3</xliff:g> or device.</string>
<!-- Body of a dialog. This text is confirming that the user wants to use their SD card as portable storage, but the formatting process will erase existing content on the card. The first placeholder is for the name of the device (e.g. a brand name of the SD card or USB drive). The second and third placeholders are for the general references (e.g. SD card, USB drive). [CHAR LIMIT=NONE] --> <!-- Body of a dialog. This text is confirming that the user wants to use their SD card as portable storage, but the formatting process will erase existing content on the card. The first placeholder is for the name of the device (e.g. a brand name of the SD card or USB drive). The second and third placeholders are for the general references (e.g. SD card, USB drive). [CHAR LIMIT=NONE] -->
<string name="storage_wizard_format_confirm_v2_body_external">This <xliff:g id="name" example="SanDisk SD card">^1</xliff:g> needs to be formatted to store photos, videos, music, and more. <string name="storage_wizard_format_confirm_v2_body_external">This <xliff:g id="name" example="SanDisk SD card">^1</xliff:g> needs to be formatted to store photos, videos, music, and more.
\n\nFormatting will erase existing content on the <xliff:g id="name" example="SD card">^2</xliff:g>. To avoid losing content, back it up to another <xliff:g id="name" example="SD card">^3</xliff:g> or device.</string> \n\nFormatting will erase existing content on the <xliff:g id="name" example="SD card">^2</xliff:g>. To avoid losing content, back it up to another <xliff:g id="name" example="SD card">^3</xliff:g> or device.</string>
@@ -3437,8 +3437,8 @@
<string name="storage_wizard_slow_v2_title">Slow <xliff:g id="name" example="SD card">^1</xliff:g></string> <string name="storage_wizard_slow_v2_title">Slow <xliff:g id="name" example="SD card">^1</xliff:g></string>
<!-- Strings that are part of a full-screen message. These strings let the user know that their storage device is slow, and include some options they can try. The first placeholder is for the name of the storage device (e.g. brand name of the SD card), and the second and third placeholders are for the general references (e.g. SD card, USB drive, etc.). [CHAR LIMIT=NONE] --> <!-- Strings that are part of a full-screen message. These strings let the user know that their storage device is slow, and include some options they can try. The first placeholder is for the name of the storage device (e.g. brand name of the SD card), and the second and third placeholders are for the general references (e.g. SD card, USB drive, etc.). [CHAR LIMIT=NONE] -->
<string name="storage_wizard_slow_v2_body">You can still use this <xliff:g id="name" example="SanDisk SD card">^1</xliff:g>, but it may be slow. <string name="storage_wizard_slow_v2_body">You can still use this <xliff:g id="name" example="SanDisk SD card">^1</xliff:g>, but it may be slow.
\n\nApps stored on this <xliff:g id="name" example="SD card">^2</xliff:g> may not work properly, and content transfers could take a long time. \n\nApps stored on this <xliff:g id="name" example="SD card">^2</xliff:g> may not work properly, and content transfers could take a long time.
\n\nTry using a faster <xliff:g id="name" example="SD card">^3</xliff:g>, or use this <xliff:g id="name" example="SD card">^4</xliff:g> for portable storage instead.</string> \n\nTry using a faster <xliff:g id="name" example="SD card">^3</xliff:g>, or use this <xliff:g id="name" example="SD card">^4</xliff:g> for portable storage instead.</string>
<!-- Action of a dialog. This action will start the wizard from the beginning, letting the user make an alternative choice. [CHAR LIMIT=32] --> <!-- Action of a dialog. This action will start the wizard from the beginning, letting the user make an alternative choice. [CHAR LIMIT=32] -->
<string name="storage_wizard_slow_v2_start_over">Start over</string> <string name="storage_wizard_slow_v2_start_over">Start over</string>
<!-- Action of a dialog. This action will continue the wizard, meaning the user acknowledges their card is slow. [CHAR LIMIT=32] --> <!-- Action of a dialog. This action will continue the wizard, meaning the user acknowledges their card is slow. [CHAR LIMIT=32] -->
@@ -13729,8 +13729,12 @@
<string name="contacts_storage_device_only_preference_summary">New contacts won\'t be synced with an account</string> <string name="contacts_storage_device_only_preference_summary">New contacts won\'t be synced with an account</string>
<!-- Text for explaining the selection in Contacts Storage Settings [CHAR LIMIT=NONE] --> <!-- Text for explaining the selection in Contacts Storage Settings [CHAR LIMIT=NONE] -->
<string name="contacts_storage_selection_message">Contacts will be saved to your device and synced to your account by default</string> <string name="contacts_storage_selection_message">Contacts will be saved to your device and synced to your account by default</string>
<!-- Text for error toast when there's error setting the default account in Contacts Storage Settings [CHAR LIMIT=NONE] -->
<string name="contacts_storage_set_default_account_error_message">Error setting the default account</string>
<!-- Text for displaying when no account is set as default account [CHAR LIMIT=50] --> <!-- Text for displaying when no account is set as default account [CHAR LIMIT=50] -->
<string name="contacts_storage_no_account_set">No default set</string> <string name="contacts_storage_no_account_set_summary">No default set</string>
<!-- Text for displaying when default account is set as local only [CHAR LIMIT=50] -->
<string name="contacts_storage_local_account_summary">Device only</string>
<!-- Text for add account selection message when no account has been added [CHAR LIMIT=100] --> <!-- Text for add account selection message when no account has been added [CHAR LIMIT=100] -->
<string name="contacts_storage_first_time_add_account_message">Add an account to get started</string> <string name="contacts_storage_first_time_add_account_message">Add an account to get started</string>

View File

@@ -1,4 +1,3 @@
/* /*
* Copyright (C) 2024 The Android Open Source Project * Copyright (C) 2024 The Android Open Source Project
* *
@@ -16,12 +15,13 @@
*/ */
package com.android.settings.applications.contacts; package com.android.settings.applications.contacts;
import static android.provider.ContactsContract.RawContacts.DefaultAccount;
import android.accounts.Account; import android.accounts.Account;
import android.content.Context; import android.content.Context;
import android.os.UserHandle; import android.os.UserHandle;
import android.provider.ContactsContract; import android.provider.ContactsContract.RawContacts.DefaultAccount.DefaultAccountAndState;
import android.util.Log;
import androidx.preference.PreferenceScreen;
import com.android.settings.R; import com.android.settings.R;
import com.android.settings.core.BasePreferenceController; import com.android.settings.core.BasePreferenceController;
@@ -36,33 +36,53 @@ public class ContactsStoragePreferenceController extends BasePreferenceControlle
private final AuthenticatorHelper mAuthenticatorHelper; private final AuthenticatorHelper mAuthenticatorHelper;
private DefaultAccountAndState mCurrentDefaultAccountAndState;
public ContactsStoragePreferenceController(Context context, String preferenceKey) { public ContactsStoragePreferenceController(Context context, String preferenceKey) {
super(context, preferenceKey); super(context, preferenceKey);
mAuthenticatorHelper = new AuthenticatorHelper(mContext, mAuthenticatorHelper = new AuthenticatorHelper(mContext,
new UserHandle(UserHandle.myUserId()), null); new UserHandle(UserHandle.myUserId()), null);
try {
mCurrentDefaultAccountAndState =
DefaultAccount.getDefaultAccountForNewContacts(mContext.getContentResolver());
} catch (IllegalStateException e) {
Log.e(TAG, "The default account is in an invalid state: " + e);
} catch (RuntimeException e) {
Log.e(TAG, "Failed to look up the default account: " + e);
}
} }
@Override @Override
public int getAvailabilityStatus() { public int getAvailabilityStatus() {
return Flags.enableContactsDefaultAccountInSettings() return (Flags.enableContactsDefaultAccountInSettings()
? AVAILABLE : CONDITIONALLY_UNAVAILABLE; && mCurrentDefaultAccountAndState != null) ? AVAILABLE : CONDITIONALLY_UNAVAILABLE;
} }
@Override @Override
public CharSequence getSummary() { public CharSequence getSummary() {
Account currentDefaultAccount = if (mCurrentDefaultAccountAndState != null) {
ContactsContract.Settings.getDefaultAccount(mContext.getContentResolver()); int currentDefaultAccountState = mCurrentDefaultAccountAndState.getState();
if (currentDefaultAccount == null) { Account currentDefaultAccount = mCurrentDefaultAccountAndState.getAccount();
if (currentDefaultAccountState
== DefaultAccountAndState.DEFAULT_ACCOUNT_STATE_NOT_SET) {
return mContext.getResources().getString( return mContext.getResources().getString(
R.string.contacts_storage_no_account_set); R.string.contacts_storage_no_account_set_summary);
} } else if (currentDefaultAccountState
== DefaultAccountAndState.DEFAULT_ACCOUNT_STATE_LOCAL) {
return mContext.getResources().getString(
R.string.contacts_storage_local_account_summary);
} else if (currentDefaultAccount != null) {
String accountTypeLabel = (String) mAuthenticatorHelper.getLabelForType(mContext, String accountTypeLabel = (String) mAuthenticatorHelper.getLabelForType(mContext,
currentDefaultAccount.type); currentDefaultAccount.type);
// If there's no account type, or the account type is the same as the // If there's no account type, or the account type is the same as the
// current default account name, just return the account name. // current default account name, just return the account name.
if (accountTypeLabel == null || accountTypeLabel.equals(currentDefaultAccount.name)) { if (accountTypeLabel == null || accountTypeLabel.equals(
currentDefaultAccount.name)) {
return currentDefaultAccount.name; return currentDefaultAccount.name;
} }
return accountTypeLabel + " | " + currentDefaultAccount.name; return accountTypeLabel + " | " + currentDefaultAccount.name;
} }
}
return "";
}
} }

View File

@@ -1,4 +1,3 @@
/* /*
* Copyright (C) 2020 The Android Open Source Project * Copyright (C) 2020 The Android Open Source Project
* *
@@ -17,25 +16,27 @@
package com.android.settings.applications.contacts; package com.android.settings.applications.contacts;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.provider.ContactsContract.RawContacts.DefaultAccount;
import static android.provider.Settings.ACTION_ADD_ACCOUNT; import static android.provider.Settings.ACTION_ADD_ACCOUNT;
import static android.provider.Settings.EXTRA_ACCOUNT_TYPES; import static android.provider.Settings.EXTRA_ACCOUNT_TYPES;
import android.accounts.Account; import android.accounts.Account;
import android.accounts.AccountManager;
import android.app.settings.SettingsEnums; import android.app.settings.SettingsEnums;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.res.Resources; import android.content.res.Resources;
import android.os.Bundle; import android.os.Bundle;
import android.os.UserHandle; import android.os.UserHandle;
import android.provider.ContactsContract.Settings; import android.provider.ContactsContract.RawContacts.DefaultAccount.DefaultAccountAndState;
import android.widget.Toast;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.UiThread; import androidx.annotation.UiThread;
import androidx.preference.Preference; import androidx.preference.Preference;
import androidx.preference.Preference.OnPreferenceClickListener; import androidx.preference.Preference.OnPreferenceClickListener;
import androidx.preference.PreferenceScreen; import androidx.preference.PreferenceScreen;
import com.android.internal.annotations.VisibleForTesting;
import com.android.settings.R; import com.android.settings.R;
import com.android.settings.accounts.AddAccountSettings; import com.android.settings.accounts.AddAccountSettings;
import com.android.settings.dashboard.DashboardFragment; import com.android.settings.dashboard.DashboardFragment;
@@ -46,6 +47,7 @@ import com.android.settingslib.search.SearchIndexable;
import com.android.settingslib.widget.SelectorWithWidgetPreference; import com.android.settingslib.widget.SelectorWithWidgetPreference;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
/** /**
@@ -59,7 +61,7 @@ public class ContactsStorageSettings extends DashboardFragment
private static final String TAG = "ContactsStorageSettings"; private static final String TAG = "ContactsStorageSettings";
private static final String PREF_KEY_ADD_ACCOUNT = "add_account"; private static final String PREF_KEY_ADD_ACCOUNT = "add_account";
private static final String PREF_KEY_DEVICE_ONLY = "device_only_account_preference"; private static final String PREF_KEY_DEVICE_ONLY = "device_only_account_preference";
private final Map<String, Account> mAccountMap = new HashMap<>(); private final Map<String, DefaultAccountAndState> mAccountMap = new HashMap<>();
private AuthenticatorHelper mAuthenticatorHelper; private AuthenticatorHelper mAuthenticatorHelper;
@Override @Override
@@ -73,13 +75,18 @@ public class ContactsStorageSettings extends DashboardFragment
@Override @Override
public void onRadioButtonClicked(@NonNull SelectorWithWidgetPreference selectedPref) { public void onRadioButtonClicked(@NonNull SelectorWithWidgetPreference selectedPref) {
final String selectedPreferenceKey = selectedPref.getKey(); final String selectedPreferenceKey = selectedPref.getKey();
// Check if current provider is different from the selected provider. // Check if current account is different from the selected account.
for (String preferenceKey : mAccountMap.keySet()) { for (String preferenceKey : mAccountMap.keySet()) {
if (selectedPreferenceKey.equals(preferenceKey)) { if (selectedPreferenceKey.equals(preferenceKey)) {
try {
DefaultAccount.setDefaultAccountForNewContacts(getContentResolver(),
mAccountMap.get(preferenceKey));
selectedPref.setChecked(true); selectedPref.setChecked(true);
//TODO: Call DefaultAccount.setDefaultAccountForNewContacts once } catch (RuntimeException e) {
// the implementation is ready. Toast.makeText(getContext(),
Settings.setDefaultAccount(getContentResolver(), mAccountMap.get(preferenceKey)); R.string.contacts_storage_set_default_account_error_message,
Toast.LENGTH_SHORT).show();
}
} else { } else {
SelectorWithWidgetPreference unSelectedPreference = SelectorWithWidgetPreference unSelectedPreference =
getPreferenceScreen().findPreference(preferenceKey); getPreferenceScreen().findPreference(preferenceKey);
@@ -92,10 +99,7 @@ public class ContactsStorageSettings extends DashboardFragment
public boolean onPreferenceClick(@NonNull Preference preference) { public boolean onPreferenceClick(@NonNull Preference preference) {
if (PREF_KEY_ADD_ACCOUNT.equals(preference.getKey())) { if (PREF_KEY_ADD_ACCOUNT.equals(preference.getKey())) {
Resources resources = Resources.getSystem(); String[] accountTypesArray = getEligibleAccountTypes();
String[] accountTypesArray =
resources.getStringArray(
com.android.internal.R.array.config_rawContactsEligibleDefaultAccountTypes);
Intent intent = new Intent(ACTION_ADD_ACCOUNT); Intent intent = new Intent(ACTION_ADD_ACCOUNT);
intent.setClass(getContext(), AddAccountSettings.class); intent.setClass(getContext(), AddAccountSettings.class);
intent.putExtra(EXTRA_ACCOUNT_TYPES, accountTypesArray); intent.putExtra(EXTRA_ACCOUNT_TYPES, accountTypesArray);
@@ -119,48 +123,60 @@ public class ContactsStorageSettings extends DashboardFragment
// when creating eligible account preferences. // when creating eligible account preferences.
mAccountMap.clear(); mAccountMap.clear();
final PreferenceScreen screen = getPreferenceScreen(); final PreferenceScreen screen = getPreferenceScreen();
AccountManager accountManager = AccountManager.get(getPrefContext()); List<Account> accounts = DefaultAccount.getEligibleCloudAccounts(getContentResolver());
//TODO: Call DefaultAccount.getDefaultAccountForNewContacts once for (int i = 0; i < accounts.size(); i++) {
// implementation is ready. screen.addPreference(buildAccountPreference(accounts.get(i), /*order=*/i));
Account[] accounts = accountManager.getAccounts(); }
// If there's no eligible account types, the "Add Account" preference should
for (int i = 0; i < accounts.length; i++) { // not be shown to the users.
screen.addPreference(buildAccountPreference(accounts[i], i)); if (getEligibleAccountTypes().length > 0) {
screen.addPreference(buildAddAccountPreference(accounts.isEmpty()));
} }
screen.addPreference(buildAddAccountPreference(accounts.length == 0));
setupDeviceOnlyPreference(); setupDeviceOnlyPreference();
setDefaultAccountPreference();
//TODO: Call DefaultAccount.ListEligibleCloudAccounts once the
// implementation is ready. And differentiate device only account vs account not set case.
Account currentDefaultAccount = Settings.getDefaultAccount(getContentResolver());
String preferenceKey = currentDefaultAccount != null ?
String.valueOf(currentDefaultAccount.hashCode()) : PREF_KEY_DEVICE_ONLY;
SelectorWithWidgetPreference preference = getPreferenceScreen().findPreference(
preferenceKey);
if (preference != null) {
preference.setChecked(true);
}
} }
private void setupDeviceOnlyPreference() { private void setupDeviceOnlyPreference() {
SelectorWithWidgetPreference preference = findPreference(PREF_KEY_DEVICE_ONLY); SelectorWithWidgetPreference preference = findPreference(PREF_KEY_DEVICE_ONLY);
if (preference != null) { if (preference != null) {
preference.setOnClickListener(this); preference.setOnClickListener(this);
mAccountMap.put(PREF_KEY_DEVICE_ONLY, null); mAccountMap.put(PREF_KEY_DEVICE_ONLY, DefaultAccountAndState.ofLocal());
}
}
private void setDefaultAccountPreference() {
DefaultAccountAndState currentDefaultAccountAndState =
DefaultAccount.getDefaultAccountForNewContacts(getContentResolver());
String preferenceKey = getAccountHashCode(currentDefaultAccountAndState);
Account currentDefaultAccount = currentDefaultAccountAndState.getAccount();
// Set the current default account preference to be checked if found among existing
// preferences. If not, then create a new preference for default account.
SelectorWithWidgetPreference preference = null;
if (mAccountMap.containsKey(preferenceKey)) {
preference = getPreferenceScreen().findPreference(preferenceKey);
} else if (preferenceKey != null && currentDefaultAccount != null) {
preference = buildAccountPreference(currentDefaultAccount, mAccountMap.size());
getPreferenceScreen().addPreference(preference);
}
if (preference != null) {
preference.setChecked(true);
} }
} }
//TODO: Add preference category on account preferences. //TODO: Add preference category on account preferences.
private Preference buildAccountPreference(Account account, int order) { private SelectorWithWidgetPreference buildAccountPreference(Account account, int order) {
SelectorWithWidgetPreference preference = new SelectorWithWidgetPreference( SelectorWithWidgetPreference preference = new SelectorWithWidgetPreference(
getPrefContext()); getPrefContext());
DefaultAccountAndState accountAndState = DefaultAccountAndState.ofCloud(account);
String preferenceKey = getAccountHashCode(accountAndState);
preference.setTitle(mAuthenticatorHelper.getLabelForType(getPrefContext(), account.type)); preference.setTitle(mAuthenticatorHelper.getLabelForType(getPrefContext(), account.type));
preference.setIcon(mAuthenticatorHelper.getDrawableForType(getPrefContext(), account.type)); preference.setIcon(mAuthenticatorHelper.getDrawableForType(getPrefContext(), account.type));
preference.setSummary(account.name); preference.setSummary(account.name);
preference.setKey(String.valueOf(account.hashCode())); preference.setKey(preferenceKey);
preference.setOnClickListener(this); preference.setOnClickListener(this);
preference.setOrder(order); preference.setOrder(order);
mAccountMap.put(String.valueOf(account.hashCode()), account); mAccountMap.put(preferenceKey, accountAndState);
return preference; return preference;
} }
@@ -178,6 +194,29 @@ public class ContactsStorageSettings extends DashboardFragment
return preference; return preference;
} }
private @Nullable String getAccountHashCode(DefaultAccountAndState currentDefaultAccountAndState) {
Account currentDefaultAccount = currentDefaultAccountAndState.getAccount();
if (currentDefaultAccount != null && (currentDefaultAccountAndState.getState()
== DefaultAccountAndState.DEFAULT_ACCOUNT_STATE_CLOUD
|| currentDefaultAccountAndState.getState()
== DefaultAccountAndState.DEFAULT_ACCOUNT_STATE_SIM)) {
return String.valueOf(currentDefaultAccount.hashCode());
} else if (currentDefaultAccountAndState.getState()
== DefaultAccountAndState.DEFAULT_ACCOUNT_STATE_LOCAL) {
return PREF_KEY_DEVICE_ONLY;
} else {
// If the account is not set or in error state, it should just return null and won't
// set the checked status in radio button.
return null;
}
}
@VisibleForTesting
String[] getEligibleAccountTypes() {
return Resources.getSystem().getStringArray(
com.android.internal.R.array.config_rawContactsEligibleDefaultAccountTypes);
}
@Override @Override
protected int getPreferenceScreenResId() { protected int getPreferenceScreenResId() {
return R.xml.contacts_storage_settings; return R.xml.contacts_storage_settings;

View File

@@ -16,8 +16,9 @@
package com.android.settings.applications.contacts; package com.android.settings.applications.contacts;
import static android.provider.ContactsContract.Settings.KEY_DEFAULT_ACCOUNT; import static android.provider.ContactsContract.RawContacts.DefaultAccount.KEY_DEFAULT_ACCOUNT_STATE;
import static android.provider.ContactsContract.Settings.QUERY_DEFAULT_ACCOUNT_METHOD; import static android.provider.ContactsContract.RawContacts.DefaultAccount.QUERY_DEFAULT_ACCOUNT_FOR_NEW_CONTACTS_METHOD;
import static android.provider.ContactsContract.Settings;
import static com.android.settings.core.BasePreferenceController.AVAILABLE; import static com.android.settings.core.BasePreferenceController.AVAILABLE;
import static com.android.settings.core.BasePreferenceController.CONDITIONALLY_UNAVAILABLE; import static com.android.settings.core.BasePreferenceController.CONDITIONALLY_UNAVAILABLE;
@@ -28,25 +29,26 @@ import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import android.accounts.Account; import android.accounts.Account;
import android.accounts.AccountManager; import android.accounts.AccountManager;
import android.content.ContentProviderClient;
import android.content.ContentResolver; import android.content.ContentResolver;
import android.content.Context; import android.content.Context;
import android.content.res.Resources; import android.content.res.Resources;
import android.os.Bundle; import android.os.Bundle;
import android.platform.test.annotations.EnableFlags; import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.RequiresFlagsDisabled;
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.platform.test.flag.junit.SetFlagsRule; import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.ContactsContract; import android.provider.ContactsContract;
import android.provider.ContactsContract.RawContacts.DefaultAccount.DefaultAccountAndState;
import com.android.settings.R; import com.android.settings.R;
import com.android.settings.flags.Flags; import com.android.settings.flags.Flags;
import com.android.settings.testutils.shadow.ShadowAuthenticationHelper; import com.android.settings.testutils.shadow.ShadowAuthenticationHelper;
import org.junit.Before; import org.junit.Before;
import android.platform.test.annotations.RequiresFlagsDisabled;
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
@@ -62,11 +64,6 @@ public class ContactsStoragePreferenceControllerTest {
private static final String CONTACTS_DEFAULT_ACCOUNT_PREFERENCE_KEY = private static final String CONTACTS_DEFAULT_ACCOUNT_PREFERENCE_KEY =
"contacts_default_account"; "contacts_default_account";
private static final Account TEST_ACCOUNT1 = new Account("test@gmail.com", "type1");
private static final Account TEST_ACCOUNT2 = new Account("test@samsung.com", "type2");
private static final Account TEST_ACCOUNT3 = new Account("LABEL3", "type3");
@Rule @Rule
public final MockitoRule mockito = MockitoJUnit.rule(); public final MockitoRule mockito = MockitoJUnit.rule();
@@ -83,6 +80,9 @@ public class ContactsStoragePreferenceControllerTest {
@Mock @Mock
private ContentResolver mContentResolver; private ContentResolver mContentResolver;
@Mock
private ContentProviderClient mContentProviderClient;
@Mock @Mock
private Resources mResources; private Resources mResources;
@@ -94,9 +94,15 @@ public class ContactsStoragePreferenceControllerTest {
@Before @Before
public void setUp() throws Exception { public void setUp() throws Exception {
when(mContext.getContentResolver()).thenReturn(mContentResolver); when(mContext.getContentResolver()).thenReturn(mContentResolver);
when(mContentResolver.acquireContentProviderClient(
eq(ContactsContract.AUTHORITY_URI))).thenReturn(mContentProviderClient);
when(mContext.getSystemService(eq(Context.ACCOUNT_SERVICE))).thenReturn(mAccountManager); when(mContext.getSystemService(eq(Context.ACCOUNT_SERVICE))).thenReturn(mAccountManager);
when(mAccountManager.getAccountsAsUser(anyInt())).thenReturn(new Account[]{}); when(mAccountManager.getAccountsAsUser(anyInt())).thenReturn(new Account[]{});
Bundle bundle = new Bundle();
bundle.putInt(KEY_DEFAULT_ACCOUNT_STATE,
DefaultAccountAndState.DEFAULT_ACCOUNT_STATE_NOT_SET);
when(mContentProviderClient.call(eq(QUERY_DEFAULT_ACCOUNT_FOR_NEW_CONTACTS_METHOD), any(),
any())).thenReturn(bundle);
mPreferenceController = new ContactsStoragePreferenceController(mContext, mPreferenceController = new ContactsStoragePreferenceController(mContext,
CONTACTS_DEFAULT_ACCOUNT_PREFERENCE_KEY); CONTACTS_DEFAULT_ACCOUNT_PREFERENCE_KEY);
} }
@@ -104,13 +110,39 @@ public class ContactsStoragePreferenceControllerTest {
@Test @Test
@EnableFlags(Flags.FLAG_ENABLE_CONTACTS_DEFAULT_ACCOUNT_IN_SETTINGS) @EnableFlags(Flags.FLAG_ENABLE_CONTACTS_DEFAULT_ACCOUNT_IN_SETTINGS)
public void getAvailabilityStatus_flagIsOn_shouldReturnAvailable() { public void getAvailabilityStatus_flagIsOn_shouldReturnAvailable() {
assertThat(mPreferenceController.getAvailabilityStatus()).isEqualTo(AVAILABLE); assertThat(mPreferenceController.getAvailabilityStatus()).isEqualTo(AVAILABLE);
} }
@Test @Test
@RequiresFlagsDisabled(Flags.FLAG_ENABLE_CONTACTS_DEFAULT_ACCOUNT_IN_SETTINGS) @RequiresFlagsDisabled(Flags.FLAG_ENABLE_CONTACTS_DEFAULT_ACCOUNT_IN_SETTINGS)
public void getAvailabilityStatus_flagIsOff_shouldReturnConditionallyUnavailable() { public void getAvailabilityStatus_flagIsOff_shouldReturnConditionallyUnavailable() {
assertThat(mPreferenceController.getAvailabilityStatus()).isEqualTo(
CONDITIONALLY_UNAVAILABLE);
}
@Test
@EnableFlags(Flags.FLAG_ENABLE_CONTACTS_DEFAULT_ACCOUNT_IN_SETTINGS)
public void getAvailabilityStatus_illegalStateExceptionThrown_shouldReturnConditionallyUnavailable()
throws Exception {
when(mContentProviderClient.call(eq(QUERY_DEFAULT_ACCOUNT_FOR_NEW_CONTACTS_METHOD), any(),
any())).thenThrow(new IllegalStateException());
mPreferenceController = new ContactsStoragePreferenceController(mContext,
CONTACTS_DEFAULT_ACCOUNT_PREFERENCE_KEY);
assertThat(mPreferenceController.getAvailabilityStatus()).isEqualTo(
CONDITIONALLY_UNAVAILABLE);
}
@Test
@EnableFlags(Flags.FLAG_ENABLE_CONTACTS_DEFAULT_ACCOUNT_IN_SETTINGS)
public void getAvailabilityStatus_runtimeExceptionThrown_shouldReturnConditionallyUnavailable()
throws Exception {
when(mContentProviderClient.call(eq(QUERY_DEFAULT_ACCOUNT_FOR_NEW_CONTACTS_METHOD), any(),
any())).thenThrow(new RuntimeException());
mPreferenceController = new ContactsStoragePreferenceController(mContext,
CONTACTS_DEFAULT_ACCOUNT_PREFERENCE_KEY);
assertThat(mPreferenceController.getAvailabilityStatus()).isEqualTo( assertThat(mPreferenceController.getAvailabilityStatus()).isEqualTo(
CONDITIONALLY_UNAVAILABLE); CONDITIONALLY_UNAVAILABLE);
@@ -118,43 +150,77 @@ public class ContactsStoragePreferenceControllerTest {
@Test @Test
public void getSummary_noAccountIsSetAsDefault_shouldReturnNoAccountSetSummary() { public void getSummary_noAccountIsSetAsDefault_shouldReturnNoAccountSetSummary() {
Bundle bundle = new Bundle();
bundle.putParcelable(KEY_DEFAULT_ACCOUNT, null);
when(mContentResolver.call(eq(ContactsContract.AUTHORITY_URI),
eq(QUERY_DEFAULT_ACCOUNT_METHOD), any(), any())).thenReturn(bundle);
when(mContext.getResources()).thenReturn(mResources); when(mContext.getResources()).thenReturn(mResources);
when(mResources.getString(eq(R.string.contacts_storage_no_account_set))).thenReturn( when(mResources.getString(eq(R.string.contacts_storage_no_account_set_summary))).thenReturn(
"No default set"); "No default set");
// Fetch the default account from CP2.
mPreferenceController = new ContactsStoragePreferenceController(mContext,
CONTACTS_DEFAULT_ACCOUNT_PREFERENCE_KEY);
assertThat(mPreferenceController.getSummary()).isEqualTo("No default set"); assertThat(mPreferenceController.getSummary()).isEqualTo("No default set");
} }
@Test @Test
public void getSummary_googleAccountIsSetAsDefault_shouldReturnGoogleAccountTypeAndAccountName() { public void getSummary_localAccountIsSetAsDefault_shouldReturnLocalAccountSetSummary()
throws Exception {
Bundle bundle = new Bundle(); Bundle bundle = new Bundle();
bundle.putParcelable(KEY_DEFAULT_ACCOUNT, TEST_ACCOUNT1); bundle.putInt(KEY_DEFAULT_ACCOUNT_STATE,
when(mContentResolver.call(eq(ContactsContract.AUTHORITY_URI), DefaultAccountAndState.DEFAULT_ACCOUNT_STATE_LOCAL);
eq(QUERY_DEFAULT_ACCOUNT_METHOD), any(), any())).thenReturn(bundle); when(mContentProviderClient.call(eq(QUERY_DEFAULT_ACCOUNT_FOR_NEW_CONTACTS_METHOD), any(),
any())).thenReturn(bundle);
when(mContext.getResources()).thenReturn(mResources);
when(mResources.getString(eq(R.string.contacts_storage_local_account_summary))).thenReturn(
"Device only");
mPreferenceController = new ContactsStoragePreferenceController(mContext,
CONTACTS_DEFAULT_ACCOUNT_PREFERENCE_KEY);
assertThat(mPreferenceController.getSummary()).isEqualTo("Device only");
}
@Test
public void getSummary_googleAccountIsSetAsDefault_shouldReturnGoogleAccountTypeAndAccountName()
throws Exception {
Bundle bundle = new Bundle();
bundle.putInt(KEY_DEFAULT_ACCOUNT_STATE,
DefaultAccountAndState.DEFAULT_ACCOUNT_STATE_CLOUD);
bundle.putString(Settings.ACCOUNT_TYPE, "type1");
bundle.putString(Settings.ACCOUNT_NAME, "test@gmail.com");
when(mContentProviderClient.call(eq(QUERY_DEFAULT_ACCOUNT_FOR_NEW_CONTACTS_METHOD), any(),
any())).thenReturn(bundle);
mPreferenceController = new ContactsStoragePreferenceController(mContext,
CONTACTS_DEFAULT_ACCOUNT_PREFERENCE_KEY);
assertThat(mPreferenceController.getSummary()).isEqualTo("LABEL1 | test@gmail.com"); assertThat(mPreferenceController.getSummary()).isEqualTo("LABEL1 | test@gmail.com");
} }
@Test @Test
public void getSummary_samsungAccountIsSetAsDefault_shouldReturnSamsungAccountTypeAndAccountName() { public void getSummary_samsungAccountIsSetAsDefault_shouldReturnSamsungAccountTypeAndAccountName()
throws Exception {
Bundle bundle = new Bundle(); Bundle bundle = new Bundle();
bundle.putParcelable(KEY_DEFAULT_ACCOUNT, TEST_ACCOUNT2); bundle.putInt(KEY_DEFAULT_ACCOUNT_STATE,
when(mContentResolver.call(eq(ContactsContract.AUTHORITY_URI), DefaultAccountAndState.DEFAULT_ACCOUNT_STATE_CLOUD);
eq(QUERY_DEFAULT_ACCOUNT_METHOD), any(), any())).thenReturn(bundle); bundle.putString(Settings.ACCOUNT_TYPE, "type2");
bundle.putString(Settings.ACCOUNT_NAME, "test@samsung.com");
when(mContentProviderClient.call(eq(QUERY_DEFAULT_ACCOUNT_FOR_NEW_CONTACTS_METHOD), any(),
any())).thenReturn(bundle);
mPreferenceController = new ContactsStoragePreferenceController(mContext,
CONTACTS_DEFAULT_ACCOUNT_PREFERENCE_KEY);
assertThat(mPreferenceController.getSummary()).isEqualTo("LABEL2 | test@samsung.com"); assertThat(mPreferenceController.getSummary()).isEqualTo("LABEL2 | test@samsung.com");
} }
@Test @Test
public void getSummary_accountLabelSameAsAccountName_onlyReturnAccountName() { public void getSummary_accountLabelSameAsAccountName_onlyReturnAccountName() throws Exception {
Bundle bundle = new Bundle(); Bundle bundle = new Bundle();
bundle.putParcelable(KEY_DEFAULT_ACCOUNT, TEST_ACCOUNT3); bundle.putInt(KEY_DEFAULT_ACCOUNT_STATE,
when(mContentResolver.call(eq(ContactsContract.AUTHORITY_URI), DefaultAccountAndState.DEFAULT_ACCOUNT_STATE_CLOUD);
eq(QUERY_DEFAULT_ACCOUNT_METHOD), any(), any())).thenReturn(bundle); bundle.putString(Settings.ACCOUNT_TYPE, "type3");
bundle.putString(Settings.ACCOUNT_NAME, "LABEL3");
when(mContentProviderClient.call(eq(QUERY_DEFAULT_ACCOUNT_FOR_NEW_CONTACTS_METHOD), any(),
any())).thenReturn(bundle);
mPreferenceController = new ContactsStoragePreferenceController(mContext,
CONTACTS_DEFAULT_ACCOUNT_PREFERENCE_KEY);
// Since package name and account name is the same, we only return account name. // Since package name and account name is the same, we only return account name.
assertThat(mPreferenceController.getSummary()).isEqualTo("LABEL3"); assertThat(mPreferenceController.getSummary()).isEqualTo("LABEL3");

View File

@@ -15,16 +15,17 @@
*/ */
package com.android.settings.applications.contacts; package com.android.settings.applications.contacts;
import static android.provider.ContactsContract.Settings.KEY_DEFAULT_ACCOUNT; import static android.provider.ContactsContract.RawContacts.DefaultAccount.KEY_DEFAULT_ACCOUNT_STATE;
import static android.provider.ContactsContract.Settings.QUERY_DEFAULT_ACCOUNT_METHOD; import static android.provider.ContactsContract.RawContacts.DefaultAccount.KEY_ELIGIBLE_DEFAULT_ACCOUNTS;
import static android.provider.ContactsContract.Settings.SET_DEFAULT_ACCOUNT_METHOD; import static android.provider.ContactsContract.RawContacts.DefaultAccount.QUERY_DEFAULT_ACCOUNT_FOR_NEW_CONTACTS_METHOD;
import static android.provider.ContactsContract.RawContacts.DefaultAccount.QUERY_ELIGIBLE_DEFAULT_ACCOUNTS_METHOD;
import static android.provider.ContactsContract.RawContacts.DefaultAccount.SET_DEFAULT_ACCOUNT_FOR_NEW_CONTACTS_METHOD;
import static android.provider.Settings.ACTION_ADD_ACCOUNT; import static android.provider.Settings.ACTION_ADD_ACCOUNT;
import static android.provider.Settings.EXTRA_ACCOUNT_TYPES; import static android.provider.Settings.EXTRA_ACCOUNT_TYPES;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.spy; import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
@@ -33,14 +34,17 @@ import static org.mockito.Mockito.when;
import android.accounts.Account; import android.accounts.Account;
import android.accounts.AccountManager; import android.accounts.AccountManager;
import android.app.settings.SettingsEnums; import android.app.settings.SettingsEnums;
import android.content.ContentProviderClient;
import android.content.ContentResolver; import android.content.ContentResolver;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.res.Resources; import android.content.res.Resources;
import android.os.Bundle; import android.os.Bundle;
import android.provider.ContactsContract; import android.provider.ContactsContract;
import android.provider.ContactsContract.RawContacts.DefaultAccount.DefaultAccountAndState;
import android.provider.SearchIndexableResource; import android.provider.SearchIndexableResource;
import androidx.preference.Preference;
import androidx.preference.PreferenceManager; import androidx.preference.PreferenceManager;
import androidx.preference.PreferenceScreen; import androidx.preference.PreferenceScreen;
import androidx.test.core.app.ApplicationProvider; import androidx.test.core.app.ApplicationProvider;
@@ -63,6 +67,7 @@ import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment; import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config; import org.robolectric.annotation.Config;
import java.util.ArrayList;
import java.util.List; import java.util.List;
@RunWith(RobolectricTestRunner.class) @RunWith(RobolectricTestRunner.class)
@@ -76,6 +81,8 @@ public class ContactsStorageSettingsTest {
private static final Account TEST_ACCOUNT2 = new Account("test@samsung.com", "type2"); private static final Account TEST_ACCOUNT2 = new Account("test@samsung.com", "type2");
private static final Account TEST_ACCOUNT3 = new Account("test@outlook.com", "type3");
@Rule @Rule
public final MockitoRule mockito = MockitoJUnit.rule(); public final MockitoRule mockito = MockitoJUnit.rule();
@Spy @Spy
@@ -83,8 +90,7 @@ public class ContactsStorageSettingsTest {
@Mock @Mock
private ContentResolver mContentResolver; private ContentResolver mContentResolver;
@Mock @Mock
private AccountManager mAccountManager; private ContentProviderClient mContentProviderClient;
private PreferenceManager mPreferenceManager; private PreferenceManager mPreferenceManager;
private TestContactsStorageSettings mContactsStorageSettings; private TestContactsStorageSettings mContactsStorageSettings;
private PreferenceScreen mScreen; private PreferenceScreen mScreen;
@@ -92,8 +98,8 @@ public class ContactsStorageSettingsTest {
@Before @Before
public void setUp() throws Exception { public void setUp() throws Exception {
mContactsStorageSettings = spy(new TestContactsStorageSettings(mContext, mContentResolver)); mContactsStorageSettings = spy(new TestContactsStorageSettings(mContext, mContentResolver));
when(mContext.getSystemService(eq(Context.ACCOUNT_SERVICE))).thenReturn(mAccountManager); when(mContentResolver.acquireContentProviderClient(
when(mAccountManager.getAccountsAsUser(anyInt())).thenReturn(new Account[]{}); eq(ContactsContract.AUTHORITY_URI))).thenReturn(mContentProviderClient);
mPreferenceManager = new PreferenceManager(mContext); mPreferenceManager = new PreferenceManager(mContext);
when(mContactsStorageSettings.getPreferenceManager()).thenReturn(mPreferenceManager); when(mContactsStorageSettings.getPreferenceManager()).thenReturn(mPreferenceManager);
mScreen = spy(new PreferenceScreen(mContext, /* attrs= */ null)); mScreen = spy(new PreferenceScreen(mContext, /* attrs= */ null));
@@ -115,12 +121,17 @@ public class ContactsStorageSettingsTest {
} }
@Test @Test
public void verifyDeviceOnlyPreference_onClick_setDefaultAccountToNull() { public void verifyDeviceOnlyPreference_onClick_setDefaultAccountToNull() throws Exception {
when(mAccountManager.getAccounts()).thenReturn(new Account[]{}); Bundle currentDefaultAccount = new Bundle();
Bundle bundle = new Bundle(); currentDefaultAccount.putInt(KEY_DEFAULT_ACCOUNT_STATE,
bundle.putParcelable(KEY_DEFAULT_ACCOUNT, null); DefaultAccountAndState.DEFAULT_ACCOUNT_STATE_NOT_SET);
when(mContentResolver.call(eq(ContactsContract.AUTHORITY_URI), when(mContentProviderClient.call(eq(QUERY_DEFAULT_ACCOUNT_FOR_NEW_CONTACTS_METHOD), any(),
eq(QUERY_DEFAULT_ACCOUNT_METHOD), any(), any())).thenReturn(bundle); any())).thenReturn(currentDefaultAccount);
Bundle eligibleAccountBundle = new Bundle();
eligibleAccountBundle.putParcelableArrayList(KEY_ELIGIBLE_DEFAULT_ACCOUNTS,
new ArrayList<>());
when(mContentProviderClient.call(eq(QUERY_ELIGIBLE_DEFAULT_ACCOUNTS_METHOD), any(),
any())).thenReturn(eligibleAccountBundle);
PreferenceScreen settingScreen = mPreferenceManager.inflateFromResource(mContext, PreferenceScreen settingScreen = mPreferenceManager.inflateFromResource(mContext,
R.xml.contacts_storage_settings, mScreen); R.xml.contacts_storage_settings, mScreen);
@@ -139,18 +150,27 @@ public class ContactsStorageSettingsTest {
assertThat(deviceOnlyPreference.isChecked()).isTrue(); assertThat(deviceOnlyPreference.isChecked()).isTrue();
ArgumentCaptor<Bundle> captor = ArgumentCaptor.forClass(Bundle.class); ArgumentCaptor<Bundle> captor = ArgumentCaptor.forClass(Bundle.class);
verify(mContentResolver).call(eq(ContactsContract.AUTHORITY_URI), verify(mContentProviderClient).call(eq(SET_DEFAULT_ACCOUNT_FOR_NEW_CONTACTS_METHOD), any(),
eq(SET_DEFAULT_ACCOUNT_METHOD), any(), captor.capture()); captor.capture());
Bundle accountBundle = captor.getValue(); Bundle accountBundle = captor.getValue();
assertThat(accountBundle.getString(ContactsContract.Settings.ACCOUNT_NAME)).isNull(); assertThat(accountBundle.getString(ContactsContract.Settings.ACCOUNT_NAME)).isNull();
assertThat(accountBundle.getString(ContactsContract.Settings.ACCOUNT_TYPE)).isNull(); assertThat(accountBundle.getString(ContactsContract.Settings.ACCOUNT_TYPE)).isNull();
} }
@Test @Test
public void verifyAddAccountPreference_onClick_startAddAccountActivity() { public void verifyAddAccountPreference_eligibleAccountsAvailable_startAddAccountActivityOnClick()
when(mAccountManager.getAccounts()).thenReturn(new Account[]{}); throws Exception {
when(mContentResolver.call(eq(ContactsContract.AUTHORITY_URI), Bundle currentDefaultAccount = new Bundle();
eq(QUERY_DEFAULT_ACCOUNT_METHOD), any(), any())).thenReturn(Bundle.EMPTY); currentDefaultAccount.putInt(KEY_DEFAULT_ACCOUNT_STATE,
DefaultAccountAndState.DEFAULT_ACCOUNT_STATE_NOT_SET);
when(mContentProviderClient.call(eq(QUERY_DEFAULT_ACCOUNT_FOR_NEW_CONTACTS_METHOD), any(),
any())).thenReturn(currentDefaultAccount);
Bundle eligibleAccountBundle = new Bundle();
eligibleAccountBundle.putParcelableArrayList(KEY_ELIGIBLE_DEFAULT_ACCOUNTS,
new ArrayList<>());
when(mContentProviderClient.call(eq(QUERY_ELIGIBLE_DEFAULT_ACCOUNTS_METHOD), any(),
any())).thenReturn(eligibleAccountBundle);
mContactsStorageSettings.setEligibleAccountTypes(new String[]{"com.google"});
mContactsStorageSettings.refreshUI(); mContactsStorageSettings.refreshUI();
@@ -167,16 +187,45 @@ public class ContactsStorageSettingsTest {
assertThat(addAccountIntent.getComponent().getClassName()).isEqualTo( assertThat(addAccountIntent.getComponent().getClassName()).isEqualTo(
AddAccountSettings.class.getCanonicalName()); AddAccountSettings.class.getCanonicalName());
String[] eligibleAccounts = (String[]) addAccountIntent.getExtra(EXTRA_ACCOUNT_TYPES); String[] eligibleAccounts = (String[]) addAccountIntent.getExtra(EXTRA_ACCOUNT_TYPES);
assertThat(eligibleAccounts).isEmpty(); assertThat(List.of(eligibleAccounts)).containsExactly("com.google");
} }
@Test @Test
public void verifyEligibleAccountPreference_onClick_setSelectedDefaultAccount() { public void verifyAddAccountPreference_noEligibleAccountsAvailable_dontShowPreference()
when(mAccountManager.getAccounts()).thenReturn(new Account[]{TEST_ACCOUNT1, TEST_ACCOUNT2}); throws Exception {
Bundle bundle = new Bundle(); Bundle currentDefaultAccount = new Bundle();
bundle.putParcelable(KEY_DEFAULT_ACCOUNT, TEST_ACCOUNT2); currentDefaultAccount.putInt(KEY_DEFAULT_ACCOUNT_STATE,
when(mContentResolver.call(eq(ContactsContract.AUTHORITY_URI), DefaultAccountAndState.DEFAULT_ACCOUNT_STATE_NOT_SET);
eq(QUERY_DEFAULT_ACCOUNT_METHOD), any(), any())).thenReturn(bundle); when(mContentProviderClient.call(eq(QUERY_DEFAULT_ACCOUNT_FOR_NEW_CONTACTS_METHOD), any(),
any())).thenReturn(currentDefaultAccount);
Bundle eligibleAccountBundle = new Bundle();
eligibleAccountBundle.putParcelableArrayList(KEY_ELIGIBLE_DEFAULT_ACCOUNTS,
new ArrayList<>());
when(mContentProviderClient.call(eq(QUERY_ELIGIBLE_DEFAULT_ACCOUNTS_METHOD), any(),
any())).thenReturn(eligibleAccountBundle);
mContactsStorageSettings.setEligibleAccountTypes(new String[]{});
mContactsStorageSettings.refreshUI();
Preference addAccountPreference = mScreen.findPreference(PREF_KEY_ADD_ACCOUNT);
assertThat(addAccountPreference).isNull();
}
@Test
public void verifyEligibleAccountPreference_onClick_setSelectedDefaultAccount()
throws Exception {
Bundle currentDefaultAccount = new Bundle();
currentDefaultAccount.putInt(KEY_DEFAULT_ACCOUNT_STATE,
DefaultAccountAndState.DEFAULT_ACCOUNT_STATE_LOCAL);
when(mContentProviderClient.call(eq(QUERY_DEFAULT_ACCOUNT_FOR_NEW_CONTACTS_METHOD), any(),
any())).thenReturn(currentDefaultAccount);
Bundle eligibleAccountBundle = new Bundle();
ArrayList<Account> eligibleAccounts = new ArrayList<>(
List.of(TEST_ACCOUNT1, TEST_ACCOUNT2));
eligibleAccountBundle.putParcelableArrayList(KEY_ELIGIBLE_DEFAULT_ACCOUNTS,
eligibleAccounts);
when(mContentProviderClient.call(eq(QUERY_ELIGIBLE_DEFAULT_ACCOUNTS_METHOD), any(),
any())).thenReturn(eligibleAccountBundle);
mContactsStorageSettings.refreshUI(); mContactsStorageSettings.refreshUI();
@@ -197,8 +246,8 @@ public class ContactsStorageSettingsTest {
assertThat(account2Preference.isChecked()).isTrue(); assertThat(account2Preference.isChecked()).isTrue();
ArgumentCaptor<Bundle> captor = ArgumentCaptor.forClass(Bundle.class); ArgumentCaptor<Bundle> captor = ArgumentCaptor.forClass(Bundle.class);
verify(mContentResolver).call(eq(ContactsContract.AUTHORITY_URI), verify(mContentProviderClient).call(eq(SET_DEFAULT_ACCOUNT_FOR_NEW_CONTACTS_METHOD), any(),
eq(SET_DEFAULT_ACCOUNT_METHOD), any(), captor.capture()); captor.capture());
Bundle setAccountBundle = captor.getValue(); Bundle setAccountBundle = captor.getValue();
assertThat(setAccountBundle.getString(ContactsContract.Settings.ACCOUNT_NAME)).isEqualTo( assertThat(setAccountBundle.getString(ContactsContract.Settings.ACCOUNT_NAME)).isEqualTo(
"test@samsung.com"); "test@samsung.com");
@@ -206,6 +255,49 @@ public class ContactsStorageSettingsTest {
"type2"); "type2");
} }
@Test
public void verifyAccountPreference_defaultAccountIsNotEligibleCloudAccount_createNewDefaultAccountPreference()
throws Exception {
Bundle currentDefaultAccount = new Bundle();
currentDefaultAccount.putInt(KEY_DEFAULT_ACCOUNT_STATE,
DefaultAccountAndState.DEFAULT_ACCOUNT_STATE_CLOUD);
currentDefaultAccount.putString(ContactsContract.Settings.ACCOUNT_NAME, TEST_ACCOUNT3.name);
currentDefaultAccount.putString(ContactsContract.Settings.ACCOUNT_TYPE, TEST_ACCOUNT3.type);
when(mContentProviderClient.call(eq(QUERY_DEFAULT_ACCOUNT_FOR_NEW_CONTACTS_METHOD), any(),
any())).thenReturn(currentDefaultAccount);
Bundle eligibleAccountBundle = new Bundle();
ArrayList<Account> eligibleAccounts = new ArrayList<>(
List.of(TEST_ACCOUNT1, TEST_ACCOUNT2));
eligibleAccountBundle.putParcelableArrayList(KEY_ELIGIBLE_DEFAULT_ACCOUNTS,
eligibleAccounts);
when(mContentProviderClient.call(eq(QUERY_ELIGIBLE_DEFAULT_ACCOUNTS_METHOD), any(),
any())).thenReturn(eligibleAccountBundle);
mContactsStorageSettings.refreshUI();
SelectorWithWidgetPreference account1Preference = mScreen.findPreference(
String.valueOf(TEST_ACCOUNT1.hashCode()));
assertThat(account1Preference.getTitle()).isEqualTo("LABEL1");
assertThat(account1Preference.getSummary()).isEqualTo("test@gmail.com");
assertThat(account1Preference.getIcon()).isNotNull();
SelectorWithWidgetPreference account2Preference = mScreen.findPreference(
String.valueOf(TEST_ACCOUNT2.hashCode()));
assertThat(account2Preference.getTitle()).isEqualTo("LABEL2");
assertThat(account2Preference.getSummary()).isEqualTo("test@samsung.com");
assertThat(account2Preference.getIcon()).isNotNull();
SelectorWithWidgetPreference account3Preference = mScreen.findPreference(
String.valueOf(TEST_ACCOUNT3.hashCode()));
assertThat(account3Preference.getTitle()).isEqualTo("LABEL3");
assertThat(account3Preference.getSummary()).isEqualTo("test@outlook.com");
assertThat(account3Preference.getIcon()).isNotNull();
assertThat(account1Preference.isChecked()).isFalse();
assertThat(account2Preference.isChecked()).isFalse();
assertThat(account3Preference.isChecked()).isTrue();
}
@Test @Test
public void searchIndexProvider_shouldIndexResource() { public void searchIndexProvider_shouldIndexResource() {
final List<SearchIndexableResource> indexRes = final List<SearchIndexableResource> indexRes =
@@ -220,6 +312,7 @@ public class ContactsStorageSettingsTest {
private static class TestContactsStorageSettings extends ContactsStorageSettings { private static class TestContactsStorageSettings extends ContactsStorageSettings {
private final Context mContext; private final Context mContext;
private final ContentResolver mContentResolver; private final ContentResolver mContentResolver;
private String[] mEligibleAccountTypes;
TestContactsStorageSettings(Context context, ContentResolver contentResolver) { TestContactsStorageSettings(Context context, ContentResolver contentResolver) {
mContext = context; mContext = context;
@@ -236,5 +329,16 @@ public class ContactsStorageSettingsTest {
// Override it so we can access this method in test // Override it so we can access this method in test
return mContentResolver; return mContentResolver;
} }
@Override
String[] getEligibleAccountTypes() {
return mEligibleAccountTypes == null ? Resources.getSystem().getStringArray(
com.android.internal.R.array.config_rawContactsEligibleDefaultAccountTypes)
: mEligibleAccountTypes;
}
public void setEligibleAccountTypes(String[] eligibleAccountTypes) {
mEligibleAccountTypes = eligibleAccountTypes;
}
} }
} }