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