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