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