• 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.email.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 java.io.File;
34 import java.net.URI;
35 import java.net.URISyntaxException;
36 import java.util.ArrayList;
37 import java.util.UUID;
38 
39 
40 /**
41  * EmailContent is the superclass of the various classes of content stored by EmailProvider.
42  *
43  * It is intended to include 1) column definitions for use with the Provider, and 2) convenience
44  * methods for saving and retrieving content from the Provider.
45  *
46  * This class will be used by 1) the Email process (which includes the application and
47  * EmaiLProvider) as well as 2) the Exchange process (which runs independently).  It will
48  * necessarily be cloned for use in these two cases.
49  *
50  * Conventions used in naming columns:
51  *   RECORD_ID is the primary key for all Email records
52  *   The SyncColumns interface is used by all classes that are synced to the server directly
53  *   (Mailbox and Email)
54  *
55  *   <name>_KEY always refers to a foreign key
56  *   <name>_ID always refers to a unique identifier (whether on client, server, etc.)
57  *
58  */
59 public abstract class EmailContent {
60     public static final String AUTHORITY = EmailProvider.EMAIL_AUTHORITY;
61     public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY);
62     // All classes share this
63     public static final String RECORD_ID = "_id";
64 
65     private static final String[] COUNT_COLUMNS = new String[]{"count(*)"};
66 
67     /**
68      * This projection can be used with any of the EmailContent classes, when all you need
69      * is a list of id's.  Use ID_PROJECTION_COLUMN to access the row data.
70      */
71     public static final String[] ID_PROJECTION = new String[] {
72         RECORD_ID
73     };
74     public static final int ID_PROJECTION_COLUMN = 0;
75 
76     public static final String FIELD_COLUMN_NAME = "field";
77     public static final String ADD_COLUMN_NAME = "add";
78 
79     // Newly created objects get this id
80     private static final int NOT_SAVED = -1;
81     // The base Uri that this piece of content came from
82     public Uri mBaseUri;
83     // Lazily initialized uri for this Content
84     private Uri mUri = null;
85     // The id of the Content
86     public long mId = NOT_SAVED;
87 
88     // Write the Content into a ContentValues container
toContentValues()89     public abstract ContentValues toContentValues();
90     // Read the Content from a ContentCursor
restore(Cursor cursor)91     public abstract <T extends EmailContent> T restore (Cursor cursor);
92 
93     // The Uri is lazily initialized
getUri()94     public Uri getUri() {
95         if (mUri == null) {
96             mUri = ContentUris.withAppendedId(mBaseUri, mId);
97         }
98         return mUri;
99     }
100 
isSaved()101     public boolean isSaved() {
102         return mId != NOT_SAVED;
103     }
104 
105     @SuppressWarnings("unchecked")
106     // The Content sub class must have a no-arg constructor
getContent(Cursor cursor, Class<T> klass)107     static public <T extends EmailContent> T getContent(Cursor cursor, Class<T> klass) {
108         try {
109             T content = klass.newInstance();
110             content.mId = cursor.getLong(0);
111             return (T)content.restore(cursor);
112         } catch (IllegalAccessException e) {
113             e.printStackTrace();
114         } catch (InstantiationException e) {
115             e.printStackTrace();
116         }
117         return null;
118     }
119 
save(Context context)120     public Uri save(Context context) {
121         if (isSaved()) {
122             throw new UnsupportedOperationException();
123         }
124         Uri res = context.getContentResolver().insert(mBaseUri, toContentValues());
125         mId = Long.parseLong(res.getPathSegments().get(1));
126         return res;
127     }
128 
update(Context context, ContentValues contentValues)129     public int update(Context context, ContentValues contentValues) {
130         if (!isSaved()) {
131             throw new UnsupportedOperationException();
132         }
133         return context.getContentResolver().update(getUri(), contentValues, null, null);
134     }
135 
update(Context context, Uri baseUri, long id, ContentValues contentValues)136     static public int update(Context context, Uri baseUri, long id, ContentValues contentValues) {
137         return context.getContentResolver()
138             .update(ContentUris.withAppendedId(baseUri, id), contentValues, null, null);
139     }
140 
141     /**
142      * Generic count method that can be used for any ContentProvider
143      * @param context the calling Context
144      * @param uri the Uri for the provider query
145      * @param selection as with a query call
146      * @param selectionArgs as with a query call
147      * @return the number of items matching the query (or zero)
148      */
count(Context context, Uri uri, String selection, String[] selectionArgs)149     static public int count(Context context, Uri uri, String selection, String[] selectionArgs) {
150         Cursor cursor = context.getContentResolver()
151             .query(uri, COUNT_COLUMNS, selection, selectionArgs, null);
152         try {
153             if (!cursor.moveToFirst()) {
154                 return 0;
155             }
156             return cursor.getInt(0);
157         } finally {
158             cursor.close();
159         }
160     }
161 
162     /**
163      * no public constructor since this is a utility class
164      */
EmailContent()165     private EmailContent() {
166     }
167 
168     public interface SyncColumns {
169         public static final String ID = "_id";
170         // source id (string) : the source's name of this item
171         public static final String SERVER_ID = "syncServerId";
172         // source's timestamp (long) for this item
173         public static final String SERVER_TIMESTAMP = "syncServerTimeStamp";
174     }
175 
176     public interface BodyColumns {
177         public static final String ID = "_id";
178         // Foreign key to the message corresponding to this body
179         public static final String MESSAGE_KEY = "messageKey";
180         // The html content itself
181         public static final String HTML_CONTENT = "htmlContent";
182         // The plain text content itself
183         public static final String TEXT_CONTENT = "textContent";
184         // Replied-to or forwarded body (in html form)
185         public static final String HTML_REPLY = "htmlReply";
186         // Replied-to or forwarded body (in text form)
187         public static final String TEXT_REPLY = "textReply";
188         // Message id of the source (if this is a reply/forward)
189         public static final String SOURCE_MESSAGE_KEY = "sourceMessageKey";
190         // The text to be placed between a reply/forward response and the original message
191         public static final String INTRO_TEXT = "introText";
192     }
193 
194     public static final class Body extends EmailContent implements BodyColumns {
195         public static final String TABLE_NAME = "Body";
196         public static final Uri CONTENT_URI = Uri.parse(EmailContent.CONTENT_URI + "/body");
197 
198         public static final int CONTENT_ID_COLUMN = 0;
199         public static final int CONTENT_MESSAGE_KEY_COLUMN = 1;
200         public static final int CONTENT_HTML_CONTENT_COLUMN = 2;
201         public static final int CONTENT_TEXT_CONTENT_COLUMN = 3;
202         public static final int CONTENT_HTML_REPLY_COLUMN = 4;
203         public static final int CONTENT_TEXT_REPLY_COLUMN = 5;
204         public static final int CONTENT_SOURCE_KEY_COLUMN = 6;
205         public static final int CONTENT_INTRO_TEXT_COLUMN = 7;
206         public static final String[] CONTENT_PROJECTION = new String[] {
207             RECORD_ID, BodyColumns.MESSAGE_KEY, BodyColumns.HTML_CONTENT, BodyColumns.TEXT_CONTENT,
208             BodyColumns.HTML_REPLY, BodyColumns.TEXT_REPLY, BodyColumns.SOURCE_MESSAGE_KEY,
209             BodyColumns.INTRO_TEXT
210         };
211 
212         public static final String[] COMMON_PROJECTION_TEXT = new String[] {
213             RECORD_ID, BodyColumns.TEXT_CONTENT
214         };
215         public static final String[] COMMON_PROJECTION_HTML = new String[] {
216             RECORD_ID, BodyColumns.HTML_CONTENT
217         };
218         public static final String[] COMMON_PROJECTION_REPLY_TEXT = new String[] {
219             RECORD_ID, BodyColumns.TEXT_REPLY
220         };
221         public static final String[] COMMON_PROJECTION_REPLY_HTML = new String[] {
222             RECORD_ID, BodyColumns.HTML_REPLY
223         };
224         public static final String[] COMMON_PROJECTION_INTRO = new String[] {
225             RECORD_ID, BodyColumns.INTRO_TEXT
226         };
227         public static final int COMMON_PROJECTION_COLUMN_TEXT = 1;
228 
229         public long mMessageKey;
230         public String mHtmlContent;
231         public String mTextContent;
232         public String mHtmlReply;
233         public String mTextReply;
234         public long mSourceKey;
235         public String mIntroText;
236 
Body()237         public Body() {
238             mBaseUri = CONTENT_URI;
239         }
240 
241         @Override
toContentValues()242         public ContentValues toContentValues() {
243             ContentValues values = new ContentValues();
244 
245             // Assign values for each row.
246             values.put(BodyColumns.MESSAGE_KEY, mMessageKey);
247             values.put(BodyColumns.HTML_CONTENT, mHtmlContent);
248             values.put(BodyColumns.TEXT_CONTENT, mTextContent);
249             values.put(BodyColumns.HTML_REPLY, mHtmlReply);
250             values.put(BodyColumns.TEXT_REPLY, mTextReply);
251             values.put(BodyColumns.SOURCE_MESSAGE_KEY, mSourceKey);
252             values.put(BodyColumns.INTRO_TEXT, mIntroText);
253             return values;
254         }
255 
restoreBodyWithCursor(Cursor cursor)256         private static Body restoreBodyWithCursor(Cursor cursor) {
257             try {
258                 if (cursor.moveToFirst()) {
259                     return getContent(cursor, Body.class);
260                 } else {
261                     return null;
262                 }
263             } finally {
264                 cursor.close();
265             }
266         }
267 
restoreBodyWithId(Context context, long id)268         public static Body restoreBodyWithId(Context context, long id) {
269             Uri u = ContentUris.withAppendedId(Body.CONTENT_URI, id);
270             Cursor c = context.getContentResolver().query(u, Body.CONTENT_PROJECTION,
271                     null, null, null);
272             return restoreBodyWithCursor(c);
273         }
274 
restoreBodyWithMessageId(Context context, long messageId)275         public static Body restoreBodyWithMessageId(Context context, long messageId) {
276             Cursor c = context.getContentResolver().query(Body.CONTENT_URI,
277                     Body.CONTENT_PROJECTION, Body.MESSAGE_KEY + "=?",
278                     new String[] {Long.toString(messageId)}, null);
279             return restoreBodyWithCursor(c);
280         }
281 
282         /**
283          * Returns the bodyId for the given messageId, or -1 if no body is found.
284          */
lookupBodyIdWithMessageId(ContentResolver resolver, long messageId)285         public static long lookupBodyIdWithMessageId(ContentResolver resolver, long messageId) {
286             Cursor c = resolver.query(Body.CONTENT_URI, ID_PROJECTION,
287                     Body.MESSAGE_KEY + "=?",
288                     new String[] {Long.toString(messageId)}, null);
289             try {
290                 return c.moveToFirst() ? c.getLong(ID_PROJECTION_COLUMN) : -1;
291             } finally {
292                 c.close();
293             }
294         }
295 
296         /**
297          * Updates the Body for a messageId with the given ContentValues.
298          * If the message has no body, a new body is inserted for the message.
299          * Warning: the argument "values" is modified by this method, setting MESSAGE_KEY.
300          */
updateBodyWithMessageId(Context context, long messageId, ContentValues values)301         public static void updateBodyWithMessageId(Context context, long messageId,
302                 ContentValues values) {
303             ContentResolver resolver = context.getContentResolver();
304             long bodyId = lookupBodyIdWithMessageId(resolver, messageId);
305             values.put(BodyColumns.MESSAGE_KEY, messageId);
306             if (bodyId == -1) {
307                 resolver.insert(CONTENT_URI, values);
308             } else {
309                 final Uri uri = ContentUris.withAppendedId(CONTENT_URI, bodyId);
310                 resolver.update(uri, values, null, null);
311             }
312         }
313 
restoreTextWithMessageId(Context context, long messageId, String[] projection)314         private static String restoreTextWithMessageId(Context context, long messageId,
315                 String[] projection) {
316             Cursor c = context.getContentResolver().query(Body.CONTENT_URI, projection,
317                     Body.MESSAGE_KEY + "=?", new String[] {Long.toString(messageId)}, null);
318             try {
319                 if (c.moveToFirst()) {
320                     return c.getString(COMMON_PROJECTION_COLUMN_TEXT);
321                 } else {
322                     return null;
323                 }
324             } finally {
325                 c.close();
326             }
327         }
328 
restoreBodyTextWithMessageId(Context context, long messageId)329         public static String restoreBodyTextWithMessageId(Context context, long messageId) {
330             return restoreTextWithMessageId(context, messageId, Body.COMMON_PROJECTION_TEXT);
331         }
332 
restoreBodyHtmlWithMessageId(Context context, long messageId)333         public static String restoreBodyHtmlWithMessageId(Context context, long messageId) {
334             return restoreTextWithMessageId(context, messageId, Body.COMMON_PROJECTION_HTML);
335         }
336 
restoreReplyTextWithMessageId(Context context, long messageId)337         public static String restoreReplyTextWithMessageId(Context context, long messageId) {
338             return restoreTextWithMessageId(context, messageId, Body.COMMON_PROJECTION_REPLY_TEXT);
339         }
340 
restoreReplyHtmlWithMessageId(Context context, long messageId)341         public static String restoreReplyHtmlWithMessageId(Context context, long messageId) {
342             return restoreTextWithMessageId(context, messageId, Body.COMMON_PROJECTION_REPLY_HTML);
343         }
344 
restoreIntroTextWithMessageId(Context context, long messageId)345         public static String restoreIntroTextWithMessageId(Context context, long messageId) {
346             return restoreTextWithMessageId(context, messageId, Body.COMMON_PROJECTION_INTRO);
347         }
348 
349         @Override
350         @SuppressWarnings("unchecked")
restore(Cursor c)351         public EmailContent.Body restore(Cursor c) {
352             mBaseUri = EmailContent.Body.CONTENT_URI;
353             mMessageKey = c.getLong(CONTENT_MESSAGE_KEY_COLUMN);
354             mHtmlContent = c.getString(CONTENT_HTML_CONTENT_COLUMN);
355             mTextContent = c.getString(CONTENT_TEXT_CONTENT_COLUMN);
356             mHtmlReply = c.getString(CONTENT_HTML_REPLY_COLUMN);
357             mTextReply = c.getString(CONTENT_TEXT_REPLY_COLUMN);
358             mSourceKey = c.getLong(CONTENT_SOURCE_KEY_COLUMN);
359             mIntroText = c.getString(CONTENT_INTRO_TEXT_COLUMN);
360             return this;
361         }
362 
update()363         public boolean update() {
364             // TODO Auto-generated method stub
365             return false;
366         }
367     }
368 
369     public interface MessageColumns {
370         public static final String ID = "_id";
371         // Basic columns used in message list presentation
372         // The name as shown to the user in a message list
373         public static final String DISPLAY_NAME = "displayName";
374         // The time (millis) as shown to the user in a message list [INDEX]
375         public static final String TIMESTAMP = "timeStamp";
376         // Message subject
377         public static final String SUBJECT = "subject";
378         // Boolean, unread = 0, read = 1 [INDEX]
379         public static final String FLAG_READ = "flagRead";
380         // Load state, see constants below (unloaded, partial, complete, deleted)
381         public static final String FLAG_LOADED = "flagLoaded";
382         // Boolean, unflagged = 0, flagged (favorite) = 1
383         public static final String FLAG_FAVORITE = "flagFavorite";
384         // Boolean, no attachment = 0, attachment = 1
385         public static final String FLAG_ATTACHMENT = "flagAttachment";
386         // Bit field for flags which we'll not be selecting on
387         public static final String FLAGS = "flags";
388 
389         // Sync related identifiers
390         // Any client-required identifier
391         public static final String CLIENT_ID = "clientId";
392         // The message-id in the message's header
393         public static final String MESSAGE_ID = "messageId";
394 
395         // References to other Email objects in the database
396         // Foreign key to the Mailbox holding this message [INDEX]
397         public static final String MAILBOX_KEY = "mailboxKey";
398         // Foreign key to the Account holding this message
399         public static final String ACCOUNT_KEY = "accountKey";
400 
401         // Address lists, packed with Address.pack()
402         public static final String FROM_LIST = "fromList";
403         public static final String TO_LIST = "toList";
404         public static final String CC_LIST = "ccList";
405         public static final String BCC_LIST = "bccList";
406         public static final String REPLY_TO_LIST = "replyToList";
407     }
408 
409     public static final class Message extends EmailContent implements SyncColumns, MessageColumns {
410         public static final String TABLE_NAME = "Message";
411         public static final String UPDATED_TABLE_NAME = "Message_Updates";
412         public static final String DELETED_TABLE_NAME = "Message_Deletes";
413 
414         // To refer to a specific message, use ContentUris.withAppendedId(CONTENT_URI, id)
415         public static final Uri CONTENT_URI = Uri.parse(EmailContent.CONTENT_URI + "/message");
416         public static final Uri SYNCED_CONTENT_URI =
417             Uri.parse(EmailContent.CONTENT_URI + "/syncedMessage");
418         public static final Uri DELETED_CONTENT_URI =
419             Uri.parse(EmailContent.CONTENT_URI + "/deletedMessage");
420         public static final Uri UPDATED_CONTENT_URI =
421             Uri.parse(EmailContent.CONTENT_URI + "/updatedMessage");
422 
423         public static final String KEY_TIMESTAMP_DESC = MessageColumns.TIMESTAMP + " desc";
424 
425         public static final int CONTENT_ID_COLUMN = 0;
426         public static final int CONTENT_DISPLAY_NAME_COLUMN = 1;
427         public static final int CONTENT_TIMESTAMP_COLUMN = 2;
428         public static final int CONTENT_SUBJECT_COLUMN = 3;
429         public static final int CONTENT_FLAG_READ_COLUMN = 4;
430         public static final int CONTENT_FLAG_LOADED_COLUMN = 5;
431         public static final int CONTENT_FLAG_FAVORITE_COLUMN = 6;
432         public static final int CONTENT_FLAG_ATTACHMENT_COLUMN = 7;
433         public static final int CONTENT_FLAGS_COLUMN = 8;
434         public static final int CONTENT_SERVER_ID_COLUMN = 9;
435         public static final int CONTENT_CLIENT_ID_COLUMN = 10;
436         public static final int CONTENT_MESSAGE_ID_COLUMN = 11;
437         public static final int CONTENT_MAILBOX_KEY_COLUMN = 12;
438         public static final int CONTENT_ACCOUNT_KEY_COLUMN = 13;
439         public static final int CONTENT_FROM_LIST_COLUMN = 14;
440         public static final int CONTENT_TO_LIST_COLUMN = 15;
441         public static final int CONTENT_CC_LIST_COLUMN = 16;
442         public static final int CONTENT_BCC_LIST_COLUMN = 17;
443         public static final int CONTENT_REPLY_TO_COLUMN = 18;
444         public static final int CONTENT_SERVER_TIMESTAMP_COLUMN = 19;
445 
446         public static final String[] CONTENT_PROJECTION = new String[] {
447             RECORD_ID,
448             MessageColumns.DISPLAY_NAME, MessageColumns.TIMESTAMP,
449             MessageColumns.SUBJECT, MessageColumns.FLAG_READ,
450             MessageColumns.FLAG_LOADED, MessageColumns.FLAG_FAVORITE,
451             MessageColumns.FLAG_ATTACHMENT, MessageColumns.FLAGS,
452             SyncColumns.SERVER_ID, MessageColumns.CLIENT_ID,
453             MessageColumns.MESSAGE_ID, MessageColumns.MAILBOX_KEY,
454             MessageColumns.ACCOUNT_KEY, MessageColumns.FROM_LIST,
455             MessageColumns.TO_LIST, MessageColumns.CC_LIST,
456             MessageColumns.BCC_LIST, MessageColumns.REPLY_TO_LIST,
457             SyncColumns.SERVER_TIMESTAMP,
458         };
459 
460         public static final int LIST_ID_COLUMN = 0;
461         public static final int LIST_DISPLAY_NAME_COLUMN = 1;
462         public static final int LIST_TIMESTAMP_COLUMN = 2;
463         public static final int LIST_SUBJECT_COLUMN = 3;
464         public static final int LIST_READ_COLUMN = 4;
465         public static final int LIST_LOADED_COLUMN = 5;
466         public static final int LIST_FAVORITE_COLUMN = 6;
467         public static final int LIST_ATTACHMENT_COLUMN = 7;
468         public static final int LIST_FLAGS_COLUMN = 8;
469         public static final int LIST_MAILBOX_KEY_COLUMN = 9;
470         public static final int LIST_ACCOUNT_KEY_COLUMN = 10;
471         public static final int LIST_SERVER_ID_COLUMN = 11;
472 
473         // Public projection for common list columns
474         public static final String[] LIST_PROJECTION = new String[] {
475             RECORD_ID,
476             MessageColumns.DISPLAY_NAME, MessageColumns.TIMESTAMP,
477             MessageColumns.SUBJECT, MessageColumns.FLAG_READ,
478             MessageColumns.FLAG_LOADED, MessageColumns.FLAG_FAVORITE,
479             MessageColumns.FLAG_ATTACHMENT, MessageColumns.FLAGS,
480             MessageColumns.MAILBOX_KEY, MessageColumns.ACCOUNT_KEY,
481             SyncColumns.SERVER_ID
482         };
483 
484         public static final int ID_COLUMNS_ID_COLUMN = 0;
485         public static final int ID_COLUMNS_SYNC_SERVER_ID = 1;
486         public static final String[] ID_COLUMNS_PROJECTION = new String[] {
487             RECORD_ID, SyncColumns.SERVER_ID
488         };
489 
490         public static final int ID_MAILBOX_COLUMN_ID = 0;
491         public static final int ID_MAILBOX_COLUMN_MAILBOX_KEY = 1;
492         public static final String[] ID_MAILBOX_PROJECTION = new String[] {
493             RECORD_ID, MessageColumns.MAILBOX_KEY
494         };
495 
496         public static final String[] ID_COLUMN_PROJECTION = new String[] { RECORD_ID };
497 
498         // _id field is in AbstractContent
499         public String mDisplayName;
500         public long mTimeStamp;
501         public String mSubject;
502         public boolean mFlagRead = false;
503         public int mFlagLoaded = FLAG_LOADED_UNLOADED;
504         public boolean mFlagFavorite = false;
505         public boolean mFlagAttachment = false;
506         public int mFlags = 0;
507 
508         public String mServerId;
509         public long mServerTimeStamp;
510         public String mClientId;
511         public String mMessageId;
512 
513         public long mMailboxKey;
514         public long mAccountKey;
515 
516         public String mFrom;
517         public String mTo;
518         public String mCc;
519         public String mBcc;
520         public String mReplyTo;
521 
522         // The following transient members may be used while building and manipulating messages,
523         // but they are NOT persisted directly by EmailProvider
524         transient public String mText;
525         transient public String mHtml;
526         transient public String mTextReply;
527         transient public String mHtmlReply;
528         transient public long mSourceKey;
529         transient public ArrayList<Attachment> mAttachments = null;
530         transient public String mIntroText;
531 
532         // Values used in mFlagRead
533         public static final int UNREAD = 0;
534         public static final int READ = 1;
535 
536         // Values used in mFlagLoaded
537         public static final int FLAG_LOADED_UNLOADED = 0;
538         public static final int FLAG_LOADED_COMPLETE = 1;
539         public static final int FLAG_LOADED_PARTIAL = 2;
540         public static final int FLAG_LOADED_DELETED = 3;
541 
542         // Bits used in mFlags
543         // These three states are mutually exclusive, and indicate whether the message is an
544         // original, a reply, or a forward
545         public static final int FLAG_TYPE_ORIGINAL = 0;
546         public static final int FLAG_TYPE_REPLY = 1<<0;
547         public static final int FLAG_TYPE_FORWARD = 1<<1;
548         public static final int FLAG_TYPE_MASK = FLAG_TYPE_REPLY | FLAG_TYPE_FORWARD;
549 
Message()550         public Message() {
551             mBaseUri = CONTENT_URI;
552         }
553 
554         @Override
toContentValues()555         public ContentValues toContentValues() {
556             ContentValues values = new ContentValues();
557 
558             // Assign values for each row.
559             values.put(MessageColumns.DISPLAY_NAME, mDisplayName);
560             values.put(MessageColumns.TIMESTAMP, mTimeStamp);
561             values.put(MessageColumns.SUBJECT, mSubject);
562             values.put(MessageColumns.FLAG_READ, mFlagRead);
563             values.put(MessageColumns.FLAG_LOADED, mFlagLoaded);
564             values.put(MessageColumns.FLAG_FAVORITE, mFlagFavorite);
565             values.put(MessageColumns.FLAG_ATTACHMENT, mFlagAttachment);
566             values.put(MessageColumns.FLAGS, mFlags);
567 
568             values.put(SyncColumns.SERVER_ID, mServerId);
569             values.put(SyncColumns.SERVER_TIMESTAMP, mServerTimeStamp);
570             values.put(MessageColumns.CLIENT_ID, mClientId);
571             values.put(MessageColumns.MESSAGE_ID, mMessageId);
572 
573             values.put(MessageColumns.MAILBOX_KEY, mMailboxKey);
574             values.put(MessageColumns.ACCOUNT_KEY, mAccountKey);
575 
576             values.put(MessageColumns.FROM_LIST, mFrom);
577             values.put(MessageColumns.TO_LIST, mTo);
578             values.put(MessageColumns.CC_LIST, mCc);
579             values.put(MessageColumns.BCC_LIST, mBcc);
580             values.put(MessageColumns.REPLY_TO_LIST, mReplyTo);
581 
582             return values;
583         }
584 
restoreMessageWithId(Context context, long id)585         public static Message restoreMessageWithId(Context context, long id) {
586             Uri u = ContentUris.withAppendedId(Message.CONTENT_URI, id);
587             Cursor c = context.getContentResolver().query(u, Message.CONTENT_PROJECTION,
588                     null, null, null);
589 
590             try {
591                 if (c.moveToFirst()) {
592                     return getContent(c, Message.class);
593                 } else {
594                     return null;
595                 }
596             } finally {
597                 c.close();
598             }
599         }
600 
601         @Override
602         @SuppressWarnings("unchecked")
restore(Cursor c)603         public EmailContent.Message restore(Cursor c) {
604             mBaseUri = CONTENT_URI;
605             mId = c.getLong(CONTENT_ID_COLUMN);
606             mDisplayName = c.getString(CONTENT_DISPLAY_NAME_COLUMN);
607             mTimeStamp = c.getLong(CONTENT_TIMESTAMP_COLUMN);
608             mSubject = c.getString(CONTENT_SUBJECT_COLUMN);
609             mFlagRead = c.getInt(CONTENT_FLAG_READ_COLUMN) == 1;
610             mFlagLoaded = c.getInt(CONTENT_FLAG_LOADED_COLUMN);
611             mFlagFavorite = c.getInt(CONTENT_FLAG_FAVORITE_COLUMN) == 1;
612             mFlagAttachment = c.getInt(CONTENT_FLAG_ATTACHMENT_COLUMN) == 1;
613             mFlags = c.getInt(CONTENT_FLAGS_COLUMN);
614             mServerId = c.getString(CONTENT_SERVER_ID_COLUMN);
615             mServerTimeStamp = c.getLong(CONTENT_SERVER_TIMESTAMP_COLUMN);
616             mClientId = c.getString(CONTENT_CLIENT_ID_COLUMN);
617             mMessageId = c.getString(CONTENT_MESSAGE_ID_COLUMN);
618             mMailboxKey = c.getLong(CONTENT_MAILBOX_KEY_COLUMN);
619             mAccountKey = c.getLong(CONTENT_ACCOUNT_KEY_COLUMN);
620             mFrom = c.getString(CONTENT_FROM_LIST_COLUMN);
621             mTo = c.getString(CONTENT_TO_LIST_COLUMN);
622             mCc = c.getString(CONTENT_CC_LIST_COLUMN);
623             mBcc = c.getString(CONTENT_BCC_LIST_COLUMN);
624             mReplyTo = c.getString(CONTENT_REPLY_TO_COLUMN);
625             return this;
626         }
627 
update()628         public boolean update() {
629             // TODO Auto-generated method stub
630             return false;
631         }
632 
633         /*
634          * Override this so that we can store the Body first and link it to the Message
635          * Also, attachments when we get there...
636          * (non-Javadoc)
637          * @see com.android.email.provider.EmailContent#save(android.content.Context)
638          */
639         @Override
save(Context context)640         public Uri save(Context context) {
641 
642             boolean doSave = !isSaved();
643 
644             // This logic is in place so I can (a) short circuit the expensive stuff when
645             // possible, and (b) override (and throw) if anyone tries to call save() or update()
646             // directly for Message, which are unsupported.
647             if (mText == null && mHtml == null && mTextReply == null && mHtmlReply == null &&
648                     (mAttachments == null || mAttachments.isEmpty())) {
649                 if (doSave) {
650                     return super.save(context);
651                 } else {
652                     // Call update, rather than super.update in case we ever override it
653                     if (update(context, toContentValues()) == 1) {
654                         return getUri();
655                     }
656                     return null;
657                 }
658             }
659 
660             ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
661             addSaveOps(ops);
662             try {
663                 ContentProviderResult[] results =
664                     context.getContentResolver().applyBatch(EmailProvider.EMAIL_AUTHORITY, ops);
665                 // If saving, set the mId's of the various saved objects
666                 if (doSave) {
667                     Uri u = results[0].uri;
668                     mId = Long.parseLong(u.getPathSegments().get(1));
669                     if (mAttachments != null) {
670                         int resultOffset = 2;
671                         for (Attachment a : mAttachments) {
672                             // Save the id of the attachment record
673                             u = results[resultOffset++].uri;
674                             if (u != null) {
675                                 a.mId = Long.parseLong(u.getPathSegments().get(1));
676                             }
677                             a.mMessageKey = mId;
678                         }
679                     }
680                     return u;
681                 } else {
682                     return null;
683                 }
684             } catch (RemoteException e) {
685                 // There is nothing to be done here; fail by returning null
686             } catch (OperationApplicationException e) {
687                 // There is nothing to be done here; fail by returning null
688             }
689             return null;
690         }
691 
addSaveOps(ArrayList<ContentProviderOperation> ops)692         public void addSaveOps(ArrayList<ContentProviderOperation> ops) {
693             // First, save the message
694             ContentProviderOperation.Builder b = ContentProviderOperation.newInsert(mBaseUri);
695             ops.add(b.withValues(toContentValues()).build());
696 
697             // Create and save the body
698             ContentValues cv = new ContentValues();
699             if (mText != null) {
700                 cv.put(Body.TEXT_CONTENT, mText);
701             }
702             if (mHtml != null) {
703                 cv.put(Body.HTML_CONTENT, mHtml);
704             }
705             if (mTextReply != null) {
706                 cv.put(Body.TEXT_REPLY, mTextReply);
707             }
708             if (mHtmlReply != null) {
709                 cv.put(Body.HTML_REPLY, mHtmlReply);
710             }
711             if (mSourceKey != 0) {
712                 cv.put(Body.SOURCE_MESSAGE_KEY, mSourceKey);
713             }
714             if (mIntroText != null) {
715                 cv.put(Body.INTRO_TEXT, mIntroText);
716             }
717             b = ContentProviderOperation.newInsert(Body.CONTENT_URI);
718             b.withValues(cv);
719             ContentValues backValues = new ContentValues();
720             int messageBackValue = ops.size() - 1;
721             backValues.put(Body.MESSAGE_KEY, messageBackValue);
722             ops.add(b.withValueBackReferences(backValues).build());
723 
724             // Create the attaachments, if any
725             if (mAttachments != null) {
726                 for (Attachment att: mAttachments) {
727                     ops.add(ContentProviderOperation.newInsert(Attachment.CONTENT_URI)
728                         .withValues(att.toContentValues())
729                         .withValueBackReference(Attachment.MESSAGE_KEY, messageBackValue)
730                         .build());
731                 }
732             }
733         }
734     }
735 
736     public interface AccountColumns {
737         public static final String ID = "_id";
738         // The display name of the account (user-settable)
739         public static final String DISPLAY_NAME = "displayName";
740         // The email address corresponding to this account
741         public static final String EMAIL_ADDRESS = "emailAddress";
742         // A server-based sync key on an account-wide basis (EAS needs this)
743         public static final String SYNC_KEY = "syncKey";
744         // The default sync lookback period for this account
745         public static final String SYNC_LOOKBACK = "syncLookback";
746         // The default sync frequency for this account, in minutes
747         public static final String SYNC_INTERVAL = "syncInterval";
748         // A foreign key into the account manager, having host, login, password, port, and ssl flags
749         public static final String HOST_AUTH_KEY_RECV = "hostAuthKeyRecv";
750         // (optional) A foreign key into the account manager, having host, login, password, port,
751         // and ssl flags
752         public static final String HOST_AUTH_KEY_SEND = "hostAuthKeySend";
753         // Flags
754         public static final String FLAGS = "flags";
755         // Default account
756         public static final String IS_DEFAULT = "isDefault";
757         // Old-Style UUID for compatibility with previous versions
758         public static final String COMPATIBILITY_UUID = "compatibilityUuid";
759         // User name (for outgoing messages)
760         public static final String SENDER_NAME = "senderName";
761         // Ringtone
762         public static final String RINGTONE_URI = "ringtoneUri";
763         // Protocol version (arbitrary string, used by EAS currently)
764         public static final String PROTOCOL_VERSION = "protocolVersion";
765         // The number of new messages (reported by the sync/download engines
766         public static final String NEW_MESSAGE_COUNT = "newMessageCount";
767     }
768 
769     public static final class Account extends EmailContent implements AccountColumns, Parcelable {
770         public static final String TABLE_NAME = "Account";
771         public static final Uri CONTENT_URI = Uri.parse(EmailContent.CONTENT_URI + "/account");
772         public static final Uri ADD_TO_FIELD_URI =
773             Uri.parse(EmailContent.CONTENT_URI + "/accountIdAddToField");
774 
775         public final static int FLAGS_NOTIFY_NEW_MAIL = 1;
776         public final static int FLAGS_VIBRATE = 2;
777         public static final int FLAGS_DELETE_POLICY_MASK = 4+8;
778         public static final int FLAGS_DELETE_POLICY_SHIFT = 2;
779 
780         public static final int DELETE_POLICY_NEVER = 0;
781         public static final int DELETE_POLICY_7DAYS = 1;        // not supported
782         public static final int DELETE_POLICY_ON_DELETE = 2;
783 
784         // Sentinel values for the mSyncInterval field of both Account records
785         public static final int CHECK_INTERVAL_NEVER = -1;
786         public static final int CHECK_INTERVAL_PUSH = -2;
787 
788         public static final int SYNC_WINDOW_USER = -1;
789 
790         public String mDisplayName;
791         public String mEmailAddress;
792         public String mSyncKey;
793         public int mSyncLookback;
794         public int mSyncInterval;
795         public long mHostAuthKeyRecv;
796         public long mHostAuthKeySend;
797         public int mFlags;
798         public boolean mIsDefault;          // note: callers should use getDefaultAccountId()
799         public String mCompatibilityUuid;
800         public String mSenderName;
801         public String mRingtoneUri;
802         public String mProtocolVersion;
803         public int mNewMessageCount;
804 
805         // Convenience for creating an account
806         public transient HostAuth mHostAuthRecv;
807         public transient HostAuth mHostAuthSend;
808 
809         public static final int CONTENT_ID_COLUMN = 0;
810         public static final int CONTENT_DISPLAY_NAME_COLUMN = 1;
811         public static final int CONTENT_EMAIL_ADDRESS_COLUMN = 2;
812         public static final int CONTENT_SYNC_KEY_COLUMN = 3;
813         public static final int CONTENT_SYNC_LOOKBACK_COLUMN = 4;
814         public static final int CONTENT_SYNC_INTERVAL_COLUMN = 5;
815         public static final int CONTENT_HOST_AUTH_KEY_RECV_COLUMN = 6;
816         public static final int CONTENT_HOST_AUTH_KEY_SEND_COLUMN = 7;
817         public static final int CONTENT_FLAGS_COLUMN = 8;
818         public static final int CONTENT_IS_DEFAULT_COLUMN = 9;
819         public static final int CONTENT_COMPATIBILITY_UUID_COLUMN = 10;
820         public static final int CONTENT_SENDER_NAME_COLUMN = 11;
821         public static final int CONTENT_RINGTONE_URI_COLUMN = 12;
822         public static final int CONTENT_PROTOCOL_VERSION_COLUMN = 13;
823         public static final int CONTENT_NEW_MESSAGE_COUNT_COLUMN = 14;
824 
825         public static final String[] CONTENT_PROJECTION = new String[] {
826             RECORD_ID, AccountColumns.DISPLAY_NAME,
827             AccountColumns.EMAIL_ADDRESS, AccountColumns.SYNC_KEY, AccountColumns.SYNC_LOOKBACK,
828             AccountColumns.SYNC_INTERVAL, AccountColumns.HOST_AUTH_KEY_RECV,
829             AccountColumns.HOST_AUTH_KEY_SEND, AccountColumns.FLAGS, AccountColumns.IS_DEFAULT,
830             AccountColumns.COMPATIBILITY_UUID, AccountColumns.SENDER_NAME,
831             AccountColumns.RINGTONE_URI, AccountColumns.PROTOCOL_VERSION,
832             AccountColumns.NEW_MESSAGE_COUNT
833         };
834 
835         public static final int CONTENT_MAILBOX_TYPE_COLUMN = 1;
836 
837         /**
838          * This projection is for listing account id's only
839          */
840         public static final String[] ID_TYPE_PROJECTION = new String[] {
841             RECORD_ID, MailboxColumns.TYPE
842         };
843 
844         public static final String MAILBOX_SELECTION =
845             MessageColumns.MAILBOX_KEY + " =?";
846 
847         public static final String UNREAD_COUNT_SELECTION =
848             MessageColumns.MAILBOX_KEY + " =? and " + MessageColumns.FLAG_READ + "= 0";
849 
850         /**
851          * This projection is for searching for the default account
852          */
853         private static final String[] DEFAULT_ID_PROJECTION = new String[] {
854             RECORD_ID, IS_DEFAULT
855         };
856 
857         /**
858          * no public constructor since this is a utility class
859          */
Account()860         public Account() {
861             mBaseUri = CONTENT_URI;
862 
863             // other defaults (policy)
864             mRingtoneUri = "content://settings/system/notification_sound";
865             mSyncInterval = -1;
866             mSyncLookback = -1;
867             mFlags = FLAGS_NOTIFY_NEW_MAIL;
868             mCompatibilityUuid = UUID.randomUUID().toString();
869         }
870 
restoreAccountWithId(Context context, long id)871         public static Account restoreAccountWithId(Context context, long id) {
872             Uri u = ContentUris.withAppendedId(Account.CONTENT_URI, id);
873             Cursor c = context.getContentResolver().query(u, Account.CONTENT_PROJECTION,
874                     null, null, null);
875 
876             try {
877                 if (c.moveToFirst()) {
878                     return getContent(c, Account.class);
879                 } else {
880                     return null;
881                 }
882             } finally {
883                 c.close();
884             }
885         }
886 
887         /**
888          * Refresh an account that has already been loaded.  This is slightly less expensive
889          * that generating a brand-new account object.
890          */
refresh(Context context)891         public void refresh(Context context) {
892             Cursor c = context.getContentResolver().query(this.getUri(), Account.CONTENT_PROJECTION,
893                     null, null, null);
894             try {
895                 c.moveToFirst();
896                 restore(c);
897             } finally {
898                 if (c != null) {
899                     c.close();
900                 }
901             }
902         }
903 
904         @Override
905         @SuppressWarnings("unchecked")
restore(Cursor cursor)906         public EmailContent.Account restore(Cursor cursor) {
907             mId = cursor.getLong(CONTENT_ID_COLUMN);
908             mBaseUri = CONTENT_URI;
909             mDisplayName = cursor.getString(CONTENT_DISPLAY_NAME_COLUMN);
910             mEmailAddress = cursor.getString(CONTENT_EMAIL_ADDRESS_COLUMN);
911             mSyncKey = cursor.getString(CONTENT_SYNC_KEY_COLUMN);
912             mSyncLookback = cursor.getInt(CONTENT_SYNC_LOOKBACK_COLUMN);
913             mSyncInterval = cursor.getInt(CONTENT_SYNC_INTERVAL_COLUMN);
914             mHostAuthKeyRecv = cursor.getLong(CONTENT_HOST_AUTH_KEY_RECV_COLUMN);
915             mHostAuthKeySend = cursor.getLong(CONTENT_HOST_AUTH_KEY_SEND_COLUMN);
916             mFlags = cursor.getInt(CONTENT_FLAGS_COLUMN);
917             mIsDefault = cursor.getInt(CONTENT_IS_DEFAULT_COLUMN) == 1;
918             mCompatibilityUuid = cursor.getString(CONTENT_COMPATIBILITY_UUID_COLUMN);
919             mSenderName = cursor.getString(CONTENT_SENDER_NAME_COLUMN);
920             mRingtoneUri = cursor.getString(CONTENT_RINGTONE_URI_COLUMN);
921             mProtocolVersion = cursor.getString(CONTENT_PROTOCOL_VERSION_COLUMN);
922             mNewMessageCount = cursor.getInt(CONTENT_NEW_MESSAGE_COUNT_COLUMN);
923             return this;
924         }
925 
getId(Uri u)926         private long getId(Uri u) {
927             return Long.parseLong(u.getPathSegments().get(1));
928         }
929 
930         /**
931          * @return the user-visible name for the account
932          */
getDisplayName()933         public String getDisplayName() {
934             return mDisplayName;
935         }
936 
937         /**
938          * Set the description.  Be sure to call save() to commit to database.
939          * @param description the new description
940          */
setDisplayName(String description)941         public void setDisplayName(String description) {
942             mDisplayName = description;
943         }
944 
945         /**
946          * @return the email address for this account
947          */
getEmailAddress()948         public String getEmailAddress() {
949             return mEmailAddress;
950         }
951 
952         /**
953          * Set the Email address for this account.  Be sure to call save() to commit to database.
954          * @param emailAddress the new email address for this account
955          */
setEmailAddress(String emailAddress)956         public void setEmailAddress(String emailAddress) {
957             mEmailAddress = emailAddress;
958         }
959 
960         /**
961          * @return the sender's name for this account
962          */
getSenderName()963         public String getSenderName() {
964             return mSenderName;
965         }
966 
967         /**
968          * Set the sender's name.  Be sure to call save() to commit to database.
969          * @param name the new sender name
970          */
setSenderName(String name)971         public void setSenderName(String name) {
972             mSenderName = name;
973         }
974 
975         /**
976          * @return the minutes per check (for polling)
977          * TODO define sentinel values for "never", "push", etc.  See Account.java
978          */
getSyncInterval()979         public int getSyncInterval()
980         {
981             return mSyncInterval;
982         }
983 
984         /**
985          * Set the minutes per check (for polling).  Be sure to call save() to commit to database.
986          * TODO define sentinel values for "never", "push", etc.  See Account.java
987          * @param minutes the number of minutes between polling checks
988          */
setSyncInterval(int minutes)989         public void setSyncInterval(int minutes)
990         {
991             mSyncInterval = minutes;
992         }
993 
994         /**
995          * @return the sync lookback window in # of days
996          * TODO define sentinel values for "all", "1 month", etc.  See Account.java
997          */
getSyncLookback()998         public int getSyncLookback() {
999             return mSyncLookback;
1000         }
1001 
1002         /**
1003          * Set the sync lookback window in # of days.  Be sure to call save() to commit to database.
1004          * TODO define sentinel values for "all", "1 month", etc.  See Account.java
1005          * @param days number of days to look back for syncing messages
1006          */
setSyncLookback(int days)1007         public void setSyncLookback(int days) {
1008             mSyncLookback = days;
1009         }
1010 
1011         /**
1012          * @return the flags for this account
1013          * @see #FLAGS_NOTIFY_NEW_MAIL
1014          * @see #FLAGS_VIBRATE
1015          */
getFlags()1016         public int getFlags() {
1017             return mFlags;
1018         }
1019 
1020         /**
1021          * Set the flags for this account
1022          * @see #FLAGS_NOTIFY_NEW_MAIL
1023          * @see #FLAGS_VIBRATE
1024          * @param newFlags the new value for the flags
1025          */
setFlags(int newFlags)1026         public void setFlags(int newFlags) {
1027             mFlags = newFlags;
1028         }
1029 
1030         /**
1031          * @return the ringtone Uri for this account
1032          */
getRingtone()1033         public String getRingtone() {
1034             return mRingtoneUri;
1035         }
1036 
1037         /**
1038          * Set the ringtone Uri for this account
1039          * @param newUri the new URI string for the ringtone for this account
1040          */
setRingtone(String newUri)1041         public void setRingtone(String newUri) {
1042             mRingtoneUri = newUri;
1043         }
1044 
1045         /**
1046          * Set the "delete policy" as a simple 0,1,2 value set.
1047          * @param newPolicy the new delete policy
1048          */
setDeletePolicy(int newPolicy)1049         public void setDeletePolicy(int newPolicy) {
1050             mFlags &= ~FLAGS_DELETE_POLICY_MASK;
1051             mFlags |= (newPolicy << FLAGS_DELETE_POLICY_SHIFT) & FLAGS_DELETE_POLICY_MASK;
1052         }
1053 
1054         /**
1055          * Return the "delete policy" as a simple 0,1,2 value set.
1056          * @return the current delete policy
1057          */
getDeletePolicy()1058         public int getDeletePolicy() {
1059             return (mFlags & FLAGS_DELETE_POLICY_MASK) >> FLAGS_DELETE_POLICY_SHIFT;
1060         }
1061 
1062         /**
1063          * Return the Uuid associated with this account.  This is primarily for compatibility
1064          * with accounts set up by previous versions, because there are externals references
1065          * to the Uuid (e.g. desktop shortcuts).
1066          */
getUuid()1067         String getUuid() {
1068             return mCompatibilityUuid;
1069         }
1070 
1071         /**
1072          * For compatibility while converting to provider model, generate a "store URI"
1073          *
1074          * @return a string in the form of a Uri, as used by the other parts of the email app
1075          */
getStoreUri(Context context)1076         public String getStoreUri(Context context) {
1077             // reconstitute if necessary
1078             if (mHostAuthRecv == null) {
1079                 mHostAuthRecv = HostAuth.restoreHostAuthWithId(context, mHostAuthKeyRecv);
1080             }
1081             // convert if available
1082             if (mHostAuthRecv != null) {
1083                 String storeUri = mHostAuthRecv.getStoreUri();
1084                 if (storeUri != null) {
1085                     return storeUri;
1086                 }
1087             }
1088             return "";
1089         }
1090 
1091         /**
1092          * For compatibility while converting to provider model, generate a "sender URI"
1093          *
1094          * @return a string in the form of a Uri, as used by the other parts of the email app
1095          */
getSenderUri(Context context)1096         public String getSenderUri(Context context) {
1097             // reconstitute if necessary
1098             if (mHostAuthSend == null) {
1099                 mHostAuthSend = HostAuth.restoreHostAuthWithId(context, mHostAuthKeySend);
1100             }
1101             // convert if available
1102             if (mHostAuthSend != null) {
1103                 String senderUri = mHostAuthSend.getStoreUri();
1104                 if (senderUri != null) {
1105                     return senderUri;
1106                 }
1107             }
1108             return "";
1109         }
1110 
1111         /**
1112          * For compatibility while converting to provider model, set the store URI
1113          *
1114          * @param context
1115          * @param storeUri the new value
1116          */
1117         @Deprecated
setStoreUri(Context context, String storeUri)1118         public void setStoreUri(Context context, String storeUri) {
1119             // reconstitute or create if necessary
1120             if (mHostAuthRecv == null) {
1121                 if (mHostAuthKeyRecv != 0) {
1122                     mHostAuthRecv = HostAuth.restoreHostAuthWithId(context, mHostAuthKeyRecv);
1123                 } else {
1124                     mHostAuthRecv = new EmailContent.HostAuth();
1125                 }
1126             }
1127 
1128             if (mHostAuthRecv != null) {
1129                 mHostAuthRecv.setStoreUri(storeUri);
1130             }
1131         }
1132 
1133         /**
1134          * For compatibility while converting to provider model, set the sender URI
1135          *
1136          * @param context
1137          * @param senderUri the new value
1138          */
1139         @Deprecated
setSenderUri(Context context, String senderUri)1140         public void setSenderUri(Context context, String senderUri) {
1141             // reconstitute or create if necessary
1142             if (mHostAuthSend == null) {
1143                 if (mHostAuthKeySend != 0) {
1144                     mHostAuthSend = HostAuth.restoreHostAuthWithId(context, mHostAuthKeySend);
1145                 } else {
1146                     mHostAuthSend = new EmailContent.HostAuth();
1147                 }
1148             }
1149 
1150             if (mHostAuthSend != null) {
1151                 mHostAuthSend.setStoreUri(senderUri);
1152             }
1153         }
1154 
1155         /**
1156          * For compatibility while converting to provider model, generate a "local store URI"
1157          *
1158          * @return a string in the form of a Uri, as used by the other parts of the email app
1159          */
getLocalStoreUri(Context context)1160         public String getLocalStoreUri(Context context) {
1161             return "local://localhost/" + context.getDatabasePath(getUuid() + ".db");
1162         }
1163 
1164         /**
1165          * Set the account to be the default account.  If this is set to "true", when the account
1166          * is saved, all other accounts will have the same value set to "false".
1167          * @param newDefaultState the new default state - if true, others will be cleared.
1168          */
setDefaultAccount(boolean newDefaultState)1169         public void setDefaultAccount(boolean newDefaultState) {
1170             mIsDefault = newDefaultState;
1171         }
1172 
1173         /**
1174          * Helper method for finding the default account.
1175          */
getDefaultAccountWhere(Context context, String where)1176         static private long getDefaultAccountWhere(Context context, String where) {
1177             Cursor cursor = context.getContentResolver().query(CONTENT_URI,
1178                     DEFAULT_ID_PROJECTION,
1179                     where, null, null);
1180             try {
1181                 if (cursor.moveToFirst()) {
1182                     return cursor.getLong(0);   // column 0 is id
1183                 }
1184             } finally {
1185                 cursor.close();
1186             }
1187             return -1;
1188         }
1189 
1190         /**
1191          * Return the id of the default account.  If one hasn't been explicitly specified, return
1192          * the first one in the database.  For any account saved in the DB, this must be used
1193          * to check for the default account - the mIsDefault field is set lazily and may be
1194          * incorrect.
1195          * @param context the caller's context
1196          * @return the id of the default account, or -1 if there are no accounts
1197          */
getDefaultAccountId(Context context)1198         static public long getDefaultAccountId(Context context) {
1199             long id = getDefaultAccountWhere(context, AccountColumns.IS_DEFAULT + "=1");
1200             if (id == -1) {
1201                 id = getDefaultAccountWhere(context, null);
1202             }
1203             return id;
1204         }
1205 
1206         /**
1207          * Override update to enforce a single default account, and do it atomically
1208          */
1209         @Override
update(Context context, ContentValues cv)1210         public int update(Context context, ContentValues cv) {
1211             if (cv.containsKey(AccountColumns.IS_DEFAULT) &&
1212                     cv.getAsBoolean(AccountColumns.IS_DEFAULT)) {
1213                 ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
1214                 ContentValues cv1 = new ContentValues();
1215                 cv1.put(AccountColumns.IS_DEFAULT, false);
1216                 // Clear the default flag in all accounts
1217                 ops.add(ContentProviderOperation.newUpdate(CONTENT_URI).withValues(cv1).build());
1218                 // Update this account
1219                 ops.add(ContentProviderOperation
1220                         .newUpdate(ContentUris.withAppendedId(CONTENT_URI, mId))
1221                         .withValues(cv).build());
1222                 try {
1223                     context.getContentResolver().applyBatch(EmailProvider.EMAIL_AUTHORITY, ops);
1224                     return 1;
1225                 } catch (RemoteException e) {
1226                     // There is nothing to be done here; fail by returning 0
1227                 } catch (OperationApplicationException e) {
1228                     // There is nothing to be done here; fail by returning 0
1229                 }
1230                 return 0;
1231             }
1232             return super.update(context, cv);
1233         }
1234 
1235         /*
1236          * Override this so that we can store the HostAuth's first and link them to the Account
1237          * (non-Javadoc)
1238          * @see com.android.email.provider.EmailContent#save(android.content.Context)
1239          */
1240         @Override
save(Context context)1241         public Uri save(Context context) {
1242             if (isSaved()) {
1243                 throw new UnsupportedOperationException();
1244             }
1245             // This logic is in place so I can (a) short circuit the expensive stuff when
1246             // possible, and (b) override (and throw) if anyone tries to call save() or update()
1247             // directly for Account, which are unsupported.
1248             if (mHostAuthRecv == null && mHostAuthSend == null && mIsDefault == false) {
1249                     return super.save(context);
1250             }
1251 
1252             int index = 0;
1253             int recvIndex = -1;
1254             int sendIndex = -1;
1255 
1256             // Create operations for saving the send and recv hostAuths
1257             // Also, remember which operation in the array they represent
1258             ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
1259             if (mHostAuthRecv != null) {
1260                 recvIndex = index++;
1261                 ops.add(ContentProviderOperation.newInsert(mHostAuthRecv.mBaseUri)
1262                         .withValues(mHostAuthRecv.toContentValues())
1263                         .build());
1264             }
1265             if (mHostAuthSend != null) {
1266                 sendIndex = index++;
1267                 ops.add(ContentProviderOperation.newInsert(mHostAuthRecv.mBaseUri)
1268                         .withValues(mHostAuthSend.toContentValues())
1269                         .build());
1270             }
1271 
1272             // Create operations for making this the only default account
1273             // Note, these are always updates because they change existing accounts
1274             if (mIsDefault) {
1275                 index++;
1276                 ContentValues cv1 = new ContentValues();
1277                 cv1.put(AccountColumns.IS_DEFAULT, 0);
1278                 ops.add(ContentProviderOperation.newUpdate(CONTENT_URI).withValues(cv1).build());
1279             }
1280 
1281             // Now do the Account
1282             ContentValues cv = null;
1283             if (recvIndex >= 0 || sendIndex >= 0) {
1284                 cv = new ContentValues();
1285                 if (recvIndex >= 0) {
1286                     cv.put(Account.HOST_AUTH_KEY_RECV, recvIndex);
1287                 }
1288                 if (sendIndex >= 0) {
1289                     cv.put(Account.HOST_AUTH_KEY_SEND, sendIndex);
1290                 }
1291             }
1292 
1293             ContentProviderOperation.Builder b = ContentProviderOperation.newInsert(mBaseUri);
1294             b.withValues(toContentValues());
1295             if (cv != null) {
1296                 b.withValueBackReferences(cv);
1297             }
1298             ops.add(b.build());
1299 
1300             try {
1301                 ContentProviderResult[] results =
1302                     context.getContentResolver().applyBatch(EmailProvider.EMAIL_AUTHORITY, ops);
1303                 // If saving, set the mId's of the various saved objects
1304                 if (recvIndex >= 0) {
1305                     long newId = getId(results[recvIndex].uri);
1306                     mHostAuthKeyRecv = newId;
1307                     mHostAuthRecv.mId = newId;
1308                 }
1309                 if (sendIndex >= 0) {
1310                     long newId = getId(results[sendIndex].uri);
1311                     mHostAuthKeySend = newId;
1312                     mHostAuthSend.mId = newId;
1313                 }
1314                 Uri u = results[index].uri;
1315                 mId = getId(u);
1316                 return u;
1317             } catch (RemoteException e) {
1318                 // There is nothing to be done here; fail by returning null
1319             } catch (OperationApplicationException e) {
1320                 // There is nothing to be done here; fail by returning null
1321             }
1322             return null;
1323         }
1324 
1325         @Override
toContentValues()1326         public ContentValues toContentValues() {
1327             ContentValues values = new ContentValues();
1328             values.put(AccountColumns.DISPLAY_NAME, mDisplayName);
1329             values.put(AccountColumns.EMAIL_ADDRESS, mEmailAddress);
1330             values.put(AccountColumns.SYNC_KEY, mSyncKey);
1331             values.put(AccountColumns.SYNC_LOOKBACK, mSyncLookback);
1332             values.put(AccountColumns.SYNC_INTERVAL, mSyncInterval);
1333             values.put(AccountColumns.HOST_AUTH_KEY_RECV, mHostAuthKeyRecv);
1334             values.put(AccountColumns.HOST_AUTH_KEY_SEND, mHostAuthKeySend);
1335             values.put(AccountColumns.FLAGS, mFlags);
1336             values.put(AccountColumns.IS_DEFAULT, mIsDefault);
1337             values.put(AccountColumns.COMPATIBILITY_UUID, mCompatibilityUuid);
1338             values.put(AccountColumns.SENDER_NAME, mSenderName);
1339             values.put(AccountColumns.RINGTONE_URI, mRingtoneUri);
1340             values.put(AccountColumns.PROTOCOL_VERSION, mProtocolVersion);
1341             values.put(AccountColumns.NEW_MESSAGE_COUNT, mNewMessageCount);
1342             return values;
1343         }
1344 
1345         /**
1346          * Supports Parcelable
1347          */
describeContents()1348         public int describeContents() {
1349             return 0;
1350         }
1351 
1352         /**
1353          * Supports Parcelable
1354          */
1355         public static final Parcelable.Creator<EmailContent.Account> CREATOR
1356                 = new Parcelable.Creator<EmailContent.Account>() {
1357             public EmailContent.Account createFromParcel(Parcel in) {
1358                 return new EmailContent.Account(in);
1359             }
1360 
1361             public EmailContent.Account[] newArray(int size) {
1362                 return new EmailContent.Account[size];
1363             }
1364         };
1365 
1366         /**
1367          * Supports Parcelable
1368          */
writeToParcel(Parcel dest, int flags)1369         public void writeToParcel(Parcel dest, int flags) {
1370             // mBaseUri is not parceled
1371             dest.writeLong(mId);
1372             dest.writeString(mDisplayName);
1373             dest.writeString(mEmailAddress);
1374             dest.writeString(mSyncKey);
1375             dest.writeInt(mSyncLookback);
1376             dest.writeInt(mSyncInterval);
1377             dest.writeLong(mHostAuthKeyRecv);
1378             dest.writeLong(mHostAuthKeySend);
1379             dest.writeInt(mFlags);
1380             dest.writeByte(mIsDefault ? (byte)1 : (byte)0);
1381             dest.writeString(mCompatibilityUuid);
1382             dest.writeString(mSenderName);
1383             dest.writeString(mRingtoneUri);
1384             dest.writeString(mProtocolVersion);
1385             dest.writeInt(mNewMessageCount);
1386 
1387             if (mHostAuthRecv != null) {
1388                 dest.writeByte((byte)1);
1389                 mHostAuthRecv.writeToParcel(dest, flags);
1390             } else {
1391                 dest.writeByte((byte)0);
1392             }
1393 
1394             if (mHostAuthSend != null) {
1395                 dest.writeByte((byte)1);
1396                 mHostAuthSend.writeToParcel(dest, flags);
1397             } else {
1398                 dest.writeByte((byte)0);
1399             }
1400         }
1401 
1402         /**
1403          * Supports Parcelable
1404          */
Account(Parcel in)1405         public Account(Parcel in) {
1406             mBaseUri = EmailContent.Account.CONTENT_URI;
1407             mId = in.readLong();
1408             mDisplayName = in.readString();
1409             mEmailAddress = in.readString();
1410             mSyncKey = in.readString();
1411             mSyncLookback = in.readInt();
1412             mSyncInterval = in.readInt();
1413             mHostAuthKeyRecv = in.readLong();
1414             mHostAuthKeySend = in.readLong();
1415             mFlags = in.readInt();
1416             mIsDefault = in.readByte() == 1;
1417             mCompatibilityUuid = in.readString();
1418             mSenderName = in.readString();
1419             mRingtoneUri = in.readString();
1420             mProtocolVersion = in.readString();
1421             mNewMessageCount = in.readInt();
1422 
1423             mHostAuthRecv = null;
1424             if (in.readByte() == 1) {
1425                 mHostAuthRecv = new EmailContent.HostAuth(in);
1426             }
1427 
1428             mHostAuthSend = null;
1429             if (in.readByte() == 1) {
1430                 mHostAuthSend = new EmailContent.HostAuth(in);
1431             }
1432         }
1433 
1434         /**
1435          * For debugger support only - DO NOT use for code.
1436          */
1437         @Override
toString()1438         public String toString() {
1439             StringBuilder sb = new StringBuilder('[');
1440             if (mHostAuthRecv != null && mHostAuthRecv.mProtocol != null) {
1441                 sb.append(mHostAuthRecv.mProtocol);
1442                 sb.append(':');
1443             }
1444             if (mDisplayName != null)   sb.append(mDisplayName);
1445             sb.append(':');
1446             if (mEmailAddress != null)  sb.append(mEmailAddress);
1447             sb.append(':');
1448             if (mSenderName != null)    sb.append(mSenderName);
1449             sb.append(']');
1450             return sb.toString();
1451         }
1452 
1453     }
1454 
1455     public interface AttachmentColumns {
1456         public static final String ID = "_id";
1457         // The display name of the attachment
1458         public static final String FILENAME = "fileName";
1459         // The mime type of the attachment
1460         public static final String MIME_TYPE = "mimeType";
1461         // The size of the attachment in bytes
1462         public static final String SIZE = "size";
1463         // The (internal) contentId of the attachment (inline attachments will have these)
1464         public static final String CONTENT_ID = "contentId";
1465         // The location of the loaded attachment (probably a file)
1466         public static final String CONTENT_URI = "contentUri";
1467         // A foreign key into the Message table (the message owning this attachment)
1468         public static final String MESSAGE_KEY = "messageKey";
1469         // The location of the attachment on the server side
1470         // For IMAP, this is a part number (e.g. 2.1); for EAS, it's the internal file name
1471         public static final String LOCATION = "location";
1472         // The transfer encoding of the attachment
1473         public static final String ENCODING = "encoding";
1474     }
1475 
1476     public static final class Attachment extends EmailContent implements AttachmentColumns {
1477         public static final String TABLE_NAME = "Attachment";
1478         public static final Uri CONTENT_URI = Uri.parse(EmailContent.CONTENT_URI + "/attachment");
1479         // This must be used with an appended id: ContentUris.withAppendedId(MESSAGE_ID_URI, id)
1480         public static final Uri MESSAGE_ID_URI = Uri.parse(
1481                 EmailContent.CONTENT_URI + "/attachment/message");
1482 
1483         public String mFileName;
1484         public String mMimeType;
1485         public long mSize;
1486         public String mContentId;
1487         public String mContentUri;
1488         public long mMessageKey;
1489         public String mLocation;
1490         public String mEncoding;
1491 
1492         public static final int CONTENT_ID_COLUMN = 0;
1493         public static final int CONTENT_FILENAME_COLUMN = 1;
1494         public static final int CONTENT_MIME_TYPE_COLUMN = 2;
1495         public static final int CONTENT_SIZE_COLUMN = 3;
1496         public static final int CONTENT_CONTENT_ID_COLUMN = 4;
1497         public static final int CONTENT_CONTENT_URI_COLUMN = 5;
1498         public static final int CONTENT_MESSAGE_ID_COLUMN = 6;
1499         public static final int CONTENT_LOCATION_COLUMN = 7;
1500         public static final int CONTENT_ENCODING_COLUMN = 8;
1501         public static final String[] CONTENT_PROJECTION = new String[] {
1502             RECORD_ID, AttachmentColumns.FILENAME, AttachmentColumns.MIME_TYPE,
1503             AttachmentColumns.SIZE, AttachmentColumns.CONTENT_ID, AttachmentColumns.CONTENT_URI,
1504             AttachmentColumns.MESSAGE_KEY, AttachmentColumns.LOCATION, AttachmentColumns.ENCODING
1505         };
1506 
1507         /**
1508          * no public constructor since this is a utility class
1509          */
Attachment()1510         public Attachment() {
1511             mBaseUri = CONTENT_URI;
1512         }
1513 
1514          /**
1515          * Restore an Attachment from the database, given its unique id
1516          * @param context
1517          * @param id
1518          * @return the instantiated Attachment
1519          */
restoreAttachmentWithId(Context context, long id)1520         public static Attachment restoreAttachmentWithId (Context context, long id) {
1521             Uri u = ContentUris.withAppendedId(Attachment.CONTENT_URI, id);
1522             Cursor c = context.getContentResolver().query(u, Attachment.CONTENT_PROJECTION,
1523                     null, null, null);
1524 
1525             try {
1526                 if (c.moveToFirst()) {
1527                     return getContent(c, Attachment.class);
1528                 } else {
1529                     return null;
1530                 }
1531             } finally {
1532                 c.close();
1533             }
1534         }
1535 
1536         /**
1537          * Restore all the Attachments of a message given its messageId
1538          */
restoreAttachmentsWithMessageId(Context context, long messageId)1539         public static Attachment[] restoreAttachmentsWithMessageId(Context context,
1540                 long messageId) {
1541             Uri uri = ContentUris.withAppendedId(MESSAGE_ID_URI, messageId);
1542             Cursor c = context.getContentResolver().query(uri, CONTENT_PROJECTION,
1543                     null, null, null);
1544             try {
1545                 int count = c.getCount();
1546                 Attachment[] attachments = new Attachment[count];
1547                 for (int i = 0; i < count; ++i) {
1548                     c.moveToNext();
1549                     attachments[i] = new Attachment().restore(c);
1550                 }
1551                 return attachments;
1552             } finally {
1553                 c.close();
1554             }
1555         }
1556 
1557         /**
1558          * Creates a unique file in the external store by appending a hyphen
1559          * and a number to the given filename.
1560          * @param filename
1561          * @return a new File object, or null if one could not be created
1562          */
createUniqueFile(String filename)1563         public static File createUniqueFile(String filename) {
1564             // TODO Handle internal storage, as required
1565             if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
1566                 File directory = Environment.getExternalStorageDirectory();
1567                 File file = new File(directory, filename);
1568                 if (!file.exists()) {
1569                     return file;
1570                 }
1571                 // Get the extension of the file, if any.
1572                 int index = filename.lastIndexOf('.');
1573                 String name = filename;
1574                 String extension = "";
1575                 if (index != -1) {
1576                     name = filename.substring(0, index);
1577                     extension = filename.substring(index);
1578                 }
1579                 for (int i = 2; i < Integer.MAX_VALUE; i++) {
1580                     file = new File(directory, name + '-' + i + extension);
1581                     if (!file.exists()) {
1582                         return file;
1583                     }
1584                 }
1585                 return null;
1586             }
1587             return null;
1588         }
1589 
1590         @Override
1591         @SuppressWarnings("unchecked")
restore(Cursor cursor)1592         public EmailContent.Attachment restore(Cursor cursor) {
1593             mBaseUri = CONTENT_URI;
1594             mId = cursor.getLong(CONTENT_ID_COLUMN);
1595             mFileName= cursor.getString(CONTENT_FILENAME_COLUMN);
1596             mMimeType = cursor.getString(CONTENT_MIME_TYPE_COLUMN);
1597             mSize = cursor.getLong(CONTENT_SIZE_COLUMN);
1598             mContentId = cursor.getString(CONTENT_CONTENT_ID_COLUMN);
1599             mContentUri = cursor.getString(CONTENT_CONTENT_URI_COLUMN);
1600             mMessageKey = cursor.getLong(CONTENT_MESSAGE_ID_COLUMN);
1601             mLocation = cursor.getString(CONTENT_LOCATION_COLUMN);
1602             mEncoding = cursor.getString(CONTENT_ENCODING_COLUMN);
1603             return this;
1604         }
1605 
1606         @Override
toContentValues()1607         public ContentValues toContentValues() {
1608             ContentValues values = new ContentValues();
1609             values.put(AttachmentColumns.FILENAME, mFileName);
1610             values.put(AttachmentColumns.MIME_TYPE, mMimeType);
1611             values.put(AttachmentColumns.SIZE, mSize);
1612             values.put(AttachmentColumns.CONTENT_ID, mContentId);
1613             values.put(AttachmentColumns.CONTENT_URI, mContentUri);
1614             values.put(AttachmentColumns.MESSAGE_KEY, mMessageKey);
1615             values.put(AttachmentColumns.LOCATION, mLocation);
1616             values.put(AttachmentColumns.ENCODING, mEncoding);
1617             return values;
1618         }
1619 
describeContents()1620         public int describeContents() {
1621              return 0;
1622         }
1623 
writeToParcel(Parcel dest, int flags)1624         public void writeToParcel(Parcel dest, int flags) {
1625             // mBaseUri is not parceled
1626             dest.writeLong(mId);
1627             dest.writeString(mFileName);
1628             dest.writeString(mMimeType);
1629             dest.writeLong(mSize);
1630             dest.writeString(mContentId);
1631             dest.writeString(mContentUri);
1632             dest.writeLong(mMessageKey);
1633             dest.writeString(mLocation);
1634             dest.writeString(mEncoding);
1635         }
1636 
Attachment(Parcel in)1637         public Attachment(Parcel in) {
1638             mBaseUri = EmailContent.Attachment.CONTENT_URI;
1639             mId = in.readLong();
1640             mFileName = in.readString();
1641             mMimeType = in.readString();
1642             mSize = in.readLong();
1643             mContentId = in.readString();
1644             mContentUri = in.readString();
1645             mMessageKey = in.readLong();
1646             mLocation = in.readString();
1647             mEncoding = in.readString();
1648          }
1649 
1650         public static final Parcelable.Creator<EmailContent.Attachment> CREATOR
1651         = new Parcelable.Creator<EmailContent.Attachment>() {
1652             public EmailContent.Attachment createFromParcel(Parcel in) {
1653                 return new EmailContent.Attachment(in);
1654             }
1655 
1656             public EmailContent.Attachment[] newArray(int size) {
1657                 return new EmailContent.Attachment[size];
1658             }
1659         };
1660 
1661         @Override
toString()1662         public String toString() {
1663             return "[" + mFileName + ", " + mMimeType + ", " + mSize + ", " + mContentId + ", "
1664                     + mContentUri + ", " + mMessageKey + ", " + mLocation + ", " + mEncoding + "]";
1665         }
1666     }
1667 
1668     public interface MailboxColumns {
1669         public static final String ID = "_id";
1670         // The display name of this mailbox [INDEX]
1671         static final String DISPLAY_NAME = "displayName";
1672         // The server's identifier for this mailbox
1673         public static final String SERVER_ID = "serverId";
1674         // The server's identifier for the parent of this mailbox (null = top-level)
1675         public static final String PARENT_SERVER_ID = "parentServerId";
1676         // A foreign key to the Account that owns this mailbox
1677         public static final String ACCOUNT_KEY = "accountKey";
1678         // The type (role) of this mailbox
1679         public static final String TYPE = "type";
1680         // The hierarchy separator character
1681         public static final String DELIMITER = "delimiter";
1682         // Server-based sync key or validity marker (e.g. "SyncKey" for EAS, "uidvalidity" for IMAP)
1683         public static final String SYNC_KEY = "syncKey";
1684         // The sync lookback period for this mailbox (or null if using the account default)
1685         public static final String SYNC_LOOKBACK = "syncLookback";
1686         // The sync frequency for this mailbox (or null if using the account default)
1687         public static final String SYNC_INTERVAL = "syncInterval";
1688         // The time of last successful sync completion (millis)
1689         public static final String SYNC_TIME = "syncTime";
1690         // Cached unread count
1691         public static final String UNREAD_COUNT = "unreadCount";
1692         // Visibility of this folder in a list of folders [INDEX]
1693         public static final String FLAG_VISIBLE = "flagVisible";
1694         // Other states, as a bit field, e.g. CHILDREN_VISIBLE, HAS_CHILDREN
1695         public static final String FLAGS = "flags";
1696         // Backward compatible
1697         public static final String VISIBLE_LIMIT = "visibleLimit";
1698         // Sync status (can be used as desired by sync services)
1699         public static final String SYNC_STATUS = "syncStatus";
1700     }
1701 
1702     public static final class Mailbox extends EmailContent implements SyncColumns, MailboxColumns {
1703         public static final String TABLE_NAME = "Mailbox";
1704         public static final Uri CONTENT_URI = Uri.parse(EmailContent.CONTENT_URI + "/mailbox");
1705         public static final Uri ADD_TO_FIELD_URI =
1706             Uri.parse(EmailContent.CONTENT_URI + "/mailboxIdAddToField");
1707 
1708         public String mDisplayName;
1709         public String mServerId;
1710         public String mParentServerId;
1711         public long mAccountKey;
1712         public int mType;
1713         public int mDelimiter;
1714         public String mSyncKey;
1715         public int mSyncLookback;
1716         public int mSyncInterval;
1717         public long mSyncTime;
1718         public int mUnreadCount;
1719         public boolean mFlagVisible = true;
1720         public int mFlags;
1721         public int mVisibleLimit;
1722         public String mSyncStatus;
1723 
1724         public static final int CONTENT_ID_COLUMN = 0;
1725         public static final int CONTENT_DISPLAY_NAME_COLUMN = 1;
1726         public static final int CONTENT_SERVER_ID_COLUMN = 2;
1727         public static final int CONTENT_PARENT_SERVER_ID_COLUMN = 3;
1728         public static final int CONTENT_ACCOUNT_KEY_COLUMN = 4;
1729         public static final int CONTENT_TYPE_COLUMN = 5;
1730         public static final int CONTENT_DELIMITER_COLUMN = 6;
1731         public static final int CONTENT_SYNC_KEY_COLUMN = 7;
1732         public static final int CONTENT_SYNC_LOOKBACK_COLUMN = 8;
1733         public static final int CONTENT_SYNC_INTERVAL_COLUMN = 9;
1734         public static final int CONTENT_SYNC_TIME_COLUMN = 10;
1735         public static final int CONTENT_UNREAD_COUNT_COLUMN = 11;
1736         public static final int CONTENT_FLAG_VISIBLE_COLUMN = 12;
1737         public static final int CONTENT_FLAGS_COLUMN = 13;
1738         public static final int CONTENT_VISIBLE_LIMIT_COLUMN = 14;
1739         public static final int CONTENT_SYNC_STATUS_COLUMN = 15;
1740         public static final String[] CONTENT_PROJECTION = new String[] {
1741             RECORD_ID, MailboxColumns.DISPLAY_NAME, MailboxColumns.SERVER_ID,
1742             MailboxColumns.PARENT_SERVER_ID, MailboxColumns.ACCOUNT_KEY, MailboxColumns.TYPE,
1743             MailboxColumns.DELIMITER, MailboxColumns.SYNC_KEY, MailboxColumns.SYNC_LOOKBACK,
1744             MailboxColumns.SYNC_INTERVAL, MailboxColumns.SYNC_TIME,MailboxColumns.UNREAD_COUNT,
1745             MailboxColumns.FLAG_VISIBLE, MailboxColumns.FLAGS, MailboxColumns.VISIBLE_LIMIT,
1746             MailboxColumns.SYNC_STATUS
1747         };
1748         public static final long NO_MAILBOX = -1;
1749 
1750         // Sentinel values for the mSyncInterval field of both Mailbox records
1751         public static final int CHECK_INTERVAL_NEVER = -1;
1752         public static final int CHECK_INTERVAL_PUSH = -2;
1753         // The following two sentinel values are used by EAS
1754         // Ping indicates that the EAS mailbox is synced based on a "ping" from the server
1755         public static final int CHECK_INTERVAL_PING = -3;
1756         // Push-Hold indicates an EAS push or ping Mailbox shouldn't sync just yet
1757         public static final int CHECK_INTERVAL_PUSH_HOLD = -4;
1758 
1759         private static final String WHERE_TYPE_AND_ACCOUNT_KEY =
1760             MailboxColumns.TYPE + "=? and " + MailboxColumns.ACCOUNT_KEY + "=?";
1761 
1762         // Types of mailboxes.  The list is ordered to match a typical UI presentation, e.g.
1763         // placing the inbox at the top.
1764         // The "main" mailbox for the account, almost always referred to as "Inbox"
1765         // Arrays of "special_mailbox_display_names" and "special_mailbox_icons" are depends on
1766         // types Id of mailboxes.
1767         public static final int TYPE_INBOX = 0;
1768         // Types of mailboxes
1769         // Holds mail (generic)
1770         public static final int TYPE_MAIL = 1;
1771         // Parent-only mailbox; holds no mail
1772         public static final int TYPE_PARENT = 2;
1773         // Holds drafts
1774         public static final int TYPE_DRAFTS = 3;
1775         // The local outbox associated with the Account
1776         public static final int TYPE_OUTBOX = 4;
1777         // Holds sent mail
1778         public static final int TYPE_SENT = 5;
1779         // Holds deleted mail
1780         public static final int TYPE_TRASH = 6;
1781         // Holds junk mail
1782         public static final int TYPE_JUNK = 7;
1783 
1784         // Types after this are used for non-mail mailboxes (as in EAS)
1785         public static final int TYPE_NOT_EMAIL = 0x40;
1786         public static final int TYPE_CALENDAR = 0x41;
1787         public static final int TYPE_CONTACTS = 0x42;
1788         public static final int TYPE_TASKS = 0x43;
1789         public static final int TYPE_EAS_ACCOUNT_MAILBOX = 0x44;
1790 
1791         // Bit field flags
1792         public static final int FLAG_HAS_CHILDREN = 1<<0;
1793         public static final int FLAG_CHILDREN_VISIBLE = 1<<1;
1794         public static final int FLAG_CANT_PUSH = 1<<2;
1795 
1796         // Magic mailbox ID's
1797         // NOTE:  This is a quick solution for merged mailboxes.  I would rather implement this
1798         // with a more generic way of packaging and sharing queries between activities
1799         public static final long QUERY_ALL_INBOXES = -2;
1800         public static final long QUERY_ALL_UNREAD = -3;
1801         public static final long QUERY_ALL_FAVORITES = -4;
1802         public static final long QUERY_ALL_DRAFTS = -5;
1803         public static final long QUERY_ALL_OUTBOX = -6;
1804 
Mailbox()1805         public Mailbox() {
1806             mBaseUri = CONTENT_URI;
1807         }
1808 
1809          /**
1810          * Restore a Mailbox from the database, given its unique id
1811          * @param context
1812          * @param id
1813          * @return the instantiated Mailbox
1814          */
restoreMailboxWithId(Context context, long id)1815         public static Mailbox restoreMailboxWithId(Context context, long id) {
1816             Uri u = ContentUris.withAppendedId(Mailbox.CONTENT_URI, id);
1817             Cursor c = context.getContentResolver().query(u, Mailbox.CONTENT_PROJECTION,
1818                     null, null, null);
1819 
1820             try {
1821                 if (c.moveToFirst()) {
1822                     return EmailContent.getContent(c, Mailbox.class);
1823                 } else {
1824                     return null;
1825                 }
1826             } finally {
1827                 c.close();
1828             }
1829         }
1830 
1831         @Override
1832         @SuppressWarnings("unchecked")
restore(Cursor cursor)1833         public EmailContent.Mailbox restore(Cursor cursor) {
1834             mBaseUri = CONTENT_URI;
1835             mId = cursor.getLong(CONTENT_ID_COLUMN);
1836             mDisplayName = cursor.getString(CONTENT_DISPLAY_NAME_COLUMN);
1837             mServerId = cursor.getString(CONTENT_SERVER_ID_COLUMN);
1838             mParentServerId = cursor.getString(CONTENT_PARENT_SERVER_ID_COLUMN);
1839             mAccountKey = cursor.getLong(CONTENT_ACCOUNT_KEY_COLUMN);
1840             mType = cursor.getInt(CONTENT_TYPE_COLUMN);
1841             mDelimiter = cursor.getInt(CONTENT_DELIMITER_COLUMN);
1842             mSyncKey = cursor.getString(CONTENT_SYNC_KEY_COLUMN);
1843             mSyncLookback = cursor.getInt(CONTENT_SYNC_LOOKBACK_COLUMN);
1844             mSyncInterval = cursor.getInt(CONTENT_SYNC_INTERVAL_COLUMN);
1845             mSyncTime = cursor.getLong(CONTENT_SYNC_TIME_COLUMN);
1846             mUnreadCount = cursor.getInt(CONTENT_UNREAD_COUNT_COLUMN);
1847             mFlagVisible = cursor.getInt(CONTENT_FLAG_VISIBLE_COLUMN) == 1;
1848             mFlags = cursor.getInt(CONTENT_FLAGS_COLUMN);
1849             mVisibleLimit = cursor.getInt(CONTENT_VISIBLE_LIMIT_COLUMN);
1850             mSyncStatus = cursor.getString(CONTENT_SYNC_STATUS_COLUMN);
1851             return this;
1852         }
1853 
1854         @Override
toContentValues()1855         public ContentValues toContentValues() {
1856             ContentValues values = new ContentValues();
1857             values.put(MailboxColumns.DISPLAY_NAME, mDisplayName);
1858             values.put(MailboxColumns.SERVER_ID, mServerId);
1859             values.put(MailboxColumns.PARENT_SERVER_ID, mParentServerId);
1860             values.put(MailboxColumns.ACCOUNT_KEY, mAccountKey);
1861             values.put(MailboxColumns.TYPE, mType);
1862             values.put(MailboxColumns.DELIMITER, mDelimiter);
1863             values.put(MailboxColumns.SYNC_KEY, mSyncKey);
1864             values.put(MailboxColumns.SYNC_LOOKBACK, mSyncLookback);
1865             values.put(MailboxColumns.SYNC_INTERVAL, mSyncInterval);
1866             values.put(MailboxColumns.SYNC_TIME, mSyncTime);
1867             values.put(MailboxColumns.UNREAD_COUNT, mUnreadCount);
1868             values.put(MailboxColumns.FLAG_VISIBLE, mFlagVisible);
1869             values.put(MailboxColumns.FLAGS, mFlags);
1870             values.put(MailboxColumns.VISIBLE_LIMIT, mVisibleLimit);
1871             values.put(MailboxColumns.SYNC_STATUS, mSyncStatus);
1872             return values;
1873         }
1874 
1875         /**
1876          * Convenience method to return the id of a given type of Mailbox for a given Account
1877          * @param context the caller's context, used to get a ContentResolver
1878          * @param accountId the id of the account to be queried
1879          * @param type the mailbox type, as defined above
1880          * @return the id of the mailbox, or -1 if not found
1881          */
findMailboxOfType(Context context, long accountId, int type)1882         public static long findMailboxOfType(Context context, long accountId, int type) {
1883             long mailboxId = NO_MAILBOX;
1884             String[] bindArguments = new String[] {Long.toString(type), Long.toString(accountId)};
1885             Cursor c = context.getContentResolver().query(Mailbox.CONTENT_URI,
1886                     ID_PROJECTION, WHERE_TYPE_AND_ACCOUNT_KEY, bindArguments, null);
1887             try {
1888                 if (c.moveToFirst()) {
1889                     mailboxId = c.getLong(ID_PROJECTION_COLUMN);
1890                 }
1891             } finally {
1892                 c.close();
1893             }
1894             return mailboxId;
1895         }
1896     }
1897 
1898     public interface HostAuthColumns {
1899         public static final String ID = "_id";
1900         // The protocol (e.g. "imap", "pop3", "eas", "smtp"
1901         static final String PROTOCOL = "protocol";
1902         // The host address
1903         static final String ADDRESS = "address";
1904         // The port to use for the connection
1905         static final String PORT = "port";
1906         // General purpose flags
1907         static final String FLAGS = "flags";
1908         // The login (user name)
1909         static final String LOGIN = "login";
1910         // Password
1911         static final String PASSWORD = "password";
1912         // A domain or path, if required (used in IMAP and EAS)
1913         static final String DOMAIN = "domain";
1914         // DEPRECATED - Will not be set or stored
1915         static final String ACCOUNT_KEY = "accountKey";
1916     }
1917 
1918     public static final class HostAuth extends EmailContent implements HostAuthColumns, Parcelable {
1919         public static final String TABLE_NAME = "HostAuth";
1920         public static final Uri CONTENT_URI = Uri.parse(EmailContent.CONTENT_URI + "/hostauth");
1921 
1922         public static final int FLAG_SSL = 1;
1923         public static final int FLAG_TLS = 2;
1924         public static final int FLAG_AUTHENTICATE = 4;
1925         public static final int FLAG_TRUST_ALL_CERTIFICATES = 8;
1926 
1927         public String mProtocol;
1928         public String mAddress;
1929         public int mPort;
1930         public int mFlags;
1931         public String mLogin;
1932         public String mPassword;
1933         public String mDomain;
1934         public long mAccountKey;        // DEPRECATED - Will not be set or stored
1935 
1936         public static final int CONTENT_ID_COLUMN = 0;
1937         public static final int CONTENT_PROTOCOL_COLUMN = 1;
1938         public static final int CONTENT_ADDRESS_COLUMN = 2;
1939         public static final int CONTENT_PORT_COLUMN = 3;
1940         public static final int CONTENT_FLAGS_COLUMN = 4;
1941         public static final int CONTENT_LOGIN_COLUMN = 5;
1942         public static final int CONTENT_PASSWORD_COLUMN = 6;
1943         public static final int CONTENT_DOMAIN_COLUMN = 7;
1944         public static final int CONTENT_ACCOUNT_KEY_COLUMN = 8;
1945 
1946         public static final String[] CONTENT_PROJECTION = new String[] {
1947             RECORD_ID, HostAuthColumns.PROTOCOL, HostAuthColumns.ADDRESS, HostAuthColumns.PORT,
1948             HostAuthColumns.FLAGS, HostAuthColumns.LOGIN,
1949             HostAuthColumns.PASSWORD, HostAuthColumns.DOMAIN,
1950             HostAuthColumns.ACCOUNT_KEY
1951         };
1952 
1953         /**
1954          * no public constructor since this is a utility class
1955          */
HostAuth()1956         public HostAuth() {
1957             mBaseUri = CONTENT_URI;
1958 
1959             // other defaults policy)
1960             mPort = -1;
1961         }
1962 
1963          /**
1964          * Restore a HostAuth from the database, given its unique id
1965          * @param context
1966          * @param id
1967          * @return the instantiated HostAuth
1968          */
restoreHostAuthWithId(Context context, long id)1969         public static HostAuth restoreHostAuthWithId(Context context, long id) {
1970             Uri u = ContentUris.withAppendedId(EmailContent.HostAuth.CONTENT_URI, id);
1971             Cursor c = context.getContentResolver().query(u, HostAuth.CONTENT_PROJECTION,
1972                     null, null, null);
1973 
1974             try {
1975                 if (c.moveToFirst()) {
1976                     return getContent(c, HostAuth.class);
1977                 } else {
1978                     return null;
1979                 }
1980             } finally {
1981                 c.close();
1982             }
1983         }
1984 
1985         @Override
1986         @SuppressWarnings("unchecked")
restore(Cursor cursor)1987         public EmailContent.HostAuth restore(Cursor cursor) {
1988             mBaseUri = CONTENT_URI;
1989             mId = cursor.getLong(CONTENT_ID_COLUMN);
1990             mProtocol = cursor.getString(CONTENT_PROTOCOL_COLUMN);
1991             mAddress = cursor.getString(CONTENT_ADDRESS_COLUMN);
1992             mPort = cursor.getInt(CONTENT_PORT_COLUMN);
1993             mFlags = cursor.getInt(CONTENT_FLAGS_COLUMN);
1994             mLogin = cursor.getString(CONTENT_LOGIN_COLUMN);
1995             mPassword = cursor.getString(CONTENT_PASSWORD_COLUMN);
1996             mDomain = cursor.getString(CONTENT_DOMAIN_COLUMN);
1997             mAccountKey = cursor.getLong(CONTENT_ACCOUNT_KEY_COLUMN);
1998             return this;
1999         }
2000 
2001         @Override
toContentValues()2002         public ContentValues toContentValues() {
2003             ContentValues values = new ContentValues();
2004             values.put(HostAuthColumns.PROTOCOL, mProtocol);
2005             values.put(HostAuthColumns.ADDRESS, mAddress);
2006             values.put(HostAuthColumns.PORT, mPort);
2007             values.put(HostAuthColumns.FLAGS, mFlags);
2008             values.put(HostAuthColumns.LOGIN, mLogin);
2009             values.put(HostAuthColumns.PASSWORD, mPassword);
2010             values.put(HostAuthColumns.DOMAIN, mDomain);
2011             values.put(HostAuthColumns.ACCOUNT_KEY, mAccountKey);
2012             return values;
2013         }
2014 
2015         /**
2016          * For compatibility while converting to provider model, generate a "store URI"
2017          * TODO cache this so we don't rebuild every time
2018          *
2019          * @return a string in the form of a Uri, as used by the other parts of the email app
2020          */
getStoreUri()2021         public String getStoreUri() {
2022             String security;
2023             switch (mFlags & (FLAG_SSL | FLAG_TLS | FLAG_TRUST_ALL_CERTIFICATES)) {
2024                 case FLAG_SSL:
2025                     security = "+ssl+";
2026                     break;
2027                 case FLAG_SSL | FLAG_TRUST_ALL_CERTIFICATES:
2028                     security = "+ssl+trustallcerts";
2029                     break;
2030                 case FLAG_TLS:
2031                     security = "+tls+";
2032                     break;
2033                 case FLAG_TLS | FLAG_TRUST_ALL_CERTIFICATES:
2034                     security = "+tls+trustallcerts";
2035                     break;
2036                 default:
2037                     security = "";
2038                     break;
2039             }
2040             String userInfo = null;
2041             if ((mFlags & FLAG_AUTHENTICATE) != 0) {
2042                 String trimUser = (mLogin != null) ? mLogin.trim() : "";
2043                 String trimPassword = (mPassword != null) ? mPassword.trim() : "";
2044                 userInfo = trimUser + ":" + trimPassword;
2045             }
2046             String address = (mAddress != null) ? mAddress.trim() : null;
2047             String path = (mDomain != null) ? "/" + mDomain : null;
2048 
2049             URI uri;
2050             try {
2051                 uri = new URI(
2052                         mProtocol + security,
2053                         userInfo,
2054                         address,
2055                         mPort,
2056                         path,
2057                         null,
2058                         null);
2059                 return uri.toString();
2060             } catch (URISyntaxException e) {
2061                 return null;
2062             }
2063         }
2064 
2065         /**
2066          * For compatibility while converting to provider model, set fields from a "store URI"
2067          *
2068          * @param uriString a String containing a Uri
2069          */
2070         @Deprecated
setStoreUri(String uriString)2071         public void setStoreUri(String uriString) {
2072             try {
2073                 URI uri = new URI(uriString);
2074                 mLogin = null;
2075                 mPassword = null;
2076                 mFlags &= ~FLAG_AUTHENTICATE;
2077                 if (uri.getUserInfo() != null) {
2078                     String[] userInfoParts = uri.getUserInfo().split(":", 2);
2079                     mLogin = userInfoParts[0];
2080                     mFlags |= FLAG_AUTHENTICATE;
2081                     if (userInfoParts.length > 1) {
2082                         mPassword = userInfoParts[1];
2083                     }
2084                 }
2085 
2086                 // Set protocol, security, and additional flags based on uri scheme
2087                 String[] schemeParts = uri.getScheme().split("\\+");
2088                 mProtocol = (schemeParts.length >= 1) ? schemeParts[0] : null;
2089                 mFlags &= ~(FLAG_SSL | FLAG_TLS | FLAG_TRUST_ALL_CERTIFICATES);
2090                 boolean ssl = false;
2091                 if (schemeParts.length >= 2) {
2092                     String part1 = schemeParts[1];
2093                     if ("ssl".equals(part1)) {
2094                         ssl = true;
2095                         mFlags |= FLAG_SSL;
2096                     } else if ("tls".equals(part1)) {
2097                         mFlags |= FLAG_TLS;
2098                     }
2099                     if (schemeParts.length >= 3) {
2100                         String part2 = schemeParts[2];
2101                         if ("trustallcerts".equals(part2)) {
2102                             mFlags |= FLAG_TRUST_ALL_CERTIFICATES;
2103                         }
2104                     }
2105                 }
2106 
2107                 mAddress = uri.getHost();
2108                 mPort = uri.getPort();
2109                 if (mPort == -1) {
2110                     // infer port# from protocol + security
2111                     // SSL implies a different port - TLS runs in the "regular" port
2112                     // TODO:  This really shouldn't be here - it should have been set up
2113                     // in the account setup screens.
2114                     if ("pop3".equals(mProtocol)) {
2115                         mPort = ssl ? 995 : 110;
2116                     } else if ("imap".equals(mProtocol)) {
2117                         mPort = ssl ? 993 : 143;
2118                     } else if ("eas".equals(mProtocol)) {
2119                         mPort = ssl ? 443 : 80;
2120                     }  else if ("smtp".equals(mProtocol)) {
2121                         mPort = ssl ? 465 : 587;
2122                     }
2123                 }
2124 
2125                 if (uri.getPath() != null && uri.getPath().length() > 0) {
2126                     mDomain = uri.getPath().substring(1);
2127                 }
2128 
2129 
2130             } catch (URISyntaxException use) {
2131                 /*
2132                  * We should always be able to parse our own settings.
2133                  */
2134                 throw new Error(use);
2135             }
2136 
2137         }
2138 
2139         /**
2140          * Supports Parcelable
2141          */
describeContents()2142         public int describeContents() {
2143             return 0;
2144         }
2145 
2146         /**
2147          * Supports Parcelable
2148          */
2149         public static final Parcelable.Creator<EmailContent.HostAuth> CREATOR
2150                 = new Parcelable.Creator<EmailContent.HostAuth>() {
2151             public EmailContent.HostAuth createFromParcel(Parcel in) {
2152                 return new EmailContent.HostAuth(in);
2153             }
2154 
2155             public EmailContent.HostAuth[] newArray(int size) {
2156                 return new EmailContent.HostAuth[size];
2157             }
2158         };
2159 
2160         /**
2161          * Supports Parcelable
2162          */
writeToParcel(Parcel dest, int flags)2163         public void writeToParcel(Parcel dest, int flags) {
2164             // mBaseUri is not parceled
2165             dest.writeLong(mId);
2166             dest.writeString(mProtocol);
2167             dest.writeString(mAddress);
2168             dest.writeInt(mPort);
2169             dest.writeInt(mFlags);
2170             dest.writeString(mLogin);
2171             dest.writeString(mPassword);
2172             dest.writeString(mDomain);
2173             dest.writeLong(mAccountKey);
2174         }
2175 
2176         /**
2177          * Supports Parcelable
2178          */
HostAuth(Parcel in)2179         public HostAuth(Parcel in) {
2180             mBaseUri = CONTENT_URI;
2181             mId = in.readLong();
2182             mProtocol = in.readString();
2183             mAddress = in.readString();
2184             mPort = in.readInt();
2185             mFlags = in.readInt();
2186             mLogin = in.readString();
2187             mPassword = in.readString();
2188             mDomain = in.readString();
2189             mAccountKey = in.readLong();
2190         }
2191 
2192         /**
2193          * For debugger support only - DO NOT use for code.
2194          */
2195         @Override
toString()2196         public String toString() {
2197             return getStoreUri();
2198         }
2199     }
2200 }
2201