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