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