• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.settings.applications.contacts;
17 
18 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
19 import static android.provider.ContactsContract.RawContacts.DefaultAccount;
20 import static android.provider.Settings.ACTION_ADD_ACCOUNT;
21 import static android.provider.Settings.EXTRA_ACCOUNT_TYPES;
22 
23 import android.accounts.Account;
24 import android.app.settings.SettingsEnums;
25 import android.content.Context;
26 import android.content.Intent;
27 import android.content.res.Resources;
28 import android.os.Bundle;
29 import android.os.UserHandle;
30 import android.provider.ContactsContract.RawContacts.DefaultAccount.DefaultAccountAndState;
31 import android.util.Log;
32 import android.widget.Toast;
33 
34 import androidx.annotation.NonNull;
35 import androidx.annotation.Nullable;
36 import androidx.annotation.UiThread;
37 import androidx.preference.Preference;
38 import androidx.preference.Preference.OnPreferenceClickListener;
39 import androidx.preference.PreferenceGroup;
40 
41 import com.android.internal.annotations.VisibleForTesting;
42 import com.android.settings.R;
43 import com.android.settings.accounts.AddAccountSettings;
44 import com.android.settings.dashboard.DashboardFragment;
45 import com.android.settings.search.BaseSearchIndexProvider;
46 import com.android.settingslib.RestrictedPreference;
47 import com.android.settingslib.accounts.AuthenticatorHelper;
48 import com.android.settingslib.search.SearchIndexable;
49 import com.android.settingslib.widget.SelectorWithWidgetPreference;
50 
51 import java.util.HashMap;
52 import java.util.List;
53 import java.util.Map;
54 
55 /**
56  * Settings page for contacts default account
57  */
58 @SearchIndexable
59 public class ContactsStorageSettings extends DashboardFragment
60         implements SelectorWithWidgetPreference.OnClickListener, OnPreferenceClickListener,
61         AuthenticatorHelper.OnAccountsUpdateListener {
62     public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
63             new BaseSearchIndexProvider(R.xml.contacts_storage_settings);
64     private static final String TAG = "ContactsStorageSettings";
65     private static final String PREF_KEY_ADD_ACCOUNT = "add_account";
66     private static final String PREF_KEY_DEVICE_ONLY = "device_only_account_preference";
67     private static final String PREF_KEY_ACCOUNT_CATEGORY = "account_category";
68     private final Map<String, DefaultAccountAndState> mAccountMap = new HashMap<>();
69     private AuthenticatorHelper mAuthenticatorHelper;
70 
71     @Override
onAttach(@onNull Context context)72     public void onAttach(@NonNull Context context) {
73         super.onAttach(context);
74         mAuthenticatorHelper = new AuthenticatorHelper(context,
75                 new UserHandle(UserHandle.myUserId()), this);
76         mAuthenticatorHelper.listenToAccountUpdates();
77         preloadEligibleAccountIcon();
78     }
79 
80     @Override
onDetach()81     public void onDetach() {
82         super.onDetach();
83         mAuthenticatorHelper.stopListeningToAccountUpdates();
84     }
85 
86     @UiThread
87     @Override
onRadioButtonClicked(@onNull SelectorWithWidgetPreference selectedPref)88     public void onRadioButtonClicked(@NonNull SelectorWithWidgetPreference selectedPref) {
89         final String selectedPreferenceKey = selectedPref.getKey();
90         // Check if current account is different from the selected account.
91         for (String preferenceKey : mAccountMap.keySet()) {
92             if (selectedPreferenceKey.equals(preferenceKey)) {
93                 try {
94                     DefaultAccountAndState currentDefaultAccount = mAccountMap.get(preferenceKey);
95                     DefaultAccount.setDefaultAccountForNewContacts(getContentResolver(),
96                             currentDefaultAccount);
97                     selectedPref.setChecked(true);
98                     if (currentDefaultAccount.getState()
99                             == DefaultAccountAndState.DEFAULT_ACCOUNT_STATE_CLOUD) {
100                         startMoveLocalAndSimContactsActivity();
101                     }
102                 } catch (RuntimeException e) {
103                     Log.e(TAG, "Error setting the default account " + e);
104                     Toast.makeText(getContext(),
105                             R.string.contacts_storage_set_default_account_error_message,
106                             Toast.LENGTH_SHORT).show();
107                 }
108             } else {
109                 SelectorWithWidgetPreference unSelectedPreference =
110                         getPreferenceScreen().findPreference(preferenceKey);
111                 if (unSelectedPreference != null) {
112                     unSelectedPreference.setChecked(false);
113                 }
114             }
115         }
116     }
117 
onPreferenceClick(@onNull Preference preference)118     public boolean onPreferenceClick(@NonNull Preference preference) {
119         if (PREF_KEY_ADD_ACCOUNT.equals(preference.getKey())) {
120             String[] accountTypesArray = getEligibleAccountTypes();
121             Intent intent = new Intent(ACTION_ADD_ACCOUNT);
122             intent.setClass(getContext(), AddAccountSettings.class);
123             intent.putExtra(EXTRA_ACCOUNT_TYPES, accountTypesArray);
124             intent.addFlags(FLAG_ACTIVITY_NEW_TASK);
125             getContext().startActivity(intent);
126             return true;
127         }
128         return false;
129     }
130 
131     @Override
onAccountsUpdate(UserHandle userHandle)132     public void onAccountsUpdate(UserHandle userHandle) {
133         preloadEligibleAccountIcon();
134         refreshUI();
135     }
136 
137     @Override
onCreatePreferences(@onNull Bundle savedInstanceState, @NonNull String rootKey)138     public void onCreatePreferences(@NonNull Bundle savedInstanceState,
139             @NonNull String rootKey) {
140         super.onCreatePreferences(savedInstanceState, rootKey);
141         refreshUI();
142     }
143 
144     @UiThread
refreshUI()145     void refreshUI() {
146         // Clear all the accounts stored in the map and later on re-fetch the eligible accounts
147         // when creating eligible account preferences.
148         mAccountMap.clear();
149         final PreferenceGroup preferenceGroup = findPreference(PREF_KEY_ACCOUNT_CATEGORY);
150         preferenceGroup.removeAll();
151         // If the default account is SIM, we should show in the page, otherwise don't show.
152         SelectorWithWidgetPreference simAccountPreference = buildSimAccountPreference();
153         if (simAccountPreference != null) {
154             preferenceGroup.addPreference(simAccountPreference);
155         }
156         List<Account> accounts = DefaultAccount.getEligibleCloudAccounts(getContentResolver());
157         for (int i = 0; i < accounts.size(); i++) {
158             preferenceGroup.addPreference(
159                     buildCloudAccountPreference(accounts.get(i), /*order=*/i));
160         }
161         // If there's no eligible account types, the "Add Account" preference should
162         // not be shown to the users.
163         if (getEligibleAccountTypes().length > 0) {
164             preferenceGroup.addPreference(buildAddAccountPreference(accounts.isEmpty()));
165         }
166         setupDeviceOnlyPreference();
167         setDefaultAccountPreference(preferenceGroup);
168     }
169 
preloadEligibleAccountIcon()170     private void preloadEligibleAccountIcon() {
171         String[] accountTypes = getEligibleAccountTypes();
172         for (String accountType : accountTypes) {
173             // Preload the drawable for the account type to avoid the latency when rendering the
174             // account preference.
175             mAuthenticatorHelper.preloadDrawableForType(getContext(), accountType);
176         }
177     }
178 
setupDeviceOnlyPreference()179     private void setupDeviceOnlyPreference() {
180         SelectorWithWidgetPreference preference = findPreference(PREF_KEY_DEVICE_ONLY);
181         if (preference != null) {
182             preference.setOnClickListener(this);
183             mAccountMap.put(PREF_KEY_DEVICE_ONLY, DefaultAccountAndState.ofLocal());
184         }
185     }
186 
setDefaultAccountPreference(PreferenceGroup preferenceGroup)187     private void setDefaultAccountPreference(PreferenceGroup preferenceGroup) {
188         DefaultAccountAndState currentDefaultAccountAndState =
189                 DefaultAccount.getDefaultAccountForNewContacts(getContentResolver());
190         String preferenceKey = getAccountHashCode(currentDefaultAccountAndState);
191         Account currentDefaultAccount = currentDefaultAccountAndState.getAccount();
192 
193         // Set the current default account preference to be checked if found among existing
194         // preferences. If not, then create a new preference for default account.
195         SelectorWithWidgetPreference preference = null;
196         if (mAccountMap.containsKey(preferenceKey)) {
197             preference = getPreferenceScreen().findPreference(preferenceKey);
198         } else if (preferenceKey != null && currentDefaultAccount != null) {
199             preference = buildCloudAccountPreference(currentDefaultAccount, mAccountMap.size());
200             preferenceGroup.addPreference(preference);
201         }
202         if (preference != null) {
203             preference.setChecked(true);
204         }
205     }
206 
buildCloudAccountPreference(Account account, int order)207     private SelectorWithWidgetPreference buildCloudAccountPreference(Account account, int order) {
208         SelectorWithWidgetPreference preference = new SelectorWithWidgetPreference(
209                 getPrefContext());
210         DefaultAccountAndState accountAndState = DefaultAccountAndState.ofCloud(account);
211         String preferenceKey = getAccountHashCode(accountAndState);
212         String accountPreferenceTitle = getString(R.string.contacts_storage_account_title,
213                 mAuthenticatorHelper.getLabelForType(getPrefContext(), account.type));
214         preference.setTitle(accountPreferenceTitle);
215         preference.setIcon(mAuthenticatorHelper.getDrawableForType(getPrefContext(), account.type));
216         preference.setSummary(account.name);
217         preference.setKey(preferenceKey);
218         preference.setOnClickListener(this);
219         preference.setOrder(order);
220         mAccountMap.put(preferenceKey, accountAndState);
221         return preference;
222     }
223 
224     @Nullable
buildSimAccountPreference()225     private SelectorWithWidgetPreference buildSimAccountPreference() {
226         DefaultAccountAndState currentDefaultAccountAndState =
227                 DefaultAccount.getDefaultAccountForNewContacts(getContentResolver());
228         if (currentDefaultAccountAndState.getState()
229                 == DefaultAccountAndState.DEFAULT_ACCOUNT_STATE_SIM) {
230             String preferenceKey = getAccountHashCode(currentDefaultAccountAndState);
231             SelectorWithWidgetPreference preference = new SelectorWithWidgetPreference(
232                     getPrefContext());
233             preference.setTitle(R.string.sim_card_label);
234             preference.setIcon(R.drawable.ic_sim_card);
235             preference.setSummary(R.string.contacts_storage_device_only_preference_summary);
236             preference.setKey(preferenceKey);
237             preference.setOnClickListener(this);
238             mAccountMap.put(preferenceKey, currentDefaultAccountAndState);
239             return preference;
240         }
241         return null;
242     }
243 
buildAddAccountPreference(boolean noAccountBeenAdded)244     private RestrictedPreference buildAddAccountPreference(boolean noAccountBeenAdded) {
245         RestrictedPreference preference = new RestrictedPreference(getPrefContext());
246         preference.setKey(PREF_KEY_ADD_ACCOUNT);
247         if (noAccountBeenAdded) {
248             preference.setTitle(R.string.contacts_storage_first_time_add_account_message);
249         } else {
250             preference.setTitle(R.string.add_account_label);
251         }
252         preference.setIcon(R.drawable.ic_add_24dp);
253         preference.setOnPreferenceClickListener(this);
254         preference.setOrder(998);
255         return preference;
256     }
257 
startMoveLocalAndSimContactsActivity()258     private void startMoveLocalAndSimContactsActivity() {
259         Intent intent = new Intent()
260                 .setAction(DefaultAccount.ACTION_MOVE_CONTACTS_TO_DEFAULT_ACCOUNT)
261                 .setPackage("com.android.providers.contacts")
262                 .addFlags(FLAG_ACTIVITY_NEW_TASK);
263         getContext().startActivity(intent);
264     }
265 
266     @Nullable
getAccountHashCode( DefaultAccountAndState currentDefaultAccountAndState)267     private String getAccountHashCode(
268             DefaultAccountAndState currentDefaultAccountAndState) {
269         Account currentDefaultAccount = currentDefaultAccountAndState.getAccount();
270         if (currentDefaultAccount != null && (currentDefaultAccountAndState.getState()
271                 == DefaultAccountAndState.DEFAULT_ACCOUNT_STATE_CLOUD
272                 || currentDefaultAccountAndState.getState()
273                 == DefaultAccountAndState.DEFAULT_ACCOUNT_STATE_SIM)) {
274             return String.valueOf(currentDefaultAccount.hashCode());
275         } else if (currentDefaultAccountAndState.getState()
276                 == DefaultAccountAndState.DEFAULT_ACCOUNT_STATE_LOCAL) {
277             return PREF_KEY_DEVICE_ONLY;
278         } else {
279             // If the account is not set or in error state, it should just return null and won't
280             // set the checked status in radio button.
281             return null;
282         }
283     }
284 
285     @VisibleForTesting
getEligibleAccountTypes()286     String[] getEligibleAccountTypes() {
287         return Resources.getSystem().getStringArray(
288                 com.android.internal.R.array.config_rawContactsEligibleDefaultAccountTypes);
289     }
290 
291     @Override
getPreferenceScreenResId()292     protected int getPreferenceScreenResId() {
293         return R.xml.contacts_storage_settings;
294     }
295 
296     @Override
getLogTag()297     protected String getLogTag() {
298         return TAG;
299     }
300 
301     @Override
getMetricsCategory()302     public int getMetricsCategory() {
303         return SettingsEnums.CONTACTS_STORAGE;
304     }
305 }
306