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