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
17package com.android.emailcommon.provider;
18
19import android.content.ContentProviderOperation;
20import android.content.ContentProviderResult;
21import android.content.ContentResolver;
22import android.content.ContentUris;
23import android.content.ContentValues;
24import android.content.Context;
25import android.content.OperationApplicationException;
26import android.content.res.Resources;
27import android.database.Cursor;
28import android.net.Uri;
29import android.os.Environment;
30import android.os.Parcel;
31import android.os.Parcelable;
32import android.os.RemoteException;
33
34import com.android.emailcommon.utility.TextUtilities;
35import com.android.emailcommon.utility.Utility;
36import com.android.emailcommon.R;
37import com.android.mail.providers.UIProvider;
38import com.android.mail.utils.LogUtils;
39import com.google.common.annotations.VisibleForTesting;
40
41import java.io.File;
42import java.util.ArrayList;
43
44
45/**
46 * EmailContent is the superclass of the various classes of content stored by EmailProvider.
47 *
48 * It is intended to include 1) column definitions for use with the Provider, and 2) convenience
49 * methods for saving and retrieving content from the Provider.
50 *
51 * This class will be used by 1) the Email process (which includes the application and
52 * EmaiLProvider) as well as 2) the Exchange process (which runs independently).  It will
53 * necessarily be cloned for use in these two cases.
54 *
55 * Conventions used in naming columns:
56 *   RECORD_ID is the primary key for all Email records
57 *   The SyncColumns interface is used by all classes that are synced to the server directly
58 *   (Mailbox and Email)
59 *
60 *   <name>_KEY always refers to a foreign key
61 *   <name>_ID always refers to a unique identifier (whether on client, server, etc.)
62 *
63 */
64public abstract class EmailContent {
65    public static final int NOTIFICATION_MAILBOX_ID_COLUMN = 0;
66    public static final int NOTIFICATION_MAILBOX_UNREAD_COUNT_COLUMN = 1;
67    public static final int NOTIFICATION_MAILBOX_UNSEEN_COUNT_COLUMN = 2;
68
69    // All classes share this
70    public static final String RECORD_ID = "_id";
71
72    public static final String[] COUNT_COLUMNS = new String[]{"count(*)"};
73
74    /**
75     * This projection can be used with any of the EmailContent classes, when all you need
76     * is a list of id's.  Use ID_PROJECTION_COLUMN to access the row data.
77     */
78    public static final String[] ID_PROJECTION = new String[] {
79        RECORD_ID
80    };
81    public static final int ID_PROJECTION_COLUMN = 0;
82
83    public static final String ID_SELECTION = RECORD_ID + " =?";
84
85    public static final String FIELD_COLUMN_NAME = "field";
86    public static final String ADD_COLUMN_NAME = "add";
87    public static final String SET_COLUMN_NAME = "set";
88
89    public static final int SYNC_STATUS_NONE = UIProvider.SyncStatus.NO_SYNC;
90    public static final int SYNC_STATUS_USER = UIProvider.SyncStatus.USER_REFRESH;
91    public static final int SYNC_STATUS_BACKGROUND = UIProvider.SyncStatus.BACKGROUND_SYNC;
92    public static final int SYNC_STATUS_LIVE = UIProvider.SyncStatus.LIVE_QUERY;
93
94    public static final int LAST_SYNC_RESULT_SUCCESS = UIProvider.LastSyncResult.SUCCESS;
95    public static final int LAST_SYNC_RESULT_AUTH_ERROR = UIProvider.LastSyncResult.AUTH_ERROR;
96    public static final int LAST_SYNC_RESULT_SECURITY_ERROR =
97            UIProvider.LastSyncResult.SECURITY_ERROR;
98    public static final int LAST_SYNC_RESULT_CONNECTION_ERROR =
99            UIProvider.LastSyncResult.CONNECTION_ERROR;
100    public static final int LAST_SYNC_RESULT_INTERNAL_ERROR =
101            UIProvider.LastSyncResult.INTERNAL_ERROR;
102
103    // Newly created objects get this id
104    public static final int NOT_SAVED = -1;
105    // The base Uri that this piece of content came from
106    public Uri mBaseUri;
107    // Lazily initialized uri for this Content
108    private Uri mUri = null;
109    // The id of the Content
110    public long mId = NOT_SAVED;
111
112    // Write the Content into a ContentValues container
113    public abstract ContentValues toContentValues();
114    // Read the Content from a ContentCursor
115    public abstract void restore (Cursor cursor);
116
117
118    public static String EMAIL_PACKAGE_NAME;
119    public static String AUTHORITY;
120    // The notifier authority is used to send notifications regarding changes to messages (insert,
121    // delete, or update) and is intended as an optimization for use by clients of message list
122    // cursors (initially, the email AppWidget).
123    public static String NOTIFIER_AUTHORITY;
124    public static Uri CONTENT_URI;
125    public static final String PARAMETER_LIMIT = "limit";
126    public static Uri CONTENT_NOTIFIER_URI;
127    public static Uri PICK_TRASH_FOLDER_URI;
128    public static Uri PICK_SENT_FOLDER_URI;
129    public static Uri MAILBOX_NOTIFICATION_URI;
130    public static Uri MAILBOX_MOST_RECENT_MESSAGE_URI;
131    public static Uri ACCOUNT_CHECK_URI;
132
133    public static String PROVIDER_PERMISSION;
134
135    public static synchronized void init(Context context) {
136        if (AUTHORITY == null) {
137            final Resources res = context.getResources();
138            EMAIL_PACKAGE_NAME = res.getString(R.string.email_package_name);
139            AUTHORITY = EMAIL_PACKAGE_NAME + ".provider";
140            LogUtils.d("EmailContent", "init for " + AUTHORITY);
141            NOTIFIER_AUTHORITY = EMAIL_PACKAGE_NAME + ".notifier";
142            CONTENT_URI = Uri.parse("content://" + AUTHORITY);
143            CONTENT_NOTIFIER_URI = Uri.parse("content://" + NOTIFIER_AUTHORITY);
144            PICK_TRASH_FOLDER_URI = Uri.parse("content://" + AUTHORITY + "/pickTrashFolder");
145            PICK_SENT_FOLDER_URI = Uri.parse("content://" + AUTHORITY + "/pickSentFolder");
146            MAILBOX_NOTIFICATION_URI = Uri.parse("content://" + AUTHORITY + "/mailboxNotification");
147            MAILBOX_MOST_RECENT_MESSAGE_URI = Uri.parse("content://" + AUTHORITY +
148                    "/mailboxMostRecentMessage");
149            ACCOUNT_CHECK_URI = Uri.parse("content://" + AUTHORITY + "/accountCheck");
150            PROVIDER_PERMISSION = EMAIL_PACKAGE_NAME + ".permission.ACCESS_PROVIDER";
151            // Initialize subclasses
152            Account.initAccount();
153            Mailbox.initMailbox();
154            QuickResponse.initQuickResponse();
155            HostAuth.initHostAuth();
156            Policy.initPolicy();
157            Message.initMessage();
158            MessageMove.init();
159            MessageStateChange.init();
160            Body.initBody();
161            Attachment.initAttachment();
162        }
163    }
164
165    public static boolean isInitialSyncKey(final String syncKey) {
166        return syncKey == null || syncKey.isEmpty() || syncKey.equals("0");
167    }
168
169    // The Uri is lazily initialized
170    public Uri getUri() {
171        if (mUri == null) {
172            mUri = ContentUris.withAppendedId(mBaseUri, mId);
173        }
174        return mUri;
175    }
176
177    public boolean isSaved() {
178        return mId != NOT_SAVED;
179    }
180
181
182    /**
183     * Restore a subclass of EmailContent from the database
184     * @param context the caller's context
185     * @param klass the class to restore
186     * @param contentUri the content uri of the EmailContent subclass
187     * @param contentProjection the content projection for the EmailContent subclass
188     * @param id the unique id of the object
189     * @return the instantiated object
190     */
191    public static <T extends EmailContent> T restoreContentWithId(Context context,
192            Class<T> klass, Uri contentUri, String[] contentProjection, long id) {
193        Uri u = ContentUris.withAppendedId(contentUri, id);
194        Cursor c = context.getContentResolver().query(u, contentProjection, null, null, null);
195        if (c == null) throw new ProviderUnavailableException();
196        try {
197            if (c.moveToFirst()) {
198                return getContent(c, klass);
199            } else {
200                return null;
201            }
202        } finally {
203            c.close();
204        }
205    }
206
207
208    // The Content sub class must have a no-arg constructor
209    static public <T extends EmailContent> T getContent(Cursor cursor, Class<T> klass) {
210        try {
211            T content = klass.newInstance();
212            content.mId = cursor.getLong(0);
213            content.restore(cursor);
214            return content;
215        } catch (IllegalAccessException e) {
216            e.printStackTrace();
217        } catch (InstantiationException e) {
218            e.printStackTrace();
219        }
220        return null;
221    }
222
223    public Uri save(Context context) {
224        if (isSaved()) {
225            throw new UnsupportedOperationException();
226        }
227        Uri res = context.getContentResolver().insert(mBaseUri, toContentValues());
228        mId = Long.parseLong(res.getPathSegments().get(1));
229        return res;
230    }
231
232    public int update(Context context, ContentValues contentValues) {
233        if (!isSaved()) {
234            throw new UnsupportedOperationException();
235        }
236        return context.getContentResolver().update(getUri(), contentValues, null, null);
237    }
238
239    static public int update(Context context, Uri baseUri, long id, ContentValues contentValues) {
240        return context.getContentResolver()
241            .update(ContentUris.withAppendedId(baseUri, id), contentValues, null, null);
242    }
243
244    static public int delete(Context context, Uri baseUri, long id) {
245        return context.getContentResolver()
246            .delete(ContentUris.withAppendedId(baseUri, id), null, null);
247    }
248
249    /**
250     * Generic count method that can be used for any ContentProvider
251     *
252     * @param context the calling Context
253     * @param uri the Uri for the provider query
254     * @param selection as with a query call
255     * @param selectionArgs as with a query call
256     * @return the number of items matching the query (or zero)
257     */
258    static public int count(Context context, Uri uri, String selection, String[] selectionArgs) {
259        return Utility.getFirstRowLong(context,
260                uri, COUNT_COLUMNS, selection, selectionArgs, null, 0, Long.valueOf(0)).intValue();
261    }
262
263    /**
264     * Same as {@link #count(Context, Uri, String, String[])} without selection.
265     */
266    static public int count(Context context, Uri uri) {
267        return count(context, uri, null, null);
268    }
269
270    static public Uri uriWithLimit(Uri uri, int limit) {
271        return uri.buildUpon().appendQueryParameter(EmailContent.PARAMETER_LIMIT,
272                Integer.toString(limit)).build();
273    }
274
275    /**
276     * no public constructor since this is a utility class
277     */
278    protected EmailContent() {
279    }
280
281    public interface SyncColumns {
282        public static final String ID = "_id";
283        // source id (string) : the source's name of this item
284        public static final String SERVER_ID = "syncServerId";
285        // source's timestamp (long) for this item
286        public static final String SERVER_TIMESTAMP = "syncServerTimeStamp";
287    }
288
289    public interface BodyColumns {
290        public static final String ID = "_id";
291        // Foreign key to the message corresponding to this body
292        public static final String MESSAGE_KEY = "messageKey";
293        // The html content itself
294        public static final String HTML_CONTENT = "htmlContent";
295        // The plain text content itself
296        public static final String TEXT_CONTENT = "textContent";
297        // Replied-to or forwarded body (in html form)
298        @Deprecated
299        public static final String HTML_REPLY = "htmlReply";
300        // Replied-to or forwarded body (in text form)
301        @Deprecated
302        public static final String TEXT_REPLY = "textReply";
303        // A reference to a message's unique id used in reply/forward.
304        // Protocol code can be expected to use this column in determining whether a message can be
305        // deleted safely (i.e. isn't referenced by other messages)
306        public static final String SOURCE_MESSAGE_KEY = "sourceMessageKey";
307        // The text to be placed between a reply/forward response and the original message
308        @Deprecated
309        public static final String INTRO_TEXT = "introText";
310        // The start of quoted text within our text content
311        public static final String QUOTED_TEXT_START_POS = "quotedTextStartPos";
312    }
313
314    public static final class Body extends EmailContent implements BodyColumns {
315        public static final String TABLE_NAME = "Body";
316
317        public static final String SELECTION_BY_MESSAGE_KEY = MESSAGE_KEY + "=?";
318
319        public static Uri CONTENT_URI;
320
321        public static void initBody() {
322            CONTENT_URI = Uri.parse(EmailContent.CONTENT_URI + "/body");
323        }
324
325        public static final int CONTENT_ID_COLUMN = 0;
326        public static final int CONTENT_MESSAGE_KEY_COLUMN = 1;
327        public static final int CONTENT_HTML_CONTENT_COLUMN = 2;
328        public static final int CONTENT_TEXT_CONTENT_COLUMN = 3;
329        @Deprecated
330        public static final int CONTENT_HTML_REPLY_COLUMN = 4;
331        @Deprecated
332        public static final int CONTENT_TEXT_REPLY_COLUMN = 5;
333        public static final int CONTENT_SOURCE_KEY_COLUMN = 6;
334        @Deprecated
335        public static final int CONTENT_INTRO_TEXT_COLUMN = 7;
336        public static final int CONTENT_QUOTED_TEXT_START_POS_COLUMN = 8;
337
338        public static final String[] CONTENT_PROJECTION = new String[] {
339            RECORD_ID, BodyColumns.MESSAGE_KEY, BodyColumns.HTML_CONTENT, BodyColumns.TEXT_CONTENT,
340            BodyColumns.HTML_REPLY, BodyColumns.TEXT_REPLY, BodyColumns.SOURCE_MESSAGE_KEY,
341            BodyColumns.INTRO_TEXT, BodyColumns.QUOTED_TEXT_START_POS
342        };
343
344        public static final String[] COMMON_PROJECTION_TEXT = new String[] {
345            RECORD_ID, BodyColumns.TEXT_CONTENT
346        };
347        public static final String[] COMMON_PROJECTION_HTML = new String[] {
348            RECORD_ID, BodyColumns.HTML_CONTENT
349        };
350        @Deprecated
351        public static final String[] COMMON_PROJECTION_REPLY_TEXT = new String[] {
352            RECORD_ID, BodyColumns.TEXT_REPLY
353        };
354        @Deprecated
355        public static final String[] COMMON_PROJECTION_REPLY_HTML = new String[] {
356            RECORD_ID, BodyColumns.HTML_REPLY
357        };
358        @Deprecated
359        public static final String[] COMMON_PROJECTION_INTRO = new String[] {
360            RECORD_ID, BodyColumns.INTRO_TEXT
361        };
362        public static final String[] COMMON_PROJECTION_SOURCE = new String[] {
363            RECORD_ID, BodyColumns.SOURCE_MESSAGE_KEY
364        };
365        public static final int COMMON_PROJECTION_COLUMN_TEXT = 1;
366
367        private static final String[] PROJECTION_SOURCE_KEY =
368            new String[] { BodyColumns.SOURCE_MESSAGE_KEY };
369
370        public long mMessageKey;
371        public String mHtmlContent;
372        public String mTextContent;
373        @Deprecated
374        public String mHtmlReply;
375        @Deprecated
376        public String mTextReply;
377        public int mQuotedTextStartPos;
378
379        /**
380         * Points to the ID of the message being replied to or forwarded. Will always be set,
381         * even if {@link #mHtmlReply} and {@link #mTextReply} are null (indicating the user doesn't
382         * want to include quoted text.
383         */
384        public long mSourceKey;
385        @Deprecated
386        public String mIntroText;
387
388        public Body() {
389            mBaseUri = CONTENT_URI;
390        }
391
392        @Override
393        public ContentValues toContentValues() {
394            ContentValues values = new ContentValues();
395
396            // Assign values for each row.
397            values.put(BodyColumns.MESSAGE_KEY, mMessageKey);
398            values.put(BodyColumns.HTML_CONTENT, mHtmlContent);
399            values.put(BodyColumns.TEXT_CONTENT, mTextContent);
400            values.put(BodyColumns.HTML_REPLY, mHtmlReply);
401            values.put(BodyColumns.TEXT_REPLY, mTextReply);
402            values.put(BodyColumns.SOURCE_MESSAGE_KEY, mSourceKey);
403            values.put(BodyColumns.INTRO_TEXT, mIntroText);
404            return values;
405        }
406
407        /**
408         * Given a cursor, restore a Body from it
409         * @param cursor a cursor which must NOT be null
410         * @return the Body as restored from the cursor
411         */
412        private static Body restoreBodyWithCursor(Cursor cursor) {
413            try {
414                if (cursor.moveToFirst()) {
415                    return getContent(cursor, Body.class);
416                } else {
417                    return null;
418                }
419            } finally {
420                cursor.close();
421            }
422        }
423
424        public static Body restoreBodyWithId(Context context, long id) {
425            Uri u = ContentUris.withAppendedId(Body.CONTENT_URI, id);
426            Cursor c = context.getContentResolver().query(u, Body.CONTENT_PROJECTION,
427                    null, null, null);
428            if (c == null) throw new ProviderUnavailableException();
429            return restoreBodyWithCursor(c);
430        }
431
432        public static Body restoreBodyWithMessageId(Context context, long messageId) {
433            Cursor c = context.getContentResolver().query(Body.CONTENT_URI,
434                    Body.CONTENT_PROJECTION, Body.MESSAGE_KEY + "=?",
435                    new String[] {Long.toString(messageId)}, null);
436            if (c == null) throw new ProviderUnavailableException();
437            return restoreBodyWithCursor(c);
438        }
439
440        /**
441         * Returns the bodyId for the given messageId, or -1 if no body is found.
442         */
443        public static long lookupBodyIdWithMessageId(Context context, long messageId) {
444            return Utility.getFirstRowLong(context, Body.CONTENT_URI,
445                    ID_PROJECTION, Body.MESSAGE_KEY + "=?",
446                    new String[] {Long.toString(messageId)}, null, ID_PROJECTION_COLUMN,
447                            Long.valueOf(-1));
448        }
449
450        /**
451         * Updates the Body for a messageId with the given ContentValues.
452         * If the message has no body, a new body is inserted for the message.
453         * Warning: the argument "values" is modified by this method, setting MESSAGE_KEY.
454         */
455        public static void updateBodyWithMessageId(Context context, long messageId,
456                ContentValues values) {
457            ContentResolver resolver = context.getContentResolver();
458            long bodyId = lookupBodyIdWithMessageId(context, messageId);
459            values.put(BodyColumns.MESSAGE_KEY, messageId);
460            if (bodyId == -1) {
461                resolver.insert(CONTENT_URI, values);
462            } else {
463                final Uri uri = ContentUris.withAppendedId(CONTENT_URI, bodyId);
464                resolver.update(uri, values, null, null);
465            }
466        }
467
468        @VisibleForTesting
469        public static long restoreBodySourceKey(Context context, long messageId) {
470            return Utility.getFirstRowLong(context, Body.CONTENT_URI,
471                    Body.PROJECTION_SOURCE_KEY,
472                    Body.MESSAGE_KEY + "=?", new String[] {Long.toString(messageId)}, null, 0,
473                            Long.valueOf(0));
474        }
475
476        private static String restoreTextWithMessageId(Context context, long messageId,
477                String[] projection) {
478            Cursor c = context.getContentResolver().query(Body.CONTENT_URI, projection,
479                    Body.MESSAGE_KEY + "=?", new String[] {Long.toString(messageId)}, null);
480            if (c == null) throw new ProviderUnavailableException();
481            try {
482                if (c.moveToFirst()) {
483                    return c.getString(COMMON_PROJECTION_COLUMN_TEXT);
484                } else {
485                    return null;
486                }
487            } finally {
488                c.close();
489            }
490        }
491
492        public static String restoreBodyTextWithMessageId(Context context, long messageId) {
493            return restoreTextWithMessageId(context, messageId, Body.COMMON_PROJECTION_TEXT);
494        }
495
496        public static String restoreBodyHtmlWithMessageId(Context context, long messageId) {
497            return restoreTextWithMessageId(context, messageId, Body.COMMON_PROJECTION_HTML);
498        }
499
500        @Deprecated
501        public static String restoreReplyTextWithMessageId(Context context, long messageId) {
502            return restoreTextWithMessageId(context, messageId, Body.COMMON_PROJECTION_REPLY_TEXT);
503        }
504
505        @Deprecated
506        public static String restoreReplyHtmlWithMessageId(Context context, long messageId) {
507            return restoreTextWithMessageId(context, messageId, Body.COMMON_PROJECTION_REPLY_HTML);
508        }
509
510        @Deprecated
511        public static String restoreIntroTextWithMessageId(Context context, long messageId) {
512            return restoreTextWithMessageId(context, messageId, Body.COMMON_PROJECTION_INTRO);
513        }
514
515        @Override
516        public void restore(Cursor cursor) {
517            mBaseUri = EmailContent.Body.CONTENT_URI;
518            mMessageKey = cursor.getLong(CONTENT_MESSAGE_KEY_COLUMN);
519            mHtmlContent = cursor.getString(CONTENT_HTML_CONTENT_COLUMN);
520            mTextContent = cursor.getString(CONTENT_TEXT_CONTENT_COLUMN);
521            mHtmlReply = cursor.getString(CONTENT_HTML_REPLY_COLUMN);
522            mTextReply = cursor.getString(CONTENT_TEXT_REPLY_COLUMN);
523            mSourceKey = cursor.getLong(CONTENT_SOURCE_KEY_COLUMN);
524            mIntroText = cursor.getString(CONTENT_INTRO_TEXT_COLUMN);
525            mQuotedTextStartPos = cursor.getInt(CONTENT_QUOTED_TEXT_START_POS_COLUMN);
526        }
527    }
528
529    public interface MessageColumns {
530        public static final String ID = "_id";
531        // Basic columns used in message list presentation
532        // The name as shown to the user in a message list
533        public static final String DISPLAY_NAME = "displayName";
534        // The time (millis) as shown to the user in a message list [INDEX]
535        public static final String TIMESTAMP = "timeStamp";
536        // Message subject
537        public static final String SUBJECT = "subject";
538        // Boolean, unread = 0, read = 1 [INDEX]
539        public static final String FLAG_READ = "flagRead";
540        // Load state, see constants below (unloaded, partial, complete, deleted)
541        public static final String FLAG_LOADED = "flagLoaded";
542        // Boolean, unflagged = 0, flagged (favorite) = 1
543        public static final String FLAG_FAVORITE = "flagFavorite";
544        // Boolean, no attachment = 0, attachment = 1
545        public static final String FLAG_ATTACHMENT = "flagAttachment";
546        // Bit field for flags which we'll not be selecting on
547        public static final String FLAGS = "flags";
548
549        // Sync related identifiers
550        // Saved draft info (reusing the never-used "clientId" column)
551        public static final String DRAFT_INFO = "clientId";
552        // The message-id in the message's header
553        public static final String MESSAGE_ID = "messageId";
554
555        // References to other Email objects in the database
556        // Foreign key to the Mailbox holding this message [INDEX]
557        public static final String MAILBOX_KEY = "mailboxKey";
558        // Foreign key to the Account holding this message
559        public static final String ACCOUNT_KEY = "accountKey";
560
561        // Address lists, packed with Address.pack()
562        public static final String FROM_LIST = "fromList";
563        public static final String TO_LIST = "toList";
564        public static final String CC_LIST = "ccList";
565        public static final String BCC_LIST = "bccList";
566        public static final String REPLY_TO_LIST = "replyToList";
567        // Meeting invitation related information (for now, start time in ms)
568        public static final String MEETING_INFO = "meetingInfo";
569        // A text "snippet" derived from the body of the message
570        public static final String SNIPPET = "snippet";
571        // A column that can be used by sync adapters to store search-related information about
572        // a retrieved message (the messageKey for search results will be a TYPE_SEARCH mailbox
573        // and the sync adapter might, for example, need more information about the original source
574        // of the message)
575        public static final String PROTOCOL_SEARCH_INFO = "protocolSearchInfo";
576        // Simple thread topic
577        public static final String THREAD_TOPIC = "threadTopic";
578        // For sync adapter use
579        public static final String SYNC_DATA = "syncData";
580
581        /** Boolean, unseen = 0, seen = 1 [INDEX] */
582        public static final String FLAG_SEEN = "flagSeen";
583    }
584
585    public static final class Message extends EmailContent implements SyncColumns, MessageColumns {
586        private static final String LOG_TAG = "Email";
587
588        public static final String TABLE_NAME = "Message";
589        public static final String UPDATED_TABLE_NAME = "Message_Updates";
590        public static final String DELETED_TABLE_NAME = "Message_Deletes";
591
592        // To refer to a specific message, use ContentUris.withAppendedId(CONTENT_URI, id)
593        public static Uri CONTENT_URI;
594        public static Uri CONTENT_URI_LIMIT_1;
595        public static Uri SYNCED_CONTENT_URI;
596        public static Uri SELECTED_MESSAGE_CONTENT_URI ;
597        public static Uri DELETED_CONTENT_URI;
598        public static Uri UPDATED_CONTENT_URI;
599        public static Uri NOTIFIER_URI;
600
601        public static void initMessage() {
602            CONTENT_URI = Uri.parse(EmailContent.CONTENT_URI + "/message");
603            CONTENT_URI_LIMIT_1 = uriWithLimit(CONTENT_URI, 1);
604            SYNCED_CONTENT_URI =
605                    Uri.parse(EmailContent.CONTENT_URI + "/syncedMessage");
606            SELECTED_MESSAGE_CONTENT_URI =
607                    Uri.parse(EmailContent.CONTENT_URI + "/messageBySelection");
608            DELETED_CONTENT_URI =
609                    Uri.parse(EmailContent.CONTENT_URI + "/deletedMessage");
610            UPDATED_CONTENT_URI =
611                    Uri.parse(EmailContent.CONTENT_URI + "/updatedMessage");
612            NOTIFIER_URI =
613                    Uri.parse(EmailContent.CONTENT_NOTIFIER_URI + "/message");
614        }
615
616        public static final String KEY_TIMESTAMP_DESC = MessageColumns.TIMESTAMP + " desc";
617
618        public static final int CONTENT_ID_COLUMN = 0;
619        public static final int CONTENT_DISPLAY_NAME_COLUMN = 1;
620        public static final int CONTENT_TIMESTAMP_COLUMN = 2;
621        public static final int CONTENT_SUBJECT_COLUMN = 3;
622        public static final int CONTENT_FLAG_READ_COLUMN = 4;
623        public static final int CONTENT_FLAG_LOADED_COLUMN = 5;
624        public static final int CONTENT_FLAG_FAVORITE_COLUMN = 6;
625        public static final int CONTENT_FLAG_ATTACHMENT_COLUMN = 7;
626        public static final int CONTENT_FLAGS_COLUMN = 8;
627        public static final int CONTENT_SERVER_ID_COLUMN = 9;
628        public static final int CONTENT_DRAFT_INFO_COLUMN = 10;
629        public static final int CONTENT_MESSAGE_ID_COLUMN = 11;
630        public static final int CONTENT_MAILBOX_KEY_COLUMN = 12;
631        public static final int CONTENT_ACCOUNT_KEY_COLUMN = 13;
632        public static final int CONTENT_FROM_LIST_COLUMN = 14;
633        public static final int CONTENT_TO_LIST_COLUMN = 15;
634        public static final int CONTENT_CC_LIST_COLUMN = 16;
635        public static final int CONTENT_BCC_LIST_COLUMN = 17;
636        public static final int CONTENT_REPLY_TO_COLUMN = 18;
637        public static final int CONTENT_SERVER_TIMESTAMP_COLUMN = 19;
638        public static final int CONTENT_MEETING_INFO_COLUMN = 20;
639        public static final int CONTENT_SNIPPET_COLUMN = 21;
640        public static final int CONTENT_PROTOCOL_SEARCH_INFO_COLUMN = 22;
641        public static final int CONTENT_THREAD_TOPIC_COLUMN = 23;
642        public static final int CONTENT_SYNC_DATA_COLUMN = 24;
643        public static final int CONTENT_FLAG_SEEN_COLUMN = 25;
644
645        public static final String[] CONTENT_PROJECTION = new String[] {
646            RECORD_ID,
647            MessageColumns.DISPLAY_NAME, MessageColumns.TIMESTAMP,
648            MessageColumns.SUBJECT, MessageColumns.FLAG_READ,
649            MessageColumns.FLAG_LOADED, MessageColumns.FLAG_FAVORITE,
650            MessageColumns.FLAG_ATTACHMENT, MessageColumns.FLAGS,
651            SyncColumns.SERVER_ID, MessageColumns.DRAFT_INFO,
652            MessageColumns.MESSAGE_ID, MessageColumns.MAILBOX_KEY,
653            MessageColumns.ACCOUNT_KEY, MessageColumns.FROM_LIST,
654            MessageColumns.TO_LIST, MessageColumns.CC_LIST,
655            MessageColumns.BCC_LIST, MessageColumns.REPLY_TO_LIST,
656            SyncColumns.SERVER_TIMESTAMP, MessageColumns.MEETING_INFO,
657            MessageColumns.SNIPPET, MessageColumns.PROTOCOL_SEARCH_INFO,
658            MessageColumns.THREAD_TOPIC, MessageColumns.SYNC_DATA, MessageColumns.FLAG_SEEN
659        };
660
661        public static final int LIST_ID_COLUMN = 0;
662        public static final int LIST_DISPLAY_NAME_COLUMN = 1;
663        public static final int LIST_TIMESTAMP_COLUMN = 2;
664        public static final int LIST_SUBJECT_COLUMN = 3;
665        public static final int LIST_READ_COLUMN = 4;
666        public static final int LIST_LOADED_COLUMN = 5;
667        public static final int LIST_FAVORITE_COLUMN = 6;
668        public static final int LIST_ATTACHMENT_COLUMN = 7;
669        public static final int LIST_FLAGS_COLUMN = 8;
670        public static final int LIST_MAILBOX_KEY_COLUMN = 9;
671        public static final int LIST_ACCOUNT_KEY_COLUMN = 10;
672        public static final int LIST_SERVER_ID_COLUMN = 11;
673        public static final int LIST_SNIPPET_COLUMN = 12;
674
675        // Public projection for common list columns
676        public static final String[] LIST_PROJECTION = new String[] {
677            RECORD_ID,
678            MessageColumns.DISPLAY_NAME, MessageColumns.TIMESTAMP,
679            MessageColumns.SUBJECT, MessageColumns.FLAG_READ,
680            MessageColumns.FLAG_LOADED, MessageColumns.FLAG_FAVORITE,
681            MessageColumns.FLAG_ATTACHMENT, MessageColumns.FLAGS,
682            MessageColumns.MAILBOX_KEY, MessageColumns.ACCOUNT_KEY,
683            SyncColumns.SERVER_ID, MessageColumns.SNIPPET
684        };
685
686        public static final int ID_COLUMNS_ID_COLUMN = 0;
687        public static final int ID_COLUMNS_SYNC_SERVER_ID = 1;
688        public static final String[] ID_COLUMNS_PROJECTION = new String[] {
689            RECORD_ID, SyncColumns.SERVER_ID
690        };
691
692        public static final String[] ID_COLUMN_PROJECTION = new String[] { RECORD_ID };
693
694        public static final String ACCOUNT_KEY_SELECTION =
695            MessageColumns.ACCOUNT_KEY + "=?";
696
697        public static final String[] MAILBOX_KEY_PROJECTION = new String[] { MAILBOX_KEY };
698
699        /**
700         * Selection for messages that are loaded
701         *
702         * POP messages at the initial stage have very little information. (Server UID only)
703         * Use this to make sure they're not visible on any UI.
704         * This means unread counts on the mailbox list can be different from the
705         * number of messages in the message list, but it should be transient...
706         */
707        public static final String FLAG_LOADED_SELECTION =
708            MessageColumns.FLAG_LOADED + " IN ("
709            +     Message.FLAG_LOADED_PARTIAL + "," + Message.FLAG_LOADED_COMPLETE
710            +     ")";
711
712        public static final String ALL_FAVORITE_SELECTION =
713            MessageColumns.FLAG_FAVORITE + "=1 AND "
714            + MessageColumns.MAILBOX_KEY + " NOT IN ("
715            +     "SELECT " + MailboxColumns.ID + " FROM " + Mailbox.TABLE_NAME + ""
716            +     " WHERE " + MailboxColumns.TYPE + " = " + Mailbox.TYPE_TRASH
717            +     ")"
718            + " AND " + FLAG_LOADED_SELECTION;
719
720        /** Selection to retrieve all messages in "inbox" for any account */
721        public static final String ALL_INBOX_SELECTION =
722            MessageColumns.MAILBOX_KEY + " IN ("
723            +     "SELECT " + MailboxColumns.ID + " FROM " + Mailbox.TABLE_NAME
724            +     " WHERE " + MailboxColumns.TYPE + " = " + Mailbox.TYPE_INBOX
725            +     ")"
726            + " AND " + FLAG_LOADED_SELECTION;
727
728        /** Selection to retrieve all messages in "drafts" for any account */
729        public static final String ALL_DRAFT_SELECTION =
730            MessageColumns.MAILBOX_KEY + " IN ("
731            +     "SELECT " + MailboxColumns.ID + " FROM " + Mailbox.TABLE_NAME
732            +     " WHERE " + MailboxColumns.TYPE + " = " + Mailbox.TYPE_DRAFTS
733            +     ")"
734            + " AND " + FLAG_LOADED_SELECTION;
735
736        /** Selection to retrieve all messages in "outbox" for any account */
737        public static final String ALL_OUTBOX_SELECTION =
738            MessageColumns.MAILBOX_KEY + " IN ("
739            +     "SELECT " + MailboxColumns.ID + " FROM " + Mailbox.TABLE_NAME
740            +     " WHERE " + MailboxColumns.TYPE + " = " + Mailbox.TYPE_OUTBOX
741            +     ")"; // NOTE No flag_loaded test for outboxes.
742
743        /** Selection to retrieve unread messages in "inbox" for any account */
744        public static final String ALL_UNREAD_SELECTION =
745            MessageColumns.FLAG_READ + "=0 AND " + ALL_INBOX_SELECTION;
746
747        /** Selection to retrieve unread messages in "inbox" for one account */
748        public static final String PER_ACCOUNT_UNREAD_SELECTION =
749            ACCOUNT_KEY_SELECTION + " AND " + ALL_UNREAD_SELECTION;
750
751        /** Selection to retrieve all messages in "inbox" for one account */
752        public static final String PER_ACCOUNT_INBOX_SELECTION =
753            ACCOUNT_KEY_SELECTION + " AND " + ALL_INBOX_SELECTION;
754
755        public static final String PER_ACCOUNT_FAVORITE_SELECTION =
756            ACCOUNT_KEY_SELECTION + " AND " + ALL_FAVORITE_SELECTION;
757
758        public static final String MAILBOX_SELECTION = MAILBOX_KEY + "=?";
759
760        // _id field is in AbstractContent
761        public String mDisplayName;
762        public long mTimeStamp;
763        public String mSubject;
764        public boolean mFlagRead = false;
765        public boolean mFlagSeen = false;
766        public int mFlagLoaded = FLAG_LOADED_UNLOADED;
767        public boolean mFlagFavorite = false;
768        public boolean mFlagAttachment = false;
769        public int mFlags = 0;
770
771        public String mServerId;
772        public long mServerTimeStamp;
773        public int mDraftInfo;
774        public String mMessageId;
775
776        public long mMailboxKey;
777        public long mAccountKey;
778
779        public String mFrom;
780        public String mTo;
781        public String mCc;
782        public String mBcc;
783        public String mReplyTo;
784
785        // For now, just the start time of a meeting invite, in ms
786        public String mMeetingInfo;
787
788        public String mSnippet;
789
790        public String mProtocolSearchInfo;
791
792        public String mThreadTopic;
793
794        public String mSyncData;
795
796        /**
797         * Base64-encoded representation of the byte array provided by servers for identifying
798         * messages belonging to the same conversation thread. Currently unsupported and not
799         * persisted in the database.
800         */
801        public String mServerConversationId;
802
803        // The following transient members may be used while building and manipulating messages,
804        // but they are NOT persisted directly by EmailProvider. See Body for related fields.
805        transient public String mText;
806        transient public String mHtml;
807        transient public String mTextReply;
808        transient public String mHtmlReply;
809        transient public long mSourceKey;
810        transient public ArrayList<Attachment> mAttachments = null;
811        transient public String mIntroText;
812        transient public int mQuotedTextStartPos;
813
814
815        // Values used in mFlagRead
816        public static final int UNREAD = 0;
817        public static final int READ = 1;
818
819        // Values used in mFlagLoaded
820        public static final int FLAG_LOADED_UNLOADED = 0;
821        public static final int FLAG_LOADED_COMPLETE = 1;
822        public static final int FLAG_LOADED_PARTIAL = 2;
823        public static final int FLAG_LOADED_DELETED = 3;
824        public static final int FLAG_LOADED_UNKNOWN = 4;
825
826        // Bits used in mFlags
827        // The following three states are mutually exclusive, and indicate whether the message is an
828        // original, a reply, or a forward
829        public static final int FLAG_TYPE_REPLY = 1<<0;
830        public static final int FLAG_TYPE_FORWARD = 1<<1;
831        public static final int FLAG_TYPE_MASK = FLAG_TYPE_REPLY | FLAG_TYPE_FORWARD;
832        // The following flags indicate messages that are determined to be incoming meeting related
833        // (e.g. invites from others)
834        public static final int FLAG_INCOMING_MEETING_INVITE = 1<<2;
835        public static final int FLAG_INCOMING_MEETING_CANCEL = 1<<3;
836        public static final int FLAG_INCOMING_MEETING_MASK =
837            FLAG_INCOMING_MEETING_INVITE | FLAG_INCOMING_MEETING_CANCEL;
838        // The following flags indicate messages that are outgoing and meeting related
839        // (e.g. invites TO others)
840        public static final int FLAG_OUTGOING_MEETING_INVITE = 1<<4;
841        public static final int FLAG_OUTGOING_MEETING_CANCEL = 1<<5;
842        public static final int FLAG_OUTGOING_MEETING_ACCEPT = 1<<6;
843        public static final int FLAG_OUTGOING_MEETING_DECLINE = 1<<7;
844        public static final int FLAG_OUTGOING_MEETING_TENTATIVE = 1<<8;
845        public static final int FLAG_OUTGOING_MEETING_MASK =
846            FLAG_OUTGOING_MEETING_INVITE | FLAG_OUTGOING_MEETING_CANCEL |
847            FLAG_OUTGOING_MEETING_ACCEPT | FLAG_OUTGOING_MEETING_DECLINE |
848            FLAG_OUTGOING_MEETING_TENTATIVE;
849        public static final int FLAG_OUTGOING_MEETING_REQUEST_MASK =
850            FLAG_OUTGOING_MEETING_INVITE | FLAG_OUTGOING_MEETING_CANCEL;
851        // 8 general purpose flags (bits) that may be used at the discretion of the sync adapter
852        public static final int FLAG_SYNC_ADAPTER_SHIFT = 9;
853        public static final int FLAG_SYNC_ADAPTER_MASK = 255 << FLAG_SYNC_ADAPTER_SHIFT;
854        /** If set, the outgoing message should *not* include the quoted original message. */
855        public static final int FLAG_NOT_INCLUDE_QUOTED_TEXT = 1 << 17;
856        public static final int FLAG_REPLIED_TO = 1 << 18;
857        public static final int FLAG_FORWARDED = 1 << 19;
858
859        // Outgoing, original message
860        public static final int FLAG_TYPE_ORIGINAL = 1 << 20;
861        // Outgoing, reply all message; note, FLAG_TYPE_REPLY should also be set for backward
862        // compatibility
863        public static final int FLAG_TYPE_REPLY_ALL = 1 << 21;
864
865        // Flag used in draftInfo to indicate that the reference message should be appended
866        public static final int DRAFT_INFO_APPEND_REF_MESSAGE = 1 << 24;
867        public static final int DRAFT_INFO_QUOTE_POS_MASK = 0xFFFFFF;
868
869        /** a pseudo ID for "no message". */
870        public static final long NO_MESSAGE = -1L;
871
872        private static final int ATTACHMENT_INDEX_OFFSET = 2;
873
874        public Message() {
875            mBaseUri = CONTENT_URI;
876        }
877
878        @Override
879        public ContentValues toContentValues() {
880            ContentValues values = new ContentValues();
881
882            // Assign values for each row.
883            values.put(MessageColumns.DISPLAY_NAME, mDisplayName);
884            values.put(MessageColumns.TIMESTAMP, mTimeStamp);
885            values.put(MessageColumns.SUBJECT, mSubject);
886            values.put(MessageColumns.FLAG_READ, mFlagRead);
887            values.put(MessageColumns.FLAG_SEEN, mFlagSeen);
888            values.put(MessageColumns.FLAG_LOADED, mFlagLoaded);
889            values.put(MessageColumns.FLAG_FAVORITE, mFlagFavorite);
890            values.put(MessageColumns.FLAG_ATTACHMENT, mFlagAttachment);
891            values.put(MessageColumns.FLAGS, mFlags);
892            values.put(SyncColumns.SERVER_ID, mServerId);
893            values.put(SyncColumns.SERVER_TIMESTAMP, mServerTimeStamp);
894            values.put(MessageColumns.DRAFT_INFO, mDraftInfo);
895            values.put(MessageColumns.MESSAGE_ID, mMessageId);
896            values.put(MessageColumns.MAILBOX_KEY, mMailboxKey);
897            values.put(MessageColumns.ACCOUNT_KEY, mAccountKey);
898            values.put(MessageColumns.FROM_LIST, mFrom);
899            values.put(MessageColumns.TO_LIST, mTo);
900            values.put(MessageColumns.CC_LIST, mCc);
901            values.put(MessageColumns.BCC_LIST, mBcc);
902            values.put(MessageColumns.REPLY_TO_LIST, mReplyTo);
903            values.put(MessageColumns.MEETING_INFO, mMeetingInfo);
904            values.put(MessageColumns.SNIPPET, mSnippet);
905            values.put(MessageColumns.PROTOCOL_SEARCH_INFO, mProtocolSearchInfo);
906            values.put(MessageColumns.THREAD_TOPIC, mThreadTopic);
907            values.put(MessageColumns.SYNC_DATA, mSyncData);
908            return values;
909        }
910
911        public static Message restoreMessageWithId(Context context, long id) {
912            return EmailContent.restoreContentWithId(context, Message.class,
913                    Message.CONTENT_URI, Message.CONTENT_PROJECTION, id);
914        }
915
916        @Override
917        public void restore(Cursor cursor) {
918            mBaseUri = CONTENT_URI;
919            mId = cursor.getLong(CONTENT_ID_COLUMN);
920            mDisplayName = cursor.getString(CONTENT_DISPLAY_NAME_COLUMN);
921            mTimeStamp = cursor.getLong(CONTENT_TIMESTAMP_COLUMN);
922            mSubject = cursor.getString(CONTENT_SUBJECT_COLUMN);
923            mFlagRead = cursor.getInt(CONTENT_FLAG_READ_COLUMN) == 1;
924            mFlagSeen = cursor.getInt(CONTENT_FLAG_SEEN_COLUMN) == 1;
925            mFlagLoaded = cursor.getInt(CONTENT_FLAG_LOADED_COLUMN);
926            mFlagFavorite = cursor.getInt(CONTENT_FLAG_FAVORITE_COLUMN) == 1;
927            mFlagAttachment = cursor.getInt(CONTENT_FLAG_ATTACHMENT_COLUMN) == 1;
928            mFlags = cursor.getInt(CONTENT_FLAGS_COLUMN);
929            mServerId = cursor.getString(CONTENT_SERVER_ID_COLUMN);
930            mServerTimeStamp = cursor.getLong(CONTENT_SERVER_TIMESTAMP_COLUMN);
931            mDraftInfo = cursor.getInt(CONTENT_DRAFT_INFO_COLUMN);
932            mMessageId = cursor.getString(CONTENT_MESSAGE_ID_COLUMN);
933            mMailboxKey = cursor.getLong(CONTENT_MAILBOX_KEY_COLUMN);
934            mAccountKey = cursor.getLong(CONTENT_ACCOUNT_KEY_COLUMN);
935            mFrom = cursor.getString(CONTENT_FROM_LIST_COLUMN);
936            mTo = cursor.getString(CONTENT_TO_LIST_COLUMN);
937            mCc = cursor.getString(CONTENT_CC_LIST_COLUMN);
938            mBcc = cursor.getString(CONTENT_BCC_LIST_COLUMN);
939            mReplyTo = cursor.getString(CONTENT_REPLY_TO_COLUMN);
940            mMeetingInfo = cursor.getString(CONTENT_MEETING_INFO_COLUMN);
941            mSnippet = cursor.getString(CONTENT_SNIPPET_COLUMN);
942            mProtocolSearchInfo = cursor.getString(CONTENT_PROTOCOL_SEARCH_INFO_COLUMN);
943            mThreadTopic = cursor.getString(CONTENT_THREAD_TOPIC_COLUMN);
944            mSyncData = cursor.getString(CONTENT_SYNC_DATA_COLUMN);
945        }
946
947        /*
948         * Override this so that we can store the Body first and link it to the Message
949         * Also, attachments when we get there...
950         * (non-Javadoc)
951         * @see com.android.email.provider.EmailContent#save(android.content.Context)
952         */
953        @Override
954        public Uri save(Context context) {
955
956            boolean doSave = !isSaved();
957
958            // This logic is in place so I can (a) short circuit the expensive stuff when
959            // possible, and (b) override (and throw) if anyone tries to call save() or update()
960            // directly for Message, which are unsupported.
961            if (mText == null && mHtml == null && mTextReply == null && mHtmlReply == null &&
962                    (mAttachments == null || mAttachments.isEmpty())) {
963                if (doSave) {
964                    return super.save(context);
965                } else {
966                    // Call update, rather than super.update in case we ever override it
967                    if (update(context, toContentValues()) == 1) {
968                        return getUri();
969                    }
970                    return null;
971                }
972            }
973
974            final ArrayList<ContentProviderOperation> ops =
975                    new ArrayList<ContentProviderOperation>();
976            addSaveOps(ops);
977            try {
978                final ContentProviderResult[] results =
979                    context.getContentResolver().applyBatch(AUTHORITY, ops);
980                // If saving, set the mId's of the various saved objects
981                if (doSave) {
982                    Uri u = results[0].uri;
983                    mId = Long.parseLong(u.getPathSegments().get(1));
984                    if (mAttachments != null) {
985                        // Skip over the first two items in the result array
986                        for (int i = 0; i < mAttachments.size(); i++) {
987                            final Attachment a = mAttachments.get(i);
988
989                            final int resultIndex = i + ATTACHMENT_INDEX_OFFSET;
990                            // Save the id of the attachment record
991                            if (resultIndex < results.length) {
992                                u = results[resultIndex].uri;
993                            } else {
994                                // We didn't find the expected attachment, log this error
995                                LogUtils.e(LOG_TAG, "Invalid index into ContentProviderResults: " +
996                                        resultIndex);
997                                u = null;
998                            }
999                            if (u != null) {
1000                                a.mId = Long.parseLong(u.getPathSegments().get(1));
1001                            }
1002                            a.mMessageKey = mId;
1003                        }
1004                    }
1005                    return u;
1006                } else {
1007                    return null;
1008                }
1009            } catch (RemoteException e) {
1010                // There is nothing to be done here; fail by returning null
1011            } catch (OperationApplicationException e) {
1012                // There is nothing to be done here; fail by returning null
1013            }
1014            return null;
1015        }
1016
1017        /**
1018         * Save or update a message
1019         * @param ops an array of CPOs that we'll add to
1020         */
1021        public void addSaveOps(ArrayList<ContentProviderOperation> ops) {
1022            boolean isNew = !isSaved();
1023            ContentProviderOperation.Builder b;
1024            // First, save/update the message
1025            if (isNew) {
1026                b = ContentProviderOperation.newInsert(mBaseUri);
1027            } else {
1028                b = ContentProviderOperation.newUpdate(mBaseUri)
1029                        .withSelection(Message.RECORD_ID + "=?", new String[] {Long.toString(mId)});
1030            }
1031            // Generate the snippet here, before we create the CPO for Message
1032            if (mText != null) {
1033                mSnippet = TextUtilities.makeSnippetFromPlainText(mText);
1034            } else if (mHtml != null) {
1035                mSnippet = TextUtilities.makeSnippetFromHtmlText(mHtml);
1036            }
1037            ops.add(b.withValues(toContentValues()).build());
1038
1039            // Create and save the body
1040            ContentValues cv = new ContentValues();
1041            if (mText != null) {
1042                cv.put(Body.TEXT_CONTENT, mText);
1043            }
1044            if (mHtml != null) {
1045                cv.put(Body.HTML_CONTENT, mHtml);
1046            }
1047            if (mSourceKey != 0) {
1048                cv.put(Body.SOURCE_MESSAGE_KEY, mSourceKey);
1049            }
1050            if (mQuotedTextStartPos != 0) {
1051                cv.put(Body.QUOTED_TEXT_START_POS, mQuotedTextStartPos);
1052            }
1053            // We'll need this if we're new
1054            int messageBackValue = ops.size() - 1;
1055            // Only create a body if we've got some data
1056            if (!cv.keySet().isEmpty()) {
1057                b = ContentProviderOperation.newInsert(Body.CONTENT_URI);
1058                // Put our message id in the Body
1059                if (!isNew) {
1060                    cv.put(Body.MESSAGE_KEY, mId);
1061                }
1062                b.withValues(cv);
1063                // If we're new, create a back value entry
1064                if (isNew) {
1065                    ContentValues backValues = new ContentValues();
1066                    backValues.put(Body.MESSAGE_KEY, messageBackValue);
1067                    b.withValueBackReferences(backValues);
1068                }
1069                // And add the Body operation
1070                ops.add(b.build());
1071            }
1072
1073            // Create the attaachments, if any
1074            if (mAttachments != null) {
1075                for (Attachment att: mAttachments) {
1076                    if (!isNew) {
1077                        att.mMessageKey = mId;
1078                    }
1079                    b = ContentProviderOperation.newInsert(Attachment.CONTENT_URI)
1080                            .withValues(att.toContentValues());
1081                    if (isNew) {
1082                        b.withValueBackReference(Attachment.MESSAGE_KEY, messageBackValue);
1083                    }
1084                    ops.add(b.build());
1085                }
1086            }
1087        }
1088
1089        /**
1090         * @return number of favorite (starred) messages throughout all accounts.
1091         */
1092        public static int getFavoriteMessageCount(Context context) {
1093            return count(context, Message.CONTENT_URI, ALL_FAVORITE_SELECTION, null);
1094        }
1095
1096        /**
1097         * @return number of favorite (starred) messages for an account
1098         */
1099        public static int getFavoriteMessageCount(Context context, long accountId) {
1100            return count(context, Message.CONTENT_URI, PER_ACCOUNT_FAVORITE_SELECTION,
1101                    new String[]{Long.toString(accountId)});
1102        }
1103
1104        public static long getKeyColumnLong(Context context, long messageId, String column) {
1105            String[] columns =
1106                Utility.getRowColumns(context, Message.CONTENT_URI, messageId, column);
1107            if (columns != null && columns[0] != null) {
1108                return Long.parseLong(columns[0]);
1109            }
1110            return -1;
1111        }
1112
1113        /**
1114         * Returns the where clause for a message list selection.
1115         *
1116         * Accesses the detabase to determine the mailbox type.  DO NOT CALL FROM UI THREAD.
1117         */
1118        public static String buildMessageListSelection(
1119                Context context, long accountId, long mailboxId) {
1120
1121            if (mailboxId == Mailbox.QUERY_ALL_INBOXES) {
1122                return Message.ALL_INBOX_SELECTION;
1123            }
1124            if (mailboxId == Mailbox.QUERY_ALL_DRAFTS) {
1125                return Message.ALL_DRAFT_SELECTION;
1126            }
1127            if (mailboxId == Mailbox.QUERY_ALL_OUTBOX) {
1128                return Message.ALL_OUTBOX_SELECTION;
1129            }
1130            if (mailboxId == Mailbox.QUERY_ALL_UNREAD) {
1131                return Message.ALL_UNREAD_SELECTION;
1132            }
1133            // TODO: we only support per-account starred mailbox right now, but presumably, we
1134            // can surface the same thing for unread.
1135            if (mailboxId == Mailbox.QUERY_ALL_FAVORITES) {
1136                if (accountId == Account.ACCOUNT_ID_COMBINED_VIEW) {
1137                    return Message.ALL_FAVORITE_SELECTION;
1138                }
1139
1140                final StringBuilder selection = new StringBuilder();
1141                selection.append(MessageColumns.ACCOUNT_KEY).append('=').append(accountId)
1142                        .append(" AND ")
1143                        .append(Message.ALL_FAVORITE_SELECTION);
1144                return selection.toString();
1145            }
1146
1147            // Now it's a regular mailbox.
1148            final StringBuilder selection = new StringBuilder();
1149
1150            selection.append(MessageColumns.MAILBOX_KEY).append('=').append(mailboxId);
1151
1152            if (Mailbox.getMailboxType(context, mailboxId) != Mailbox.TYPE_OUTBOX) {
1153                selection.append(" AND ").append(Message.FLAG_LOADED_SELECTION);
1154            }
1155            return selection.toString();
1156        }
1157
1158        public void setFlags(boolean quotedReply, boolean quotedForward) {
1159            // Set message flags as well
1160            if (quotedReply || quotedForward) {
1161                mFlags &= ~EmailContent.Message.FLAG_TYPE_MASK;
1162                mFlags |= quotedReply
1163                        ? EmailContent.Message.FLAG_TYPE_REPLY
1164                        : EmailContent.Message.FLAG_TYPE_FORWARD;
1165            }
1166        }
1167    }
1168
1169    public interface AttachmentColumns {
1170        public static final String ID = "_id";
1171        // The display name of the attachment
1172        public static final String FILENAME = "fileName";
1173        // The mime type of the attachment
1174        public static final String MIME_TYPE = "mimeType";
1175        // The size of the attachment in bytes
1176        public static final String SIZE = "size";
1177        // The (internal) contentId of the attachment (inline attachments will have these)
1178        public static final String CONTENT_ID = "contentId";
1179        // The location of the loaded attachment (probably a file)
1180        @SuppressWarnings("hiding")
1181        public static final String CONTENT_URI = "contentUri";
1182        // The cached location of the attachment
1183        public static final String CACHED_FILE = "cachedFile";
1184        // A foreign key into the Message table (the message owning this attachment)
1185        public static final String MESSAGE_KEY = "messageKey";
1186        // The location of the attachment on the server side
1187        // For IMAP, this is a part number (e.g. 2.1); for EAS, it's the internal file name
1188        public static final String LOCATION = "location";
1189        // The transfer encoding of the attachment
1190        public static final String ENCODING = "encoding";
1191        // Not currently used
1192        public static final String CONTENT = "content";
1193        // Flags
1194        public static final String FLAGS = "flags";
1195        // Content that is actually contained in the Attachment row
1196        public static final String CONTENT_BYTES = "content_bytes";
1197        // A foreign key into the Account table (for the message owning this attachment)
1198        public static final String ACCOUNT_KEY = "accountKey";
1199        // The UIProvider state of the attachment
1200        public static final String UI_STATE = "uiState";
1201        // The UIProvider destination of the attachment
1202        public static final String UI_DESTINATION = "uiDestination";
1203        // The UIProvider downloaded size of the attachment
1204        public static final String UI_DOWNLOADED_SIZE = "uiDownloadedSize";
1205    }
1206
1207    public static final class Attachment extends EmailContent
1208            implements AttachmentColumns, Parcelable {
1209        public static final String TABLE_NAME = "Attachment";
1210        public static final String ATTACHMENT_PROVIDER_LEGACY_URI_PREFIX =
1211                "content://com.android.email.attachmentprovider";
1212
1213        public static final String CACHED_FILE_QUERY_PARAM = "filePath";
1214
1215        public static Uri CONTENT_URI;
1216        // This must be used with an appended id: ContentUris.withAppendedId(MESSAGE_ID_URI, id)
1217        public static Uri MESSAGE_ID_URI;
1218        public static String ATTACHMENT_PROVIDER_URI_PREFIX;
1219        public static boolean sUsingLegacyPrefix;
1220
1221        public static void initAttachment() {
1222            CONTENT_URI = Uri.parse(EmailContent.CONTENT_URI + "/attachment");
1223            MESSAGE_ID_URI = Uri.parse(
1224                    EmailContent.CONTENT_URI + "/attachment/message");
1225            ATTACHMENT_PROVIDER_URI_PREFIX = "content://" + EmailContent.EMAIL_PACKAGE_NAME +
1226                    ".attachmentprovider";
1227            sUsingLegacyPrefix =
1228                    ATTACHMENT_PROVIDER_URI_PREFIX.equals(ATTACHMENT_PROVIDER_LEGACY_URI_PREFIX);
1229        }
1230
1231        public String mFileName;
1232        public String mMimeType;
1233        public long mSize;
1234        public String mContentId;
1235        private String mContentUri;
1236        private String mCachedFileUri;
1237        public long mMessageKey;
1238        public String mLocation;
1239        public String mEncoding;
1240        public String mContent; // Not currently used
1241        public int mFlags;
1242        public byte[] mContentBytes;
1243        public long mAccountKey;
1244        public int mUiState;
1245        public int mUiDestination;
1246        public int mUiDownloadedSize;
1247
1248        public static final int CONTENT_ID_COLUMN = 0;
1249        public static final int CONTENT_FILENAME_COLUMN = 1;
1250        public static final int CONTENT_MIME_TYPE_COLUMN = 2;
1251        public static final int CONTENT_SIZE_COLUMN = 3;
1252        public static final int CONTENT_CONTENT_ID_COLUMN = 4;
1253        public static final int CONTENT_CONTENT_URI_COLUMN = 5;
1254        public static final int CONTENT_CACHED_FILE_COLUMN = 6;
1255        public static final int CONTENT_MESSAGE_ID_COLUMN = 7;
1256        public static final int CONTENT_LOCATION_COLUMN = 8;
1257        public static final int CONTENT_ENCODING_COLUMN = 9;
1258        public static final int CONTENT_CONTENT_COLUMN = 10; // Not currently used
1259        public static final int CONTENT_FLAGS_COLUMN = 11;
1260        public static final int CONTENT_CONTENT_BYTES_COLUMN = 12;
1261        public static final int CONTENT_ACCOUNT_KEY_COLUMN = 13;
1262        public static final int CONTENT_UI_STATE_COLUMN = 14;
1263        public static final int CONTENT_UI_DESTINATION_COLUMN = 15;
1264        public static final int CONTENT_UI_DOWNLOADED_SIZE_COLUMN = 16;
1265        public static final String[] CONTENT_PROJECTION = new String[] {
1266            RECORD_ID, AttachmentColumns.FILENAME, AttachmentColumns.MIME_TYPE,
1267            AttachmentColumns.SIZE, AttachmentColumns.CONTENT_ID, AttachmentColumns.CONTENT_URI,
1268            AttachmentColumns.CACHED_FILE, AttachmentColumns.MESSAGE_KEY,
1269            AttachmentColumns.LOCATION, AttachmentColumns.ENCODING, AttachmentColumns.CONTENT,
1270            AttachmentColumns.FLAGS, AttachmentColumns.CONTENT_BYTES, AttachmentColumns.ACCOUNT_KEY,
1271            AttachmentColumns.UI_STATE, AttachmentColumns.UI_DESTINATION,
1272            AttachmentColumns.UI_DOWNLOADED_SIZE
1273        };
1274
1275        // All attachments with an empty URI, regardless of mailbox
1276        public static final String PRECACHE_SELECTION =
1277            AttachmentColumns.CONTENT_URI + " isnull AND " + Attachment.FLAGS + "=0";
1278        // Attachments with an empty URI that are in an inbox
1279        public static final String PRECACHE_INBOX_SELECTION =
1280            PRECACHE_SELECTION + " AND " + AttachmentColumns.MESSAGE_KEY + " IN ("
1281            +     "SELECT " + MessageColumns.ID + " FROM " + Message.TABLE_NAME
1282            +     " WHERE " + Message.ALL_INBOX_SELECTION
1283            +     ")";
1284
1285        // Bits used in mFlags
1286        // WARNING: AttachmentDownloadService relies on the fact that ALL of the flags below
1287        // disqualify attachments for precaching.  If you add a flag that does NOT disqualify an
1288        // attachment for precaching, you MUST change the PRECACHE_SELECTION definition above
1289
1290        // Instruct Rfc822Output to 1) not use Content-Disposition and 2) use multipart/alternative
1291        // with this attachment.  This is only valid if there is one and only one attachment and
1292        // that attachment has this flag set
1293        public static final int FLAG_ICS_ALTERNATIVE_PART = 1<<0;
1294        // Indicate that this attachment has been requested for downloading by the user; this is
1295        // the highest priority for attachment downloading
1296        public static final int FLAG_DOWNLOAD_USER_REQUEST = 1<<1;
1297        // Indicate that this attachment needs to be downloaded as part of an outgoing forwarded
1298        // message
1299        public static final int FLAG_DOWNLOAD_FORWARD = 1<<2;
1300        // Indicates that the attachment download failed in a non-recoverable manner
1301        public static final int FLAG_DOWNLOAD_FAILED = 1<<3;
1302        // Allow "room" for some additional download-related flags here
1303        // Indicates that the attachment will be smart-forwarded
1304        public static final int FLAG_SMART_FORWARD = 1<<8;
1305        // Indicates that the attachment cannot be forwarded due to a policy restriction
1306        public static final int FLAG_POLICY_DISALLOWS_DOWNLOAD = 1<<9;
1307        // Indicates that this is a dummy placeholder attachment.
1308        public static final int FLAG_DUMMY_ATTACHMENT = 1<<10;
1309
1310        /**
1311         * no public constructor since this is a utility class
1312         */
1313        public Attachment() {
1314            mBaseUri = CONTENT_URI;
1315        }
1316
1317        public void setCachedFileUri(String cachedFile) {
1318            mCachedFileUri = cachedFile;
1319        }
1320
1321        public String getCachedFileUri() {
1322            return mCachedFileUri;
1323        }
1324
1325        public void setContentUri(String contentUri) {
1326            mContentUri = contentUri;
1327        }
1328
1329        public String getContentUri() {
1330            if (mContentUri == null) return null; //
1331            // If we're not using the legacy prefix and the uri IS, we need to modify it
1332            if (!Attachment.sUsingLegacyPrefix &&
1333                    mContentUri.startsWith(Attachment.ATTACHMENT_PROVIDER_LEGACY_URI_PREFIX)) {
1334                // In an upgrade scenario, we may still have legacy attachment Uri's
1335                // Skip past content://
1336                int prefix = mContentUri.indexOf('/', 10);
1337                if (prefix > 0) {
1338                    // Create a proper uri string using the actual provider
1339                    return ATTACHMENT_PROVIDER_URI_PREFIX + "/" + mContentUri.substring(prefix);
1340                } else {
1341                    LogUtils.e("Attachment", "Improper contentUri format: " + mContentUri);
1342                    // Belt & suspenders; can't really happen
1343                    return mContentUri;
1344                }
1345            } else {
1346                return mContentUri;
1347            }
1348        }
1349
1350         /**
1351         * Restore an Attachment from the database, given its unique id
1352         * @param context
1353         * @param id
1354         * @return the instantiated Attachment
1355         */
1356        public static Attachment restoreAttachmentWithId(Context context, long id) {
1357            return EmailContent.restoreContentWithId(context, Attachment.class,
1358                    Attachment.CONTENT_URI, Attachment.CONTENT_PROJECTION, id);
1359        }
1360
1361        /**
1362         * Restore all the Attachments of a message given its messageId
1363         */
1364        public static Attachment[] restoreAttachmentsWithMessageId(Context context,
1365                long messageId) {
1366            Uri uri = ContentUris.withAppendedId(MESSAGE_ID_URI, messageId);
1367            Cursor c = context.getContentResolver().query(uri, CONTENT_PROJECTION,
1368                    null, null, null);
1369            try {
1370                int count = c.getCount();
1371                Attachment[] attachments = new Attachment[count];
1372                for (int i = 0; i < count; ++i) {
1373                    c.moveToNext();
1374                    Attachment attach = new Attachment();
1375                    attach.restore(c);
1376                    attachments[i] = attach;
1377                }
1378                return attachments;
1379            } finally {
1380                c.close();
1381            }
1382        }
1383
1384        /**
1385         * Creates a unique file in the external store by appending a hyphen
1386         * and a number to the given filename.
1387         * @param filename
1388         * @return a new File object, or null if one could not be created
1389         */
1390        public static File createUniqueFile(String filename) {
1391            // TODO Handle internal storage, as required
1392            if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
1393                File directory = Environment.getExternalStorageDirectory();
1394                File file = new File(directory, filename);
1395                if (!file.exists()) {
1396                    return file;
1397                }
1398                // Get the extension of the file, if any.
1399                int index = filename.lastIndexOf('.');
1400                String name = filename;
1401                String extension = "";
1402                if (index != -1) {
1403                    name = filename.substring(0, index);
1404                    extension = filename.substring(index);
1405                }
1406                for (int i = 2; i < Integer.MAX_VALUE; i++) {
1407                    file = new File(directory, name + '-' + i + extension);
1408                    if (!file.exists()) {
1409                        return file;
1410                    }
1411                }
1412                return null;
1413            }
1414            return null;
1415        }
1416
1417        @Override
1418        public void restore(Cursor cursor) {
1419            mBaseUri = CONTENT_URI;
1420            mId = cursor.getLong(CONTENT_ID_COLUMN);
1421            mFileName= cursor.getString(CONTENT_FILENAME_COLUMN);
1422            mMimeType = cursor.getString(CONTENT_MIME_TYPE_COLUMN);
1423            mSize = cursor.getLong(CONTENT_SIZE_COLUMN);
1424            mContentId = cursor.getString(CONTENT_CONTENT_ID_COLUMN);
1425            mContentUri = cursor.getString(CONTENT_CONTENT_URI_COLUMN);
1426            mCachedFileUri = cursor.getString(CONTENT_CACHED_FILE_COLUMN);
1427            mMessageKey = cursor.getLong(CONTENT_MESSAGE_ID_COLUMN);
1428            mLocation = cursor.getString(CONTENT_LOCATION_COLUMN);
1429            mEncoding = cursor.getString(CONTENT_ENCODING_COLUMN);
1430            mContent = cursor.getString(CONTENT_CONTENT_COLUMN);
1431            mFlags = cursor.getInt(CONTENT_FLAGS_COLUMN);
1432            mContentBytes = cursor.getBlob(CONTENT_CONTENT_BYTES_COLUMN);
1433            mAccountKey = cursor.getLong(CONTENT_ACCOUNT_KEY_COLUMN);
1434            mUiState = cursor.getInt(CONTENT_UI_STATE_COLUMN);
1435            mUiDestination = cursor.getInt(CONTENT_UI_DESTINATION_COLUMN);
1436            mUiDownloadedSize = cursor.getInt(CONTENT_UI_DOWNLOADED_SIZE_COLUMN);
1437        }
1438
1439        @Override
1440        public ContentValues toContentValues() {
1441            ContentValues values = new ContentValues();
1442            values.put(AttachmentColumns.FILENAME, mFileName);
1443            values.put(AttachmentColumns.MIME_TYPE, mMimeType);
1444            values.put(AttachmentColumns.SIZE, mSize);
1445            values.put(AttachmentColumns.CONTENT_ID, mContentId);
1446            values.put(AttachmentColumns.CONTENT_URI, mContentUri);
1447            values.put(AttachmentColumns.CACHED_FILE, mCachedFileUri);
1448            values.put(AttachmentColumns.MESSAGE_KEY, mMessageKey);
1449            values.put(AttachmentColumns.LOCATION, mLocation);
1450            values.put(AttachmentColumns.ENCODING, mEncoding);
1451            values.put(AttachmentColumns.CONTENT, mContent);
1452            values.put(AttachmentColumns.FLAGS, mFlags);
1453            values.put(AttachmentColumns.CONTENT_BYTES, mContentBytes);
1454            values.put(AttachmentColumns.ACCOUNT_KEY, mAccountKey);
1455            values.put(AttachmentColumns.UI_STATE, mUiState);
1456            values.put(AttachmentColumns.UI_DESTINATION, mUiDestination);
1457            values.put(AttachmentColumns.UI_DOWNLOADED_SIZE, mUiDownloadedSize);
1458            return values;
1459        }
1460
1461        @Override
1462        public int describeContents() {
1463             return 0;
1464        }
1465
1466        @Override
1467        public void writeToParcel(Parcel dest, int flags) {
1468            // mBaseUri is not parceled
1469            dest.writeLong(mId);
1470            dest.writeString(mFileName);
1471            dest.writeString(mMimeType);
1472            dest.writeLong(mSize);
1473            dest.writeString(mContentId);
1474            dest.writeString(mContentUri);
1475            dest.writeString(mCachedFileUri);
1476            dest.writeLong(mMessageKey);
1477            dest.writeString(mLocation);
1478            dest.writeString(mEncoding);
1479            dest.writeString(mContent);
1480            dest.writeInt(mFlags);
1481            dest.writeLong(mAccountKey);
1482            if (mContentBytes == null) {
1483                dest.writeInt(-1);
1484            } else {
1485                dest.writeInt(mContentBytes.length);
1486                dest.writeByteArray(mContentBytes);
1487            }
1488            dest.writeInt(mUiState);
1489            dest.writeInt(mUiDestination);
1490            dest.writeInt(mUiDownloadedSize);
1491        }
1492
1493        public Attachment(Parcel in) {
1494            mBaseUri = EmailContent.Attachment.CONTENT_URI;
1495            mId = in.readLong();
1496            mFileName = in.readString();
1497            mMimeType = in.readString();
1498            mSize = in.readLong();
1499            mContentId = in.readString();
1500            mContentUri = in.readString();
1501            mCachedFileUri = in.readString();
1502            mMessageKey = in.readLong();
1503            mLocation = in.readString();
1504            mEncoding = in.readString();
1505            mContent = in.readString();
1506            mFlags = in.readInt();
1507            mAccountKey = in.readLong();
1508            final int contentBytesLen = in.readInt();
1509            if (contentBytesLen == -1) {
1510                mContentBytes = null;
1511            } else {
1512                mContentBytes = new byte[contentBytesLen];
1513                in.readByteArray(mContentBytes);
1514            }
1515            mUiState = in.readInt();
1516            mUiDestination = in.readInt();
1517            mUiDownloadedSize = in.readInt();
1518         }
1519
1520        public static final Parcelable.Creator<EmailContent.Attachment> CREATOR
1521                = new Parcelable.Creator<EmailContent.Attachment>() {
1522            @Override
1523            public EmailContent.Attachment createFromParcel(Parcel in) {
1524                return new EmailContent.Attachment(in);
1525            }
1526
1527            @Override
1528            public EmailContent.Attachment[] newArray(int size) {
1529                return new EmailContent.Attachment[size];
1530            }
1531        };
1532
1533        @Override
1534        public String toString() {
1535            return "[" + mFileName + ", " + mMimeType + ", " + mSize + ", " + mContentId + ", "
1536                    + mContentUri + ", " + mCachedFileUri + ", " + mMessageKey + ", "
1537                    + mLocation + ", " + mEncoding  + ", " + mFlags + ", " + mContentBytes + ", "
1538                    + mAccountKey +  "," + mUiState + "," + mUiDestination + ","
1539                    + mUiDownloadedSize + "]";
1540        }
1541    }
1542
1543    public interface AccountColumns {
1544        public static final String ID = "_id";
1545        // The display name of the account (user-settable)
1546        public static final String DISPLAY_NAME = "displayName";
1547        // The email address corresponding to this account
1548        public static final String EMAIL_ADDRESS = "emailAddress";
1549        // A server-based sync key on an account-wide basis (EAS needs this)
1550        public static final String SYNC_KEY = "syncKey";
1551        // The default sync lookback period for this account
1552        public static final String SYNC_LOOKBACK = "syncLookback";
1553        // The default sync frequency for this account, in minutes
1554        public static final String SYNC_INTERVAL = "syncInterval";
1555        // A foreign key into the account manager, having host, login, password, port, and ssl flags
1556        public static final String HOST_AUTH_KEY_RECV = "hostAuthKeyRecv";
1557        // (optional) A foreign key into the account manager, having host, login, password, port,
1558        // and ssl flags
1559        public static final String HOST_AUTH_KEY_SEND = "hostAuthKeySend";
1560        // Flags
1561        public static final String FLAGS = "flags";
1562        /**
1563         * Default account
1564         *
1565         * @deprecated This should never be used any more, as default accounts are handled
1566         *             differently now
1567         */
1568        @Deprecated
1569        public static final String IS_DEFAULT = "isDefault";
1570        // Old-Style UUID for compatibility with previous versions
1571        public static final String COMPATIBILITY_UUID = "compatibilityUuid";
1572        // User name (for outgoing messages)
1573        public static final String SENDER_NAME = "senderName";
1574        /**
1575         * Ringtone
1576         *
1577         * @deprecated Only used for creating the database (legacy reasons) and migration.
1578         */
1579        @Deprecated
1580        public static final String RINGTONE_URI = "ringtoneUri";
1581        // Protocol version (arbitrary string, used by EAS currently)
1582        public static final String PROTOCOL_VERSION = "protocolVersion";
1583        // The number of new messages (reported by the sync/download engines
1584        public static final String NEW_MESSAGE_COUNT = "newMessageCount";
1585        // Legacy flags defining security (provisioning) requirements of this account; this
1586        // information is now found in the Policy table; POLICY_KEY (below) is the foreign key
1587        @Deprecated
1588        public static final String SECURITY_FLAGS = "securityFlags";
1589        // Server-based sync key for the security policies currently enforced
1590        public static final String SECURITY_SYNC_KEY = "securitySyncKey";
1591        // Signature to use with this account
1592        public static final String SIGNATURE = "signature";
1593        // A foreign key into the Policy table
1594        public static final String POLICY_KEY = "policyKey";
1595        // Current duration of the Exchange ping
1596        public static final String PING_DURATION = "pingDuration";
1597    }
1598
1599    public interface QuickResponseColumns {
1600        static final String ID = "_id";
1601        // The QuickResponse text
1602        static final String TEXT = "quickResponse";
1603        // A foreign key into the Account table owning the QuickResponse
1604        static final String ACCOUNT_KEY = "accountKey";
1605    }
1606
1607    public interface MailboxColumns {
1608        public static final String ID = "_id";
1609        // The display name of this mailbox [INDEX]
1610        static final String DISPLAY_NAME = "displayName";
1611        // The server's identifier for this mailbox
1612        public static final String SERVER_ID = "serverId";
1613        // The server's identifier for the parent of this mailbox (null = top-level)
1614        public static final String PARENT_SERVER_ID = "parentServerId";
1615        // A foreign key for the parent of this mailbox (-1 = top-level, 0=uninitialized)
1616        public static final String PARENT_KEY = "parentKey";
1617        // A foreign key to the Account that owns this mailbox
1618        public static final String ACCOUNT_KEY = "accountKey";
1619        // The type (role) of this mailbox
1620        public static final String TYPE = "type";
1621        // The hierarchy separator character
1622        public static final String DELIMITER = "delimiter";
1623        // Server-based sync key or validity marker (e.g. "SyncKey" for EAS, "uidvalidity" for IMAP)
1624        public static final String SYNC_KEY = "syncKey";
1625        // The sync lookback period for this mailbox (or null if using the account default)
1626        public static final String SYNC_LOOKBACK = "syncLookback";
1627        // The sync frequency for this mailbox (or null if using the account default)
1628        public static final String SYNC_INTERVAL = "syncInterval";
1629        // The time of last successful sync completion (millis)
1630        public static final String SYNC_TIME = "syncTime";
1631        // Cached unread count
1632        public static final String UNREAD_COUNT = "unreadCount";
1633        // Visibility of this folder in a list of folders [INDEX]
1634        public static final String FLAG_VISIBLE = "flagVisible";
1635        // Other states, as a bit field, e.g. CHILDREN_VISIBLE, HAS_CHILDREN
1636        public static final String FLAGS = "flags";
1637        // Backward compatible
1638        @Deprecated
1639        public static final String VISIBLE_LIMIT = "visibleLimit";
1640        // Sync status (can be used as desired by sync services)
1641        public static final String SYNC_STATUS = "syncStatus";
1642        // Number of messages locally available in the mailbox.
1643        public static final String MESSAGE_COUNT = "messageCount";
1644        // The last time a message in this mailbox has been read (in millis)
1645        public static final String LAST_TOUCHED_TIME = "lastTouchedTime";
1646        // The UIProvider sync status
1647        public static final String UI_SYNC_STATUS = "uiSyncStatus";
1648        // The UIProvider last sync result
1649        public static final String UI_LAST_SYNC_RESULT = "uiLastSyncResult";
1650        /**
1651         * The UIProvider sync status
1652         *
1653         * @deprecated This is no longer used by anything except for creating the database.
1654         */
1655        @Deprecated
1656        public static final String LAST_NOTIFIED_MESSAGE_KEY = "lastNotifiedMessageKey";
1657        /**
1658         * The UIProvider last sync result
1659        *
1660        * @deprecated This is no longer used by anything except for creating the database.
1661        */
1662       @Deprecated
1663        public static final String LAST_NOTIFIED_MESSAGE_COUNT = "lastNotifiedMessageCount";
1664        // The total number of messages in the remote mailbox
1665        public static final String TOTAL_COUNT = "totalCount";
1666        // The full hierarchical name of this folder, in the form a/b/c
1667        public static final String HIERARCHICAL_NAME = "hierarchicalName";
1668        // The last time that we did a full sync. Set from SystemClock.elapsedRealtime().
1669        public static final String LAST_FULL_SYNC_TIME = "lastFullSyncTime";
1670    }
1671
1672    public interface HostAuthColumns {
1673        public static final String ID = "_id";
1674        // The protocol (e.g. "imap", "pop3", "eas", "smtp"
1675        static final String PROTOCOL = "protocol";
1676        // The host address
1677        static final String ADDRESS = "address";
1678        // The port to use for the connection
1679        static final String PORT = "port";
1680        // General purpose flags
1681        static final String FLAGS = "flags";
1682        // The login (user name)
1683        static final String LOGIN = "login";
1684        // Password
1685        static final String PASSWORD = "password";
1686        // A domain or path, if required (used in IMAP and EAS)
1687        static final String DOMAIN = "domain";
1688        // An alias to a local client certificate for SSL
1689        static final String CLIENT_CERT_ALIAS = "certAlias";
1690        // DEPRECATED - Will not be set or stored
1691        static final String ACCOUNT_KEY = "accountKey";
1692        // A blob containing an X509 server certificate
1693        static final String SERVER_CERT = "serverCert";
1694    }
1695
1696    public interface PolicyColumns {
1697        public static final String ID = "_id";
1698        public static final String PASSWORD_MODE = "passwordMode";
1699        public static final String PASSWORD_MIN_LENGTH = "passwordMinLength";
1700        public static final String PASSWORD_EXPIRATION_DAYS = "passwordExpirationDays";
1701        public static final String PASSWORD_HISTORY = "passwordHistory";
1702        public static final String PASSWORD_COMPLEX_CHARS = "passwordComplexChars";
1703        public static final String PASSWORD_MAX_FAILS = "passwordMaxFails";
1704        public static final String MAX_SCREEN_LOCK_TIME = "maxScreenLockTime";
1705        public static final String REQUIRE_REMOTE_WIPE = "requireRemoteWipe";
1706        public static final String REQUIRE_ENCRYPTION = "requireEncryption";
1707        public static final String REQUIRE_ENCRYPTION_EXTERNAL = "requireEncryptionExternal";
1708        // ICS additions
1709        // Note: the appearance of these columns does not imply that we support these features; only
1710        // that we store them in the Policy structure
1711        public static final String REQUIRE_MANUAL_SYNC_WHEN_ROAMING = "requireManualSyncRoaming";
1712        public static final String DONT_ALLOW_CAMERA = "dontAllowCamera";
1713        public static final String DONT_ALLOW_ATTACHMENTS = "dontAllowAttachments";
1714        public static final String DONT_ALLOW_HTML = "dontAllowHtml";
1715        public static final String MAX_ATTACHMENT_SIZE = "maxAttachmentSize";
1716        public static final String MAX_TEXT_TRUNCATION_SIZE = "maxTextTruncationSize";
1717        public static final String MAX_HTML_TRUNCATION_SIZE = "maxHTMLTruncationSize";
1718        public static final String MAX_EMAIL_LOOKBACK = "maxEmailLookback";
1719        public static final String MAX_CALENDAR_LOOKBACK = "maxCalendarLookback";
1720        // Indicates that the server allows password recovery, not that we support it
1721        public static final String PASSWORD_RECOVERY_ENABLED = "passwordRecoveryEnabled";
1722        // Tokenized strings indicating protocol specific policies enforced/unsupported
1723        public static final String PROTOCOL_POLICIES_ENFORCED = "protocolPoliciesEnforced";
1724        public static final String PROTOCOL_POLICIES_UNSUPPORTED = "protocolPoliciesUnsupported";
1725    }
1726}
1727