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