Home | History | Annotate | Download | only in provider
      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