1aa3e7a53861d62b8ca3858c83d6cb07ae4617cf9Paul Westbrook/**
2aa3e7a53861d62b8ca3858c83d6cb07ae4617cf9Paul Westbrook * Copyright (c) 2011, Google Inc.
3aa3e7a53861d62b8ca3858c83d6cb07ae4617cf9Paul Westbrook *
4aa3e7a53861d62b8ca3858c83d6cb07ae4617cf9Paul Westbrook * Licensed under the Apache License, Version 2.0 (the "License");
5aa3e7a53861d62b8ca3858c83d6cb07ae4617cf9Paul Westbrook * you may not use this file except in compliance with the License.
6aa3e7a53861d62b8ca3858c83d6cb07ae4617cf9Paul Westbrook * You may obtain a copy of the License at
7aa3e7a53861d62b8ca3858c83d6cb07ae4617cf9Paul Westbrook *
8aa3e7a53861d62b8ca3858c83d6cb07ae4617cf9Paul Westbrook *     http://www.apache.org/licenses/LICENSE-2.0
9aa3e7a53861d62b8ca3858c83d6cb07ae4617cf9Paul Westbrook *
10aa3e7a53861d62b8ca3858c83d6cb07ae4617cf9Paul Westbrook * Unless required by applicable law or agreed to in writing, software
11aa3e7a53861d62b8ca3858c83d6cb07ae4617cf9Paul Westbrook * distributed under the License is distributed on an "AS IS" BASIS,
12aa3e7a53861d62b8ca3858c83d6cb07ae4617cf9Paul Westbrook * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13aa3e7a53861d62b8ca3858c83d6cb07ae4617cf9Paul Westbrook * See the License for the specific language governing permissions and
14aa3e7a53861d62b8ca3858c83d6cb07ae4617cf9Paul Westbrook * limitations under the License.
15aa3e7a53861d62b8ca3858c83d6cb07ae4617cf9Paul Westbrook */
16aa3e7a53861d62b8ca3858c83d6cb07ae4617cf9Paul Westbrook
1730e2c24b056542f3b1b438aeb798305d1226d0c8Andy Huangpackage com.android.mail.providers;
18aa3e7a53861d62b8ca3858c83d6cb07ae4617cf9Paul Westbrook
19a6b671dd9f5ba358a05888b3ab3bf1c5cb5cf493Marc Blankimport android.app.Activity;
20aa3e7a53861d62b8ca3858c83d6cb07ae4617cf9Paul Westbrookimport android.content.ContentProvider;
21c95485c4c63e3667d9a9d3bde0f8e57da6e02f17Marc Blankimport android.content.ContentProviderClient;
22bfe41fe4632795af332cd003b105ece7d222a28bPaul Westbrookimport android.content.ContentResolver;
23aa3e7a53861d62b8ca3858c83d6cb07ae4617cf9Paul Westbrookimport android.content.ContentValues;
24db8f2f9ad5dcb1d52c399520129d89b1ca38f423Paul Westbrookimport android.content.Context;
25bb303e7a71ae6e54d58bb1d5d254dc58317c6382Paul Westbrookimport android.content.CursorLoader;
26b287833c2645e2502792821e54d7226cfad59955Marc Blankimport android.content.Intent;
27b287833c2645e2502792821e54d7226cfad59955Marc Blankimport android.content.Loader;
28bb303e7a71ae6e54d58bb1d5d254dc58317c6382Paul Westbrookimport android.content.Loader.OnLoadCompleteListener;
29db8f2f9ad5dcb1d52c399520129d89b1ca38f423Paul Westbrookimport android.content.SharedPreferences;
3045402ff1f55eb83fc9add99f83649a259b5c51a2Paul Westbrookimport android.content.res.Resources;
31aa3e7a53861d62b8ca3858c83d6cb07ae4617cf9Paul Westbrookimport android.database.Cursor;
32aa3e7a53861d62b8ca3858c83d6cb07ae4617cf9Paul Westbrookimport android.database.MatrixCursor;
33aa3e7a53861d62b8ca3858c83d6cb07ae4617cf9Paul Westbrookimport android.net.Uri;
342388c5d208acd4e4e658aaacbbc5f080ee8e9f7cPaul Westbrookimport android.os.Bundle;
35aa3e7a53861d62b8ca3858c83d6cb07ae4617cf9Paul Westbrook
3645402ff1f55eb83fc9add99f83649a259b5c51a2Paul Westbrookimport com.android.mail.R;
37b287833c2645e2502792821e54d7226cfad59955Marc Blankimport com.android.mail.providers.UIProvider.AccountCursorExtraKeys;
38b334c9035e9b7a38766bb66c29da2208525d1e11Paul Westbrookimport com.android.mail.utils.LogTag;
39b287833c2645e2502792821e54d7226cfad59955Marc Blankimport com.android.mail.utils.LogUtils;
40b287833c2645e2502792821e54d7226cfad59955Marc Blankimport com.android.mail.utils.MatrixCursorWithExtra;
41bc14a6f8da73a563c27dd99dc433f8c301dfe8edAndy Huangimport com.google.common.collect.ImmutableList;
429ace18a77e1c61700291f6116bb8162c8fce1fadMarc Blankimport com.google.common.collect.Maps;
435301c17703e01eccd31f91027373093791929997Paul Westbrookimport com.google.common.collect.Sets;
44aa3e7a53861d62b8ca3858c83d6cb07ae4617cf9Paul Westbrook
45bc14a6f8da73a563c27dd99dc433f8c301dfe8edAndy Huangimport org.json.JSONArray;
46bc14a6f8da73a563c27dd99dc433f8c301dfe8edAndy Huangimport org.json.JSONException;
47bc14a6f8da73a563c27dd99dc433f8c301dfe8edAndy Huangimport org.json.JSONObject;
48bc14a6f8da73a563c27dd99dc433f8c301dfe8edAndy Huang
49bc14a6f8da73a563c27dd99dc433f8c301dfe8edAndy Huangimport java.util.LinkedHashMap;
50c597cb55f02fe41c9fb180d1b66311e1a06523e1Paul Westbrookimport java.util.List;
51aa3e7a53861d62b8ca3858c83d6cb07ae4617cf9Paul Westbrookimport java.util.Map;
525301c17703e01eccd31f91027373093791929997Paul Westbrookimport java.util.Set;
5372a9d380a0effb191c4ef8e32899ea62ebf52f55Paul Westbrook
54aa3e7a53861d62b8ca3858c83d6cb07ae4617cf9Paul Westbrook
55aa3e7a53861d62b8ca3858c83d6cb07ae4617cf9Paul Westbrook/**
566006991395131d7d1db4f883ff8c7a87498b6eecVikram Aggarwal * The Mail App provider allows email providers to register "accounts" and the UI has a single
57aa3e7a53861d62b8ca3858c83d6cb07ae4617cf9Paul Westbrook * place to query for the list of accounts.
58aa3e7a53861d62b8ca3858c83d6cb07ae4617cf9Paul Westbrook *
59aa3e7a53861d62b8ca3858c83d6cb07ae4617cf9Paul Westbrook * During development this will allow new account types to be added, and allow them to be shown in
60aa3e7a53861d62b8ca3858c83d6cb07ae4617cf9Paul Westbrook * the application.  For example, the mock accounts can be enabled/disabled.
61aa3e7a53861d62b8ca3858c83d6cb07ae4617cf9Paul Westbrook * In the future, once other processes can add new accounts, this could allow other "mail"
62aa3e7a53861d62b8ca3858c83d6cb07ae4617cf9Paul Westbrook * applications have their content appear within the application
63aa3e7a53861d62b8ca3858c83d6cb07ae4617cf9Paul Westbrook */
64c2074c4442eacf5604dd3cecd06b363e8fc574dePaul Westbrookpublic abstract class MailAppProvider extends ContentProvider
65bb303e7a71ae6e54d58bb1d5d254dc58317c6382Paul Westbrook        implements OnLoadCompleteListener<Cursor>{
66aa3e7a53861d62b8ca3858c83d6cb07ae4617cf9Paul Westbrook
67c2074c4442eacf5604dd3cecd06b363e8fc574dePaul Westbrook    private static final String SHARED_PREFERENCES_NAME = "MailAppProvider";
68db8f2f9ad5dcb1d52c399520129d89b1ca38f423Paul Westbrook    private static final String ACCOUNT_LIST_KEY = "accountList";
690d647354209f4bf57cffa35f008e09853977b189Andy Huang    private static final String LAST_VIEWED_ACCOUNT_KEY = "lastViewedAccount";
7047d0e65536c55d7aad8c902aef31efb41fd05cf2Mindy Pereira    private static final String LAST_SENT_FROM_ACCOUNT_KEY = "lastSendFromAccount";
71db8f2f9ad5dcb1d52c399520129d89b1ca38f423Paul Westbrook
722388c5d208acd4e4e658aaacbbc5f080ee8e9f7cPaul Westbrook    /**
732388c5d208acd4e4e658aaacbbc5f080ee8e9f7cPaul Westbrook     * Extra used in the result from the activity launched by the intent specified
742388c5d208acd4e4e658aaacbbc5f080ee8e9f7cPaul Westbrook     * by {@link #getNoAccountsIntent} to return the list of accounts.  The data
752388c5d208acd4e4e658aaacbbc5f080ee8e9f7cPaul Westbrook     * specified by this extra key should be a ParcelableArray.
762388c5d208acd4e4e658aaacbbc5f080ee8e9f7cPaul Westbrook     */
772388c5d208acd4e4e658aaacbbc5f080ee8e9f7cPaul Westbrook    public static final String ADD_ACCOUNT_RESULT_ACCOUNTS_EXTRA = "addAccountResultAccounts";
782388c5d208acd4e4e658aaacbbc5f080ee8e9f7cPaul Westbrook
79b334c9035e9b7a38766bb66c29da2208525d1e11Paul Westbrook    private final static String LOG_TAG = LogTag.getLogTag();
80f464a541c5571836aaf5bca346c40c26e8e3c16dPaul Westbrook
81bc14a6f8da73a563c27dd99dc433f8c301dfe8edAndy Huang    private final LinkedHashMap<Uri, AccountCacheEntry> mAccountCache =
82bc14a6f8da73a563c27dd99dc433f8c301dfe8edAndy Huang            new LinkedHashMap<Uri, AccountCacheEntry>();
835301c17703e01eccd31f91027373093791929997Paul Westbrook
84bb303e7a71ae6e54d58bb1d5d254dc58317c6382Paul Westbrook    private final Map<Uri, CursorLoader> mCursorLoaderMap = Maps.newHashMap();
85bb303e7a71ae6e54d58bb1d5d254dc58317c6382Paul Westbrook
86bfe41fe4632795af332cd003b105ece7d222a28bPaul Westbrook    private ContentResolver mResolver;
8777177b171c483d485bdbff0178564394e8f57d0fPaul Westbrook    private static String sAuthority;
88c2074c4442eacf5604dd3cecd06b363e8fc574dePaul Westbrook    private static MailAppProvider sInstance;
8977177b171c483d485bdbff0178564394e8f57d0fPaul Westbrook
902388c5d208acd4e4e658aaacbbc5f080ee8e9f7cPaul Westbrook    private volatile boolean mAccountsFullyLoaded = false;
912388c5d208acd4e4e658aaacbbc5f080ee8e9f7cPaul Westbrook
92db8f2f9ad5dcb1d52c399520129d89b1ca38f423Paul Westbrook    private SharedPreferences mSharedPrefs;
93db8f2f9ad5dcb1d52c399520129d89b1ca38f423Paul Westbrook
9477177b171c483d485bdbff0178564394e8f57d0fPaul Westbrook    /**
956a62146d0af3bf33ff472e4fbfad64f6c582dd8eVikram Aggarwal     * Allows the implementing provider to specify the authority for this provider. Email and Gmail
966a62146d0af3bf33ff472e4fbfad64f6c582dd8eVikram Aggarwal     * must specify different authorities.
9777177b171c483d485bdbff0178564394e8f57d0fPaul Westbrook     */
9877177b171c483d485bdbff0178564394e8f57d0fPaul Westbrook    protected abstract String getAuthority();
9977177b171c483d485bdbff0178564394e8f57d0fPaul Westbrook
1002388c5d208acd4e4e658aaacbbc5f080ee8e9f7cPaul Westbrook    /**
1016a62146d0af3bf33ff472e4fbfad64f6c582dd8eVikram Aggarwal     * Authority for the suggestions provider. Email and Gmail must specify different authorities,
1026a62146d0af3bf33ff472e4fbfad64f6c582dd8eVikram Aggarwal     * much like the implementation of {@link #getAuthority()}.
1036a62146d0af3bf33ff472e4fbfad64f6c582dd8eVikram Aggarwal     * @return the suggestion authority associated with this provider.
1046a62146d0af3bf33ff472e4fbfad64f6c582dd8eVikram Aggarwal     */
1056a62146d0af3bf33ff472e4fbfad64f6c582dd8eVikram Aggarwal    public abstract String getSuggestionAuthority();
1066a62146d0af3bf33ff472e4fbfad64f6c582dd8eVikram Aggarwal
1076a62146d0af3bf33ff472e4fbfad64f6c582dd8eVikram Aggarwal    /**
1086a62146d0af3bf33ff472e4fbfad64f6c582dd8eVikram Aggarwal     * Allows the implementing provider to specify an intent that should be used in a call to
1092388c5d208acd4e4e658aaacbbc5f080ee8e9f7cPaul Westbrook     * {@link Context#startActivityForResult(android.content.Intent)} when the account provider
1102388c5d208acd4e4e658aaacbbc5f080ee8e9f7cPaul Westbrook     * doesn't return any accounts.
1112388c5d208acd4e4e658aaacbbc5f080ee8e9f7cPaul Westbrook     *
1122388c5d208acd4e4e658aaacbbc5f080ee8e9f7cPaul Westbrook     * The result from the {@link Activity} activity should include the list of accounts in
1132388c5d208acd4e4e658aaacbbc5f080ee8e9f7cPaul Westbrook     * the returned intent, in the
1142388c5d208acd4e4e658aaacbbc5f080ee8e9f7cPaul Westbrook
11570f130ee9be956e72bc0686e80c3fc4c92807451Vikram Aggarwal     * @return Intent or null, if the provider doesn't specify a behavior when no accounts are
1162388c5d208acd4e4e658aaacbbc5f080ee8e9f7cPaul Westbrook     * specified.
1172388c5d208acd4e4e658aaacbbc5f080ee8e9f7cPaul Westbrook     */
1182388c5d208acd4e4e658aaacbbc5f080ee8e9f7cPaul Westbrook    protected abstract Intent getNoAccountsIntent(Context context);
1192388c5d208acd4e4e658aaacbbc5f080ee8e9f7cPaul Westbrook
1202388c5d208acd4e4e658aaacbbc5f080ee8e9f7cPaul Westbrook    /**
121828f46144e79c7bb0856f43dc237e4faef3623deVikram Aggarwal     * The cursor returned from a call to {@link android.content.ContentResolver#query()} with this
1222388c5d208acd4e4e658aaacbbc5f080ee8e9f7cPaul Westbrook     * uri will return a cursor that with columns that are a subset of the columns specified
1232388c5d208acd4e4e658aaacbbc5f080ee8e9f7cPaul Westbrook     * in {@link UIProvider.ConversationColumns}
1242388c5d208acd4e4e658aaacbbc5f080ee8e9f7cPaul Westbrook     * The cursor returned by this query can return a {@link android.os.Bundle}
1252388c5d208acd4e4e658aaacbbc5f080ee8e9f7cPaul Westbrook     * from a call to {@link android.database.Cursor#getExtras()}.  This Bundle may have
1262388c5d208acd4e4e658aaacbbc5f080ee8e9f7cPaul Westbrook     * values with keys listed in {@link AccountCursorExtraKeys}
1272388c5d208acd4e4e658aaacbbc5f080ee8e9f7cPaul Westbrook     */
128aa3e7a53861d62b8ca3858c83d6cb07ae4617cf9Paul Westbrook    public static Uri getAccountsUri() {
12977177b171c483d485bdbff0178564394e8f57d0fPaul Westbrook        return Uri.parse("content://" + sAuthority + "/");
130aa3e7a53861d62b8ca3858c83d6cb07ae4617cf9Paul Westbrook    }
131aa3e7a53861d62b8ca3858c83d6cb07ae4617cf9Paul Westbrook
132c2074c4442eacf5604dd3cecd06b363e8fc574dePaul Westbrook    public static MailAppProvider getInstance() {
133db8f2f9ad5dcb1d52c399520129d89b1ca38f423Paul Westbrook        return sInstance;
134db8f2f9ad5dcb1d52c399520129d89b1ca38f423Paul Westbrook    }
135db8f2f9ad5dcb1d52c399520129d89b1ca38f423Paul Westbrook
1366a62146d0af3bf33ff472e4fbfad64f6c582dd8eVikram Aggarwal    /** Default constructor */
1376006991395131d7d1db4f883ff8c7a87498b6eecVikram Aggarwal    protected MailAppProvider() {
1386a62146d0af3bf33ff472e4fbfad64f6c582dd8eVikram Aggarwal    }
1396a62146d0af3bf33ff472e4fbfad64f6c582dd8eVikram Aggarwal
140aa3e7a53861d62b8ca3858c83d6cb07ae4617cf9Paul Westbrook    @Override
141aa3e7a53861d62b8ca3858c83d6cb07ae4617cf9Paul Westbrook    public boolean onCreate() {
14277177b171c483d485bdbff0178564394e8f57d0fPaul Westbrook        sAuthority = getAuthority();
14345402ff1f55eb83fc9add99f83649a259b5c51a2Paul Westbrook        sInstance = this;
144bfe41fe4632795af332cd003b105ece7d222a28bPaul Westbrook        mResolver = getContext().getContentResolver();
145bb303e7a71ae6e54d58bb1d5d254dc58317c6382Paul Westbrook
146db8f2f9ad5dcb1d52c399520129d89b1ca38f423Paul Westbrook        // Load the previously saved account list
147db8f2f9ad5dcb1d52c399520129d89b1ca38f423Paul Westbrook        loadCachedAccountList();
148db8f2f9ad5dcb1d52c399520129d89b1ca38f423Paul Westbrook
14945402ff1f55eb83fc9add99f83649a259b5c51a2Paul Westbrook        final Resources res = getContext().getResources();
15045402ff1f55eb83fc9add99f83649a259b5c51a2Paul Westbrook        // Load the uris for the account list
15145402ff1f55eb83fc9add99f83649a259b5c51a2Paul Westbrook        final String[] accountQueryUris = res.getStringArray(R.array.account_providers);
152f9dbd206d1dd37e8ff2e3e72759710f5e6fb45aePaul Westbrook
15345402ff1f55eb83fc9add99f83649a259b5c51a2Paul Westbrook        for (String accountQueryUri : accountQueryUris) {
15445402ff1f55eb83fc9add99f83649a259b5c51a2Paul Westbrook            final Uri uri = Uri.parse(accountQueryUri);
15545402ff1f55eb83fc9add99f83649a259b5c51a2Paul Westbrook            addAccountsForUriAsync(uri);
156f9dbd206d1dd37e8ff2e3e72759710f5e6fb45aePaul Westbrook        }
157f9dbd206d1dd37e8ff2e3e72759710f5e6fb45aePaul Westbrook
158aa3e7a53861d62b8ca3858c83d6cb07ae4617cf9Paul Westbrook        return true;
159aa3e7a53861d62b8ca3858c83d6cb07ae4617cf9Paul Westbrook    }
160aa3e7a53861d62b8ca3858c83d6cb07ae4617cf9Paul Westbrook
161aa3e7a53861d62b8ca3858c83d6cb07ae4617cf9Paul Westbrook    @Override
162bfe41fe4632795af332cd003b105ece7d222a28bPaul Westbrook    public void shutdown() {
16345402ff1f55eb83fc9add99f83649a259b5c51a2Paul Westbrook        sInstance = null;
164bb303e7a71ae6e54d58bb1d5d254dc58317c6382Paul Westbrook
165bb303e7a71ae6e54d58bb1d5d254dc58317c6382Paul Westbrook        for (CursorLoader loader : mCursorLoaderMap.values()) {
166bb303e7a71ae6e54d58bb1d5d254dc58317c6382Paul Westbrook            loader.stopLoading();
167bb303e7a71ae6e54d58bb1d5d254dc58317c6382Paul Westbrook        }
168bb303e7a71ae6e54d58bb1d5d254dc58317c6382Paul Westbrook        mCursorLoaderMap.clear();
169bfe41fe4632795af332cd003b105ece7d222a28bPaul Westbrook    }
170bfe41fe4632795af332cd003b105ece7d222a28bPaul Westbrook
171bfe41fe4632795af332cd003b105ece7d222a28bPaul Westbrook    @Override
172aa3e7a53861d62b8ca3858c83d6cb07ae4617cf9Paul Westbrook    public Cursor query(Uri url, String[] projection, String selection, String[] selectionArgs,
173aa3e7a53861d62b8ca3858c83d6cb07ae4617cf9Paul Westbrook            String sortOrder) {
174aa3e7a53861d62b8ca3858c83d6cb07ae4617cf9Paul Westbrook        // This content provider currently only supports one query (to return the list of accounts).
175aa3e7a53861d62b8ca3858c83d6cb07ae4617cf9Paul Westbrook        // No reason to check the uri.  Currently only checking the projections
176aa3e7a53861d62b8ca3858c83d6cb07ae4617cf9Paul Westbrook
177aa3e7a53861d62b8ca3858c83d6cb07ae4617cf9Paul Westbrook        // Validates and returns the projection that should be used.
178583a114f7b72ef1b2ebe8af53870ccb089afe530Paul Westbrook        final String[] resultProjection = UIProviderValidator.validateAccountProjection(projection);
1792388c5d208acd4e4e658aaacbbc5f080ee8e9f7cPaul Westbrook        final Bundle extras = new Bundle();
1802388c5d208acd4e4e658aaacbbc5f080ee8e9f7cPaul Westbrook        extras.putInt(AccountCursorExtraKeys.ACCOUNTS_LOADED, mAccountsFullyLoaded ? 1 : 0);
181aa3e7a53861d62b8ca3858c83d6cb07ae4617cf9Paul Westbrook
182bb303e7a71ae6e54d58bb1d5d254dc58317c6382Paul Westbrook        // Make a copy of the account cache
183bc14a6f8da73a563c27dd99dc433f8c301dfe8edAndy Huang        final List<AccountCacheEntry> accountList;
184db8f2f9ad5dcb1d52c399520129d89b1ca38f423Paul Westbrook        synchronized (mAccountCache) {
185bc14a6f8da73a563c27dd99dc433f8c301dfe8edAndy Huang            accountList = ImmutableList.copyOf(mAccountCache.values());
186bb303e7a71ae6e54d58bb1d5d254dc58317c6382Paul Westbrook        }
1872388c5d208acd4e4e658aaacbbc5f080ee8e9f7cPaul Westbrook
1882388c5d208acd4e4e658aaacbbc5f080ee8e9f7cPaul Westbrook        final MatrixCursor cursor =
1892388c5d208acd4e4e658aaacbbc5f080ee8e9f7cPaul Westbrook                new MatrixCursorWithExtra(resultProjection, accountList.size(), extras);
1902388c5d208acd4e4e658aaacbbc5f080ee8e9f7cPaul Westbrook
19172a9d380a0effb191c4ef8e32899ea62ebf52f55Paul Westbrook        for (AccountCacheEntry accountEntry : accountList) {
19272a9d380a0effb191c4ef8e32899ea62ebf52f55Paul Westbrook            final Account account = accountEntry.mAccount;
193aa3e7a53861d62b8ca3858c83d6cb07ae4617cf9Paul Westbrook            final MatrixCursor.RowBuilder builder = cursor.newRow();
194a4836711cf7909575bfb25720232c22c201efd06Tony Mantler            final Map<String, Object> accountValues = account.getValueMap();
195aa3e7a53861d62b8ca3858c83d6cb07ae4617cf9Paul Westbrook
196a9a05d566650f31f09191eda0ba61af843bd4cd8Vikram Aggarwal            for (final String columnName : resultProjection) {
197d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                if (accountValues.containsKey(columnName)) {
198d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                    builder.add(accountValues.get(columnName));
199d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                } else {
200d5edd2d02649dffb40065fdb6a16acf91552b800Scott Kennedy                    throw new IllegalStateException("Unexpected column: " + columnName);
201aa3e7a53861d62b8ca3858c83d6cb07ae4617cf9Paul Westbrook                }
202aa3e7a53861d62b8ca3858c83d6cb07ae4617cf9Paul Westbrook            }
203aa3e7a53861d62b8ca3858c83d6cb07ae4617cf9Paul Westbrook        }
204bfe41fe4632795af332cd003b105ece7d222a28bPaul Westbrook
205bfe41fe4632795af332cd003b105ece7d222a28bPaul Westbrook        cursor.setNotificationUri(mResolver, getAccountsUri());
206aa3e7a53861d62b8ca3858c83d6cb07ae4617cf9Paul Westbrook        return cursor;
207aa3e7a53861d62b8ca3858c83d6cb07ae4617cf9Paul Westbrook    }
208aa3e7a53861d62b8ca3858c83d6cb07ae4617cf9Paul Westbrook
209aa3e7a53861d62b8ca3858c83d6cb07ae4617cf9Paul Westbrook    @Override
210aa3e7a53861d62b8ca3858c83d6cb07ae4617cf9Paul Westbrook    public Uri insert(Uri url, ContentValues values) {
211aa3e7a53861d62b8ca3858c83d6cb07ae4617cf9Paul Westbrook        return url;
212aa3e7a53861d62b8ca3858c83d6cb07ae4617cf9Paul Westbrook    }
213aa3e7a53861d62b8ca3858c83d6cb07ae4617cf9Paul Westbrook
214aa3e7a53861d62b8ca3858c83d6cb07ae4617cf9Paul Westbrook    @Override
215aa3e7a53861d62b8ca3858c83d6cb07ae4617cf9Paul Westbrook    public int update(Uri url, ContentValues values, String selection,
216aa3e7a53861d62b8ca3858c83d6cb07ae4617cf9Paul Westbrook            String[] selectionArgs) {
217aa3e7a53861d62b8ca3858c83d6cb07ae4617cf9Paul Westbrook        return 0;
218aa3e7a53861d62b8ca3858c83d6cb07ae4617cf9Paul Westbrook    }
219aa3e7a53861d62b8ca3858c83d6cb07ae4617cf9Paul Westbrook
220aa3e7a53861d62b8ca3858c83d6cb07ae4617cf9Paul Westbrook    @Override
221aa3e7a53861d62b8ca3858c83d6cb07ae4617cf9Paul Westbrook    public int delete(Uri url, String selection, String[] selectionArgs) {
222aa3e7a53861d62b8ca3858c83d6cb07ae4617cf9Paul Westbrook        return 0;
223aa3e7a53861d62b8ca3858c83d6cb07ae4617cf9Paul Westbrook    }
224aa3e7a53861d62b8ca3858c83d6cb07ae4617cf9Paul Westbrook
225aa3e7a53861d62b8ca3858c83d6cb07ae4617cf9Paul Westbrook    @Override
226aa3e7a53861d62b8ca3858c83d6cb07ae4617cf9Paul Westbrook    public String getType(Uri uri) {
227aa3e7a53861d62b8ca3858c83d6cb07ae4617cf9Paul Westbrook        return null;
228aa3e7a53861d62b8ca3858c83d6cb07ae4617cf9Paul Westbrook    }
229aa3e7a53861d62b8ca3858c83d6cb07ae4617cf9Paul Westbrook
230f464a541c5571836aaf5bca346c40c26e8e3c16dPaul Westbrook    /**
23155124f6fbc230042714d6eec119f0573e2878246Mindy Pereira     * Asynchronously adds all of the accounts that are specified by the result set returned by
232f464a541c5571836aaf5bca346c40c26e8e3c16dPaul Westbrook     * {@link ContentProvider#query()} for the specified uri.  The content provider handling the
233f464a541c5571836aaf5bca346c40c26e8e3c16dPaul Westbrook     * query needs to handle the {@link UIProvider.ACCOUNTS_PROJECTION}
234bb303e7a71ae6e54d58bb1d5d254dc58317c6382Paul Westbrook     * Any changes to the underlying provider will automatically be reflected.
235f464a541c5571836aaf5bca346c40c26e8e3c16dPaul Westbrook     * @param accountsQueryUri
236f464a541c5571836aaf5bca346c40c26e8e3c16dPaul Westbrook     */
23745402ff1f55eb83fc9add99f83649a259b5c51a2Paul Westbrook    private void addAccountsForUriAsync(Uri accountsQueryUri) {
23845402ff1f55eb83fc9add99f83649a259b5c51a2Paul Westbrook        startAccountsLoader(accountsQueryUri);
239bb303e7a71ae6e54d58bb1d5d254dc58317c6382Paul Westbrook    }
240f464a541c5571836aaf5bca346c40c26e8e3c16dPaul Westbrook
2412388c5d208acd4e4e658aaacbbc5f080ee8e9f7cPaul Westbrook    /**
2422388c5d208acd4e4e658aaacbbc5f080ee8e9f7cPaul Westbrook     * Returns the intent that should be used in a call to
2432388c5d208acd4e4e658aaacbbc5f080ee8e9f7cPaul Westbrook     * {@link Context#startActivity(android.content.Intent)} when the account provider doesn't
2442388c5d208acd4e4e658aaacbbc5f080ee8e9f7cPaul Westbrook     * return any accounts
2452388c5d208acd4e4e658aaacbbc5f080ee8e9f7cPaul Westbrook     * @return Intent or null, if the provider doesn't specify a behavior when no acccounts are
2462388c5d208acd4e4e658aaacbbc5f080ee8e9f7cPaul Westbrook     * specified.
2472388c5d208acd4e4e658aaacbbc5f080ee8e9f7cPaul Westbrook     */
2482388c5d208acd4e4e658aaacbbc5f080ee8e9f7cPaul Westbrook    public static Intent getNoAccountIntent(Context context) {
2492388c5d208acd4e4e658aaacbbc5f080ee8e9f7cPaul Westbrook        return getInstance().getNoAccountsIntent(context);
2502388c5d208acd4e4e658aaacbbc5f080ee8e9f7cPaul Westbrook    }
2512388c5d208acd4e4e658aaacbbc5f080ee8e9f7cPaul Westbrook
252bb303e7a71ae6e54d58bb1d5d254dc58317c6382Paul Westbrook    private synchronized void startAccountsLoader(Uri accountsQueryUri) {
253bb303e7a71ae6e54d58bb1d5d254dc58317c6382Paul Westbrook        final CursorLoader accountsCursorLoader = new CursorLoader(getContext(), accountsQueryUri,
254bb303e7a71ae6e54d58bb1d5d254dc58317c6382Paul Westbrook                UIProvider.ACCOUNTS_PROJECTION, null, null, null);
2555301c17703e01eccd31f91027373093791929997Paul Westbrook
256bb303e7a71ae6e54d58bb1d5d254dc58317c6382Paul Westbrook        // Listen for the results
257bb303e7a71ae6e54d58bb1d5d254dc58317c6382Paul Westbrook        accountsCursorLoader.registerListener(accountsQueryUri.hashCode(), this);
258bb303e7a71ae6e54d58bb1d5d254dc58317c6382Paul Westbrook        accountsCursorLoader.startLoading();
2595301c17703e01eccd31f91027373093791929997Paul Westbrook
260bb303e7a71ae6e54d58bb1d5d254dc58317c6382Paul Westbrook        // If there is a previous loader for the given uri, stop it
261bb303e7a71ae6e54d58bb1d5d254dc58317c6382Paul Westbrook        final CursorLoader oldLoader = mCursorLoaderMap.get(accountsQueryUri);
262bb303e7a71ae6e54d58bb1d5d254dc58317c6382Paul Westbrook        if (oldLoader != null) {
263bb303e7a71ae6e54d58bb1d5d254dc58317c6382Paul Westbrook            oldLoader.stopLoading();
2645301c17703e01eccd31f91027373093791929997Paul Westbrook        }
265bb303e7a71ae6e54d58bb1d5d254dc58317c6382Paul Westbrook        mCursorLoaderMap.put(accountsQueryUri, accountsCursorLoader);
266f464a541c5571836aaf5bca346c40c26e8e3c16dPaul Westbrook    }
267aa3e7a53861d62b8ca3858c83d6cb07ae4617cf9Paul Westbrook
268e95c7ded028607899d9596685c7cec21c1274d8ePaul Westbrook    private void addAccountImpl(Account account, Uri accountsQueryUri, boolean notify) {
269bc14a6f8da73a563c27dd99dc433f8c301dfe8edAndy Huang        addAccountImpl(account.uri, new AccountCacheEntry(account, accountsQueryUri));
270c597cb55f02fe41c9fb180d1b66311e1a06523e1Paul Westbrook
271bfe41fe4632795af332cd003b105ece7d222a28bPaul Westbrook        // Explicitly calling this out of the synchronized block in case any of the observers get
272bfe41fe4632795af332cd003b105ece7d222a28bPaul Westbrook        // called synchronously.
273e95c7ded028607899d9596685c7cec21c1274d8ePaul Westbrook        if (notify) {
274e95c7ded028607899d9596685c7cec21c1274d8ePaul Westbrook            broadcastAccountChange();
275e95c7ded028607899d9596685c7cec21c1274d8ePaul Westbrook        }
276aa3e7a53861d62b8ca3858c83d6cb07ae4617cf9Paul Westbrook    }
277aa3e7a53861d62b8ca3858c83d6cb07ae4617cf9Paul Westbrook
278bc14a6f8da73a563c27dd99dc433f8c301dfe8edAndy Huang    private void addAccountImpl(Uri key, AccountCacheEntry accountEntry) {
279db8f2f9ad5dcb1d52c399520129d89b1ca38f423Paul Westbrook        synchronized (mAccountCache) {
280bc14a6f8da73a563c27dd99dc433f8c301dfe8edAndy Huang            LogUtils.v(LOG_TAG, "adding account %s", accountEntry.mAccount);
281bc14a6f8da73a563c27dd99dc433f8c301dfe8edAndy Huang            // LinkedHashMap will not change the iteration order when re-inserting a key
282bc14a6f8da73a563c27dd99dc433f8c301dfe8edAndy Huang            mAccountCache.put(key, accountEntry);
283e95c7ded028607899d9596685c7cec21c1274d8ePaul Westbrook        }
284bfe41fe4632795af332cd003b105ece7d222a28bPaul Westbrook    }
285bfe41fe4632795af332cd003b105ece7d222a28bPaul Westbrook
286bfe41fe4632795af332cd003b105ece7d222a28bPaul Westbrook    private static void broadcastAccountChange() {
287c2074c4442eacf5604dd3cecd06b363e8fc574dePaul Westbrook        final MailAppProvider provider = sInstance;
288bfe41fe4632795af332cd003b105ece7d222a28bPaul Westbrook
289bfe41fe4632795af332cd003b105ece7d222a28bPaul Westbrook        if (provider != null) {
290bfe41fe4632795af332cd003b105ece7d222a28bPaul Westbrook            provider.mResolver.notifyChange(getAccountsUri(), null);
291bfe41fe4632795af332cd003b105ece7d222a28bPaul Westbrook        }
292aa3e7a53861d62b8ca3858c83d6cb07ae4617cf9Paul Westbrook    }
293aa3e7a53861d62b8ca3858c83d6cb07ae4617cf9Paul Westbrook
2940d647354209f4bf57cffa35f008e09853977b189Andy Huang    /**
2950d647354209f4bf57cffa35f008e09853977b189Andy Huang     * Returns the {@link Account#uri} (in String form) of the last viewed account.
2960d647354209f4bf57cffa35f008e09853977b189Andy Huang     */
2970d647354209f4bf57cffa35f008e09853977b189Andy Huang    public String getLastViewedAccount() {
2980d647354209f4bf57cffa35f008e09853977b189Andy Huang        return getPreferences().getString(LAST_VIEWED_ACCOUNT_KEY, null);
2990d647354209f4bf57cffa35f008e09853977b189Andy Huang    }
3000d647354209f4bf57cffa35f008e09853977b189Andy Huang
3010d647354209f4bf57cffa35f008e09853977b189Andy Huang    /**
3020d647354209f4bf57cffa35f008e09853977b189Andy Huang     * Persists the {@link Account#uri} (in String form) of the last viewed account.
3030d647354209f4bf57cffa35f008e09853977b189Andy Huang     */
3040d647354209f4bf57cffa35f008e09853977b189Andy Huang    public void setLastViewedAccount(String accountUriStr) {
3050d647354209f4bf57cffa35f008e09853977b189Andy Huang        final SharedPreferences.Editor editor = getPreferences().edit();
3060d647354209f4bf57cffa35f008e09853977b189Andy Huang        editor.putString(LAST_VIEWED_ACCOUNT_KEY, accountUriStr);
3070d647354209f4bf57cffa35f008e09853977b189Andy Huang        editor.apply();
3080d647354209f4bf57cffa35f008e09853977b189Andy Huang    }
3090d647354209f4bf57cffa35f008e09853977b189Andy Huang
31047d0e65536c55d7aad8c902aef31efb41fd05cf2Mindy Pereira    /**
31147d0e65536c55d7aad8c902aef31efb41fd05cf2Mindy Pereira     * Returns the {@link Account#uri} (in String form) of the last account the
31247d0e65536c55d7aad8c902aef31efb41fd05cf2Mindy Pereira     * user compose a message from.
31347d0e65536c55d7aad8c902aef31efb41fd05cf2Mindy Pereira     */
31447d0e65536c55d7aad8c902aef31efb41fd05cf2Mindy Pereira    public String getLastSentFromAccount() {
31547d0e65536c55d7aad8c902aef31efb41fd05cf2Mindy Pereira        return getPreferences().getString(LAST_SENT_FROM_ACCOUNT_KEY, null);
31647d0e65536c55d7aad8c902aef31efb41fd05cf2Mindy Pereira    }
31747d0e65536c55d7aad8c902aef31efb41fd05cf2Mindy Pereira
31847d0e65536c55d7aad8c902aef31efb41fd05cf2Mindy Pereira    /**
31947d0e65536c55d7aad8c902aef31efb41fd05cf2Mindy Pereira     * Persists the {@link Account#uri} (in String form) of the last account the
32047d0e65536c55d7aad8c902aef31efb41fd05cf2Mindy Pereira     * user compose a message from.
32147d0e65536c55d7aad8c902aef31efb41fd05cf2Mindy Pereira     */
32247d0e65536c55d7aad8c902aef31efb41fd05cf2Mindy Pereira    public void setLastSentFromAccount(String accountUriStr) {
32347d0e65536c55d7aad8c902aef31efb41fd05cf2Mindy Pereira        final SharedPreferences.Editor editor = getPreferences().edit();
32447d0e65536c55d7aad8c902aef31efb41fd05cf2Mindy Pereira        editor.putString(LAST_SENT_FROM_ACCOUNT_KEY, accountUriStr);
32547d0e65536c55d7aad8c902aef31efb41fd05cf2Mindy Pereira        editor.apply();
32647d0e65536c55d7aad8c902aef31efb41fd05cf2Mindy Pereira    }
32747d0e65536c55d7aad8c902aef31efb41fd05cf2Mindy Pereira
328db8f2f9ad5dcb1d52c399520129d89b1ca38f423Paul Westbrook    private void loadCachedAccountList() {
329bc14a6f8da73a563c27dd99dc433f8c301dfe8edAndy Huang        JSONArray accounts = null;
330bc14a6f8da73a563c27dd99dc433f8c301dfe8edAndy Huang        try {
331bc14a6f8da73a563c27dd99dc433f8c301dfe8edAndy Huang            final String accountsJson = getPreferences().getString(ACCOUNT_LIST_KEY, null);
332bc14a6f8da73a563c27dd99dc433f8c301dfe8edAndy Huang            if (accountsJson != null) {
333bc14a6f8da73a563c27dd99dc433f8c301dfe8edAndy Huang                accounts = new JSONArray(accountsJson);
334db8f2f9ad5dcb1d52c399520129d89b1ca38f423Paul Westbrook            }
335bc14a6f8da73a563c27dd99dc433f8c301dfe8edAndy Huang        } catch (Exception e) {
336bc14a6f8da73a563c27dd99dc433f8c301dfe8edAndy Huang            LogUtils.e(LOG_TAG, e, "ignoring unparsable accounts cache");
337bc14a6f8da73a563c27dd99dc433f8c301dfe8edAndy Huang        }
338bc14a6f8da73a563c27dd99dc433f8c301dfe8edAndy Huang
339bc14a6f8da73a563c27dd99dc433f8c301dfe8edAndy Huang        if (accounts == null) {
340bc14a6f8da73a563c27dd99dc433f8c301dfe8edAndy Huang            return;
341bc14a6f8da73a563c27dd99dc433f8c301dfe8edAndy Huang        }
342bc14a6f8da73a563c27dd99dc433f8c301dfe8edAndy Huang
343bc14a6f8da73a563c27dd99dc433f8c301dfe8edAndy Huang        for (int i = 0; i < accounts.length(); i++) {
344bc14a6f8da73a563c27dd99dc433f8c301dfe8edAndy Huang            try {
345bc14a6f8da73a563c27dd99dc433f8c301dfe8edAndy Huang                final AccountCacheEntry accountEntry = new AccountCacheEntry(
346bc14a6f8da73a563c27dd99dc433f8c301dfe8edAndy Huang                        accounts.getJSONObject(i));
347bc14a6f8da73a563c27dd99dc433f8c301dfe8edAndy Huang
348bc14a6f8da73a563c27dd99dc433f8c301dfe8edAndy Huang                if (accountEntry.mAccount.settings == null) {
349d0705dcd413e763d4ba1ee1acc3c67afe05a08d4Andy Huang                    LogUtils.e(LOG_TAG, "Dropping account that doesn't specify settings");
350bc14a6f8da73a563c27dd99dc433f8c301dfe8edAndy Huang                    continue;
351d0705dcd413e763d4ba1ee1acc3c67afe05a08d4Andy Huang                }
352bc14a6f8da73a563c27dd99dc433f8c301dfe8edAndy Huang
353bc14a6f8da73a563c27dd99dc433f8c301dfe8edAndy Huang                Account account = accountEntry.mAccount;
354bc14a6f8da73a563c27dd99dc433f8c301dfe8edAndy Huang                ContentProviderClient client =
355bc14a6f8da73a563c27dd99dc433f8c301dfe8edAndy Huang                        mResolver.acquireContentProviderClient(account.uri);
356bc14a6f8da73a563c27dd99dc433f8c301dfe8edAndy Huang                if (client != null) {
357bc14a6f8da73a563c27dd99dc433f8c301dfe8edAndy Huang                    client.release();
358bc14a6f8da73a563c27dd99dc433f8c301dfe8edAndy Huang                    addAccountImpl(account.uri, accountEntry);
359bc14a6f8da73a563c27dd99dc433f8c301dfe8edAndy Huang                } else {
360bc14a6f8da73a563c27dd99dc433f8c301dfe8edAndy Huang                    LogUtils.e(LOG_TAG, "Dropping account without provider: %s",
361bc14a6f8da73a563c27dd99dc433f8c301dfe8edAndy Huang                            account.name);
362bc14a6f8da73a563c27dd99dc433f8c301dfe8edAndy Huang                }
363bc14a6f8da73a563c27dd99dc433f8c301dfe8edAndy Huang
364bc14a6f8da73a563c27dd99dc433f8c301dfe8edAndy Huang            } catch (Exception e) {
365bc14a6f8da73a563c27dd99dc433f8c301dfe8edAndy Huang                // Unable to create account object, skip to next
366bc14a6f8da73a563c27dd99dc433f8c301dfe8edAndy Huang                LogUtils.e(LOG_TAG, e,
367bc14a6f8da73a563c27dd99dc433f8c301dfe8edAndy Huang                        "Unable to create account object from serialized form");
368d0705dcd413e763d4ba1ee1acc3c67afe05a08d4Andy Huang            }
369db8f2f9ad5dcb1d52c399520129d89b1ca38f423Paul Westbrook        }
370bc14a6f8da73a563c27dd99dc433f8c301dfe8edAndy Huang        broadcastAccountChange();
371db8f2f9ad5dcb1d52c399520129d89b1ca38f423Paul Westbrook    }
372db8f2f9ad5dcb1d52c399520129d89b1ca38f423Paul Westbrook
373db8f2f9ad5dcb1d52c399520129d89b1ca38f423Paul Westbrook    private void cacheAccountList() {
374bc14a6f8da73a563c27dd99dc433f8c301dfe8edAndy Huang        final List<AccountCacheEntry> accountList;
375bc14a6f8da73a563c27dd99dc433f8c301dfe8edAndy Huang
376db8f2f9ad5dcb1d52c399520129d89b1ca38f423Paul Westbrook        synchronized (mAccountCache) {
377bc14a6f8da73a563c27dd99dc433f8c301dfe8edAndy Huang            accountList = ImmutableList.copyOf(mAccountCache.values());
378db8f2f9ad5dcb1d52c399520129d89b1ca38f423Paul Westbrook        }
379db8f2f9ad5dcb1d52c399520129d89b1ca38f423Paul Westbrook
380bc14a6f8da73a563c27dd99dc433f8c301dfe8edAndy Huang        final JSONArray arr = new JSONArray();
38172a9d380a0effb191c4ef8e32899ea62ebf52f55Paul Westbrook        for (AccountCacheEntry accountEntry : accountList) {
382bc14a6f8da73a563c27dd99dc433f8c301dfe8edAndy Huang            arr.put(accountEntry.toJSONObject());
383db8f2f9ad5dcb1d52c399520129d89b1ca38f423Paul Westbrook        }
384db8f2f9ad5dcb1d52c399520129d89b1ca38f423Paul Westbrook
385db8f2f9ad5dcb1d52c399520129d89b1ca38f423Paul Westbrook        final SharedPreferences.Editor editor = getPreferences().edit();
386bc14a6f8da73a563c27dd99dc433f8c301dfe8edAndy Huang        editor.putString(ACCOUNT_LIST_KEY, arr.toString());
387db8f2f9ad5dcb1d52c399520129d89b1ca38f423Paul Westbrook        editor.apply();
388db8f2f9ad5dcb1d52c399520129d89b1ca38f423Paul Westbrook    }
389db8f2f9ad5dcb1d52c399520129d89b1ca38f423Paul Westbrook
390db8f2f9ad5dcb1d52c399520129d89b1ca38f423Paul Westbrook    private SharedPreferences getPreferences() {
391db8f2f9ad5dcb1d52c399520129d89b1ca38f423Paul Westbrook        if (mSharedPrefs == null) {
392db8f2f9ad5dcb1d52c399520129d89b1ca38f423Paul Westbrook            mSharedPrefs = getContext().getSharedPreferences(
393db8f2f9ad5dcb1d52c399520129d89b1ca38f423Paul Westbrook                    SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
394db8f2f9ad5dcb1d52c399520129d89b1ca38f423Paul Westbrook        }
395db8f2f9ad5dcb1d52c399520129d89b1ca38f423Paul Westbrook        return mSharedPrefs;
396db8f2f9ad5dcb1d52c399520129d89b1ca38f423Paul Westbrook    }
397db8f2f9ad5dcb1d52c399520129d89b1ca38f423Paul Westbrook
398a6b671dd9f5ba358a05888b3ab3bf1c5cb5cf493Marc Blank    static public Account getAccountFromAccountUri(Uri accountUri) {
399a6b671dd9f5ba358a05888b3ab3bf1c5cb5cf493Marc Blank        MailAppProvider provider = getInstance();
400a6b671dd9f5ba358a05888b3ab3bf1c5cb5cf493Marc Blank        if (provider != null && provider.mAccountsFullyLoaded) {
401a6b671dd9f5ba358a05888b3ab3bf1c5cb5cf493Marc Blank            synchronized(provider.mAccountCache) {
402a6b671dd9f5ba358a05888b3ab3bf1c5cb5cf493Marc Blank                AccountCacheEntry entry = provider.mAccountCache.get(accountUri);
403a6b671dd9f5ba358a05888b3ab3bf1c5cb5cf493Marc Blank                if (entry != null) {
404a6b671dd9f5ba358a05888b3ab3bf1c5cb5cf493Marc Blank                    return entry.mAccount;
405a6b671dd9f5ba358a05888b3ab3bf1c5cb5cf493Marc Blank                }
406a6b671dd9f5ba358a05888b3ab3bf1c5cb5cf493Marc Blank            }
407a6b671dd9f5ba358a05888b3ab3bf1c5cb5cf493Marc Blank        }
408a6b671dd9f5ba358a05888b3ab3bf1c5cb5cf493Marc Blank        return null;
409a6b671dd9f5ba358a05888b3ab3bf1c5cb5cf493Marc Blank    }
410a6b671dd9f5ba358a05888b3ab3bf1c5cb5cf493Marc Blank
411bb303e7a71ae6e54d58bb1d5d254dc58317c6382Paul Westbrook    @Override
412bb303e7a71ae6e54d58bb1d5d254dc58317c6382Paul Westbrook    public void onLoadComplete(Loader<Cursor> loader, Cursor data) {
413bb303e7a71ae6e54d58bb1d5d254dc58317c6382Paul Westbrook        if (data == null) {
414bb303e7a71ae6e54d58bb1d5d254dc58317c6382Paul Westbrook            LogUtils.d(LOG_TAG, "null account cursor returned");
415bb303e7a71ae6e54d58bb1d5d254dc58317c6382Paul Westbrook            return;
416bb303e7a71ae6e54d58bb1d5d254dc58317c6382Paul Westbrook        }
417bb303e7a71ae6e54d58bb1d5d254dc58317c6382Paul Westbrook
418e3e8429f5665e5cfe3901962ffbae204a5c38147Paul Westbrook        LogUtils.d(LOG_TAG, "Cursor with %d accounts returned", data.getCount());
419bb303e7a71ae6e54d58bb1d5d254dc58317c6382Paul Westbrook        final CursorLoader cursorLoader = (CursorLoader)loader;
420bb303e7a71ae6e54d58bb1d5d254dc58317c6382Paul Westbrook        final Uri accountsQueryUri = cursorLoader.getUri();
42172a9d380a0effb191c4ef8e32899ea62ebf52f55Paul Westbrook
422bc14a6f8da73a563c27dd99dc433f8c301dfe8edAndy Huang        // preserve ordering on partial updates
423bc14a6f8da73a563c27dd99dc433f8c301dfe8edAndy Huang        // also preserve ordering on complete updates for any that existed previously
424bc14a6f8da73a563c27dd99dc433f8c301dfe8edAndy Huang
425bc14a6f8da73a563c27dd99dc433f8c301dfe8edAndy Huang
426bc14a6f8da73a563c27dd99dc433f8c301dfe8edAndy Huang        final List<AccountCacheEntry> accountList;
42772a9d380a0effb191c4ef8e32899ea62ebf52f55Paul Westbrook        synchronized (mAccountCache) {
428bc14a6f8da73a563c27dd99dc433f8c301dfe8edAndy Huang            accountList = ImmutableList.copyOf(mAccountCache.values());
42972a9d380a0effb191c4ef8e32899ea62ebf52f55Paul Westbrook        }
43072a9d380a0effb191c4ef8e32899ea62ebf52f55Paul Westbrook
43172a9d380a0effb191c4ef8e32899ea62ebf52f55Paul Westbrook        // Build a set of the account uris that had been associated with that query
432c597cb55f02fe41c9fb180d1b66311e1a06523e1Paul Westbrook        final Set<Uri> previousQueryUriSet = Sets.newHashSet();
43372a9d380a0effb191c4ef8e32899ea62ebf52f55Paul Westbrook        for (AccountCacheEntry entry : accountList) {
43472a9d380a0effb191c4ef8e32899ea62ebf52f55Paul Westbrook            if (accountsQueryUri.equals(entry.mAccountsQueryUri)) {
435c597cb55f02fe41c9fb180d1b66311e1a06523e1Paul Westbrook                previousQueryUriSet.add(entry.mAccount.uri);
436c597cb55f02fe41c9fb180d1b66311e1a06523e1Paul Westbrook            }
43772a9d380a0effb191c4ef8e32899ea62ebf52f55Paul Westbrook        }
438bb303e7a71ae6e54d58bb1d5d254dc58317c6382Paul Westbrook
439e95c7ded028607899d9596685c7cec21c1274d8ePaul Westbrook        // Update the internal state of this provider if the returned result set
440e95c7ded028607899d9596685c7cec21c1274d8ePaul Westbrook        // represents all accounts
441e95c7ded028607899d9596685c7cec21c1274d8ePaul Westbrook        // TODO: determine what should happen with a heterogeneous set of accounts
442e95c7ded028607899d9596685c7cec21c1274d8ePaul Westbrook        final Bundle extra = data.getExtras();
443e95c7ded028607899d9596685c7cec21c1274d8ePaul Westbrook        mAccountsFullyLoaded = extra.getInt(AccountCursorExtraKeys.ACCOUNTS_LOADED) != 0;
444e95c7ded028607899d9596685c7cec21c1274d8ePaul Westbrook
445bb303e7a71ae6e54d58bb1d5d254dc58317c6382Paul Westbrook        final Set<Uri> newQueryUriMap = Sets.newHashSet();
446c597cb55f02fe41c9fb180d1b66311e1a06523e1Paul Westbrook
447c597cb55f02fe41c9fb180d1b66311e1a06523e1Paul Westbrook        // We are relying on the fact that all accounts are added in the order specified in the
448c597cb55f02fe41c9fb180d1b66311e1a06523e1Paul Westbrook        // cursor.  Initially assume that we insert these items to at the end of the list
449bb303e7a71ae6e54d58bb1d5d254dc58317c6382Paul Westbrook        while (data.moveToNext()) {
450bb303e7a71ae6e54d58bb1d5d254dc58317c6382Paul Westbrook            final Account account = new Account(data);
451bb303e7a71ae6e54d58bb1d5d254dc58317c6382Paul Westbrook            final Uri accountUri = account.uri;
452bb303e7a71ae6e54d58bb1d5d254dc58317c6382Paul Westbrook            newQueryUriMap.add(accountUri);
453bc14a6f8da73a563c27dd99dc433f8c301dfe8edAndy Huang            // preserve existing order if already present and this is a partial update,
454bc14a6f8da73a563c27dd99dc433f8c301dfe8edAndy Huang            // otherwise add to the end
455bc14a6f8da73a563c27dd99dc433f8c301dfe8edAndy Huang            //
456bc14a6f8da73a563c27dd99dc433f8c301dfe8edAndy Huang            // N.B. this ordering policy means the order in which providers respond will affect
457bc14a6f8da73a563c27dd99dc433f8c301dfe8edAndy Huang            // the order of accounts.
458bc14a6f8da73a563c27dd99dc433f8c301dfe8edAndy Huang            if (mAccountsFullyLoaded) {
459d0705dcd413e763d4ba1ee1acc3c67afe05a08d4Andy Huang                synchronized (mAccountCache) {
460bc14a6f8da73a563c27dd99dc433f8c301dfe8edAndy Huang                    // removing the existing item will prevent LinkedHashMap from preserving the
461bc14a6f8da73a563c27dd99dc433f8c301dfe8edAndy Huang                    // original insertion order
462bc14a6f8da73a563c27dd99dc433f8c301dfe8edAndy Huang                    mAccountCache.remove(accountUri);
463d0705dcd413e763d4ba1ee1acc3c67afe05a08d4Andy Huang                }
464d0705dcd413e763d4ba1ee1acc3c67afe05a08d4Andy Huang            }
465bc14a6f8da73a563c27dd99dc433f8c301dfe8edAndy Huang            addAccountImpl(account, accountsQueryUri, false /* don't notify */);
466bb303e7a71ae6e54d58bb1d5d254dc58317c6382Paul Westbrook        }
46768b6927dbf5678c9b3728e52782a2f1eef55466eVikram Aggarwal        // Remove all of the accounts that are in the new result set
468c597cb55f02fe41c9fb180d1b66311e1a06523e1Paul Westbrook        previousQueryUriSet.removeAll(newQueryUriMap);
469bb303e7a71ae6e54d58bb1d5d254dc58317c6382Paul Westbrook
47068b6927dbf5678c9b3728e52782a2f1eef55466eVikram Aggarwal        // For all of the entries that had been in the previous result set, and are not
47168b6927dbf5678c9b3728e52782a2f1eef55466eVikram Aggarwal        // in the new result set, remove them from the cache
472c597cb55f02fe41c9fb180d1b66311e1a06523e1Paul Westbrook        if (previousQueryUriSet.size() > 0 && mAccountsFullyLoaded) {
473bc14a6f8da73a563c27dd99dc433f8c301dfe8edAndy Huang            synchronized (mAccountCache) {
474bc14a6f8da73a563c27dd99dc433f8c301dfe8edAndy Huang                for (Uri accountUri : previousQueryUriSet) {
475bc14a6f8da73a563c27dd99dc433f8c301dfe8edAndy Huang                    LogUtils.d(LOG_TAG, "Removing account %s", accountUri);
476bc14a6f8da73a563c27dd99dc433f8c301dfe8edAndy Huang                    mAccountCache.remove(accountUri);
477bc14a6f8da73a563c27dd99dc433f8c301dfe8edAndy Huang                }
478bc14a6f8da73a563c27dd99dc433f8c301dfe8edAndy Huang            }
479bb303e7a71ae6e54d58bb1d5d254dc58317c6382Paul Westbrook        }
480e95c7ded028607899d9596685c7cec21c1274d8ePaul Westbrook        broadcastAccountChange();
481bc14a6f8da73a563c27dd99dc433f8c301dfe8edAndy Huang
482bc14a6f8da73a563c27dd99dc433f8c301dfe8edAndy Huang        // Cache the updated account list
483bc14a6f8da73a563c27dd99dc433f8c301dfe8edAndy Huang        cacheAccountList();
484bb303e7a71ae6e54d58bb1d5d254dc58317c6382Paul Westbrook    }
48572a9d380a0effb191c4ef8e32899ea62ebf52f55Paul Westbrook
48672a9d380a0effb191c4ef8e32899ea62ebf52f55Paul Westbrook    /**
48772a9d380a0effb191c4ef8e32899ea62ebf52f55Paul Westbrook     * Object that allows the Account Cache provider to associate the account with the content
48872a9d380a0effb191c4ef8e32899ea62ebf52f55Paul Westbrook     * provider uri that originated that account.
48972a9d380a0effb191c4ef8e32899ea62ebf52f55Paul Westbrook     */
490bc14a6f8da73a563c27dd99dc433f8c301dfe8edAndy Huang    private static class AccountCacheEntry {
49172a9d380a0effb191c4ef8e32899ea62ebf52f55Paul Westbrook        final Account mAccount;
49272a9d380a0effb191c4ef8e32899ea62ebf52f55Paul Westbrook        final Uri mAccountsQueryUri;
49372a9d380a0effb191c4ef8e32899ea62ebf52f55Paul Westbrook
494bc14a6f8da73a563c27dd99dc433f8c301dfe8edAndy Huang        private static final String KEY_ACCOUNT = "acct";
495bc14a6f8da73a563c27dd99dc433f8c301dfe8edAndy Huang        private static final String KEY_QUERY_URI = "queryUri";
49672a9d380a0effb191c4ef8e32899ea62ebf52f55Paul Westbrook
497bc14a6f8da73a563c27dd99dc433f8c301dfe8edAndy Huang        public AccountCacheEntry(Account account, Uri accountQueryUri) {
49872a9d380a0effb191c4ef8e32899ea62ebf52f55Paul Westbrook            mAccount = account;
49972a9d380a0effb191c4ef8e32899ea62ebf52f55Paul Westbrook            mAccountsQueryUri = accountQueryUri;
50072a9d380a0effb191c4ef8e32899ea62ebf52f55Paul Westbrook        }
50172a9d380a0effb191c4ef8e32899ea62ebf52f55Paul Westbrook
502bc14a6f8da73a563c27dd99dc433f8c301dfe8edAndy Huang        public AccountCacheEntry(JSONObject o) throws JSONException {
503bc14a6f8da73a563c27dd99dc433f8c301dfe8edAndy Huang            mAccount = Account.newinstance(o.getString(KEY_ACCOUNT));
5046dde178687e85aaab9b3f8e9c124c3ab99b77d5aVikram Aggarwal            if (mAccount == null) {
5056dde178687e85aaab9b3f8e9c124c3ab99b77d5aVikram Aggarwal                throw new IllegalArgumentException("AccountCacheEntry de-serializing failed. "
506bc14a6f8da73a563c27dd99dc433f8c301dfe8edAndy Huang                        + "Account object could not be created from the JSONObject: "
507bc14a6f8da73a563c27dd99dc433f8c301dfe8edAndy Huang                        + o);
5086dde178687e85aaab9b3f8e9c124c3ab99b77d5aVikram Aggarwal            }
5090a88d81a9f6d6997ff62c669824f1f7e85206ca3Andy Huang            if (mAccount.settings == Settings.EMPTY_SETTINGS) {
5100a88d81a9f6d6997ff62c669824f1f7e85206ca3Andy Huang                throw new IllegalArgumentException("AccountCacheEntry de-serializing failed. "
511bc14a6f8da73a563c27dd99dc433f8c301dfe8edAndy Huang                        + "Settings could not be created from the JSONObject: " + o);
512bc14a6f8da73a563c27dd99dc433f8c301dfe8edAndy Huang            }
513bc14a6f8da73a563c27dd99dc433f8c301dfe8edAndy Huang            final String uriStr = o.optString(KEY_QUERY_URI, null);
514bc14a6f8da73a563c27dd99dc433f8c301dfe8edAndy Huang            if (uriStr != null) {
515bc14a6f8da73a563c27dd99dc433f8c301dfe8edAndy Huang                mAccountsQueryUri = Uri.parse(uriStr);
516bc14a6f8da73a563c27dd99dc433f8c301dfe8edAndy Huang            } else {
517bc14a6f8da73a563c27dd99dc433f8c301dfe8edAndy Huang                mAccountsQueryUri = null;
5180a88d81a9f6d6997ff62c669824f1f7e85206ca3Andy Huang            }
519c597cb55f02fe41c9fb180d1b66311e1a06523e1Paul Westbrook        }
520c597cb55f02fe41c9fb180d1b66311e1a06523e1Paul Westbrook
521bc14a6f8da73a563c27dd99dc433f8c301dfe8edAndy Huang        public JSONObject toJSONObject() {
522bc14a6f8da73a563c27dd99dc433f8c301dfe8edAndy Huang            try {
523bc14a6f8da73a563c27dd99dc433f8c301dfe8edAndy Huang                return new JSONObject()
524bc14a6f8da73a563c27dd99dc433f8c301dfe8edAndy Huang                .put(KEY_ACCOUNT, mAccount.serialize())
525bc14a6f8da73a563c27dd99dc433f8c301dfe8edAndy Huang                .putOpt(KEY_QUERY_URI, mAccountsQueryUri);
526bc14a6f8da73a563c27dd99dc433f8c301dfe8edAndy Huang            } catch (JSONException e) {
527bc14a6f8da73a563c27dd99dc433f8c301dfe8edAndy Huang                // shouldn't happen
528bc14a6f8da73a563c27dd99dc433f8c301dfe8edAndy Huang                throw new IllegalArgumentException(e);
529bc14a6f8da73a563c27dd99dc433f8c301dfe8edAndy Huang            }
53072a9d380a0effb191c4ef8e32899ea62ebf52f55Paul Westbrook        }
531bc14a6f8da73a563c27dd99dc433f8c301dfe8edAndy Huang
53272a9d380a0effb191c4ef8e32899ea62ebf52f55Paul Westbrook    }
5332388c5d208acd4e4e658aaacbbc5f080ee8e9f7cPaul Westbrook}
534