Home | History | Annotate | Download | only in provider
      1 /*
      2  * Copyright (C) 2009 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.email.provider;
     18 
     19 import android.content.ContentProviderOperation;
     20 import android.content.ContentProviderResult;
     21 import android.content.ContentResolver;
     22 import android.content.ContentUris;
     23 import android.content.ContentValues;
     24 import android.content.Context;
     25 import android.content.OperationApplicationException;
     26 import android.database.Cursor;
     27 import android.net.Uri;
     28 import android.os.Environment;
     29 import android.os.Parcel;
     30 import android.os.Parcelable;
     31 import android.os.RemoteException;
     32 
     33 import java.io.File;
     34 import java.net.URI;
     35 import java.net.URISyntaxException;
     36 import java.util.ArrayList;
     37 import java.util.List;
     38 import 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  */
     60 public 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