• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
toContentValues()105     public abstract ContentValues toContentValues();
106     // Read the Content from a ContentCursor
restore(Cursor cursor)107     public abstract void restore (Cursor cursor);
108 
109     // The Uri is lazily initialized
getUri()110     public Uri getUri() {
111         if (mUri == null) {
112             mUri = ContentUris.withAppendedId(mBaseUri, mId);
113         }
114         return mUri;
115     }
116 
isSaved()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      */
restoreContentWithId(Context context, Class<T> klass, Uri contentUri, String[] contentProjection, long id)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
getContent(Cursor cursor, Class<T> klass)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 
save(Context context)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 
update(Context context, ContentValues contentValues)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 
update(Context context, Uri baseUri, long id, ContentValues contentValues)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 
delete(Context context, Uri baseUri, long id)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      */
count(Context context, Uri uri, String selection, String[] selectionArgs)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      */
count(Context context, Uri uri)206     static public int count(Context context, Uri uri) {
207         return count(context, uri, null, null);
208     }
209 
uriWithLimit(Uri uri, int limit)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      */
EmailContent()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 
Body()306         public Body() {
307             mBaseUri = CONTENT_URI;
308         }
309 
310         @Override
toContentValues()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          */
restoreBodyWithCursor(Cursor cursor)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 
restoreBodyWithId(Context context, long id)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 
restoreBodyWithMessageId(Context context, long messageId)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          */
lookupBodyIdWithMessageId(Context context, long messageId)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          */
updateBodyWithMessageId(Context context, long messageId, ContentValues values)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
restoreBodySourceKey(Context context, long messageId)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 
restoreTextWithMessageId(Context context, long messageId, String[] projection)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 
restoreBodyTextWithMessageId(Context context, long messageId)410         public static String restoreBodyTextWithMessageId(Context context, long messageId) {
411             return restoreTextWithMessageId(context, messageId, Body.COMMON_PROJECTION_TEXT);
412         }
413 
restoreBodyHtmlWithMessageId(Context context, long messageId)414         public static String restoreBodyHtmlWithMessageId(Context context, long messageId) {
415             return restoreTextWithMessageId(context, messageId, Body.COMMON_PROJECTION_HTML);
416         }
417 
restoreReplyTextWithMessageId(Context context, long messageId)418         public static String restoreReplyTextWithMessageId(Context context, long messageId) {
419             return restoreTextWithMessageId(context, messageId, Body.COMMON_PROJECTION_REPLY_TEXT);
420         }
421 
restoreReplyHtmlWithMessageId(Context context, long messageId)422         public static String restoreReplyHtmlWithMessageId(Context context, long messageId) {
423             return restoreTextWithMessageId(context, messageId, Body.COMMON_PROJECTION_REPLY_HTML);
424         }
425 
restoreIntroTextWithMessageId(Context context, long messageId)426         public static String restoreIntroTextWithMessageId(Context context, long messageId) {
427             return restoreTextWithMessageId(context, messageId, Body.COMMON_PROJECTION_INTRO);
428         }
429 
430         @Override
restore(Cursor cursor)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 
update()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 
Message()753         public Message() {
754             mBaseUri = CONTENT_URI;
755         }
756 
757         @Override
toContentValues()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 
restoreMessageWithId(Context context, long id)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
restore(Cursor cursor)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 
update()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
save(Context context)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 
addSaveOps(ArrayList<ContentProviderOperation> ops)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          */
getFavoriteMessageCount(Context context)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          */
getFavoriteMessageCount(Context context, long accountId)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 
getKeyColumnLong(Context context, long messageId, String column)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          */
buildMessageListSelection(Context context, long mailboxId)968         public static String buildMessageListSelection(Context context, long mailboxId) {
969 
970             if (mailboxId == Mailbox.QUERY_ALL_INBOXES) {
971                 return Message.ALL_INBOX_SELECTION;
972             }
973             if (mailboxId == Mailbox.QUERY_ALL_DRAFTS) {
974                 return Message.ALL_DRAFT_SELECTION;
975             }
976             if (mailboxId == Mailbox.QUERY_ALL_OUTBOX) {
977                 return Message.ALL_OUTBOX_SELECTION;
978             }
979             if (mailboxId == Mailbox.QUERY_ALL_UNREAD) {
980                 return Message.ALL_UNREAD_SELECTION;
981             }
982             if (mailboxId == Mailbox.QUERY_ALL_FAVORITES) {
983                 return Message.ALL_FAVORITE_SELECTION;
984             }
985 
986             // Now it's a regular mailbox.
987             final StringBuilder selection = new StringBuilder();
988 
989             selection.append(MessageColumns.MAILBOX_KEY).append('=').append(mailboxId);
990 
991             if (Mailbox.getMailboxType(context, mailboxId) != Mailbox.TYPE_OUTBOX) {
992                 selection.append(" AND ").append(Message.FLAG_LOADED_SELECTION);
993             }
994             return selection.toString();
995         }
996     }
997 
998     public interface AttachmentColumns {
999         public static final String ID = "_id";
1000         // The display name of the attachment
1001         public static final String FILENAME = "fileName";
1002         // The mime type of the attachment
1003         public static final String MIME_TYPE = "mimeType";
1004         // The size of the attachment in bytes
1005         public static final String SIZE = "size";
1006         // The (internal) contentId of the attachment (inline attachments will have these)
1007         public static final String CONTENT_ID = "contentId";
1008         // The location of the loaded attachment (probably a file)
1009         @SuppressWarnings("hiding")
1010         public static final String CONTENT_URI = "contentUri";
1011         // A foreign key into the Message table (the message owning this attachment)
1012         public static final String MESSAGE_KEY = "messageKey";
1013         // The location of the attachment on the server side
1014         // For IMAP, this is a part number (e.g. 2.1); for EAS, it's the internal file name
1015         public static final String LOCATION = "location";
1016         // The transfer encoding of the attachment
1017         public static final String ENCODING = "encoding";
1018         // Not currently used
1019         public static final String CONTENT = "content";
1020         // Flags
1021         public static final String FLAGS = "flags";
1022         // Content that is actually contained in the Attachment row
1023         public static final String CONTENT_BYTES = "content_bytes";
1024         // A foreign key into the Account table (for the message owning this attachment)
1025         public static final String ACCOUNT_KEY = "accountKey";
1026     }
1027 
1028     public static final class Attachment extends EmailContent
1029             implements AttachmentColumns, Parcelable {
1030         public static final String TABLE_NAME = "Attachment";
1031         @SuppressWarnings("hiding")
1032         public static final Uri CONTENT_URI = Uri.parse(EmailContent.CONTENT_URI + "/attachment");
1033         // This must be used with an appended id: ContentUris.withAppendedId(MESSAGE_ID_URI, id)
1034         public static final Uri MESSAGE_ID_URI = Uri.parse(
1035                 EmailContent.CONTENT_URI + "/attachment/message");
1036 
1037         public String mFileName;
1038         public String mMimeType;
1039         public long mSize;
1040         public String mContentId;
1041         public String mContentUri;
1042         public long mMessageKey;
1043         public String mLocation;
1044         public String mEncoding;
1045         public String mContent; // Not currently used
1046         public int mFlags;
1047         public byte[] mContentBytes;
1048         public long mAccountKey;
1049 
1050         public static final int CONTENT_ID_COLUMN = 0;
1051         public static final int CONTENT_FILENAME_COLUMN = 1;
1052         public static final int CONTENT_MIME_TYPE_COLUMN = 2;
1053         public static final int CONTENT_SIZE_COLUMN = 3;
1054         public static final int CONTENT_CONTENT_ID_COLUMN = 4;
1055         public static final int CONTENT_CONTENT_URI_COLUMN = 5;
1056         public static final int CONTENT_MESSAGE_ID_COLUMN = 6;
1057         public static final int CONTENT_LOCATION_COLUMN = 7;
1058         public static final int CONTENT_ENCODING_COLUMN = 8;
1059         public static final int CONTENT_CONTENT_COLUMN = 9; // Not currently used
1060         public static final int CONTENT_FLAGS_COLUMN = 10;
1061         public static final int CONTENT_CONTENT_BYTES_COLUMN = 11;
1062         public static final int CONTENT_ACCOUNT_KEY_COLUMN = 12;
1063         public static final String[] CONTENT_PROJECTION = new String[] {
1064             RECORD_ID, AttachmentColumns.FILENAME, AttachmentColumns.MIME_TYPE,
1065             AttachmentColumns.SIZE, AttachmentColumns.CONTENT_ID, AttachmentColumns.CONTENT_URI,
1066             AttachmentColumns.MESSAGE_KEY, AttachmentColumns.LOCATION, AttachmentColumns.ENCODING,
1067             AttachmentColumns.CONTENT, AttachmentColumns.FLAGS, AttachmentColumns.CONTENT_BYTES,
1068             AttachmentColumns.ACCOUNT_KEY
1069         };
1070 
1071         // All attachments with an empty URI, regardless of mailbox
1072         public static final String PRECACHE_SELECTION =
1073             AttachmentColumns.CONTENT_URI + " isnull AND " + Attachment.FLAGS + "=0";
1074         // Attachments with an empty URI that are in an inbox
1075         public static final String PRECACHE_INBOX_SELECTION =
1076             PRECACHE_SELECTION + " AND " + AttachmentColumns.MESSAGE_KEY + " IN ("
1077             +     "SELECT " + MessageColumns.ID + " FROM " + Message.TABLE_NAME
1078             +     " WHERE " + Message.ALL_INBOX_SELECTION
1079             +     ")";
1080 
1081         // Bits used in mFlags
1082         // WARNING: AttachmentDownloadService relies on the fact that ALL of the flags below
1083         // disqualify attachments for precaching.  If you add a flag that does NOT disqualify an
1084         // attachment for precaching, you MUST change the PRECACHE_SELECTION definition above
1085 
1086         // Instruct Rfc822Output to 1) not use Content-Disposition and 2) use multipart/alternative
1087         // with this attachment.  This is only valid if there is one and only one attachment and
1088         // that attachment has this flag set
1089         public static final int FLAG_ICS_ALTERNATIVE_PART = 1<<0;
1090         // Indicate that this attachment has been requested for downloading by the user; this is
1091         // the highest priority for attachment downloading
1092         public static final int FLAG_DOWNLOAD_USER_REQUEST = 1<<1;
1093         // Indicate that this attachment needs to be downloaded as part of an outgoing forwarded
1094         // message
1095         public static final int FLAG_DOWNLOAD_FORWARD = 1<<2;
1096         // Indicates that the attachment download failed in a non-recoverable manner
1097         public static final int FLAG_DOWNLOAD_FAILED = 1<<3;
1098         // Allow "room" for some additional download-related flags here
1099         // Indicates that the attachment will be smart-forwarded
1100         public static final int FLAG_SMART_FORWARD = 1<<8;
1101         // Indicates that the attachment cannot be forwarded due to a policy restriction
1102         public static final int FLAG_POLICY_DISALLOWS_DOWNLOAD = 1<<9;
1103         /**
1104          * no public constructor since this is a utility class
1105          */
Attachment()1106         public Attachment() {
1107             mBaseUri = CONTENT_URI;
1108         }
1109 
1110          /**
1111          * Restore an Attachment from the database, given its unique id
1112          * @param context
1113          * @param id
1114          * @return the instantiated Attachment
1115          */
restoreAttachmentWithId(Context context, long id)1116         public static Attachment restoreAttachmentWithId(Context context, long id) {
1117             return EmailContent.restoreContentWithId(context, Attachment.class,
1118                     Attachment.CONTENT_URI, Attachment.CONTENT_PROJECTION, id);
1119         }
1120 
1121         /**
1122          * Restore all the Attachments of a message given its messageId
1123          */
restoreAttachmentsWithMessageId(Context context, long messageId)1124         public static Attachment[] restoreAttachmentsWithMessageId(Context context,
1125                 long messageId) {
1126             Uri uri = ContentUris.withAppendedId(MESSAGE_ID_URI, messageId);
1127             Cursor c = context.getContentResolver().query(uri, CONTENT_PROJECTION,
1128                     null, null, null);
1129             try {
1130                 int count = c.getCount();
1131                 Attachment[] attachments = new Attachment[count];
1132                 for (int i = 0; i < count; ++i) {
1133                     c.moveToNext();
1134                     Attachment attach = new Attachment();
1135                     attach.restore(c);
1136                     attachments[i] = attach;
1137                 }
1138                 return attachments;
1139             } finally {
1140                 c.close();
1141             }
1142         }
1143 
1144         /**
1145          * Creates a unique file in the external store by appending a hyphen
1146          * and a number to the given filename.
1147          * @param filename
1148          * @return a new File object, or null if one could not be created
1149          */
createUniqueFile(String filename)1150         public static File createUniqueFile(String filename) {
1151             // TODO Handle internal storage, as required
1152             if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
1153                 File directory = Environment.getExternalStorageDirectory();
1154                 File file = new File(directory, filename);
1155                 if (!file.exists()) {
1156                     return file;
1157                 }
1158                 // Get the extension of the file, if any.
1159                 int index = filename.lastIndexOf('.');
1160                 String name = filename;
1161                 String extension = "";
1162                 if (index != -1) {
1163                     name = filename.substring(0, index);
1164                     extension = filename.substring(index);
1165                 }
1166                 for (int i = 2; i < Integer.MAX_VALUE; i++) {
1167                     file = new File(directory, name + '-' + i + extension);
1168                     if (!file.exists()) {
1169                         return file;
1170                     }
1171                 }
1172                 return null;
1173             }
1174             return null;
1175         }
1176 
1177         @Override
restore(Cursor cursor)1178         public void restore(Cursor cursor) {
1179             mBaseUri = CONTENT_URI;
1180             mId = cursor.getLong(CONTENT_ID_COLUMN);
1181             mFileName= cursor.getString(CONTENT_FILENAME_COLUMN);
1182             mMimeType = cursor.getString(CONTENT_MIME_TYPE_COLUMN);
1183             mSize = cursor.getLong(CONTENT_SIZE_COLUMN);
1184             mContentId = cursor.getString(CONTENT_CONTENT_ID_COLUMN);
1185             mContentUri = cursor.getString(CONTENT_CONTENT_URI_COLUMN);
1186             mMessageKey = cursor.getLong(CONTENT_MESSAGE_ID_COLUMN);
1187             mLocation = cursor.getString(CONTENT_LOCATION_COLUMN);
1188             mEncoding = cursor.getString(CONTENT_ENCODING_COLUMN);
1189             mContent = cursor.getString(CONTENT_CONTENT_COLUMN);
1190             mFlags = cursor.getInt(CONTENT_FLAGS_COLUMN);
1191             mContentBytes = cursor.getBlob(CONTENT_CONTENT_BYTES_COLUMN);
1192             mAccountKey = cursor.getLong(CONTENT_ACCOUNT_KEY_COLUMN);
1193         }
1194 
1195         @Override
toContentValues()1196         public ContentValues toContentValues() {
1197             ContentValues values = new ContentValues();
1198             values.put(AttachmentColumns.FILENAME, mFileName);
1199             values.put(AttachmentColumns.MIME_TYPE, mMimeType);
1200             values.put(AttachmentColumns.SIZE, mSize);
1201             values.put(AttachmentColumns.CONTENT_ID, mContentId);
1202             values.put(AttachmentColumns.CONTENT_URI, mContentUri);
1203             values.put(AttachmentColumns.MESSAGE_KEY, mMessageKey);
1204             values.put(AttachmentColumns.LOCATION, mLocation);
1205             values.put(AttachmentColumns.ENCODING, mEncoding);
1206             values.put(AttachmentColumns.CONTENT, mContent);
1207             values.put(AttachmentColumns.FLAGS, mFlags);
1208             values.put(AttachmentColumns.CONTENT_BYTES, mContentBytes);
1209             values.put(AttachmentColumns.ACCOUNT_KEY, mAccountKey);
1210             return values;
1211         }
1212 
1213         @Override
describeContents()1214         public int describeContents() {
1215              return 0;
1216         }
1217 
1218         @Override
writeToParcel(Parcel dest, int flags)1219         public void writeToParcel(Parcel dest, int flags) {
1220             // mBaseUri is not parceled
1221             dest.writeLong(mId);
1222             dest.writeString(mFileName);
1223             dest.writeString(mMimeType);
1224             dest.writeLong(mSize);
1225             dest.writeString(mContentId);
1226             dest.writeString(mContentUri);
1227             dest.writeLong(mMessageKey);
1228             dest.writeString(mLocation);
1229             dest.writeString(mEncoding);
1230             dest.writeString(mContent);
1231             dest.writeInt(mFlags);
1232             dest.writeLong(mAccountKey);
1233             if (mContentBytes == null) {
1234                 dest.writeInt(-1);
1235             } else {
1236                 dest.writeInt(mContentBytes.length);
1237                 dest.writeByteArray(mContentBytes);
1238             }
1239         }
1240 
Attachment(Parcel in)1241         public Attachment(Parcel in) {
1242             mBaseUri = EmailContent.Attachment.CONTENT_URI;
1243             mId = in.readLong();
1244             mFileName = in.readString();
1245             mMimeType = in.readString();
1246             mSize = in.readLong();
1247             mContentId = in.readString();
1248             mContentUri = in.readString();
1249             mMessageKey = in.readLong();
1250             mLocation = in.readString();
1251             mEncoding = in.readString();
1252             mContent = in.readString();
1253             mFlags = in.readInt();
1254             mAccountKey = in.readLong();
1255             final int contentBytesLen = in.readInt();
1256             if (contentBytesLen == -1) {
1257                 mContentBytes = null;
1258             } else {
1259                 mContentBytes = new byte[contentBytesLen];
1260                 in.readByteArray(mContentBytes);
1261             }
1262          }
1263 
1264         public static final Parcelable.Creator<EmailContent.Attachment> CREATOR
1265                 = new Parcelable.Creator<EmailContent.Attachment>() {
1266             @Override
1267             public EmailContent.Attachment createFromParcel(Parcel in) {
1268                 return new EmailContent.Attachment(in);
1269             }
1270 
1271             @Override
1272             public EmailContent.Attachment[] newArray(int size) {
1273                 return new EmailContent.Attachment[size];
1274             }
1275         };
1276 
1277         @Override
toString()1278         public String toString() {
1279             return "[" + mFileName + ", " + mMimeType + ", " + mSize + ", " + mContentId + ", "
1280                     + mContentUri + ", " + mMessageKey + ", " + mLocation + ", " + mEncoding  + ", "
1281                     + mFlags + ", " + mContentBytes + ", " + mAccountKey + "]";
1282         }
1283     }
1284 
1285     public interface AccountColumns {
1286         public static final String ID = "_id";
1287         // The display name of the account (user-settable)
1288         public static final String DISPLAY_NAME = "displayName";
1289         // The email address corresponding to this account
1290         public static final String EMAIL_ADDRESS = "emailAddress";
1291         // A server-based sync key on an account-wide basis (EAS needs this)
1292         public static final String SYNC_KEY = "syncKey";
1293         // The default sync lookback period for this account
1294         public static final String SYNC_LOOKBACK = "syncLookback";
1295         // The default sync frequency for this account, in minutes
1296         public static final String SYNC_INTERVAL = "syncInterval";
1297         // A foreign key into the account manager, having host, login, password, port, and ssl flags
1298         public static final String HOST_AUTH_KEY_RECV = "hostAuthKeyRecv";
1299         // (optional) A foreign key into the account manager, having host, login, password, port,
1300         // and ssl flags
1301         public static final String HOST_AUTH_KEY_SEND = "hostAuthKeySend";
1302         // Flags
1303         public static final String FLAGS = "flags";
1304         // Default account
1305         public static final String IS_DEFAULT = "isDefault";
1306         // Old-Style UUID for compatibility with previous versions
1307         public static final String COMPATIBILITY_UUID = "compatibilityUuid";
1308         // User name (for outgoing messages)
1309         public static final String SENDER_NAME = "senderName";
1310         // Ringtone
1311         public static final String RINGTONE_URI = "ringtoneUri";
1312         // Protocol version (arbitrary string, used by EAS currently)
1313         public static final String PROTOCOL_VERSION = "protocolVersion";
1314         // The number of new messages (reported by the sync/download engines
1315         public static final String NEW_MESSAGE_COUNT = "newMessageCount";
1316         // Legacy flags defining security (provisioning) requirements of this account; this
1317         // information is now found in the Policy table; POLICY_KEY (below) is the foreign key
1318         @Deprecated
1319         public static final String SECURITY_FLAGS = "securityFlags";
1320         // Server-based sync key for the security policies currently enforced
1321         public static final String SECURITY_SYNC_KEY = "securitySyncKey";
1322         // Signature to use with this account
1323         public static final String SIGNATURE = "signature";
1324         // A foreign key into the Policy table
1325         public static final String POLICY_KEY = "policyKey";
1326         // The last notified message id
1327         public static final String NOTIFIED_MESSAGE_ID = "notifiedMessageId";
1328         // The most recent notified message count
1329         public static final String NOTIFIED_MESSAGE_COUNT = "notifiedMessageCount";
1330     }
1331 
1332     public interface QuickResponseColumns {
1333         // The QuickResponse text
1334         static final String TEXT = "quickResponse";
1335         // A foreign key into the Account table owning the QuickResponse
1336         static final String ACCOUNT_KEY = "accountKey";
1337     }
1338 
1339     public interface MailboxColumns {
1340         public static final String ID = "_id";
1341         // The display name of this mailbox [INDEX]
1342         static final String DISPLAY_NAME = "displayName";
1343         // The server's identifier for this mailbox
1344         public static final String SERVER_ID = "serverId";
1345         // The server's identifier for the parent of this mailbox (null = top-level)
1346         public static final String PARENT_SERVER_ID = "parentServerId";
1347         // A foreign key for the parent of this mailbox (-1 = top-level, 0=uninitialized)
1348         public static final String PARENT_KEY = "parentKey";
1349         // A foreign key to the Account that owns this mailbox
1350         public static final String ACCOUNT_KEY = "accountKey";
1351         // The type (role) of this mailbox
1352         public static final String TYPE = "type";
1353         // The hierarchy separator character
1354         public static final String DELIMITER = "delimiter";
1355         // Server-based sync key or validity marker (e.g. "SyncKey" for EAS, "uidvalidity" for IMAP)
1356         public static final String SYNC_KEY = "syncKey";
1357         // The sync lookback period for this mailbox (or null if using the account default)
1358         public static final String SYNC_LOOKBACK = "syncLookback";
1359         // The sync frequency for this mailbox (or null if using the account default)
1360         public static final String SYNC_INTERVAL = "syncInterval";
1361         // The time of last successful sync completion (millis)
1362         public static final String SYNC_TIME = "syncTime";
1363         // Cached unread count
1364         public static final String UNREAD_COUNT = "unreadCount";
1365         // Visibility of this folder in a list of folders [INDEX]
1366         public static final String FLAG_VISIBLE = "flagVisible";
1367         // Other states, as a bit field, e.g. CHILDREN_VISIBLE, HAS_CHILDREN
1368         public static final String FLAGS = "flags";
1369         // Backward compatible
1370         public static final String VISIBLE_LIMIT = "visibleLimit";
1371         // Sync status (can be used as desired by sync services)
1372         public static final String SYNC_STATUS = "syncStatus";
1373         // Number of messages in the mailbox.
1374         public static final String MESSAGE_COUNT = "messageCount";
1375         // Message ID of the last 'seen' message
1376         public static final String LAST_SEEN_MESSAGE_KEY = "lastSeenMessageKey";
1377         // The last time a message in this mailbox has been read (in millis)
1378         public static final String LAST_TOUCHED_TIME = "lastTouchedTime";
1379     }
1380 
1381     public interface HostAuthColumns {
1382         public static final String ID = "_id";
1383         // The protocol (e.g. "imap", "pop3", "eas", "smtp"
1384         static final String PROTOCOL = "protocol";
1385         // The host address
1386         static final String ADDRESS = "address";
1387         // The port to use for the connection
1388         static final String PORT = "port";
1389         // General purpose flags
1390         static final String FLAGS = "flags";
1391         // The login (user name)
1392         static final String LOGIN = "login";
1393         // Password
1394         static final String PASSWORD = "password";
1395         // A domain or path, if required (used in IMAP and EAS)
1396         static final String DOMAIN = "domain";
1397         // An alias to a local client certificate for SSL
1398         static final String CLIENT_CERT_ALIAS = "certAlias";
1399         // DEPRECATED - Will not be set or stored
1400         static final String ACCOUNT_KEY = "accountKey";
1401     }
1402 
1403     public interface PolicyColumns {
1404         public static final String ID = "_id";
1405         public static final String PASSWORD_MODE = "passwordMode";
1406         public static final String PASSWORD_MIN_LENGTH = "passwordMinLength";
1407         public static final String PASSWORD_EXPIRATION_DAYS = "passwordExpirationDays";
1408         public static final String PASSWORD_HISTORY = "passwordHistory";
1409         public static final String PASSWORD_COMPLEX_CHARS = "passwordComplexChars";
1410         public static final String PASSWORD_MAX_FAILS = "passwordMaxFails";
1411         public static final String MAX_SCREEN_LOCK_TIME = "maxScreenLockTime";
1412         public static final String REQUIRE_REMOTE_WIPE = "requireRemoteWipe";
1413         public static final String REQUIRE_ENCRYPTION = "requireEncryption";
1414         public static final String REQUIRE_ENCRYPTION_EXTERNAL = "requireEncryptionExternal";
1415         // ICS additions
1416         // Note: the appearance of these columns does not imply that we support these features; only
1417         // that we store them in the Policy structure
1418         public static final String REQUIRE_MANUAL_SYNC_WHEN_ROAMING = "requireManualSyncRoaming";
1419         public static final String DONT_ALLOW_CAMERA = "dontAllowCamera";
1420         public static final String DONT_ALLOW_ATTACHMENTS = "dontAllowAttachments";
1421         public static final String DONT_ALLOW_HTML = "dontAllowHtml";
1422         public static final String MAX_ATTACHMENT_SIZE = "maxAttachmentSize";
1423         public static final String MAX_TEXT_TRUNCATION_SIZE = "maxTextTruncationSize";
1424         public static final String MAX_HTML_TRUNCATION_SIZE = "maxHTMLTruncationSize";
1425         public static final String MAX_EMAIL_LOOKBACK = "maxEmailLookback";
1426         public static final String MAX_CALENDAR_LOOKBACK = "maxCalendarLookback";
1427         // Indicates that the server allows password recovery, not that we support it
1428         public static final String PASSWORD_RECOVERY_ENABLED = "passwordRecoveryEnabled";
1429     }
1430 }
1431