1/*
2 * Copyright (C) 2009 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.emailcommon.provider;
18
19import android.content.ContentProviderOperation;
20import android.content.ContentProviderResult;
21import android.content.ContentResolver;
22import android.content.ContentUris;
23import android.content.ContentValues;
24import android.content.Context;
25import android.content.OperationApplicationException;
26import android.content.res.Resources;
27import android.database.ContentObservable;
28import android.database.ContentObserver;
29import android.database.Cursor;
30import android.net.Uri;
31import android.os.Environment;
32import android.os.Looper;
33import android.os.Parcel;
34import android.os.Parcelable;
35import android.os.RemoteException;
36import android.provider.BaseColumns;
37
38import com.android.emailcommon.Logging;
39import com.android.emailcommon.R;
40import com.android.emailcommon.utility.TextUtilities;
41import com.android.emailcommon.utility.Utility;
42import com.android.mail.providers.UIProvider;
43import com.android.mail.utils.LogUtils;
44import com.google.common.annotations.VisibleForTesting;
45
46import org.apache.commons.io.IOUtils;
47
48import java.io.File;
49import java.io.IOException;
50import java.io.InputStream;
51import java.lang.ref.WeakReference;
52import 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 */
74public 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
131    public abstract ContentValues toContentValues();
132    // Read the Content from a ContentCursor
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.
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
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
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
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
214    public Uri getUri() {
215        if (mUri == null) {
216            mUri = ContentUris.withAppendedId(mBaseUri, mId);
217        }
218        return mUri;
219    }
220
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     */
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
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     */
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     */
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     */
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     */
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     */
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")
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
340        public SelfContentObserver(final EmailContent content) {
341            super(null);
342            mContent = new WeakReference<EmailContent>(content);
343        }
344
345        @Override
346        public boolean deliverSelfNotifications() {
347            return false;
348        }
349
350        @Override
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
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
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
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
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
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     */
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     */
419    static public int count(Context context, Uri uri) {
420        return count(context, uri, null, null);
421    }
422
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     */
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
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
506        public Body() {
507            mBaseUri = CONTENT_URI;
508        }
509
510        @Override
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         */
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
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         */
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         */
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
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
582        public static Uri getBodyTextUriForMessageWithId(long messageId) {
583            return EmailContent.CONTENT_URI.buildUpon()
584                    .appendPath("bodyText").appendPath(Long.toString(messageId)).build();
585        }
586
587        public static Uri getBodyHtmlUriForMessageWithId(long messageId) {
588            return EmailContent.CONTENT_URI.buildUpon()
589                    .appendPath("bodyHtml").appendPath(Long.toString(messageId)).build();
590        }
591
592        public static String restoreBodyTextWithMessageId(Context context, long messageId) {
593            return readBodyFromProvider(context,
594                    getBodyTextUriForMessageWithId(messageId).toString());
595        }
596
597        public static String restoreBodyHtmlWithMessageId(Context context, long messageId) {
598            return readBodyFromProvider(context,
599                    getBodyHtmlUriForMessageWithId(messageId).toString());
600        }
601
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
619        public void restore(final Cursor cursor) {
620            throw new UnsupportedOperationException("Must have context to restore Body object");
621        }
622
623        @Override
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
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
993        public Message() {
994            mBaseUri = CONTENT_URI;
995        }
996
997        @Override
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
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
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
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         */
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         */
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         */
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
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         */
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
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
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         */
1437        public Attachment() {
1438            mBaseUri = CONTENT_URI;
1439        }
1440
1441        public void setCachedFileUri(String cachedFile) {
1442            mCachedFileUri = cachedFile;
1443        }
1444
1445        public String getCachedFileUri() {
1446            return mCachedFileUri;
1447        }
1448
1449        public void setContentUri(String contentUri) {
1450            mContentUri = contentUri;
1451        }
1452
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         */
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         */
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         */
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
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
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
1586        public int describeContents() {
1587             return 0;
1588        }
1589
1590        @Override
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
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
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