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