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