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