1 /* 2 * Copyright (C) 2024 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.providers.contacts; 17 18 import android.accounts.Account; 19 import android.accounts.AccountManager; 20 import android.content.Context; 21 import android.content.res.Resources; 22 import android.provider.ContactsContract; 23 import android.provider.ContactsContract.RawContacts.DefaultAccount.DefaultAccountAndState; 24 import android.util.Log; 25 26 import com.android.internal.R; 27 import com.android.providers.contacts.util.NeededForTesting; 28 29 import java.util.ArrayList; 30 import java.util.Arrays; 31 import java.util.HashSet; 32 import java.util.List; 33 import java.util.Set; 34 35 /** 36 * A utility class to provide methods to load and set the default account. 37 */ 38 @NeededForTesting 39 public class DefaultAccountManager { 40 private static final String TAG = "DefaultAccountManager"; 41 42 private static HashSet<String> sEligibleSystemCloudAccountTypes = null; 43 44 private final Context mContext; 45 private final ContactsDatabaseHelper mDbHelper; 46 private final SyncSettingsHelper mSyncSettingsHelper; 47 private final AccountManager mAccountManager; 48 DefaultAccountManager(Context context, ContactsDatabaseHelper dbHelper)49 DefaultAccountManager(Context context, ContactsDatabaseHelper dbHelper) { 50 this(context, dbHelper, new SyncSettingsHelper(), AccountManager.get(context)); 51 } 52 53 // Keep it in proguard for testing: once it's used in production code, remove this annotation. 54 @NeededForTesting DefaultAccountManager(Context context, ContactsDatabaseHelper dbHelper, SyncSettingsHelper syncSettingsHelper, AccountManager accountManager)55 DefaultAccountManager(Context context, ContactsDatabaseHelper dbHelper, 56 SyncSettingsHelper syncSettingsHelper, AccountManager accountManager) { 57 mContext = context; 58 mDbHelper = dbHelper; 59 mSyncSettingsHelper = syncSettingsHelper; 60 mAccountManager = accountManager; 61 } 62 getEligibleSystemAccountTypes(Context context)63 private static synchronized Set<String> getEligibleSystemAccountTypes(Context context) { 64 if (sEligibleSystemCloudAccountTypes == null) { 65 sEligibleSystemCloudAccountTypes = new HashSet<>(); 66 67 Resources resources = Resources.getSystem(); 68 String[] accountTypesArray = 69 resources.getStringArray(R.array.config_rawContactsEligibleDefaultAccountTypes); 70 71 sEligibleSystemCloudAccountTypes.addAll(Arrays.asList(accountTypesArray)); 72 } 73 return sEligibleSystemCloudAccountTypes; 74 } 75 76 @NeededForTesting setEligibleSystemCloudAccountTypesForTesting(String[] accountTypes)77 static synchronized void setEligibleSystemCloudAccountTypesForTesting(String[] accountTypes) { 78 sEligibleSystemCloudAccountTypes = new HashSet<>(Arrays.asList(accountTypes)); 79 } 80 81 /** 82 * Try to push an account as the default account. 83 * 84 * @param defaultAccount account to be set as the default account. 85 * @return true if the default account is successfully updated, or no update is needed. 86 */ 87 @NeededForTesting tryPushDefaultAccount(DefaultAccountAndState defaultAccount)88 public boolean tryPushDefaultAccount(DefaultAccountAndState defaultAccount) { 89 if (!isValidDefaultAccount(defaultAccount)) { 90 Log.w(TAG, "Attempt to push an invalid default account."); 91 return false; 92 } 93 94 DefaultAccountAndState previousDefaultAccount = pullDefaultAccount(); 95 96 if (defaultAccount.equals(previousDefaultAccount)) { 97 Log.w(TAG, "Account has already been set as default before"); 98 } else { 99 directlySetDefaultAccountInDb(defaultAccount); 100 } 101 return true; 102 } 103 isValidDefaultAccount(DefaultAccountAndState defaultAccount)104 private boolean isValidDefaultAccount(DefaultAccountAndState defaultAccount) { 105 if (defaultAccount.getState() == DefaultAccountAndState.DEFAULT_ACCOUNT_STATE_CLOUD) { 106 return defaultAccount.getAccount() != null 107 && isCloudAccount(defaultAccount.getAccount()); 108 109 } 110 if (defaultAccount.getState() == DefaultAccountAndState.DEFAULT_ACCOUNT_STATE_SIM) { 111 return defaultAccount.getAccount() != null && isSimAccount(defaultAccount.getAccount()); 112 } 113 return defaultAccount.getAccount() == null; 114 } 115 116 /** 117 * Get a list of cloud accounts that is eligible to set as the default account. 118 * @return the list of cloud accounts. 119 */ getEligibleCloudAccounts()120 public List<Account> getEligibleCloudAccounts() { 121 List<Account> eligibleAccounts = new ArrayList<>(); 122 Account[] accounts = mAccountManager.getAccounts(); 123 for (Account account : accounts) { 124 if (isEligibleSystemCloudAccount(account)) { 125 eligibleAccounts.add(account); 126 } 127 } 128 return eligibleAccounts; 129 } 130 131 132 /** 133 * Pull the default account from the DB. 134 */ 135 @NeededForTesting pullDefaultAccount()136 public DefaultAccountAndState pullDefaultAccount() { 137 DefaultAccountAndState defaultAccount = getDefaultAccountFromDb(); 138 if (isValidDefaultAccount(defaultAccount)) { 139 return defaultAccount; 140 } else { 141 Log.w(TAG, "Default account stored in the DB is no longer valid."); 142 directlySetDefaultAccountInDb(DefaultAccountAndState.ofNotSet()); 143 return DefaultAccountAndState.ofNotSet(); 144 } 145 } 146 directlySetDefaultAccountInDb(DefaultAccountAndState defaultAccount)147 private void directlySetDefaultAccountInDb(DefaultAccountAndState defaultAccount) { 148 switch (defaultAccount.getState()) { 149 case DefaultAccountAndState.DEFAULT_ACCOUNT_STATE_NOT_SET: { 150 mDbHelper.clearDefaultAccount(); 151 break; 152 } 153 case DefaultAccountAndState.DEFAULT_ACCOUNT_STATE_LOCAL: { 154 mDbHelper.setDefaultAccount(AccountWithDataSet.LOCAL.getAccountName(), 155 AccountWithDataSet.LOCAL.getAccountType()); 156 break; 157 } 158 case DefaultAccountAndState.DEFAULT_ACCOUNT_STATE_CLOUD: 159 case DefaultAccountAndState.DEFAULT_ACCOUNT_STATE_SIM: 160 assert defaultAccount.getAccount() != null; 161 mDbHelper.setDefaultAccount(defaultAccount.getAccount().name, 162 defaultAccount.getAccount().type); 163 break; 164 default: 165 Log.e(TAG, "Incorrect default account category"); 166 break; 167 } 168 } 169 isSimAccount(Account account)170 private boolean isSimAccount(Account account) { 171 if (account == null) { 172 return false; 173 } 174 175 final List<ContactsContract.SimAccount> simAccounts = mDbHelper.getAllSimAccounts(); 176 AccountWithDataSet accountWithDataSet = new AccountWithDataSet(account.name, account.type, 177 null); 178 return accountWithDataSet.inSimAccounts(simAccounts); 179 } 180 isLocalAccount(Account account)181 private boolean isLocalAccount(Account account) { 182 return (account == null) || ((account.name.equals(AccountWithDataSet.LOCAL.getAccountName()) 183 && account.type.equals(AccountWithDataSet.LOCAL.getAccountType()))); 184 } 185 isCloudAccount(Account account)186 private boolean isCloudAccount(Account account) { 187 if (account == null) { 188 return false; 189 } 190 191 Account[] accountsInThisType = mAccountManager.getAccountsByType(account.type); 192 for (Account currentAccount : accountsInThisType) { 193 if (currentAccount.equals(account)) { 194 return true; 195 } 196 } 197 return false; 198 } 199 isEligibleSystemCloudAccount(Account account)200 private boolean isEligibleSystemCloudAccount(Account account) { 201 return account != null && getEligibleSystemAccountTypes(mContext).contains(account.type) 202 && !mSyncSettingsHelper.isSyncOff(account); 203 } 204 getDefaultAccountFromDb()205 private DefaultAccountAndState getDefaultAccountFromDb() { 206 Account[] defaultAccountFromDb = mDbHelper.getDefaultAccountIfAny(); 207 if (defaultAccountFromDb.length == 0) { 208 return DefaultAccountAndState.ofNotSet(); 209 } 210 211 Account account = defaultAccountFromDb[0]; 212 if (isLocalAccount(account)) { 213 return DefaultAccountAndState.ofLocal(); 214 } 215 216 if (isSimAccount(account)) { 217 return DefaultAccountAndState.ofSim(account); 218 } 219 220 // Assume it's cloud account. 221 return DefaultAccountAndState.ofCloud(account); 222 } 223 } 224