EmailContent.java revision fa1b3a8f37eada5efad690b7abd32ae248aa2f2b
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 an account
1101        public transient HostAuth mHostAuthRecv;
1102        public transient HostAuth mHostAuthSend;
1103        public transient Policy mPolicy;
1104
1105        public static final int CONTENT_ID_COLUMN = 0;
1106        public static final int CONTENT_DISPLAY_NAME_COLUMN = 1;
1107        public static final int CONTENT_EMAIL_ADDRESS_COLUMN = 2;
1108        public static final int CONTENT_SYNC_KEY_COLUMN = 3;
1109        public static final int CONTENT_SYNC_LOOKBACK_COLUMN = 4;
1110        public static final int CONTENT_SYNC_INTERVAL_COLUMN = 5;
1111        public static final int CONTENT_HOST_AUTH_KEY_RECV_COLUMN = 6;
1112        public static final int CONTENT_HOST_AUTH_KEY_SEND_COLUMN = 7;
1113        public static final int CONTENT_FLAGS_COLUMN = 8;
1114        public static final int CONTENT_IS_DEFAULT_COLUMN = 9;
1115        public static final int CONTENT_COMPATIBILITY_UUID_COLUMN = 10;
1116        public static final int CONTENT_SENDER_NAME_COLUMN = 11;
1117        public static final int CONTENT_RINGTONE_URI_COLUMN = 12;
1118        public static final int CONTENT_PROTOCOL_VERSION_COLUMN = 13;
1119        public static final int CONTENT_NEW_MESSAGE_COUNT_COLUMN = 14;
1120        public static final int CONTENT_SECURITY_SYNC_KEY_COLUMN = 15;
1121        public static final int CONTENT_SIGNATURE_COLUMN = 16;
1122        public static final int CONTENT_POLICY_KEY = 17;
1123
1124        public static final String[] CONTENT_PROJECTION = new String[] {
1125            RECORD_ID, AccountColumns.DISPLAY_NAME,
1126            AccountColumns.EMAIL_ADDRESS, AccountColumns.SYNC_KEY, AccountColumns.SYNC_LOOKBACK,
1127            AccountColumns.SYNC_INTERVAL, AccountColumns.HOST_AUTH_KEY_RECV,
1128            AccountColumns.HOST_AUTH_KEY_SEND, AccountColumns.FLAGS, AccountColumns.IS_DEFAULT,
1129            AccountColumns.COMPATIBILITY_UUID, AccountColumns.SENDER_NAME,
1130            AccountColumns.RINGTONE_URI, AccountColumns.PROTOCOL_VERSION,
1131            AccountColumns.NEW_MESSAGE_COUNT, AccountColumns.SECURITY_SYNC_KEY,
1132            AccountColumns.SIGNATURE, AccountColumns.POLICY_KEY
1133        };
1134
1135        public static final int CONTENT_MAILBOX_TYPE_COLUMN = 1;
1136
1137        /**
1138         * This projection is for listing account id's only
1139         */
1140        public static final String[] ID_TYPE_PROJECTION = new String[] {
1141            RECORD_ID, MailboxColumns.TYPE
1142        };
1143
1144        public static final int ACCOUNT_FLAGS_COLUMN_ID = 0;
1145        public static final int ACCOUNT_FLAGS_COLUMN_FLAGS = 1;
1146        public static final String[] ACCOUNT_FLAGS_PROJECTION = new String[] {
1147                AccountColumns.ID, AccountColumns.FLAGS};
1148
1149        public static final String MAILBOX_SELECTION =
1150            MessageColumns.MAILBOX_KEY + " =?";
1151
1152        public static final String UNREAD_COUNT_SELECTION =
1153            MessageColumns.MAILBOX_KEY + " =? and " + MessageColumns.FLAG_READ + "= 0";
1154
1155        private static final String UUID_SELECTION = AccountColumns.COMPATIBILITY_UUID + " =?";
1156
1157        public static final String SECURITY_NONZERO_SELECTION =
1158            Account.POLICY_KEY + " IS NOT NULL AND " + Account.POLICY_KEY + "!=0";
1159
1160        private static final String FIND_INBOX_SELECTION =
1161                MailboxColumns.TYPE + " = " + Mailbox.TYPE_INBOX +
1162                " AND " + MailboxColumns.ACCOUNT_KEY + " =?";
1163
1164        /**
1165         * This projection is for searching for the default account
1166         */
1167        private static final String[] DEFAULT_ID_PROJECTION = new String[] {
1168            RECORD_ID, IS_DEFAULT
1169        };
1170
1171        /**
1172         * no public constructor since this is a utility class
1173         */
1174        public Account() {
1175            mBaseUri = CONTENT_URI;
1176
1177            // other defaults (policy)
1178            mRingtoneUri = "content://settings/system/notification_sound";
1179            mSyncInterval = -1;
1180            mSyncLookback = -1;
1181            mFlags = FLAGS_NOTIFY_NEW_MAIL;
1182            mCompatibilityUuid = UUID.randomUUID().toString();
1183        }
1184
1185        public static Account restoreAccountWithId(Context context, long id) {
1186            return EmailContent.restoreContentWithId(context, Account.class,
1187                    Account.CONTENT_URI, Account.CONTENT_PROJECTION, id);
1188        }
1189
1190        /**
1191         * Returns {@code true} if the given account ID is a "normal" account. Normal accounts
1192         * always have an ID greater than {@code 0} and not equal to any pseudo account IDs
1193         * (such as {@link #ACCOUNT_ID_COMBINED_VIEW})
1194         */
1195        public static boolean isNormalAccount(long accountId) {
1196            return (accountId > 0L) && (accountId != ACCOUNT_ID_COMBINED_VIEW);
1197        }
1198
1199        /**
1200         * Refresh an account that has already been loaded.  This is slightly less expensive
1201         * that generating a brand-new account object.
1202         */
1203        public void refresh(Context context) {
1204            Cursor c = context.getContentResolver().query(getUri(), Account.CONTENT_PROJECTION,
1205                    null, null, null);
1206            try {
1207                c.moveToFirst();
1208                restore(c);
1209            } finally {
1210                if (c != null) {
1211                    c.close();
1212                }
1213            }
1214        }
1215
1216        @Override
1217        public void restore(Cursor cursor) {
1218            mId = cursor.getLong(CONTENT_ID_COLUMN);
1219            mBaseUri = CONTENT_URI;
1220            mDisplayName = cursor.getString(CONTENT_DISPLAY_NAME_COLUMN);
1221            mEmailAddress = cursor.getString(CONTENT_EMAIL_ADDRESS_COLUMN);
1222            mSyncKey = cursor.getString(CONTENT_SYNC_KEY_COLUMN);
1223            mSyncLookback = cursor.getInt(CONTENT_SYNC_LOOKBACK_COLUMN);
1224            mSyncInterval = cursor.getInt(CONTENT_SYNC_INTERVAL_COLUMN);
1225            mHostAuthKeyRecv = cursor.getLong(CONTENT_HOST_AUTH_KEY_RECV_COLUMN);
1226            mHostAuthKeySend = cursor.getLong(CONTENT_HOST_AUTH_KEY_SEND_COLUMN);
1227            mFlags = cursor.getInt(CONTENT_FLAGS_COLUMN);
1228            mIsDefault = cursor.getInt(CONTENT_IS_DEFAULT_COLUMN) == 1;
1229            mCompatibilityUuid = cursor.getString(CONTENT_COMPATIBILITY_UUID_COLUMN);
1230            mSenderName = cursor.getString(CONTENT_SENDER_NAME_COLUMN);
1231            mRingtoneUri = cursor.getString(CONTENT_RINGTONE_URI_COLUMN);
1232            mProtocolVersion = cursor.getString(CONTENT_PROTOCOL_VERSION_COLUMN);
1233            mNewMessageCount = cursor.getInt(CONTENT_NEW_MESSAGE_COUNT_COLUMN);
1234            mSecuritySyncKey = cursor.getString(CONTENT_SECURITY_SYNC_KEY_COLUMN);
1235            mSignature = cursor.getString(CONTENT_SIGNATURE_COLUMN);
1236            mPolicyKey = cursor.getLong(CONTENT_POLICY_KEY);
1237        }
1238
1239        private long getId(Uri u) {
1240            return Long.parseLong(u.getPathSegments().get(1));
1241        }
1242
1243        /**
1244         * @return the user-visible name for the account
1245         */
1246        public String getDisplayName() {
1247            return mDisplayName;
1248        }
1249
1250        /**
1251         * Set the description.  Be sure to call save() to commit to database.
1252         * @param description the new description
1253         */
1254        public void setDisplayName(String description) {
1255            mDisplayName = description;
1256        }
1257
1258        /**
1259         * @return the email address for this account
1260         */
1261        public String getEmailAddress() {
1262            return mEmailAddress;
1263        }
1264
1265        /**
1266         * Set the Email address for this account.  Be sure to call save() to commit to database.
1267         * @param emailAddress the new email address for this account
1268         */
1269        public void setEmailAddress(String emailAddress) {
1270            mEmailAddress = emailAddress;
1271        }
1272
1273        /**
1274         * @return the sender's name for this account
1275         */
1276        public String getSenderName() {
1277            return mSenderName;
1278        }
1279
1280        /**
1281         * Set the sender's name.  Be sure to call save() to commit to database.
1282         * @param name the new sender name
1283         */
1284        public void setSenderName(String name) {
1285            mSenderName = name;
1286        }
1287
1288        public String getSignature() {
1289            return mSignature;
1290        }
1291
1292        public void setSignature(String signature) {
1293            mSignature = signature;
1294        }
1295
1296        /**
1297         * @return the minutes per check (for polling)
1298         * TODO define sentinel values for "never", "push", etc.  See Account.java
1299         */
1300        public int getSyncInterval() {
1301            return mSyncInterval;
1302        }
1303
1304        /**
1305         * Set the minutes per check (for polling).  Be sure to call save() to commit to database.
1306         * TODO define sentinel values for "never", "push", etc.  See Account.java
1307         * @param minutes the number of minutes between polling checks
1308         */
1309        public void setSyncInterval(int minutes) {
1310            mSyncInterval = minutes;
1311        }
1312
1313        /**
1314         * @return One of the {@code Account.SYNC_WINDOW_*} constants that represents the sync
1315         *     lookback window.
1316         * TODO define sentinel values for "all", "1 month", etc.  See Account.java
1317         */
1318        public int getSyncLookback() {
1319            return mSyncLookback;
1320        }
1321
1322        /**
1323         * Set the sync lookback window.  Be sure to call save() to commit to database.
1324         * TODO define sentinel values for "all", "1 month", etc.  See Account.java
1325         * @param value One of the {@code Account.SYNC_WINDOW_*} constants
1326         */
1327        public void setSyncLookback(int value) {
1328            mSyncLookback = value;
1329        }
1330
1331        /**
1332         * @return the flags for this account
1333         * @see #FLAGS_NOTIFY_NEW_MAIL
1334         * @see #FLAGS_VIBRATE_ALWAYS
1335         * @see #FLAGS_VIBRATE_WHEN_SILENT
1336         */
1337        public int getFlags() {
1338            return mFlags;
1339        }
1340
1341        /**
1342         * Set the flags for this account
1343         * @see #FLAGS_NOTIFY_NEW_MAIL
1344         * @see #FLAGS_VIBRATE_ALWAYS
1345         * @see #FLAGS_VIBRATE_WHEN_SILENT
1346         * @param newFlags the new value for the flags
1347         */
1348        public void setFlags(int newFlags) {
1349            mFlags = newFlags;
1350        }
1351
1352        /**
1353         * @return the ringtone Uri for this account
1354         */
1355        public String getRingtone() {
1356            return mRingtoneUri;
1357        }
1358
1359        /**
1360         * Set the ringtone Uri for this account
1361         * @param newUri the new URI string for the ringtone for this account
1362         */
1363        public void setRingtone(String newUri) {
1364            mRingtoneUri = newUri;
1365        }
1366
1367        /**
1368         * Set the "delete policy" as a simple 0,1,2 value set.
1369         * @param newPolicy the new delete policy
1370         */
1371        public void setDeletePolicy(int newPolicy) {
1372            mFlags &= ~FLAGS_DELETE_POLICY_MASK;
1373            mFlags |= (newPolicy << FLAGS_DELETE_POLICY_SHIFT) & FLAGS_DELETE_POLICY_MASK;
1374        }
1375
1376        /**
1377         * Return the "delete policy" as a simple 0,1,2 value set.
1378         * @return the current delete policy
1379         */
1380        public int getDeletePolicy() {
1381            return (mFlags & FLAGS_DELETE_POLICY_MASK) >> FLAGS_DELETE_POLICY_SHIFT;
1382        }
1383
1384        /**
1385         * Return the Uuid associated with this account.  This is primarily for compatibility
1386         * with accounts set up by previous versions, because there are externals references
1387         * to the Uuid (e.g. desktop shortcuts).
1388         */
1389        public String getUuid() {
1390            return mCompatibilityUuid;
1391        }
1392
1393        /**
1394         * For compatibility while converting to provider model, generate a "store URI"
1395         *
1396         * @return a string in the form of a Uri, as used by the other parts of the email app
1397         */
1398        public String getStoreUri(Context context) {
1399            // reconstitute if necessary
1400            if (mHostAuthRecv == null) {
1401                mHostAuthRecv = HostAuth.restoreHostAuthWithId(context, mHostAuthKeyRecv);
1402            }
1403            // convert if available
1404            if (mHostAuthRecv != null) {
1405                String storeUri = mHostAuthRecv.getStoreUri();
1406                if (storeUri != null) {
1407                    return storeUri;
1408                }
1409            }
1410            return "";
1411        }
1412
1413        /**
1414         * For compatibility while converting to provider model, generate a "sender URI"
1415         *
1416         * @return a string in the form of a Uri, as used by the other parts of the email app
1417         */
1418        public String getSenderUri(Context context) {
1419            // reconstitute if necessary
1420            if (mHostAuthSend == null) {
1421                mHostAuthSend = HostAuth.restoreHostAuthWithId(context, mHostAuthKeySend);
1422            }
1423            // convert if available
1424            if (mHostAuthSend != null) {
1425                String senderUri = mHostAuthSend.getStoreUri();
1426                if (senderUri != null) {
1427                    return senderUri;
1428                }
1429            }
1430            return "";
1431        }
1432
1433        public HostAuth getOrCreateHostAuthSend(Context context) {
1434            if (mHostAuthSend == null) {
1435                if (mHostAuthKeySend != 0) {
1436                    mHostAuthSend = HostAuth.restoreHostAuthWithId(context, mHostAuthKeySend);
1437                } else {
1438                    mHostAuthSend = new HostAuth();
1439                }
1440            }
1441            return mHostAuthSend;
1442        }
1443
1444        public HostAuth getOrCreateHostAuthRecv(Context context) {
1445            if (mHostAuthRecv == null) {
1446                if (mHostAuthKeyRecv != 0) {
1447                    mHostAuthRecv = HostAuth.restoreHostAuthWithId(context, mHostAuthKeyRecv);
1448                } else {
1449                    mHostAuthRecv = new HostAuth();
1450                }
1451            }
1452            return mHostAuthRecv;
1453        }
1454
1455        /**
1456         * For compatibility while converting to provider model, generate a "local store URI"
1457         *
1458         * @return a string in the form of a Uri, as used by the other parts of the email app
1459         */
1460        public String getLocalStoreUri(Context context) {
1461            return "local://localhost/" + context.getDatabasePath(getUuid() + ".db");
1462        }
1463
1464        /**
1465         * @return true if the instance is of an EAS account.
1466         *
1467         * NOTE This method accesses the DB if {@link #mHostAuthRecv} hasn't been restored yet.
1468         * Use caution when you use this on the main thread.
1469         */
1470        public boolean isEasAccount(Context context) {
1471            return "eas".equals(getProtocol(context));
1472        }
1473
1474        /**
1475         * @return true if the account supports "move messages".
1476         */
1477        public static boolean supportsMoveMessages(Context context, long accountId) {
1478            String protocol = getProtocol(context, accountId);
1479            return "eas".equals(protocol) || "imap".equals(protocol);
1480        }
1481
1482        /**
1483         * Set the account to be the default account.  If this is set to "true", when the account
1484         * is saved, all other accounts will have the same value set to "false".
1485         * @param newDefaultState the new default state - if true, others will be cleared.
1486         */
1487        public void setDefaultAccount(boolean newDefaultState) {
1488            mIsDefault = newDefaultState;
1489        }
1490
1491        /**
1492         * Helper method for finding the default account.
1493         */
1494        static private long getDefaultAccountWhere(Context context, String where) {
1495            return Utility.getFirstRowLong(context, CONTENT_URI,
1496                    DEFAULT_ID_PROJECTION,
1497                    where, null, null, 0, Long.valueOf(-1));
1498        }
1499
1500        /**
1501         * @return {@link Uri} to this {@link Account} in the
1502         * {@code content://com.android.email.provider/account/UUID} format, which is safe to use
1503         * for desktop shortcuts.
1504         *
1505         * <p>We don't want to store _id in shortcuts, because
1506         * {@link com.android.email.provider.AccountBackupRestore} won't preserve it.
1507         */
1508        public Uri getShortcutSafeUri() {
1509            return getShortcutSafeUriFromUuid(mCompatibilityUuid);
1510        }
1511
1512        /**
1513         * @return {@link Uri} to an {@link Account} with a {@code uuid}.
1514         */
1515        public static Uri getShortcutSafeUriFromUuid(String uuid) {
1516            return CONTENT_URI.buildUpon().appendEncodedPath(uuid).build();
1517        }
1518
1519        /**
1520         * Parse {@link Uri} in the {@code content://com.android.email.provider/account/ID} format
1521         * where ID = account id (used on Eclair, Android 2.0-2.1) or UUID, and return _id of
1522         * the {@link Account} associated with it.
1523         *
1524         * @param context context to access DB
1525         * @param uri URI of interest
1526         * @return _id of the {@link Account} associated with ID, or -1 if none found.
1527         */
1528        public static long getAccountIdFromShortcutSafeUri(Context context, Uri uri) {
1529            // Make sure the URI is in the correct format.
1530            if (!"content".equals(uri.getScheme())
1531                    || !AUTHORITY.equals(uri.getAuthority())) {
1532                return -1;
1533            }
1534
1535            final List<String> ps = uri.getPathSegments();
1536            if (ps.size() != 2 || !"account".equals(ps.get(0))) {
1537                return -1;
1538            }
1539
1540            // Now get the ID part.
1541            final String id = ps.get(1);
1542
1543            // First, see if ID can be parsed as long.  (Eclair-style)
1544            // (UUIDs have '-' in them, so they are always non-parsable.)
1545            try {
1546                return Long.parseLong(id);
1547            } catch (NumberFormatException ok) {
1548                // OK, it's not a long.  Continue...
1549            }
1550
1551            // Now id is a UUId.
1552            return getAccountIdFromUuid(context, id);
1553        }
1554
1555        /**
1556         * @return ID of the account with the given UUID.
1557         */
1558        public static long getAccountIdFromUuid(Context context, String uuid) {
1559            return Utility.getFirstRowLong(context,
1560                    CONTENT_URI, ID_PROJECTION,
1561                    UUID_SELECTION, new String[] {uuid}, null, 0, -1L);
1562        }
1563
1564        /**
1565         * Return the id of the default account.  If one hasn't been explicitly specified, return
1566         * the first one in the database.  For any account saved in the DB, this must be used
1567         * to check for the default account - the mIsDefault field is set lazily and may be
1568         * incorrect.
1569         * @param context the caller's context
1570         * @return the id of the default account, or -1 if there are no accounts
1571         */
1572        static public long getDefaultAccountId(Context context) {
1573            long id = getDefaultAccountWhere(context, AccountColumns.IS_DEFAULT + "=1");
1574            if (id == -1) {
1575                id = getDefaultAccountWhere(context, null);
1576            }
1577            return id;
1578        }
1579
1580        /**
1581         * Given an account id, return the account's protocol
1582         * @param context the caller's context
1583         * @param accountId the id of the account to be examined
1584         * @return the account's protocol (or null if the Account or HostAuth do not exist)
1585         */
1586        public static String getProtocol(Context context, long accountId) {
1587            Account account = Account.restoreAccountWithId(context, accountId);
1588            if (account != null) {
1589                return account.getProtocol(context);
1590             }
1591            return null;
1592        }
1593
1594        /**
1595         * Return the account's protocol
1596         * @param context the caller's context
1597         * @return the account's protocol (or null if the HostAuth doesn't not exist)
1598         */
1599        public String getProtocol(Context context) {
1600            HostAuth hostAuth = HostAuth.restoreHostAuthWithId(context, mHostAuthKeyRecv);
1601            if (hostAuth != null) {
1602                return hostAuth.mProtocol;
1603            }
1604            return null;
1605        }
1606
1607        /**
1608         * Return the account ID for a message with a given id
1609         *
1610         * @param context the caller's context
1611         * @param messageId the id of the message
1612         * @return the account ID, or -1 if the account doesn't exist
1613         */
1614        public static long getAccountIdForMessageId(Context context, long messageId) {
1615            return Message.getKeyColumnLong(context, messageId, MessageColumns.ACCOUNT_KEY);
1616        }
1617
1618        /**
1619         * Return the account for a message with a given id
1620         * @param context the caller's context
1621         * @param messageId the id of the message
1622         * @return the account, or null if the account doesn't exist
1623         */
1624        public static Account getAccountForMessageId(Context context, long messageId) {
1625            long accountId = getAccountIdForMessageId(context, messageId);
1626            if (accountId != -1) {
1627                return Account.restoreAccountWithId(context, accountId);
1628            }
1629            return null;
1630        }
1631
1632        /**
1633         * @return true if an {@code accountId} is assigned to any existing account.
1634         */
1635        public static boolean isValidId(Context context, long accountId) {
1636            return null != Utility.getFirstRowLong(context, CONTENT_URI, ID_PROJECTION,
1637                    ID_SELECTION, new String[] {Long.toString(accountId)}, null,
1638                    ID_PROJECTION_COLUMN);
1639        }
1640
1641        /**
1642         * Check a single account for security hold status.
1643         */
1644        public static boolean isSecurityHold(Context context, long accountId) {
1645            return (Utility.getFirstRowLong(context,
1646                    ContentUris.withAppendedId(Account.CONTENT_URI, accountId),
1647                    ACCOUNT_FLAGS_PROJECTION, null, null, null, ACCOUNT_FLAGS_COLUMN_FLAGS, 0L)
1648                    & Account.FLAGS_SECURITY_HOLD) != 0;
1649        }
1650
1651        /**
1652         * @return id of the "inbox" mailbox, or -1 if not found.
1653         */
1654        public static long getInboxId(Context context, long accountId) {
1655            return Utility.getFirstRowLong(context, Mailbox.CONTENT_URI, ID_PROJECTION,
1656                    FIND_INBOX_SELECTION, new String[] {Long.toString(accountId)}, null,
1657                    ID_PROJECTION_COLUMN, -1L);
1658        }
1659
1660        /**
1661         * Clear all account hold flags that are set.
1662         *
1663         * (This will trigger watchers, and in particular will cause EAS to try and resync the
1664         * account(s).)
1665         */
1666        public static void clearSecurityHoldOnAllAccounts(Context context) {
1667            ContentResolver resolver = context.getContentResolver();
1668            Cursor c = resolver.query(Account.CONTENT_URI, ACCOUNT_FLAGS_PROJECTION,
1669                    SECURITY_NONZERO_SELECTION, null, null);
1670            try {
1671                while (c.moveToNext()) {
1672                    int flags = c.getInt(ACCOUNT_FLAGS_COLUMN_FLAGS);
1673
1674                    if (0 != (flags & FLAGS_SECURITY_HOLD)) {
1675                        ContentValues cv = new ContentValues();
1676                        cv.put(AccountColumns.FLAGS, flags & ~FLAGS_SECURITY_HOLD);
1677                        long accountId = c.getLong(ACCOUNT_FLAGS_COLUMN_ID);
1678                        Uri uri = ContentUris.withAppendedId(Account.CONTENT_URI, accountId);
1679                        resolver.update(uri, cv, null, null);
1680                    }
1681                }
1682            } finally {
1683                c.close();
1684            }
1685        }
1686
1687        /**
1688         * Override update to enforce a single default account, and do it atomically
1689         */
1690        @Override
1691        public int update(Context context, ContentValues cv) {
1692            if (mPolicy != null && mPolicyKey <= 0) {
1693                // If a policy is set and there's no policy, link it to the account
1694                Policy.setAccountPolicy(context, this, mPolicy, null);
1695            }
1696            if (cv.containsKey(AccountColumns.IS_DEFAULT) &&
1697                    cv.getAsBoolean(AccountColumns.IS_DEFAULT)) {
1698                ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
1699                ContentValues cv1 = new ContentValues();
1700                cv1.put(AccountColumns.IS_DEFAULT, false);
1701                // Clear the default flag in all accounts
1702                ops.add(ContentProviderOperation.newUpdate(CONTENT_URI).withValues(cv1).build());
1703                // Update this account
1704                ops.add(ContentProviderOperation
1705                        .newUpdate(ContentUris.withAppendedId(CONTENT_URI, mId))
1706                        .withValues(cv).build());
1707                try {
1708                    context.getContentResolver().applyBatch(AUTHORITY, ops);
1709                    return 1;
1710                } catch (RemoteException e) {
1711                    // There is nothing to be done here; fail by returning 0
1712                } catch (OperationApplicationException e) {
1713                    // There is nothing to be done here; fail by returning 0
1714                }
1715                return 0;
1716            }
1717            return super.update(context, cv);
1718        }
1719
1720        /*
1721         * Override this so that we can store the HostAuth's first and link them to the Account
1722         * (non-Javadoc)
1723         * @see com.android.email.provider.EmailContent#save(android.content.Context)
1724         */
1725        @Override
1726        public Uri save(Context context) {
1727            if (isSaved()) {
1728                throw new UnsupportedOperationException();
1729            }
1730            // This logic is in place so I can (a) short circuit the expensive stuff when
1731            // possible, and (b) override (and throw) if anyone tries to call save() or update()
1732            // directly for Account, which are unsupported.
1733            if (mHostAuthRecv == null && mHostAuthSend == null && mIsDefault == false &&
1734                    mPolicy != null) {
1735                return super.save(context);
1736            }
1737
1738            int index = 0;
1739            int recvIndex = -1;
1740            int sendIndex = -1;
1741            int policyIndex = -1;
1742
1743            // Create operations for saving the send and recv hostAuths
1744            // Also, remember which operation in the array they represent
1745            ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
1746            if (mHostAuthRecv != null) {
1747                recvIndex = index++;
1748                ops.add(ContentProviderOperation.newInsert(mHostAuthRecv.mBaseUri)
1749                        .withValues(mHostAuthRecv.toContentValues())
1750                        .build());
1751            }
1752            if (mHostAuthSend != null) {
1753                sendIndex = index++;
1754                ops.add(ContentProviderOperation.newInsert(mHostAuthSend.mBaseUri)
1755                        .withValues(mHostAuthSend.toContentValues())
1756                        .build());
1757            }
1758            if (mPolicy != null) {
1759                policyIndex = index++;
1760                ops.add(ContentProviderOperation.newInsert(mPolicy.mBaseUri)
1761                        .withValues(mPolicy.toContentValues())
1762                        .build());
1763            }
1764
1765            // Create operations for making this the only default account
1766            // Note, these are always updates because they change existing accounts
1767            if (mIsDefault) {
1768                index++;
1769                ContentValues cv1 = new ContentValues();
1770                cv1.put(AccountColumns.IS_DEFAULT, 0);
1771                ops.add(ContentProviderOperation.newUpdate(CONTENT_URI).withValues(cv1).build());
1772            }
1773
1774            // Now do the Account
1775            ContentValues cv = null;
1776            if (recvIndex >= 0 || sendIndex >= 0) {
1777                cv = new ContentValues();
1778                if (recvIndex >= 0) {
1779                    cv.put(Account.HOST_AUTH_KEY_RECV, recvIndex);
1780                }
1781                if (sendIndex >= 0) {
1782                    cv.put(Account.HOST_AUTH_KEY_SEND, sendIndex);
1783                }
1784                if (policyIndex >= 0) {
1785                    cv.put(Account.POLICY_KEY, policyIndex);
1786                }
1787            }
1788
1789            ContentProviderOperation.Builder b = ContentProviderOperation.newInsert(mBaseUri);
1790            b.withValues(toContentValues());
1791            if (cv != null) {
1792                b.withValueBackReferences(cv);
1793            }
1794            ops.add(b.build());
1795
1796            try {
1797                ContentProviderResult[] results =
1798                    context.getContentResolver().applyBatch(AUTHORITY, ops);
1799                // If saving, set the mId's of the various saved objects
1800                if (recvIndex >= 0) {
1801                    long newId = getId(results[recvIndex].uri);
1802                    mHostAuthKeyRecv = newId;
1803                    mHostAuthRecv.mId = newId;
1804                }
1805                if (sendIndex >= 0) {
1806                    long newId = getId(results[sendIndex].uri);
1807                    mHostAuthKeySend = newId;
1808                    mHostAuthSend.mId = newId;
1809                }
1810                if (policyIndex >= 0) {
1811                    long newId = getId(results[policyIndex].uri);
1812                    mPolicyKey = newId;
1813                    mPolicy.mId = newId;
1814                }
1815                Uri u = results[index].uri;
1816                mId = getId(u);
1817                return u;
1818            } catch (RemoteException e) {
1819                // There is nothing to be done here; fail by returning null
1820            } catch (OperationApplicationException e) {
1821                // There is nothing to be done here; fail by returning null
1822            }
1823            return null;
1824        }
1825
1826        @Override
1827        public ContentValues toContentValues() {
1828            ContentValues values = new ContentValues();
1829            values.put(AccountColumns.DISPLAY_NAME, mDisplayName);
1830            values.put(AccountColumns.EMAIL_ADDRESS, mEmailAddress);
1831            values.put(AccountColumns.SYNC_KEY, mSyncKey);
1832            values.put(AccountColumns.SYNC_LOOKBACK, mSyncLookback);
1833            values.put(AccountColumns.SYNC_INTERVAL, mSyncInterval);
1834            values.put(AccountColumns.HOST_AUTH_KEY_RECV, mHostAuthKeyRecv);
1835            values.put(AccountColumns.HOST_AUTH_KEY_SEND, mHostAuthKeySend);
1836            values.put(AccountColumns.FLAGS, mFlags);
1837            values.put(AccountColumns.IS_DEFAULT, mIsDefault);
1838            values.put(AccountColumns.COMPATIBILITY_UUID, mCompatibilityUuid);
1839            values.put(AccountColumns.SENDER_NAME, mSenderName);
1840            values.put(AccountColumns.RINGTONE_URI, mRingtoneUri);
1841            values.put(AccountColumns.PROTOCOL_VERSION, mProtocolVersion);
1842            values.put(AccountColumns.NEW_MESSAGE_COUNT, mNewMessageCount);
1843            values.put(AccountColumns.SECURITY_SYNC_KEY, mSecuritySyncKey);
1844            values.put(AccountColumns.SIGNATURE, mSignature);
1845            values.put(AccountColumns.POLICY_KEY, mPolicyKey);
1846            return values;
1847        }
1848
1849        /**
1850         * Supports Parcelable
1851         */
1852        @Override
1853        public int describeContents() {
1854            return 0;
1855        }
1856
1857        /**
1858         * Supports Parcelable
1859         */
1860        public static final Parcelable.Creator<EmailContent.Account> CREATOR
1861                = new Parcelable.Creator<EmailContent.Account>() {
1862            @Override
1863            public EmailContent.Account createFromParcel(Parcel in) {
1864                return new EmailContent.Account(in);
1865            }
1866
1867            @Override
1868            public EmailContent.Account[] newArray(int size) {
1869                return new EmailContent.Account[size];
1870            }
1871        };
1872
1873        /**
1874         * Supports Parcelable
1875         */
1876        @Override
1877        public void writeToParcel(Parcel dest, int flags) {
1878            // mBaseUri is not parceled
1879            dest.writeLong(mId);
1880            dest.writeString(mDisplayName);
1881            dest.writeString(mEmailAddress);
1882            dest.writeString(mSyncKey);
1883            dest.writeInt(mSyncLookback);
1884            dest.writeInt(mSyncInterval);
1885            dest.writeLong(mHostAuthKeyRecv);
1886            dest.writeLong(mHostAuthKeySend);
1887            dest.writeInt(mFlags);
1888            dest.writeByte(mIsDefault ? (byte)1 : (byte)0);
1889            dest.writeString(mCompatibilityUuid);
1890            dest.writeString(mSenderName);
1891            dest.writeString(mRingtoneUri);
1892            dest.writeString(mProtocolVersion);
1893            dest.writeInt(mNewMessageCount);
1894            dest.writeString(mSecuritySyncKey);
1895            dest.writeString(mSignature);
1896            dest.writeLong(mPolicyKey);
1897
1898            if (mHostAuthRecv != null) {
1899                dest.writeByte((byte)1);
1900                mHostAuthRecv.writeToParcel(dest, flags);
1901            } else {
1902                dest.writeByte((byte)0);
1903            }
1904
1905            if (mHostAuthSend != null) {
1906                dest.writeByte((byte)1);
1907                mHostAuthSend.writeToParcel(dest, flags);
1908            } else {
1909                dest.writeByte((byte)0);
1910            }
1911        }
1912
1913        /**
1914         * Supports Parcelable
1915         */
1916        public Account(Parcel in) {
1917            mBaseUri = EmailContent.Account.CONTENT_URI;
1918            mId = in.readLong();
1919            mDisplayName = in.readString();
1920            mEmailAddress = in.readString();
1921            mSyncKey = in.readString();
1922            mSyncLookback = in.readInt();
1923            mSyncInterval = in.readInt();
1924            mHostAuthKeyRecv = in.readLong();
1925            mHostAuthKeySend = in.readLong();
1926            mFlags = in.readInt();
1927            mIsDefault = in.readByte() == 1;
1928            mCompatibilityUuid = in.readString();
1929            mSenderName = in.readString();
1930            mRingtoneUri = in.readString();
1931            mProtocolVersion = in.readString();
1932            mNewMessageCount = in.readInt();
1933            mSecuritySyncKey = in.readString();
1934            mSignature = in.readString();
1935            mPolicyKey = in.readLong();
1936
1937            mHostAuthRecv = null;
1938            if (in.readByte() == 1) {
1939                mHostAuthRecv = new HostAuth(in);
1940            }
1941
1942            mHostAuthSend = null;
1943            if (in.readByte() == 1) {
1944                mHostAuthSend = new HostAuth(in);
1945            }
1946        }
1947
1948        /**
1949         * For debugger support only - DO NOT use for code.
1950         */
1951        @Override
1952        public String toString() {
1953            StringBuilder sb = new StringBuilder('[');
1954            if (mHostAuthRecv != null && mHostAuthRecv.mProtocol != null) {
1955                sb.append(mHostAuthRecv.mProtocol);
1956                sb.append(':');
1957            }
1958            if (mDisplayName != null)   sb.append(mDisplayName);
1959            sb.append(':');
1960            if (mEmailAddress != null)  sb.append(mEmailAddress);
1961            sb.append(':');
1962            if (mSenderName != null)    sb.append(mSenderName);
1963            sb.append(']');
1964            return sb.toString();
1965        }
1966
1967    }
1968
1969    public interface AttachmentColumns {
1970        public static final String ID = "_id";
1971        // The display name of the attachment
1972        public static final String FILENAME = "fileName";
1973        // The mime type of the attachment
1974        public static final String MIME_TYPE = "mimeType";
1975        // The size of the attachment in bytes
1976        public static final String SIZE = "size";
1977        // The (internal) contentId of the attachment (inline attachments will have these)
1978        public static final String CONTENT_ID = "contentId";
1979        // The location of the loaded attachment (probably a file)
1980        @SuppressWarnings("hiding")
1981        public static final String CONTENT_URI = "contentUri";
1982        // A foreign key into the Message table (the message owning this attachment)
1983        public static final String MESSAGE_KEY = "messageKey";
1984        // The location of the attachment on the server side
1985        // For IMAP, this is a part number (e.g. 2.1); for EAS, it's the internal file name
1986        public static final String LOCATION = "location";
1987        // The transfer encoding of the attachment
1988        public static final String ENCODING = "encoding";
1989        // Not currently used
1990        public static final String CONTENT = "content";
1991        // Flags
1992        public static final String FLAGS = "flags";
1993        // Content that is actually contained in the Attachment row
1994        public static final String CONTENT_BYTES = "content_bytes";
1995        // A foreign key into the Account table (for the message owning this attachment)
1996        public static final String ACCOUNT_KEY = "accountKey";
1997    }
1998
1999    public static final class Attachment extends EmailContent
2000            implements AttachmentColumns, Parcelable {
2001        public static final String TABLE_NAME = "Attachment";
2002        @SuppressWarnings("hiding")
2003        public static final Uri CONTENT_URI = Uri.parse(EmailContent.CONTENT_URI + "/attachment");
2004        // This must be used with an appended id: ContentUris.withAppendedId(MESSAGE_ID_URI, id)
2005        public static final Uri MESSAGE_ID_URI = Uri.parse(
2006                EmailContent.CONTENT_URI + "/attachment/message");
2007
2008        public String mFileName;
2009        public String mMimeType;
2010        public long mSize;
2011        public String mContentId;
2012        public String mContentUri;
2013        public long mMessageKey;
2014        public String mLocation;
2015        public String mEncoding;
2016        public String mContent; // Not currently used
2017        public int mFlags;
2018        public byte[] mContentBytes;
2019        public long mAccountKey;
2020
2021        public static final int CONTENT_ID_COLUMN = 0;
2022        public static final int CONTENT_FILENAME_COLUMN = 1;
2023        public static final int CONTENT_MIME_TYPE_COLUMN = 2;
2024        public static final int CONTENT_SIZE_COLUMN = 3;
2025        public static final int CONTENT_CONTENT_ID_COLUMN = 4;
2026        public static final int CONTENT_CONTENT_URI_COLUMN = 5;
2027        public static final int CONTENT_MESSAGE_ID_COLUMN = 6;
2028        public static final int CONTENT_LOCATION_COLUMN = 7;
2029        public static final int CONTENT_ENCODING_COLUMN = 8;
2030        public static final int CONTENT_CONTENT_COLUMN = 9; // Not currently used
2031        public static final int CONTENT_FLAGS_COLUMN = 10;
2032        public static final int CONTENT_CONTENT_BYTES_COLUMN = 11;
2033        public static final int CONTENT_ACCOUNT_KEY_COLUMN = 12;
2034        public static final String[] CONTENT_PROJECTION = new String[] {
2035            RECORD_ID, AttachmentColumns.FILENAME, AttachmentColumns.MIME_TYPE,
2036            AttachmentColumns.SIZE, AttachmentColumns.CONTENT_ID, AttachmentColumns.CONTENT_URI,
2037            AttachmentColumns.MESSAGE_KEY, AttachmentColumns.LOCATION, AttachmentColumns.ENCODING,
2038            AttachmentColumns.CONTENT, AttachmentColumns.FLAGS, AttachmentColumns.CONTENT_BYTES,
2039            AttachmentColumns.ACCOUNT_KEY
2040        };
2041
2042        // All attachments with an empty URI, regardless of mailbox
2043        public static final String PRECACHE_SELECTION =
2044            AttachmentColumns.CONTENT_URI + " isnull AND " + Attachment.FLAGS + "=0";
2045        // Attachments with an empty URI that are in an inbox
2046        public static final String PRECACHE_INBOX_SELECTION =
2047            PRECACHE_SELECTION + " AND " + AttachmentColumns.MESSAGE_KEY + " IN ("
2048            +     "SELECT " + MessageColumns.ID + " FROM " + Message.TABLE_NAME
2049            +     " WHERE " + Message.ALL_INBOX_SELECTION
2050            +     ")";
2051
2052        // Bits used in mFlags
2053        // WARNING: AttachmentDownloadService relies on the fact that ALL of the flags below
2054        // disqualify attachments for precaching.  If you add a flag that does NOT disqualify an
2055        // attachment for precaching, you MUST change the PRECACHE_SELECTION definition above
2056
2057        // Instruct Rfc822Output to 1) not use Content-Disposition and 2) use multipart/alternative
2058        // with this attachment.  This is only valid if there is one and only one attachment and
2059        // that attachment has this flag set
2060        public static final int FLAG_ICS_ALTERNATIVE_PART = 1<<0;
2061        // Indicate that this attachment has been requested for downloading by the user; this is
2062        // the highest priority for attachment downloading
2063        public static final int FLAG_DOWNLOAD_USER_REQUEST = 1<<1;
2064        // Indicate that this attachment needs to be downloaded as part of an outgoing forwarded
2065        // message
2066        public static final int FLAG_DOWNLOAD_FORWARD = 1<<2;
2067        // Indicates that the attachment download failed in a non-recoverable manner
2068        public static final int FLAG_DOWNLOAD_FAILED = 1<<3;
2069        // Allow "room" for some additional download-related flags here
2070        // Indicates that the attachment will be smart-forwarded
2071        public static final int FLAG_SMART_FORWARD = 1<<8;
2072        // Indicates that the attachment cannot be forwarded due to a policy restriction
2073        public static final int FLAG_POLICY_DISALLOWS_DOWNLOAD = 1<<9;
2074        /**
2075         * no public constructor since this is a utility class
2076         */
2077        public Attachment() {
2078            mBaseUri = CONTENT_URI;
2079        }
2080
2081         /**
2082         * Restore an Attachment from the database, given its unique id
2083         * @param context
2084         * @param id
2085         * @return the instantiated Attachment
2086         */
2087        public static Attachment restoreAttachmentWithId(Context context, long id) {
2088            return EmailContent.restoreContentWithId(context, Attachment.class,
2089                    Attachment.CONTENT_URI, Attachment.CONTENT_PROJECTION, id);
2090        }
2091
2092        /**
2093         * Restore all the Attachments of a message given its messageId
2094         */
2095        public static Attachment[] restoreAttachmentsWithMessageId(Context context,
2096                long messageId) {
2097            Uri uri = ContentUris.withAppendedId(MESSAGE_ID_URI, messageId);
2098            Cursor c = context.getContentResolver().query(uri, CONTENT_PROJECTION,
2099                    null, null, null);
2100            try {
2101                int count = c.getCount();
2102                Attachment[] attachments = new Attachment[count];
2103                for (int i = 0; i < count; ++i) {
2104                    c.moveToNext();
2105                    Attachment attach = new Attachment();
2106                    attach.restore(c);
2107                    attachments[i] = attach;
2108                }
2109                return attachments;
2110            } finally {
2111                c.close();
2112            }
2113        }
2114
2115        /**
2116         * Creates a unique file in the external store by appending a hyphen
2117         * and a number to the given filename.
2118         * @param filename
2119         * @return a new File object, or null if one could not be created
2120         */
2121        public static File createUniqueFile(String filename) {
2122            // TODO Handle internal storage, as required
2123            if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
2124                File directory = Environment.getExternalStorageDirectory();
2125                File file = new File(directory, filename);
2126                if (!file.exists()) {
2127                    return file;
2128                }
2129                // Get the extension of the file, if any.
2130                int index = filename.lastIndexOf('.');
2131                String name = filename;
2132                String extension = "";
2133                if (index != -1) {
2134                    name = filename.substring(0, index);
2135                    extension = filename.substring(index);
2136                }
2137                for (int i = 2; i < Integer.MAX_VALUE; i++) {
2138                    file = new File(directory, name + '-' + i + extension);
2139                    if (!file.exists()) {
2140                        return file;
2141                    }
2142                }
2143                return null;
2144            }
2145            return null;
2146        }
2147
2148        @Override
2149        public void restore(Cursor cursor) {
2150            mBaseUri = CONTENT_URI;
2151            mId = cursor.getLong(CONTENT_ID_COLUMN);
2152            mFileName= cursor.getString(CONTENT_FILENAME_COLUMN);
2153            mMimeType = cursor.getString(CONTENT_MIME_TYPE_COLUMN);
2154            mSize = cursor.getLong(CONTENT_SIZE_COLUMN);
2155            mContentId = cursor.getString(CONTENT_CONTENT_ID_COLUMN);
2156            mContentUri = cursor.getString(CONTENT_CONTENT_URI_COLUMN);
2157            mMessageKey = cursor.getLong(CONTENT_MESSAGE_ID_COLUMN);
2158            mLocation = cursor.getString(CONTENT_LOCATION_COLUMN);
2159            mEncoding = cursor.getString(CONTENT_ENCODING_COLUMN);
2160            mContent = cursor.getString(CONTENT_CONTENT_COLUMN);
2161            mFlags = cursor.getInt(CONTENT_FLAGS_COLUMN);
2162            mContentBytes = cursor.getBlob(CONTENT_CONTENT_BYTES_COLUMN);
2163            mAccountKey = cursor.getLong(CONTENT_ACCOUNT_KEY_COLUMN);
2164        }
2165
2166        @Override
2167        public ContentValues toContentValues() {
2168            ContentValues values = new ContentValues();
2169            values.put(AttachmentColumns.FILENAME, mFileName);
2170            values.put(AttachmentColumns.MIME_TYPE, mMimeType);
2171            values.put(AttachmentColumns.SIZE, mSize);
2172            values.put(AttachmentColumns.CONTENT_ID, mContentId);
2173            values.put(AttachmentColumns.CONTENT_URI, mContentUri);
2174            values.put(AttachmentColumns.MESSAGE_KEY, mMessageKey);
2175            values.put(AttachmentColumns.LOCATION, mLocation);
2176            values.put(AttachmentColumns.ENCODING, mEncoding);
2177            values.put(AttachmentColumns.CONTENT, mContent);
2178            values.put(AttachmentColumns.FLAGS, mFlags);
2179            values.put(AttachmentColumns.CONTENT_BYTES, mContentBytes);
2180            values.put(AttachmentColumns.ACCOUNT_KEY, mAccountKey);
2181            return values;
2182        }
2183
2184        @Override
2185        public int describeContents() {
2186             return 0;
2187        }
2188
2189        @Override
2190        public void writeToParcel(Parcel dest, int flags) {
2191            // mBaseUri is not parceled
2192            dest.writeLong(mId);
2193            dest.writeString(mFileName);
2194            dest.writeString(mMimeType);
2195            dest.writeLong(mSize);
2196            dest.writeString(mContentId);
2197            dest.writeString(mContentUri);
2198            dest.writeLong(mMessageKey);
2199            dest.writeString(mLocation);
2200            dest.writeString(mEncoding);
2201            dest.writeString(mContent);
2202            dest.writeInt(mFlags);
2203            dest.writeLong(mAccountKey);
2204            if (mContentBytes == null) {
2205                dest.writeInt(-1);
2206            } else {
2207                dest.writeInt(mContentBytes.length);
2208                dest.writeByteArray(mContentBytes);
2209            }
2210        }
2211
2212        public Attachment(Parcel in) {
2213            mBaseUri = EmailContent.Attachment.CONTENT_URI;
2214            mId = in.readLong();
2215            mFileName = in.readString();
2216            mMimeType = in.readString();
2217            mSize = in.readLong();
2218            mContentId = in.readString();
2219            mContentUri = in.readString();
2220            mMessageKey = in.readLong();
2221            mLocation = in.readString();
2222            mEncoding = in.readString();
2223            mContent = in.readString();
2224            mFlags = in.readInt();
2225            mAccountKey = in.readLong();
2226            final int contentBytesLen = in.readInt();
2227            if (contentBytesLen == -1) {
2228                mContentBytes = null;
2229            } else {
2230                mContentBytes = new byte[contentBytesLen];
2231                in.readByteArray(mContentBytes);
2232            }
2233         }
2234
2235        public static final Parcelable.Creator<EmailContent.Attachment> CREATOR
2236                = new Parcelable.Creator<EmailContent.Attachment>() {
2237            @Override
2238            public EmailContent.Attachment createFromParcel(Parcel in) {
2239                return new EmailContent.Attachment(in);
2240            }
2241
2242            @Override
2243            public EmailContent.Attachment[] newArray(int size) {
2244                return new EmailContent.Attachment[size];
2245            }
2246        };
2247
2248        @Override
2249        public String toString() {
2250            return "[" + mFileName + ", " + mMimeType + ", " + mSize + ", " + mContentId + ", "
2251                    + mContentUri + ", " + mMessageKey + ", " + mLocation + ", " + mEncoding  + ", "
2252                    + mFlags + ", " + mContentBytes + ", " + mAccountKey + "]";
2253        }
2254    }
2255
2256    public interface MailboxColumns {
2257        public static final String ID = "_id";
2258        // The display name of this mailbox [INDEX]
2259        static final String DISPLAY_NAME = "displayName";
2260        // The server's identifier for this mailbox
2261        public static final String SERVER_ID = "serverId";
2262        // The server's identifier for the parent of this mailbox (null = top-level)
2263        public static final String PARENT_SERVER_ID = "parentServerId";
2264        // A foreign key for the parent of this mailbox (-1 = top-level, 0=uninitialized)
2265        public static final String PARENT_KEY = "parentKey";
2266        // A foreign key to the Account that owns this mailbox
2267        public static final String ACCOUNT_KEY = "accountKey";
2268        // The type (role) of this mailbox
2269        public static final String TYPE = "type";
2270        // The hierarchy separator character
2271        public static final String DELIMITER = "delimiter";
2272        // Server-based sync key or validity marker (e.g. "SyncKey" for EAS, "uidvalidity" for IMAP)
2273        public static final String SYNC_KEY = "syncKey";
2274        // The sync lookback period for this mailbox (or null if using the account default)
2275        public static final String SYNC_LOOKBACK = "syncLookback";
2276        // The sync frequency for this mailbox (or null if using the account default)
2277        public static final String SYNC_INTERVAL = "syncInterval";
2278        // The time of last successful sync completion (millis)
2279        public static final String SYNC_TIME = "syncTime";
2280        // Cached unread count
2281        public static final String UNREAD_COUNT = "unreadCount";
2282        // Visibility of this folder in a list of folders [INDEX]
2283        public static final String FLAG_VISIBLE = "flagVisible";
2284        // Other states, as a bit field, e.g. CHILDREN_VISIBLE, HAS_CHILDREN
2285        public static final String FLAGS = "flags";
2286        // Backward compatible
2287        public static final String VISIBLE_LIMIT = "visibleLimit";
2288        // Sync status (can be used as desired by sync services)
2289        public static final String SYNC_STATUS = "syncStatus";
2290        // Number of messages in the mailbox.
2291        public static final String MESSAGE_COUNT = "messageCount";
2292        // Number of messages in the mailbox.
2293        public static final String LAST_SEEN_MESSAGE_KEY = "lastSeenMessageKey";
2294    }
2295
2296    public interface HostAuthColumns {
2297        public static final String ID = "_id";
2298        // The protocol (e.g. "imap", "pop3", "eas", "smtp"
2299        static final String PROTOCOL = "protocol";
2300        // The host address
2301        static final String ADDRESS = "address";
2302        // The port to use for the connection
2303        static final String PORT = "port";
2304        // General purpose flags
2305        static final String FLAGS = "flags";
2306        // The login (user name)
2307        static final String LOGIN = "login";
2308        // Password
2309        static final String PASSWORD = "password";
2310        // A domain or path, if required (used in IMAP and EAS)
2311        static final String DOMAIN = "domain";
2312        // DEPRECATED - Will not be set or stored
2313        static final String ACCOUNT_KEY = "accountKey";
2314    }
2315
2316    public interface PolicyColumns {
2317        public static final String ID = "_id";
2318        public static final String PASSWORD_MODE = "passwordMode";
2319        public static final String PASSWORD_MIN_LENGTH = "passwordMinLength";
2320        public static final String PASSWORD_EXPIRATION_DAYS = "passwordExpirationDays";
2321        public static final String PASSWORD_HISTORY = "passwordHistory";
2322        public static final String PASSWORD_COMPLEX_CHARS = "passwordComplexChars";
2323        public static final String PASSWORD_MAX_FAILS = "passwordMaxFails";
2324        public static final String MAX_SCREEN_LOCK_TIME = "maxScreenLockTime";
2325        public static final String REQUIRE_REMOTE_WIPE = "requireRemoteWipe";
2326        public static final String REQUIRE_ENCRYPTION = "requireEncryption";
2327        public static final String REQUIRE_ENCRYPTION_EXTERNAL = "requireEncryptionExternal";
2328        // ICS additions
2329        // Note: the appearance of these columns does not imply that we support these features; only
2330        // that we store them in the Policy structure
2331        public static final String REQUIRE_MANUAL_SYNC_WHEN_ROAMING = "requireManualSyncRoaming";
2332        public static final String DONT_ALLOW_CAMERA = "dontAllowCamera";
2333        public static final String DONT_ALLOW_ATTACHMENTS = "dontAllowAttachments";
2334        public static final String DONT_ALLOW_HTML = "dontAllowHtml";
2335        public static final String MAX_ATTACHMENT_SIZE = "maxAttachmentSize";
2336        public static final String MAX_TEXT_TRUNCATION_SIZE = "maxTextTruncationSize";
2337        public static final String MAX_HTML_TRUNCATION_SIZE = "maxHTMLTruncationSize";
2338        public static final String MAX_EMAIL_LOOKBACK = "maxEmailLookback";
2339        public static final String MAX_CALENDAR_LOOKBACK = "maxCalendarLookback";
2340        // Indicates that the server allows password recovery, not that we support it
2341        public static final String PASSWORD_RECOVERY_ENABLED = "passwordRecoveryEnabled";
2342    }
2343}
2344