• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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