12ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey/*
22ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey * Copyright (C) 2009 The Android Open Source Project
32ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey *
42ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey * Licensed under the Apache License, Version 2.0 (the "License");
52ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey * you may not use this file except in compliance with the License.
62ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey * You may obtain a copy of the License at
72ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey *
82ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey *      http://www.apache.org/licenses/LICENSE-2.0
92ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey *
102ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey * Unless required by applicable law or agreed to in writing, software
112ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey * distributed under the License is distributed on an "AS IS" BASIS,
122ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
132ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey * See the License for the specific language governing permissions and
142ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey * limitations under the License.
152ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey */
162ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey
172ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkeypackage com.android.contacts.model;
182ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey
193f0b7b87cd41b5a9cd631b6fcf29ea5025905e18Jeff Sharkeyimport android.accounts.Account;
205f4af705cef8c914d5875983900e5cf5a5524b68Evan Millarimport android.accounts.AccountManager;
215f4af705cef8c914d5875983900e5cf5a5524b68Evan Millarimport android.accounts.AuthenticatorDescription;
228e55e0daa3967dbfdada4a42bec51a0435bc3384Fred Quintanaimport android.accounts.OnAccountsUpdateListener;
23ab066931efd4b6408b5f57026b421eb4a7934a39Jeff Sharkeyimport android.content.BroadcastReceiver;
243f0b7b87cd41b5a9cd631b6fcf29ea5025905e18Jeff Sharkeyimport android.content.ContentResolver;
25802b205ac677ffbde9aaf4fa3cfa9b94e8c98a44Jeff Sharkeyimport android.content.Context;
263f0b7b87cd41b5a9cd631b6fcf29ea5025905e18Jeff Sharkeyimport android.content.IContentService;
27ab066931efd4b6408b5f57026b421eb4a7934a39Jeff Sharkeyimport android.content.Intent;
28ab066931efd4b6408b5f57026b421eb4a7934a39Jeff Sharkeyimport android.content.IntentFilter;
293f0b7b87cd41b5a9cd631b6fcf29ea5025905e18Jeff Sharkeyimport android.content.SyncAdapterType;
30b5cd5959f0f0c5aa8ce50d3e872c0dcec12af9d4Dmitri Plotnikovimport android.content.SyncStatusObserver;
312ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkeyimport android.content.pm.PackageManager;
3208bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuanimport android.content.pm.ResolveInfo;
3308bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuanimport android.net.Uri;
3408bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuanimport android.os.AsyncTask;
35b5cd5959f0f0c5aa8ce50d3e872c0dcec12af9d4Dmitri Plotnikovimport android.os.Handler;
36b5cd5959f0f0c5aa8ce50d3e872c0dcec12af9d4Dmitri Plotnikovimport android.os.HandlerThread;
37a012aec6f22dd6a37c518c895db45b173e186ef4Daisuke Miyakawaimport android.os.Looper;
38b5cd5959f0f0c5aa8ce50d3e872c0dcec12af9d4Dmitri Plotnikovimport android.os.Message;
393f0b7b87cd41b5a9cd631b6fcf29ea5025905e18Jeff Sharkeyimport android.os.RemoteException;
40b77be6d2ad06edfd8751f55043e4aa9fd9f36015Dmitri Plotnikovimport android.os.SystemClock;
413f0b7b87cd41b5a9cd631b6fcf29ea5025905e18Jeff Sharkeyimport android.provider.ContactsContract;
429aa9e846dec6c2958be6ce120e138e484fdba330Makoto Onukiimport android.text.TextUtils;
433f0b7b87cd41b5a9cd631b6fcf29ea5025905e18Jeff Sharkeyimport android.util.Log;
44e598332967106e3db63b73c701f21902d169efefMakoto Onukiimport android.util.TimingLogger;
452ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey
46e0b2f1e2d01d1ac52ba207dc7ce76971d853298eChiao Chengimport com.android.contacts.ContactsUtils;
47e0b2f1e2d01d1ac52ba207dc7ce76971d853298eChiao Chengimport com.android.contacts.list.ContactListFilterController;
48851222a96b5d68602fb361ea3527101e893f67e3Maurice Chuimport com.android.contacts.model.account.AccountType;
49851222a96b5d68602fb361ea3527101e893f67e3Maurice Chuimport com.android.contacts.model.account.AccountTypeWithDataSet;
50851222a96b5d68602fb361ea3527101e893f67e3Maurice Chuimport com.android.contacts.model.account.AccountWithDataSet;
51851222a96b5d68602fb361ea3527101e893f67e3Maurice Chuimport com.android.contacts.model.account.ExchangeAccountType;
52851222a96b5d68602fb361ea3527101e893f67e3Maurice Chuimport com.android.contacts.model.account.ExternalAccountType;
53851222a96b5d68602fb361ea3527101e893f67e3Maurice Chuimport com.android.contacts.model.account.FallbackAccountType;
54851222a96b5d68602fb361ea3527101e893f67e3Maurice Chuimport com.android.contacts.model.account.GoogleAccountType;
55851222a96b5d68602fb361ea3527101e893f67e3Maurice Chuimport com.android.contacts.model.dataitem.DataKind;
56e0b2f1e2d01d1ac52ba207dc7ce76971d853298eChiao Chengimport com.android.contacts.util.Constants;
57e0b2f1e2d01d1ac52ba207dc7ce76971d853298eChiao Chengimport com.android.internal.util.Objects;
58e0b2f1e2d01d1ac52ba207dc7ce76971d853298eChiao Chengimport com.google.common.annotations.VisibleForTesting;
59e0b2f1e2d01d1ac52ba207dc7ce76971d853298eChiao Chengimport com.google.common.collect.Lists;
60e0b2f1e2d01d1ac52ba207dc7ce76971d853298eChiao Chengimport com.google.common.collect.Maps;
61e0b2f1e2d01d1ac52ba207dc7ce76971d853298eChiao Chengimport com.google.common.collect.Sets;
62e0b2f1e2d01d1ac52ba207dc7ce76971d853298eChiao Cheng
639aa9e846dec6c2958be6ce120e138e484fdba330Makoto Onukiimport java.util.Collection;
64b5cd5959f0f0c5aa8ce50d3e872c0dcec12af9d4Dmitri Plotnikovimport java.util.Collections;
65b5cd5959f0f0c5aa8ce50d3e872c0dcec12af9d4Dmitri Plotnikovimport java.util.Comparator;
662ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkeyimport java.util.HashMap;
672b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoroimport java.util.List;
689aa9e846dec6c2958be6ce120e138e484fdba330Makoto Onukiimport java.util.Map;
692b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoroimport java.util.Set;
7056c55827a83eef9ebafbb68beb3656c2506c1e0fDmitri Plotnikovimport java.util.concurrent.CountDownLatch;
7108bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuanimport java.util.concurrent.atomic.AtomicBoolean;
722ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey
732ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey/**
744597c92d655d45447780b32c7572acef110b6ed1Dmitri Plotnikov * Singleton holder for all parsed {@link AccountType} available on the
752ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey * system, typically filled through {@link PackageManager} queries.
762ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey */
776f667b55687bf9193323802e8f3234f0ab254388Dmitri Plotnikovpublic abstract class AccountTypeManager {
78179c9960e50019608d91661cfbcbb3cc8bc48093Dmitri Plotnikov    static final String TAG = "AccountTypeManager";
796f667b55687bf9193323802e8f3234f0ab254388Dmitri Plotnikov
806f667b55687bf9193323802e8f3234f0ab254388Dmitri Plotnikov    public static final String ACCOUNT_TYPE_SERVICE = "contactAccountTypes";
816f667b55687bf9193323802e8f3234f0ab254388Dmitri Plotnikov
826f667b55687bf9193323802e8f3234f0ab254388Dmitri Plotnikov    /**
836f667b55687bf9193323802e8f3234f0ab254388Dmitri Plotnikov     * Requests the singleton instance of {@link AccountTypeManager} with data bound from
846f667b55687bf9193323802e8f3234f0ab254388Dmitri Plotnikov     * the available authenticators. This method can safely be called from the UI thread.
856f667b55687bf9193323802e8f3234f0ab254388Dmitri Plotnikov     */
866f667b55687bf9193323802e8f3234f0ab254388Dmitri Plotnikov    public static AccountTypeManager getInstance(Context context) {
873572293500a7406813e61e6fedd63ca606debc19Jeff Hamilton        context = context.getApplicationContext();
88179c9960e50019608d91661cfbcbb3cc8bc48093Dmitri Plotnikov        AccountTypeManager service =
89179c9960e50019608d91661cfbcbb3cc8bc48093Dmitri Plotnikov                (AccountTypeManager) context.getSystemService(ACCOUNT_TYPE_SERVICE);
90179c9960e50019608d91661cfbcbb3cc8bc48093Dmitri Plotnikov        if (service == null) {
91179c9960e50019608d91661cfbcbb3cc8bc48093Dmitri Plotnikov            service = createAccountTypeManager(context);
92179c9960e50019608d91661cfbcbb3cc8bc48093Dmitri Plotnikov            Log.e(TAG, "No account type service in context: " + context);
93179c9960e50019608d91661cfbcbb3cc8bc48093Dmitri Plotnikov        }
94179c9960e50019608d91661cfbcbb3cc8bc48093Dmitri Plotnikov        return service;
956f667b55687bf9193323802e8f3234f0ab254388Dmitri Plotnikov    }
966f667b55687bf9193323802e8f3234f0ab254388Dmitri Plotnikov
976f667b55687bf9193323802e8f3234f0ab254388Dmitri Plotnikov    public static synchronized AccountTypeManager createAccountTypeManager(Context context) {
986f667b55687bf9193323802e8f3234f0ab254388Dmitri Plotnikov        return new AccountTypeManagerImpl(context);
996f667b55687bf9193323802e8f3234f0ab254388Dmitri Plotnikov    }
1006f667b55687bf9193323802e8f3234f0ab254388Dmitri Plotnikov
1016f74c0f3313cbb08ee8a8fbb79bfefc5b03fe215Makoto Onuki    /**
1026f74c0f3313cbb08ee8a8fbb79bfefc5b03fe215Makoto Onuki     * Returns the list of all accounts (if contactWritableOnly is false) or just the list of
1036f74c0f3313cbb08ee8a8fbb79bfefc5b03fe215Makoto Onuki     * contact writable accounts (if contactWritableOnly is true).
1046f74c0f3313cbb08ee8a8fbb79bfefc5b03fe215Makoto Onuki     */
1056f74c0f3313cbb08ee8a8fbb79bfefc5b03fe215Makoto Onuki    // TODO: Consider splitting this into getContactWritableAccounts() and getAllAccounts()
1066f74c0f3313cbb08ee8a8fbb79bfefc5b03fe215Makoto Onuki    public abstract List<AccountWithDataSet> getAccounts(boolean contactWritableOnly);
1076f74c0f3313cbb08ee8a8fbb79bfefc5b03fe215Makoto Onuki
1086f74c0f3313cbb08ee8a8fbb79bfefc5b03fe215Makoto Onuki    /**
1096f74c0f3313cbb08ee8a8fbb79bfefc5b03fe215Makoto Onuki     * Returns the list of accounts that are group writable.
1106f74c0f3313cbb08ee8a8fbb79bfefc5b03fe215Makoto Onuki     */
1116f74c0f3313cbb08ee8a8fbb79bfefc5b03fe215Makoto Onuki    public abstract List<AccountWithDataSet> getGroupWritableAccounts();
1126f667b55687bf9193323802e8f3234f0ab254388Dmitri Plotnikov
1133ae114e72617f2faea281d82f7f4ee026d8c5674Makoto Onuki    public abstract AccountType getAccountType(AccountTypeWithDataSet accountTypeWithDataSet);
1143ae114e72617f2faea281d82f7f4ee026d8c5674Makoto Onuki
1153ae114e72617f2faea281d82f7f4ee026d8c5674Makoto Onuki    public final AccountType getAccountType(String accountType, String dataSet) {
1163ae114e72617f2faea281d82f7f4ee026d8c5674Makoto Onuki        return getAccountType(AccountTypeWithDataSet.get(accountType, dataSet));
1173ae114e72617f2faea281d82f7f4ee026d8c5674Makoto Onuki    }
1183ae114e72617f2faea281d82f7f4ee026d8c5674Makoto Onuki
1193ae114e72617f2faea281d82f7f4ee026d8c5674Makoto Onuki    public final AccountType getAccountTypeForAccount(AccountWithDataSet account) {
1203ae114e72617f2faea281d82f7f4ee026d8c5674Makoto Onuki        return getAccountType(account.getAccountTypeWithDataSet());
1213ae114e72617f2faea281d82f7f4ee026d8c5674Makoto Onuki    }
1226f667b55687bf9193323802e8f3234f0ab254388Dmitri Plotnikov
1236f667b55687bf9193323802e8f3234f0ab254388Dmitri Plotnikov    /**
1246ad227f990265254864a04d3289292ca42330c71Makoto Onuki     * @return Unmodifiable map from {@link AccountTypeWithDataSet}s to {@link AccountType}s
1256ad227f990265254864a04d3289292ca42330c71Makoto Onuki     * which support the "invite" feature and have one or more account.
12608bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan     *
12708bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan     * This is a filtered down and more "usable" list compared to
12808bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan     * {@link #getAllInvitableAccountTypes}, where usable is defined as:
12908bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan     * (1) making sure that the app that contributed the account type is not disabled
13008bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan     * (in order to avoid presenting the user with an option that does nothing), and
13108bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan     * (2) that there is at least one raw contact with that account type in the database
13208bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan     * (assuming that the user probably doesn't use that account type).
13308bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan     *
13408bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan     * Warning: Don't use on the UI thread because this can scan the database.
1359aa9e846dec6c2958be6ce120e138e484fdba330Makoto Onuki     */
13608bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan    public abstract Map<AccountTypeWithDataSet, AccountType> getUsableInvitableAccountTypes();
1379aa9e846dec6c2958be6ce120e138e484fdba330Makoto Onuki
1389aa9e846dec6c2958be6ce120e138e484fdba330Makoto Onuki    /**
1396f667b55687bf9193323802e8f3234f0ab254388Dmitri Plotnikov     * Find the best {@link DataKind} matching the requested
1402b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro     * {@link AccountType#accountType}, {@link AccountType#dataSet}, and {@link DataKind#mimeType}.
1412b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro     * If no direct match found, we try searching {@link FallbackAccountType}.
1426f667b55687bf9193323802e8f3234f0ab254388Dmitri Plotnikov     */
1432b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro    public DataKind getKindOrFallback(String accountType, String dataSet, String mimeType) {
1442b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro        final AccountType type = getAccountType(accountType, dataSet);
1456f667b55687bf9193323802e8f3234f0ab254388Dmitri Plotnikov        return type == null ? null : type.getKindForMimetype(mimeType);
1466f667b55687bf9193323802e8f3234f0ab254388Dmitri Plotnikov    }
147558669dab4109afebd19eade1f95a396215fb44dMakoto Onuki
148a012aec6f22dd6a37c518c895db45b173e186ef4Daisuke Miyakawa    /**
149558669dab4109afebd19eade1f95a396215fb44dMakoto Onuki     * Returns all registered {@link AccountType}s, including extension ones.
150558669dab4109afebd19eade1f95a396215fb44dMakoto Onuki     *
151558669dab4109afebd19eade1f95a396215fb44dMakoto Onuki     * @param contactWritableOnly if true, it only returns ones that support writing contacts.
152558669dab4109afebd19eade1f95a396215fb44dMakoto Onuki     */
153558669dab4109afebd19eade1f95a396215fb44dMakoto Onuki    public abstract List<AccountType> getAccountTypes(boolean contactWritableOnly);
154a012aec6f22dd6a37c518c895db45b173e186ef4Daisuke Miyakawa
155a012aec6f22dd6a37c518c895db45b173e186ef4Daisuke Miyakawa    /**
156a012aec6f22dd6a37c518c895db45b173e186ef4Daisuke Miyakawa     * @param contactWritableOnly if true, it only returns ones that support writing contacts.
157a012aec6f22dd6a37c518c895db45b173e186ef4Daisuke Miyakawa     * @return true when this instance contains the given account.
158a012aec6f22dd6a37c518c895db45b173e186ef4Daisuke Miyakawa     */
159a012aec6f22dd6a37c518c895db45b173e186ef4Daisuke Miyakawa    public boolean contains(AccountWithDataSet account, boolean contactWritableOnly) {
160a012aec6f22dd6a37c518c895db45b173e186ef4Daisuke Miyakawa        for (AccountWithDataSet account_2 : getAccounts(false)) {
161a012aec6f22dd6a37c518c895db45b173e186ef4Daisuke Miyakawa            if (account.equals(account_2)) {
162a012aec6f22dd6a37c518c895db45b173e186ef4Daisuke Miyakawa                return true;
163a012aec6f22dd6a37c518c895db45b173e186ef4Daisuke Miyakawa            }
164a012aec6f22dd6a37c518c895db45b173e186ef4Daisuke Miyakawa        }
165a012aec6f22dd6a37c518c895db45b173e186ef4Daisuke Miyakawa        return false;
166a012aec6f22dd6a37c518c895db45b173e186ef4Daisuke Miyakawa    }
1676f667b55687bf9193323802e8f3234f0ab254388Dmitri Plotnikov}
1686f667b55687bf9193323802e8f3234f0ab254388Dmitri Plotnikov
1696f667b55687bf9193323802e8f3234f0ab254388Dmitri Plotnikovclass AccountTypeManagerImpl extends AccountTypeManager
170b5cd5959f0f0c5aa8ce50d3e872c0dcec12af9d4Dmitri Plotnikov        implements OnAccountsUpdateListener, SyncStatusObserver {
1712ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey
17208bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan    private static final Map<AccountTypeWithDataSet, AccountType>
17308bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan            EMPTY_UNMODIFIABLE_ACCOUNT_TYPE_MAP =
17408bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan            Collections.unmodifiableMap(new HashMap<AccountTypeWithDataSet, AccountType>());
17508bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan
17608bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan    /**
17708bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan     * A sample contact URI used to test whether any activities will respond to an
17808bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan     * invitable intent with the given URI as the intent data. This doesn't need to be
17908bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan     * specific to a real contact because an app that intercepts the intent should probably do so
18008bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan     * for all types of contact URIs.
18108bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan     */
18208bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan    private static final Uri SAMPLE_CONTACT_URI = ContactsContract.Contacts.getLookupUri(
18308bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan            1, "xxx");
18408bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan
1851e349d136d0162ce82a2d51b8a3724972e20bd7cDmitri Plotnikov    private Context mContext;
18699b1db73e9db33aa76883a7f60feafbc38e2860aJeff Sharkey    private AccountManager mAccountManager;
1875f4af705cef8c914d5875983900e5cf5a5524b68Evan Millar
1881dc28bef7d5353310359c3711b6cdac390d8a2e0Dmitri Plotnikov    private AccountType mFallbackAccountType;
189ab066931efd4b6408b5f57026b421eb4a7934a39Jeff Sharkey
1902b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro    private List<AccountWithDataSet> mAccounts = Lists.newArrayList();
1916f74c0f3313cbb08ee8a8fbb79bfefc5b03fe215Makoto Onuki    private List<AccountWithDataSet> mContactWritableAccounts = Lists.newArrayList();
1926f74c0f3313cbb08ee8a8fbb79bfefc5b03fe215Makoto Onuki    private List<AccountWithDataSet> mGroupWritableAccounts = Lists.newArrayList();
1936ad227f990265254864a04d3289292ca42330c71Makoto Onuki    private Map<AccountTypeWithDataSet, AccountType> mAccountTypesWithDataSets = Maps.newHashMap();
1946ad227f990265254864a04d3289292ca42330c71Makoto Onuki    private Map<AccountTypeWithDataSet, AccountType> mInvitableAccountTypes =
19508bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan            EMPTY_UNMODIFIABLE_ACCOUNT_TYPE_MAP;
19608bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan
19708bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan    private final InvitableAccountTypeCache mInvitableAccountTypeCache;
19808bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan
19908bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan    /**
20008bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan     * The boolean value is equal to true if the {@link InvitableAccountTypeCache} has been
20108bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan     * initialized. False otherwise.
20208bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan     */
20308bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan    private final AtomicBoolean mInvitablesCacheIsInitialized = new AtomicBoolean(false);
20408bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan
20508bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan    /**
20608bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan     * The boolean value is equal to true if the {@link FindInvitablesTask} is still executing.
20708bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan     * False otherwise.
20808bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan     */
20908bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan    private final AtomicBoolean mInvitablesTaskIsRunning = new AtomicBoolean(false);
2103f0b7b87cd41b5a9cd631b6fcf29ea5025905e18Jeff Sharkey
21156c55827a83eef9ebafbb68beb3656c2506c1e0fDmitri Plotnikov    private static final int MESSAGE_LOAD_DATA = 0;
21256c55827a83eef9ebafbb68beb3656c2506c1e0fDmitri Plotnikov    private static final int MESSAGE_PROCESS_BROADCAST_INTENT = 1;
213b5cd5959f0f0c5aa8ce50d3e872c0dcec12af9d4Dmitri Plotnikov
214b5cd5959f0f0c5aa8ce50d3e872c0dcec12af9d4Dmitri Plotnikov    private HandlerThread mListenerThread;
215b5cd5959f0f0c5aa8ce50d3e872c0dcec12af9d4Dmitri Plotnikov    private Handler mListenerHandler;
216b5cd5959f0f0c5aa8ce50d3e872c0dcec12af9d4Dmitri Plotnikov
217a012aec6f22dd6a37c518c895db45b173e186ef4Daisuke Miyakawa    private final Handler mMainThreadHandler = new Handler(Looper.getMainLooper());
218a012aec6f22dd6a37c518c895db45b173e186ef4Daisuke Miyakawa    private final Runnable mCheckFilterValidityRunnable = new Runnable () {
219a012aec6f22dd6a37c518c895db45b173e186ef4Daisuke Miyakawa        @Override
220a012aec6f22dd6a37c518c895db45b173e186ef4Daisuke Miyakawa        public void run() {
221be79b8f05f45c4cddeea9106399521529ec27caeKatherine Kuan            ContactListFilterController.getInstance(mContext).checkFilterValidity(true);
222a012aec6f22dd6a37c518c895db45b173e186ef4Daisuke Miyakawa        }
223a012aec6f22dd6a37c518c895db45b173e186ef4Daisuke Miyakawa    };
224a012aec6f22dd6a37c518c895db45b173e186ef4Daisuke Miyakawa
2256f667b55687bf9193323802e8f3234f0ab254388Dmitri Plotnikov    private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
2266f667b55687bf9193323802e8f3234f0ab254388Dmitri Plotnikov
2276f667b55687bf9193323802e8f3234f0ab254388Dmitri Plotnikov        @Override
2286f667b55687bf9193323802e8f3234f0ab254388Dmitri Plotnikov        public void onReceive(Context context, Intent intent) {
2296f667b55687bf9193323802e8f3234f0ab254388Dmitri Plotnikov            Message msg = mListenerHandler.obtainMessage(MESSAGE_PROCESS_BROADCAST_INTENT, intent);
2306f667b55687bf9193323802e8f3234f0ab254388Dmitri Plotnikov            mListenerHandler.sendMessage(msg);
2316f667b55687bf9193323802e8f3234f0ab254388Dmitri Plotnikov        }
2326f667b55687bf9193323802e8f3234f0ab254388Dmitri Plotnikov
2336f667b55687bf9193323802e8f3234f0ab254388Dmitri Plotnikov    };
2346f667b55687bf9193323802e8f3234f0ab254388Dmitri Plotnikov
23556c55827a83eef9ebafbb68beb3656c2506c1e0fDmitri Plotnikov    /* A latch that ensures that asynchronous initialization completes before data is used */
236b77be6d2ad06edfd8751f55043e4aa9fd9f36015Dmitri Plotnikov    private volatile CountDownLatch mInitializationLatch = new CountDownLatch(1);
23756c55827a83eef9ebafbb68beb3656c2506c1e0fDmitri Plotnikov
238b5cd5959f0f0c5aa8ce50d3e872c0dcec12af9d4Dmitri Plotnikov    private static final Comparator<Account> ACCOUNT_COMPARATOR = new Comparator<Account>() {
239b5cd5959f0f0c5aa8ce50d3e872c0dcec12af9d4Dmitri Plotnikov        @Override
2402b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro        public int compare(Account a, Account b) {
2412b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro            String aDataSet = null;
2422b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro            String bDataSet = null;
2432b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro            if (a instanceof AccountWithDataSet) {
2442b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro                aDataSet = ((AccountWithDataSet) a).dataSet;
2452b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro            }
2462b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro            if (b instanceof AccountWithDataSet) {
2472b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro                bDataSet = ((AccountWithDataSet) b).dataSet;
2482b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro            }
2492b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro
2502b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro            if (Objects.equal(a.name, b.name) && Objects.equal(a.type, b.type)
2512b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro                    && Objects.equal(aDataSet, bDataSet)) {
2522b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro                return 0;
2532b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro            } else if (b.name == null || b.type == null) {
2542b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro                return -1;
2552b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro            } else if (a.name == null || a.type == null) {
2562b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro                return 1;
2572b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro            } else {
2582b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro                int diff = a.name.compareTo(b.name);
2592b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro                if (diff != 0) {
2602b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro                    return diff;
2612b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro                }
2622b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro                diff = a.type.compareTo(b.type);
2632b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro                if (diff != 0) {
2642b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro                    return diff;
2652b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro                }
2662b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro
2672b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro                // Accounts without data sets get sorted before those that have them.
2682b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro                if (aDataSet != null) {
2692b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro                    return bDataSet == null ? 1 : aDataSet.compareTo(bDataSet);
2702b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro                } else {
2712b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro                    return -1;
2722b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro                }
273b5cd5959f0f0c5aa8ce50d3e872c0dcec12af9d4Dmitri Plotnikov            }
274b5cd5959f0f0c5aa8ce50d3e872c0dcec12af9d4Dmitri Plotnikov        }
275b5cd5959f0f0c5aa8ce50d3e872c0dcec12af9d4Dmitri Plotnikov    };
2765f4af705cef8c914d5875983900e5cf5a5524b68Evan Millar
2775f4af705cef8c914d5875983900e5cf5a5524b68Evan Millar    /**
2787f86847eddbec5dff4d87ac9243d839593582e42Jeff Sharkey     * Internal constructor that only performs initial parsing.
2792ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey     */
2806f667b55687bf9193323802e8f3234f0ab254388Dmitri Plotnikov    public AccountTypeManagerImpl(Context context) {
2811e349d136d0162ce82a2d51b8a3724972e20bd7cDmitri Plotnikov        mContext = context;
2821dc28bef7d5353310359c3711b6cdac390d8a2e0Dmitri Plotnikov        mFallbackAccountType = new FallbackAccountType(context);
2831dc28bef7d5353310359c3711b6cdac390d8a2e0Dmitri Plotnikov
284b77be6d2ad06edfd8751f55043e4aa9fd9f36015Dmitri Plotnikov        mAccountManager = AccountManager.get(mContext);
285ab066931efd4b6408b5f57026b421eb4a7934a39Jeff Sharkey
286b5cd5959f0f0c5aa8ce50d3e872c0dcec12af9d4Dmitri Plotnikov        mListenerThread = new HandlerThread("AccountChangeListener");
287b5cd5959f0f0c5aa8ce50d3e872c0dcec12af9d4Dmitri Plotnikov        mListenerThread.start();
288b5cd5959f0f0c5aa8ce50d3e872c0dcec12af9d4Dmitri Plotnikov        mListenerHandler = new Handler(mListenerThread.getLooper()) {
289b5cd5959f0f0c5aa8ce50d3e872c0dcec12af9d4Dmitri Plotnikov            @Override
290b5cd5959f0f0c5aa8ce50d3e872c0dcec12af9d4Dmitri Plotnikov            public void handleMessage(Message msg) {
291b5cd5959f0f0c5aa8ce50d3e872c0dcec12af9d4Dmitri Plotnikov                switch (msg.what) {
29256c55827a83eef9ebafbb68beb3656c2506c1e0fDmitri Plotnikov                    case MESSAGE_LOAD_DATA:
29356c55827a83eef9ebafbb68beb3656c2506c1e0fDmitri Plotnikov                        loadAccountsInBackground();
29456c55827a83eef9ebafbb68beb3656c2506c1e0fDmitri Plotnikov                        break;
295b5cd5959f0f0c5aa8ce50d3e872c0dcec12af9d4Dmitri Plotnikov                    case MESSAGE_PROCESS_BROADCAST_INTENT:
296b5cd5959f0f0c5aa8ce50d3e872c0dcec12af9d4Dmitri Plotnikov                        processBroadcastIntent((Intent) msg.obj);
297b5cd5959f0f0c5aa8ce50d3e872c0dcec12af9d4Dmitri Plotnikov                        break;
298b5cd5959f0f0c5aa8ce50d3e872c0dcec12af9d4Dmitri Plotnikov                }
299b5cd5959f0f0c5aa8ce50d3e872c0dcec12af9d4Dmitri Plotnikov            }
300b5cd5959f0f0c5aa8ce50d3e872c0dcec12af9d4Dmitri Plotnikov        };
301b5cd5959f0f0c5aa8ce50d3e872c0dcec12af9d4Dmitri Plotnikov
30208bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan        mInvitableAccountTypeCache = new InvitableAccountTypeCache();
30308bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan
30499b1db73e9db33aa76883a7f60feafbc38e2860aJeff Sharkey        // Request updates when packages or accounts change
305f4ac04628763d8df783768b86d5e7b162a9a3d38Daisuke Miyakawa        IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
30699b1db73e9db33aa76883a7f60feafbc38e2860aJeff Sharkey        filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
30799b1db73e9db33aa76883a7f60feafbc38e2860aJeff Sharkey        filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
30899b1db73e9db33aa76883a7f60feafbc38e2860aJeff Sharkey        filter.addDataScheme("package");
3096f667b55687bf9193323802e8f3234f0ab254388Dmitri Plotnikov        mContext.registerReceiver(mBroadcastReceiver, filter);
310e23c9e1380c1489901c95d34c6974d1848120416Suchi Amalapurapu        IntentFilter sdFilter = new IntentFilter();
311e23c9e1380c1489901c95d34c6974d1848120416Suchi Amalapurapu        sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
312e23c9e1380c1489901c95d34c6974d1848120416Suchi Amalapurapu        sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
3136f667b55687bf9193323802e8f3234f0ab254388Dmitri Plotnikov        mContext.registerReceiver(mBroadcastReceiver, sdFilter);
31499b1db73e9db33aa76883a7f60feafbc38e2860aJeff Sharkey
315f4ac04628763d8df783768b86d5e7b162a9a3d38Daisuke Miyakawa        // Request updates when locale is changed so that the order of each field will
316f4ac04628763d8df783768b86d5e7b162a9a3d38Daisuke Miyakawa        // be able to be changed on the locale change.
317f4ac04628763d8df783768b86d5e7b162a9a3d38Daisuke Miyakawa        filter = new IntentFilter(Intent.ACTION_LOCALE_CHANGED);
3186f667b55687bf9193323802e8f3234f0ab254388Dmitri Plotnikov        mContext.registerReceiver(mBroadcastReceiver, filter);
319f4ac04628763d8df783768b86d5e7b162a9a3d38Daisuke Miyakawa
320b5cd5959f0f0c5aa8ce50d3e872c0dcec12af9d4Dmitri Plotnikov        mAccountManager.addOnAccountsUpdatedListener(this, mListenerHandler, false);
321b5cd5959f0f0c5aa8ce50d3e872c0dcec12af9d4Dmitri Plotnikov
322b5cd5959f0f0c5aa8ce50d3e872c0dcec12af9d4Dmitri Plotnikov        ContentResolver.addStatusChangeListener(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS, this);
32356c55827a83eef9ebafbb68beb3656c2506c1e0fDmitri Plotnikov
32456c55827a83eef9ebafbb68beb3656c2506c1e0fDmitri Plotnikov        mListenerHandler.sendEmptyMessage(MESSAGE_LOAD_DATA);
3252ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey    }
3262ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey
327b5cd5959f0f0c5aa8ce50d3e872c0dcec12af9d4Dmitri Plotnikov    @Override
328b5cd5959f0f0c5aa8ce50d3e872c0dcec12af9d4Dmitri Plotnikov    public void onStatusChanged(int which) {
329b77be6d2ad06edfd8751f55043e4aa9fd9f36015Dmitri Plotnikov        mListenerHandler.sendEmptyMessage(MESSAGE_LOAD_DATA);
330b5cd5959f0f0c5aa8ce50d3e872c0dcec12af9d4Dmitri Plotnikov    }
331b5cd5959f0f0c5aa8ce50d3e872c0dcec12af9d4Dmitri Plotnikov
332b5cd5959f0f0c5aa8ce50d3e872c0dcec12af9d4Dmitri Plotnikov    public void processBroadcastIntent(Intent intent) {
333b77be6d2ad06edfd8751f55043e4aa9fd9f36015Dmitri Plotnikov        mListenerHandler.sendEmptyMessage(MESSAGE_LOAD_DATA);
334f4ac04628763d8df783768b86d5e7b162a9a3d38Daisuke Miyakawa    }
335f4ac04628763d8df783768b86d5e7b162a9a3d38Daisuke Miyakawa
336b5cd5959f0f0c5aa8ce50d3e872c0dcec12af9d4Dmitri Plotnikov    /* This notification will arrive on the background thread */
33799b1db73e9db33aa76883a7f60feafbc38e2860aJeff Sharkey    public void onAccountsUpdated(Account[] accounts) {
33899b1db73e9db33aa76883a7f60feafbc38e2860aJeff Sharkey        // Refresh to catch any changed accounts
33956c55827a83eef9ebafbb68beb3656c2506c1e0fDmitri Plotnikov        loadAccountsInBackground();
34056c55827a83eef9ebafbb68beb3656c2506c1e0fDmitri Plotnikov    }
34156c55827a83eef9ebafbb68beb3656c2506c1e0fDmitri Plotnikov
34256c55827a83eef9ebafbb68beb3656c2506c1e0fDmitri Plotnikov    /**
34356c55827a83eef9ebafbb68beb3656c2506c1e0fDmitri Plotnikov     * Returns instantly if accounts and account types have already been loaded.
34456c55827a83eef9ebafbb68beb3656c2506c1e0fDmitri Plotnikov     * Otherwise waits for the background thread to complete the loading.
34556c55827a83eef9ebafbb68beb3656c2506c1e0fDmitri Plotnikov     */
34656c55827a83eef9ebafbb68beb3656c2506c1e0fDmitri Plotnikov    void ensureAccountsLoaded() {
34756c55827a83eef9ebafbb68beb3656c2506c1e0fDmitri Plotnikov        CountDownLatch latch = mInitializationLatch;
34856c55827a83eef9ebafbb68beb3656c2506c1e0fDmitri Plotnikov        if (latch == null) {
34956c55827a83eef9ebafbb68beb3656c2506c1e0fDmitri Plotnikov            return;
35056c55827a83eef9ebafbb68beb3656c2506c1e0fDmitri Plotnikov        }
35156c55827a83eef9ebafbb68beb3656c2506c1e0fDmitri Plotnikov        while (true) {
35256c55827a83eef9ebafbb68beb3656c2506c1e0fDmitri Plotnikov            try {
35356c55827a83eef9ebafbb68beb3656c2506c1e0fDmitri Plotnikov                latch.await();
35456c55827a83eef9ebafbb68beb3656c2506c1e0fDmitri Plotnikov                return;
35556c55827a83eef9ebafbb68beb3656c2506c1e0fDmitri Plotnikov            } catch (InterruptedException e) {
35656c55827a83eef9ebafbb68beb3656c2506c1e0fDmitri Plotnikov                Thread.currentThread().interrupt();
35756c55827a83eef9ebafbb68beb3656c2506c1e0fDmitri Plotnikov            }
35856c55827a83eef9ebafbb68beb3656c2506c1e0fDmitri Plotnikov        }
35999b1db73e9db33aa76883a7f60feafbc38e2860aJeff Sharkey    }
36099b1db73e9db33aa76883a7f60feafbc38e2860aJeff Sharkey
3612ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey    /**
3622b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro     * Loads account list and corresponding account types (potentially with data sets). Always
3632b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro     * called on a background thread.
3642ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey     */
36556c55827a83eef9ebafbb68beb3656c2506c1e0fDmitri Plotnikov    protected void loadAccountsInBackground() {
36649627cc3f606085d001397ebba93dcb52bf67a5cMakoto Onuki        if (Log.isLoggable(Constants.PERFORMANCE_TAG, Log.DEBUG)) {
36749627cc3f606085d001397ebba93dcb52bf67a5cMakoto Onuki            Log.d(Constants.PERFORMANCE_TAG, "AccountTypeManager.loadAccountsInBackground start");
36849627cc3f606085d001397ebba93dcb52bf67a5cMakoto Onuki        }
369e598332967106e3db63b73c701f21902d169efefMakoto Onuki        TimingLogger timings = new TimingLogger(TAG, "loadAccountsInBackground");
370e598332967106e3db63b73c701f21902d169efefMakoto Onuki        final long startTime = SystemClock.currentThreadTimeMillis();
371e598332967106e3db63b73c701f21902d169efefMakoto Onuki        final long startTimeWall = SystemClock.elapsedRealtime();
372b77be6d2ad06edfd8751f55043e4aa9fd9f36015Dmitri Plotnikov
3732b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro        // Account types, keyed off the account type and data set concatenation.
3746f74c0f3313cbb08ee8a8fbb79bfefc5b03fe215Makoto Onuki        final Map<AccountTypeWithDataSet, AccountType> accountTypesByTypeAndDataSet =
3756f74c0f3313cbb08ee8a8fbb79bfefc5b03fe215Makoto Onuki                Maps.newHashMap();
3762b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro
3772b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro        // The same AccountTypes, but keyed off {@link RawContacts#ACCOUNT_TYPE}.  Since there can
3782b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro        // be multiple account types (with different data sets) for the same type of account, each
3792b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro        // type string may have multiple AccountType entries.
3806f74c0f3313cbb08ee8a8fbb79bfefc5b03fe215Makoto Onuki        final Map<String, List<AccountType>> accountTypesByType = Maps.newHashMap();
3812b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro
3826f74c0f3313cbb08ee8a8fbb79bfefc5b03fe215Makoto Onuki        final List<AccountWithDataSet> allAccounts = Lists.newArrayList();
3836f74c0f3313cbb08ee8a8fbb79bfefc5b03fe215Makoto Onuki        final List<AccountWithDataSet> contactWritableAccounts = Lists.newArrayList();
3846f74c0f3313cbb08ee8a8fbb79bfefc5b03fe215Makoto Onuki        final List<AccountWithDataSet> groupWritableAccounts = Lists.newArrayList();
3856f74c0f3313cbb08ee8a8fbb79bfefc5b03fe215Makoto Onuki        final Set<String> extensionPackages = Sets.newHashSet();
3862ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey
38799b1db73e9db33aa76883a7f60feafbc38e2860aJeff Sharkey        final AccountManager am = mAccountManager;
3883f0b7b87cd41b5a9cd631b6fcf29ea5025905e18Jeff Sharkey        final IContentService cs = ContentResolver.getContentService();
3892ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey
3903f0b7b87cd41b5a9cd631b6fcf29ea5025905e18Jeff Sharkey        try {
3913f0b7b87cd41b5a9cd631b6fcf29ea5025905e18Jeff Sharkey            final SyncAdapterType[] syncs = cs.getSyncAdapterTypes();
3923f0b7b87cd41b5a9cd631b6fcf29ea5025905e18Jeff Sharkey            final AuthenticatorDescription[] auths = am.getAuthenticatorTypes();
3932ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey
3942b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro            // First process sync adapters to find any that provide contact data.
3953f0b7b87cd41b5a9cd631b6fcf29ea5025905e18Jeff Sharkey            for (SyncAdapterType sync : syncs) {
396b5f40208255bf1d326e70716d5916e463bec397eJeff Hamilton                if (!ContactsContract.AUTHORITY.equals(sync.authority)) {
3973f0b7b87cd41b5a9cd631b6fcf29ea5025905e18Jeff Sharkey                    // Skip sync adapters that don't provide contact data.
3983f0b7b87cd41b5a9cd631b6fcf29ea5025905e18Jeff Sharkey                    continue;
3993f0b7b87cd41b5a9cd631b6fcf29ea5025905e18Jeff Sharkey                }
4002ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey
4013f0b7b87cd41b5a9cd631b6fcf29ea5025905e18Jeff Sharkey                // Look for the formatting details provided by each sync
4023f0b7b87cd41b5a9cd631b6fcf29ea5025905e18Jeff Sharkey                // adapter, using the authenticator to find general resources.
40369f9e6f0cd9b5401da55f251e9bd98e69643d7dfDmitri Plotnikov                final String type = sync.accountType;
40469f9e6f0cd9b5401da55f251e9bd98e69643d7dfDmitri Plotnikov                final AuthenticatorDescription auth = findAuthenticator(auths, type);
40532d64e590d4f9302d204d9a96960ca5739807500Nicklas Shamlo                if (auth == null) {
40669f9e6f0cd9b5401da55f251e9bd98e69643d7dfDmitri Plotnikov                    Log.w(TAG, "No authenticator found for type=" + type + ", ignoring it.");
40732d64e590d4f9302d204d9a96960ca5739807500Nicklas Shamlo                    continue;
40832d64e590d4f9302d204d9a96960ca5739807500Nicklas Shamlo                }
409e731d426eda3692402f3cecdc29421fcf7f1fb54Jeff Sharkey
41069f9e6f0cd9b5401da55f251e9bd98e69643d7dfDmitri Plotnikov                AccountType accountType;
41169f9e6f0cd9b5401da55f251e9bd98e69643d7dfDmitri Plotnikov                if (GoogleAccountType.ACCOUNT_TYPE.equals(type)) {
412b77be6d2ad06edfd8751f55043e4aa9fd9f36015Dmitri Plotnikov                    accountType = new GoogleAccountType(mContext, auth.packageName);
413de8a66404acd36544e3559404f6542473224aa4aMarc Blank                } else if (ExchangeAccountType.isExchangeType(type)) {
414de8a66404acd36544e3559404f6542473224aa4aMarc Blank                    accountType = new ExchangeAccountType(mContext, auth.packageName, type);
415ab066931efd4b6408b5f57026b421eb4a7934a39Jeff Sharkey                } else {
416ab066931efd4b6408b5f57026b421eb4a7934a39Jeff Sharkey                    // TODO: use syncadapter package instead, since it provides resources
417b77be6d2ad06edfd8751f55043e4aa9fd9f36015Dmitri Plotnikov                    Log.d(TAG, "Registering external account type=" + type
418ab066931efd4b6408b5f57026b421eb4a7934a39Jeff Sharkey                            + ", packageName=" + auth.packageName);
419b5e4419f4ba06d630331669a08613936889f34dfMakoto Onuki                    accountType = new ExternalAccountType(mContext, auth.packageName, false);
42086ccb6ce2ff6078b06add93aab489951ef83bc72Makoto Onuki                }
42186ccb6ce2ff6078b06add93aab489951ef83bc72Makoto Onuki                if (!accountType.isInitialized()) {
42286ccb6ce2ff6078b06add93aab489951ef83bc72Makoto Onuki                    if (accountType.isEmbedded()) {
42386ccb6ce2ff6078b06add93aab489951ef83bc72Makoto Onuki                        throw new IllegalStateException("Problem initializing embedded type "
42486ccb6ce2ff6078b06add93aab489951ef83bc72Makoto Onuki                                + accountType.getClass().getCanonicalName());
42586ccb6ce2ff6078b06add93aab489951ef83bc72Makoto Onuki                    } else {
4265226abf2ee19f53b9d9bf63109138aff79c7a222Dave Santoro                        // Skip external account types that couldn't be initialized.
4275226abf2ee19f53b9d9bf63109138aff79c7a222Dave Santoro                        continue;
4285226abf2ee19f53b9d9bf63109138aff79c7a222Dave Santoro                    }
429ab066931efd4b6408b5f57026b421eb4a7934a39Jeff Sharkey                }
430ab066931efd4b6408b5f57026b421eb4a7934a39Jeff Sharkey
43169f9e6f0cd9b5401da55f251e9bd98e69643d7dfDmitri Plotnikov                accountType.accountType = auth.type;
43269f9e6f0cd9b5401da55f251e9bd98e69643d7dfDmitri Plotnikov                accountType.titleRes = auth.labelId;
43369f9e6f0cd9b5401da55f251e9bd98e69643d7dfDmitri Plotnikov                accountType.iconRes = auth.iconId;
4342ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey
4352b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro                addAccountType(accountType, accountTypesByTypeAndDataSet, accountTypesByType);
4362b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro
4372b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro                // Check to see if the account type knows of any other non-sync-adapter packages
4382b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro                // that may provide other data sets of contact data.
4392b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro                extensionPackages.addAll(accountType.getExtensionPackageNames());
4402b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro            }
4412b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro
4422b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro            // If any extension packages were specified, process them as well.
4432b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro            if (!extensionPackages.isEmpty()) {
4442b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro                Log.d(TAG, "Registering " + extensionPackages.size() + " extension packages");
4452b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro                for (String extensionPackage : extensionPackages) {
4462b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro                    ExternalAccountType accountType =
447b5e4419f4ba06d630331669a08613936889f34dfMakoto Onuki                            new ExternalAccountType(mContext, extensionPackage, true);
4485226abf2ee19f53b9d9bf63109138aff79c7a222Dave Santoro                    if (!accountType.isInitialized()) {
4495226abf2ee19f53b9d9bf63109138aff79c7a222Dave Santoro                        // Skip external account types that couldn't be initialized.
4505226abf2ee19f53b9d9bf63109138aff79c7a222Dave Santoro                        continue;
4515226abf2ee19f53b9d9bf63109138aff79c7a222Dave Santoro                    }
4528545ec4d78e5bdaec87b0ae59c448acc96856b4cMakoto Onuki                    if (!accountType.hasContactsMetadata()) {
4538545ec4d78e5bdaec87b0ae59c448acc96856b4cMakoto Onuki                        Log.w(TAG, "Skipping extension package " + extensionPackage + " because"
4548545ec4d78e5bdaec87b0ae59c448acc96856b4cMakoto Onuki                                + " it doesn't have the CONTACTS_STRUCTURE metadata");
4558545ec4d78e5bdaec87b0ae59c448acc96856b4cMakoto Onuki                        continue;
4568545ec4d78e5bdaec87b0ae59c448acc96856b4cMakoto Onuki                    }
4578545ec4d78e5bdaec87b0ae59c448acc96856b4cMakoto Onuki                    if (TextUtils.isEmpty(accountType.accountType)) {
4588545ec4d78e5bdaec87b0ae59c448acc96856b4cMakoto Onuki                        Log.w(TAG, "Skipping extension package " + extensionPackage + " because"
4598545ec4d78e5bdaec87b0ae59c448acc96856b4cMakoto Onuki                                + " the CONTACTS_STRUCTURE metadata doesn't have the accountType"
4608545ec4d78e5bdaec87b0ae59c448acc96856b4cMakoto Onuki                                + " attribute");
4618545ec4d78e5bdaec87b0ae59c448acc96856b4cMakoto Onuki                        continue;
4628545ec4d78e5bdaec87b0ae59c448acc96856b4cMakoto Onuki                    }
4632b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro                    Log.d(TAG, "Registering extension package account type="
4642b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro                            + accountType.accountType + ", dataSet=" + accountType.dataSet
4652b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro                            + ", packageName=" + extensionPackage);
4662b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro
4672b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro                    addAccountType(accountType, accountTypesByTypeAndDataSet, accountTypesByType);
4682b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro                }
4693f0b7b87cd41b5a9cd631b6fcf29ea5025905e18Jeff Sharkey            }
4703f0b7b87cd41b5a9cd631b6fcf29ea5025905e18Jeff Sharkey        } catch (RemoteException e) {
4713f0b7b87cd41b5a9cd631b6fcf29ea5025905e18Jeff Sharkey            Log.w(TAG, "Problem loading accounts: " + e.toString());
4722ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey        }
473e598332967106e3db63b73c701f21902d169efefMakoto Onuki        timings.addSplit("Loaded account types");
474b5cd5959f0f0c5aa8ce50d3e872c0dcec12af9d4Dmitri Plotnikov
4752b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro        // Map in accounts to associate the account names with each account type entry.
476b5cd5959f0f0c5aa8ce50d3e872c0dcec12af9d4Dmitri Plotnikov        Account[] accounts = mAccountManager.getAccounts();
477b5cd5959f0f0c5aa8ce50d3e872c0dcec12af9d4Dmitri Plotnikov        for (Account account : accounts) {
478b5cd5959f0f0c5aa8ce50d3e872c0dcec12af9d4Dmitri Plotnikov            boolean syncable = false;
479b5cd5959f0f0c5aa8ce50d3e872c0dcec12af9d4Dmitri Plotnikov            try {
4802b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro                syncable = cs.getIsSyncable(account, ContactsContract.AUTHORITY) > 0;
481b5cd5959f0f0c5aa8ce50d3e872c0dcec12af9d4Dmitri Plotnikov            } catch (RemoteException e) {
482b5cd5959f0f0c5aa8ce50d3e872c0dcec12af9d4Dmitri Plotnikov                Log.e(TAG, "Cannot obtain sync flag for account: " + account, e);
483b5cd5959f0f0c5aa8ce50d3e872c0dcec12af9d4Dmitri Plotnikov            }
484b5cd5959f0f0c5aa8ce50d3e872c0dcec12af9d4Dmitri Plotnikov
485b5cd5959f0f0c5aa8ce50d3e872c0dcec12af9d4Dmitri Plotnikov            if (syncable) {
4862b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro                List<AccountType> accountTypes = accountTypesByType.get(account.type);
4872b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro                if (accountTypes != null) {
4882b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro                    // Add an account-with-data-set entry for each account type that is
4892b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro                    // authenticated by this account.
4902b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro                    for (AccountType accountType : accountTypes) {
4912b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro                        AccountWithDataSet accountWithDataSet = new AccountWithDataSet(
4922b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro                                account.name, account.type, accountType.dataSet);
4932b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro                        allAccounts.add(accountWithDataSet);
49496e87fd6e808e9d853263ffeb5104f3253a18639Daniel Lehmann                        if (accountType.areContactsWritable()) {
4956f74c0f3313cbb08ee8a8fbb79bfefc5b03fe215Makoto Onuki                            contactWritableAccounts.add(accountWithDataSet);
4966f74c0f3313cbb08ee8a8fbb79bfefc5b03fe215Makoto Onuki                        }
4976f74c0f3313cbb08ee8a8fbb79bfefc5b03fe215Makoto Onuki                        if (accountType.isGroupMembershipEditable()) {
4986f74c0f3313cbb08ee8a8fbb79bfefc5b03fe215Makoto Onuki                            groupWritableAccounts.add(accountWithDataSet);
4992b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro                        }
500b5cd5959f0f0c5aa8ce50d3e872c0dcec12af9d4Dmitri Plotnikov                    }
501b5cd5959f0f0c5aa8ce50d3e872c0dcec12af9d4Dmitri Plotnikov                }
502b5cd5959f0f0c5aa8ce50d3e872c0dcec12af9d4Dmitri Plotnikov            }
503b5cd5959f0f0c5aa8ce50d3e872c0dcec12af9d4Dmitri Plotnikov        }
504b5cd5959f0f0c5aa8ce50d3e872c0dcec12af9d4Dmitri Plotnikov
505b77be6d2ad06edfd8751f55043e4aa9fd9f36015Dmitri Plotnikov        Collections.sort(allAccounts, ACCOUNT_COMPARATOR);
5066f74c0f3313cbb08ee8a8fbb79bfefc5b03fe215Makoto Onuki        Collections.sort(contactWritableAccounts, ACCOUNT_COMPARATOR);
5076f74c0f3313cbb08ee8a8fbb79bfefc5b03fe215Makoto Onuki        Collections.sort(groupWritableAccounts, ACCOUNT_COMPARATOR);
508169ff2ab666eb0cf5414b3a5eb8333ec4ddc669aDmitri Plotnikov
509e598332967106e3db63b73c701f21902d169efefMakoto Onuki        timings.addSplit("Loaded accounts");
510b77be6d2ad06edfd8751f55043e4aa9fd9f36015Dmitri Plotnikov
511b77be6d2ad06edfd8751f55043e4aa9fd9f36015Dmitri Plotnikov        synchronized (this) {
5122b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro            mAccountTypesWithDataSets = accountTypesByTypeAndDataSet;
513b77be6d2ad06edfd8751f55043e4aa9fd9f36015Dmitri Plotnikov            mAccounts = allAccounts;
5146f74c0f3313cbb08ee8a8fbb79bfefc5b03fe215Makoto Onuki            mContactWritableAccounts = contactWritableAccounts;
5156f74c0f3313cbb08ee8a8fbb79bfefc5b03fe215Makoto Onuki            mGroupWritableAccounts = groupWritableAccounts;
51608bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan            mInvitableAccountTypes = findAllInvitableAccountTypes(
5172b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro                    mContext, allAccounts, accountTypesByTypeAndDataSet);
518b77be6d2ad06edfd8751f55043e4aa9fd9f36015Dmitri Plotnikov        }
519b77be6d2ad06edfd8751f55043e4aa9fd9f36015Dmitri Plotnikov
520e598332967106e3db63b73c701f21902d169efefMakoto Onuki        timings.dumpToLog();
521e598332967106e3db63b73c701f21902d169efefMakoto Onuki        final long endTimeWall = SystemClock.elapsedRealtime();
522e598332967106e3db63b73c701f21902d169efefMakoto Onuki        final long endTime = SystemClock.currentThreadTimeMillis();
523e598332967106e3db63b73c701f21902d169efefMakoto Onuki
5242b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro        Log.i(TAG, "Loaded meta-data for " + mAccountTypesWithDataSets.size() + " account types, "
525e598332967106e3db63b73c701f21902d169efefMakoto Onuki                + mAccounts.size() + " accounts in " + (endTimeWall - startTimeWall) + "ms(wall) "
526e598332967106e3db63b73c701f21902d169efefMakoto Onuki                + (endTime - startTime) + "ms(cpu)");
527b77be6d2ad06edfd8751f55043e4aa9fd9f36015Dmitri Plotnikov
52856c55827a83eef9ebafbb68beb3656c2506c1e0fDmitri Plotnikov        if (mInitializationLatch != null) {
52956c55827a83eef9ebafbb68beb3656c2506c1e0fDmitri Plotnikov            mInitializationLatch.countDown();
53056c55827a83eef9ebafbb68beb3656c2506c1e0fDmitri Plotnikov            mInitializationLatch = null;
53156c55827a83eef9ebafbb68beb3656c2506c1e0fDmitri Plotnikov        }
53249627cc3f606085d001397ebba93dcb52bf67a5cMakoto Onuki        if (Log.isLoggable(Constants.PERFORMANCE_TAG, Log.DEBUG)) {
53349627cc3f606085d001397ebba93dcb52bf67a5cMakoto Onuki            Log.d(Constants.PERFORMANCE_TAG, "AccountTypeManager.loadAccountsInBackground finish");
53449627cc3f606085d001397ebba93dcb52bf67a5cMakoto Onuki        }
535a012aec6f22dd6a37c518c895db45b173e186ef4Daisuke Miyakawa
536a012aec6f22dd6a37c518c895db45b173e186ef4Daisuke Miyakawa        // Check filter validity since filter may become obsolete after account update. It must be
537a012aec6f22dd6a37c518c895db45b173e186ef4Daisuke Miyakawa        // done from UI thread.
538a012aec6f22dd6a37c518c895db45b173e186ef4Daisuke Miyakawa        mMainThreadHandler.post(mCheckFilterValidityRunnable);
5392ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey    }
5402ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey
5412b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro    // Bookkeeping method for tracking the known account types in the given maps.
5422b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro    private void addAccountType(AccountType accountType,
5436ad227f990265254864a04d3289292ca42330c71Makoto Onuki            Map<AccountTypeWithDataSet, AccountType> accountTypesByTypeAndDataSet,
5442b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro            Map<String, List<AccountType>> accountTypesByType) {
5452b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro        accountTypesByTypeAndDataSet.put(accountType.getAccountTypeAndDataSet(), accountType);
5462b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro        List<AccountType> accountsForType = accountTypesByType.get(accountType.accountType);
5472b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro        if (accountsForType == null) {
5482b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro            accountsForType = Lists.newArrayList();
5492b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro        }
5502b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro        accountsForType.add(accountType);
5512b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro        accountTypesByType.put(accountType.accountType, accountsForType);
5522b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro    }
5532b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro
5542ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey    /**
5553f0b7b87cd41b5a9cd631b6fcf29ea5025905e18Jeff Sharkey     * Find a specific {@link AuthenticatorDescription} in the provided list
5563f0b7b87cd41b5a9cd631b6fcf29ea5025905e18Jeff Sharkey     * that matches the given account type.
5575f4af705cef8c914d5875983900e5cf5a5524b68Evan Millar     */
5583f0b7b87cd41b5a9cd631b6fcf29ea5025905e18Jeff Sharkey    protected static AuthenticatorDescription findAuthenticator(AuthenticatorDescription[] auths,
5593f0b7b87cd41b5a9cd631b6fcf29ea5025905e18Jeff Sharkey            String accountType) {
5603f0b7b87cd41b5a9cd631b6fcf29ea5025905e18Jeff Sharkey        for (AuthenticatorDescription auth : auths) {
5613f0b7b87cd41b5a9cd631b6fcf29ea5025905e18Jeff Sharkey            if (accountType.equals(auth.type)) {
5623f0b7b87cd41b5a9cd631b6fcf29ea5025905e18Jeff Sharkey                return auth;
563802b205ac677ffbde9aaf4fa3cfa9b94e8c98a44Jeff Sharkey            }
5642ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey        }
56532d64e590d4f9302d204d9a96960ca5739807500Nicklas Shamlo        return null;
5662ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey    }
567aad8848282f51d73ad308e9ad3ebcef592fa153fJeff Sharkey
5682ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey    /**
5696f74c0f3313cbb08ee8a8fbb79bfefc5b03fe215Makoto Onuki     * Return list of all known, contact writable {@link AccountWithDataSet}'s.
5702ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey     */
5716f667b55687bf9193323802e8f3234f0ab254388Dmitri Plotnikov    @Override
5726f74c0f3313cbb08ee8a8fbb79bfefc5b03fe215Makoto Onuki    public List<AccountWithDataSet> getAccounts(boolean contactWritableOnly) {
5736f74c0f3313cbb08ee8a8fbb79bfefc5b03fe215Makoto Onuki        ensureAccountsLoaded();
5746f74c0f3313cbb08ee8a8fbb79bfefc5b03fe215Makoto Onuki        return contactWritableOnly ? mContactWritableAccounts : mAccounts;
5756f74c0f3313cbb08ee8a8fbb79bfefc5b03fe215Makoto Onuki    }
5766f74c0f3313cbb08ee8a8fbb79bfefc5b03fe215Makoto Onuki
5776f74c0f3313cbb08ee8a8fbb79bfefc5b03fe215Makoto Onuki    /**
5786f74c0f3313cbb08ee8a8fbb79bfefc5b03fe215Makoto Onuki     * Return the list of all known, group writable {@link AccountWithDataSet}'s.
5796f74c0f3313cbb08ee8a8fbb79bfefc5b03fe215Makoto Onuki     */
5806f74c0f3313cbb08ee8a8fbb79bfefc5b03fe215Makoto Onuki    public List<AccountWithDataSet> getGroupWritableAccounts() {
58156c55827a83eef9ebafbb68beb3656c2506c1e0fDmitri Plotnikov        ensureAccountsLoaded();
5826f74c0f3313cbb08ee8a8fbb79bfefc5b03fe215Makoto Onuki        return mGroupWritableAccounts;
5832ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey    }
584d4c41d5c553745fb1126e01334319f05f0808eb3Jeff Sharkey
585ab066931efd4b6408b5f57026b421eb4a7934a39Jeff Sharkey    /**
586ab066931efd4b6408b5f57026b421eb4a7934a39Jeff Sharkey     * Find the best {@link DataKind} matching the requested
5872b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro     * {@link AccountType#accountType}, {@link AccountType#dataSet}, and {@link DataKind#mimeType}.
5882b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro     * If no direct match found, we try searching {@link FallbackAccountType}.
589ab066931efd4b6408b5f57026b421eb4a7934a39Jeff Sharkey     */
5906f667b55687bf9193323802e8f3234f0ab254388Dmitri Plotnikov    @Override
5912b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro    public DataKind getKindOrFallback(String accountType, String dataSet, String mimeType) {
59256c55827a83eef9ebafbb68beb3656c2506c1e0fDmitri Plotnikov        ensureAccountsLoaded();
593ab066931efd4b6408b5f57026b421eb4a7934a39Jeff Sharkey        DataKind kind = null;
594ab066931efd4b6408b5f57026b421eb4a7934a39Jeff Sharkey
59569f9e6f0cd9b5401da55f251e9bd98e69643d7dfDmitri Plotnikov        // Try finding account type and kind matching request
5962b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro        final AccountType type = mAccountTypesWithDataSets.get(
5976ad227f990265254864a04d3289292ca42330c71Makoto Onuki                AccountTypeWithDataSet.get(accountType, dataSet));
59869f9e6f0cd9b5401da55f251e9bd98e69643d7dfDmitri Plotnikov        if (type != null) {
59969f9e6f0cd9b5401da55f251e9bd98e69643d7dfDmitri Plotnikov            kind = type.getKindForMimetype(mimeType);
600ab066931efd4b6408b5f57026b421eb4a7934a39Jeff Sharkey        }
601ab066931efd4b6408b5f57026b421eb4a7934a39Jeff Sharkey
602ab066931efd4b6408b5f57026b421eb4a7934a39Jeff Sharkey        if (kind == null) {
603ab066931efd4b6408b5f57026b421eb4a7934a39Jeff Sharkey            // Nothing found, so try fallback as last resort
60469f9e6f0cd9b5401da55f251e9bd98e69643d7dfDmitri Plotnikov            kind = mFallbackAccountType.getKindForMimetype(mimeType);
605ab066931efd4b6408b5f57026b421eb4a7934a39Jeff Sharkey        }
606ab066931efd4b6408b5f57026b421eb4a7934a39Jeff Sharkey
607ab066931efd4b6408b5f57026b421eb4a7934a39Jeff Sharkey        if (kind == null) {
608ab066931efd4b6408b5f57026b421eb4a7934a39Jeff Sharkey            Log.w(TAG, "Unknown type=" + accountType + ", mime=" + mimeType);
609d4c41d5c553745fb1126e01334319f05f0808eb3Jeff Sharkey        }
610ab066931efd4b6408b5f57026b421eb4a7934a39Jeff Sharkey
611ab066931efd4b6408b5f57026b421eb4a7934a39Jeff Sharkey        return kind;
612d4c41d5c553745fb1126e01334319f05f0808eb3Jeff Sharkey    }
6135f4af705cef8c914d5875983900e5cf5a5524b68Evan Millar
6145f4af705cef8c914d5875983900e5cf5a5524b68Evan Millar    /**
6152b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro     * Return {@link AccountType} for the given account type and data set.
6165f4af705cef8c914d5875983900e5cf5a5524b68Evan Millar     */
6176f667b55687bf9193323802e8f3234f0ab254388Dmitri Plotnikov    @Override
6183ae114e72617f2faea281d82f7f4ee026d8c5674Makoto Onuki    public AccountType getAccountType(AccountTypeWithDataSet accountTypeWithDataSet) {
61956c55827a83eef9ebafbb68beb3656c2506c1e0fDmitri Plotnikov        ensureAccountsLoaded();
620b77be6d2ad06edfd8751f55043e4aa9fd9f36015Dmitri Plotnikov        synchronized (this) {
6213ae114e72617f2faea281d82f7f4ee026d8c5674Makoto Onuki            AccountType type = mAccountTypesWithDataSets.get(accountTypeWithDataSet);
622b77be6d2ad06edfd8751f55043e4aa9fd9f36015Dmitri Plotnikov            return type != null ? type : mFallbackAccountType;
6233f0b7b87cd41b5a9cd631b6fcf29ea5025905e18Jeff Sharkey        }
6245f4af705cef8c914d5875983900e5cf5a5524b68Evan Millar    }
6259aa9e846dec6c2958be6ce120e138e484fdba330Makoto Onuki
62608bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan    /**
62708bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan     * @return Unmodifiable map from {@link AccountTypeWithDataSet}s to {@link AccountType}s
62808bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan     * which support the "invite" feature and have one or more account. This is an unfiltered
62908bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan     * list. See {@link #getUsableInvitableAccountTypes()}.
63008bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan     */
63108bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan    private Map<AccountTypeWithDataSet, AccountType> getAllInvitableAccountTypes() {
63208bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan        ensureAccountsLoaded();
6339aa9e846dec6c2958be6ce120e138e484fdba330Makoto Onuki        return mInvitableAccountTypes;
6349aa9e846dec6c2958be6ce120e138e484fdba330Makoto Onuki    }
6359aa9e846dec6c2958be6ce120e138e484fdba330Makoto Onuki
63608bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan    @Override
63708bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan    public Map<AccountTypeWithDataSet, AccountType> getUsableInvitableAccountTypes() {
63808bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan        ensureAccountsLoaded();
63908bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan        // Since this method is not thread-safe, it's possible for multiple threads to encounter
64008bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan        // the situation where (1) the cache has not been initialized yet or
64108bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan        // (2) an async task to refresh the account type list in the cache has already been
64208bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan        // started. Hence we use {@link AtomicBoolean}s and return cached values immediately
64308bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan        // while we compute the actual result in the background. We use this approach instead of
64408bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan        // using "synchronized" because computing the account type list involves a DB read, and
64508bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan        // can potentially cause a deadlock situation if this method is called from code which
64608bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan        // holds the DB lock. The trade-off of potentially having an incorrect list of invitable
64708bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan        // account types for a short period of time seems more manageable than enforcing the
64808bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan        // context in which this method is called.
64908bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan
65008bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan        // Computing the list of usable invitable account types is done on the fly as requested.
65108bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan        // If this method has never been called before, then block until the list has been computed.
65208bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan        if (!mInvitablesCacheIsInitialized.get()) {
65308bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan            mInvitableAccountTypeCache.setCachedValue(findUsableInvitableAccountTypes(mContext));
65408bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan            mInvitablesCacheIsInitialized.set(true);
65508bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan        } else {
65608bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan            // Otherwise, there is a value in the cache. If the value has expired and
65708bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan            // an async task has not already been started by another thread, then kick off a new
65808bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan            // async task to compute the list.
65908bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan            if (mInvitableAccountTypeCache.isExpired() &&
66008bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan                    mInvitablesTaskIsRunning.compareAndSet(false, true)) {
66108bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan                new FindInvitablesTask().execute();
66208bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan            }
66308bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan        }
66408bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan
66508bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan        return mInvitableAccountTypeCache.getCachedValue();
66608bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan    }
66708bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan
6689aa9e846dec6c2958be6ce120e138e484fdba330Makoto Onuki    /**
6699aa9e846dec6c2958be6ce120e138e484fdba330Makoto Onuki     * Return all {@link AccountType}s with at least one account which supports "invite", i.e.
6709aa9e846dec6c2958be6ce120e138e484fdba330Makoto Onuki     * its {@link AccountType#getInviteContactActivityClassName()} is not empty.
6719aa9e846dec6c2958be6ce120e138e484fdba330Makoto Onuki     */
6729aa9e846dec6c2958be6ce120e138e484fdba330Makoto Onuki    @VisibleForTesting
67308bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan    static Map<AccountTypeWithDataSet, AccountType> findAllInvitableAccountTypes(Context context,
6742b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro            Collection<AccountWithDataSet> accounts,
6756ad227f990265254864a04d3289292ca42330c71Makoto Onuki            Map<AccountTypeWithDataSet, AccountType> accountTypesByTypeAndDataSet) {
6766ad227f990265254864a04d3289292ca42330c71Makoto Onuki        HashMap<AccountTypeWithDataSet, AccountType> result = Maps.newHashMap();
6772b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro        for (AccountWithDataSet account : accounts) {
6783ae114e72617f2faea281d82f7f4ee026d8c5674Makoto Onuki            AccountTypeWithDataSet accountTypeWithDataSet = account.getAccountTypeWithDataSet();
6796ad227f990265254864a04d3289292ca42330c71Makoto Onuki            AccountType type = accountTypesByTypeAndDataSet.get(accountTypeWithDataSet);
6809aa9e846dec6c2958be6ce120e138e484fdba330Makoto Onuki            if (type == null) continue; // just in case
6812b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro            if (result.containsKey(accountTypeWithDataSet)) continue;
6829aa9e846dec6c2958be6ce120e138e484fdba330Makoto Onuki
6839aa9e846dec6c2958be6ce120e138e484fdba330Makoto Onuki            if (Log.isLoggable(TAG, Log.DEBUG)) {
6842b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro                Log.d(TAG, "Type " + accountTypeWithDataSet
685d124d99339a6a3a7dc0bfa016263988120ea2a4cMakoto Onuki                        + " inviteClass=" + type.getInviteContactActivityClassName());
6869aa9e846dec6c2958be6ce120e138e484fdba330Makoto Onuki            }
6879aa9e846dec6c2958be6ce120e138e484fdba330Makoto Onuki            if (!TextUtils.isEmpty(type.getInviteContactActivityClassName())) {
6882b3f3c54d3beb017b2f59f19e9ce0ecc3e039dbcDave Santoro                result.put(accountTypeWithDataSet, type);
6899aa9e846dec6c2958be6ce120e138e484fdba330Makoto Onuki            }
6909aa9e846dec6c2958be6ce120e138e484fdba330Makoto Onuki        }
6919aa9e846dec6c2958be6ce120e138e484fdba330Makoto Onuki        return Collections.unmodifiableMap(result);
6929aa9e846dec6c2958be6ce120e138e484fdba330Makoto Onuki    }
693558669dab4109afebd19eade1f95a396215fb44dMakoto Onuki
69408bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan    /**
69508bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan     * Return all usable {@link AccountType}s that support the "invite" feature from the
69608bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan     * list of all potential invitable account types (retrieved from
69708bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan     * {@link #getAllInvitableAccountTypes}). A usable invitable account type means:
69808bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan     * (1) there is at least 1 raw contact in the database with that account type, and
69908bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan     * (2) the app contributing the account type is not disabled.
70008bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan     *
70108bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan     * Warning: Don't use on the UI thread because this can scan the database.
70208bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan     */
70308bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan    private Map<AccountTypeWithDataSet, AccountType> findUsableInvitableAccountTypes(
70408bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan            Context context) {
70508bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan        Map<AccountTypeWithDataSet, AccountType> allInvitables = getAllInvitableAccountTypes();
70608bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan        if (allInvitables.isEmpty()) {
70708bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan            return EMPTY_UNMODIFIABLE_ACCOUNT_TYPE_MAP;
70808bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan        }
70908bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan
71008bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan        final HashMap<AccountTypeWithDataSet, AccountType> result = Maps.newHashMap();
71108bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan        result.putAll(allInvitables);
71208bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan
71308bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan        final PackageManager packageManager = context.getPackageManager();
71408bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan        for (AccountTypeWithDataSet accountTypeWithDataSet : allInvitables.keySet()) {
71508bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan            AccountType accountType = allInvitables.get(accountTypeWithDataSet);
71608bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan
71708bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan            // Make sure that account types don't come from apps that are disabled.
71808bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan            Intent invitableIntent = ContactsUtils.getInvitableIntent(accountType,
71908bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan                    SAMPLE_CONTACT_URI);
72008bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan            if (invitableIntent == null) {
72108bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan                result.remove(accountTypeWithDataSet);
72208bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan                continue;
72308bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan            }
72408bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan            ResolveInfo resolveInfo = packageManager.resolveActivity(invitableIntent,
72508bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan                    PackageManager.MATCH_DEFAULT_ONLY);
72608bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan            if (resolveInfo == null) {
72708bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan                // If we can't find an activity to start for this intent, then there's no point in
72808bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan                // showing this option to the user.
72908bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan                result.remove(accountTypeWithDataSet);
73008bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan                continue;
73108bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan            }
73208bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan
73308bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan            // Make sure that there is at least 1 raw contact with this account type. This check
73408bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan            // is non-trivial and should not be done on the UI thread.
73508bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan            if (!accountTypeWithDataSet.hasData(context)) {
73608bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan                result.remove(accountTypeWithDataSet);
73708bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan            }
73808bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan        }
73908bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan
74008bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan        return Collections.unmodifiableMap(result);
74108bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan    }
74208bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan
743558669dab4109afebd19eade1f95a396215fb44dMakoto Onuki    @Override
744558669dab4109afebd19eade1f95a396215fb44dMakoto Onuki    public List<AccountType> getAccountTypes(boolean contactWritableOnly) {
74508bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan        ensureAccountsLoaded();
746558669dab4109afebd19eade1f95a396215fb44dMakoto Onuki        final List<AccountType> accountTypes = Lists.newArrayList();
747558669dab4109afebd19eade1f95a396215fb44dMakoto Onuki        synchronized (this) {
748558669dab4109afebd19eade1f95a396215fb44dMakoto Onuki            for (AccountType type : mAccountTypesWithDataSets.values()) {
749558669dab4109afebd19eade1f95a396215fb44dMakoto Onuki                if (!contactWritableOnly || type.areContactsWritable()) {
750558669dab4109afebd19eade1f95a396215fb44dMakoto Onuki                    accountTypes.add(type);
751558669dab4109afebd19eade1f95a396215fb44dMakoto Onuki                }
752558669dab4109afebd19eade1f95a396215fb44dMakoto Onuki            }
753558669dab4109afebd19eade1f95a396215fb44dMakoto Onuki        }
754558669dab4109afebd19eade1f95a396215fb44dMakoto Onuki        return accountTypes;
755558669dab4109afebd19eade1f95a396215fb44dMakoto Onuki    }
75608bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan
75708bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan    /**
75808bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan     * Background task to find all usable {@link AccountType}s that support the "invite" feature
75908bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan     * from the list of all potential invitable account types. Once the work is completed,
76008bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan     * the list of account types is stored in the {@link AccountTypeManager}'s
76108bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan     * {@link InvitableAccountTypeCache}.
76208bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan     */
76308bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan    private class FindInvitablesTask extends AsyncTask<Void, Void,
76408bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan            Map<AccountTypeWithDataSet, AccountType>> {
76508bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan
76608bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan        @Override
76708bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan        protected Map<AccountTypeWithDataSet, AccountType> doInBackground(Void... params) {
76808bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan            return findUsableInvitableAccountTypes(mContext);
76908bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan        }
77008bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan
77108bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan        @Override
77208bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan        protected void onPostExecute(Map<AccountTypeWithDataSet, AccountType> accountTypes) {
77308bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan            mInvitableAccountTypeCache.setCachedValue(accountTypes);
77408bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan            mInvitablesTaskIsRunning.set(false);
77508bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan        }
77608bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan    }
77708bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan
77808bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan    /**
77908bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan     * This cache holds a list of invitable {@link AccountTypeWithDataSet}s, in the form of a
78008bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan     * {@link Map<AccountTypeWithDataSet, AccountType>}. Note that the cached value is valid only
78108bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan     * for {@link #TIME_TO_LIVE} milliseconds.
78208bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan     */
78308bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan    private static final class InvitableAccountTypeCache {
78408bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan
78508bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan        /**
78608bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan         * The cached {@link #mInvitableAccountTypes} list expires after this number of milliseconds
78708bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan         * has elapsed.
78808bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan         */
78908bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan        private static final long TIME_TO_LIVE = 60000;
79008bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan
79108bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan        private Map<AccountTypeWithDataSet, AccountType> mInvitableAccountTypes;
79208bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan
79308bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan        private long mTimeLastSet;
79408bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan
79508bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan        /**
79608bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan         * Returns true if the data in this cache is stale and needs to be refreshed. Returns false
79708bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan         * otherwise.
79808bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan         */
79908bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan        public boolean isExpired() {
80008bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan             return SystemClock.elapsedRealtime() - mTimeLastSet > TIME_TO_LIVE;
80108bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan        }
80208bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan
80308bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan        /**
80408bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan         * Returns the cached value. Note that the caller is responsible for checking
80508bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan         * {@link #isExpired()} to ensure that the value is not stale.
80608bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan         */
80708bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan        public Map<AccountTypeWithDataSet, AccountType> getCachedValue() {
80808bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan            return mInvitableAccountTypes;
80908bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan        }
81008bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan
81108bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan        public void setCachedValue(Map<AccountTypeWithDataSet, AccountType> map) {
81208bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan            mInvitableAccountTypes = map;
81308bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan            mTimeLastSet = SystemClock.elapsedRealtime();
81408bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan        }
81508bcf715d5ea7f07ce18a282d9850ac70552ca9dKatherine Kuan    }
8162ae666ec99ae9318936a9326e5243987e4e1c586Jeff Sharkey}
817