• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2009 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 
18 package com.android.emailcommon.provider;
19 
20 import android.content.ContentProviderOperation;
21 import android.content.ContentResolver;
22 import android.content.ContentUris;
23 import android.content.ContentValues;
24 import android.content.Context;
25 import android.content.OperationApplicationException;
26 import android.database.Cursor;
27 import android.net.Uri;
28 import android.os.Bundle;
29 import android.os.Parcel;
30 import android.os.Parcelable;
31 import android.os.RemoteException;
32 import android.provider.CalendarContract;
33 import android.provider.ContactsContract;
34 import android.text.TextUtils;
35 import android.util.SparseBooleanArray;
36 
37 import com.android.emailcommon.Logging;
38 import com.android.emailcommon.R;
39 import com.android.emailcommon.provider.EmailContent.MailboxColumns;
40 import com.android.emailcommon.utility.Utility;
41 import com.android.mail.utils.LogUtils;
42 
43 import java.util.ArrayList;
44 
45 public 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 
initMailbox()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 
formatMailboxIdExtra(final int index)88     private static String formatMailboxIdExtra(final int index) {
89         return String.format(SYNC_EXTRA_MAILBOX_ID_PATTERN, index);
90     }
91 
createSyncBundle(final ArrayList<Long> mailboxIds)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 
createSyncBundle(final long[] mailboxIds)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 
createSyncBundle(final long mailboxId)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 
getMailboxIdsFromBundle(Bundle bundle)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 
isAccountOnlyExtras(Bundle bundle)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 
isPushOnlyExtras(Bundle bundle)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);
SYNCABLE_TYPES.put(TYPE_INBOX, true)348         SYNCABLE_TYPES.put(TYPE_INBOX, true);
SYNCABLE_TYPES.put(TYPE_MAIL, false)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);
SYNCABLE_TYPES.put(TYPE_SENT, true)353         SYNCABLE_TYPES.put(TYPE_SENT, true);
SYNCABLE_TYPES.put(TYPE_TRASH, false)354         SYNCABLE_TYPES.put(TYPE_TRASH, false);
SYNCABLE_TYPES.put(TYPE_CALENDAR, true)355         SYNCABLE_TYPES.put(TYPE_CALENDAR, true);
SYNCABLE_TYPES.put(TYPE_CONTACTS, 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 
Mailbox()412     public Mailbox() {
413         mBaseUri = CONTENT_URI;
414     }
415 
getSystemMailboxName(Context context, int mailboxType)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      */
restoreMailboxWithId(Context context, long id)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      */
newSystemMailbox(Context context, long accountId, int mailboxType)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      */
restoreMailboxForPath(Context context, long accountId, String path)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      */
getMailboxForPath(Context context, long accountId, String path)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      */
isSyncableType(final int mailboxType)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      */
getDefaultSyncStateForType(final int mailboxType)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      */
isSyncable()570     public boolean isSyncable() {
571         return (mTotalCount >= 0) && isSyncableType(mType);
572     }
573 
574     @Override
restore(Cursor cursor)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
toContentValues()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      */
updateMessageCount(final Context c, final int count)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      */
updateLastFullSyncTime(final Context c, final long syncTime)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      */
findMailboxOfType(Context context, long accountId, int type)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      */
restoreMailboxOfType(Context context, long accountId, int type)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      */
getMailboxForMessageId(Context context, long messageId)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      */
getMailboxType(Context context, long mailboxId)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      */
getDisplayName(Context context, long mailboxId)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 
getMailboxMessageCount(Context c, long mailboxId)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      */
isRefreshable(Context context, long mailboxId)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      */
canHaveMessagesMoved()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      */
getHashes()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
describeContents()814     public int describeContents() {
815         return 0;
816     }
817 
818     // Parcelable
819     @Override
writeToParcel(Parcel dest, int flags)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 
Mailbox(Parcel in)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
toString()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      */
getMailboxesForPush(final ContentResolver cr, final long accountId)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      */
getMailboxIdsForSync(final ContentResolver cr, final long accountId)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      */
getMailboxIdsForSyncByType(final ContentResolver cr, final long accountId, final int mailboxType)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      */
getAccountIdForMailbox(final Context context, final String mailboxId)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      */
getAuthority(final int mailboxType)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 
resyncMailbox( final ContentResolver cr, final android.accounts.Account account, final long mailboxId)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