EmailContent.java revision 4c4e4c3515c3e3300e03f90e02a0c520dc2dff32
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
17package com.android.emailcommon.provider;
18
19import com.android.emailcommon.utility.TextUtilities;
20import com.android.emailcommon.utility.Utility;
21
22import android.content.ContentProviderOperation;
23import android.content.ContentProviderResult;
24import android.content.ContentResolver;
25import android.content.ContentUris;
26import android.content.ContentValues;
27import android.content.Context;
28import android.content.OperationApplicationException;
29import android.database.Cursor;
30import android.net.Uri;
31import android.os.Environment;
32import android.os.Parcel;
33import android.os.Parcelable;
34import android.os.RemoteException;
35import android.text.TextUtils;
36
37import java.io.File;
38import java.net.URI;
39import java.net.URISyntaxException;
40import java.util.ArrayList;
41import java.util.List;
42import java.util.UUID;
43
44
45/**
46 * EmailContent is the superclass of the various classes of content stored by EmailProvider.
47 *
48 * It is intended to include 1) column definitions for use with the Provider, and 2) convenience
49 * methods for saving and retrieving content from the Provider.
50 *
51 * This class will be used by 1) the Email process (which includes the application and
52 * EmaiLProvider) as well as 2) the Exchange process (which runs independently).  It will
53 * necessarily be cloned for use in these two cases.
54 *
55 * Conventions used in naming columns:
56 *   RECORD_ID is the primary key for all Email records
57 *   The SyncColumns interface is used by all classes that are synced to the server directly
58 *   (Mailbox and Email)
59 *
60 *   <name>_KEY always refers to a foreign key
61 *   <name>_ID always refers to a unique identifier (whether on client, server, etc.)
62 *
63 */
64public abstract class EmailContent {
65
66    public static final String AUTHORITY = "com.android.email.provider";
67    // The notifier authority is used to send notifications regarding changes to messages (insert,
68    // delete, or update) and is intended as an optimization for use by clients of message list
69    // cursors (initially, the email AppWidget).
70    public static final String NOTIFIER_AUTHORITY = "com.android.email.notifier";
71
72    public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY);
73    public static final String PARAMETER_LIMIT = "limit";
74
75    public static final Uri CONTENT_NOTIFIER_URI = Uri.parse("content://" + NOTIFIER_AUTHORITY);
76
77    public static final String PROVIDER_PERMISSION = "com.android.email.permission.ACCESS_PROVIDER";
78
79    // All classes share this
80    public static final String RECORD_ID = "_id";
81
82    public static final String[] COUNT_COLUMNS = new String[]{"count(*)"};
83
84    /**
85     * This projection can be used with any of the EmailContent classes, when all you need
86     * is a list of id's.  Use ID_PROJECTION_COLUMN to access the row data.
87     */
88    public static final String[] ID_PROJECTION = new String[] {
89        RECORD_ID
90    };
91    public static final int ID_PROJECTION_COLUMN = 0;
92
93    public static final String ID_SELECTION = RECORD_ID + " =?";
94
95    public static final String FIELD_COLUMN_NAME = "field";
96    public static final String ADD_COLUMN_NAME = "add";
97    public static final String SET_COLUMN_NAME = "set";
98
99    // Newly created objects get this id
100    public static final int NOT_SAVED = -1;
101    // The base Uri that this piece of content came from
102    public Uri mBaseUri;
103    // Lazily initialized uri for this Content
104    private Uri mUri = null;
105    // The id of the Content
106    public long mId = NOT_SAVED;
107
108    // Write the Content into a ContentValues container
109    public abstract ContentValues toContentValues();
110    // Read the Content from a ContentCursor
111    public abstract void restore (Cursor cursor);
112
113    // The Uri is lazily initialized
114    public Uri getUri() {
115        if (mUri == null) {
116            mUri = ContentUris.withAppendedId(mBaseUri, mId);
117        }
118        return mUri;
119    }
120
121    public boolean isSaved() {
122        return mId != NOT_SAVED;
123    }
124
125
126    /**
127     * Restore a subclass of EmailContent from the database
128     * @param context the caller's context
129     * @param klass the class to restore
130     * @param contentUri the content uri of the EmailContent subclass
131     * @param contentProjection the content projection for the EmailContent subclass
132     * @param id the unique id of the object
133     * @return the instantiated object
134     */
135    public static <T extends EmailContent> T restoreContentWithId(Context context,
136            Class<T> klass, Uri contentUri, String[] contentProjection, long id) {
137        Uri u = ContentUris.withAppendedId(contentUri, id);
138        Cursor c = context.getContentResolver().query(u, contentProjection, null, null, null);
139        if (c == null) throw new ProviderUnavailableException();
140        try {
141            if (c.moveToFirst()) {
142                return getContent(c, klass);
143            } else {
144                return null;
145            }
146        } finally {
147            c.close();
148        }
149    }
150
151
152    // The Content sub class must have a no-arg constructor
153    static public <T extends EmailContent> T getContent(Cursor cursor, Class<T> klass) {
154        try {
155            T content = klass.newInstance();
156            content.mId = cursor.getLong(0);
157            content.restore(cursor);
158            return content;
159        } catch (IllegalAccessException e) {
160            e.printStackTrace();
161        } catch (InstantiationException e) {
162            e.printStackTrace();
163        }
164        return null;
165    }
166
167    public Uri save(Context context) {
168        if (isSaved()) {
169            throw new UnsupportedOperationException();
170        }
171        Uri res = context.getContentResolver().insert(mBaseUri, toContentValues());
172        mId = Long.parseLong(res.getPathSegments().get(1));
173        return res;
174    }
175
176    public int update(Context context, ContentValues contentValues) {
177        if (!isSaved()) {
178            throw new UnsupportedOperationException();
179        }
180        return context.getContentResolver().update(getUri(), contentValues, null, null);
181    }
182
183    static public int update(Context context, Uri baseUri, long id, ContentValues contentValues) {
184        return context.getContentResolver()
185            .update(ContentUris.withAppendedId(baseUri, id), contentValues, null, null);
186    }
187
188    static public int delete(Context context, Uri baseUri, long id) {
189        return context.getContentResolver()
190            .delete(ContentUris.withAppendedId(baseUri, id), null, null);
191    }
192
193    /**
194     * Generic count method that can be used for any ContentProvider
195     *
196     * @param context the calling Context
197     * @param uri the Uri for the provider query
198     * @param selection as with a query call
199     * @param selectionArgs as with a query call
200     * @return the number of items matching the query (or zero)
201     */
202    static public int count(Context context, Uri uri, String selection, String[] selectionArgs) {
203        return Utility.getFirstRowLong(context,
204                uri, COUNT_COLUMNS, selection, selectionArgs, null, 0, Long.valueOf(0)).intValue();
205    }
206
207    /**
208     * Same as {@link #count(Context, Uri, String, String[])} without selection.
209     */
210    static public int count(Context context, Uri uri) {
211        return count(context, uri, null, null);
212    }
213
214    static public Uri uriWithLimit(Uri uri, int limit) {
215        return uri.buildUpon().appendQueryParameter(EmailContent.PARAMETER_LIMIT,
216                Integer.toString(limit)).build();
217    }
218
219    /**
220     * no public constructor since this is a utility class
221     */
222    protected EmailContent() {
223    }
224
225    public interface SyncColumns {
226        public static final String ID = "_id";
227        // source id (string) : the source's name of this item
228        public static final String SERVER_ID = "syncServerId";
229        // source's timestamp (long) for this item
230        public static final String SERVER_TIMESTAMP = "syncServerTimeStamp";
231    }
232
233    public interface BodyColumns {
234        public static final String ID = "_id";
235        // Foreign key to the message corresponding to this body
236        public static final String MESSAGE_KEY = "messageKey";
237        // The html content itself
238        public static final String HTML_CONTENT = "htmlContent";
239        // The plain text content itself
240        public static final String TEXT_CONTENT = "textContent";
241        // Replied-to or forwarded body (in html form)
242        public static final String HTML_REPLY = "htmlReply";
243        // Replied-to or forwarded body (in text form)
244        public static final String TEXT_REPLY = "textReply";
245        // A reference to a message's unique id used in reply/forward.
246        // Protocol code can be expected to use this column in determining whether a message can be
247        // deleted safely (i.e. isn't referenced by other messages)
248        public static final String SOURCE_MESSAGE_KEY = "sourceMessageKey";
249        // The text to be placed between a reply/forward response and the original message
250        public static final String INTRO_TEXT = "introText";
251    }
252
253    public static final class Body extends EmailContent implements BodyColumns {
254        public static final String TABLE_NAME = "Body";
255
256        @SuppressWarnings("hiding")
257        public static final Uri CONTENT_URI = Uri.parse(EmailContent.CONTENT_URI + "/body");
258
259        public static final int CONTENT_ID_COLUMN = 0;
260        public static final int CONTENT_MESSAGE_KEY_COLUMN = 1;
261        public static final int CONTENT_HTML_CONTENT_COLUMN = 2;
262        public static final int CONTENT_TEXT_CONTENT_COLUMN = 3;
263        public static final int CONTENT_HTML_REPLY_COLUMN = 4;
264        public static final int CONTENT_TEXT_REPLY_COLUMN = 5;
265        public static final int CONTENT_SOURCE_KEY_COLUMN = 6;
266        public static final int CONTENT_INTRO_TEXT_COLUMN = 7;
267        public static final String[] CONTENT_PROJECTION = new String[] {
268            RECORD_ID, BodyColumns.MESSAGE_KEY, BodyColumns.HTML_CONTENT, BodyColumns.TEXT_CONTENT,
269            BodyColumns.HTML_REPLY, BodyColumns.TEXT_REPLY, BodyColumns.SOURCE_MESSAGE_KEY,
270            BodyColumns.INTRO_TEXT
271        };
272
273        public static final String[] COMMON_PROJECTION_TEXT = new String[] {
274            RECORD_ID, BodyColumns.TEXT_CONTENT
275        };
276        public static final String[] COMMON_PROJECTION_HTML = new String[] {
277            RECORD_ID, BodyColumns.HTML_CONTENT
278        };
279        public static final String[] COMMON_PROJECTION_REPLY_TEXT = new String[] {
280            RECORD_ID, BodyColumns.TEXT_REPLY
281        };
282        public static final String[] COMMON_PROJECTION_REPLY_HTML = new String[] {
283            RECORD_ID, BodyColumns.HTML_REPLY
284        };
285        public static final String[] COMMON_PROJECTION_INTRO = new String[] {
286            RECORD_ID, BodyColumns.INTRO_TEXT
287        };
288        public static final String[] COMMON_PROJECTION_SOURCE = new String[] {
289            RECORD_ID, BodyColumns.SOURCE_MESSAGE_KEY
290        };
291         public static final int COMMON_PROJECTION_COLUMN_TEXT = 1;
292
293        private static final String[] PROJECTION_SOURCE_KEY =
294            new String[] { BodyColumns.SOURCE_MESSAGE_KEY };
295
296        public long mMessageKey;
297        public String mHtmlContent;
298        public String mTextContent;
299        public String mHtmlReply;
300        public String mTextReply;
301        public long mSourceKey;
302        public String mIntroText;
303
304        public Body() {
305            mBaseUri = CONTENT_URI;
306        }
307
308        @Override
309        public ContentValues toContentValues() {
310            ContentValues values = new ContentValues();
311
312            // Assign values for each row.
313            values.put(BodyColumns.MESSAGE_KEY, mMessageKey);
314            values.put(BodyColumns.HTML_CONTENT, mHtmlContent);
315            values.put(BodyColumns.TEXT_CONTENT, mTextContent);
316            values.put(BodyColumns.HTML_REPLY, mHtmlReply);
317            values.put(BodyColumns.TEXT_REPLY, mTextReply);
318            values.put(BodyColumns.SOURCE_MESSAGE_KEY, mSourceKey);
319            values.put(BodyColumns.INTRO_TEXT, mIntroText);
320            return values;
321        }
322
323        /**
324         * Given a cursor, restore a Body from it
325         * @param cursor a cursor which must NOT be null
326         * @return the Body as restored from the cursor
327         */
328        private static Body restoreBodyWithCursor(Cursor cursor) {
329            try {
330                if (cursor.moveToFirst()) {
331                    return getContent(cursor, Body.class);
332                } else {
333                    return null;
334                }
335            } finally {
336                cursor.close();
337            }
338        }
339
340        public static Body restoreBodyWithId(Context context, long id) {
341            Uri u = ContentUris.withAppendedId(Body.CONTENT_URI, id);
342            Cursor c = context.getContentResolver().query(u, Body.CONTENT_PROJECTION,
343                    null, null, null);
344            if (c == null) throw new ProviderUnavailableException();
345            return restoreBodyWithCursor(c);
346        }
347
348        public static Body restoreBodyWithMessageId(Context context, long messageId) {
349            Cursor c = context.getContentResolver().query(Body.CONTENT_URI,
350                    Body.CONTENT_PROJECTION, Body.MESSAGE_KEY + "=?",
351                    new String[] {Long.toString(messageId)}, null);
352            if (c == null) throw new ProviderUnavailableException();
353            return restoreBodyWithCursor(c);
354        }
355
356        /**
357         * Returns the bodyId for the given messageId, or -1 if no body is found.
358         */
359        public static long lookupBodyIdWithMessageId(Context context, long messageId) {
360            return Utility.getFirstRowLong(context, Body.CONTENT_URI,
361                    ID_PROJECTION, Body.MESSAGE_KEY + "=?",
362                    new String[] {Long.toString(messageId)}, null, ID_PROJECTION_COLUMN,
363                            Long.valueOf(-1));
364        }
365
366        /**
367         * Updates the Body for a messageId with the given ContentValues.
368         * If the message has no body, a new body is inserted for the message.
369         * Warning: the argument "values" is modified by this method, setting MESSAGE_KEY.
370         */
371        public static void updateBodyWithMessageId(Context context, long messageId,
372                ContentValues values) {
373            ContentResolver resolver = context.getContentResolver();
374            long bodyId = lookupBodyIdWithMessageId(context, messageId);
375            values.put(BodyColumns.MESSAGE_KEY, messageId);
376            if (bodyId == -1) {
377                resolver.insert(CONTENT_URI, values);
378            } else {
379                final Uri uri = ContentUris.withAppendedId(CONTENT_URI, bodyId);
380                resolver.update(uri, values, null, null);
381            }
382        }
383
384        public static long restoreBodySourceKey(Context context, long messageId) {
385            return Utility.getFirstRowLong(context, Body.CONTENT_URI,
386                    Body.PROJECTION_SOURCE_KEY,
387                    Body.MESSAGE_KEY + "=?", new String[] {Long.toString(messageId)}, null, 0,
388                            Long.valueOf(0));
389        }
390
391        private static String restoreTextWithMessageId(Context context, long messageId,
392                String[] projection) {
393            Cursor c = context.getContentResolver().query(Body.CONTENT_URI, projection,
394                    Body.MESSAGE_KEY + "=?", new String[] {Long.toString(messageId)}, null);
395            if (c == null) throw new ProviderUnavailableException();
396            try {
397                if (c.moveToFirst()) {
398                    return c.getString(COMMON_PROJECTION_COLUMN_TEXT);
399                } else {
400                    return null;
401                }
402            } finally {
403                c.close();
404            }
405        }
406
407        public static String restoreBodyTextWithMessageId(Context context, long messageId) {
408            return restoreTextWithMessageId(context, messageId, Body.COMMON_PROJECTION_TEXT);
409        }
410
411        public static String restoreBodyHtmlWithMessageId(Context context, long messageId) {
412            return restoreTextWithMessageId(context, messageId, Body.COMMON_PROJECTION_HTML);
413        }
414
415        public static String restoreReplyTextWithMessageId(Context context, long messageId) {
416            return restoreTextWithMessageId(context, messageId, Body.COMMON_PROJECTION_REPLY_TEXT);
417        }
418
419        public static String restoreReplyHtmlWithMessageId(Context context, long messageId) {
420            return restoreTextWithMessageId(context, messageId, Body.COMMON_PROJECTION_REPLY_HTML);
421        }
422
423        public static String restoreIntroTextWithMessageId(Context context, long messageId) {
424            return restoreTextWithMessageId(context, messageId, Body.COMMON_PROJECTION_INTRO);
425        }
426
427        @Override
428        public void restore(Cursor cursor) {
429            mBaseUri = EmailContent.Body.CONTENT_URI;
430            mMessageKey = cursor.getLong(CONTENT_MESSAGE_KEY_COLUMN);
431            mHtmlContent = cursor.getString(CONTENT_HTML_CONTENT_COLUMN);
432            mTextContent = cursor.getString(CONTENT_TEXT_CONTENT_COLUMN);
433            mHtmlReply = cursor.getString(CONTENT_HTML_REPLY_COLUMN);
434            mTextReply = cursor.getString(CONTENT_TEXT_REPLY_COLUMN);
435            mSourceKey = cursor.getLong(CONTENT_SOURCE_KEY_COLUMN);
436            mIntroText = cursor.getString(CONTENT_INTRO_TEXT_COLUMN);
437        }
438
439        public boolean update() {
440            // TODO Auto-generated method stub
441            return false;
442        }
443    }
444
445    public interface MessageColumns {
446        public static final String ID = "_id";
447        // Basic columns used in message list presentation
448        // The name as shown to the user in a message list
449        public static final String DISPLAY_NAME = "displayName";
450        // The time (millis) as shown to the user in a message list [INDEX]
451        public static final String TIMESTAMP = "timeStamp";
452        // Message subject
453        public static final String SUBJECT = "subject";
454        // Boolean, unread = 0, read = 1 [INDEX]
455        public static final String FLAG_READ = "flagRead";
456        // Load state, see constants below (unloaded, partial, complete, deleted)
457        public static final String FLAG_LOADED = "flagLoaded";
458        // Boolean, unflagged = 0, flagged (favorite) = 1
459        public static final String FLAG_FAVORITE = "flagFavorite";
460        // Boolean, no attachment = 0, attachment = 1
461        public static final String FLAG_ATTACHMENT = "flagAttachment";
462        // Bit field for flags which we'll not be selecting on
463        public static final String FLAGS = "flags";
464
465        // Sync related identifiers
466        // Any client-required identifier
467        public static final String CLIENT_ID = "clientId";
468        // The message-id in the message's header
469        public static final String MESSAGE_ID = "messageId";
470
471        // References to other Email objects in the database
472        // Foreign key to the Mailbox holding this message [INDEX]
473        public static final String MAILBOX_KEY = "mailboxKey";
474        // Foreign key to the Account holding this message
475        public static final String ACCOUNT_KEY = "accountKey";
476
477        // Address lists, packed with Address.pack()
478        public static final String FROM_LIST = "fromList";
479        public static final String TO_LIST = "toList";
480        public static final String CC_LIST = "ccList";
481        public static final String BCC_LIST = "bccList";
482        public static final String REPLY_TO_LIST = "replyToList";
483        // Meeting invitation related information (for now, start time in ms)
484        public static final String MEETING_INFO = "meetingInfo";
485        // A text "snippet" derived from the body of the message
486        public static final String SNIPPET = "snippet";
487    }
488
489    public static final class Message extends EmailContent implements SyncColumns, MessageColumns {
490        public static final String TABLE_NAME = "Message";
491        public static final String UPDATED_TABLE_NAME = "Message_Updates";
492        public static final String DELETED_TABLE_NAME = "Message_Deletes";
493
494        // To refer to a specific message, use ContentUris.withAppendedId(CONTENT_URI, id)
495        @SuppressWarnings("hiding")
496        public static final Uri CONTENT_URI = Uri.parse(EmailContent.CONTENT_URI + "/message");
497        public static final Uri CONTENT_URI_LIMIT_1 = uriWithLimit(CONTENT_URI, 1);
498        public static final Uri SYNCED_CONTENT_URI =
499            Uri.parse(EmailContent.CONTENT_URI + "/syncedMessage");
500        public static final Uri DELETED_CONTENT_URI =
501            Uri.parse(EmailContent.CONTENT_URI + "/deletedMessage");
502        public static final Uri UPDATED_CONTENT_URI =
503            Uri.parse(EmailContent.CONTENT_URI + "/updatedMessage");
504        public static final Uri NOTIFIER_URI =
505            Uri.parse(EmailContent.CONTENT_NOTIFIER_URI + "/message");
506
507        public static final String KEY_TIMESTAMP_DESC = MessageColumns.TIMESTAMP + " desc";
508
509        public static final int CONTENT_ID_COLUMN = 0;
510        public static final int CONTENT_DISPLAY_NAME_COLUMN = 1;
511        public static final int CONTENT_TIMESTAMP_COLUMN = 2;
512        public static final int CONTENT_SUBJECT_COLUMN = 3;
513        public static final int CONTENT_FLAG_READ_COLUMN = 4;
514        public static final int CONTENT_FLAG_LOADED_COLUMN = 5;
515        public static final int CONTENT_FLAG_FAVORITE_COLUMN = 6;
516        public static final int CONTENT_FLAG_ATTACHMENT_COLUMN = 7;
517        public static final int CONTENT_FLAGS_COLUMN = 8;
518        public static final int CONTENT_SERVER_ID_COLUMN = 9;
519        public static final int CONTENT_CLIENT_ID_COLUMN = 10;
520        public static final int CONTENT_MESSAGE_ID_COLUMN = 11;
521        public static final int CONTENT_MAILBOX_KEY_COLUMN = 12;
522        public static final int CONTENT_ACCOUNT_KEY_COLUMN = 13;
523        public static final int CONTENT_FROM_LIST_COLUMN = 14;
524        public static final int CONTENT_TO_LIST_COLUMN = 15;
525        public static final int CONTENT_CC_LIST_COLUMN = 16;
526        public static final int CONTENT_BCC_LIST_COLUMN = 17;
527        public static final int CONTENT_REPLY_TO_COLUMN = 18;
528        public static final int CONTENT_SERVER_TIMESTAMP_COLUMN = 19;
529        public static final int CONTENT_MEETING_INFO_COLUMN = 20;
530        public static final int CONTENT_SNIPPET_COLUMN = 21;
531
532        public static final String[] CONTENT_PROJECTION = new String[] {
533            RECORD_ID,
534            MessageColumns.DISPLAY_NAME, MessageColumns.TIMESTAMP,
535            MessageColumns.SUBJECT, MessageColumns.FLAG_READ,
536            MessageColumns.FLAG_LOADED, MessageColumns.FLAG_FAVORITE,
537            MessageColumns.FLAG_ATTACHMENT, MessageColumns.FLAGS,
538            SyncColumns.SERVER_ID, MessageColumns.CLIENT_ID,
539            MessageColumns.MESSAGE_ID, MessageColumns.MAILBOX_KEY,
540            MessageColumns.ACCOUNT_KEY, MessageColumns.FROM_LIST,
541            MessageColumns.TO_LIST, MessageColumns.CC_LIST,
542            MessageColumns.BCC_LIST, MessageColumns.REPLY_TO_LIST,
543            SyncColumns.SERVER_TIMESTAMP, MessageColumns.MEETING_INFO,
544            MessageColumns.SNIPPET
545        };
546
547        public static final int LIST_ID_COLUMN = 0;
548        public static final int LIST_DISPLAY_NAME_COLUMN = 1;
549        public static final int LIST_TIMESTAMP_COLUMN = 2;
550        public static final int LIST_SUBJECT_COLUMN = 3;
551        public static final int LIST_READ_COLUMN = 4;
552        public static final int LIST_LOADED_COLUMN = 5;
553        public static final int LIST_FAVORITE_COLUMN = 6;
554        public static final int LIST_ATTACHMENT_COLUMN = 7;
555        public static final int LIST_FLAGS_COLUMN = 8;
556        public static final int LIST_MAILBOX_KEY_COLUMN = 9;
557        public static final int LIST_ACCOUNT_KEY_COLUMN = 10;
558        public static final int LIST_SERVER_ID_COLUMN = 11;
559        public static final int LIST_SNIPPET_COLUMN = 12;
560
561        // Public projection for common list columns
562        public static final String[] LIST_PROJECTION = new String[] {
563            RECORD_ID,
564            MessageColumns.DISPLAY_NAME, MessageColumns.TIMESTAMP,
565            MessageColumns.SUBJECT, MessageColumns.FLAG_READ,
566            MessageColumns.FLAG_LOADED, MessageColumns.FLAG_FAVORITE,
567            MessageColumns.FLAG_ATTACHMENT, MessageColumns.FLAGS,
568            MessageColumns.MAILBOX_KEY, MessageColumns.ACCOUNT_KEY,
569            SyncColumns.SERVER_ID, MessageColumns.SNIPPET
570        };
571
572        public static final int ID_COLUMNS_ID_COLUMN = 0;
573        public static final int ID_COLUMNS_SYNC_SERVER_ID = 1;
574        public static final String[] ID_COLUMNS_PROJECTION = new String[] {
575            RECORD_ID, SyncColumns.SERVER_ID
576        };
577
578        public static final int ID_MAILBOX_COLUMN_ID = 0;
579        public static final int ID_MAILBOX_COLUMN_MAILBOX_KEY = 1;
580        public static final String[] ID_MAILBOX_PROJECTION = new String[] {
581            RECORD_ID, MessageColumns.MAILBOX_KEY
582        };
583
584        public static final String[] ID_COLUMN_PROJECTION = new String[] { RECORD_ID };
585
586        private static final String ACCOUNT_KEY_SELECTION =
587            MessageColumns.ACCOUNT_KEY + "=?";
588
589        /**
590         * Selection for messages that are loaded
591         *
592         * POP messages at the initial stage have very little information. (Server UID only)
593         * Use this to make sure they're not visible on any UI.
594         * This means unread counts on the mailbox list can be different from the
595         * number of messages in the message list, but it should be transient...
596         */
597        public static final String FLAG_LOADED_SELECTION =
598            MessageColumns.FLAG_LOADED + " IN ("
599            +     Message.FLAG_LOADED_PARTIAL + "," + Message.FLAG_LOADED_COMPLETE
600            +     ")";
601
602        public static final String ALL_FAVORITE_SELECTION =
603            MessageColumns.FLAG_FAVORITE + "=1 AND "
604            + MessageColumns.MAILBOX_KEY + " NOT IN ("
605            +     "SELECT " + MailboxColumns.ID + " FROM " + Mailbox.TABLE_NAME + ""
606            +     " WHERE " + MailboxColumns.TYPE + " = " + Mailbox.TYPE_TRASH
607            +     ")"
608            + " AND " + FLAG_LOADED_SELECTION;
609
610        /** Selection to retrieve all messages in "inbox" for any account */
611        public static final String ALL_INBOX_SELECTION =
612            MessageColumns.MAILBOX_KEY + " IN ("
613            +     "SELECT " + MailboxColumns.ID + " FROM " + Mailbox.TABLE_NAME
614            +     " WHERE " + MailboxColumns.TYPE + " = " + Mailbox.TYPE_INBOX
615            +     ")"
616            + " AND " + FLAG_LOADED_SELECTION;
617
618        /** Selection to retrieve all messages in "drafts" for any account */
619        public static final String ALL_DRAFT_SELECTION =
620            MessageColumns.MAILBOX_KEY + " IN ("
621            +     "SELECT " + MailboxColumns.ID + " FROM " + Mailbox.TABLE_NAME
622            +     " WHERE " + MailboxColumns.TYPE + " = " + Mailbox.TYPE_DRAFTS
623            +     ")"
624            + " AND " + FLAG_LOADED_SELECTION;
625
626        /** Selection to retrieve all messages in "outbox" for any account */
627        public static final String ALL_OUTBOX_SELECTION =
628            MessageColumns.MAILBOX_KEY + " IN ("
629            +     "SELECT " + MailboxColumns.ID + " FROM " + Mailbox.TABLE_NAME
630            +     " WHERE " + MailboxColumns.TYPE + " = " + Mailbox.TYPE_OUTBOX
631            +     ")"; // NOTE No flag_loaded test for outboxes.
632
633        /** Selection to retrieve unread messages in "inbox" for any account */
634        public static final String ALL_UNREAD_SELECTION =
635            MessageColumns.FLAG_READ + "=0 AND " + ALL_INBOX_SELECTION;
636
637        /** Selection to retrieve all messages in "inbox" for one account */
638        public static final String PER_ACCOUNT_INBOX_SELECTION =
639            ACCOUNT_KEY_SELECTION + " AND " + ALL_INBOX_SELECTION;
640
641        private static final String ACCOUNT_FAVORITE_SELECTION =
642            ACCOUNT_KEY_SELECTION + " AND " + ALL_FAVORITE_SELECTION;
643
644        // _id field is in AbstractContent
645        public String mDisplayName;
646        public long mTimeStamp;
647        public String mSubject;
648        public boolean mFlagRead = false;
649        public int mFlagLoaded = FLAG_LOADED_UNLOADED;
650        public boolean mFlagFavorite = false;
651        public boolean mFlagAttachment = false;
652        public int mFlags = 0;
653
654        public String mServerId;
655        public long mServerTimeStamp;
656        public String mClientId;
657        public String mMessageId;
658
659        public long mMailboxKey;
660        public long mAccountKey;
661
662        public String mFrom;
663        public String mTo;
664        public String mCc;
665        public String mBcc;
666        public String mReplyTo;
667
668        // For now, just the start time of a meeting invite, in ms
669        public String mMeetingInfo;
670
671        public String mSnippet;
672
673        // The following transient members may be used while building and manipulating messages,
674        // but they are NOT persisted directly by EmailProvider
675        transient public String mText;
676        transient public String mHtml;
677        transient public String mTextReply;
678        transient public String mHtmlReply;
679        transient public long mSourceKey;
680        transient public ArrayList<Attachment> mAttachments = null;
681        transient public String mIntroText;
682
683        // Values used in mFlagRead
684        public static final int UNREAD = 0;
685        public static final int READ = 1;
686
687        // Values used in mFlagLoaded
688        public static final int FLAG_LOADED_UNLOADED = 0;
689        public static final int FLAG_LOADED_COMPLETE = 1;
690        public static final int FLAG_LOADED_PARTIAL = 2;
691        public static final int FLAG_LOADED_DELETED = 3;
692
693        // Bits used in mFlags
694        // The following three states are mutually exclusive, and indicate whether the message is an
695        // original, a reply, or a forward
696        public static final int FLAG_TYPE_ORIGINAL = 0;
697        public static final int FLAG_TYPE_REPLY = 1<<0;
698        public static final int FLAG_TYPE_FORWARD = 1<<1;
699        public static final int FLAG_TYPE_MASK = FLAG_TYPE_REPLY | FLAG_TYPE_FORWARD;
700        // The following flags indicate messages that are determined to be incoming meeting related
701        // (e.g. invites from others)
702        public static final int FLAG_INCOMING_MEETING_INVITE = 1<<2;
703        public static final int FLAG_INCOMING_MEETING_CANCEL = 1<<3;
704        public static final int FLAG_INCOMING_MEETING_MASK =
705            FLAG_INCOMING_MEETING_INVITE | FLAG_INCOMING_MEETING_CANCEL;
706        // The following flags indicate messages that are outgoing and meeting related
707        // (e.g. invites TO others)
708        public static final int FLAG_OUTGOING_MEETING_INVITE = 1<<4;
709        public static final int FLAG_OUTGOING_MEETING_CANCEL = 1<<5;
710        public static final int FLAG_OUTGOING_MEETING_ACCEPT = 1<<6;
711        public static final int FLAG_OUTGOING_MEETING_DECLINE = 1<<7;
712        public static final int FLAG_OUTGOING_MEETING_TENTATIVE = 1<<8;
713        public static final int FLAG_OUTGOING_MEETING_MASK =
714            FLAG_OUTGOING_MEETING_INVITE | FLAG_OUTGOING_MEETING_CANCEL |
715            FLAG_OUTGOING_MEETING_ACCEPT | FLAG_OUTGOING_MEETING_DECLINE |
716            FLAG_OUTGOING_MEETING_TENTATIVE;
717        public static final int FLAG_OUTGOING_MEETING_REQUEST_MASK =
718            FLAG_OUTGOING_MEETING_INVITE | FLAG_OUTGOING_MEETING_CANCEL;
719        // 8 general purpose flags (bits) that may be used at the discretion of the sync adapter
720        public static final int FLAG_SYNC_ADAPTER_SHIFT = 9;
721        public static final int FLAG_SYNC_ADAPTER_MASK = 255 << FLAG_SYNC_ADAPTER_SHIFT;
722        /** If set, the outgoing message should *not* include the quoted original message. */
723        public static final int FLAG_NOT_INCLUDE_QUOTED_TEXT = 1 << 17;
724        public static final int FLAG_REPLIED_TO = 1 << 18;
725        public static final int FLAG_FORWARDED = 1 << 19;
726
727        public Message() {
728            mBaseUri = CONTENT_URI;
729        }
730
731        @Override
732        public ContentValues toContentValues() {
733            ContentValues values = new ContentValues();
734
735            // Assign values for each row.
736            values.put(MessageColumns.DISPLAY_NAME, mDisplayName);
737            values.put(MessageColumns.TIMESTAMP, mTimeStamp);
738            values.put(MessageColumns.SUBJECT, mSubject);
739            values.put(MessageColumns.FLAG_READ, mFlagRead);
740            values.put(MessageColumns.FLAG_LOADED, mFlagLoaded);
741            values.put(MessageColumns.FLAG_FAVORITE, mFlagFavorite);
742            values.put(MessageColumns.FLAG_ATTACHMENT, mFlagAttachment);
743            values.put(MessageColumns.FLAGS, mFlags);
744
745            values.put(SyncColumns.SERVER_ID, mServerId);
746            values.put(SyncColumns.SERVER_TIMESTAMP, mServerTimeStamp);
747            values.put(MessageColumns.CLIENT_ID, mClientId);
748            values.put(MessageColumns.MESSAGE_ID, mMessageId);
749
750            values.put(MessageColumns.MAILBOX_KEY, mMailboxKey);
751            values.put(MessageColumns.ACCOUNT_KEY, mAccountKey);
752
753            values.put(MessageColumns.FROM_LIST, mFrom);
754            values.put(MessageColumns.TO_LIST, mTo);
755            values.put(MessageColumns.CC_LIST, mCc);
756            values.put(MessageColumns.BCC_LIST, mBcc);
757            values.put(MessageColumns.REPLY_TO_LIST, mReplyTo);
758
759            values.put(MessageColumns.MEETING_INFO, mMeetingInfo);
760
761            values.put(MessageColumns.SNIPPET, mSnippet);
762
763            return values;
764        }
765
766        public static Message restoreMessageWithId(Context context, long id) {
767            return EmailContent.restoreContentWithId(context, Message.class,
768                    Message.CONTENT_URI, Message.CONTENT_PROJECTION, id);
769        }
770
771        @Override
772        public void restore(Cursor cursor) {
773            mBaseUri = CONTENT_URI;
774            mId = cursor.getLong(CONTENT_ID_COLUMN);
775            mDisplayName = cursor.getString(CONTENT_DISPLAY_NAME_COLUMN);
776            mTimeStamp = cursor.getLong(CONTENT_TIMESTAMP_COLUMN);
777            mSubject = cursor.getString(CONTENT_SUBJECT_COLUMN);
778            mFlagRead = cursor.getInt(CONTENT_FLAG_READ_COLUMN) == 1;
779            mFlagLoaded = cursor.getInt(CONTENT_FLAG_LOADED_COLUMN);
780            mFlagFavorite = cursor.getInt(CONTENT_FLAG_FAVORITE_COLUMN) == 1;
781            mFlagAttachment = cursor.getInt(CONTENT_FLAG_ATTACHMENT_COLUMN) == 1;
782            mFlags = cursor.getInt(CONTENT_FLAGS_COLUMN);
783            mServerId = cursor.getString(CONTENT_SERVER_ID_COLUMN);
784            mServerTimeStamp = cursor.getLong(CONTENT_SERVER_TIMESTAMP_COLUMN);
785            mClientId = cursor.getString(CONTENT_CLIENT_ID_COLUMN);
786            mMessageId = cursor.getString(CONTENT_MESSAGE_ID_COLUMN);
787            mMailboxKey = cursor.getLong(CONTENT_MAILBOX_KEY_COLUMN);
788            mAccountKey = cursor.getLong(CONTENT_ACCOUNT_KEY_COLUMN);
789            mFrom = cursor.getString(CONTENT_FROM_LIST_COLUMN);
790            mTo = cursor.getString(CONTENT_TO_LIST_COLUMN);
791            mCc = cursor.getString(CONTENT_CC_LIST_COLUMN);
792            mBcc = cursor.getString(CONTENT_BCC_LIST_COLUMN);
793            mReplyTo = cursor.getString(CONTENT_REPLY_TO_COLUMN);
794            mMeetingInfo = cursor.getString(CONTENT_MEETING_INFO_COLUMN);
795            mSnippet = cursor.getString(CONTENT_SNIPPET_COLUMN);
796        }
797
798        public boolean update() {
799            // TODO Auto-generated method stub
800            return false;
801        }
802
803        /*
804         * Override this so that we can store the Body first and link it to the Message
805         * Also, attachments when we get there...
806         * (non-Javadoc)
807         * @see com.android.email.provider.EmailContent#save(android.content.Context)
808         */
809        @Override
810        public Uri save(Context context) {
811
812            boolean doSave = !isSaved();
813
814            // This logic is in place so I can (a) short circuit the expensive stuff when
815            // possible, and (b) override (and throw) if anyone tries to call save() or update()
816            // directly for Message, which are unsupported.
817            if (mText == null && mHtml == null && mTextReply == null && mHtmlReply == null &&
818                    (mAttachments == null || mAttachments.isEmpty())) {
819                if (doSave) {
820                    return super.save(context);
821                } else {
822                    // Call update, rather than super.update in case we ever override it
823                    if (update(context, toContentValues()) == 1) {
824                        return getUri();
825                    }
826                    return null;
827                }
828            }
829
830            ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
831            addSaveOps(ops);
832            try {
833                ContentProviderResult[] results =
834                    context.getContentResolver().applyBatch(AUTHORITY, ops);
835                // If saving, set the mId's of the various saved objects
836                if (doSave) {
837                    Uri u = results[0].uri;
838                    mId = Long.parseLong(u.getPathSegments().get(1));
839                    if (mAttachments != null) {
840                        int resultOffset = 2;
841                        for (Attachment a : mAttachments) {
842                            // Save the id of the attachment record
843                            u = results[resultOffset++].uri;
844                            if (u != null) {
845                                a.mId = Long.parseLong(u.getPathSegments().get(1));
846                            }
847                            a.mMessageKey = mId;
848                        }
849                    }
850                    return u;
851                } else {
852                    return null;
853                }
854            } catch (RemoteException e) {
855                // There is nothing to be done here; fail by returning null
856            } catch (OperationApplicationException e) {
857                // There is nothing to be done here; fail by returning null
858            }
859            return null;
860        }
861
862        public void addSaveOps(ArrayList<ContentProviderOperation> ops) {
863            // First, save the message
864            ContentProviderOperation.Builder b = ContentProviderOperation.newInsert(mBaseUri);
865            // Generate the snippet here, before we create the CPO for Message
866            if (mText != null) {
867                mSnippet = TextUtilities.makeSnippetFromPlainText(mText);
868            } else if (mHtml != null) {
869                mSnippet = TextUtilities.makeSnippetFromHtmlText(mHtml);
870            }
871            ops.add(b.withValues(toContentValues()).build());
872
873            // Create and save the body
874            ContentValues cv = new ContentValues();
875            if (mText != null) {
876                cv.put(Body.TEXT_CONTENT, mText);
877            }
878            if (mHtml != null) {
879                cv.put(Body.HTML_CONTENT, mHtml);
880            }
881            if (mTextReply != null) {
882                cv.put(Body.TEXT_REPLY, mTextReply);
883            }
884            if (mHtmlReply != null) {
885                cv.put(Body.HTML_REPLY, mHtmlReply);
886            }
887            if (mSourceKey != 0) {
888                cv.put(Body.SOURCE_MESSAGE_KEY, mSourceKey);
889            }
890            if (mIntroText != null) {
891                cv.put(Body.INTRO_TEXT, mIntroText);
892            }
893            b = ContentProviderOperation.newInsert(Body.CONTENT_URI);
894            b.withValues(cv);
895            ContentValues backValues = new ContentValues();
896            int messageBackValue = ops.size() - 1;
897            backValues.put(Body.MESSAGE_KEY, messageBackValue);
898            ops.add(b.withValueBackReferences(backValues).build());
899
900            // Create the attaachments, if any
901            if (mAttachments != null) {
902                for (Attachment att: mAttachments) {
903                    ops.add(ContentProviderOperation.newInsert(Attachment.CONTENT_URI)
904                        .withValues(att.toContentValues())
905                        .withValueBackReference(Attachment.MESSAGE_KEY, messageBackValue)
906                        .build());
907                }
908            }
909        }
910
911        /**
912         * @return number of favorite (starred) messages throughout all accounts.
913         */
914        public static int getFavoriteMessageCount(Context context) {
915            return count(context, Message.CONTENT_URI, ALL_FAVORITE_SELECTION, null);
916        }
917
918        /**
919         * @return number of favorite (starred) messages for an account
920         */
921        public static int getFavoriteMessageCount(Context context, long accountId) {
922            return count(context, Message.CONTENT_URI, ACCOUNT_FAVORITE_SELECTION,
923                    new String[]{Long.toString(accountId)});
924        }
925
926        public static long getKeyColumnLong(Context context, long messageId, String column) {
927            String[] columns =
928                Utility.getRowColumns(context, Message.CONTENT_URI, messageId, column);
929            if (columns != null && columns[0] != null) {
930                return Long.parseLong(columns[0]);
931            }
932            return -1;
933        }
934
935        /**
936         * Returns the where clause for a message list selection.
937         *
938         * Accesses the detabase to determine the mailbox type.  DO NOT CALL FROM UI THREAD.
939         */
940        public static String buildMessageListSelection(Context context, long mailboxId) {
941
942            if (mailboxId == Mailbox.QUERY_ALL_INBOXES) {
943                return Message.ALL_INBOX_SELECTION;
944            }
945            if (mailboxId == Mailbox.QUERY_ALL_DRAFTS) {
946                return Message.ALL_DRAFT_SELECTION;
947            }
948            if (mailboxId == Mailbox.QUERY_ALL_OUTBOX) {
949                return Message.ALL_OUTBOX_SELECTION;
950            }
951            if (mailboxId == Mailbox.QUERY_ALL_UNREAD) {
952                return Message.ALL_UNREAD_SELECTION;
953            }
954            if (mailboxId == Mailbox.QUERY_ALL_FAVORITES) {
955                return Message.ALL_FAVORITE_SELECTION;
956            }
957
958            // Now it's a regular mailbox.
959            final StringBuilder selection = new StringBuilder();
960
961            selection.append(MessageColumns.MAILBOX_KEY).append('=').append(mailboxId);
962
963            if (Mailbox.getMailboxType(context, mailboxId) != Mailbox.TYPE_OUTBOX) {
964                selection.append(" AND ").append(Message.FLAG_LOADED_SELECTION);
965            }
966            return selection.toString();
967        }
968    }
969
970    public interface AccountColumns {
971        public static final String ID = "_id";
972        // The display name of the account (user-settable)
973        public static final String DISPLAY_NAME = "displayName";
974        // The email address corresponding to this account
975        public static final String EMAIL_ADDRESS = "emailAddress";
976        // A server-based sync key on an account-wide basis (EAS needs this)
977        public static final String SYNC_KEY = "syncKey";
978        // The default sync lookback period for this account
979        public static final String SYNC_LOOKBACK = "syncLookback";
980        // The default sync frequency for this account, in minutes
981        public static final String SYNC_INTERVAL = "syncInterval";
982        // A foreign key into the account manager, having host, login, password, port, and ssl flags
983        public static final String HOST_AUTH_KEY_RECV = "hostAuthKeyRecv";
984        // (optional) A foreign key into the account manager, having host, login, password, port,
985        // and ssl flags
986        public static final String HOST_AUTH_KEY_SEND = "hostAuthKeySend";
987        // Flags
988        public static final String FLAGS = "flags";
989        // Default account
990        public static final String IS_DEFAULT = "isDefault";
991        // Old-Style UUID for compatibility with previous versions
992        public static final String COMPATIBILITY_UUID = "compatibilityUuid";
993        // User name (for outgoing messages)
994        public static final String SENDER_NAME = "senderName";
995        // Ringtone
996        public static final String RINGTONE_URI = "ringtoneUri";
997        // Protocol version (arbitrary string, used by EAS currently)
998        public static final String PROTOCOL_VERSION = "protocolVersion";
999        // The number of new messages (reported by the sync/download engines
1000        public static final String NEW_MESSAGE_COUNT = "newMessageCount";
1001        // Legacy flags defining security (provisioning) requirements of this account; this
1002        // information is now found in the Policy table; POLICY_KEY (below) is the foreign key
1003        @Deprecated
1004        public static final String SECURITY_FLAGS = "securityFlags";
1005        // Server-based sync key for the security policies currently enforced
1006        public static final String SECURITY_SYNC_KEY = "securitySyncKey";
1007        // Signature to use with this account
1008        public static final String SIGNATURE = "signature";
1009        // A foreign key into the Policy table
1010        public static final String POLICY_KEY = "policyKey";
1011    }
1012
1013    public static final class Account extends EmailContent implements AccountColumns, Parcelable {
1014        public static final String TABLE_NAME = "Account";
1015        @SuppressWarnings("hiding")
1016        public static final Uri CONTENT_URI = Uri.parse(EmailContent.CONTENT_URI + "/account");
1017        public static final Uri ADD_TO_FIELD_URI =
1018            Uri.parse(EmailContent.CONTENT_URI + "/accountIdAddToField");
1019        public static final Uri RESET_NEW_MESSAGE_COUNT_URI =
1020            Uri.parse(EmailContent.CONTENT_URI + "/resetNewMessageCount");
1021        public static final Uri NOTIFIER_URI =
1022            Uri.parse(EmailContent.CONTENT_NOTIFIER_URI + "/account");
1023
1024        // Define all pseudo account IDs here to avoid conflict with one another.
1025        /**
1026         * Pseudo account ID to represent a "combined account" that includes messages and mailboxes
1027         * from all defined accounts.
1028         *
1029         * <em>IMPORTANT</em>: This must never be stored to the database.
1030         */
1031        public static final long ACCOUNT_ID_COMBINED_VIEW = 0x1000000000000000L;
1032        /**
1033         * Pseudo account ID to represent "no account". This may be used any time the account ID
1034         * may not be known or when we want to specifically select "no" account.
1035         *
1036         * <em>IMPORTANT</em>: This must never be stored to the database.
1037         */
1038        public static final long PSEUDO_ACCOUNT_ID_NONE = -1L;
1039
1040        // Whether or not the user has asked for notifications of new mail in this account
1041        public final static int FLAGS_NOTIFY_NEW_MAIL = 1<<0;
1042        // Whether or not the user has asked for vibration notifications with all new mail
1043        public final static int FLAGS_VIBRATE_ALWAYS = 1<<1;
1044        // Bit mask for the account's deletion policy (see DELETE_POLICY_x below)
1045        public static final int FLAGS_DELETE_POLICY_MASK = 1<<2 | 1<<3;
1046        public static final int FLAGS_DELETE_POLICY_SHIFT = 2;
1047        // Whether the account is in the process of being created; any account reconciliation code
1048        // MUST ignore accounts with this bit set; in addition, ContentObservers for this data
1049        // SHOULD consider the state of this flag during operation
1050        public static final int FLAGS_INCOMPLETE = 1<<4;
1051        // Security hold is used when the device is not in compliance with security policies
1052        // required by the server; in this state, the user MUST be alerted to the need to update
1053        // security settings.  Sync adapters SHOULD NOT attempt to sync when this flag is set.
1054        public static final int FLAGS_SECURITY_HOLD = 1<<5;
1055        // Whether or not the user has asked for vibration notifications when the ringer is silent
1056        public static final int FLAGS_VIBRATE_WHEN_SILENT = 1<<6;
1057        // Whether the account supports "smart forward" (i.e. the server appends the original
1058        // message along with any attachments to the outgoing message)
1059        public static final int FLAGS_SUPPORTS_SMART_FORWARD = 1<<7;
1060        // Whether the account should try to cache attachments in the background
1061        public static final int FLAGS_BACKGROUND_ATTACHMENTS = 1<<8;
1062        // Available to sync adapter
1063        public static final int FLAGS_SYNC_ADAPTER = 1<<9;
1064        // Sync disabled is a status commanded by the server; the sync adapter SHOULD NOT try to
1065        // sync mailboxes in this account automatically.  A manual sync request to sync a mailbox
1066        // with sync disabled SHOULD try to sync and report any failure result via the UI.
1067        public static final int FLAGS_SYNC_DISABLED = 1<<10;
1068
1069        // Deletion policy (see FLAGS_DELETE_POLICY_MASK, above)
1070        public static final int DELETE_POLICY_NEVER = 0;
1071        public static final int DELETE_POLICY_7DAYS = 1<<0;        // not supported
1072        public static final int DELETE_POLICY_ON_DELETE = 1<<1;
1073
1074        // Sentinel values for the mSyncInterval field of both Account records
1075        public static final int CHECK_INTERVAL_NEVER = -1;
1076        public static final int CHECK_INTERVAL_PUSH = -2;
1077
1078        public String mDisplayName;
1079        public String mEmailAddress;
1080        public String mSyncKey;
1081        public int mSyncLookback;
1082        public int mSyncInterval;
1083        public long mHostAuthKeyRecv;
1084        public long mHostAuthKeySend;
1085        public int mFlags;
1086        public boolean mIsDefault;          // note: callers should use getDefaultAccountId()
1087        public String mCompatibilityUuid;
1088        public String mSenderName;
1089        public String mRingtoneUri;
1090        public String mProtocolVersion;
1091        public int mNewMessageCount;
1092        public String mSecuritySyncKey;
1093        public String mSignature;
1094        public long mPolicyKey;
1095
1096        // Convenience for creating an account
1097        public transient HostAuth mHostAuthRecv;
1098        public transient HostAuth mHostAuthSend;
1099        public transient Policy mPolicy;
1100
1101        public static final int CONTENT_ID_COLUMN = 0;
1102        public static final int CONTENT_DISPLAY_NAME_COLUMN = 1;
1103        public static final int CONTENT_EMAIL_ADDRESS_COLUMN = 2;
1104        public static final int CONTENT_SYNC_KEY_COLUMN = 3;
1105        public static final int CONTENT_SYNC_LOOKBACK_COLUMN = 4;
1106        public static final int CONTENT_SYNC_INTERVAL_COLUMN = 5;
1107        public static final int CONTENT_HOST_AUTH_KEY_RECV_COLUMN = 6;
1108        public static final int CONTENT_HOST_AUTH_KEY_SEND_COLUMN = 7;
1109        public static final int CONTENT_FLAGS_COLUMN = 8;
1110        public static final int CONTENT_IS_DEFAULT_COLUMN = 9;
1111        public static final int CONTENT_COMPATIBILITY_UUID_COLUMN = 10;
1112        public static final int CONTENT_SENDER_NAME_COLUMN = 11;
1113        public static final int CONTENT_RINGTONE_URI_COLUMN = 12;
1114        public static final int CONTENT_PROTOCOL_VERSION_COLUMN = 13;
1115        public static final int CONTENT_NEW_MESSAGE_COUNT_COLUMN = 14;
1116        public static final int CONTENT_SECURITY_SYNC_KEY_COLUMN = 15;
1117        public static final int CONTENT_SIGNATURE_COLUMN = 16;
1118        public static final int CONTENT_POLICY_KEY = 17;
1119
1120        public static final String[] CONTENT_PROJECTION = new String[] {
1121            RECORD_ID, AccountColumns.DISPLAY_NAME,
1122            AccountColumns.EMAIL_ADDRESS, AccountColumns.SYNC_KEY, AccountColumns.SYNC_LOOKBACK,
1123            AccountColumns.SYNC_INTERVAL, AccountColumns.HOST_AUTH_KEY_RECV,
1124            AccountColumns.HOST_AUTH_KEY_SEND, AccountColumns.FLAGS, AccountColumns.IS_DEFAULT,
1125            AccountColumns.COMPATIBILITY_UUID, AccountColumns.SENDER_NAME,
1126            AccountColumns.RINGTONE_URI, AccountColumns.PROTOCOL_VERSION,
1127            AccountColumns.NEW_MESSAGE_COUNT, AccountColumns.SECURITY_SYNC_KEY,
1128            AccountColumns.SIGNATURE, AccountColumns.POLICY_KEY
1129        };
1130
1131        public static final int CONTENT_MAILBOX_TYPE_COLUMN = 1;
1132
1133        /**
1134         * This projection is for listing account id's only
1135         */
1136        public static final String[] ID_TYPE_PROJECTION = new String[] {
1137            RECORD_ID, MailboxColumns.TYPE
1138        };
1139
1140        public static final int ACCOUNT_FLAGS_COLUMN_ID = 0;
1141        public static final int ACCOUNT_FLAGS_COLUMN_FLAGS = 1;
1142        public static final String[] ACCOUNT_FLAGS_PROJECTION = new String[] {
1143                AccountColumns.ID, AccountColumns.FLAGS};
1144
1145        public static final String MAILBOX_SELECTION =
1146            MessageColumns.MAILBOX_KEY + " =?";
1147
1148        public static final String UNREAD_COUNT_SELECTION =
1149            MessageColumns.MAILBOX_KEY + " =? and " + MessageColumns.FLAG_READ + "= 0";
1150
1151        private static final String UUID_SELECTION = AccountColumns.COMPATIBILITY_UUID + " =?";
1152
1153        public static final String SECURITY_NONZERO_SELECTION =
1154            Account.POLICY_KEY + " IS NOT NULL AND " + Account.POLICY_KEY + "!=0";
1155
1156        private static final String FIND_INBOX_SELECTION =
1157                MailboxColumns.TYPE + " = " + Mailbox.TYPE_INBOX +
1158                " AND " + MailboxColumns.ACCOUNT_KEY + " =?";
1159
1160        /**
1161         * This projection is for searching for the default account
1162         */
1163        private static final String[] DEFAULT_ID_PROJECTION = new String[] {
1164            RECORD_ID, IS_DEFAULT
1165        };
1166
1167        /**
1168         * no public constructor since this is a utility class
1169         */
1170        public Account() {
1171            mBaseUri = CONTENT_URI;
1172
1173            // other defaults (policy)
1174            mRingtoneUri = "content://settings/system/notification_sound";
1175            mSyncInterval = -1;
1176            mSyncLookback = -1;
1177            mFlags = FLAGS_NOTIFY_NEW_MAIL;
1178            mCompatibilityUuid = UUID.randomUUID().toString();
1179        }
1180
1181        public static Account restoreAccountWithId(Context context, long id) {
1182            return EmailContent.restoreContentWithId(context, Account.class,
1183                    Account.CONTENT_URI, Account.CONTENT_PROJECTION, id);
1184        }
1185
1186        /**
1187         * Returns {@code true} if the given account ID is a "normal" account. Normal accounts
1188         * always have an ID greater than {@code 0} and not equal to any pseudo account IDs
1189         * (such as {@link #ACCOUNT_ID_COMBINED_VIEW})
1190         */
1191        public static boolean isNormalAccount(long accountId) {
1192            return (accountId > 0L) && (accountId != ACCOUNT_ID_COMBINED_VIEW);
1193        }
1194
1195        /**
1196         * Refresh an account that has already been loaded.  This is slightly less expensive
1197         * that generating a brand-new account object.
1198         */
1199        public void refresh(Context context) {
1200            Cursor c = context.getContentResolver().query(getUri(), Account.CONTENT_PROJECTION,
1201                    null, null, null);
1202            try {
1203                c.moveToFirst();
1204                restore(c);
1205            } finally {
1206                if (c != null) {
1207                    c.close();
1208                }
1209            }
1210        }
1211
1212        @Override
1213        public void restore(Cursor cursor) {
1214            mId = cursor.getLong(CONTENT_ID_COLUMN);
1215            mBaseUri = CONTENT_URI;
1216            mDisplayName = cursor.getString(CONTENT_DISPLAY_NAME_COLUMN);
1217            mEmailAddress = cursor.getString(CONTENT_EMAIL_ADDRESS_COLUMN);
1218            mSyncKey = cursor.getString(CONTENT_SYNC_KEY_COLUMN);
1219            mSyncLookback = cursor.getInt(CONTENT_SYNC_LOOKBACK_COLUMN);
1220            mSyncInterval = cursor.getInt(CONTENT_SYNC_INTERVAL_COLUMN);
1221            mHostAuthKeyRecv = cursor.getLong(CONTENT_HOST_AUTH_KEY_RECV_COLUMN);
1222            mHostAuthKeySend = cursor.getLong(CONTENT_HOST_AUTH_KEY_SEND_COLUMN);
1223            mFlags = cursor.getInt(CONTENT_FLAGS_COLUMN);
1224            mIsDefault = cursor.getInt(CONTENT_IS_DEFAULT_COLUMN) == 1;
1225            mCompatibilityUuid = cursor.getString(CONTENT_COMPATIBILITY_UUID_COLUMN);
1226            mSenderName = cursor.getString(CONTENT_SENDER_NAME_COLUMN);
1227            mRingtoneUri = cursor.getString(CONTENT_RINGTONE_URI_COLUMN);
1228            mProtocolVersion = cursor.getString(CONTENT_PROTOCOL_VERSION_COLUMN);
1229            mNewMessageCount = cursor.getInt(CONTENT_NEW_MESSAGE_COUNT_COLUMN);
1230            mSecuritySyncKey = cursor.getString(CONTENT_SECURITY_SYNC_KEY_COLUMN);
1231            mSignature = cursor.getString(CONTENT_SIGNATURE_COLUMN);
1232            mPolicyKey = cursor.getLong(CONTENT_POLICY_KEY);
1233        }
1234
1235        private long getId(Uri u) {
1236            return Long.parseLong(u.getPathSegments().get(1));
1237        }
1238
1239        /**
1240         * @return the user-visible name for the account
1241         */
1242        public String getDisplayName() {
1243            return mDisplayName;
1244        }
1245
1246        /**
1247         * Set the description.  Be sure to call save() to commit to database.
1248         * @param description the new description
1249         */
1250        public void setDisplayName(String description) {
1251            mDisplayName = description;
1252        }
1253
1254        /**
1255         * @return the email address for this account
1256         */
1257        public String getEmailAddress() {
1258            return mEmailAddress;
1259        }
1260
1261        /**
1262         * Set the Email address for this account.  Be sure to call save() to commit to database.
1263         * @param emailAddress the new email address for this account
1264         */
1265        public void setEmailAddress(String emailAddress) {
1266            mEmailAddress = emailAddress;
1267        }
1268
1269        /**
1270         * @return the sender's name for this account
1271         */
1272        public String getSenderName() {
1273            return mSenderName;
1274        }
1275
1276        /**
1277         * Set the sender's name.  Be sure to call save() to commit to database.
1278         * @param name the new sender name
1279         */
1280        public void setSenderName(String name) {
1281            mSenderName = name;
1282        }
1283
1284        public String getSignature() {
1285            return mSignature;
1286        }
1287
1288        public void setSignature(String signature) {
1289            mSignature = signature;
1290        }
1291
1292        /**
1293         * @return the minutes per check (for polling)
1294         * TODO define sentinel values for "never", "push", etc.  See Account.java
1295         */
1296        public int getSyncInterval() {
1297            return mSyncInterval;
1298        }
1299
1300        /**
1301         * Set the minutes per check (for polling).  Be sure to call save() to commit to database.
1302         * TODO define sentinel values for "never", "push", etc.  See Account.java
1303         * @param minutes the number of minutes between polling checks
1304         */
1305        public void setSyncInterval(int minutes) {
1306            mSyncInterval = minutes;
1307        }
1308
1309        /**
1310         * @return One of the {@code Account.SYNC_WINDOW_*} constants that represents the sync
1311         *     lookback window.
1312         * TODO define sentinel values for "all", "1 month", etc.  See Account.java
1313         */
1314        public int getSyncLookback() {
1315            return mSyncLookback;
1316        }
1317
1318        /**
1319         * Set the sync lookback window.  Be sure to call save() to commit to database.
1320         * TODO define sentinel values for "all", "1 month", etc.  See Account.java
1321         * @param value One of the {@code Account.SYNC_WINDOW_*} constants
1322         */
1323        public void setSyncLookback(int value) {
1324            mSyncLookback = value;
1325        }
1326
1327        /**
1328         * @return the flags for this account
1329         * @see #FLAGS_NOTIFY_NEW_MAIL
1330         * @see #FLAGS_VIBRATE_ALWAYS
1331         * @see #FLAGS_VIBRATE_WHEN_SILENT
1332         */
1333        public int getFlags() {
1334            return mFlags;
1335        }
1336
1337        /**
1338         * Set the flags for this account
1339         * @see #FLAGS_NOTIFY_NEW_MAIL
1340         * @see #FLAGS_VIBRATE_ALWAYS
1341         * @see #FLAGS_VIBRATE_WHEN_SILENT
1342         * @param newFlags the new value for the flags
1343         */
1344        public void setFlags(int newFlags) {
1345            mFlags = newFlags;
1346        }
1347
1348        /**
1349         * @return the ringtone Uri for this account
1350         */
1351        public String getRingtone() {
1352            return mRingtoneUri;
1353        }
1354
1355        /**
1356         * Set the ringtone Uri for this account
1357         * @param newUri the new URI string for the ringtone for this account
1358         */
1359        public void setRingtone(String newUri) {
1360            mRingtoneUri = newUri;
1361        }
1362
1363        /**
1364         * Set the "delete policy" as a simple 0,1,2 value set.
1365         * @param newPolicy the new delete policy
1366         */
1367        public void setDeletePolicy(int newPolicy) {
1368            mFlags &= ~FLAGS_DELETE_POLICY_MASK;
1369            mFlags |= (newPolicy << FLAGS_DELETE_POLICY_SHIFT) & FLAGS_DELETE_POLICY_MASK;
1370        }
1371
1372        /**
1373         * Return the "delete policy" as a simple 0,1,2 value set.
1374         * @return the current delete policy
1375         */
1376        public int getDeletePolicy() {
1377            return (mFlags & FLAGS_DELETE_POLICY_MASK) >> FLAGS_DELETE_POLICY_SHIFT;
1378        }
1379
1380        /**
1381         * Return the Uuid associated with this account.  This is primarily for compatibility
1382         * with accounts set up by previous versions, because there are externals references
1383         * to the Uuid (e.g. desktop shortcuts).
1384         */
1385        public String getUuid() {
1386            return mCompatibilityUuid;
1387        }
1388
1389        /**
1390         * For compatibility while converting to provider model, generate a "store URI"
1391         *
1392         * @return a string in the form of a Uri, as used by the other parts of the email app
1393         */
1394        public String getStoreUri(Context context) {
1395            // reconstitute if necessary
1396            if (mHostAuthRecv == null) {
1397                mHostAuthRecv = HostAuth.restoreHostAuthWithId(context, mHostAuthKeyRecv);
1398            }
1399            // convert if available
1400            if (mHostAuthRecv != null) {
1401                String storeUri = mHostAuthRecv.getStoreUri();
1402                if (storeUri != null) {
1403                    return storeUri;
1404                }
1405            }
1406            return "";
1407        }
1408
1409        /**
1410         * For compatibility while converting to provider model, generate a "sender URI"
1411         *
1412         * @return a string in the form of a Uri, as used by the other parts of the email app
1413         */
1414        public String getSenderUri(Context context) {
1415            // reconstitute if necessary
1416            if (mHostAuthSend == null) {
1417                mHostAuthSend = HostAuth.restoreHostAuthWithId(context, mHostAuthKeySend);
1418            }
1419            // convert if available
1420            if (mHostAuthSend != null) {
1421                String senderUri = mHostAuthSend.getStoreUri();
1422                if (senderUri != null) {
1423                    return senderUri;
1424                }
1425            }
1426            return "";
1427        }
1428
1429        public HostAuth getOrCreateHostAuthSend(Context context) {
1430            if (mHostAuthSend == null) {
1431                if (mHostAuthKeySend != 0) {
1432                    mHostAuthSend = HostAuth.restoreHostAuthWithId(context, mHostAuthKeySend);
1433                } else {
1434                    mHostAuthSend = new EmailContent.HostAuth();
1435                }
1436            }
1437            return mHostAuthSend;
1438        }
1439
1440        public HostAuth getOrCreateHostAuthRecv(Context context) {
1441            if (mHostAuthRecv == null) {
1442                if (mHostAuthKeyRecv != 0) {
1443                    mHostAuthRecv = HostAuth.restoreHostAuthWithId(context, mHostAuthKeyRecv);
1444                } else {
1445                    mHostAuthRecv = new EmailContent.HostAuth();
1446                }
1447            }
1448            return mHostAuthRecv;
1449        }
1450
1451        /**
1452         * For compatibility while converting to provider model, generate a "local store URI"
1453         *
1454         * @return a string in the form of a Uri, as used by the other parts of the email app
1455         */
1456        public String getLocalStoreUri(Context context) {
1457            return "local://localhost/" + context.getDatabasePath(getUuid() + ".db");
1458        }
1459
1460        /**
1461         * @return true if the instance is of an EAS account.
1462         *
1463         * NOTE This method accesses the DB if {@link #mHostAuthRecv} hasn't been restored yet.
1464         * Use caution when you use this on the main thread.
1465         */
1466        public boolean isEasAccount(Context context) {
1467            return "eas".equals(getProtocol(context));
1468        }
1469
1470        /**
1471         * @return true if the account supports "move messages".
1472         */
1473        public static boolean supportsMoveMessages(Context context, long accountId) {
1474            String protocol = getProtocol(context, accountId);
1475            return "eas".equals(protocol) || "imap".equals(protocol);
1476        }
1477
1478        /**
1479         * Set the account to be the default account.  If this is set to "true", when the account
1480         * is saved, all other accounts will have the same value set to "false".
1481         * @param newDefaultState the new default state - if true, others will be cleared.
1482         */
1483        public void setDefaultAccount(boolean newDefaultState) {
1484            mIsDefault = newDefaultState;
1485        }
1486
1487        /**
1488         * Helper method for finding the default account.
1489         */
1490        static private long getDefaultAccountWhere(Context context, String where) {
1491            return Utility.getFirstRowLong(context, CONTENT_URI,
1492                    DEFAULT_ID_PROJECTION,
1493                    where, null, null, 0, Long.valueOf(-1));
1494        }
1495
1496        /**
1497         * @return {@link Uri} to this {@link Account} in the
1498         * {@code content://com.android.email.provider/account/UUID} format, which is safe to use
1499         * for desktop shortcuts.
1500         *
1501         * <p>We don't want to store _id in shortcuts, because
1502         * {@link com.android.email.provider.AccountBackupRestore} won't preserve it.
1503         */
1504        public Uri getShortcutSafeUri() {
1505            return getShortcutSafeUriFromUuid(mCompatibilityUuid);
1506        }
1507
1508        /**
1509         * @return {@link Uri} to an {@link Account} with a {@code uuid}.
1510         */
1511        public static Uri getShortcutSafeUriFromUuid(String uuid) {
1512            return CONTENT_URI.buildUpon().appendEncodedPath(uuid).build();
1513        }
1514
1515        /**
1516         * Parse {@link Uri} in the {@code content://com.android.email.provider/account/ID} format
1517         * where ID = account id (used on Eclair, Android 2.0-2.1) or UUID, and return _id of
1518         * the {@link Account} associated with it.
1519         *
1520         * @param context context to access DB
1521         * @param uri URI of interest
1522         * @return _id of the {@link Account} associated with ID, or -1 if none found.
1523         */
1524        public static long getAccountIdFromShortcutSafeUri(Context context, Uri uri) {
1525            // Make sure the URI is in the correct format.
1526            if (!"content".equals(uri.getScheme())
1527                    || !AUTHORITY.equals(uri.getAuthority())) {
1528                return -1;
1529            }
1530
1531            final List<String> ps = uri.getPathSegments();
1532            if (ps.size() != 2 || !"account".equals(ps.get(0))) {
1533                return -1;
1534            }
1535
1536            // Now get the ID part.
1537            final String id = ps.get(1);
1538
1539            // First, see if ID can be parsed as long.  (Eclair-style)
1540            // (UUIDs have '-' in them, so they are always non-parsable.)
1541            try {
1542                return Long.parseLong(id);
1543            } catch (NumberFormatException ok) {
1544                // OK, it's not a long.  Continue...
1545            }
1546
1547            // Now id is a UUId.
1548            return getAccountIdFromUuid(context, id);
1549        }
1550
1551        /**
1552         * @return ID of the account with the given UUID.
1553         */
1554        public static long getAccountIdFromUuid(Context context, String uuid) {
1555            return Utility.getFirstRowLong(context,
1556                    CONTENT_URI, ID_PROJECTION,
1557                    UUID_SELECTION, new String[] {uuid}, null, 0, -1L);
1558        }
1559
1560        /**
1561         * Return the id of the default account.  If one hasn't been explicitly specified, return
1562         * the first one in the database.  For any account saved in the DB, this must be used
1563         * to check for the default account - the mIsDefault field is set lazily and may be
1564         * incorrect.
1565         * @param context the caller's context
1566         * @return the id of the default account, or -1 if there are no accounts
1567         */
1568        static public long getDefaultAccountId(Context context) {
1569            long id = getDefaultAccountWhere(context, AccountColumns.IS_DEFAULT + "=1");
1570            if (id == -1) {
1571                id = getDefaultAccountWhere(context, null);
1572            }
1573            return id;
1574        }
1575
1576        /**
1577         * Given an account id, return the account's protocol
1578         * @param context the caller's context
1579         * @param accountId the id of the account to be examined
1580         * @return the account's protocol (or null if the Account or HostAuth do not exist)
1581         */
1582        public static String getProtocol(Context context, long accountId) {
1583            Account account = Account.restoreAccountWithId(context, accountId);
1584            if (account != null) {
1585                return account.getProtocol(context);
1586             }
1587            return null;
1588        }
1589
1590        /**
1591         * Return the account's protocol
1592         * @param context the caller's context
1593         * @return the account's protocol (or null if the HostAuth doesn't not exist)
1594         */
1595        public String getProtocol(Context context) {
1596            HostAuth hostAuth = HostAuth.restoreHostAuthWithId(context, mHostAuthKeyRecv);
1597            if (hostAuth != null) {
1598                return hostAuth.mProtocol;
1599            }
1600            return null;
1601        }
1602
1603        /**
1604         * Return the account ID for a message with a given id
1605         *
1606         * @param context the caller's context
1607         * @param messageId the id of the message
1608         * @return the account ID, or -1 if the account doesn't exist
1609         */
1610        public static long getAccountIdForMessageId(Context context, long messageId) {
1611            return Message.getKeyColumnLong(context, messageId, MessageColumns.ACCOUNT_KEY);
1612        }
1613
1614        /**
1615         * Return the account for a message with a given id
1616         * @param context the caller's context
1617         * @param messageId the id of the message
1618         * @return the account, or null if the account doesn't exist
1619         */
1620        public static Account getAccountForMessageId(Context context, long messageId) {
1621            long accountId = getAccountIdForMessageId(context, messageId);
1622            if (accountId != -1) {
1623                return Account.restoreAccountWithId(context, accountId);
1624            }
1625            return null;
1626        }
1627
1628        /**
1629         * @return true if an {@code accountId} is assigned to any existing account.
1630         */
1631        public static boolean isValidId(Context context, long accountId) {
1632            return null != Utility.getFirstRowLong(context, CONTENT_URI, ID_PROJECTION,
1633                    ID_SELECTION, new String[] {Long.toString(accountId)}, null,
1634                    ID_PROJECTION_COLUMN);
1635        }
1636
1637        /**
1638         * Check a single account for security hold status.
1639         */
1640        public static boolean isSecurityHold(Context context, long accountId) {
1641            return (Utility.getFirstRowLong(context,
1642                    ContentUris.withAppendedId(Account.CONTENT_URI, accountId),
1643                    ACCOUNT_FLAGS_PROJECTION, null, null, null, ACCOUNT_FLAGS_COLUMN_FLAGS, 0L)
1644                    & Account.FLAGS_SECURITY_HOLD) != 0;
1645        }
1646
1647        /**
1648         * @return id of the "inbox" mailbox, or -1 if not found.
1649         */
1650        public static long getInboxId(Context context, long accountId) {
1651            return Utility.getFirstRowLong(context, Mailbox.CONTENT_URI, ID_PROJECTION,
1652                    FIND_INBOX_SELECTION, new String[] {Long.toString(accountId)}, null,
1653                    ID_PROJECTION_COLUMN, -1L);
1654        }
1655
1656        /**
1657         * Clear all account hold flags that are set.
1658         *
1659         * (This will trigger watchers, and in particular will cause EAS to try and resync the
1660         * account(s).)
1661         */
1662        public static void clearSecurityHoldOnAllAccounts(Context context) {
1663            ContentResolver resolver = context.getContentResolver();
1664            Cursor c = resolver.query(Account.CONTENT_URI, ACCOUNT_FLAGS_PROJECTION,
1665                    SECURITY_NONZERO_SELECTION, null, null);
1666            try {
1667                while (c.moveToNext()) {
1668                    int flags = c.getInt(ACCOUNT_FLAGS_COLUMN_FLAGS);
1669
1670                    if (0 != (flags & FLAGS_SECURITY_HOLD)) {
1671                        ContentValues cv = new ContentValues();
1672                        cv.put(AccountColumns.FLAGS, flags & ~FLAGS_SECURITY_HOLD);
1673                        long accountId = c.getLong(ACCOUNT_FLAGS_COLUMN_ID);
1674                        Uri uri = ContentUris.withAppendedId(Account.CONTENT_URI, accountId);
1675                        resolver.update(uri, cv, null, null);
1676                    }
1677                }
1678            } finally {
1679                c.close();
1680            }
1681        }
1682
1683        /**
1684         * Override update to enforce a single default account, and do it atomically
1685         */
1686        @Override
1687        public int update(Context context, ContentValues cv) {
1688            if (mPolicy != null && mPolicyKey <= 0) {
1689                // If a policy is set and there's no policy, link it to the account
1690                mPolicy.setAccountPolicy(context, this, null);
1691            }
1692            if (cv.containsKey(AccountColumns.IS_DEFAULT) &&
1693                    cv.getAsBoolean(AccountColumns.IS_DEFAULT)) {
1694                ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
1695                ContentValues cv1 = new ContentValues();
1696                cv1.put(AccountColumns.IS_DEFAULT, false);
1697                // Clear the default flag in all accounts
1698                ops.add(ContentProviderOperation.newUpdate(CONTENT_URI).withValues(cv1).build());
1699                // Update this account
1700                ops.add(ContentProviderOperation
1701                        .newUpdate(ContentUris.withAppendedId(CONTENT_URI, mId))
1702                        .withValues(cv).build());
1703                try {
1704                    context.getContentResolver().applyBatch(AUTHORITY, ops);
1705                    return 1;
1706                } catch (RemoteException e) {
1707                    // There is nothing to be done here; fail by returning 0
1708                } catch (OperationApplicationException e) {
1709                    // There is nothing to be done here; fail by returning 0
1710                }
1711                return 0;
1712            }
1713            return super.update(context, cv);
1714        }
1715
1716        /*
1717         * Override this so that we can store the HostAuth's first and link them to the Account
1718         * (non-Javadoc)
1719         * @see com.android.email.provider.EmailContent#save(android.content.Context)
1720         */
1721        @Override
1722        public Uri save(Context context) {
1723            if (isSaved()) {
1724                throw new UnsupportedOperationException();
1725            }
1726            // This logic is in place so I can (a) short circuit the expensive stuff when
1727            // possible, and (b) override (and throw) if anyone tries to call save() or update()
1728            // directly for Account, which are unsupported.
1729            if (mHostAuthRecv == null && mHostAuthSend == null && mIsDefault == false &&
1730                    mPolicy != null) {
1731                return super.save(context);
1732            }
1733
1734            int index = 0;
1735            int recvIndex = -1;
1736            int sendIndex = -1;
1737            int policyIndex = -1;
1738
1739            // Create operations for saving the send and recv hostAuths
1740            // Also, remember which operation in the array they represent
1741            ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
1742            if (mHostAuthRecv != null) {
1743                recvIndex = index++;
1744                ops.add(ContentProviderOperation.newInsert(mHostAuthRecv.mBaseUri)
1745                        .withValues(mHostAuthRecv.toContentValues())
1746                        .build());
1747            }
1748            if (mHostAuthSend != null) {
1749                sendIndex = index++;
1750                ops.add(ContentProviderOperation.newInsert(mHostAuthSend.mBaseUri)
1751                        .withValues(mHostAuthSend.toContentValues())
1752                        .build());
1753            }
1754            if (mPolicy != null) {
1755                policyIndex = index++;
1756                ops.add(ContentProviderOperation.newInsert(mPolicy.mBaseUri)
1757                        .withValues(mPolicy.toContentValues())
1758                        .build());
1759            }
1760
1761            // Create operations for making this the only default account
1762            // Note, these are always updates because they change existing accounts
1763            if (mIsDefault) {
1764                index++;
1765                ContentValues cv1 = new ContentValues();
1766                cv1.put(AccountColumns.IS_DEFAULT, 0);
1767                ops.add(ContentProviderOperation.newUpdate(CONTENT_URI).withValues(cv1).build());
1768            }
1769
1770            // Now do the Account
1771            ContentValues cv = null;
1772            if (recvIndex >= 0 || sendIndex >= 0) {
1773                cv = new ContentValues();
1774                if (recvIndex >= 0) {
1775                    cv.put(Account.HOST_AUTH_KEY_RECV, recvIndex);
1776                }
1777                if (sendIndex >= 0) {
1778                    cv.put(Account.HOST_AUTH_KEY_SEND, sendIndex);
1779                }
1780                if (policyIndex >= 0) {
1781                    cv.put(Account.POLICY_KEY, policyIndex);
1782                }
1783            }
1784
1785            ContentProviderOperation.Builder b = ContentProviderOperation.newInsert(mBaseUri);
1786            b.withValues(toContentValues());
1787            if (cv != null) {
1788                b.withValueBackReferences(cv);
1789            }
1790            ops.add(b.build());
1791
1792            try {
1793                ContentProviderResult[] results =
1794                    context.getContentResolver().applyBatch(AUTHORITY, ops);
1795                // If saving, set the mId's of the various saved objects
1796                if (recvIndex >= 0) {
1797                    long newId = getId(results[recvIndex].uri);
1798                    mHostAuthKeyRecv = newId;
1799                    mHostAuthRecv.mId = newId;
1800                }
1801                if (sendIndex >= 0) {
1802                    long newId = getId(results[sendIndex].uri);
1803                    mHostAuthKeySend = newId;
1804                    mHostAuthSend.mId = newId;
1805                }
1806                if (policyIndex >= 0) {
1807                    long newId = getId(results[policyIndex].uri);
1808                    mPolicyKey = newId;
1809                    mPolicy.mId = newId;
1810                }
1811                Uri u = results[index].uri;
1812                mId = getId(u);
1813                return u;
1814            } catch (RemoteException e) {
1815                // There is nothing to be done here; fail by returning null
1816            } catch (OperationApplicationException e) {
1817                // There is nothing to be done here; fail by returning null
1818            }
1819            return null;
1820        }
1821
1822        @Override
1823        public ContentValues toContentValues() {
1824            ContentValues values = new ContentValues();
1825            values.put(AccountColumns.DISPLAY_NAME, mDisplayName);
1826            values.put(AccountColumns.EMAIL_ADDRESS, mEmailAddress);
1827            values.put(AccountColumns.SYNC_KEY, mSyncKey);
1828            values.put(AccountColumns.SYNC_LOOKBACK, mSyncLookback);
1829            values.put(AccountColumns.SYNC_INTERVAL, mSyncInterval);
1830            values.put(AccountColumns.HOST_AUTH_KEY_RECV, mHostAuthKeyRecv);
1831            values.put(AccountColumns.HOST_AUTH_KEY_SEND, mHostAuthKeySend);
1832            values.put(AccountColumns.FLAGS, mFlags);
1833            values.put(AccountColumns.IS_DEFAULT, mIsDefault);
1834            values.put(AccountColumns.COMPATIBILITY_UUID, mCompatibilityUuid);
1835            values.put(AccountColumns.SENDER_NAME, mSenderName);
1836            values.put(AccountColumns.RINGTONE_URI, mRingtoneUri);
1837            values.put(AccountColumns.PROTOCOL_VERSION, mProtocolVersion);
1838            values.put(AccountColumns.NEW_MESSAGE_COUNT, mNewMessageCount);
1839            values.put(AccountColumns.SECURITY_SYNC_KEY, mSecuritySyncKey);
1840            values.put(AccountColumns.SIGNATURE, mSignature);
1841            values.put(AccountColumns.POLICY_KEY, mPolicyKey);
1842            return values;
1843        }
1844
1845        /**
1846         * Supports Parcelable
1847         */
1848        @Override
1849        public int describeContents() {
1850            return 0;
1851        }
1852
1853        /**
1854         * Supports Parcelable
1855         */
1856        public static final Parcelable.Creator<EmailContent.Account> CREATOR
1857                = new Parcelable.Creator<EmailContent.Account>() {
1858            @Override
1859            public EmailContent.Account createFromParcel(Parcel in) {
1860                return new EmailContent.Account(in);
1861            }
1862
1863            @Override
1864            public EmailContent.Account[] newArray(int size) {
1865                return new EmailContent.Account[size];
1866            }
1867        };
1868
1869        /**
1870         * Supports Parcelable
1871         */
1872        @Override
1873        public void writeToParcel(Parcel dest, int flags) {
1874            // mBaseUri is not parceled
1875            dest.writeLong(mId);
1876            dest.writeString(mDisplayName);
1877            dest.writeString(mEmailAddress);
1878            dest.writeString(mSyncKey);
1879            dest.writeInt(mSyncLookback);
1880            dest.writeInt(mSyncInterval);
1881            dest.writeLong(mHostAuthKeyRecv);
1882            dest.writeLong(mHostAuthKeySend);
1883            dest.writeInt(mFlags);
1884            dest.writeByte(mIsDefault ? (byte)1 : (byte)0);
1885            dest.writeString(mCompatibilityUuid);
1886            dest.writeString(mSenderName);
1887            dest.writeString(mRingtoneUri);
1888            dest.writeString(mProtocolVersion);
1889            dest.writeInt(mNewMessageCount);
1890            dest.writeString(mSecuritySyncKey);
1891            dest.writeString(mSignature);
1892            dest.writeLong(mPolicyKey);
1893
1894            if (mHostAuthRecv != null) {
1895                dest.writeByte((byte)1);
1896                mHostAuthRecv.writeToParcel(dest, flags);
1897            } else {
1898                dest.writeByte((byte)0);
1899            }
1900
1901            if (mHostAuthSend != null) {
1902                dest.writeByte((byte)1);
1903                mHostAuthSend.writeToParcel(dest, flags);
1904            } else {
1905                dest.writeByte((byte)0);
1906            }
1907        }
1908
1909        /**
1910         * Supports Parcelable
1911         */
1912        public Account(Parcel in) {
1913            mBaseUri = EmailContent.Account.CONTENT_URI;
1914            mId = in.readLong();
1915            mDisplayName = in.readString();
1916            mEmailAddress = in.readString();
1917            mSyncKey = in.readString();
1918            mSyncLookback = in.readInt();
1919            mSyncInterval = in.readInt();
1920            mHostAuthKeyRecv = in.readLong();
1921            mHostAuthKeySend = in.readLong();
1922            mFlags = in.readInt();
1923            mIsDefault = in.readByte() == 1;
1924            mCompatibilityUuid = in.readString();
1925            mSenderName = in.readString();
1926            mRingtoneUri = in.readString();
1927            mProtocolVersion = in.readString();
1928            mNewMessageCount = in.readInt();
1929            mSecuritySyncKey = in.readString();
1930            mSignature = in.readString();
1931            mPolicyKey = in.readLong();
1932
1933            mHostAuthRecv = null;
1934            if (in.readByte() == 1) {
1935                mHostAuthRecv = new EmailContent.HostAuth(in);
1936            }
1937
1938            mHostAuthSend = null;
1939            if (in.readByte() == 1) {
1940                mHostAuthSend = new EmailContent.HostAuth(in);
1941            }
1942        }
1943
1944        /**
1945         * For debugger support only - DO NOT use for code.
1946         */
1947        @Override
1948        public String toString() {
1949            StringBuilder sb = new StringBuilder('[');
1950            if (mHostAuthRecv != null && mHostAuthRecv.mProtocol != null) {
1951                sb.append(mHostAuthRecv.mProtocol);
1952                sb.append(':');
1953            }
1954            if (mDisplayName != null)   sb.append(mDisplayName);
1955            sb.append(':');
1956            if (mEmailAddress != null)  sb.append(mEmailAddress);
1957            sb.append(':');
1958            if (mSenderName != null)    sb.append(mSenderName);
1959            sb.append(']');
1960            return sb.toString();
1961        }
1962
1963    }
1964
1965    public interface AttachmentColumns {
1966        public static final String ID = "_id";
1967        // The display name of the attachment
1968        public static final String FILENAME = "fileName";
1969        // The mime type of the attachment
1970        public static final String MIME_TYPE = "mimeType";
1971        // The size of the attachment in bytes
1972        public static final String SIZE = "size";
1973        // The (internal) contentId of the attachment (inline attachments will have these)
1974        public static final String CONTENT_ID = "contentId";
1975        // The location of the loaded attachment (probably a file)
1976        @SuppressWarnings("hiding")
1977        public static final String CONTENT_URI = "contentUri";
1978        // A foreign key into the Message table (the message owning this attachment)
1979        public static final String MESSAGE_KEY = "messageKey";
1980        // The location of the attachment on the server side
1981        // For IMAP, this is a part number (e.g. 2.1); for EAS, it's the internal file name
1982        public static final String LOCATION = "location";
1983        // The transfer encoding of the attachment
1984        public static final String ENCODING = "encoding";
1985        // Not currently used
1986        public static final String CONTENT = "content";
1987        // Flags
1988        public static final String FLAGS = "flags";
1989        // Content that is actually contained in the Attachment row
1990        public static final String CONTENT_BYTES = "content_bytes";
1991        // A foreign key into the Account table (for the message owning this attachment)
1992        public static final String ACCOUNT_KEY = "accountKey";
1993    }
1994
1995    public static final class Attachment extends EmailContent
1996            implements AttachmentColumns, Parcelable {
1997        public static final String TABLE_NAME = "Attachment";
1998        @SuppressWarnings("hiding")
1999        public static final Uri CONTENT_URI = Uri.parse(EmailContent.CONTENT_URI + "/attachment");
2000        // This must be used with an appended id: ContentUris.withAppendedId(MESSAGE_ID_URI, id)
2001        public static final Uri MESSAGE_ID_URI = Uri.parse(
2002                EmailContent.CONTENT_URI + "/attachment/message");
2003
2004        public String mFileName;
2005        public String mMimeType;
2006        public long mSize;
2007        public String mContentId;
2008        public String mContentUri;
2009        public long mMessageKey;
2010        public String mLocation;
2011        public String mEncoding;
2012        public String mContent; // Not currently used
2013        public int mFlags;
2014        public byte[] mContentBytes;
2015        public long mAccountKey;
2016
2017        public static final int CONTENT_ID_COLUMN = 0;
2018        public static final int CONTENT_FILENAME_COLUMN = 1;
2019        public static final int CONTENT_MIME_TYPE_COLUMN = 2;
2020        public static final int CONTENT_SIZE_COLUMN = 3;
2021        public static final int CONTENT_CONTENT_ID_COLUMN = 4;
2022        public static final int CONTENT_CONTENT_URI_COLUMN = 5;
2023        public static final int CONTENT_MESSAGE_ID_COLUMN = 6;
2024        public static final int CONTENT_LOCATION_COLUMN = 7;
2025        public static final int CONTENT_ENCODING_COLUMN = 8;
2026        public static final int CONTENT_CONTENT_COLUMN = 9; // Not currently used
2027        public static final int CONTENT_FLAGS_COLUMN = 10;
2028        public static final int CONTENT_CONTENT_BYTES_COLUMN = 11;
2029        public static final int CONTENT_ACCOUNT_KEY_COLUMN = 12;
2030        public static final String[] CONTENT_PROJECTION = new String[] {
2031            RECORD_ID, AttachmentColumns.FILENAME, AttachmentColumns.MIME_TYPE,
2032            AttachmentColumns.SIZE, AttachmentColumns.CONTENT_ID, AttachmentColumns.CONTENT_URI,
2033            AttachmentColumns.MESSAGE_KEY, AttachmentColumns.LOCATION, AttachmentColumns.ENCODING,
2034            AttachmentColumns.CONTENT, AttachmentColumns.FLAGS, AttachmentColumns.CONTENT_BYTES,
2035            AttachmentColumns.ACCOUNT_KEY
2036        };
2037
2038        // All attachments with an empty URI, regardless of mailbox
2039        public static final String PRECACHE_SELECTION =
2040            AttachmentColumns.CONTENT_URI + " isnull AND " + Attachment.FLAGS + "=0";
2041        // Attachments with an empty URI that are in an inbox
2042        public static final String PRECACHE_INBOX_SELECTION =
2043            PRECACHE_SELECTION + " AND " + AttachmentColumns.MESSAGE_KEY + " IN ("
2044            +     "SELECT " + MessageColumns.ID + " FROM " + Message.TABLE_NAME
2045            +     " WHERE " + Message.ALL_INBOX_SELECTION
2046            +     ")";
2047
2048        // Bits used in mFlags
2049        // WARNING: AttachmentDownloadService relies on the fact that ALL of the flags below
2050        // disqualify attachments for precaching.  If you add a flag that does NOT disqualify an
2051        // attachment for precaching, you MUST change the PRECACHE_SELECTION definition above
2052
2053        // Instruct Rfc822Output to 1) not use Content-Disposition and 2) use multipart/alternative
2054        // with this attachment.  This is only valid if there is one and only one attachment and
2055        // that attachment has this flag set
2056        public static final int FLAG_ICS_ALTERNATIVE_PART = 1<<0;
2057        // Indicate that this attachment has been requested for downloading by the user; this is
2058        // the highest priority for attachment downloading
2059        public static final int FLAG_DOWNLOAD_USER_REQUEST = 1<<1;
2060        // Indicate that this attachment needs to be downloaded as part of an outgoing forwarded
2061        // message
2062        public static final int FLAG_DOWNLOAD_FORWARD = 1<<2;
2063        // Indicates that the attachment download failed in a non-recoverable manner
2064        public static final int FLAG_DOWNLOAD_FAILED = 1<<3;
2065        // Allow "room" for some additional download-related flags here
2066        // Indicates that the attachment will be smart-forwarded
2067        public static final int FLAG_SMART_FORWARD = 1<<8;
2068        // Indicates that the attachment cannot be forwarded due to a policy restriction
2069        public static final int FLAG_POLICY_DISALLOWS_DOWNLOAD = 1<<9;
2070        /**
2071         * no public constructor since this is a utility class
2072         */
2073        public Attachment() {
2074            mBaseUri = CONTENT_URI;
2075        }
2076
2077         /**
2078         * Restore an Attachment from the database, given its unique id
2079         * @param context
2080         * @param id
2081         * @return the instantiated Attachment
2082         */
2083        public static Attachment restoreAttachmentWithId(Context context, long id) {
2084            return EmailContent.restoreContentWithId(context, Attachment.class,
2085                    Attachment.CONTENT_URI, Attachment.CONTENT_PROJECTION, id);
2086        }
2087
2088        /**
2089         * Restore all the Attachments of a message given its messageId
2090         */
2091        public static Attachment[] restoreAttachmentsWithMessageId(Context context,
2092                long messageId) {
2093            Uri uri = ContentUris.withAppendedId(MESSAGE_ID_URI, messageId);
2094            Cursor c = context.getContentResolver().query(uri, CONTENT_PROJECTION,
2095                    null, null, null);
2096            try {
2097                int count = c.getCount();
2098                Attachment[] attachments = new Attachment[count];
2099                for (int i = 0; i < count; ++i) {
2100                    c.moveToNext();
2101                    Attachment attach = new Attachment();
2102                    attach.restore(c);
2103                    attachments[i] = attach;
2104                }
2105                return attachments;
2106            } finally {
2107                c.close();
2108            }
2109        }
2110
2111        /**
2112         * Creates a unique file in the external store by appending a hyphen
2113         * and a number to the given filename.
2114         * @param filename
2115         * @return a new File object, or null if one could not be created
2116         */
2117        public static File createUniqueFile(String filename) {
2118            // TODO Handle internal storage, as required
2119            if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
2120                File directory = Environment.getExternalStorageDirectory();
2121                File file = new File(directory, filename);
2122                if (!file.exists()) {
2123                    return file;
2124                }
2125                // Get the extension of the file, if any.
2126                int index = filename.lastIndexOf('.');
2127                String name = filename;
2128                String extension = "";
2129                if (index != -1) {
2130                    name = filename.substring(0, index);
2131                    extension = filename.substring(index);
2132                }
2133                for (int i = 2; i < Integer.MAX_VALUE; i++) {
2134                    file = new File(directory, name + '-' + i + extension);
2135                    if (!file.exists()) {
2136                        return file;
2137                    }
2138                }
2139                return null;
2140            }
2141            return null;
2142        }
2143
2144        @Override
2145        public void restore(Cursor cursor) {
2146            mBaseUri = CONTENT_URI;
2147            mId = cursor.getLong(CONTENT_ID_COLUMN);
2148            mFileName= cursor.getString(CONTENT_FILENAME_COLUMN);
2149            mMimeType = cursor.getString(CONTENT_MIME_TYPE_COLUMN);
2150            mSize = cursor.getLong(CONTENT_SIZE_COLUMN);
2151            mContentId = cursor.getString(CONTENT_CONTENT_ID_COLUMN);
2152            mContentUri = cursor.getString(CONTENT_CONTENT_URI_COLUMN);
2153            mMessageKey = cursor.getLong(CONTENT_MESSAGE_ID_COLUMN);
2154            mLocation = cursor.getString(CONTENT_LOCATION_COLUMN);
2155            mEncoding = cursor.getString(CONTENT_ENCODING_COLUMN);
2156            mContent = cursor.getString(CONTENT_CONTENT_COLUMN);
2157            mFlags = cursor.getInt(CONTENT_FLAGS_COLUMN);
2158            mContentBytes = cursor.getBlob(CONTENT_CONTENT_BYTES_COLUMN);
2159            mAccountKey = cursor.getLong(CONTENT_ACCOUNT_KEY_COLUMN);
2160        }
2161
2162        @Override
2163        public ContentValues toContentValues() {
2164            ContentValues values = new ContentValues();
2165            values.put(AttachmentColumns.FILENAME, mFileName);
2166            values.put(AttachmentColumns.MIME_TYPE, mMimeType);
2167            values.put(AttachmentColumns.SIZE, mSize);
2168            values.put(AttachmentColumns.CONTENT_ID, mContentId);
2169            values.put(AttachmentColumns.CONTENT_URI, mContentUri);
2170            values.put(AttachmentColumns.MESSAGE_KEY, mMessageKey);
2171            values.put(AttachmentColumns.LOCATION, mLocation);
2172            values.put(AttachmentColumns.ENCODING, mEncoding);
2173            values.put(AttachmentColumns.CONTENT, mContent);
2174            values.put(AttachmentColumns.FLAGS, mFlags);
2175            values.put(AttachmentColumns.CONTENT_BYTES, mContentBytes);
2176            values.put(AttachmentColumns.ACCOUNT_KEY, mAccountKey);
2177            return values;
2178        }
2179
2180        @Override
2181        public int describeContents() {
2182             return 0;
2183        }
2184
2185        @Override
2186        public void writeToParcel(Parcel dest, int flags) {
2187            // mBaseUri is not parceled
2188            dest.writeLong(mId);
2189            dest.writeString(mFileName);
2190            dest.writeString(mMimeType);
2191            dest.writeLong(mSize);
2192            dest.writeString(mContentId);
2193            dest.writeString(mContentUri);
2194            dest.writeLong(mMessageKey);
2195            dest.writeString(mLocation);
2196            dest.writeString(mEncoding);
2197            dest.writeString(mContent);
2198            dest.writeInt(mFlags);
2199            dest.writeLong(mAccountKey);
2200            if (mContentBytes == null) {
2201                dest.writeInt(-1);
2202            } else {
2203                dest.writeInt(mContentBytes.length);
2204                dest.writeByteArray(mContentBytes);
2205            }
2206        }
2207
2208        public Attachment(Parcel in) {
2209            mBaseUri = EmailContent.Attachment.CONTENT_URI;
2210            mId = in.readLong();
2211            mFileName = in.readString();
2212            mMimeType = in.readString();
2213            mSize = in.readLong();
2214            mContentId = in.readString();
2215            mContentUri = in.readString();
2216            mMessageKey = in.readLong();
2217            mLocation = in.readString();
2218            mEncoding = in.readString();
2219            mContent = in.readString();
2220            mFlags = in.readInt();
2221            mAccountKey = in.readLong();
2222            final int contentBytesLen = in.readInt();
2223            if (contentBytesLen == -1) {
2224                mContentBytes = null;
2225            } else {
2226                mContentBytes = new byte[contentBytesLen];
2227                in.readByteArray(mContentBytes);
2228            }
2229         }
2230
2231        public static final Parcelable.Creator<EmailContent.Attachment> CREATOR
2232                = new Parcelable.Creator<EmailContent.Attachment>() {
2233            @Override
2234            public EmailContent.Attachment createFromParcel(Parcel in) {
2235                return new EmailContent.Attachment(in);
2236            }
2237
2238            @Override
2239            public EmailContent.Attachment[] newArray(int size) {
2240                return new EmailContent.Attachment[size];
2241            }
2242        };
2243
2244        @Override
2245        public String toString() {
2246            return "[" + mFileName + ", " + mMimeType + ", " + mSize + ", " + mContentId + ", "
2247                    + mContentUri + ", " + mMessageKey + ", " + mLocation + ", " + mEncoding  + ", "
2248                    + mFlags + ", " + mContentBytes + ", " + mAccountKey + "]";
2249        }
2250    }
2251
2252    public interface MailboxColumns {
2253        public static final String ID = "_id";
2254        // The display name of this mailbox [INDEX]
2255        static final String DISPLAY_NAME = "displayName";
2256        // The server's identifier for this mailbox
2257        public static final String SERVER_ID = "serverId";
2258        // The server's identifier for the parent of this mailbox (null = top-level)
2259        public static final String PARENT_SERVER_ID = "parentServerId";
2260        // A foreign key for the parent of this mailbox (-1 = top-level, 0=uninitialized)
2261        public static final String PARENT_KEY = "parentKey";
2262        // A foreign key to the Account that owns this mailbox
2263        public static final String ACCOUNT_KEY = "accountKey";
2264        // The type (role) of this mailbox
2265        public static final String TYPE = "type";
2266        // The hierarchy separator character
2267        public static final String DELIMITER = "delimiter";
2268        // Server-based sync key or validity marker (e.g. "SyncKey" for EAS, "uidvalidity" for IMAP)
2269        public static final String SYNC_KEY = "syncKey";
2270        // The sync lookback period for this mailbox (or null if using the account default)
2271        public static final String SYNC_LOOKBACK = "syncLookback";
2272        // The sync frequency for this mailbox (or null if using the account default)
2273        public static final String SYNC_INTERVAL = "syncInterval";
2274        // The time of last successful sync completion (millis)
2275        public static final String SYNC_TIME = "syncTime";
2276        // Cached unread count
2277        public static final String UNREAD_COUNT = "unreadCount";
2278        // Visibility of this folder in a list of folders [INDEX]
2279        public static final String FLAG_VISIBLE = "flagVisible";
2280        // Other states, as a bit field, e.g. CHILDREN_VISIBLE, HAS_CHILDREN
2281        public static final String FLAGS = "flags";
2282        // Backward compatible
2283        public static final String VISIBLE_LIMIT = "visibleLimit";
2284        // Sync status (can be used as desired by sync services)
2285        public static final String SYNC_STATUS = "syncStatus";
2286        // Number of messages in the mailbox.
2287        public static final String MESSAGE_COUNT = "messageCount";
2288        // Number of messages in the mailbox.
2289        public static final String LAST_SEEN_MESSAGE_KEY = "lastSeenMessageKey";
2290    }
2291
2292    public interface HostAuthColumns {
2293        public static final String ID = "_id";
2294        // The protocol (e.g. "imap", "pop3", "eas", "smtp"
2295        static final String PROTOCOL = "protocol";
2296        // The host address
2297        static final String ADDRESS = "address";
2298        // The port to use for the connection
2299        static final String PORT = "port";
2300        // General purpose flags
2301        static final String FLAGS = "flags";
2302        // The login (user name)
2303        static final String LOGIN = "login";
2304        // Password
2305        static final String PASSWORD = "password";
2306        // A domain or path, if required (used in IMAP and EAS)
2307        static final String DOMAIN = "domain";
2308        // DEPRECATED - Will not be set or stored
2309        static final String ACCOUNT_KEY = "accountKey";
2310    }
2311
2312    public static final class HostAuth extends EmailContent implements HostAuthColumns, Parcelable {
2313        public static final String TABLE_NAME = "HostAuth";
2314        @SuppressWarnings("hiding")
2315        public static final Uri CONTENT_URI = Uri.parse(EmailContent.CONTENT_URI + "/hostauth");
2316
2317        public static final int PORT_UNKNOWN = -1;
2318
2319        public static final int FLAG_NONE         = 0x00;    // No flags
2320        public static final int FLAG_SSL          = 0x01;    // Use SSL
2321        public static final int FLAG_TLS          = 0x02;    // Use TLS
2322        public static final int FLAG_AUTHENTICATE = 0x04;    // Use name/password for authentication
2323        public static final int FLAG_TRUST_ALL    = 0x08;    // Trust all certificates
2324        // Mask of settings directly configurable by the user
2325        public static final int USER_CONFIG_MASK  = 0x0b;
2326
2327        public String mProtocol;
2328        public String mAddress;
2329        public int mPort;
2330        public int mFlags;
2331        public String mLogin;
2332        public String mPassword;
2333        public String mDomain;
2334
2335        public static final int CONTENT_ID_COLUMN = 0;
2336        public static final int CONTENT_PROTOCOL_COLUMN = 1;
2337        public static final int CONTENT_ADDRESS_COLUMN = 2;
2338        public static final int CONTENT_PORT_COLUMN = 3;
2339        public static final int CONTENT_FLAGS_COLUMN = 4;
2340        public static final int CONTENT_LOGIN_COLUMN = 5;
2341        public static final int CONTENT_PASSWORD_COLUMN = 6;
2342        public static final int CONTENT_DOMAIN_COLUMN = 7;
2343
2344        public static final String[] CONTENT_PROJECTION = new String[] {
2345            RECORD_ID, HostAuthColumns.PROTOCOL, HostAuthColumns.ADDRESS, HostAuthColumns.PORT,
2346            HostAuthColumns.FLAGS, HostAuthColumns.LOGIN,
2347            HostAuthColumns.PASSWORD, HostAuthColumns.DOMAIN
2348        };
2349
2350        /**
2351         * no public constructor since this is a utility class
2352         */
2353        public HostAuth() {
2354            mBaseUri = CONTENT_URI;
2355
2356            // other defaults policy)
2357            mPort = PORT_UNKNOWN;
2358        }
2359
2360         /**
2361         * Restore a HostAuth from the database, given its unique id
2362         * @param context
2363         * @param id
2364         * @return the instantiated HostAuth
2365         */
2366        public static HostAuth restoreHostAuthWithId(Context context, long id) {
2367            return EmailContent.restoreContentWithId(context, HostAuth.class,
2368                    HostAuth.CONTENT_URI, HostAuth.CONTENT_PROJECTION, id);
2369        }
2370
2371
2372        /**
2373         * Returns the scheme for the specified flags.
2374         */
2375        public static String getSchemeString(String protocol, int flags) {
2376            String security = "";
2377            switch (flags & USER_CONFIG_MASK) {
2378                case FLAG_SSL:
2379                    security = "+ssl+";
2380                    break;
2381                case FLAG_SSL | FLAG_TRUST_ALL:
2382                    security = "+ssl+trustallcerts";
2383                    break;
2384                case FLAG_TLS:
2385                    security = "+tls+";
2386                    break;
2387                case FLAG_TLS | FLAG_TRUST_ALL:
2388                    security = "+tls+trustallcerts";
2389                    break;
2390            }
2391            return protocol + security;
2392        }
2393
2394        /**
2395         * Returns the flags for the specified scheme.
2396         */
2397        public static int getSchemeFlags(String scheme) {
2398            String[] schemeParts = scheme.split("\\+");
2399            int flags = HostAuth.FLAG_NONE;
2400            if (schemeParts.length >= 2) {
2401                String part1 = schemeParts[1];
2402                if ("ssl".equals(part1)) {
2403                    flags |= HostAuth.FLAG_SSL;
2404                } else if ("tls".equals(part1)) {
2405                    flags |= HostAuth.FLAG_TLS;
2406                }
2407                if (schemeParts.length >= 3) {
2408                    String part2 = schemeParts[2];
2409                    if ("trustallcerts".equals(part2)) {
2410                        flags |= HostAuth.FLAG_TRUST_ALL;
2411                    }
2412                }
2413            }
2414            return flags;
2415        }
2416
2417        @Override
2418        public void restore(Cursor cursor) {
2419            mBaseUri = CONTENT_URI;
2420            mId = cursor.getLong(CONTENT_ID_COLUMN);
2421            mProtocol = cursor.getString(CONTENT_PROTOCOL_COLUMN);
2422            mAddress = cursor.getString(CONTENT_ADDRESS_COLUMN);
2423            mPort = cursor.getInt(CONTENT_PORT_COLUMN);
2424            mFlags = cursor.getInt(CONTENT_FLAGS_COLUMN);
2425            mLogin = cursor.getString(CONTENT_LOGIN_COLUMN);
2426            mPassword = cursor.getString(CONTENT_PASSWORD_COLUMN);
2427            mDomain = cursor.getString(CONTENT_DOMAIN_COLUMN);
2428        }
2429
2430        @Override
2431        public ContentValues toContentValues() {
2432            ContentValues values = new ContentValues();
2433            values.put(HostAuthColumns.PROTOCOL, mProtocol);
2434            values.put(HostAuthColumns.ADDRESS, mAddress);
2435            values.put(HostAuthColumns.PORT, mPort);
2436            values.put(HostAuthColumns.FLAGS, mFlags);
2437            values.put(HostAuthColumns.LOGIN, mLogin);
2438            values.put(HostAuthColumns.PASSWORD, mPassword);
2439            values.put(HostAuthColumns.DOMAIN, mDomain);
2440            values.put(HostAuthColumns.ACCOUNT_KEY, 0); // Need something to satisfy the DB
2441            return values;
2442        }
2443
2444        /**
2445         * For compatibility while converting to provider model, generate a "store URI"
2446         * TODO cache this so we don't rebuild every time
2447         *
2448         * @return a string in the form of a Uri, as used by the other parts of the email app
2449         */
2450        public String getStoreUri() {
2451            String userInfo = null;
2452            if ((mFlags & FLAG_AUTHENTICATE) != 0) {
2453                String trimUser = (mLogin != null) ? mLogin.trim() : "";
2454                String password = (mPassword != null) ? mPassword : "";
2455                userInfo = trimUser + ":" + password;
2456            }
2457            String scheme = getSchemeString(mProtocol, mFlags);
2458            String address = (mAddress != null) ? mAddress.trim() : null;
2459            String path = (mDomain != null) ? "/" + mDomain : null;
2460
2461            URI uri;
2462            try {
2463                uri = new URI(
2464                        scheme,
2465                        userInfo,
2466                        address,
2467                        mPort,
2468                        path,
2469                        null,
2470                        null);
2471                return uri.toString();
2472            } catch (URISyntaxException e) {
2473                return null;
2474            }
2475        }
2476
2477        /**
2478         * Sets the user name and password from URI user info string
2479         */
2480        public void setLogin(String userInfo) {
2481            String userName = null;
2482            String userPassword = null;
2483            if (!TextUtils.isEmpty(userInfo)) {
2484                String[] userInfoParts = userInfo.split(":", 2);
2485                userName = userInfoParts[0];
2486                if (userInfoParts.length > 1) {
2487                    userPassword = userInfoParts[1];
2488                }
2489            }
2490            setLogin(userName, userPassword);
2491        }
2492
2493        /**
2494         * Sets the user name and password
2495         */
2496        public void setLogin(String userName, String userPassword) {
2497            mLogin = userName;
2498            mPassword = userPassword;
2499
2500            if (mLogin == null) {
2501                mFlags &= ~FLAG_AUTHENTICATE;
2502            } else {
2503                mFlags |= FLAG_AUTHENTICATE;
2504            }
2505        }
2506
2507        /**
2508         * Returns the login information. [0] is the username and [1] is the password. If
2509         * {@link #FLAG_AUTHENTICATE} is not set, {@code null} is returned.
2510         */
2511        public String[] getLogin() {
2512            if ((mFlags & FLAG_AUTHENTICATE) != 0) {
2513                String trimUser = (mLogin != null) ? mLogin.trim() : "";
2514                String password = (mPassword != null) ? mPassword : "";
2515                return new String[] { trimUser, password };
2516            }
2517            return null;
2518        }
2519
2520        /**
2521         * Sets the connection values of the auth structure per the given scheme, host and port.
2522         */
2523        public void setConnection(String scheme, String host, int port) {
2524            String[] schemeParts = scheme.split("\\+");
2525            String protocol = schemeParts[0];
2526            int flags = getSchemeFlags(scheme);
2527
2528            setConnection(protocol, host, port, flags);
2529        }
2530
2531        public void setConnection(String protocol, String address, int port, int flags) {
2532            // Set protocol, security, and additional flags based on uri scheme
2533            mProtocol = protocol;
2534
2535            mFlags &= ~(FLAG_SSL | FLAG_TLS | FLAG_TRUST_ALL);
2536            mFlags |= (flags & USER_CONFIG_MASK);
2537
2538            mAddress = address;
2539            mPort = port;
2540            if (mPort == PORT_UNKNOWN) {
2541                boolean useSSL = ((mFlags & FLAG_SSL) != 0);
2542                // infer port# from protocol + security
2543                // SSL implies a different port - TLS runs in the "regular" port
2544                // NOTE: Although the port should be setup in the various setup screens, this
2545                // block cannot easily be moved because we get process URIs from other sources
2546                // (e.g. for tests, provider templates and account restore) that may or may not
2547                // have a port specified.
2548                if ("pop3".equals(mProtocol)) {
2549                    mPort = useSSL ? 995 : 110;
2550                } else if ("imap".equals(mProtocol)) {
2551                    mPort = useSSL ? 993 : 143;
2552                } else if ("eas".equals(mProtocol)) {
2553                    mPort = useSSL ? 443 : 80;
2554                }  else if ("smtp".equals(mProtocol)) {
2555                    mPort = useSSL ? 465 : 587;
2556                }
2557            }
2558        }
2559
2560        /**
2561         * Supports Parcelable
2562         */
2563        @Override
2564        public int describeContents() {
2565            return 0;
2566        }
2567
2568        /**
2569         * Supports Parcelable
2570         */
2571        public static final Parcelable.Creator<EmailContent.HostAuth> CREATOR
2572                = new Parcelable.Creator<EmailContent.HostAuth>() {
2573            @Override
2574            public EmailContent.HostAuth createFromParcel(Parcel in) {
2575                return new EmailContent.HostAuth(in);
2576            }
2577
2578            @Override
2579            public EmailContent.HostAuth[] newArray(int size) {
2580                return new EmailContent.HostAuth[size];
2581            }
2582        };
2583
2584        /**
2585         * Supports Parcelable
2586         */
2587        @Override
2588        public void writeToParcel(Parcel dest, int flags) {
2589            // mBaseUri is not parceled
2590            dest.writeLong(mId);
2591            dest.writeString(mProtocol);
2592            dest.writeString(mAddress);
2593            dest.writeInt(mPort);
2594            dest.writeInt(mFlags);
2595            dest.writeString(mLogin);
2596            dest.writeString(mPassword);
2597            dest.writeString(mDomain);
2598        }
2599
2600        /**
2601         * Supports Parcelable
2602         */
2603        public HostAuth(Parcel in) {
2604            mBaseUri = CONTENT_URI;
2605            mId = in.readLong();
2606            mProtocol = in.readString();
2607            mAddress = in.readString();
2608            mPort = in.readInt();
2609            mFlags = in.readInt();
2610            mLogin = in.readString();
2611            mPassword = in.readString();
2612            mDomain = in.readString();
2613        }
2614
2615        /**
2616         * For debugger support only - DO NOT use for code.
2617         */
2618        @Override
2619        public String toString() {
2620            return getStoreUri();
2621        }
2622
2623        @Override
2624        public boolean equals(Object o) {
2625            if (!(o instanceof HostAuth)) {
2626                return false;
2627            }
2628            HostAuth that = (HostAuth)o;
2629            return mPort == that.mPort
2630                    && mFlags == that.mFlags
2631                    && Utility.areStringsEqual(mProtocol, that.mProtocol)
2632                    && Utility.areStringsEqual(mAddress, that.mAddress)
2633                    && Utility.areStringsEqual(mLogin, that.mLogin)
2634                    && Utility.areStringsEqual(mPassword, that.mPassword)
2635                    && Utility.areStringsEqual(mDomain, that.mDomain);
2636        }
2637    }
2638
2639    public interface PolicyColumns {
2640        public static final String ID = "_id";
2641        public static final String PASSWORD_MODE = "passwordMode";
2642        public static final String PASSWORD_MIN_LENGTH = "passwordMinLength";
2643        public static final String PASSWORD_EXPIRATION_DAYS = "passwordExpirationDays";
2644        public static final String PASSWORD_HISTORY = "passwordHistory";
2645        public static final String PASSWORD_COMPLEX_CHARS = "passwordComplexChars";
2646        public static final String PASSWORD_MAX_FAILS = "passwordMaxFails";
2647        public static final String MAX_SCREEN_LOCK_TIME = "maxScreenLockTime";
2648        public static final String REQUIRE_REMOTE_WIPE = "requireRemoteWipe";
2649        public static final String REQUIRE_ENCRYPTION = "requireEncryption";
2650        public static final String REQUIRE_ENCRYPTION_EXTERNAL = "requireEncryptionExternal";
2651        // ICS additions
2652        // Note: the appearance of these columns does not imply that we support these features; only
2653        // that we store them in the Policy structure
2654        public static final String REQUIRE_MANUAL_SYNC_WHEN_ROAMING = "requireManualSyncRoaming";
2655        public static final String DONT_ALLOW_CAMERA = "dontAllowCamera";
2656        public static final String DONT_ALLOW_ATTACHMENTS = "dontAllowAttachments";
2657        public static final String DONT_ALLOW_HTML = "dontAllowHtml";
2658        public static final String MAX_ATTACHMENT_SIZE = "maxAttachmentSize";
2659        public static final String MAX_TEXT_TRUNCATION_SIZE = "maxTextTruncationSize";
2660        public static final String MAX_HTML_TRUNCATION_SIZE = "maxHTMLTruncationSize";
2661        public static final String MAX_EMAIL_LOOKBACK = "maxEmailLookback";
2662        public static final String MAX_CALENDAR_LOOKBACK = "maxCalendarLookback";
2663        // Indicates that the server allows password recovery, not that we support it
2664        public static final String PASSWORD_RECOVERY_ENABLED = "passwordRecoveryEnabled";
2665    }
2666}
2667