MailAppProvider.java revision 707f24c693f116901660ce4b07598b474e681319
1/**
2 * Copyright (c) 2011, Google Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *     http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.mail.providers;
18
19import android.app.Activity;
20import android.content.ContentProvider;
21import android.content.ContentResolver;
22import android.content.ContentValues;
23import android.content.Context;
24import android.content.CursorLoader;
25import android.content.Intent;
26import android.content.Loader;
27import android.content.Loader.OnLoadCompleteListener;
28import android.content.SharedPreferences;
29import android.database.Cursor;
30import android.database.MatrixCursor;
31import android.net.Uri;
32import android.os.Bundle;
33import android.provider.BaseColumns;
34import android.text.TextUtils;
35
36import com.android.mail.providers.UIProvider.AccountCursorExtraKeys;
37import com.android.mail.providers.protos.boot.AccountReceiver;
38import com.android.mail.utils.LogTag;
39import com.android.mail.utils.LogUtils;
40import com.android.mail.utils.MatrixCursorWithExtra;
41import com.google.common.collect.ImmutableSet;
42import com.google.common.collect.Maps;
43import com.google.common.collect.Sets;
44
45import java.util.Collections;
46import java.util.Map;
47import java.util.Set;
48import java.util.regex.Pattern;
49
50
51
52/**
53 * The Mail App provider allows email providers to register "accounts" and the UI has a single
54 * place to query for the list of accounts.
55 *
56 * During development this will allow new account types to be added, and allow them to be shown in
57 * the application.  For example, the mock accounts can be enabled/disabled.
58 * In the future, once other processes can add new accounts, this could allow other "mail"
59 * applications have their content appear within the application
60 */
61public abstract class MailAppProvider extends ContentProvider
62        implements OnLoadCompleteListener<Cursor>{
63
64    private static final String SHARED_PREFERENCES_NAME = "MailAppProvider";
65    private static final String ACCOUNT_LIST_KEY = "accountList";
66    private static final String LAST_VIEWED_ACCOUNT_KEY = "lastViewedAccount";
67    private static final String LAST_SENT_FROM_ACCOUNT_KEY = "lastSendFromAccount";
68
69    /**
70     * Extra used in the result from the activity launched by the intent specified
71     * by {@link #getNoAccountsIntent} to return the list of accounts.  The data
72     * specified by this extra key should be a ParcelableArray.
73     */
74    public static final String ADD_ACCOUNT_RESULT_ACCOUNTS_EXTRA = "addAccountResultAccounts";
75
76    private final static String LOG_TAG = LogTag.getLogTag();
77
78    private final Map<Uri, AccountCacheEntry> mAccountCache = Maps.newHashMap();
79
80    private final Map<Uri, CursorLoader> mCursorLoaderMap = Maps.newHashMap();
81
82    private ContentResolver mResolver;
83    private static String sAuthority;
84    private static MailAppProvider sInstance;
85
86    private volatile boolean mAccountsFullyLoaded = false;
87
88    private SharedPreferences mSharedPrefs;
89
90    /**
91     * Allows the implementing provider to specify the authority for this provider. Email and Gmail
92     * must specify different authorities.
93     */
94    protected abstract String getAuthority();
95
96    /**
97     * Authority for the suggestions provider. Email and Gmail must specify different authorities,
98     * much like the implementation of {@link #getAuthority()}.
99     * @return the suggestion authority associated with this provider.
100     */
101    public abstract String getSuggestionAuthority();
102
103    /**
104     * Allows the implementing provider to specify an intent that should be used in a call to
105     * {@link Context#startActivityForResult(android.content.Intent)} when the account provider
106     * doesn't return any accounts.
107     *
108     * The result from the {@link Activity} activity should include the list of accounts in
109     * the returned intent, in the
110
111     * @return Intent or null, if the provider doesn't specify a behavior when no accounts are
112     * specified.
113     */
114    protected abstract Intent getNoAccountsIntent(Context context);
115
116    /**
117     * The cursor returned from a call to {@link android.content.ContentResolver#query()} with this
118     * uri will return a cursor that with columns that are a subset of the columns specified
119     * in {@link UIProvider.ConversationColumns}
120     * The cursor returned by this query can return a {@link android.os.Bundle}
121     * from a call to {@link android.database.Cursor#getExtras()}.  This Bundle may have
122     * values with keys listed in {@link AccountCursorExtraKeys}
123     */
124    public static Uri getAccountsUri() {
125        return Uri.parse("content://" + sAuthority + "/");
126    }
127
128    public static MailAppProvider getInstance() {
129        return sInstance;
130    }
131
132    /** Default constructor */
133    protected MailAppProvider() {
134        sInstance = this;
135    }
136
137    @Override
138    public boolean onCreate() {
139        sAuthority = getAuthority();
140        mResolver = getContext().getContentResolver();
141
142        final Intent intent = new Intent(AccountReceiver.ACTION_PROVIDER_CREATED);
143        getContext().sendBroadcast(intent);
144
145        // Load the previously saved account list
146        loadCachedAccountList();
147
148        return true;
149    }
150
151    @Override
152    public void shutdown() {
153        sInstance = null;
154
155        for (CursorLoader loader : mCursorLoaderMap.values()) {
156            loader.stopLoading();
157        }
158        mCursorLoaderMap.clear();
159    }
160
161    @Override
162    public Cursor query(Uri url, String[] projection, String selection, String[] selectionArgs,
163            String sortOrder) {
164        // This content provider currently only supports one query (to return the list of accounts).
165        // No reason to check the uri.  Currently only checking the projections
166
167        // Validates and returns the projection that should be used.
168        final String[] resultProjection = UIProviderValidator.validateAccountProjection(projection);
169        final Bundle extras = new Bundle();
170        extras.putInt(AccountCursorExtraKeys.ACCOUNTS_LOADED, mAccountsFullyLoaded ? 1 : 0);
171
172        // Make a copy of the account cache
173
174        final Set<AccountCacheEntry> accountList;
175        synchronized (mAccountCache) {
176            accountList = ImmutableSet.copyOf(mAccountCache.values());
177        }
178
179        final MatrixCursor cursor =
180                new MatrixCursorWithExtra(resultProjection, accountList.size(), extras);
181
182        for (AccountCacheEntry accountEntry : accountList) {
183            final Account account = accountEntry.mAccount;
184            final MatrixCursor.RowBuilder builder = cursor.newRow();
185
186            for (String column : resultProjection) {
187                if (TextUtils.equals(column, BaseColumns._ID)) {
188                    // TODO(pwestbro): remove this as it isn't used.
189                    builder.add(Integer.valueOf(0));
190                } else if (TextUtils.equals(column, UIProvider.AccountColumns.NAME)) {
191                    builder.add(account.name);
192                } else if (TextUtils.equals(column, UIProvider.AccountColumns.PROVIDER_VERSION)) {
193                    // TODO fix this
194                    builder.add(Integer.valueOf(account.providerVersion));
195                } else if (TextUtils.equals(column, UIProvider.AccountColumns.URI)) {
196                    builder.add(account.uri);
197                } else if (TextUtils.equals(column, UIProvider.AccountColumns.CAPABILITIES)) {
198                    builder.add(Integer.valueOf(account.capabilities));
199                } else if (TextUtils.equals(column, UIProvider.AccountColumns.FOLDER_LIST_URI)) {
200                    builder.add(account.folderListUri);
201                } else if (TextUtils
202                        .equals(column, UIProvider.AccountColumns.FULL_FOLDER_LIST_URI)) {
203                    builder.add(account.fullFolderListUri);
204                } else if (TextUtils.equals(column, UIProvider.AccountColumns.SEARCH_URI)) {
205                    builder.add(account.searchUri);
206                } else if (TextUtils.equals(column,
207                        UIProvider.AccountColumns.ACCOUNT_FROM_ADDRESSES)) {
208                    builder.add(account.accountFromAddresses);
209                } else if (TextUtils.equals(column, UIProvider.AccountColumns.SAVE_DRAFT_URI)) {
210                    builder.add(account.saveDraftUri);
211                } else if (TextUtils.equals(column, UIProvider.AccountColumns.SEND_MAIL_URI)) {
212                    builder.add(account.sendMessageUri);
213                } else if (TextUtils.equals(column,
214                        UIProvider.AccountColumns.EXPUNGE_MESSAGE_URI)) {
215                    builder.add(account.expungeMessageUri);
216                } else if (TextUtils.equals(column, UIProvider.AccountColumns.UNDO_URI)) {
217                    builder.add(account.undoUri);
218                } else if (TextUtils.equals(column,
219                        UIProvider.AccountColumns.SETTINGS_INTENT_URI)) {
220                    builder.add(account.settingsIntentUri);
221                } else if (TextUtils.equals(column,
222                        UIProvider.AccountColumns.HELP_INTENT_URI)) {
223                    builder.add(account.helpIntentUri);
224                } else if (TextUtils.equals(column,
225                        UIProvider.AccountColumns.SEND_FEEDBACK_INTENT_URI)) {
226                    builder.add(account.sendFeedbackIntentUri);
227                } else if (TextUtils.equals(column, UIProvider.AccountColumns.SYNC_STATUS)) {
228                    builder.add(Integer.valueOf(account.syncStatus));
229                } else if (TextUtils.equals(column, UIProvider.AccountColumns.COMPOSE_URI)) {
230                    builder.add(account.composeIntentUri);
231                } else if (TextUtils.equals(column, UIProvider.AccountColumns.MIME_TYPE)) {
232                    builder.add(account.mimeType);
233                } else if (TextUtils.equals(column,
234                        UIProvider.AccountColumns.RECENT_FOLDER_LIST_URI)) {
235                    builder.add(account.recentFolderListUri);
236                } else if (TextUtils.equals(column,
237                        UIProvider.AccountColumns.DEFAULT_RECENT_FOLDER_LIST_URI)) {
238                    builder.add(account.defaultRecentFolderListUri);
239                } else if (TextUtils.equals(column,
240                        UIProvider.AccountColumns.MANUAL_SYNC_URI)) {
241                    builder.add(account.manualSyncUri);
242                } else if (TextUtils.equals(column, UIProvider.AccountColumns.COLOR)) {
243                    builder.add(account.color);
244                } else if (TextUtils.equals(column,
245                        UIProvider.AccountColumns.SettingsColumns.SIGNATURE)) {
246                    builder.add(account.settings.signature);
247                } else if (TextUtils.equals(column,
248                        UIProvider.AccountColumns.SettingsColumns.AUTO_ADVANCE)) {
249                    builder.add(Integer.valueOf(account.settings.autoAdvance));
250                } else if (TextUtils.equals(column,
251                        UIProvider.AccountColumns.SettingsColumns.MESSAGE_TEXT_SIZE)) {
252                    builder.add(Integer.valueOf(account.settings.messageTextSize));
253                } else if (TextUtils.equals(column,
254                        UIProvider.AccountColumns.SettingsColumns.REPLY_BEHAVIOR)) {
255                    builder.add(Integer.valueOf(account.settings.replyBehavior));
256                } else if (TextUtils.equals(column,
257                        UIProvider.AccountColumns.SettingsColumns.HIDE_CHECKBOXES)) {
258                    builder.add(Integer.valueOf(account.settings.hideCheckboxes ? 1 : 0));
259                } else if (TextUtils.equals(column,
260                        UIProvider.AccountColumns.SettingsColumns.CONFIRM_DELETE)) {
261                    builder.add(Integer.valueOf(account.settings.confirmDelete ? 1 : 0));
262                } else if (TextUtils.equals(column,
263                        UIProvider.AccountColumns.SettingsColumns.CONFIRM_ARCHIVE)) {
264                    builder.add(Integer.valueOf(account.settings.confirmArchive ? 1 : 0));
265                } else if (TextUtils.equals(column,
266                        UIProvider.AccountColumns.SettingsColumns.CONFIRM_SEND)) {
267                    builder.add(Integer.valueOf(account.settings.confirmSend ? 1 : 0));
268                } else if (TextUtils.equals(column,
269                        UIProvider.AccountColumns.SettingsColumns.DEFAULT_INBOX)) {
270                    builder.add(account.settings.defaultInbox);
271                } else if (TextUtils.equals(column,
272                        UIProvider.AccountColumns.SettingsColumns.DEFAULT_INBOX_NAME)) {
273                    builder.add(account.settings.defaultInboxName);
274                } else if (TextUtils.equals(column,
275                        UIProvider.AccountColumns.SettingsColumns.SNAP_HEADERS)) {
276                    builder.add(Integer.valueOf(account.settings.snapHeaders));
277                } else if (TextUtils.equals(column,
278                        UIProvider.AccountColumns.SettingsColumns.FORCE_REPLY_FROM_DEFAULT)) {
279                    builder.add(Integer.valueOf(account.settings.forceReplyFromDefault ? 1 : 0));
280                } else if (TextUtils.equals(column,
281                        UIProvider.AccountColumns.SettingsColumns.MAX_ATTACHMENT_SIZE)) {
282                    builder.add(account.settings.maxAttachmentSize);
283                } else if (TextUtils.equals(column,
284                        UIProvider.AccountColumns.SettingsColumns.SWIPE)) {
285                    builder.add(account.settings.swipe);
286                } else if (TextUtils.equals(column,
287                        UIProvider.AccountColumns.SettingsColumns.PRIORITY_ARROWS_ENABLED)) {
288                    builder.add(Integer.valueOf(account.settings.priorityArrowsEnabled ? 1 : 0));
289                } else {
290                    throw new IllegalStateException("Column not found: " + column);
291                }
292            }
293        }
294
295        cursor.setNotificationUri(mResolver, getAccountsUri());
296        return cursor;
297    }
298
299    @Override
300    public Uri insert(Uri url, ContentValues values) {
301        return url;
302    }
303
304    @Override
305    public int update(Uri url, ContentValues values, String selection,
306            String[] selectionArgs) {
307        return 0;
308    }
309
310    @Override
311    public int delete(Uri url, String selection, String[] selectionArgs) {
312        return 0;
313    }
314
315    @Override
316    public String getType(Uri uri) {
317        return null;
318    }
319
320    /**
321     * Asynchronously ads all of the accounts that are specified by the result set returned by
322     * {@link ContentProvider#query()} for the specified uri.  The content provider handling the
323     * query needs to handle the {@link UIProvider.ACCOUNTS_PROJECTION}
324     * Any changes to the underlying provider will automatically be reflected.
325     * @param resolver
326     * @param accountsQueryUri
327     */
328    public static void addAccountsForUriAsync(Uri accountsQueryUri) {
329        getInstance().startAccountsLoader(accountsQueryUri);
330    }
331
332    /**
333     * Returns the intent that should be used in a call to
334     * {@link Context#startActivity(android.content.Intent)} when the account provider doesn't
335     * return any accounts
336     * @return Intent or null, if the provider doesn't specify a behavior when no acccounts are
337     * specified.
338     */
339    public static Intent getNoAccountIntent(Context context) {
340        return getInstance().getNoAccountsIntent(context);
341    }
342
343    private synchronized void startAccountsLoader(Uri accountsQueryUri) {
344        final CursorLoader accountsCursorLoader = new CursorLoader(getContext(), accountsQueryUri,
345                UIProvider.ACCOUNTS_PROJECTION, null, null, null);
346
347        // Listen for the results
348        accountsCursorLoader.registerListener(accountsQueryUri.hashCode(), this);
349        accountsCursorLoader.startLoading();
350
351        // If there is a previous loader for the given uri, stop it
352        final CursorLoader oldLoader = mCursorLoaderMap.get(accountsQueryUri);
353        if (oldLoader != null) {
354            oldLoader.stopLoading();
355        }
356        mCursorLoaderMap.put(accountsQueryUri, accountsCursorLoader);
357    }
358
359    public static void addAccount(Account account, Uri accountsQueryUri) {
360        final MailAppProvider provider = getInstance();
361        if (provider == null) {
362            throw new IllegalStateException("MailAppProvider not intialized");
363        }
364        provider.addAccountImpl(account, accountsQueryUri, true /* notify */);
365    }
366
367    private void addAccountImpl(Account account, Uri accountsQueryUri, boolean notify) {
368        synchronized (mAccountCache) {
369            if (account != null) {
370                LogUtils.v(LOG_TAG, "adding account %s", account);
371                mAccountCache.put(account.uri, new AccountCacheEntry(account, accountsQueryUri));
372            }
373        }
374        // Explicitly calling this out of the synchronized block in case any of the observers get
375        // called synchronously.
376        if (notify) {
377            broadcastAccountChange();
378        }
379
380        // Cache the updated account list
381        cacheAccountList();
382    }
383
384    public static void removeAccount(Uri accountUri) {
385        final MailAppProvider provider = getInstance();
386        if (provider == null) {
387            throw new IllegalStateException("MailAppProvider not intialized");
388        }
389        provider.removeAccounts(Collections.singleton(accountUri), true /* notify */);
390    }
391
392    private void removeAccounts(Set<Uri> uris, boolean notify) {
393        synchronized (mAccountCache) {
394            for (Uri accountUri : uris) {
395                mAccountCache.remove(accountUri);
396            }
397        }
398
399        // Explicitly calling this out of the synchronized block in case any of the observers get
400        // called synchronously.
401        if (notify) {
402            broadcastAccountChange();
403        }
404
405        // Cache the updated account list
406        cacheAccountList();
407    }
408
409    private static void broadcastAccountChange() {
410        final MailAppProvider provider = sInstance;
411
412        if (provider != null) {
413            provider.mResolver.notifyChange(getAccountsUri(), null);
414        }
415    }
416
417    /**
418     * Returns the {@link Account#uri} (in String form) of the last viewed account.
419     */
420    public String getLastViewedAccount() {
421        return getPreferences().getString(LAST_VIEWED_ACCOUNT_KEY, null);
422    }
423
424    /**
425     * Persists the {@link Account#uri} (in String form) of the last viewed account.
426     */
427    public void setLastViewedAccount(String accountUriStr) {
428        final SharedPreferences.Editor editor = getPreferences().edit();
429        editor.putString(LAST_VIEWED_ACCOUNT_KEY, accountUriStr);
430        editor.apply();
431    }
432
433    /**
434     * Returns the {@link Account#uri} (in String form) of the last account the
435     * user compose a message from.
436     */
437    public String getLastSentFromAccount() {
438        return getPreferences().getString(LAST_SENT_FROM_ACCOUNT_KEY, null);
439    }
440
441    /**
442     * Persists the {@link Account#uri} (in String form) of the last account the
443     * user compose a message from.
444     */
445    public void setLastSentFromAccount(String accountUriStr) {
446        final SharedPreferences.Editor editor = getPreferences().edit();
447        editor.putString(LAST_SENT_FROM_ACCOUNT_KEY, accountUriStr);
448        editor.apply();
449    }
450
451    private void loadCachedAccountList() {
452        final SharedPreferences preference = getPreferences();
453
454        final Set<String> accountsStringSet = preference.getStringSet(ACCOUNT_LIST_KEY, null);
455
456        if (accountsStringSet != null) {
457            for (String serializedAccount : accountsStringSet) {
458                try {
459                    final AccountCacheEntry accountEntry =
460                            new AccountCacheEntry(serializedAccount);
461                    if (accountEntry.mAccount.settings != null) {
462                        addAccountImpl(accountEntry.mAccount, accountEntry.mAccountsQueryUri,
463                                false /* don't notify */);
464                    } else {
465                        LogUtils.e(LOG_TAG, "Dropping account that doesn't specify settings");
466                    }
467                } catch (Exception e) {
468                    // Unable to create account object, skip to next
469                    LogUtils.e(LOG_TAG, e,
470                            "Unable to create account object from serialized string '%s'",
471                            serializedAccount);
472                }
473            }
474            broadcastAccountChange();
475        }
476    }
477
478    private void cacheAccountList() {
479        final Set<AccountCacheEntry> accountList;
480        synchronized (mAccountCache) {
481            accountList = ImmutableSet.copyOf(mAccountCache.values());
482        }
483
484        final Set<String> serializedAccounts = Sets.newHashSet();
485        for (AccountCacheEntry accountEntry : accountList) {
486            serializedAccounts.add(accountEntry.serialize());
487        }
488
489        final SharedPreferences.Editor editor = getPreferences().edit();
490        editor.putStringSet(ACCOUNT_LIST_KEY, serializedAccounts);
491        editor.apply();
492    }
493
494    private SharedPreferences getPreferences() {
495        if (mSharedPrefs == null) {
496            mSharedPrefs = getContext().getSharedPreferences(
497                    SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
498        }
499        return mSharedPrefs;
500    }
501
502    static public Account getAccountFromAccountUri(Uri accountUri) {
503        MailAppProvider provider = getInstance();
504        if (provider != null && provider.mAccountsFullyLoaded) {
505            synchronized(provider.mAccountCache) {
506                AccountCacheEntry entry = provider.mAccountCache.get(accountUri);
507                if (entry != null) {
508                    return entry.mAccount;
509                }
510            }
511        }
512        return null;
513    }
514
515    @Override
516    public void onLoadComplete(Loader<Cursor> loader, Cursor data) {
517        if (data == null) {
518            LogUtils.d(LOG_TAG, "null account cursor returned");
519            return;
520        }
521
522        LogUtils.d(LOG_TAG, "Cursor with %d accounts returned", data.getCount());
523        final CursorLoader cursorLoader = (CursorLoader)loader;
524        final Uri accountsQueryUri = cursorLoader.getUri();
525
526        final Set<AccountCacheEntry> accountList;
527        synchronized (mAccountCache) {
528            accountList = ImmutableSet.copyOf(mAccountCache.values());
529        }
530
531        // Build a set of the account uris that had been associated with that query
532        final Set<Uri> previousQueryUriMap = Sets.newHashSet();
533        for (AccountCacheEntry entry : accountList) {
534            if (accountsQueryUri.equals(entry.mAccountsQueryUri)) {
535                previousQueryUriMap.add(entry.mAccount.uri);
536            }
537        }
538
539        // Update the internal state of this provider if the returned result set
540        // represents all accounts
541        // TODO: determine what should happen with a heterogeneous set of accounts
542        final Bundle extra = data.getExtras();
543        mAccountsFullyLoaded = extra.getInt(AccountCursorExtraKeys.ACCOUNTS_LOADED) != 0;
544
545        final Set<Uri> newQueryUriMap = Sets.newHashSet();
546        while (data.moveToNext()) {
547            final Account account = new Account(data);
548            final Uri accountUri = account.uri;
549            newQueryUriMap.add(accountUri);
550            addAccountImpl(account, accountsQueryUri, false /* don't notify */);
551        }
552
553        if (previousQueryUriMap != null) {
554            // Remove all of the accounts that are in the new result set
555            previousQueryUriMap.removeAll(newQueryUriMap);
556
557            // For all of the entries that had been in the previous result set, and are not
558            // in the new result set, remove them from the cache
559            if (previousQueryUriMap.size() > 0 && mAccountsFullyLoaded) {
560              removeAccounts(previousQueryUriMap, false /* don't notify */);
561            }
562        }
563        broadcastAccountChange();
564    }
565
566    /**
567     * Object that allows the Account Cache provider to associate the account with the content
568     * provider uri that originated that account.
569     */
570    private static class AccountCacheEntry {
571        final Account mAccount;
572        final Uri mAccountsQueryUri;
573
574        private static final String ACCOUNT_ENTRY_COMPONENT_SEPARATOR = "^**^";
575        private static final Pattern ACCOUNT_ENTRY_COMPONENT_SEPARATOR_PATTERN =
576                Pattern.compile("\\^\\*\\*\\^");
577
578        private static final int NUMBER_MEMBERS = 2;
579
580        public AccountCacheEntry(Account account, Uri accountQueryUri) {
581            mAccount = account;
582            mAccountsQueryUri = accountQueryUri;
583        }
584
585        /**
586         * Return a serialized String for this AccountCacheEntry.
587         */
588        public synchronized String serialize() {
589            StringBuilder out = new StringBuilder();
590            out.append(mAccount.serialize()).append(ACCOUNT_ENTRY_COMPONENT_SEPARATOR);
591            final String accountQueryUri =
592                    mAccountsQueryUri != null ? mAccountsQueryUri.toString() : "";
593            out.append(accountQueryUri);
594            return out.toString();
595        }
596
597        /**
598         * Create an account cache object from a serialized string previously stored away.
599         * If the serializedString does not parse as a valid account, we throw an
600         * {@link IllegalArgumentException}. The caller is responsible for checking this and
601         * ignoring the newly created object if the exception is thrown.
602         * @param serializedString
603         */
604        public AccountCacheEntry(String serializedString) throws IllegalArgumentException {
605            String[] cacheEntryMembers = TextUtils.split(serializedString,
606                    ACCOUNT_ENTRY_COMPONENT_SEPARATOR_PATTERN);
607            if (cacheEntryMembers.length != NUMBER_MEMBERS) {
608                throw new IllegalArgumentException("AccountCacheEntry de-serializing failed. "
609                        + "Wrong number of members detected. "
610                        + cacheEntryMembers.length + " detected");
611            }
612            mAccount = Account.newinstance(cacheEntryMembers[0]);
613            if (mAccount == null) {
614                throw new IllegalArgumentException("AccountCacheEntry de-serializing failed. "
615                        + "Account object could not be created from the serialized string: "
616                        + serializedString);
617            }
618            if (mAccount.settings == Settings.EMPTY_SETTINGS) {
619                throw new IllegalArgumentException("AccountCacheEntry de-serializing failed. "
620                        + "Settings could not be created from the string: " + serializedString);
621            }
622            mAccountsQueryUri = !TextUtils.isEmpty(cacheEntryMembers[1]) ?
623                    Uri.parse(cacheEntryMembers[1]) : null;
624        }
625    }
626}
627