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