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