blob: fa8d6e24556fa8bb346f18b594f6f9201f598ab0 [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 Hagerott75895e72016-12-12 17:21:57 -080043import com.android.contacts.model.account.AccountInfo;
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 Hagerott75895e72016-12-12 17:21:57 -080053import com.google.common.base.Preconditions;
Marcus Hagerott04be88c2016-12-07 09:55:03 -080054import com.google.common.base.Function;
Marcus Hagerott2a4673a2016-12-15 11:49:47 -080055import com.google.common.base.Objects;
Marcus Hagerott67a06392016-10-13 15:16:58 -070056import com.google.common.base.Predicate;
Marcus Hagerotte7a71cb2016-12-09 16:26:14 -080057import com.google.common.base.Predicates;
Marcus Hagerott67a06392016-10-13 15:16:58 -070058import com.google.common.collect.Collections2;
Marcus Hagerott75895e72016-12-12 17:21:57 -080059import com.google.common.collect.Lists;
Marcus Hagerott04be88c2016-12-07 09:55:03 -080060import com.google.common.util.concurrent.FutureCallback;
61import com.google.common.util.concurrent.Futures;
62import com.google.common.util.concurrent.ListenableFuture;
63import com.google.common.util.concurrent.ListeningExecutorService;
Chiao Cheng6c712f42012-11-26 15:35:28 -080064
Gary Maiac333592016-09-28 17:27:40 -070065import java.util.ArrayList;
Chiao Cheng6c712f42012-11-26 15:35:28 -080066import java.util.Collections;
Chiao Cheng6c712f42012-11-26 15:35:28 -080067import java.util.List;
Marcus Hagerott04be88c2016-12-07 09:55:03 -080068import java.util.concurrent.Callable;
69import java.util.concurrent.Executor;
Chiao Cheng6c712f42012-11-26 15:35:28 -080070
Marcus Hagerott67a06392016-10-13 15:16:58 -070071import javax.annotation.Nullable;
72
Chiao Cheng6c712f42012-11-26 15:35:28 -080073/**
74 * Singleton holder for all parsed {@link AccountType} available on the
75 * system, typically filled through {@link PackageManager} queries.
76 */
77public abstract class AccountTypeManager {
78 static final String TAG = "AccountTypeManager";
79
80 private static final Object mInitializationLock = new Object();
81 private static AccountTypeManager mAccountTypeManager;
82
Marcus Hagerotte7a71cb2016-12-09 16:26:14 -080083 public static final String BROADCAST_ACCOUNTS_CHANGED = AccountTypeManager.class.getName() +
84 ".AccountsChanged";
85
Chiao Cheng6c712f42012-11-26 15:35:28 -080086 /**
87 * Requests the singleton instance of {@link AccountTypeManager} with data bound from
88 * the available authenticators. This method can safely be called from the UI thread.
89 */
90 public static AccountTypeManager getInstance(Context context) {
Marcus Hagerottfac695a2016-08-24 17:02:40 -070091 if (!hasRequiredPermissions(context)) {
92 // Hopefully any component that depends on the values returned by this class
93 // will be restarted if the permissions change.
94 return EMPTY;
95 }
Chiao Cheng6c712f42012-11-26 15:35:28 -080096 synchronized (mInitializationLock) {
97 if (mAccountTypeManager == null) {
98 context = context.getApplicationContext();
Marcus Hagerott04be88c2016-12-07 09:55:03 -080099 mAccountTypeManager = new AccountTypeManagerImpl(context);
Chiao Cheng6c712f42012-11-26 15:35:28 -0800100 }
101 }
102 return mAccountTypeManager;
103 }
104
105 /**
106 * Set the instance of account type manager. This is only for and should only be used by unit
107 * tests. While having this method is not ideal, it's simpler than the alternative of
108 * holding this as a service in the ContactsApplication context class.
109 *
110 * @param mockManager The mock AccountTypeManager.
111 */
Chiao Cheng6c712f42012-11-26 15:35:28 -0800112 public static void setInstanceForTest(AccountTypeManager mockManager) {
113 synchronized (mInitializationLock) {
114 mAccountTypeManager = mockManager;
115 }
116 }
117
Marcus Hagerottfac695a2016-08-24 17:02:40 -0700118 private static final AccountTypeManager EMPTY = new AccountTypeManager() {
Marcus Hagerott04be88c2016-12-07 09:55:03 -0800119
Marcus Hagerottfac695a2016-08-24 17:02:40 -0700120 @Override
121 public List<AccountWithDataSet> getAccounts(boolean contactWritableOnly) {
122 return Collections.emptyList();
123 }
124
125 @Override
Marcus Hagerott75895e72016-12-12 17:21:57 -0800126 public ListenableFuture<List<AccountInfo>> getAccountsAsync() {
127 return Futures.immediateFuture(Collections.<AccountInfo>emptyList());
Marcus Hagerott67a06392016-10-13 15:16:58 -0700128 }
129
130 @Override
Marcus Hagerott75895e72016-12-12 17:21:57 -0800131 public ListenableFuture<List<AccountInfo>> filterAccountsAsync(
132 Predicate<AccountInfo> filter) {
133 return Futures.immediateFuture(Collections.<AccountInfo>emptyList());
Marcus Hagerott04be88c2016-12-07 09:55:03 -0800134 }
135
136 @Override
Marcus Hagerott75895e72016-12-12 17:21:57 -0800137 public AccountInfo getAccountInfoForAccount(AccountWithDataSet account) {
138 return null;
Marcus Hagerotte7a71cb2016-12-09 16:26:14 -0800139 }
140
141 @Override
Gary Maiac333592016-09-28 17:27:40 -0700142 public List<AccountWithDataSet> getGroupWritableAccounts() {
143 return Collections.emptyList();
Marcus Hagerottfac695a2016-08-24 17:02:40 -0700144 }
145
146 @Override
Wenyi Wang56c8a0c2016-09-30 11:11:10 -0700147 public Account getDefaultGoogleAccount() {
148 return null;
149 }
150
151 @Override
Marcus Hagerottfac695a2016-08-24 17:02:40 -0700152 public AccountType getAccountType(AccountTypeWithDataSet accountTypeWithDataSet) {
153 return null;
154 }
Marcus Hagerottfac695a2016-08-24 17:02:40 -0700155 };
156
Chiao Cheng6c712f42012-11-26 15:35:28 -0800157 /**
158 * Returns the list of all accounts (if contactWritableOnly is false) or just the list of
159 * contact writable accounts (if contactWritableOnly is true).
160 */
161 // TODO: Consider splitting this into getContactWritableAccounts() and getAllAccounts()
162 public abstract List<AccountWithDataSet> getAccounts(boolean contactWritableOnly);
163
Wenyi Wangaa0e6ff2016-07-06 17:22:42 -0700164 /**
Marcus Hagerott04be88c2016-12-07 09:55:03 -0800165 * Loads accounts in background and returns future that will complete with list of all accounts
166 */
Marcus Hagerott75895e72016-12-12 17:21:57 -0800167 public abstract ListenableFuture<List<AccountInfo>> getAccountsAsync();
Marcus Hagerott04be88c2016-12-07 09:55:03 -0800168
Marcus Hagerott75895e72016-12-12 17:21:57 -0800169 /**
170 * Loads accounts and applies the fitler returning only for which the predicate is true
171 */
172 public abstract ListenableFuture<List<AccountInfo>> filterAccountsAsync(
173 Predicate<AccountInfo> filter);
174
175 public abstract AccountInfo getAccountInfoForAccount(AccountWithDataSet account);
Marcus Hagerotte7a71cb2016-12-09 16:26:14 -0800176
Marcus Hagerott04be88c2016-12-07 09:55:03 -0800177 /**
Chiao Cheng6c712f42012-11-26 15:35:28 -0800178 * Returns the list of accounts that are group writable.
179 */
180 public abstract List<AccountWithDataSet> getGroupWritableAccounts();
181
Wenyi Wang56c8a0c2016-09-30 11:11:10 -0700182 /**
183 * Returns the default google account.
184 */
185 public abstract Account getDefaultGoogleAccount();
186
Marcus Hagerott396aab72016-12-12 12:00:21 -0800187 /**
188 * Returns the Google Accounts.
189 *
190 * <p>This method exists in addition to filterAccountsByTypeAsync because it should be safe
191 * to call synchronously.
192 * </p>
193 */
Marcus Hagerott75895e72016-12-12 17:21:57 -0800194 public List<AccountInfo> getWritableGoogleAccounts() {
Marcus Hagerott396aab72016-12-12 12:00:21 -0800195 // This implementation may block and should be overridden by the Impl class
Marcus Hagerott75895e72016-12-12 17:21:57 -0800196 return Futures.getUnchecked(filterAccountsAsync(new Predicate<AccountInfo>() {
Marcus Hagerott396aab72016-12-12 12:00:21 -0800197 @Override
Marcus Hagerott75895e72016-12-12 17:21:57 -0800198 public boolean apply(@Nullable AccountInfo input) {
199 return input.getType().areContactsWritable() &&
200 GoogleAccountType.ACCOUNT_TYPE.equals(input.getType().accountType);
Marcus Hagerott396aab72016-12-12 12:00:21 -0800201 }
202 }));
203 }
204
205 /**
206 * Returns true if there are real accounts (not "local" account) in the list of accounts.
207 */
208 public boolean hasNonLocalAccount() {
209 final List<AccountWithDataSet> allAccounts = getAccounts(/* contactWritableOnly */ false);
210 if (allAccounts == null || allAccounts.size() == 0) {
211 return false;
212 }
213 if (allAccounts.size() > 1) {
214 return true;
215 }
216 return !allAccounts.get(0).isNullAccount();
217 }
218
Wenyi Wang56c8a0c2016-09-30 11:11:10 -0700219 static Account getDefaultGoogleAccount(AccountManager accountManager,
220 SharedPreferences prefs, String defaultAccountKey) {
221 // Get all the google accounts on the device
222 final Account[] accounts = accountManager.getAccountsByType(
223 GoogleAccountType.ACCOUNT_TYPE);
224 if (accounts == null || accounts.length == 0) {
225 return null;
226 }
227
228 // Get the default account from preferences
229 final String defaultAccount = prefs.getString(defaultAccountKey, null);
230 final AccountWithDataSet accountWithDataSet = defaultAccount == null ? null :
231 AccountWithDataSet.unstringify(defaultAccount);
232
233 // Look for an account matching the one from preferences
234 if (accountWithDataSet != null) {
235 for (int i = 0; i < accounts.length; i++) {
236 if (TextUtils.equals(accountWithDataSet.name, accounts[i].name)
237 && TextUtils.equals(accountWithDataSet.type, accounts[i].type)) {
238 return accounts[i];
239 }
240 }
241 }
242
243 // Just return the first one
244 return accounts[0];
245 }
246
Chiao Cheng6c712f42012-11-26 15:35:28 -0800247 public abstract AccountType getAccountType(AccountTypeWithDataSet accountTypeWithDataSet);
248
249 public final AccountType getAccountType(String accountType, String dataSet) {
250 return getAccountType(AccountTypeWithDataSet.get(accountType, dataSet));
251 }
252
253 public final AccountType getAccountTypeForAccount(AccountWithDataSet account) {
Jay Shrauner2ae200d2015-01-09 11:36:20 -0800254 if (account != null) {
255 return getAccountType(account.getAccountTypeWithDataSet());
256 }
257 return getAccountType(null, null);
Chiao Cheng6c712f42012-11-26 15:35:28 -0800258 }
259
260 /**
Chiao Cheng6c712f42012-11-26 15:35:28 -0800261 * Find the best {@link DataKind} matching the requested
262 * {@link AccountType#accountType}, {@link AccountType#dataSet}, and {@link DataKind#mimeType}.
263 * If no direct match found, we try searching {@link FallbackAccountType}.
264 */
265 public DataKind getKindOrFallback(AccountType type, String mimeType) {
266 return type == null ? null : type.getKindForMimetype(mimeType);
267 }
268
269 /**
Chiao Cheng6c712f42012-11-26 15:35:28 -0800270 * @param contactWritableOnly if true, it only returns ones that support writing contacts.
271 * @return true when this instance contains the given account.
272 */
273 public boolean contains(AccountWithDataSet account, boolean contactWritableOnly) {
Tingting Wang0ac73ba2016-07-05 22:33:01 -0700274 for (AccountWithDataSet account_2 : getAccounts(contactWritableOnly)) {
Chiao Cheng6c712f42012-11-26 15:35:28 -0800275 if (account.equals(account_2)) {
276 return true;
277 }
278 }
279 return false;
280 }
Marcus Hagerottfac695a2016-08-24 17:02:40 -0700281
Marcus Hagerott596eb7e2016-10-18 10:25:12 -0700282 public boolean hasGoogleAccount() {
283 return getDefaultGoogleAccount() != null;
284 }
285
Marcus Hagerottfac695a2016-08-24 17:02:40 -0700286 private static boolean hasRequiredPermissions(Context context) {
287 final boolean canGetAccounts = ContextCompat.checkSelfPermission(context,
288 android.Manifest.permission.GET_ACCOUNTS) == PackageManager.PERMISSION_GRANTED;
289 final boolean canReadContacts = ContextCompat.checkSelfPermission(context,
290 android.Manifest.permission.READ_CONTACTS) == PackageManager.PERMISSION_GRANTED;
291 return canGetAccounts && canReadContacts;
292 }
293
Marcus Hagerott75895e72016-12-12 17:21:57 -0800294 public static Predicate<AccountInfo> nonNullAccountFilter() {
295 return new Predicate<AccountInfo>() {
Marcus Hagerott67a06392016-10-13 15:16:58 -0700296 @Override
Marcus Hagerott75895e72016-12-12 17:21:57 -0800297 public boolean apply(AccountInfo info) {
298 AccountWithDataSet account = info != null ? info.getAccount() : null;
299 return account != null && !account.isNullAccount();
300 }
301 };
302
303 }
304
305 public static Predicate<AccountInfo> writableFilter() {
306 return new Predicate<AccountInfo>() {
307 @Override
308 public boolean apply(AccountInfo account) {
309 return account.getType().areContactsWritable();
Marcus Hagerott67a06392016-10-13 15:16:58 -0700310 }
311 };
312 }
Chiao Cheng6c712f42012-11-26 15:35:28 -0800313
Marcus Hagerott75895e72016-12-12 17:21:57 -0800314 public static Predicate<AccountInfo> groupWritableFilter() {
315 return new Predicate<AccountInfo>() {
Marcus Hagerotte7a71cb2016-12-09 16:26:14 -0800316 @Override
Marcus Hagerott75895e72016-12-12 17:21:57 -0800317 public boolean apply(@Nullable AccountInfo account) {
318 return account.getType().isGroupMembershipEditable();
Marcus Hagerotte7a71cb2016-12-09 16:26:14 -0800319 }
320 };
321 }
322
Marcus Hagerott75895e72016-12-12 17:21:57 -0800323 public static Predicate<AccountInfo> onlyNonEmptyExtensionFilter(Context context) {
324 final Context appContext = context.getApplicationContext();
325 return new Predicate<AccountInfo>() {
Marcus Hagerott04be88c2016-12-07 09:55:03 -0800326 @Override
Marcus Hagerott75895e72016-12-12 17:21:57 -0800327 public boolean apply(@Nullable AccountInfo input) {
328 return !input.getType().isExtension() || input.getAccount().hasData(appContext);
Marcus Hagerotte7a71cb2016-12-09 16:26:14 -0800329 }
330 };
331 }
Wenyi Wangaa0e6ff2016-07-06 17:22:42 -0700332}
333
Chiao Cheng6c712f42012-11-26 15:35:28 -0800334class AccountTypeManagerImpl extends AccountTypeManager
335 implements OnAccountsUpdateListener, SyncStatusObserver {
336
Marcus Hagerott2a4673a2016-12-15 11:49:47 -0800337 private final Context mContext;
338 private final AccountManager mAccountManager;
339 private final DeviceLocalAccountLocator mLocalAccountLocator;
340 private final Executor mMainThreadExecutor;
341 private final ListeningExecutorService mExecutor;
Marcus Hagerotte7a71cb2016-12-09 16:26:14 -0800342 private AccountTypeProvider mTypeProvider;
Chiao Cheng6c712f42012-11-26 15:35:28 -0800343
Marcus Hagerott2a4673a2016-12-15 11:49:47 -0800344 private final AccountType mFallbackAccountType;
Chiao Cheng6c712f42012-11-26 15:35:28 -0800345
Marcus Hagerottc2093f32016-12-12 10:18:12 -0800346 private ListenableFuture<List<AccountWithDataSet>> mLocalAccountsFuture;
347 private ListenableFuture<AccountTypeProvider> mAccountTypesFuture;
348
Marcus Hagerott2a4673a2016-12-15 11:49:47 -0800349 private List<AccountWithDataSet> mLocalAccounts = new ArrayList<>();
350 private List<AccountWithDataSet> mAccountManagerAccounts = new ArrayList<>();
Chiao Cheng6c712f42012-11-26 15:35:28 -0800351
352 private final Handler mMainThreadHandler = new Handler(Looper.getMainLooper());
Chiao Cheng6c712f42012-11-26 15:35:28 -0800353
Marcus Hagerott2a4673a2016-12-15 11:49:47 -0800354 private final Function<AccountTypeProvider, List<AccountWithDataSet>> mAccountsExtractor =
355 new Function<AccountTypeProvider, List<AccountWithDataSet>>() {
356 @Nullable
357 @Override
358 public List<AccountWithDataSet> apply(@Nullable AccountTypeProvider typeProvider) {
359 return getAccountsWithDataSets(mAccountManager.getAccounts(), typeProvider);
360 }
361 };
362
363
Chiao Cheng6c712f42012-11-26 15:35:28 -0800364 private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
Chiao Cheng6c712f42012-11-26 15:35:28 -0800365 @Override
366 public void onReceive(Context context, Intent intent) {
Marcus Hagerott2a4673a2016-12-15 11:49:47 -0800367 // Don't use reloadAccountTypesIfNeeded when packages change in case a contacts.xml
368 // was updated.
Marcus Hagerott04be88c2016-12-07 09:55:03 -0800369 reloadAccountTypes();
Chiao Cheng6c712f42012-11-26 15:35:28 -0800370 }
Chiao Cheng6c712f42012-11-26 15:35:28 -0800371 };
372
Chiao Cheng6c712f42012-11-26 15:35:28 -0800373 /**
374 * Internal constructor that only performs initial parsing.
375 */
Marcus Hagerott04be88c2016-12-07 09:55:03 -0800376 public AccountTypeManagerImpl(Context context) {
Chiao Cheng6c712f42012-11-26 15:35:28 -0800377 mContext = context;
Marcus Hagerottc2093f32016-12-12 10:18:12 -0800378 mLocalAccountLocator = DeviceLocalAccountLocator.create(context);
Marcus Hagerotte7a71cb2016-12-09 16:26:14 -0800379 mTypeProvider = new AccountTypeProvider(context);
Chiao Cheng6c712f42012-11-26 15:35:28 -0800380 mFallbackAccountType = new FallbackAccountType(context);
381
Ihab Awad413589f2014-07-02 14:00:28 -0700382 mAccountManager = AccountManager.get(mContext);
Chiao Cheng6c712f42012-11-26 15:35:28 -0800383
Marcus Hagerott04be88c2016-12-07 09:55:03 -0800384 mExecutor = ContactsExecutors.getDefaultThreadPoolExecutor();
385 mMainThreadExecutor = ContactsExecutors.newHandlerExecutor(mMainThreadHandler);
Chiao Cheng6c712f42012-11-26 15:35:28 -0800386
Chiao Cheng6c712f42012-11-26 15:35:28 -0800387 // Request updates when packages or accounts change
388 IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
389 filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
390 filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
391 filter.addDataScheme("package");
392 mContext.registerReceiver(mBroadcastReceiver, filter);
393 IntentFilter sdFilter = new IntentFilter();
394 sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
395 sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
396 mContext.registerReceiver(mBroadcastReceiver, sdFilter);
397
398 // Request updates when locale is changed so that the order of each field will
399 // be able to be changed on the locale change.
400 filter = new IntentFilter(Intent.ACTION_LOCALE_CHANGED);
401 mContext.registerReceiver(mBroadcastReceiver, filter);
402
Marcus Hagerott04be88c2016-12-07 09:55:03 -0800403 mAccountManager.addOnAccountsUpdatedListener(this, mMainThreadHandler, false);
Chiao Cheng6c712f42012-11-26 15:35:28 -0800404
405 ContentResolver.addStatusChangeListener(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS, this);
406
Marcus Hagerott511504f2016-11-15 13:58:34 -0800407 if (Flags.getInstance().getBoolean(Experiments.OEM_CP2_DEVICE_ACCOUNT_DETECTION_ENABLED)) {
408 // Observe changes to RAW_CONTACTS so that we will update the list of "Device" accounts
409 // if a new device contact is added.
410 mContext.getContentResolver().registerContentObserver(
411 ContactsContract.RawContacts.CONTENT_URI, /* notifyDescendents */ true,
Marcus Hagerott04be88c2016-12-07 09:55:03 -0800412 new ContentObserver(mMainThreadHandler) {
Marcus Hagerott511504f2016-11-15 13:58:34 -0800413 @Override
414 public boolean deliverSelfNotifications() {
415 return true;
416 }
Marcus Hagerottfac695a2016-08-24 17:02:40 -0700417
Marcus Hagerott511504f2016-11-15 13:58:34 -0800418 @Override
419 public void onChange(boolean selfChange) {
Marcus Hagerottc2093f32016-12-12 10:18:12 -0800420 reloadLocalAccounts();
Marcus Hagerott511504f2016-11-15 13:58:34 -0800421 }
422
423 @Override
424 public void onChange(boolean selfChange, Uri uri) {
Marcus Hagerottc2093f32016-12-12 10:18:12 -0800425 reloadLocalAccounts();
Marcus Hagerott511504f2016-11-15 13:58:34 -0800426 }
427 });
428 }
Marcus Hagerotte7a71cb2016-12-09 16:26:14 -0800429 loadAccountTypes();
Chiao Cheng6c712f42012-11-26 15:35:28 -0800430 }
431
432 @Override
433 public void onStatusChanged(int which) {
Marcus Hagerott2a4673a2016-12-15 11:49:47 -0800434 reloadAccountTypesIfNeeded();
Chiao Cheng6c712f42012-11-26 15:35:28 -0800435 }
436
Marcus Hagerott9a679e72016-12-15 09:30:17 -0800437 /* This notification will arrive on the UI thread */
Chiao Cheng6c712f42012-11-26 15:35:28 -0800438 public void onAccountsUpdated(Account[] accounts) {
Marcus Hagerott2a4673a2016-12-15 11:49:47 -0800439 maybeNotifyAccountsUpdated(mAccountManagerAccounts,
440 getAccountsWithDataSets(accounts, mTypeProvider));
Chiao Cheng6c712f42012-11-26 15:35:28 -0800441 }
442
Marcus Hagerott2a4673a2016-12-15 11:49:47 -0800443 private void maybeNotifyAccountsUpdated(List<AccountWithDataSet> current,
444 List<AccountWithDataSet> update) {
445 if (Objects.equal(current, update)) {
446 return;
447 }
448 current.clear();
449 current.addAll(update);
450 notifyAccountsChanged();
451 }
452
453 private void notifyAccountsChanged() {
Marcus Hagerott04be88c2016-12-07 09:55:03 -0800454 ContactListFilterController.getInstance(mContext).checkFilterValidity(true);
Marcus Hagerotte7a71cb2016-12-09 16:26:14 -0800455 LocalBroadcastManager.getInstance(mContext).sendBroadcast(
456 new Intent(BROADCAST_ACCOUNTS_CHANGED));
Marcus Hagerott04be88c2016-12-07 09:55:03 -0800457 }
Marcus Hagerottfac695a2016-08-24 17:02:40 -0700458
Marcus Hagerott04be88c2016-12-07 09:55:03 -0800459 private synchronized void startLoadingIfNeeded() {
Marcus Hagerottc2093f32016-12-12 10:18:12 -0800460 if (mTypeProvider == null && mAccountTypesFuture == null) {
Marcus Hagerott2a4673a2016-12-15 11:49:47 -0800461 reloadAccountTypesIfNeeded();
Chiao Cheng6c712f42012-11-26 15:35:28 -0800462 }
Marcus Hagerottc2093f32016-12-12 10:18:12 -0800463 if (mLocalAccountsFuture == null) {
464 reloadLocalAccounts();
465 }
Chiao Cheng6c712f42012-11-26 15:35:28 -0800466 }
467
Marcus Hagerott2a4673a2016-12-15 11:49:47 -0800468 private synchronized void loadAccountTypes() {
Marcus Hagerotte7a71cb2016-12-09 16:26:14 -0800469 mTypeProvider = new AccountTypeProvider(mContext);
Chiao Cheng6c712f42012-11-26 15:35:28 -0800470
Marcus Hagerottc2093f32016-12-12 10:18:12 -0800471 mAccountTypesFuture = mExecutor.submit(new Callable<AccountTypeProvider>() {
Marcus Hagerott04be88c2016-12-07 09:55:03 -0800472 @Override
473 public AccountTypeProvider call() throws Exception {
Marcus Hagerott2a4673a2016-12-15 11:49:47 -0800474 // This will request the AccountType for each Account forcing them to be loaded
475 getAccountsWithDataSets(mAccountManager.getAccounts(), mTypeProvider);
Marcus Hagerotte7a71cb2016-12-09 16:26:14 -0800476 return mTypeProvider;
Chiao Cheng6c712f42012-11-26 15:35:28 -0800477 }
Marcus Hagerott04be88c2016-12-07 09:55:03 -0800478 });
Marcus Hagerotte7a71cb2016-12-09 16:26:14 -0800479 }
480
Marcus Hagerott2a4673a2016-12-15 11:49:47 -0800481 private FutureCallback<List<AccountWithDataSet>> newAccountsUpdatedCallback(
482 final List<AccountWithDataSet> currentAccounts) {
483 return new FutureCallback<List<AccountWithDataSet>>() {
484 @Override
485 public void onSuccess(List<AccountWithDataSet> result) {
486 maybeNotifyAccountsUpdated(currentAccounts, result);
487 }
488
489 @Override
490 public void onFailure(Throwable t) {
491 }
492 };
493 }
494
495 private synchronized void reloadAccountTypesIfNeeded() {
496 if (mTypeProvider == null || mTypeProvider.shouldUpdate(
497 mAccountManager.getAuthenticatorTypes(), ContentResolver.getSyncAdapterTypes())) {
498 reloadAccountTypes();
499 }
500 }
501
Marcus Hagerottc2093f32016-12-12 10:18:12 -0800502 private synchronized void reloadAccountTypes() {
Marcus Hagerotte7a71cb2016-12-09 16:26:14 -0800503 loadAccountTypes();
Marcus Hagerott2a4673a2016-12-15 11:49:47 -0800504 Futures.addCallback(
505 Futures.transform(mAccountTypesFuture, mAccountsExtractor),
506 newAccountsUpdatedCallback(mAccountManagerAccounts),
507 mMainThreadExecutor);
Marcus Hagerottc2093f32016-12-12 10:18:12 -0800508 }
Marcus Hagerott04be88c2016-12-07 09:55:03 -0800509
Marcus Hagerottc2093f32016-12-12 10:18:12 -0800510 private synchronized void loadLocalAccounts() {
511 mLocalAccountsFuture = mExecutor.submit(new Callable<List<AccountWithDataSet>>() {
Marcus Hagerott04be88c2016-12-07 09:55:03 -0800512 @Override
Marcus Hagerottc2093f32016-12-12 10:18:12 -0800513 public List<AccountWithDataSet> call() throws Exception {
514 return mLocalAccountLocator.getDeviceLocalAccounts();
Marcus Hagerott04be88c2016-12-07 09:55:03 -0800515 }
Marcus Hagerottc2093f32016-12-12 10:18:12 -0800516 });
517 }
518
Marcus Hagerott2a4673a2016-12-15 11:49:47 -0800519 private synchronized void reloadLocalAccounts() {
Marcus Hagerottc2093f32016-12-12 10:18:12 -0800520 loadLocalAccounts();
Marcus Hagerott2a4673a2016-12-15 11:49:47 -0800521 Futures.addCallback(mLocalAccountsFuture, newAccountsUpdatedCallback(mLocalAccounts),
522 mMainThreadExecutor);
Chiao Cheng6c712f42012-11-26 15:35:28 -0800523 }
524
525 /**
Gary Maiac333592016-09-28 17:27:40 -0700526 * Return list of all known or contact writable {@link AccountWithDataSet}'s.
527 * {@param contactWritableOnly} whether to restrict to contact writable accounts only
Chiao Cheng6c712f42012-11-26 15:35:28 -0800528 */
529 @Override
530 public List<AccountWithDataSet> getAccounts(boolean contactWritableOnly) {
Marcus Hagerott75895e72016-12-12 17:21:57 -0800531 final Predicate<AccountInfo> filter = contactWritableOnly ?
532 writableFilter() : Predicates.<AccountInfo>alwaysTrue();
Marcus Hagerottc2093f32016-12-12 10:18:12 -0800533 // TODO: Shouldn't have a synchronous version for getting all accounts
Marcus Hagerott75895e72016-12-12 17:21:57 -0800534 return Lists.transform(Futures.getUnchecked(filterAccountsAsync(filter)),
535 AccountInfo.ACCOUNT_EXTRACTOR);
Chiao Cheng6c712f42012-11-26 15:35:28 -0800536 }
537
Marcus Hagerott67a06392016-10-13 15:16:58 -0700538 @Override
Marcus Hagerott75895e72016-12-12 17:21:57 -0800539 public ListenableFuture<List<AccountInfo>> getAccountsAsync() {
540 return getAllAccountsAsyncInternal();
Marcus Hagerott67a06392016-10-13 15:16:58 -0700541 }
542
Marcus Hagerott2a4673a2016-12-15 11:49:47 -0800543 private synchronized ListenableFuture<List<AccountInfo>> getAllAccountsAsyncInternal() {
Marcus Hagerott04be88c2016-12-07 09:55:03 -0800544 startLoadingIfNeeded();
Marcus Hagerott75895e72016-12-12 17:21:57 -0800545 final AccountTypeProvider typeProvider = mTypeProvider;
Marcus Hagerott2a4673a2016-12-15 11:49:47 -0800546 final ListenableFuture<List<List<AccountWithDataSet>>> all =
547 Futures.nonCancellationPropagating(
548 Futures.successfulAsList(
549 Futures.transform(mAccountTypesFuture, mAccountsExtractor),
550 mLocalAccountsFuture));
Marcus Hagerottc2093f32016-12-12 10:18:12 -0800551
Marcus Hagerott2a4673a2016-12-15 11:49:47 -0800552 return Futures.transform(all, new Function<List<List<AccountWithDataSet>>,
Marcus Hagerott75895e72016-12-12 17:21:57 -0800553 List<AccountInfo>>() {
Marcus Hagerott2a4673a2016-12-15 11:49:47 -0800554 @Nullable
Marcus Hagerott75895e72016-12-12 17:21:57 -0800555 @Override
Marcus Hagerott2a4673a2016-12-15 11:49:47 -0800556 public List<AccountInfo> apply(@Nullable List<List<AccountWithDataSet>> input) {
Marcus Hagerott75895e72016-12-12 17:21:57 -0800557 // input.get(0) contains accounts from AccountManager
558 // input.get(1) contains device local accounts
559 Preconditions.checkArgument(input.size() == 2,
560 "List should have exactly 2 elements");
561
Marcus Hagerott2a4673a2016-12-15 11:49:47 -0800562 final List<AccountInfo> result = new ArrayList<>();
563 boolean hasWritableGoogleAccount = false;
564 for (AccountWithDataSet account : input.get(0)) {
565 hasWritableGoogleAccount = hasWritableGoogleAccount ||
566 (GoogleAccountType.ACCOUNT_TYPE.equals(account.type) &&
567 account.dataSet == null);
568
569 result.add(
570 typeProvider.getTypeForAccount(account).wrapAccount(mContext, account));
571 }
572
573 for (AccountWithDataSet account : input.get(1)) {
574 // Exclude the null account if a writable Google account exists because null
575 // account contacts are automatically converted to Google contacts in this case
576 if (hasWritableGoogleAccount && account.isNullAccount()) {
577 continue;
578 }
579 result.add(
580 typeProvider.getTypeForAccount(account).wrapAccount(mContext, account));
Marcus Hagerottc2093f32016-12-12 10:18:12 -0800581 }
Marcus Hagerott75895e72016-12-12 17:21:57 -0800582 AccountInfo.sortAccounts(null, result);
Marcus Hagerottc2093f32016-12-12 10:18:12 -0800583 return result;
584 }
585 });
586 }
587
Marcus Hagerott75895e72016-12-12 17:21:57 -0800588 @Override
589 public ListenableFuture<List<AccountInfo>> filterAccountsAsync(
590 final Predicate<AccountInfo> filter) {
591 return Futures.transform(getAllAccountsAsyncInternal(), new Function<List<AccountInfo>,
592 List<AccountInfo>>() {
593 @Override
594 public List<AccountInfo> apply(List<AccountInfo> input) {
595 return new ArrayList<>(Collections2.filter(input, filter));
596 }
597 }, mExecutor);
598 }
599
600 @Override
601 public AccountInfo getAccountInfoForAccount(AccountWithDataSet account) {
602 final AccountType type = mTypeProvider.getTypeForAccount(account);
603 if (type == null) {
604 return null;
605 }
606 return type.wrapAccount(mContext, account);
607 }
608
Marcus Hagerott2a4673a2016-12-15 11:49:47 -0800609 private List<AccountWithDataSet> getAccountsWithDataSets(Account[] accounts,
610 AccountTypeProvider typeProvider) {
611 List<AccountWithDataSet> result = new ArrayList<>();
Marcus Hagerott04be88c2016-12-07 09:55:03 -0800612 for (Account account : accounts) {
Marcus Hagerott75895e72016-12-12 17:21:57 -0800613 final List<AccountType> types = typeProvider.getAccountTypes(account.type);
Marcus Hagerott04be88c2016-12-07 09:55:03 -0800614 for (AccountType type : types) {
Marcus Hagerott2a4673a2016-12-15 11:49:47 -0800615 result.add(new AccountWithDataSet(
616 account.name, account.type, type.dataSet));
Marcus Hagerott04be88c2016-12-07 09:55:03 -0800617 }
618 }
619 return result;
620 }
621
Chiao Cheng6c712f42012-11-26 15:35:28 -0800622 /**
623 * Return the list of all known, group writable {@link AccountWithDataSet}'s.
624 */
625 public List<AccountWithDataSet> getGroupWritableAccounts() {
Marcus Hagerott75895e72016-12-12 17:21:57 -0800626 return Lists.transform(Futures.getUnchecked(
627 filterAccountsAsync(groupWritableFilter())), AccountInfo.ACCOUNT_EXTRACTOR);
Chiao Cheng6c712f42012-11-26 15:35:28 -0800628 }
629
630 /**
Wenyi Wang56c8a0c2016-09-30 11:11:10 -0700631 * Returns the default google account specified in preferences, the first google account
632 * if it is not specified in preferences or is no longer on the device, and null otherwise.
633 */
634 @Override
635 public Account getDefaultGoogleAccount() {
Wenyi Wang56c8a0c2016-09-30 11:11:10 -0700636 final SharedPreferences sharedPreferences =
637 mContext.getSharedPreferences(mContext.getPackageName(), Context.MODE_PRIVATE);
638 final String defaultAccountKey =
639 mContext.getResources().getString(R.string.contact_editor_default_account_key);
Marcus Hagerott396aab72016-12-12 12:00:21 -0800640 return getDefaultGoogleAccount(mAccountManager, sharedPreferences, defaultAccountKey);
641 }
642
643 @Override
Marcus Hagerott75895e72016-12-12 17:21:57 -0800644 public List<AccountInfo> getWritableGoogleAccounts() {
Marcus Hagerott396aab72016-12-12 12:00:21 -0800645 final Account[] googleAccounts =
646 mAccountManager.getAccountsByType(GoogleAccountType.ACCOUNT_TYPE);
Marcus Hagerott75895e72016-12-12 17:21:57 -0800647 final List<AccountInfo> result = new ArrayList<>();
Marcus Hagerott396aab72016-12-12 12:00:21 -0800648 for (Account account : googleAccounts) {
Marcus Hagerott75895e72016-12-12 17:21:57 -0800649 final AccountWithDataSet accountWithDataSet = new AccountWithDataSet(
650 account.name, account.type, null);
651 final AccountType type = mTypeProvider.getTypeForAccount(accountWithDataSet);
652
Marcus Hagerott396aab72016-12-12 12:00:21 -0800653 // Accounts with a dataSet (e.g. Google plus accounts) are not writable.
Marcus Hagerott75895e72016-12-12 17:21:57 -0800654 result.add(type.wrapAccount(mContext, accountWithDataSet));
Marcus Hagerott396aab72016-12-12 12:00:21 -0800655 }
656 return result;
657 }
658
659 @Override
660 public boolean hasNonLocalAccount() {
661 final Account[] accounts = mAccountManager.getAccounts();
662 return accounts != null && accounts.length > 0;
Wenyi Wang56c8a0c2016-09-30 11:11:10 -0700663 }
664
665 /**
Chiao Cheng6c712f42012-11-26 15:35:28 -0800666 * Find the best {@link DataKind} matching the requested
667 * {@link AccountType#accountType}, {@link AccountType#dataSet}, and {@link DataKind#mimeType}.
668 * If no direct match found, we try searching {@link FallbackAccountType}.
669 */
670 @Override
671 public DataKind getKindOrFallback(AccountType type, String mimeType) {
Chiao Cheng6c712f42012-11-26 15:35:28 -0800672 DataKind kind = null;
673
674 // Try finding account type and kind matching request
675 if (type != null) {
676 kind = type.getKindForMimetype(mimeType);
677 }
678
679 if (kind == null) {
680 // Nothing found, so try fallback as last resort
681 kind = mFallbackAccountType.getKindForMimetype(mimeType);
682 }
683
684 if (kind == null) {
Chiao Cheng5df73762012-12-12 11:28:35 -0800685 if (Log.isLoggable(TAG, Log.DEBUG)) {
686 Log.d(TAG, "Unknown type=" + type + ", mime=" + mimeType);
687 }
Chiao Cheng6c712f42012-11-26 15:35:28 -0800688 }
689
690 return kind;
691 }
692
693 /**
694 * Return {@link AccountType} for the given account type and data set.
695 */
696 @Override
697 public AccountType getAccountType(AccountTypeWithDataSet accountTypeWithDataSet) {
Marcus Hagerotte7a71cb2016-12-09 16:26:14 -0800698 return mTypeProvider.getType(
Marcus Hagerott04be88c2016-12-07 09:55:03 -0800699 accountTypeWithDataSet.accountType, accountTypeWithDataSet.dataSet);
Chiao Cheng6c712f42012-11-26 15:35:28 -0800700 }
Chiao Cheng6c712f42012-11-26 15:35:28 -0800701}