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