blob: d87b444bcf90a10eb6cea3ca6a03cc69cc4f3176 [file] [log] [blame]
Chiao Cheng6c712f42012-11-26 15:35:28 -08001/*
2 * Copyright (C) 2009 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
Gary Mai69c182a2016-12-05 13:07:03 -080017package com.android.contacts.model;
Chiao Cheng6c712f42012-11-26 15:35:28 -080018
19import android.accounts.Account;
Ihab Awad413589f2014-07-02 14:00:28 -070020import android.accounts.AccountManager;
Chiao Cheng6c712f42012-11-26 15:35:28 -080021import android.accounts.OnAccountsUpdateListener;
22import android.content.BroadcastReceiver;
23import android.content.ContentResolver;
24import android.content.Context;
Chiao Cheng6c712f42012-11-26 15:35:28 -080025import android.content.Intent;
26import android.content.IntentFilter;
Wenyi Wang56c8a0c2016-09-30 11:11:10 -070027import android.content.SharedPreferences;
Chiao Cheng6c712f42012-11-26 15:35:28 -080028import android.content.SyncStatusObserver;
29import android.content.pm.PackageManager;
Marcus Hagerottfac695a2016-08-24 17:02:40 -070030import android.database.ContentObserver;
Chiao Cheng6c712f42012-11-26 15:35:28 -080031import android.net.Uri;
Chiao Cheng6c712f42012-11-26 15:35:28 -080032import android.os.Handler;
Chiao Cheng6c712f42012-11-26 15:35:28 -080033import android.os.Looper;
Chiao Cheng6c712f42012-11-26 15:35:28 -080034import android.provider.ContactsContract;
Marcus Hagerottfac695a2016-08-24 17:02:40 -070035import android.support.v4.content.ContextCompat;
Marcus Hagerotte7a71cb2016-12-09 16:26:14 -080036import android.support.v4.content.LocalBroadcastManager;
Chiao Cheng6c712f42012-11-26 15:35:28 -080037import android.text.TextUtils;
38import android.util.Log;
Chiao Cheng6c712f42012-11-26 15:35:28 -080039
Gary Mai0a49afa2016-12-05 15:53:58 -080040import com.android.contacts.Experiments;
Wenyi Wang56c8a0c2016-09-30 11:11:10 -070041import com.android.contacts.R;
Gary Mai69c182a2016-12-05 13:07:03 -080042import com.android.contacts.list.ContactListFilterController;
Marcus Hagerott04be88c2016-12-07 09:55:03 -080043import com.android.contacts.model.account.AccountComparator;
Gary Mai69c182a2016-12-05 13:07:03 -080044import com.android.contacts.model.account.AccountType;
Marcus Hagerott04be88c2016-12-07 09:55:03 -080045import com.android.contacts.model.account.AccountTypeProvider;
Gary Mai69c182a2016-12-05 13:07:03 -080046import com.android.contacts.model.account.AccountTypeWithDataSet;
47import com.android.contacts.model.account.AccountWithDataSet;
Gary Mai69c182a2016-12-05 13:07:03 -080048import com.android.contacts.model.account.FallbackAccountType;
49import com.android.contacts.model.account.GoogleAccountType;
Gary Mai69c182a2016-12-05 13:07:03 -080050import com.android.contacts.model.dataitem.DataKind;
Marcus Hagerott04be88c2016-12-07 09:55:03 -080051import com.android.contacts.util.concurrent.ContactsExecutors;
Marcus Hagerott511504f2016-11-15 13:58:34 -080052import com.android.contactsbind.experiments.Flags;
Marcus Hagerott04be88c2016-12-07 09:55:03 -080053import com.google.common.base.Function;
Marcus Hagerott67a06392016-10-13 15:16:58 -070054import com.google.common.base.Predicate;
Marcus Hagerotte7a71cb2016-12-09 16:26:14 -080055import com.google.common.base.Predicates;
Marcus Hagerott67a06392016-10-13 15:16:58 -070056import com.google.common.collect.Collections2;
Marcus Hagerott04be88c2016-12-07 09:55:03 -080057import com.google.common.util.concurrent.FutureCallback;
58import com.google.common.util.concurrent.Futures;
59import com.google.common.util.concurrent.ListenableFuture;
60import com.google.common.util.concurrent.ListeningExecutorService;
Chiao Cheng6c712f42012-11-26 15:35:28 -080061
Gary Maiac333592016-09-28 17:27:40 -070062import java.util.ArrayList;
Chiao Cheng6c712f42012-11-26 15:35:28 -080063import java.util.Collections;
Chiao Cheng6c712f42012-11-26 15:35:28 -080064import java.util.List;
Marcus Hagerott04be88c2016-12-07 09:55:03 -080065import java.util.concurrent.Callable;
66import java.util.concurrent.Executor;
Chiao Cheng6c712f42012-11-26 15:35:28 -080067
Marcus Hagerott67a06392016-10-13 15:16:58 -070068import javax.annotation.Nullable;
69
Chiao Cheng6c712f42012-11-26 15:35:28 -080070/**
71 * Singleton holder for all parsed {@link AccountType} available on the
72 * system, typically filled through {@link PackageManager} queries.
73 */
74public abstract class AccountTypeManager {
75 static final String TAG = "AccountTypeManager";
76
77 private static final Object mInitializationLock = new Object();
78 private static AccountTypeManager mAccountTypeManager;
79
Marcus Hagerotte7a71cb2016-12-09 16:26:14 -080080 public static final String BROADCAST_ACCOUNTS_CHANGED = AccountTypeManager.class.getName() +
81 ".AccountsChanged";
82
Chiao Cheng6c712f42012-11-26 15:35:28 -080083 /**
84 * Requests the singleton instance of {@link AccountTypeManager} with data bound from
85 * the available authenticators. This method can safely be called from the UI thread.
86 */
87 public static AccountTypeManager getInstance(Context context) {
Marcus Hagerottfac695a2016-08-24 17:02:40 -070088 if (!hasRequiredPermissions(context)) {
89 // Hopefully any component that depends on the values returned by this class
90 // will be restarted if the permissions change.
91 return EMPTY;
92 }
Chiao Cheng6c712f42012-11-26 15:35:28 -080093 synchronized (mInitializationLock) {
94 if (mAccountTypeManager == null) {
95 context = context.getApplicationContext();
Marcus Hagerott04be88c2016-12-07 09:55:03 -080096 mAccountTypeManager = new AccountTypeManagerImpl(context);
Chiao Cheng6c712f42012-11-26 15:35:28 -080097 }
98 }
99 return mAccountTypeManager;
100 }
101
102 /**
103 * Set the instance of account type manager. This is only for and should only be used by unit
104 * tests. While having this method is not ideal, it's simpler than the alternative of
105 * holding this as a service in the ContactsApplication context class.
106 *
107 * @param mockManager The mock AccountTypeManager.
108 */
Chiao Cheng6c712f42012-11-26 15:35:28 -0800109 public static void setInstanceForTest(AccountTypeManager mockManager) {
110 synchronized (mInitializationLock) {
111 mAccountTypeManager = mockManager;
112 }
113 }
114
Marcus Hagerottfac695a2016-08-24 17:02:40 -0700115 private static final AccountTypeManager EMPTY = new AccountTypeManager() {
Marcus Hagerott04be88c2016-12-07 09:55:03 -0800116
Marcus Hagerottfac695a2016-08-24 17:02:40 -0700117 @Override
118 public List<AccountWithDataSet> getAccounts(boolean contactWritableOnly) {
119 return Collections.emptyList();
120 }
121
122 @Override
Marcus Hagerott67a06392016-10-13 15:16:58 -0700123 public List<AccountWithDataSet> getAccounts(Predicate<AccountWithDataSet> filter) {
124 return Collections.emptyList();
125 }
126
127 @Override
Marcus Hagerott04be88c2016-12-07 09:55:03 -0800128 public ListenableFuture<List<AccountWithDataSet>> getAllAccountsAsync() {
129 return Futures.immediateFuture(Collections.<AccountWithDataSet>emptyList());
130 }
131
132 @Override
Marcus Hagerotte7a71cb2016-12-09 16:26:14 -0800133 public ListenableFuture<List<AccountWithDataSet>> filterAccountsByTypeAsync(
134 Predicate<AccountType> type) {
135 return Futures.immediateFuture(Collections.<AccountWithDataSet>emptyList());
136 }
137
138 @Override
Gary Maiac333592016-09-28 17:27:40 -0700139 public List<AccountWithDataSet> getGroupWritableAccounts() {
140 return Collections.emptyList();
Marcus Hagerottfac695a2016-08-24 17:02:40 -0700141 }
142
143 @Override
Wenyi Wang56c8a0c2016-09-30 11:11:10 -0700144 public Account getDefaultGoogleAccount() {
145 return null;
146 }
147
148 @Override
Marcus Hagerottfac695a2016-08-24 17:02:40 -0700149 public AccountType getAccountType(AccountTypeWithDataSet accountTypeWithDataSet) {
150 return null;
151 }
Marcus Hagerottfac695a2016-08-24 17:02:40 -0700152 };
153
Chiao Cheng6c712f42012-11-26 15:35:28 -0800154 /**
155 * Returns the list of all accounts (if contactWritableOnly is false) or just the list of
156 * contact writable accounts (if contactWritableOnly is true).
157 */
158 // TODO: Consider splitting this into getContactWritableAccounts() and getAllAccounts()
159 public abstract List<AccountWithDataSet> getAccounts(boolean contactWritableOnly);
160
Marcus Hagerott67a06392016-10-13 15:16:58 -0700161 public abstract List<AccountWithDataSet> getAccounts(Predicate<AccountWithDataSet> filter);
162
Wenyi Wangaa0e6ff2016-07-06 17:22:42 -0700163 /**
Marcus Hagerott04be88c2016-12-07 09:55:03 -0800164 * Loads accounts in background and returns future that will complete with list of all accounts
165 */
166 public abstract ListenableFuture<List<AccountWithDataSet>> getAllAccountsAsync();
167
Marcus Hagerotte7a71cb2016-12-09 16:26:14 -0800168 public abstract ListenableFuture<List<AccountWithDataSet>> filterAccountsByTypeAsync(
169 Predicate<AccountType> type);
170
Marcus Hagerott04be88c2016-12-07 09:55:03 -0800171 /**
Chiao Cheng6c712f42012-11-26 15:35:28 -0800172 * Returns the list of accounts that are group writable.
173 */
174 public abstract List<AccountWithDataSet> getGroupWritableAccounts();
175
Wenyi Wang56c8a0c2016-09-30 11:11:10 -0700176 /**
177 * Returns the default google account.
178 */
179 public abstract Account getDefaultGoogleAccount();
180
Marcus Hagerott396aab72016-12-12 12:00:21 -0800181 /**
182 * Returns the Google Accounts.
183 *
184 * <p>This method exists in addition to filterAccountsByTypeAsync because it should be safe
185 * to call synchronously.
186 * </p>
187 */
188 public List<AccountWithDataSet> getWritableGoogleAccounts() {
189 // This implementation may block and should be overridden by the Impl class
190 return Futures.getUnchecked(filterAccountsByTypeAsync(new Predicate<AccountType>() {
191 @Override
192 public boolean apply(@Nullable AccountType input) {
193 return input.areContactsWritable() &&
194 GoogleAccountType.ACCOUNT_TYPE.equals(input.accountType);
195
196 }
197 }));
198 }
199
200 /**
201 * Returns true if there are real accounts (not "local" account) in the list of accounts.
202 */
203 public boolean hasNonLocalAccount() {
204 final List<AccountWithDataSet> allAccounts = getAccounts(/* contactWritableOnly */ false);
205 if (allAccounts == null || allAccounts.size() == 0) {
206 return false;
207 }
208 if (allAccounts.size() > 1) {
209 return true;
210 }
211 return !allAccounts.get(0).isNullAccount();
212 }
213
Wenyi Wang56c8a0c2016-09-30 11:11:10 -0700214 static Account getDefaultGoogleAccount(AccountManager accountManager,
215 SharedPreferences prefs, String defaultAccountKey) {
216 // Get all the google accounts on the device
217 final Account[] accounts = accountManager.getAccountsByType(
218 GoogleAccountType.ACCOUNT_TYPE);
219 if (accounts == null || accounts.length == 0) {
220 return null;
221 }
222
223 // Get the default account from preferences
224 final String defaultAccount = prefs.getString(defaultAccountKey, null);
225 final AccountWithDataSet accountWithDataSet = defaultAccount == null ? null :
226 AccountWithDataSet.unstringify(defaultAccount);
227
228 // Look for an account matching the one from preferences
229 if (accountWithDataSet != null) {
230 for (int i = 0; i < accounts.length; i++) {
231 if (TextUtils.equals(accountWithDataSet.name, accounts[i].name)
232 && TextUtils.equals(accountWithDataSet.type, accounts[i].type)) {
233 return accounts[i];
234 }
235 }
236 }
237
238 // Just return the first one
239 return accounts[0];
240 }
241
Chiao Cheng6c712f42012-11-26 15:35:28 -0800242 public abstract AccountType getAccountType(AccountTypeWithDataSet accountTypeWithDataSet);
243
244 public final AccountType getAccountType(String accountType, String dataSet) {
245 return getAccountType(AccountTypeWithDataSet.get(accountType, dataSet));
246 }
247
248 public final AccountType getAccountTypeForAccount(AccountWithDataSet account) {
Jay Shrauner2ae200d2015-01-09 11:36:20 -0800249 if (account != null) {
250 return getAccountType(account.getAccountTypeWithDataSet());
251 }
252 return getAccountType(null, null);
Chiao Cheng6c712f42012-11-26 15:35:28 -0800253 }
254
255 /**
Chiao Cheng6c712f42012-11-26 15:35:28 -0800256 * Find the best {@link DataKind} matching the requested
257 * {@link AccountType#accountType}, {@link AccountType#dataSet}, and {@link DataKind#mimeType}.
258 * If no direct match found, we try searching {@link FallbackAccountType}.
259 */
260 public DataKind getKindOrFallback(AccountType type, String mimeType) {
261 return type == null ? null : type.getKindForMimetype(mimeType);
262 }
263
264 /**
Chiao Cheng6c712f42012-11-26 15:35:28 -0800265 * @param contactWritableOnly if true, it only returns ones that support writing contacts.
266 * @return true when this instance contains the given account.
267 */
268 public boolean contains(AccountWithDataSet account, boolean contactWritableOnly) {
Tingting Wang0ac73ba2016-07-05 22:33:01 -0700269 for (AccountWithDataSet account_2 : getAccounts(contactWritableOnly)) {
Chiao Cheng6c712f42012-11-26 15:35:28 -0800270 if (account.equals(account_2)) {
271 return true;
272 }
273 }
274 return false;
275 }
Marcus Hagerottfac695a2016-08-24 17:02:40 -0700276
Marcus Hagerott596eb7e2016-10-18 10:25:12 -0700277 public boolean hasGoogleAccount() {
278 return getDefaultGoogleAccount() != null;
279 }
280
Marcus Hagerottf0e140a2016-12-05 16:59:48 -0800281 /**
282 * Sorts the accounts in-place such that defaultAccount is first in the list and the rest
283 * of the accounts are ordered in manner that is useful for display purposes
284 */
285 public static void sortAccounts(AccountWithDataSet defaultAccount,
286 List<AccountWithDataSet> accounts) {
287 Collections.sort(accounts, new AccountComparator(defaultAccount));
288 }
289
Marcus Hagerottfac695a2016-08-24 17:02:40 -0700290 private static boolean hasRequiredPermissions(Context context) {
291 final boolean canGetAccounts = ContextCompat.checkSelfPermission(context,
292 android.Manifest.permission.GET_ACCOUNTS) == PackageManager.PERMISSION_GRANTED;
293 final boolean canReadContacts = ContextCompat.checkSelfPermission(context,
294 android.Manifest.permission.READ_CONTACTS) == PackageManager.PERMISSION_GRANTED;
295 return canGetAccounts && canReadContacts;
296 }
297
Marcus Hagerott67a06392016-10-13 15:16:58 -0700298 public static Predicate<AccountWithDataSet> nonNullAccountFilter() {
299 return new Predicate<AccountWithDataSet>() {
300 @Override
301 public boolean apply(@Nullable AccountWithDataSet account) {
302 return account != null && account.name != null && account.type != null;
303 }
304 };
305 }
Chiao Cheng6c712f42012-11-26 15:35:28 -0800306
Marcus Hagerotte7a71cb2016-12-09 16:26:14 -0800307 public static Predicate<AccountWithDataSet> adaptTypeFilter(
308 final Predicate<AccountType> typeFilter, final AccountTypeProvider provider) {
309 return new Predicate<AccountWithDataSet>() {
310 @Override
311 public boolean apply(@Nullable AccountWithDataSet input) {
312 return typeFilter.apply(provider.getTypeForAccount(input));
313 }
314 };
315 }
316
Marcus Hagerott04be88c2016-12-07 09:55:03 -0800317 public static Predicate<AccountType> writableFilter() {
318 return new Predicate<AccountType>() {
319 @Override
320 public boolean apply(@Nullable AccountType account) {
321 return account.areContactsWritable();
Wenyi Wangaa0e6ff2016-07-06 17:22:42 -0700322 }
Marcus Hagerott04be88c2016-12-07 09:55:03 -0800323 };
Wenyi Wangaa0e6ff2016-07-06 17:22:42 -0700324 }
Marcus Hagerotte7a71cb2016-12-09 16:26:14 -0800325
326 public static Predicate<AccountType> groupWritableFilter() {
327 return new Predicate<AccountType>() {
328 @Override
329 public boolean apply(@Nullable AccountType account) {
330 return account.isGroupMembershipEditable();
331 }
332 };
333 }
Wenyi Wangaa0e6ff2016-07-06 17:22:42 -0700334}
335
Chiao Cheng6c712f42012-11-26 15:35:28 -0800336class AccountTypeManagerImpl extends AccountTypeManager
337 implements OnAccountsUpdateListener, SyncStatusObserver {
338
Chiao Cheng6c712f42012-11-26 15:35:28 -0800339 private Context mContext;
Ihab Awad413589f2014-07-02 14:00:28 -0700340 private AccountManager mAccountManager;
Marcus Hagerottc2093f32016-12-12 10:18:12 -0800341 private DeviceLocalAccountLocator mLocalAccountLocator;
Marcus Hagerotte7a71cb2016-12-09 16:26:14 -0800342 private AccountTypeProvider mTypeProvider;
Marcus Hagerott04be88c2016-12-07 09:55:03 -0800343 private ListeningExecutorService mExecutor;
344 private Executor mMainThreadExecutor;
Chiao Cheng6c712f42012-11-26 15:35:28 -0800345
346 private AccountType mFallbackAccountType;
347
Marcus Hagerottc2093f32016-12-12 10:18:12 -0800348 private ListenableFuture<List<AccountWithDataSet>> mLocalAccountsFuture;
349 private ListenableFuture<AccountTypeProvider> mAccountTypesFuture;
350
351 private FutureCallback<Object> mAccountsUpdateCallback = new FutureCallback<Object>() {
352 @Override
353 public void onSuccess(@Nullable Object result) {
354 onAccountsUpdatedInternal();
355 }
356
357 @Override
358 public void onFailure(Throwable t) {
359 }
360 };
Chiao Cheng6c712f42012-11-26 15:35:28 -0800361
362 private final Handler mMainThreadHandler = new Handler(Looper.getMainLooper());
Chiao Cheng6c712f42012-11-26 15:35:28 -0800363
364 private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
Chiao Cheng6c712f42012-11-26 15:35:28 -0800365 @Override
366 public void onReceive(Context context, Intent intent) {
Marcus Hagerott04be88c2016-12-07 09:55:03 -0800367 reloadAccountTypes();
Chiao Cheng6c712f42012-11-26 15:35:28 -0800368 }
Chiao Cheng6c712f42012-11-26 15:35:28 -0800369 };
370
Chiao Cheng6c712f42012-11-26 15:35:28 -0800371 /**
372 * Internal constructor that only performs initial parsing.
373 */
Marcus Hagerott04be88c2016-12-07 09:55:03 -0800374 public AccountTypeManagerImpl(Context context) {
Chiao Cheng6c712f42012-11-26 15:35:28 -0800375 mContext = context;
Marcus Hagerottc2093f32016-12-12 10:18:12 -0800376 mLocalAccountLocator = DeviceLocalAccountLocator.create(context);
Marcus Hagerotte7a71cb2016-12-09 16:26:14 -0800377 mTypeProvider = new AccountTypeProvider(context);
Chiao Cheng6c712f42012-11-26 15:35:28 -0800378 mFallbackAccountType = new FallbackAccountType(context);
379
Ihab Awad413589f2014-07-02 14:00:28 -0700380 mAccountManager = AccountManager.get(mContext);
Chiao Cheng6c712f42012-11-26 15:35:28 -0800381
Marcus Hagerott04be88c2016-12-07 09:55:03 -0800382 mExecutor = ContactsExecutors.getDefaultThreadPoolExecutor();
383 mMainThreadExecutor = ContactsExecutors.newHandlerExecutor(mMainThreadHandler);
Chiao Cheng6c712f42012-11-26 15:35:28 -0800384
Chiao Cheng6c712f42012-11-26 15:35:28 -0800385 // Request updates when packages or accounts change
386 IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
387 filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
388 filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
389 filter.addDataScheme("package");
390 mContext.registerReceiver(mBroadcastReceiver, filter);
391 IntentFilter sdFilter = new IntentFilter();
392 sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
393 sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
394 mContext.registerReceiver(mBroadcastReceiver, sdFilter);
395
396 // Request updates when locale is changed so that the order of each field will
397 // be able to be changed on the locale change.
398 filter = new IntentFilter(Intent.ACTION_LOCALE_CHANGED);
399 mContext.registerReceiver(mBroadcastReceiver, filter);
400
Marcus Hagerott04be88c2016-12-07 09:55:03 -0800401 mAccountManager.addOnAccountsUpdatedListener(this, mMainThreadHandler, false);
Chiao Cheng6c712f42012-11-26 15:35:28 -0800402
403 ContentResolver.addStatusChangeListener(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS, this);
404
Marcus Hagerott511504f2016-11-15 13:58:34 -0800405 if (Flags.getInstance().getBoolean(Experiments.OEM_CP2_DEVICE_ACCOUNT_DETECTION_ENABLED)) {
406 // Observe changes to RAW_CONTACTS so that we will update the list of "Device" accounts
407 // if a new device contact is added.
408 mContext.getContentResolver().registerContentObserver(
409 ContactsContract.RawContacts.CONTENT_URI, /* notifyDescendents */ true,
Marcus Hagerott04be88c2016-12-07 09:55:03 -0800410 new ContentObserver(mMainThreadHandler) {
Marcus Hagerott511504f2016-11-15 13:58:34 -0800411 @Override
412 public boolean deliverSelfNotifications() {
413 return true;
414 }
Marcus Hagerottfac695a2016-08-24 17:02:40 -0700415
Marcus Hagerott511504f2016-11-15 13:58:34 -0800416 @Override
417 public void onChange(boolean selfChange) {
Marcus Hagerottc2093f32016-12-12 10:18:12 -0800418 reloadLocalAccounts();
Marcus Hagerott511504f2016-11-15 13:58:34 -0800419 }
420
421 @Override
422 public void onChange(boolean selfChange, Uri uri) {
Marcus Hagerottc2093f32016-12-12 10:18:12 -0800423 reloadLocalAccounts();
Marcus Hagerott511504f2016-11-15 13:58:34 -0800424 }
425 });
426 }
Marcus Hagerotte7a71cb2016-12-09 16:26:14 -0800427 loadAccountTypes();
Chiao Cheng6c712f42012-11-26 15:35:28 -0800428 }
429
430 @Override
431 public void onStatusChanged(int which) {
Marcus Hagerott04be88c2016-12-07 09:55:03 -0800432 reloadAccountTypes();
Chiao Cheng6c712f42012-11-26 15:35:28 -0800433 }
434
435 /* This notification will arrive on the background thread */
436 public void onAccountsUpdated(Account[] accounts) {
Marcus Hagerott04be88c2016-12-07 09:55:03 -0800437 onAccountsUpdatedInternal();
Chiao Cheng6c712f42012-11-26 15:35:28 -0800438 }
439
Marcus Hagerott04be88c2016-12-07 09:55:03 -0800440 private void onAccountsUpdatedInternal() {
441 ContactListFilterController.getInstance(mContext).checkFilterValidity(true);
Marcus Hagerotte7a71cb2016-12-09 16:26:14 -0800442 LocalBroadcastManager.getInstance(mContext).sendBroadcast(
443 new Intent(BROADCAST_ACCOUNTS_CHANGED));
Marcus Hagerott04be88c2016-12-07 09:55:03 -0800444 }
Marcus Hagerottfac695a2016-08-24 17:02:40 -0700445
Marcus Hagerott04be88c2016-12-07 09:55:03 -0800446 private synchronized void startLoadingIfNeeded() {
Marcus Hagerottc2093f32016-12-12 10:18:12 -0800447 if (mTypeProvider == null && mAccountTypesFuture == null) {
Marcus Hagerott04be88c2016-12-07 09:55:03 -0800448 reloadAccountTypes();
Chiao Cheng6c712f42012-11-26 15:35:28 -0800449 }
Marcus Hagerottc2093f32016-12-12 10:18:12 -0800450 if (mLocalAccountsFuture == null) {
451 reloadLocalAccounts();
452 }
Chiao Cheng6c712f42012-11-26 15:35:28 -0800453 }
454
Marcus Hagerotte7a71cb2016-12-09 16:26:14 -0800455 private void loadAccountTypes() {
456 mTypeProvider = new AccountTypeProvider(mContext);
Chiao Cheng6c712f42012-11-26 15:35:28 -0800457
Marcus Hagerottc2093f32016-12-12 10:18:12 -0800458 mAccountTypesFuture = mExecutor.submit(new Callable<AccountTypeProvider>() {
Marcus Hagerott04be88c2016-12-07 09:55:03 -0800459 @Override
460 public AccountTypeProvider call() throws Exception {
Marcus Hagerotte7a71cb2016-12-09 16:26:14 -0800461 // This will request the AccountType for each Account
Marcus Hagerottc2093f32016-12-12 10:18:12 -0800462 getAccountsFromProvider(mTypeProvider);
Marcus Hagerotte7a71cb2016-12-09 16:26:14 -0800463 return mTypeProvider;
Chiao Cheng6c712f42012-11-26 15:35:28 -0800464 }
Marcus Hagerott04be88c2016-12-07 09:55:03 -0800465 });
Marcus Hagerotte7a71cb2016-12-09 16:26:14 -0800466 }
467
Marcus Hagerottc2093f32016-12-12 10:18:12 -0800468 private synchronized void reloadAccountTypes() {
Marcus Hagerotte7a71cb2016-12-09 16:26:14 -0800469 loadAccountTypes();
Marcus Hagerottc2093f32016-12-12 10:18:12 -0800470 Futures.addCallback(mAccountTypesFuture, mAccountsUpdateCallback, mMainThreadExecutor);
471 }
Marcus Hagerott04be88c2016-12-07 09:55:03 -0800472
Marcus Hagerottc2093f32016-12-12 10:18:12 -0800473 private synchronized void loadLocalAccounts() {
474 mLocalAccountsFuture = mExecutor.submit(new Callable<List<AccountWithDataSet>>() {
Marcus Hagerott04be88c2016-12-07 09:55:03 -0800475 @Override
Marcus Hagerottc2093f32016-12-12 10:18:12 -0800476 public List<AccountWithDataSet> call() throws Exception {
477 return mLocalAccountLocator.getDeviceLocalAccounts();
Marcus Hagerott04be88c2016-12-07 09:55:03 -0800478 }
Marcus Hagerottc2093f32016-12-12 10:18:12 -0800479 });
480 }
481
482 private void reloadLocalAccounts() {
483 loadLocalAccounts();
484 Futures.addCallback(mLocalAccountsFuture, mAccountsUpdateCallback, mMainThreadExecutor);
Chiao Cheng6c712f42012-11-26 15:35:28 -0800485 }
486
487 /**
Gary Maiac333592016-09-28 17:27:40 -0700488 * Return list of all known or contact writable {@link AccountWithDataSet}'s.
489 * {@param contactWritableOnly} whether to restrict to contact writable accounts only
Chiao Cheng6c712f42012-11-26 15:35:28 -0800490 */
491 @Override
492 public List<AccountWithDataSet> getAccounts(boolean contactWritableOnly) {
Marcus Hagerottc2093f32016-12-12 10:18:12 -0800493 final Predicate<AccountType> filter = contactWritableOnly ?
494 writableFilter() : Predicates.<AccountType>alwaysTrue();
495 // TODO: Shouldn't have a synchronous version for getting all accounts
496 return Futures.getUnchecked(filterAccountsByTypeAsync(filter));
Chiao Cheng6c712f42012-11-26 15:35:28 -0800497 }
498
Marcus Hagerott67a06392016-10-13 15:16:58 -0700499 @Override
500 public List<AccountWithDataSet> getAccounts(Predicate<AccountWithDataSet> filter) {
Marcus Hagerottc2093f32016-12-12 10:18:12 -0800501 // TODO: Shouldn't have a synchronous version for getting all accounts
502 return Futures.getUnchecked(filterAccountsAsync(filter));
Marcus Hagerott67a06392016-10-13 15:16:58 -0700503 }
504
Marcus Hagerott04be88c2016-12-07 09:55:03 -0800505 @Override
506 public ListenableFuture<List<AccountWithDataSet>> getAllAccountsAsync() {
507 startLoadingIfNeeded();
Marcus Hagerottc2093f32016-12-12 10:18:12 -0800508 return filterAccountsAsync(Predicates.<AccountWithDataSet>alwaysTrue());
Marcus Hagerott04be88c2016-12-07 09:55:03 -0800509 }
510
Marcus Hagerotte7a71cb2016-12-09 16:26:14 -0800511 @Override
512 public ListenableFuture<List<AccountWithDataSet>> filterAccountsByTypeAsync(
513 final Predicate<AccountType> typeFilter) {
Marcus Hagerottc2093f32016-12-12 10:18:12 -0800514 // Ensure that mTypeProvider is initialized so that the reference will be the same
515 // here as in the call to filterAccountsAsync
Marcus Hagerotte7a71cb2016-12-09 16:26:14 -0800516 startLoadingIfNeeded();
Marcus Hagerottc2093f32016-12-12 10:18:12 -0800517 return filterAccountsAsync(adaptTypeFilter(typeFilter, mTypeProvider));
Marcus Hagerotte7a71cb2016-12-09 16:26:14 -0800518 }
519
Marcus Hagerottc2093f32016-12-12 10:18:12 -0800520 private ListenableFuture<List<AccountWithDataSet>> filterAccountsAsync(
521 final Predicate<AccountWithDataSet> filter) {
522 startLoadingIfNeeded();
523 final ListenableFuture<List<AccountWithDataSet>> accountsFromTypes =
524 Futures.transform(Futures.nonCancellationPropagating(mAccountTypesFuture),
525 new Function<AccountTypeProvider, List<AccountWithDataSet>>() {
526 @Override
527 public List<AccountWithDataSet> apply(AccountTypeProvider provider) {
528 return getAccountsFromProvider(provider);
529 }
530 });
531
532 final ListenableFuture<List<List<AccountWithDataSet>>> all =
533 Futures.successfulAsList(accountsFromTypes, mLocalAccountsFuture);
534
535 return Futures.transform(all, new Function<List<List<AccountWithDataSet>>,
536 List<AccountWithDataSet>>() {
537 @Nullable
538 @Override
539 public List<AccountWithDataSet> apply(@Nullable List<List<AccountWithDataSet>> input) {
540 // The first result list is from the account types. Check if there is a Google
541 // account in this list and if there is exclude the null account
542 final Predicate<AccountWithDataSet> appliedFilter =
543 hasWritableGoogleAccount(input.get(0)) ?
544 Predicates.and(nonNullAccountFilter(), filter) :
545 filter;
546 List<AccountWithDataSet> result = new ArrayList<>();
547 for (List<AccountWithDataSet> list : input) {
548 if (list != null) {
549 result.addAll(Collections2.filter(list, appliedFilter));
550 }
551 }
552 return result;
553 }
554 });
555 }
556
557 private List<AccountWithDataSet> getAccountsFromProvider(AccountTypeProvider cache) {
Marcus Hagerott04be88c2016-12-07 09:55:03 -0800558 final List<AccountWithDataSet> result = new ArrayList<>();
559 final Account[] accounts = mAccountManager.getAccounts();
560 for (Account account : accounts) {
561 final List<AccountType> types = cache.getAccountTypes(account.type);
562 for (AccountType type : types) {
563 result.add(new AccountWithDataSet(account.name, account.type, type.dataSet));
564 }
565 }
566 return result;
567 }
568
Marcus Hagerottc2093f32016-12-12 10:18:12 -0800569 private boolean hasWritableGoogleAccount(List<AccountWithDataSet> accounts) {
570 if (accounts == null) {
571 return false;
572 }
573 AccountType type;
574 for (AccountWithDataSet account : accounts) {
575 if (GoogleAccountType.ACCOUNT_TYPE.equals(account.type) && account.dataSet == null) {
576 return true;
Marcus Hagerott04be88c2016-12-07 09:55:03 -0800577 }
Marcus Hagerottc2093f32016-12-12 10:18:12 -0800578 }
579 return false;
Marcus Hagerott04be88c2016-12-07 09:55:03 -0800580 }
581
582
Chiao Cheng6c712f42012-11-26 15:35:28 -0800583 /**
584 * Return the list of all known, group writable {@link AccountWithDataSet}'s.
585 */
586 public List<AccountWithDataSet> getGroupWritableAccounts() {
Marcus Hagerottc2093f32016-12-12 10:18:12 -0800587 return Futures.getUnchecked(filterAccountsByTypeAsync(groupWritableFilter()));
Chiao Cheng6c712f42012-11-26 15:35:28 -0800588 }
589
590 /**
Wenyi Wang56c8a0c2016-09-30 11:11:10 -0700591 * Returns the default google account specified in preferences, the first google account
592 * if it is not specified in preferences or is no longer on the device, and null otherwise.
593 */
594 @Override
595 public Account getDefaultGoogleAccount() {
Wenyi Wang56c8a0c2016-09-30 11:11:10 -0700596 final SharedPreferences sharedPreferences =
597 mContext.getSharedPreferences(mContext.getPackageName(), Context.MODE_PRIVATE);
598 final String defaultAccountKey =
599 mContext.getResources().getString(R.string.contact_editor_default_account_key);
Marcus Hagerott396aab72016-12-12 12:00:21 -0800600 return getDefaultGoogleAccount(mAccountManager, sharedPreferences, defaultAccountKey);
601 }
602
603 @Override
604 public List<AccountWithDataSet> getWritableGoogleAccounts() {
605 final Account[] googleAccounts =
606 mAccountManager.getAccountsByType(GoogleAccountType.ACCOUNT_TYPE);
607 final List<AccountWithDataSet> result = new ArrayList<>();
608 for (Account account : googleAccounts) {
609 // Accounts with a dataSet (e.g. Google plus accounts) are not writable.
610 result.add(new AccountWithDataSet(account.name, account.type, null));
611 }
612 return result;
613 }
614
615 @Override
616 public boolean hasNonLocalAccount() {
617 final Account[] accounts = mAccountManager.getAccounts();
618 return accounts != null && accounts.length > 0;
Wenyi Wang56c8a0c2016-09-30 11:11:10 -0700619 }
620
621 /**
Chiao Cheng6c712f42012-11-26 15:35:28 -0800622 * Find the best {@link DataKind} matching the requested
623 * {@link AccountType#accountType}, {@link AccountType#dataSet}, and {@link DataKind#mimeType}.
624 * If no direct match found, we try searching {@link FallbackAccountType}.
625 */
626 @Override
627 public DataKind getKindOrFallback(AccountType type, String mimeType) {
Chiao Cheng6c712f42012-11-26 15:35:28 -0800628 DataKind kind = null;
629
630 // Try finding account type and kind matching request
631 if (type != null) {
632 kind = type.getKindForMimetype(mimeType);
633 }
634
635 if (kind == null) {
636 // Nothing found, so try fallback as last resort
637 kind = mFallbackAccountType.getKindForMimetype(mimeType);
638 }
639
640 if (kind == null) {
Chiao Cheng5df73762012-12-12 11:28:35 -0800641 if (Log.isLoggable(TAG, Log.DEBUG)) {
642 Log.d(TAG, "Unknown type=" + type + ", mime=" + mimeType);
643 }
Chiao Cheng6c712f42012-11-26 15:35:28 -0800644 }
645
646 return kind;
647 }
648
649 /**
650 * Return {@link AccountType} for the given account type and data set.
651 */
652 @Override
653 public AccountType getAccountType(AccountTypeWithDataSet accountTypeWithDataSet) {
Marcus Hagerotte7a71cb2016-12-09 16:26:14 -0800654 return mTypeProvider.getType(
Marcus Hagerott04be88c2016-12-07 09:55:03 -0800655 accountTypeWithDataSet.accountType, accountTypeWithDataSet.dataSet);
Chiao Cheng6c712f42012-11-26 15:35:28 -0800656 }
Chiao Cheng6c712f42012-11-26 15:35:28 -0800657}