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