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