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