1 /* 2 * Copyright (C) 2009 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 18 package com.android.emailcommon.provider; 19 20 import android.content.ContentProviderOperation; 21 import android.content.ContentResolver; 22 import android.content.ContentUris; 23 import android.content.ContentValues; 24 import android.content.Context; 25 import android.content.OperationApplicationException; 26 import android.database.Cursor; 27 import android.net.Uri; 28 import android.os.Bundle; 29 import android.os.Parcel; 30 import android.os.Parcelable; 31 import android.os.RemoteException; 32 import android.provider.CalendarContract; 33 import android.provider.ContactsContract; 34 import android.text.TextUtils; 35 import android.util.SparseBooleanArray; 36 37 import com.android.emailcommon.Logging; 38 import com.android.emailcommon.R; 39 import com.android.emailcommon.utility.Utility; 40 import com.android.mail.utils.LogUtils; 41 42 import java.util.ArrayList; 43 44 public class Mailbox extends EmailContent implements EmailContent.MailboxColumns, Parcelable { 45 /** 46 * Sync extras key when syncing one or more mailboxes to specify how many 47 * mailboxes are included in the extra. 48 */ 49 public static final String SYNC_EXTRA_MAILBOX_COUNT = "__mailboxCount__"; 50 /** 51 * Sync extras key pattern when syncing one or more mailboxes to specify 52 * which mailbox to sync. Is intentionally private, we have helper functions 53 * to set up an appropriate bundle, or read its contents. 54 */ 55 private static final String SYNC_EXTRA_MAILBOX_ID_PATTERN = "__mailboxId%d__"; 56 /** 57 * Sync extra key indicating that we are doing a sync of the folder structure for an account. 58 */ 59 public static final String SYNC_EXTRA_ACCOUNT_ONLY = "__account_only__"; 60 /** 61 * Sync extra key indicating that we are only starting a ping. 62 */ 63 public static final String SYNC_EXTRA_PUSH_ONLY = "__push_only__"; 64 65 /** 66 * Sync extras key to specify that only a specific mailbox type should be synced. 67 */ 68 public static final String SYNC_EXTRA_MAILBOX_TYPE = "__mailboxType__"; 69 /** 70 * Sync extras key when syncing a mailbox to specify how many additional messages to sync. 71 */ 72 public static final String SYNC_EXTRA_DELTA_MESSAGE_COUNT = "__deltaMessageCount__"; 73 74 public static final String SYNC_EXTRA_NOOP = "__noop__"; 75 76 public static final String TABLE_NAME = "Mailbox"; 77 78 79 public static Uri CONTENT_URI; 80 public static Uri MESSAGE_COUNT_URI; 81 initMailbox()82 public static void initMailbox() { 83 CONTENT_URI = Uri.parse(EmailContent.CONTENT_URI + "/mailbox"); 84 MESSAGE_COUNT_URI = Uri.parse(EmailContent.CONTENT_URI + "/mailboxCount"); 85 } 86 formatMailboxIdExtra(final int index)87 private static String formatMailboxIdExtra(final int index) { 88 return String.format(SYNC_EXTRA_MAILBOX_ID_PATTERN, index); 89 } 90 createSyncBundle(final ArrayList<Long> mailboxIds)91 public static Bundle createSyncBundle(final ArrayList<Long> mailboxIds) { 92 final Bundle bundle = new Bundle(mailboxIds.size() + 1); 93 bundle.putInt(SYNC_EXTRA_MAILBOX_COUNT, mailboxIds.size()); 94 for (int i = 0; i < mailboxIds.size(); i++) { 95 bundle.putLong(formatMailboxIdExtra(i), mailboxIds.get(i)); 96 } 97 return bundle; 98 } 99 createSyncBundle(final long[] mailboxIds)100 public static Bundle createSyncBundle(final long[] mailboxIds) { 101 final Bundle bundle = new Bundle(mailboxIds.length + 1); 102 bundle.putInt(SYNC_EXTRA_MAILBOX_COUNT, mailboxIds.length); 103 for (int i = 0; i < mailboxIds.length; i++) { 104 bundle.putLong(formatMailboxIdExtra(i), mailboxIds[i]); 105 } 106 return bundle; 107 } 108 createSyncBundle(final long mailboxId)109 public static Bundle createSyncBundle(final long mailboxId) { 110 final Bundle bundle = new Bundle(2); 111 bundle.putInt(SYNC_EXTRA_MAILBOX_COUNT, 1); 112 bundle.putLong(formatMailboxIdExtra(0), mailboxId); 113 return bundle; 114 } 115 getMailboxIdsFromBundle(Bundle bundle)116 public static long[] getMailboxIdsFromBundle(Bundle bundle) { 117 final int count = bundle.getInt(SYNC_EXTRA_MAILBOX_COUNT, 0); 118 if (count > 0) { 119 if (bundle.getBoolean(SYNC_EXTRA_PUSH_ONLY, false)) { 120 LogUtils.w(Logging.LOG_TAG, "Mailboxes specified in a push only sync"); 121 } 122 if (bundle.getBoolean(SYNC_EXTRA_ACCOUNT_ONLY, false)) { 123 LogUtils.w(Logging.LOG_TAG, "Mailboxes specified in an account only sync"); 124 } 125 final long [] result = new long[count]; 126 for (int i = 0; i < count; i++) { 127 result[i] = bundle.getLong(formatMailboxIdExtra(i), 0); 128 } 129 130 return result; 131 } else { 132 return null; 133 } 134 } 135 isAccountOnlyExtras(Bundle bundle)136 public static boolean isAccountOnlyExtras(Bundle bundle) { 137 final boolean result = bundle.getBoolean(SYNC_EXTRA_ACCOUNT_ONLY, false); 138 if (result) { 139 final int count = bundle.getInt(SYNC_EXTRA_MAILBOX_COUNT, 0); 140 if (count != 0) { 141 LogUtils.w(Logging.LOG_TAG, "Mailboxes specified in an account only sync"); 142 } 143 } 144 return result; 145 } 146 isPushOnlyExtras(Bundle bundle)147 public static boolean isPushOnlyExtras(Bundle bundle) { 148 final boolean result = bundle.getBoolean(SYNC_EXTRA_PUSH_ONLY, false); 149 if (result) { 150 final int count = bundle.getInt(SYNC_EXTRA_MAILBOX_COUNT, 0); 151 if (count != 0) { 152 LogUtils.w(Logging.LOG_TAG, "Mailboxes specified in a push only sync"); 153 } 154 } 155 return result; 156 } 157 158 public String mDisplayName; 159 public String mServerId; 160 public String mParentServerId; 161 public long mParentKey; 162 public long mAccountKey; 163 public int mType; 164 public int mDelimiter; 165 public String mSyncKey; 166 public int mSyncLookback; 167 public int mSyncInterval; 168 public long mSyncTime; 169 public boolean mFlagVisible = true; 170 public int mFlags; 171 public String mSyncStatus; 172 public long mLastTouchedTime; 173 public int mUiSyncStatus; 174 public int mUiLastSyncResult; 175 public int mTotalCount; 176 public String mHierarchicalName; 177 public long mLastFullSyncTime; 178 179 public static final int CONTENT_ID_COLUMN = 0; 180 public static final int CONTENT_DISPLAY_NAME_COLUMN = 1; 181 public static final int CONTENT_SERVER_ID_COLUMN = 2; 182 public static final int CONTENT_PARENT_SERVER_ID_COLUMN = 3; 183 public static final int CONTENT_ACCOUNT_KEY_COLUMN = 4; 184 public static final int CONTENT_TYPE_COLUMN = 5; 185 public static final int CONTENT_DELIMITER_COLUMN = 6; 186 public static final int CONTENT_SYNC_KEY_COLUMN = 7; 187 public static final int CONTENT_SYNC_LOOKBACK_COLUMN = 8; 188 public static final int CONTENT_SYNC_INTERVAL_COLUMN = 9; 189 public static final int CONTENT_SYNC_TIME_COLUMN = 10; 190 public static final int CONTENT_FLAG_VISIBLE_COLUMN = 11; 191 public static final int CONTENT_FLAGS_COLUMN = 12; 192 public static final int CONTENT_SYNC_STATUS_COLUMN = 13; 193 public static final int CONTENT_PARENT_KEY_COLUMN = 14; 194 public static final int CONTENT_LAST_TOUCHED_TIME_COLUMN = 15; 195 public static final int CONTENT_UI_SYNC_STATUS_COLUMN = 16; 196 public static final int CONTENT_UI_LAST_SYNC_RESULT_COLUMN = 17; 197 public static final int CONTENT_TOTAL_COUNT_COLUMN = 18; 198 public static final int CONTENT_HIERARCHICAL_NAME_COLUMN = 19; 199 public static final int CONTENT_LAST_FULL_SYNC_COLUMN = 20; 200 201 /** 202 * <em>NOTE</em>: If fields are added or removed, the method {@link #getHashes()} 203 * MUST be updated. 204 */ 205 public static final String[] CONTENT_PROJECTION = new String[] { 206 MailboxColumns._ID, 207 MailboxColumns.DISPLAY_NAME, 208 MailboxColumns.SERVER_ID, 209 MailboxColumns.PARENT_SERVER_ID, 210 MailboxColumns.ACCOUNT_KEY, 211 MailboxColumns.TYPE, 212 MailboxColumns.DELIMITER, 213 MailboxColumns.SYNC_KEY, 214 MailboxColumns.SYNC_LOOKBACK, 215 MailboxColumns.SYNC_INTERVAL, 216 MailboxColumns.SYNC_TIME, 217 MailboxColumns.FLAG_VISIBLE, 218 MailboxColumns.FLAGS, 219 MailboxColumns.SYNC_STATUS, 220 MailboxColumns.PARENT_KEY, 221 MailboxColumns.LAST_TOUCHED_TIME, 222 MailboxColumns.UI_SYNC_STATUS, 223 MailboxColumns.UI_LAST_SYNC_RESULT, 224 MailboxColumns.TOTAL_COUNT, 225 MailboxColumns.HIERARCHICAL_NAME, 226 MailboxColumns.LAST_FULL_SYNC_TIME 227 }; 228 229 /** Selection by server pathname for a given account */ 230 public static final String PATH_AND_ACCOUNT_SELECTION = 231 MailboxColumns.SERVER_ID + "=? and " + MailboxColumns.ACCOUNT_KEY + "=?"; 232 233 private static final String[] MAILBOX_TYPE_PROJECTION = new String [] { 234 MailboxColumns.TYPE 235 }; 236 private static final int MAILBOX_TYPE_TYPE_COLUMN = 0; 237 238 private static final String[] MAILBOX_DISPLAY_NAME_PROJECTION = new String [] { 239 MailboxColumns.DISPLAY_NAME 240 }; 241 private static final int MAILBOX_DISPLAY_NAME_COLUMN = 0; 242 243 /** 244 * Projection to use when reading {@link MailboxColumns#ACCOUNT_KEY} for a mailbox. 245 */ 246 private static final String[] ACCOUNT_KEY_PROJECTION = { MailboxColumns.ACCOUNT_KEY }; 247 private static final int ACCOUNT_KEY_PROJECTION_ACCOUNT_KEY_COLUMN = 0; 248 249 /** 250 * Projection for querying data needed during a sync. 251 */ 252 public interface ProjectionSyncData { 253 public static final int COLUMN_SERVER_ID = 0; 254 public static final int COLUMN_SYNC_KEY = 1; 255 256 public static final String[] PROJECTION = { 257 MailboxColumns.SERVER_ID, 258 MailboxColumns.SYNC_KEY 259 }; 260 } 261 262 public static final long NO_MAILBOX = -1; 263 264 // Sentinel values for the mSyncInterval field of both Mailbox records 265 @Deprecated 266 public static final int CHECK_INTERVAL_NEVER = -1; 267 @Deprecated 268 public static final int CHECK_INTERVAL_PUSH = -2; 269 // The following two sentinel values are used by EAS 270 // Ping indicates that the EAS mailbox is synced based on a "ping" from the server 271 @Deprecated 272 public static final int CHECK_INTERVAL_PING = -3; 273 // Push-Hold indicates an EAS push or ping Mailbox shouldn't sync just yet 274 @Deprecated 275 public static final int CHECK_INTERVAL_PUSH_HOLD = -4; 276 277 // Sentinel for PARENT_KEY. Use NO_MAILBOX for toplevel mailboxes (i.e. no parents). 278 public static final long PARENT_KEY_UNINITIALIZED = 0L; 279 280 private static final String WHERE_TYPE_AND_ACCOUNT_KEY = 281 MailboxColumns.TYPE + "=? and " + MailboxColumns.ACCOUNT_KEY + "=?"; 282 283 /** 284 * Selection for mailboxes that should receive push for an account. A mailbox should receive 285 * push if it has a valid, non-initial sync key and is opted in for sync. 286 */ 287 private static final String PUSH_MAILBOXES_FOR_ACCOUNT_SELECTION = 288 MailboxColumns.SYNC_KEY + " is not null and " + MailboxColumns.SYNC_KEY + "!='' and " + 289 MailboxColumns.SYNC_KEY + "!='0' and " + MailboxColumns.SYNC_INTERVAL + 290 "=1 and " + MailboxColumns.ACCOUNT_KEY + "=?"; 291 292 /** Selection for mailboxes that say they want to sync, plus outbox, for an account. */ 293 private static final String OUTBOX_PLUS_SYNCING_AND_ACCOUNT_SELECTION = "(" 294 + MailboxColumns.TYPE + "=" + Mailbox.TYPE_OUTBOX + " or " 295 + MailboxColumns.SYNC_INTERVAL + "=1) and " + MailboxColumns.ACCOUNT_KEY + "=?"; 296 297 /** Selection for mailboxes that are configured for sync of a certain type for an account. */ 298 private static final String SYNCING_AND_TYPE_FOR_ACCOUNT_SELECTION = 299 MailboxColumns.SYNC_INTERVAL + "=1 and " + MailboxColumns.TYPE + "=? and " + 300 MailboxColumns.ACCOUNT_KEY + "=?"; 301 302 // Types of mailboxes. The list is ordered to match a typical UI presentation, e.g. 303 // placing the inbox at the top. 304 // Arrays of "special_mailbox_display_names" and "special_mailbox_icons" are depends on 305 // types Id of mailboxes. 306 /** No type specified */ 307 public static final int TYPE_NONE = -1; 308 /** The "main" mailbox for the account, almost always referred to as "Inbox" */ 309 public static final int TYPE_INBOX = 0; 310 // Types of mailboxes 311 /** Generic mailbox that holds mail */ 312 public static final int TYPE_MAIL = 1; 313 /** Parent-only mailbox; does not hold any mail */ 314 public static final int TYPE_PARENT = 2; 315 /** Drafts mailbox */ 316 public static final int TYPE_DRAFTS = 3; 317 /** Local mailbox associated with the account's outgoing mail */ 318 public static final int TYPE_OUTBOX = 4; 319 /** Sent mail; mail that was sent from the account */ 320 public static final int TYPE_SENT = 5; 321 /** Deleted mail */ 322 public static final int TYPE_TRASH = 6; 323 /** Junk mail */ 324 public static final int TYPE_JUNK = 7; 325 /** Search results */ 326 public static final int TYPE_SEARCH = 8; 327 /** Starred (virtual) */ 328 public static final int TYPE_STARRED = 9; 329 /** All unread mail (virtual) */ 330 public static final int TYPE_UNREAD = 10; 331 332 // Types after this are used for non-mail mailboxes (as in EAS) 333 public static final int TYPE_NOT_EMAIL = 0x40; 334 public static final int TYPE_CALENDAR = 0x41; 335 public static final int TYPE_CONTACTS = 0x42; 336 public static final int TYPE_TASKS = 0x43; 337 @Deprecated 338 public static final int TYPE_EAS_ACCOUNT_MAILBOX = 0x44; 339 public static final int TYPE_UNKNOWN = 0x45; 340 341 /** 342 * Specifies which mailbox types may be synced from server, and what the default sync interval 343 * value should be. 344 * If a mailbox type is in this array, then it can be synced. 345 * If the mailbox type is mapped to true in this array, then new mailboxes of that type should 346 * be set to automatically sync (either with the periodic poll, or with push, as determined 347 * by the account's sync settings). 348 * See {@link #isSyncableType} and {@link #getDefaultSyncStateForType} for how to access this 349 * data. 350 */ 351 private static final SparseBooleanArray SYNCABLE_TYPES; 352 static { 353 SYNCABLE_TYPES = new SparseBooleanArray(7); SYNCABLE_TYPES.put(TYPE_INBOX, true)354 SYNCABLE_TYPES.put(TYPE_INBOX, true); SYNCABLE_TYPES.put(TYPE_MAIL, false)355 SYNCABLE_TYPES.put(TYPE_MAIL, false); 356 // TODO: b/11158759 357 // For now, drafts folders are not syncable. 358 //SYNCABLE_TYPES.put(TYPE_DRAFTS, true); SYNCABLE_TYPES.put(TYPE_SENT, true)359 SYNCABLE_TYPES.put(TYPE_SENT, true); SYNCABLE_TYPES.put(TYPE_TRASH, false)360 SYNCABLE_TYPES.put(TYPE_TRASH, false); SYNCABLE_TYPES.put(TYPE_CALENDAR, true)361 SYNCABLE_TYPES.put(TYPE_CALENDAR, true); SYNCABLE_TYPES.put(TYPE_CONTACTS, true)362 SYNCABLE_TYPES.put(TYPE_CONTACTS, true); 363 } 364 365 public static final int TYPE_NOT_SYNCABLE = 0x100; 366 // A mailbox that holds Messages that are attachments 367 public static final int TYPE_ATTACHMENT = 0x101; 368 369 /** 370 * For each of the following folder types, we expect there to be exactly one folder of that 371 * type per account. 372 * Each sync adapter must do the following: 373 * 1) On initial sync: For each type that was not found from the server, create a local folder. 374 * 2) On folder delete: If it's of a required type, convert it to local rather than delete. 375 * 3) On folder add: If it's of a required type, convert the local folder to server. 376 * 4) When adding a duplicate (either initial sync or folder add): Error. 377 */ 378 public static final int[] REQUIRED_FOLDER_TYPES = 379 { TYPE_INBOX, TYPE_DRAFTS, TYPE_OUTBOX, TYPE_SENT, TYPE_TRASH }; 380 381 // Default "touch" time for system mailboxes 382 public static final int DRAFTS_DEFAULT_TOUCH_TIME = 2; 383 public static final int SENT_DEFAULT_TOUCH_TIME = 1; 384 385 // Bit field flags; each is defined below 386 // Warning: Do not read these flags until POP/IMAP/EAS all populate them 387 /** No flags set */ 388 public static final int FLAG_NONE = 0; 389 /** Has children in the mailbox hierarchy */ 390 public static final int FLAG_HAS_CHILDREN = 1<<0; 391 /** Children are visible in the UI */ 392 public static final int FLAG_CHILDREN_VISIBLE = 1<<1; 393 /** cannot receive "pushed" mail */ 394 public static final int FLAG_CANT_PUSH = 1<<2; 395 /** can hold emails (i.e. some parent mailboxes cannot themselves contain mail) */ 396 public static final int FLAG_HOLDS_MAIL = 1<<3; 397 /** can be used as a target for moving messages within the account */ 398 public static final int FLAG_ACCEPTS_MOVED_MAIL = 1<<4; 399 /** can be used as a target for appending messages */ 400 public static final int FLAG_ACCEPTS_APPENDED_MAIL = 1<<5; 401 /** has user settings (sync lookback, etc.) */ 402 public static final int FLAG_SUPPORTS_SETTINGS = 1<<6; 403 404 // Magic mailbox ID's 405 // NOTE: This is a quick solution for merged mailboxes. I would rather implement this 406 // with a more generic way of packaging and sharing queries between activities 407 public static final long QUERY_ALL_INBOXES = -2; 408 public static final long QUERY_ALL_UNREAD = -3; 409 public static final long QUERY_ALL_FAVORITES = -4; 410 public static final long QUERY_ALL_DRAFTS = -5; 411 public static final long QUERY_ALL_OUTBOX = -6; 412 Mailbox()413 public Mailbox() { 414 mBaseUri = CONTENT_URI; 415 } 416 getSystemMailboxName(Context context, int mailboxType)417 public static String getSystemMailboxName(Context context, int mailboxType) { 418 final int resId; 419 switch (mailboxType) { 420 case Mailbox.TYPE_INBOX: 421 resId = R.string.mailbox_name_server_inbox; 422 break; 423 case Mailbox.TYPE_OUTBOX: 424 resId = R.string.mailbox_name_server_outbox; 425 break; 426 case Mailbox.TYPE_DRAFTS: 427 resId = R.string.mailbox_name_server_drafts; 428 break; 429 case Mailbox.TYPE_TRASH: 430 resId = R.string.mailbox_name_server_trash; 431 break; 432 case Mailbox.TYPE_SENT: 433 resId = R.string.mailbox_name_server_sent; 434 break; 435 case Mailbox.TYPE_JUNK: 436 resId = R.string.mailbox_name_server_junk; 437 break; 438 case Mailbox.TYPE_STARRED: 439 resId = R.string.mailbox_name_server_starred; 440 break; 441 case Mailbox.TYPE_UNREAD: 442 resId = R.string.mailbox_name_server_all_unread; 443 break; 444 default: 445 throw new IllegalArgumentException("Illegal mailbox type"); 446 } 447 return context.getString(resId); 448 } 449 450 /** 451 * Restore a Mailbox from the database, given its unique id 452 * @param context Makes provider calls 453 * @param id Row ID of mailbox to restore 454 * @return the instantiated Mailbox 455 */ restoreMailboxWithId(Context context, long id)456 public static Mailbox restoreMailboxWithId(Context context, long id) { 457 return EmailContent.restoreContentWithId(context, Mailbox.class, 458 Mailbox.CONTENT_URI, Mailbox.CONTENT_PROJECTION, id); 459 } 460 461 /** 462 * Builds a new mailbox with "typical" settings for a system mailbox, such as a local "Drafts" 463 * mailbox. This is useful for protocols like POP3 or IMAP who don't have certain local 464 * system mailboxes synced with the server. 465 * Note: the mailbox is not persisted - clients must call {@link #save} themselves. 466 */ newSystemMailbox(Context context, long accountId, int mailboxType)467 public static Mailbox newSystemMailbox(Context context, long accountId, int mailboxType) { 468 // Sync interval and flags are different based on mailbox type. 469 // TODO: Sync interval doesn't seem to be used anywhere, make it matter or get rid of it. 470 final int syncInterval; 471 final int flags; 472 switch (mailboxType) { 473 case TYPE_INBOX: 474 flags = Mailbox.FLAG_HOLDS_MAIL | Mailbox.FLAG_ACCEPTS_MOVED_MAIL; 475 syncInterval = 0; 476 break; 477 case TYPE_SENT: 478 case TYPE_TRASH: 479 flags = Mailbox.FLAG_HOLDS_MAIL; 480 syncInterval = 0; 481 break; 482 case TYPE_DRAFTS: 483 case TYPE_OUTBOX: 484 flags = Mailbox.FLAG_HOLDS_MAIL; 485 syncInterval = Account.CHECK_INTERVAL_NEVER; 486 break; 487 default: 488 throw new IllegalArgumentException("Bad mailbox type for newSystemMailbox: " + 489 mailboxType); 490 } 491 492 final Mailbox box = new Mailbox(); 493 box.mAccountKey = accountId; 494 box.mType = mailboxType; 495 box.mSyncInterval = syncInterval; 496 box.mFlagVisible = true; 497 // TODO: Fix how display names work. 498 box.mServerId = box.mDisplayName = getSystemMailboxName(context, mailboxType); 499 box.mParentKey = Mailbox.NO_MAILBOX; 500 box.mFlags = flags; 501 return box; 502 } 503 504 /** 505 * Returns a Mailbox from the database, given its pathname and account id. All mailbox 506 * paths for a particular account must be unique. Paths are stored in the column 507 * {@link MailboxColumns#SERVER_ID} for want of yet another column in the table. 508 * @param context Makes provider calls 509 * @param accountId the ID of the account 510 * @param path the fully qualified, remote pathname 511 */ restoreMailboxForPath(Context context, long accountId, String path)512 public static Mailbox restoreMailboxForPath(Context context, long accountId, String path) { 513 final Cursor c = context.getContentResolver().query( 514 Mailbox.CONTENT_URI, 515 Mailbox.CONTENT_PROJECTION, 516 Mailbox.PATH_AND_ACCOUNT_SELECTION, 517 new String[] { path, Long.toString(accountId) }, 518 null); 519 if (c == null) throw new ProviderUnavailableException(); 520 try { 521 Mailbox mailbox = null; 522 if (c.moveToFirst()) { 523 mailbox = getContent(context, c, Mailbox.class); 524 if (c.moveToNext()) { 525 LogUtils.w(Logging.LOG_TAG, "Multiple mailboxes named \"%s\"", path); 526 } 527 } else { 528 LogUtils.i(Logging.LOG_TAG, "Could not find mailbox at \"%s\"", path); 529 } 530 return mailbox; 531 } finally { 532 c.close(); 533 } 534 } 535 536 /** 537 * Returns a {@link Mailbox} for the given path. If the path is not in the database, a new 538 * mailbox will be created. 539 */ getMailboxForPath(Context context, long accountId, String path)540 public static Mailbox getMailboxForPath(Context context, long accountId, String path) { 541 Mailbox mailbox = restoreMailboxForPath(context, accountId, path); 542 if (mailbox == null) { 543 mailbox = new Mailbox(); 544 } 545 return mailbox; 546 } 547 548 /** 549 * Check if a mailbox type can be synced with the server. 550 * @param mailboxType The type to check. 551 * @return Whether this type is syncable. 552 */ isSyncableType(final int mailboxType)553 public static boolean isSyncableType(final int mailboxType) { 554 return SYNCABLE_TYPES.indexOfKey(mailboxType) >= 0; 555 } 556 557 /** 558 * Check if a mailbox type should sync with the server by default. 559 * @param mailboxType The type to check. 560 * @return Whether this type should default to syncing. 561 */ getDefaultSyncStateForType(final int mailboxType)562 public static boolean getDefaultSyncStateForType(final int mailboxType) { 563 return SYNCABLE_TYPES.get(mailboxType); 564 } 565 566 /** 567 * Check whether this mailbox is syncable. It has to be both a server synced mailbox, and 568 * of a syncable able. 569 * @return Whether this mailbox is syncable. 570 */ isSyncable()571 public boolean isSyncable() { 572 return (mTotalCount >= 0) && isSyncableType(mType); 573 } 574 575 @Override restore(Cursor cursor)576 public void restore(Cursor cursor) { 577 mBaseUri = CONTENT_URI; 578 mId = cursor.getLong(CONTENT_ID_COLUMN); 579 mDisplayName = cursor.getString(CONTENT_DISPLAY_NAME_COLUMN); 580 mServerId = cursor.getString(CONTENT_SERVER_ID_COLUMN); 581 mParentServerId = cursor.getString(CONTENT_PARENT_SERVER_ID_COLUMN); 582 mParentKey = cursor.getLong(CONTENT_PARENT_KEY_COLUMN); 583 mAccountKey = cursor.getLong(CONTENT_ACCOUNT_KEY_COLUMN); 584 mType = cursor.getInt(CONTENT_TYPE_COLUMN); 585 mDelimiter = cursor.getInt(CONTENT_DELIMITER_COLUMN); 586 mSyncKey = cursor.getString(CONTENT_SYNC_KEY_COLUMN); 587 mSyncLookback = cursor.getInt(CONTENT_SYNC_LOOKBACK_COLUMN); 588 mSyncInterval = cursor.getInt(CONTENT_SYNC_INTERVAL_COLUMN); 589 mSyncTime = cursor.getLong(CONTENT_SYNC_TIME_COLUMN); 590 mFlagVisible = cursor.getInt(CONTENT_FLAG_VISIBLE_COLUMN) == 1; 591 mFlags = cursor.getInt(CONTENT_FLAGS_COLUMN); 592 mSyncStatus = cursor.getString(CONTENT_SYNC_STATUS_COLUMN); 593 mLastTouchedTime = cursor.getLong(CONTENT_LAST_TOUCHED_TIME_COLUMN); 594 mUiSyncStatus = cursor.getInt(CONTENT_UI_SYNC_STATUS_COLUMN); 595 mUiLastSyncResult = cursor.getInt(CONTENT_UI_LAST_SYNC_RESULT_COLUMN); 596 mTotalCount = cursor.getInt(CONTENT_TOTAL_COUNT_COLUMN); 597 mHierarchicalName = cursor.getString(CONTENT_HIERARCHICAL_NAME_COLUMN); 598 mLastFullSyncTime = cursor.getInt(CONTENT_LAST_FULL_SYNC_COLUMN); 599 } 600 601 @Override toContentValues()602 public ContentValues toContentValues() { 603 final ContentValues values = new ContentValues(20); 604 values.put(MailboxColumns.DISPLAY_NAME, mDisplayName); 605 values.put(MailboxColumns.SERVER_ID, mServerId); 606 values.put(MailboxColumns.PARENT_SERVER_ID, mParentServerId); 607 values.put(MailboxColumns.PARENT_KEY, mParentKey); 608 values.put(MailboxColumns.ACCOUNT_KEY, mAccountKey); 609 values.put(MailboxColumns.TYPE, mType); 610 values.put(MailboxColumns.DELIMITER, mDelimiter); 611 values.put(MailboxColumns.SYNC_KEY, mSyncKey); 612 values.put(MailboxColumns.SYNC_LOOKBACK, mSyncLookback); 613 values.put(MailboxColumns.SYNC_INTERVAL, mSyncInterval); 614 values.put(MailboxColumns.SYNC_TIME, mSyncTime); 615 values.put(MailboxColumns.FLAG_VISIBLE, mFlagVisible); 616 values.put(MailboxColumns.FLAGS, mFlags); 617 values.put(MailboxColumns.SYNC_STATUS, mSyncStatus); 618 values.put(MailboxColumns.LAST_TOUCHED_TIME, mLastTouchedTime); 619 values.put(MailboxColumns.UI_SYNC_STATUS, mUiSyncStatus); 620 values.put(MailboxColumns.UI_LAST_SYNC_RESULT, mUiLastSyncResult); 621 values.put(MailboxColumns.TOTAL_COUNT, mTotalCount); 622 values.put(MailboxColumns.HIERARCHICAL_NAME, mHierarchicalName); 623 values.put(MailboxColumns.LAST_FULL_SYNC_TIME, mLastFullSyncTime); 624 return values; 625 } 626 627 /** 628 * Store the updated message count in the database. 629 * @param c Makes provider calls 630 * @param count New count 631 */ updateMessageCount(final Context c, final int count)632 public void updateMessageCount(final Context c, final int count) { 633 if (count != mTotalCount) { 634 final ContentValues values = new ContentValues(1); 635 values.put(MailboxColumns.TOTAL_COUNT, count); 636 update(c, values); 637 mTotalCount = count; 638 } 639 } 640 641 /** 642 * Store the last full sync time in the database. 643 * @param c Makes provider calls 644 * @param syncTime New syncTime 645 */ updateLastFullSyncTime(final Context c, final long syncTime)646 public void updateLastFullSyncTime(final Context c, final long syncTime) { 647 if (syncTime != mLastFullSyncTime) { 648 final ContentValues values = new ContentValues(1); 649 values.put(MailboxColumns.LAST_FULL_SYNC_TIME, syncTime); 650 update(c, values); 651 mLastFullSyncTime = syncTime; 652 } 653 } 654 655 /** 656 * Convenience method to return the id of a given type of Mailbox for a given Account; the 657 * common Mailbox types (Inbox, Outbox, Sent, Drafts, Trash, and Search) are all cached by 658 * EmailProvider; therefore, we warn if the mailbox is not found in the cache 659 * 660 * @param context the caller's context, used to get a ContentResolver 661 * @param accountId the id of the account to be queried 662 * @param type the mailbox type, as defined above 663 * @return the id of the mailbox, or -1 if not found 664 */ findMailboxOfType(Context context, long accountId, int type)665 public static long findMailboxOfType(Context context, long accountId, int type) { 666 final String[] bindArguments = new String[] {Long.toString(type), Long.toString(accountId)}; 667 return Utility.getFirstRowLong(context, Mailbox.CONTENT_URI, 668 ID_PROJECTION, WHERE_TYPE_AND_ACCOUNT_KEY, bindArguments, null, 669 ID_PROJECTION_COLUMN, NO_MAILBOX); 670 } 671 672 /** 673 * Convenience method that returns the mailbox found using the method above 674 */ restoreMailboxOfType(Context context, long accountId, int type)675 public static Mailbox restoreMailboxOfType(Context context, long accountId, int type) { 676 final long mailboxId = findMailboxOfType(context, accountId, type); 677 if (mailboxId != Mailbox.NO_MAILBOX) { 678 return Mailbox.restoreMailboxWithId(context, mailboxId); 679 } 680 return null; 681 } 682 683 /** 684 * Return the mailbox for a message with a given id 685 * @param context the caller's context 686 * @param messageId the id of the message 687 * @return the mailbox, or null if the mailbox doesn't exist 688 */ getMailboxForMessageId(Context context, long messageId)689 public static Mailbox getMailboxForMessageId(Context context, long messageId) { 690 final long mailboxId = Message.getKeyColumnLong(context, messageId, 691 MessageColumns.MAILBOX_KEY); 692 if (mailboxId != -1) { 693 return Mailbox.restoreMailboxWithId(context, mailboxId); 694 } 695 return null; 696 } 697 698 /** 699 * @return mailbox type, or -1 if mailbox not found. 700 */ getMailboxType(Context context, long mailboxId)701 public static int getMailboxType(Context context, long mailboxId) { 702 final Uri url = ContentUris.withAppendedId(Mailbox.CONTENT_URI, mailboxId); 703 return Utility.getFirstRowInt(context, url, MAILBOX_TYPE_PROJECTION, 704 null, null, null, MAILBOX_TYPE_TYPE_COLUMN, -1); 705 } 706 707 /** 708 * @return mailbox display name, or null if mailbox not found. 709 */ getDisplayName(Context context, long mailboxId)710 public static String getDisplayName(Context context, long mailboxId) { 711 final Uri url = ContentUris.withAppendedId(Mailbox.CONTENT_URI, mailboxId); 712 return Utility.getFirstRowString(context, url, MAILBOX_DISPLAY_NAME_PROJECTION, 713 null, null, null, MAILBOX_DISPLAY_NAME_COLUMN); 714 } 715 716 /** 717 * @param mailboxId ID of a mailbox. This method accepts magic mailbox IDs, such as 718 * {@link #QUERY_ALL_INBOXES}. (They're all non-refreshable.) 719 * @return true if a mailbox is refreshable. 720 */ isRefreshable(Context context, long mailboxId)721 public static boolean isRefreshable(Context context, long mailboxId) { 722 if (mailboxId < 0) { 723 return false; // magic mailboxes 724 } 725 switch (getMailboxType(context, mailboxId)) { 726 case -1: // not found 727 case TYPE_DRAFTS: 728 case TYPE_OUTBOX: 729 return false; 730 } 731 return true; 732 } 733 734 /** 735 * @return whether or not this mailbox supports moving messages out of it 736 */ canHaveMessagesMoved()737 public boolean canHaveMessagesMoved() { 738 switch (mType) { 739 case TYPE_INBOX: 740 case TYPE_MAIL: 741 case TYPE_TRASH: 742 case TYPE_JUNK: 743 return true; 744 } 745 return false; // TYPE_DRAFTS, TYPE_OUTBOX, TYPE_SENT, etc 746 } 747 748 /** 749 * Returns a set of hashes that can identify this mailbox. These can be used to 750 * determine if any of the fields have been modified. 751 */ getHashes()752 public Object[] getHashes() { 753 final Object[] hash = new Object[CONTENT_PROJECTION.length]; 754 755 hash[CONTENT_ID_COLUMN] 756 = mId; 757 hash[CONTENT_DISPLAY_NAME_COLUMN] 758 = mDisplayName; 759 hash[CONTENT_SERVER_ID_COLUMN] 760 = mServerId; 761 hash[CONTENT_PARENT_SERVER_ID_COLUMN] 762 = mParentServerId; 763 hash[CONTENT_ACCOUNT_KEY_COLUMN] 764 = mAccountKey; 765 hash[CONTENT_TYPE_COLUMN] 766 = mType; 767 hash[CONTENT_DELIMITER_COLUMN] 768 = mDelimiter; 769 hash[CONTENT_SYNC_KEY_COLUMN] 770 = mSyncKey; 771 hash[CONTENT_SYNC_LOOKBACK_COLUMN] 772 = mSyncLookback; 773 hash[CONTENT_SYNC_INTERVAL_COLUMN] 774 = mSyncInterval; 775 hash[CONTENT_SYNC_TIME_COLUMN] 776 = mSyncTime; 777 hash[CONTENT_FLAG_VISIBLE_COLUMN] 778 = mFlagVisible; 779 hash[CONTENT_FLAGS_COLUMN] 780 = mFlags; 781 hash[CONTENT_SYNC_STATUS_COLUMN] 782 = mSyncStatus; 783 hash[CONTENT_PARENT_KEY_COLUMN] 784 = mParentKey; 785 hash[CONTENT_LAST_TOUCHED_TIME_COLUMN] 786 = mLastTouchedTime; 787 hash[CONTENT_UI_SYNC_STATUS_COLUMN] 788 = mUiSyncStatus; 789 hash[CONTENT_UI_LAST_SYNC_RESULT_COLUMN] 790 = mUiLastSyncResult; 791 hash[CONTENT_TOTAL_COUNT_COLUMN] 792 = mTotalCount; 793 hash[CONTENT_HIERARCHICAL_NAME_COLUMN] 794 = mHierarchicalName; 795 return hash; 796 } 797 798 // Parcelable 799 @Override describeContents()800 public int describeContents() { 801 return 0; 802 } 803 804 // Parcelable 805 @Override writeToParcel(Parcel dest, int flags)806 public void writeToParcel(Parcel dest, int flags) { 807 dest.writeParcelable(mBaseUri, flags); 808 dest.writeLong(mId); 809 dest.writeString(mDisplayName); 810 dest.writeString(mServerId); 811 dest.writeString(mParentServerId); 812 dest.writeLong(mParentKey); 813 dest.writeLong(mAccountKey); 814 dest.writeInt(mType); 815 dest.writeInt(mDelimiter); 816 dest.writeString(mSyncKey); 817 dest.writeInt(mSyncLookback); 818 dest.writeInt(mSyncInterval); 819 dest.writeLong(mSyncTime); 820 dest.writeInt(mFlagVisible ? 1 : 0); 821 dest.writeInt(mFlags); 822 dest.writeString(mSyncStatus); 823 dest.writeLong(mLastTouchedTime); 824 dest.writeInt(mUiSyncStatus); 825 dest.writeInt(mUiLastSyncResult); 826 dest.writeInt(mTotalCount); 827 dest.writeString(mHierarchicalName); 828 dest.writeLong(mLastFullSyncTime); 829 } 830 Mailbox(Parcel in)831 public Mailbox(Parcel in) { 832 mBaseUri = in.readParcelable(null); 833 mId = in.readLong(); 834 mDisplayName = in.readString(); 835 mServerId = in.readString(); 836 mParentServerId = in.readString(); 837 mParentKey = in.readLong(); 838 mAccountKey = in.readLong(); 839 mType = in.readInt(); 840 mDelimiter = in.readInt(); 841 mSyncKey = in.readString(); 842 mSyncLookback = in.readInt(); 843 mSyncInterval = in.readInt(); 844 mSyncTime = in.readLong(); 845 mFlagVisible = in.readInt() == 1; 846 mFlags = in.readInt(); 847 mSyncStatus = in.readString(); 848 mLastTouchedTime = in.readLong(); 849 mUiSyncStatus = in.readInt(); 850 mUiLastSyncResult = in.readInt(); 851 mTotalCount = in.readInt(); 852 mHierarchicalName = in.readString(); 853 mLastFullSyncTime = in.readLong(); 854 } 855 856 public static final Parcelable.Creator<Mailbox> CREATOR = new Parcelable.Creator<Mailbox>() { 857 @Override 858 public Mailbox createFromParcel(Parcel source) { 859 return new Mailbox(source); 860 } 861 862 @Override 863 public Mailbox[] newArray(int size) { 864 return new Mailbox[size]; 865 } 866 }; 867 868 @Override toString()869 public String toString() { 870 return "[Mailbox " + mId + ": " + mDisplayName + "]"; 871 } 872 873 /** 874 * Get the mailboxes that should receive push updates for an account. 875 * @param cr The {@link ContentResolver}. 876 * @param accountId The id for the account that is pushing. 877 * @return A cursor (suitable for use with {@link #restore}) with all mailboxes we should sync. 878 */ getMailboxesForPush(final ContentResolver cr, final long accountId)879 public static Cursor getMailboxesForPush(final ContentResolver cr, final long accountId) { 880 return cr.query(Mailbox.CONTENT_URI, Mailbox.CONTENT_PROJECTION, 881 PUSH_MAILBOXES_FOR_ACCOUNT_SELECTION, new String[] { Long.toString(accountId) }, 882 null); 883 } 884 885 /** 886 * Get the mailbox ids for an account that should sync when we do a full account sync. 887 * @param cr The {@link ContentResolver}. 888 * @param accountId The id for the account that is pushing. 889 * @return A cursor (with one column, containing ids) with all mailbox ids we should sync. 890 */ getMailboxIdsForSync(final ContentResolver cr, final long accountId)891 public static Cursor getMailboxIdsForSync(final ContentResolver cr, final long accountId) { 892 // We're sorting by mailbox type. The reason is that the inbox is type 0, other types 893 // (e.g. Calendar and Contacts) are all higher numbers. Upon initial sync, we'd like to 894 // sync the inbox first to improve perceived performance. 895 return cr.query(Mailbox.CONTENT_URI, Mailbox.ID_PROJECTION, 896 OUTBOX_PLUS_SYNCING_AND_ACCOUNT_SELECTION, 897 new String[] { Long.toString(accountId) }, MailboxColumns.TYPE + " ASC"); 898 } 899 900 /** 901 * Get the mailbox ids for an account that are configured for sync and have a specific type. 902 * @param accountId The id for the account that is syncing. 903 * @param mailboxType The type of the mailbox we're interested in. 904 * @return A cursor (with one column, containing ids) with all mailbox ids that match. 905 */ getMailboxIdsForSyncByType(final ContentResolver cr, final long accountId, final int mailboxType)906 public static Cursor getMailboxIdsForSyncByType(final ContentResolver cr, final long accountId, 907 final int mailboxType) { 908 return cr.query(Mailbox.CONTENT_URI, Mailbox.ID_PROJECTION, 909 SYNCING_AND_TYPE_FOR_ACCOUNT_SELECTION, 910 new String[] { Integer.toString(mailboxType), Long.toString(accountId) }, null); 911 } 912 913 /** 914 * Get the account id for a mailbox. 915 * @param context The {@link Context}. 916 * @param mailboxId The id of the mailbox we're interested in, as a {@link String}. 917 * @return The account id for the mailbox, or {@link Account#NO_ACCOUNT} if the mailbox doesn't 918 * exist. 919 */ getAccountIdForMailbox(final Context context, final String mailboxId)920 public static long getAccountIdForMailbox(final Context context, final String mailboxId) { 921 return Utility.getFirstRowLong(context, 922 Mailbox.CONTENT_URI.buildUpon().appendEncodedPath(mailboxId).build(), 923 ACCOUNT_KEY_PROJECTION, null, null, null, 924 ACCOUNT_KEY_PROJECTION_ACCOUNT_KEY_COLUMN, Account.NO_ACCOUNT); 925 } 926 927 /** 928 * Gets the correct authority for a mailbox. 929 * @param mailboxType The type of the mailbox we're interested in. 930 * @return The authority for the mailbox we're interested in. 931 */ getAuthority(final int mailboxType)932 public static String getAuthority(final int mailboxType) { 933 switch (mailboxType) { 934 case Mailbox.TYPE_CALENDAR: 935 return CalendarContract.AUTHORITY; 936 case Mailbox.TYPE_CONTACTS: 937 return ContactsContract.AUTHORITY; 938 default: 939 return EmailContent.AUTHORITY; 940 } 941 } 942 resyncMailbox(final ContentResolver cr, final android.accounts.Account account, final long mailboxId)943 public static void resyncMailbox(final ContentResolver cr, 944 final android.accounts.Account account, final long mailboxId) { 945 final Cursor cursor = cr.query(Mailbox.CONTENT_URI, 946 new String[]{ 947 MailboxColumns.TYPE, 948 MailboxColumns.SERVER_ID, 949 }, 950 MailboxColumns._ID + "=?", 951 new String[] {String.valueOf(mailboxId)}, 952 null); 953 if (cursor == null || cursor.getCount() == 0) { 954 LogUtils.w(Logging.LOG_TAG, "Mailbox %d not found", mailboxId); 955 return; 956 } 957 try { 958 cursor.moveToFirst(); 959 final int type = cursor.getInt(0); 960 if (type >= TYPE_NOT_EMAIL) { 961 throw new IllegalArgumentException( 962 String.format("Mailbox %d is not an Email mailbox", mailboxId)); 963 } 964 final String serverId = cursor.getString(1); 965 if (TextUtils.isEmpty(serverId)) { 966 throw new IllegalArgumentException( 967 String.format("Mailbox %d has no server id", mailboxId)); 968 } 969 final ArrayList<ContentProviderOperation> ops = 970 new ArrayList<ContentProviderOperation>(); 971 ops.add(ContentProviderOperation.newDelete(Message.CONTENT_URI) 972 .withSelection(Message.MAILBOX_SELECTION, 973 new String[]{String.valueOf(mailboxId)}) 974 .build()); 975 ops.add(ContentProviderOperation.newUpdate( 976 ContentUris.withAppendedId(Mailbox.CONTENT_URI, mailboxId)) 977 .withValue(MailboxColumns.SYNC_KEY, "0").build()); 978 979 cr.applyBatch(AUTHORITY, ops); 980 final Bundle extras = createSyncBundle(mailboxId); 981 extras.putBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS, true); 982 ContentResolver.requestSync(account, AUTHORITY, extras); 983 LogUtils.i(Logging.LOG_TAG, "requestSync resyncMailbox %s, %s", 984 account.toString(), extras.toString()); 985 } catch (RemoteException e) { 986 LogUtils.w(Logging.LOG_TAG, e, "Failed to wipe mailbox %d", mailboxId); 987 } catch (OperationApplicationException e) { 988 LogUtils.w(Logging.LOG_TAG, e, "Failed to wipe mailbox %d", mailboxId); 989 } finally { 990 cursor.close(); 991 } 992 } 993 } 994