• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.emailcommon.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 com.android.emailcommon.utility.TextUtilities;
34 import com.android.emailcommon.utility.Utility;
35 import com.android.mail.providers.UIProvider;
36 import com.google.common.annotations.VisibleForTesting;
37 
38 import java.io.File;
39 import java.util.ArrayList;
40 
41 
42 /**
43  * EmailContent is the superclass of the various classes of content stored by EmailProvider.
44  *
45  * It is intended to include 1) column definitions for use with the Provider, and 2) convenience
46  * methods for saving and retrieving content from the Provider.
47  *
48  * This class will be used by 1) the Email process (which includes the application and
49  * EmaiLProvider) as well as 2) the Exchange process (which runs independently).  It will
50  * necessarily be cloned for use in these two cases.
51  *
52  * Conventions used in naming columns:
53  *   RECORD_ID is the primary key for all Email records
54  *   The SyncColumns interface is used by all classes that are synced to the server directly
55  *   (Mailbox and Email)
56  *
57  *   <name>_KEY always refers to a foreign key
58  *   <name>_ID always refers to a unique identifier (whether on client, server, etc.)
59  *
60  */
61 public abstract class EmailContent {
62 
63     public static final String AUTHORITY = "com.android.email.provider";
64     // The notifier authority is used to send notifications regarding changes to messages (insert,
65     // delete, or update) and is intended as an optimization for use by clients of message list
66     // cursors (initially, the email AppWidget).
67     public static final String NOTIFIER_AUTHORITY = "com.android.email.notifier";
68 
69     public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY);
70     public static final String PARAMETER_LIMIT = "limit";
71 
72     public static final Uri CONTENT_NOTIFIER_URI = Uri.parse("content://" + NOTIFIER_AUTHORITY);
73 
74     public static final Uri MAILBOX_NOTIFICATION_URI =
75             Uri.parse("content://" + EmailContent.AUTHORITY + "/mailboxNotification");
76     public static final String[] NOTIFICATION_PROJECTION =
77         new String[] {MailboxColumns.ID, MailboxColumns.UNREAD_COUNT, MailboxColumns.MESSAGE_COUNT};
78     public static final int NOTIFICATION_MAILBOX_ID_COLUMN = 0;
79     public static final int NOTIFICATION_MAILBOX_UNREAD_COUNT_COLUMN = 1;
80     public static final int NOTIFICATION_MAILBOX_MESSAGE_COUNT_COLUMN = 2;
81 
82     public static final Uri MAILBOX_MOST_RECENT_MESSAGE_URI =
83             Uri.parse("content://" + EmailContent.AUTHORITY + "/mailboxMostRecentMessage");
84 
85     public static final String PROVIDER_PERMISSION = "com.android.email.permission.ACCESS_PROVIDER";
86 
87     // All classes share this
88     public static final String RECORD_ID = "_id";
89 
90     public static final String[] COUNT_COLUMNS = new String[]{"count(*)"};
91 
92     /**
93      * This projection can be used with any of the EmailContent classes, when all you need
94      * is a list of id's.  Use ID_PROJECTION_COLUMN to access the row data.
95      */
96     public static final String[] ID_PROJECTION = new String[] {
97         RECORD_ID
98     };
99     public static final int ID_PROJECTION_COLUMN = 0;
100 
101     public static final String ID_SELECTION = RECORD_ID + " =?";
102 
103     public static final String FIELD_COLUMN_NAME = "field";
104     public static final String ADD_COLUMN_NAME = "add";
105     public static final String SET_COLUMN_NAME = "set";
106 
107     public static final int SYNC_STATUS_NONE = UIProvider.SyncStatus.NO_SYNC;
108     public static final int SYNC_STATUS_USER = UIProvider.SyncStatus.USER_REFRESH;
109     public static final int SYNC_STATUS_BACKGROUND = UIProvider.SyncStatus.BACKGROUND_SYNC;
110 
111     public static final int LAST_SYNC_RESULT_SUCCESS = UIProvider.LastSyncResult.SUCCESS;
112     public static final int LAST_SYNC_RESULT_AUTH_ERROR = UIProvider.LastSyncResult.AUTH_ERROR;
113     public static final int LAST_SYNC_RESULT_SECURITY_ERROR =
114             UIProvider.LastSyncResult.SECURITY_ERROR;
115     public static final int LAST_SYNC_RESULT_CONNECTION_ERROR =
116             UIProvider.LastSyncResult.CONNECTION_ERROR;
117     public static final int LAST_SYNC_RESULT_INTERNAL_ERROR =
118             UIProvider.LastSyncResult.INTERNAL_ERROR;
119 
120     // Newly created objects get this id
121     public static final int NOT_SAVED = -1;
122     // The base Uri that this piece of content came from
123     public Uri mBaseUri;
124     // Lazily initialized uri for this Content
125     private Uri mUri = null;
126     // The id of the Content
127     public long mId = NOT_SAVED;
128 
129     // Write the Content into a ContentValues container
toContentValues()130     public abstract ContentValues toContentValues();
131     // Read the Content from a ContentCursor
restore(Cursor cursor)132     public abstract void restore (Cursor cursor);
133 
134     // The Uri is lazily initialized
getUri()135     public Uri getUri() {
136         if (mUri == null) {
137             mUri = ContentUris.withAppendedId(mBaseUri, mId);
138         }
139         return mUri;
140     }
141 
isSaved()142     public boolean isSaved() {
143         return mId != NOT_SAVED;
144     }
145 
146 
147     /**
148      * Restore a subclass of EmailContent from the database
149      * @param context the caller's context
150      * @param klass the class to restore
151      * @param contentUri the content uri of the EmailContent subclass
152      * @param contentProjection the content projection for the EmailContent subclass
153      * @param id the unique id of the object
154      * @return the instantiated object
155      */
restoreContentWithId(Context context, Class<T> klass, Uri contentUri, String[] contentProjection, long id)156     public static <T extends EmailContent> T restoreContentWithId(Context context,
157             Class<T> klass, Uri contentUri, String[] contentProjection, long id) {
158         Uri u = ContentUris.withAppendedId(contentUri, id);
159         Cursor c = context.getContentResolver().query(u, contentProjection, null, null, null);
160         if (c == null) throw new ProviderUnavailableException();
161         try {
162             if (c.moveToFirst()) {
163                 return getContent(c, klass);
164             } else {
165                 return null;
166             }
167         } finally {
168             c.close();
169         }
170     }
171 
172 
173     // The Content sub class must have a no-arg constructor
getContent(Cursor cursor, Class<T> klass)174     static public <T extends EmailContent> T getContent(Cursor cursor, Class<T> klass) {
175         try {
176             T content = klass.newInstance();
177             content.mId = cursor.getLong(0);
178             content.restore(cursor);
179             return content;
180         } catch (IllegalAccessException e) {
181             e.printStackTrace();
182         } catch (InstantiationException e) {
183             e.printStackTrace();
184         }
185         return null;
186     }
187 
save(Context context)188     public Uri save(Context context) {
189         if (isSaved()) {
190             throw new UnsupportedOperationException();
191         }
192         Uri res = context.getContentResolver().insert(mBaseUri, toContentValues());
193         mId = Long.parseLong(res.getPathSegments().get(1));
194         return res;
195     }
196 
update(Context context, ContentValues contentValues)197     public int update(Context context, ContentValues contentValues) {
198         if (!isSaved()) {
199             throw new UnsupportedOperationException();
200         }
201         return context.getContentResolver().update(getUri(), contentValues, null, null);
202     }
203 
update(Context context, Uri baseUri, long id, ContentValues contentValues)204     static public int update(Context context, Uri baseUri, long id, ContentValues contentValues) {
205         return context.getContentResolver()
206             .update(ContentUris.withAppendedId(baseUri, id), contentValues, null, null);
207     }
208 
delete(Context context, Uri baseUri, long id)209     static public int delete(Context context, Uri baseUri, long id) {
210         return context.getContentResolver()
211             .delete(ContentUris.withAppendedId(baseUri, id), null, null);
212     }
213 
214     /**
215      * Generic count method that can be used for any ContentProvider
216      *
217      * @param context the calling Context
218      * @param uri the Uri for the provider query
219      * @param selection as with a query call
220      * @param selectionArgs as with a query call
221      * @return the number of items matching the query (or zero)
222      */
count(Context context, Uri uri, String selection, String[] selectionArgs)223     static public int count(Context context, Uri uri, String selection, String[] selectionArgs) {
224         return Utility.getFirstRowLong(context,
225                 uri, COUNT_COLUMNS, selection, selectionArgs, null, 0, Long.valueOf(0)).intValue();
226     }
227 
228     /**
229      * Same as {@link #count(Context, Uri, String, String[])} without selection.
230      */
count(Context context, Uri uri)231     static public int count(Context context, Uri uri) {
232         return count(context, uri, null, null);
233     }
234 
uriWithLimit(Uri uri, int limit)235     static public Uri uriWithLimit(Uri uri, int limit) {
236         return uri.buildUpon().appendQueryParameter(EmailContent.PARAMETER_LIMIT,
237                 Integer.toString(limit)).build();
238     }
239 
240     /**
241      * no public constructor since this is a utility class
242      */
EmailContent()243     protected EmailContent() {
244     }
245 
246     public interface SyncColumns {
247         public static final String ID = "_id";
248         // source id (string) : the source's name of this item
249         public static final String SERVER_ID = "syncServerId";
250         // source's timestamp (long) for this item
251         public static final String SERVER_TIMESTAMP = "syncServerTimeStamp";
252     }
253 
254     public interface BodyColumns {
255         public static final String ID = "_id";
256         // Foreign key to the message corresponding to this body
257         public static final String MESSAGE_KEY = "messageKey";
258         // The html content itself
259         public static final String HTML_CONTENT = "htmlContent";
260         // The plain text content itself
261         public static final String TEXT_CONTENT = "textContent";
262         // Replied-to or forwarded body (in html form)
263         public static final String HTML_REPLY = "htmlReply";
264         // Replied-to or forwarded body (in text form)
265         public static final String TEXT_REPLY = "textReply";
266         // A reference to a message's unique id used in reply/forward.
267         // Protocol code can be expected to use this column in determining whether a message can be
268         // deleted safely (i.e. isn't referenced by other messages)
269         public static final String SOURCE_MESSAGE_KEY = "sourceMessageKey";
270         // The text to be placed between a reply/forward response and the original message
271         public static final String INTRO_TEXT = "introText";
272         // The start of quoted text within our text content
273         public static final String QUOTED_TEXT_START_POS = "quotedTextStartPos";
274     }
275 
276     public static final class Body extends EmailContent implements BodyColumns {
277         public static final String TABLE_NAME = "Body";
278 
279         @SuppressWarnings("hiding")
280         public static final Uri CONTENT_URI = Uri.parse(EmailContent.CONTENT_URI + "/body");
281 
282         public static final int CONTENT_ID_COLUMN = 0;
283         public static final int CONTENT_MESSAGE_KEY_COLUMN = 1;
284         public static final int CONTENT_HTML_CONTENT_COLUMN = 2;
285         public static final int CONTENT_TEXT_CONTENT_COLUMN = 3;
286         public static final int CONTENT_HTML_REPLY_COLUMN = 4;
287         public static final int CONTENT_TEXT_REPLY_COLUMN = 5;
288         public static final int CONTENT_SOURCE_KEY_COLUMN = 6;
289         public static final int CONTENT_INTRO_TEXT_COLUMN = 7;
290         public static final int CONTENT_QUOTED_TEXT_START_POS_COLUMN = 8;
291 
292         public static final String[] CONTENT_PROJECTION = new String[] {
293             RECORD_ID, BodyColumns.MESSAGE_KEY, BodyColumns.HTML_CONTENT, BodyColumns.TEXT_CONTENT,
294             BodyColumns.HTML_REPLY, BodyColumns.TEXT_REPLY, BodyColumns.SOURCE_MESSAGE_KEY,
295             BodyColumns.INTRO_TEXT, BodyColumns.QUOTED_TEXT_START_POS
296         };
297 
298         public static final String[] COMMON_PROJECTION_TEXT = new String[] {
299             RECORD_ID, BodyColumns.TEXT_CONTENT
300         };
301         public static final String[] COMMON_PROJECTION_HTML = new String[] {
302             RECORD_ID, BodyColumns.HTML_CONTENT
303         };
304         public static final String[] COMMON_PROJECTION_REPLY_TEXT = new String[] {
305             RECORD_ID, BodyColumns.TEXT_REPLY
306         };
307         public static final String[] COMMON_PROJECTION_REPLY_HTML = new String[] {
308             RECORD_ID, BodyColumns.HTML_REPLY
309         };
310         public static final String[] COMMON_PROJECTION_INTRO = new String[] {
311             RECORD_ID, BodyColumns.INTRO_TEXT
312         };
313         public static final String[] COMMON_PROJECTION_SOURCE = new String[] {
314             RECORD_ID, BodyColumns.SOURCE_MESSAGE_KEY
315         };
316          public static final int COMMON_PROJECTION_COLUMN_TEXT = 1;
317 
318         private static final String[] PROJECTION_SOURCE_KEY =
319             new String[] { BodyColumns.SOURCE_MESSAGE_KEY };
320 
321         public long mMessageKey;
322         public String mHtmlContent;
323         public String mTextContent;
324         public String mHtmlReply;
325         public String mTextReply;
326         public int mQuotedTextStartPos;
327 
328         /**
329          * Points to the ID of the message being replied to or forwarded. Will always be set,
330          * even if {@link #mHtmlReply} and {@link #mTextReply} are null (indicating the user doesn't
331          * want to include quoted text.
332          */
333         public long mSourceKey;
334         public String mIntroText;
335 
Body()336         public Body() {
337             mBaseUri = CONTENT_URI;
338         }
339 
340         @Override
toContentValues()341         public ContentValues toContentValues() {
342             ContentValues values = new ContentValues();
343 
344             // Assign values for each row.
345             values.put(BodyColumns.MESSAGE_KEY, mMessageKey);
346             values.put(BodyColumns.HTML_CONTENT, mHtmlContent);
347             values.put(BodyColumns.TEXT_CONTENT, mTextContent);
348             values.put(BodyColumns.HTML_REPLY, mHtmlReply);
349             values.put(BodyColumns.TEXT_REPLY, mTextReply);
350             values.put(BodyColumns.SOURCE_MESSAGE_KEY, mSourceKey);
351             values.put(BodyColumns.INTRO_TEXT, mIntroText);
352             return values;
353         }
354 
355         /**
356          * Given a cursor, restore a Body from it
357          * @param cursor a cursor which must NOT be null
358          * @return the Body as restored from the cursor
359          */
restoreBodyWithCursor(Cursor cursor)360         private static Body restoreBodyWithCursor(Cursor cursor) {
361             try {
362                 if (cursor.moveToFirst()) {
363                     return getContent(cursor, Body.class);
364                 } else {
365                     return null;
366                 }
367             } finally {
368                 cursor.close();
369             }
370         }
371 
restoreBodyWithId(Context context, long id)372         public static Body restoreBodyWithId(Context context, long id) {
373             Uri u = ContentUris.withAppendedId(Body.CONTENT_URI, id);
374             Cursor c = context.getContentResolver().query(u, Body.CONTENT_PROJECTION,
375                     null, null, null);
376             if (c == null) throw new ProviderUnavailableException();
377             return restoreBodyWithCursor(c);
378         }
379 
restoreBodyWithMessageId(Context context, long messageId)380         public static Body restoreBodyWithMessageId(Context context, long messageId) {
381             Cursor c = context.getContentResolver().query(Body.CONTENT_URI,
382                     Body.CONTENT_PROJECTION, Body.MESSAGE_KEY + "=?",
383                     new String[] {Long.toString(messageId)}, null);
384             if (c == null) throw new ProviderUnavailableException();
385             return restoreBodyWithCursor(c);
386         }
387 
388         /**
389          * Returns the bodyId for the given messageId, or -1 if no body is found.
390          */
lookupBodyIdWithMessageId(Context context, long messageId)391         public static long lookupBodyIdWithMessageId(Context context, long messageId) {
392             return Utility.getFirstRowLong(context, Body.CONTENT_URI,
393                     ID_PROJECTION, Body.MESSAGE_KEY + "=?",
394                     new String[] {Long.toString(messageId)}, null, ID_PROJECTION_COLUMN,
395                             Long.valueOf(-1));
396         }
397 
398         /**
399          * Updates the Body for a messageId with the given ContentValues.
400          * If the message has no body, a new body is inserted for the message.
401          * Warning: the argument "values" is modified by this method, setting MESSAGE_KEY.
402          */
updateBodyWithMessageId(Context context, long messageId, ContentValues values)403         public static void updateBodyWithMessageId(Context context, long messageId,
404                 ContentValues values) {
405             ContentResolver resolver = context.getContentResolver();
406             long bodyId = lookupBodyIdWithMessageId(context, messageId);
407             values.put(BodyColumns.MESSAGE_KEY, messageId);
408             if (bodyId == -1) {
409                 resolver.insert(CONTENT_URI, values);
410             } else {
411                 final Uri uri = ContentUris.withAppendedId(CONTENT_URI, bodyId);
412                 resolver.update(uri, values, null, null);
413             }
414         }
415 
416         @VisibleForTesting
restoreBodySourceKey(Context context, long messageId)417         public static long restoreBodySourceKey(Context context, long messageId) {
418             return Utility.getFirstRowLong(context, Body.CONTENT_URI,
419                     Body.PROJECTION_SOURCE_KEY,
420                     Body.MESSAGE_KEY + "=?", new String[] {Long.toString(messageId)}, null, 0,
421                             Long.valueOf(0));
422         }
423 
restoreTextWithMessageId(Context context, long messageId, String[] projection)424         private static String restoreTextWithMessageId(Context context, long messageId,
425                 String[] projection) {
426             Cursor c = context.getContentResolver().query(Body.CONTENT_URI, projection,
427                     Body.MESSAGE_KEY + "=?", new String[] {Long.toString(messageId)}, null);
428             if (c == null) throw new ProviderUnavailableException();
429             try {
430                 if (c.moveToFirst()) {
431                     return c.getString(COMMON_PROJECTION_COLUMN_TEXT);
432                 } else {
433                     return null;
434                 }
435             } finally {
436                 c.close();
437             }
438         }
439 
restoreBodyTextWithMessageId(Context context, long messageId)440         public static String restoreBodyTextWithMessageId(Context context, long messageId) {
441             return restoreTextWithMessageId(context, messageId, Body.COMMON_PROJECTION_TEXT);
442         }
443 
restoreBodyHtmlWithMessageId(Context context, long messageId)444         public static String restoreBodyHtmlWithMessageId(Context context, long messageId) {
445             return restoreTextWithMessageId(context, messageId, Body.COMMON_PROJECTION_HTML);
446         }
447 
restoreReplyTextWithMessageId(Context context, long messageId)448         public static String restoreReplyTextWithMessageId(Context context, long messageId) {
449             return restoreTextWithMessageId(context, messageId, Body.COMMON_PROJECTION_REPLY_TEXT);
450         }
451 
restoreReplyHtmlWithMessageId(Context context, long messageId)452         public static String restoreReplyHtmlWithMessageId(Context context, long messageId) {
453             return restoreTextWithMessageId(context, messageId, Body.COMMON_PROJECTION_REPLY_HTML);
454         }
455 
restoreIntroTextWithMessageId(Context context, long messageId)456         public static String restoreIntroTextWithMessageId(Context context, long messageId) {
457             return restoreTextWithMessageId(context, messageId, Body.COMMON_PROJECTION_INTRO);
458         }
459 
460         @Override
restore(Cursor cursor)461         public void restore(Cursor cursor) {
462             mBaseUri = EmailContent.Body.CONTENT_URI;
463             mMessageKey = cursor.getLong(CONTENT_MESSAGE_KEY_COLUMN);
464             mHtmlContent = cursor.getString(CONTENT_HTML_CONTENT_COLUMN);
465             mTextContent = cursor.getString(CONTENT_TEXT_CONTENT_COLUMN);
466             mHtmlReply = cursor.getString(CONTENT_HTML_REPLY_COLUMN);
467             mTextReply = cursor.getString(CONTENT_TEXT_REPLY_COLUMN);
468             mSourceKey = cursor.getLong(CONTENT_SOURCE_KEY_COLUMN);
469             mIntroText = cursor.getString(CONTENT_INTRO_TEXT_COLUMN);
470             mQuotedTextStartPos = cursor.getInt(CONTENT_QUOTED_TEXT_START_POS_COLUMN);
471         }
472 
update()473         public boolean update() {
474             // TODO Auto-generated method stub
475             return false;
476         }
477     }
478 
479     public interface MessageColumns {
480         public static final String ID = "_id";
481         // Basic columns used in message list presentation
482         // The name as shown to the user in a message list
483         public static final String DISPLAY_NAME = "displayName";
484         // The time (millis) as shown to the user in a message list [INDEX]
485         public static final String TIMESTAMP = "timeStamp";
486         // Message subject
487         public static final String SUBJECT = "subject";
488         // Boolean, unread = 0, read = 1 [INDEX]
489         public static final String FLAG_READ = "flagRead";
490         // Load state, see constants below (unloaded, partial, complete, deleted)
491         public static final String FLAG_LOADED = "flagLoaded";
492         // Boolean, unflagged = 0, flagged (favorite) = 1
493         public static final String FLAG_FAVORITE = "flagFavorite";
494         // Boolean, no attachment = 0, attachment = 1
495         public static final String FLAG_ATTACHMENT = "flagAttachment";
496         // Bit field for flags which we'll not be selecting on
497         public static final String FLAGS = "flags";
498 
499         // Sync related identifiers
500         // Any client-required identifier
501         public static final String CLIENT_ID = "clientId";
502         // The message-id in the message's header
503         public static final String MESSAGE_ID = "messageId";
504 
505         // References to other Email objects in the database
506         // Foreign key to the Mailbox holding this message [INDEX]
507         public static final String MAILBOX_KEY = "mailboxKey";
508         // Foreign key to the Account holding this message
509         public static final String ACCOUNT_KEY = "accountKey";
510 
511         // Address lists, packed with Address.pack()
512         public static final String FROM_LIST = "fromList";
513         public static final String TO_LIST = "toList";
514         public static final String CC_LIST = "ccList";
515         public static final String BCC_LIST = "bccList";
516         public static final String REPLY_TO_LIST = "replyToList";
517         // Meeting invitation related information (for now, start time in ms)
518         public static final String MEETING_INFO = "meetingInfo";
519         // A text "snippet" derived from the body of the message
520         public static final String SNIPPET = "snippet";
521         // A column that can be used by sync adapters to store search-related information about
522         // a retrieved message (the messageKey for search results will be a TYPE_SEARCH mailbox
523         // and the sync adapter might, for example, need more information about the original source
524         // of the message)
525         public static final String PROTOCOL_SEARCH_INFO = "protocolSearchInfo";
526         // Simple thread topic
527         public static final String THREAD_TOPIC = "threadTopic";
528     }
529 
530     public static final class Message extends EmailContent implements SyncColumns, MessageColumns {
531         public static final String TABLE_NAME = "Message";
532         public static final String UPDATED_TABLE_NAME = "Message_Updates";
533         public static final String DELETED_TABLE_NAME = "Message_Deletes";
534 
535         // To refer to a specific message, use ContentUris.withAppendedId(CONTENT_URI, id)
536         @SuppressWarnings("hiding")
537         public static final Uri CONTENT_URI = Uri.parse(EmailContent.CONTENT_URI + "/message");
538         public static final Uri CONTENT_URI_LIMIT_1 = uriWithLimit(CONTENT_URI, 1);
539         public static final Uri SYNCED_CONTENT_URI =
540             Uri.parse(EmailContent.CONTENT_URI + "/syncedMessage");
541         public static final Uri DELETED_CONTENT_URI =
542             Uri.parse(EmailContent.CONTENT_URI + "/deletedMessage");
543         public static final Uri UPDATED_CONTENT_URI =
544             Uri.parse(EmailContent.CONTENT_URI + "/updatedMessage");
545         public static final Uri NOTIFIER_URI =
546             Uri.parse(EmailContent.CONTENT_NOTIFIER_URI + "/message");
547 
548         public static final String KEY_TIMESTAMP_DESC = MessageColumns.TIMESTAMP + " desc";
549 
550         public static final int CONTENT_ID_COLUMN = 0;
551         public static final int CONTENT_DISPLAY_NAME_COLUMN = 1;
552         public static final int CONTENT_TIMESTAMP_COLUMN = 2;
553         public static final int CONTENT_SUBJECT_COLUMN = 3;
554         public static final int CONTENT_FLAG_READ_COLUMN = 4;
555         public static final int CONTENT_FLAG_LOADED_COLUMN = 5;
556         public static final int CONTENT_FLAG_FAVORITE_COLUMN = 6;
557         public static final int CONTENT_FLAG_ATTACHMENT_COLUMN = 7;
558         public static final int CONTENT_FLAGS_COLUMN = 8;
559         public static final int CONTENT_SERVER_ID_COLUMN = 9;
560         public static final int CONTENT_CLIENT_ID_COLUMN = 10;
561         public static final int CONTENT_MESSAGE_ID_COLUMN = 11;
562         public static final int CONTENT_MAILBOX_KEY_COLUMN = 12;
563         public static final int CONTENT_ACCOUNT_KEY_COLUMN = 13;
564         public static final int CONTENT_FROM_LIST_COLUMN = 14;
565         public static final int CONTENT_TO_LIST_COLUMN = 15;
566         public static final int CONTENT_CC_LIST_COLUMN = 16;
567         public static final int CONTENT_BCC_LIST_COLUMN = 17;
568         public static final int CONTENT_REPLY_TO_COLUMN = 18;
569         public static final int CONTENT_SERVER_TIMESTAMP_COLUMN = 19;
570         public static final int CONTENT_MEETING_INFO_COLUMN = 20;
571         public static final int CONTENT_SNIPPET_COLUMN = 21;
572         public static final int CONTENT_PROTOCOL_SEARCH_INFO_COLUMN = 22;
573         public static final int CONTENT_THREAD_TOPIC_COLUMN = 23;
574 
575         public static final String[] CONTENT_PROJECTION = new String[] {
576             RECORD_ID,
577             MessageColumns.DISPLAY_NAME, MessageColumns.TIMESTAMP,
578             MessageColumns.SUBJECT, MessageColumns.FLAG_READ,
579             MessageColumns.FLAG_LOADED, MessageColumns.FLAG_FAVORITE,
580             MessageColumns.FLAG_ATTACHMENT, MessageColumns.FLAGS,
581             SyncColumns.SERVER_ID, MessageColumns.CLIENT_ID,
582             MessageColumns.MESSAGE_ID, MessageColumns.MAILBOX_KEY,
583             MessageColumns.ACCOUNT_KEY, MessageColumns.FROM_LIST,
584             MessageColumns.TO_LIST, MessageColumns.CC_LIST,
585             MessageColumns.BCC_LIST, MessageColumns.REPLY_TO_LIST,
586             SyncColumns.SERVER_TIMESTAMP, MessageColumns.MEETING_INFO,
587             MessageColumns.SNIPPET, MessageColumns.PROTOCOL_SEARCH_INFO,
588             MessageColumns.THREAD_TOPIC
589         };
590 
591         public static final int LIST_ID_COLUMN = 0;
592         public static final int LIST_DISPLAY_NAME_COLUMN = 1;
593         public static final int LIST_TIMESTAMP_COLUMN = 2;
594         public static final int LIST_SUBJECT_COLUMN = 3;
595         public static final int LIST_READ_COLUMN = 4;
596         public static final int LIST_LOADED_COLUMN = 5;
597         public static final int LIST_FAVORITE_COLUMN = 6;
598         public static final int LIST_ATTACHMENT_COLUMN = 7;
599         public static final int LIST_FLAGS_COLUMN = 8;
600         public static final int LIST_MAILBOX_KEY_COLUMN = 9;
601         public static final int LIST_ACCOUNT_KEY_COLUMN = 10;
602         public static final int LIST_SERVER_ID_COLUMN = 11;
603         public static final int LIST_SNIPPET_COLUMN = 12;
604 
605         // Public projection for common list columns
606         public static final String[] LIST_PROJECTION = new String[] {
607             RECORD_ID,
608             MessageColumns.DISPLAY_NAME, MessageColumns.TIMESTAMP,
609             MessageColumns.SUBJECT, MessageColumns.FLAG_READ,
610             MessageColumns.FLAG_LOADED, MessageColumns.FLAG_FAVORITE,
611             MessageColumns.FLAG_ATTACHMENT, MessageColumns.FLAGS,
612             MessageColumns.MAILBOX_KEY, MessageColumns.ACCOUNT_KEY,
613             SyncColumns.SERVER_ID, MessageColumns.SNIPPET
614         };
615 
616         public static final int ID_COLUMNS_ID_COLUMN = 0;
617         public static final int ID_COLUMNS_SYNC_SERVER_ID = 1;
618         public static final String[] ID_COLUMNS_PROJECTION = new String[] {
619             RECORD_ID, SyncColumns.SERVER_ID
620         };
621 
622         public static final int ID_MAILBOX_COLUMN_ID = 0;
623         public static final int ID_MAILBOX_COLUMN_MAILBOX_KEY = 1;
624         public static final String[] ID_MAILBOX_PROJECTION = new String[] {
625             RECORD_ID, MessageColumns.MAILBOX_KEY
626         };
627 
628         public static final String[] ID_COLUMN_PROJECTION = new String[] { RECORD_ID };
629 
630         private static final String ACCOUNT_KEY_SELECTION =
631             MessageColumns.ACCOUNT_KEY + "=?";
632 
633         /**
634          * Selection for messages that are loaded
635          *
636          * POP messages at the initial stage have very little information. (Server UID only)
637          * Use this to make sure they're not visible on any UI.
638          * This means unread counts on the mailbox list can be different from the
639          * number of messages in the message list, but it should be transient...
640          */
641         public static final String FLAG_LOADED_SELECTION =
642             MessageColumns.FLAG_LOADED + " IN ("
643             +     Message.FLAG_LOADED_PARTIAL + "," + Message.FLAG_LOADED_COMPLETE
644             +     ")";
645 
646         public static final String ALL_FAVORITE_SELECTION =
647             MessageColumns.FLAG_FAVORITE + "=1 AND "
648             + MessageColumns.MAILBOX_KEY + " NOT IN ("
649             +     "SELECT " + MailboxColumns.ID + " FROM " + Mailbox.TABLE_NAME + ""
650             +     " WHERE " + MailboxColumns.TYPE + " = " + Mailbox.TYPE_TRASH
651             +     ")"
652             + " AND " + FLAG_LOADED_SELECTION;
653 
654         /** Selection to retrieve all messages in "inbox" for any account */
655         public static final String ALL_INBOX_SELECTION =
656             MessageColumns.MAILBOX_KEY + " IN ("
657             +     "SELECT " + MailboxColumns.ID + " FROM " + Mailbox.TABLE_NAME
658             +     " WHERE " + MailboxColumns.TYPE + " = " + Mailbox.TYPE_INBOX
659             +     ")"
660             + " AND " + FLAG_LOADED_SELECTION;
661 
662         /** Selection to retrieve all messages in "drafts" for any account */
663         public static final String ALL_DRAFT_SELECTION =
664             MessageColumns.MAILBOX_KEY + " IN ("
665             +     "SELECT " + MailboxColumns.ID + " FROM " + Mailbox.TABLE_NAME
666             +     " WHERE " + MailboxColumns.TYPE + " = " + Mailbox.TYPE_DRAFTS
667             +     ")"
668             + " AND " + FLAG_LOADED_SELECTION;
669 
670         /** Selection to retrieve all messages in "outbox" for any account */
671         public static final String ALL_OUTBOX_SELECTION =
672             MessageColumns.MAILBOX_KEY + " IN ("
673             +     "SELECT " + MailboxColumns.ID + " FROM " + Mailbox.TABLE_NAME
674             +     " WHERE " + MailboxColumns.TYPE + " = " + Mailbox.TYPE_OUTBOX
675             +     ")"; // NOTE No flag_loaded test for outboxes.
676 
677         /** Selection to retrieve unread messages in "inbox" for any account */
678         public static final String ALL_UNREAD_SELECTION =
679             MessageColumns.FLAG_READ + "=0 AND " + ALL_INBOX_SELECTION;
680 
681         /** Selection to retrieve unread messages in "inbox" for one account */
682         public static final String PER_ACCOUNT_UNREAD_SELECTION =
683             ACCOUNT_KEY_SELECTION + " AND " + ALL_UNREAD_SELECTION;
684 
685         /** Selection to retrieve all messages in "inbox" for one account */
686         public static final String PER_ACCOUNT_INBOX_SELECTION =
687             ACCOUNT_KEY_SELECTION + " AND " + ALL_INBOX_SELECTION;
688 
689         public static final String PER_ACCOUNT_FAVORITE_SELECTION =
690             ACCOUNT_KEY_SELECTION + " AND " + ALL_FAVORITE_SELECTION;
691 
692         // _id field is in AbstractContent
693         public String mDisplayName;
694         public long mTimeStamp;
695         public String mSubject;
696         public boolean mFlagRead = false;
697         public int mFlagLoaded = FLAG_LOADED_UNLOADED;
698         public boolean mFlagFavorite = false;
699         public boolean mFlagAttachment = false;
700         public int mFlags = 0;
701 
702         public String mServerId;
703         public long mServerTimeStamp;
704         public String mClientId;
705         public String mMessageId;
706 
707         public long mMailboxKey;
708         public long mAccountKey;
709 
710         public String mFrom;
711         public String mTo;
712         public String mCc;
713         public String mBcc;
714         public String mReplyTo;
715 
716         // For now, just the start time of a meeting invite, in ms
717         public String mMeetingInfo;
718 
719         public String mSnippet;
720 
721         public String mProtocolSearchInfo;
722 
723         public String mThreadTopic;
724 
725         /**
726          * Base64-encoded representation of the byte array provided by servers for identifying
727          * messages belonging to the same conversation thread. Currently unsupported and not
728          * persisted in the database.
729          */
730         public String mServerConversationId;
731 
732         // The following transient members may be used while building and manipulating messages,
733         // but they are NOT persisted directly by EmailProvider. See Body for related fields.
734         transient public String mText;
735         transient public String mHtml;
736         transient public String mTextReply;
737         transient public String mHtmlReply;
738         transient public long mSourceKey;
739         transient public ArrayList<Attachment> mAttachments = null;
740         transient public String mIntroText;
741         transient public int mQuotedTextStartPos;
742 
743 
744         // Values used in mFlagRead
745         public static final int UNREAD = 0;
746         public static final int READ = 1;
747 
748         // Values used in mFlagLoaded
749         public static final int FLAG_LOADED_UNLOADED = 0;
750         public static final int FLAG_LOADED_COMPLETE = 1;
751         public static final int FLAG_LOADED_PARTIAL = 2;
752         public static final int FLAG_LOADED_DELETED = 3;
753 
754         // Bits used in mFlags
755         // The following three states are mutually exclusive, and indicate whether the message is an
756         // original, a reply, or a forward
757         public static final int FLAG_TYPE_REPLY = 1<<0;
758         public static final int FLAG_TYPE_FORWARD = 1<<1;
759         public static final int FLAG_TYPE_MASK = FLAG_TYPE_REPLY | FLAG_TYPE_FORWARD;
760         // The following flags indicate messages that are determined to be incoming meeting related
761         // (e.g. invites from others)
762         public static final int FLAG_INCOMING_MEETING_INVITE = 1<<2;
763         public static final int FLAG_INCOMING_MEETING_CANCEL = 1<<3;
764         public static final int FLAG_INCOMING_MEETING_MASK =
765             FLAG_INCOMING_MEETING_INVITE | FLAG_INCOMING_MEETING_CANCEL;
766         // The following flags indicate messages that are outgoing and meeting related
767         // (e.g. invites TO others)
768         public static final int FLAG_OUTGOING_MEETING_INVITE = 1<<4;
769         public static final int FLAG_OUTGOING_MEETING_CANCEL = 1<<5;
770         public static final int FLAG_OUTGOING_MEETING_ACCEPT = 1<<6;
771         public static final int FLAG_OUTGOING_MEETING_DECLINE = 1<<7;
772         public static final int FLAG_OUTGOING_MEETING_TENTATIVE = 1<<8;
773         public static final int FLAG_OUTGOING_MEETING_MASK =
774             FLAG_OUTGOING_MEETING_INVITE | FLAG_OUTGOING_MEETING_CANCEL |
775             FLAG_OUTGOING_MEETING_ACCEPT | FLAG_OUTGOING_MEETING_DECLINE |
776             FLAG_OUTGOING_MEETING_TENTATIVE;
777         public static final int FLAG_OUTGOING_MEETING_REQUEST_MASK =
778             FLAG_OUTGOING_MEETING_INVITE | FLAG_OUTGOING_MEETING_CANCEL;
779         // 8 general purpose flags (bits) that may be used at the discretion of the sync adapter
780         public static final int FLAG_SYNC_ADAPTER_SHIFT = 9;
781         public static final int FLAG_SYNC_ADAPTER_MASK = 255 << FLAG_SYNC_ADAPTER_SHIFT;
782         /** If set, the outgoing message should *not* include the quoted original message. */
783         public static final int FLAG_NOT_INCLUDE_QUOTED_TEXT = 1 << 17;
784         public static final int FLAG_REPLIED_TO = 1 << 18;
785         public static final int FLAG_FORWARDED = 1 << 19;
786 
787         // Outgoing, original message
788         public static final int FLAG_TYPE_ORIGINAL = 1 << 20;
789         // Outgoing, reply all message; note, FLAG_TYPE_REPLY should also be set for backward
790         // compatibility
791         public static final int FLAG_TYPE_REPLY_ALL = 1 << 21;
792 
793         /** a pseudo ID for "no message". */
794         public static final long NO_MESSAGE = -1L;
795 
Message()796         public Message() {
797             mBaseUri = CONTENT_URI;
798         }
799 
800         @Override
toContentValues()801         public ContentValues toContentValues() {
802             ContentValues values = new ContentValues();
803 
804             // Assign values for each row.
805             values.put(MessageColumns.DISPLAY_NAME, mDisplayName);
806             values.put(MessageColumns.TIMESTAMP, mTimeStamp);
807             values.put(MessageColumns.SUBJECT, mSubject);
808             values.put(MessageColumns.FLAG_READ, mFlagRead);
809             values.put(MessageColumns.FLAG_LOADED, mFlagLoaded);
810             values.put(MessageColumns.FLAG_FAVORITE, mFlagFavorite);
811             values.put(MessageColumns.FLAG_ATTACHMENT, mFlagAttachment);
812             values.put(MessageColumns.FLAGS, mFlags);
813 
814             values.put(SyncColumns.SERVER_ID, mServerId);
815             values.put(SyncColumns.SERVER_TIMESTAMP, mServerTimeStamp);
816             values.put(MessageColumns.CLIENT_ID, mClientId);
817             values.put(MessageColumns.MESSAGE_ID, mMessageId);
818 
819             values.put(MessageColumns.MAILBOX_KEY, mMailboxKey);
820             values.put(MessageColumns.ACCOUNT_KEY, mAccountKey);
821 
822             values.put(MessageColumns.FROM_LIST, mFrom);
823             values.put(MessageColumns.TO_LIST, mTo);
824             values.put(MessageColumns.CC_LIST, mCc);
825             values.put(MessageColumns.BCC_LIST, mBcc);
826             values.put(MessageColumns.REPLY_TO_LIST, mReplyTo);
827 
828             values.put(MessageColumns.MEETING_INFO, mMeetingInfo);
829 
830             values.put(MessageColumns.SNIPPET, mSnippet);
831 
832             values.put(MessageColumns.PROTOCOL_SEARCH_INFO, mProtocolSearchInfo);
833 
834             values.put(MessageColumns.THREAD_TOPIC, mThreadTopic);
835             return values;
836         }
837 
restoreMessageWithId(Context context, long id)838         public static Message restoreMessageWithId(Context context, long id) {
839             return EmailContent.restoreContentWithId(context, Message.class,
840                     Message.CONTENT_URI, Message.CONTENT_PROJECTION, id);
841         }
842 
843         @Override
restore(Cursor cursor)844         public void restore(Cursor cursor) {
845             mBaseUri = CONTENT_URI;
846             mId = cursor.getLong(CONTENT_ID_COLUMN);
847             mDisplayName = cursor.getString(CONTENT_DISPLAY_NAME_COLUMN);
848             mTimeStamp = cursor.getLong(CONTENT_TIMESTAMP_COLUMN);
849             mSubject = cursor.getString(CONTENT_SUBJECT_COLUMN);
850             mFlagRead = cursor.getInt(CONTENT_FLAG_READ_COLUMN) == 1;
851             mFlagLoaded = cursor.getInt(CONTENT_FLAG_LOADED_COLUMN);
852             mFlagFavorite = cursor.getInt(CONTENT_FLAG_FAVORITE_COLUMN) == 1;
853             mFlagAttachment = cursor.getInt(CONTENT_FLAG_ATTACHMENT_COLUMN) == 1;
854             mFlags = cursor.getInt(CONTENT_FLAGS_COLUMN);
855             mServerId = cursor.getString(CONTENT_SERVER_ID_COLUMN);
856             mServerTimeStamp = cursor.getLong(CONTENT_SERVER_TIMESTAMP_COLUMN);
857             mClientId = cursor.getString(CONTENT_CLIENT_ID_COLUMN);
858             mMessageId = cursor.getString(CONTENT_MESSAGE_ID_COLUMN);
859             mMailboxKey = cursor.getLong(CONTENT_MAILBOX_KEY_COLUMN);
860             mAccountKey = cursor.getLong(CONTENT_ACCOUNT_KEY_COLUMN);
861             mFrom = cursor.getString(CONTENT_FROM_LIST_COLUMN);
862             mTo = cursor.getString(CONTENT_TO_LIST_COLUMN);
863             mCc = cursor.getString(CONTENT_CC_LIST_COLUMN);
864             mBcc = cursor.getString(CONTENT_BCC_LIST_COLUMN);
865             mReplyTo = cursor.getString(CONTENT_REPLY_TO_COLUMN);
866             mMeetingInfo = cursor.getString(CONTENT_MEETING_INFO_COLUMN);
867             mSnippet = cursor.getString(CONTENT_SNIPPET_COLUMN);
868             mProtocolSearchInfo = cursor.getString(CONTENT_PROTOCOL_SEARCH_INFO_COLUMN);
869             mThreadTopic = cursor.getString(CONTENT_THREAD_TOPIC_COLUMN);
870         }
871 
update()872         public boolean update() {
873             // TODO Auto-generated method stub
874             return false;
875         }
876 
877         /*
878          * Override this so that we can store the Body first and link it to the Message
879          * Also, attachments when we get there...
880          * (non-Javadoc)
881          * @see com.android.email.provider.EmailContent#save(android.content.Context)
882          */
883         @Override
save(Context context)884         public Uri save(Context context) {
885 
886             boolean doSave = !isSaved();
887 
888             // This logic is in place so I can (a) short circuit the expensive stuff when
889             // possible, and (b) override (and throw) if anyone tries to call save() or update()
890             // directly for Message, which are unsupported.
891             if (mText == null && mHtml == null && mTextReply == null && mHtmlReply == null &&
892                     (mAttachments == null || mAttachments.isEmpty())) {
893                 if (doSave) {
894                     return super.save(context);
895                 } else {
896                     // Call update, rather than super.update in case we ever override it
897                     if (update(context, toContentValues()) == 1) {
898                         return getUri();
899                     }
900                     return null;
901                 }
902             }
903 
904             ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
905             addSaveOps(ops);
906             try {
907                 ContentProviderResult[] results =
908                     context.getContentResolver().applyBatch(AUTHORITY, ops);
909                 // If saving, set the mId's of the various saved objects
910                 if (doSave) {
911                     Uri u = results[0].uri;
912                     mId = Long.parseLong(u.getPathSegments().get(1));
913                     if (mAttachments != null) {
914                         int resultOffset = 2;
915                         for (Attachment a : mAttachments) {
916                             // Save the id of the attachment record
917                             u = results[resultOffset++].uri;
918                             if (u != null) {
919                                 a.mId = Long.parseLong(u.getPathSegments().get(1));
920                             }
921                             a.mMessageKey = mId;
922                         }
923                     }
924                     return u;
925                 } else {
926                     return null;
927                 }
928             } catch (RemoteException e) {
929                 // There is nothing to be done here; fail by returning null
930             } catch (OperationApplicationException e) {
931                 // There is nothing to be done here; fail by returning null
932             }
933             return null;
934         }
935 
936         /**
937          * Save or update a message
938          * @param ops an array of CPOs that we'll add to
939          */
addSaveOps(ArrayList<ContentProviderOperation> ops)940         public void addSaveOps(ArrayList<ContentProviderOperation> ops) {
941             boolean isNew = !isSaved();
942             ContentProviderOperation.Builder b;
943             // First, save/update the message
944             if (isNew) {
945                 b = ContentProviderOperation.newInsert(mBaseUri);
946             } else {
947                 b = ContentProviderOperation.newUpdate(mBaseUri)
948                         .withSelection(Message.RECORD_ID + "=?", new String[] {Long.toString(mId)});
949             }
950             // Generate the snippet here, before we create the CPO for Message
951             if (mText != null) {
952                 mSnippet = TextUtilities.makeSnippetFromPlainText(mText);
953             } else if (mHtml != null) {
954                 mSnippet = TextUtilities.makeSnippetFromHtmlText(mHtml);
955             }
956             ops.add(b.withValues(toContentValues()).build());
957 
958             // Create and save the body
959             ContentValues cv = new ContentValues();
960             if (mText != null) {
961                 cv.put(Body.TEXT_CONTENT, mText);
962             }
963             if (mHtml != null) {
964                 cv.put(Body.HTML_CONTENT, mHtml);
965             }
966             if (mTextReply != null) {
967                 cv.put(Body.TEXT_REPLY, mTextReply);
968             }
969             if (mHtmlReply != null) {
970                 cv.put(Body.HTML_REPLY, mHtmlReply);
971             }
972             if (mSourceKey != 0) {
973                 cv.put(Body.SOURCE_MESSAGE_KEY, mSourceKey);
974             }
975             if (mIntroText != null) {
976                 cv.put(Body.INTRO_TEXT, mIntroText);
977             }
978             if (mQuotedTextStartPos != 0) {
979                 cv.put(Body.QUOTED_TEXT_START_POS, mQuotedTextStartPos);
980             }
981             b = ContentProviderOperation.newInsert(Body.CONTENT_URI);
982             // Put our message id in the Body
983             if (!isNew) {
984                 cv.put(Body.MESSAGE_KEY, mId);
985             }
986             b.withValues(cv);
987             // We'll need this if we're new
988             int messageBackValue = ops.size() - 1;
989             // If we're new, create a back value entry
990             if (isNew) {
991                 ContentValues backValues = new ContentValues();
992                 backValues.put(Body.MESSAGE_KEY, messageBackValue);
993                 b.withValueBackReferences(backValues);
994             }
995             // And add the Body operation
996             ops.add(b.build());
997 
998             // Create the attaachments, if any
999             if (mAttachments != null) {
1000                 for (Attachment att: mAttachments) {
1001                     if (!isNew) {
1002                         att.mMessageKey = mId;
1003                     }
1004                     b = ContentProviderOperation.newInsert(Attachment.CONTENT_URI)
1005                             .withValues(att.toContentValues());
1006                     if (isNew) {
1007                         b.withValueBackReference(Attachment.MESSAGE_KEY, messageBackValue);
1008                     }
1009                     ops.add(b.build());
1010                 }
1011             }
1012         }
1013 
1014         /**
1015          * @return number of favorite (starred) messages throughout all accounts.
1016          */
getFavoriteMessageCount(Context context)1017         public static int getFavoriteMessageCount(Context context) {
1018             return count(context, Message.CONTENT_URI, ALL_FAVORITE_SELECTION, null);
1019         }
1020 
1021         /**
1022          * @return number of favorite (starred) messages for an account
1023          */
getFavoriteMessageCount(Context context, long accountId)1024         public static int getFavoriteMessageCount(Context context, long accountId) {
1025             return count(context, Message.CONTENT_URI, PER_ACCOUNT_FAVORITE_SELECTION,
1026                     new String[]{Long.toString(accountId)});
1027         }
1028 
getKeyColumnLong(Context context, long messageId, String column)1029         public static long getKeyColumnLong(Context context, long messageId, String column) {
1030             String[] columns =
1031                 Utility.getRowColumns(context, Message.CONTENT_URI, messageId, column);
1032             if (columns != null && columns[0] != null) {
1033                 return Long.parseLong(columns[0]);
1034             }
1035             return -1;
1036         }
1037 
1038         /**
1039          * Returns the where clause for a message list selection.
1040          *
1041          * Accesses the detabase to determine the mailbox type.  DO NOT CALL FROM UI THREAD.
1042          */
buildMessageListSelection( Context context, long accountId, long mailboxId)1043         public static String buildMessageListSelection(
1044                 Context context, long accountId, long mailboxId) {
1045 
1046             if (mailboxId == Mailbox.QUERY_ALL_INBOXES) {
1047                 return Message.ALL_INBOX_SELECTION;
1048             }
1049             if (mailboxId == Mailbox.QUERY_ALL_DRAFTS) {
1050                 return Message.ALL_DRAFT_SELECTION;
1051             }
1052             if (mailboxId == Mailbox.QUERY_ALL_OUTBOX) {
1053                 return Message.ALL_OUTBOX_SELECTION;
1054             }
1055             if (mailboxId == Mailbox.QUERY_ALL_UNREAD) {
1056                 return Message.ALL_UNREAD_SELECTION;
1057             }
1058             // TODO: we only support per-account starred mailbox right now, but presumably, we
1059             // can surface the same thing for unread.
1060             if (mailboxId == Mailbox.QUERY_ALL_FAVORITES) {
1061                 if (accountId == Account.ACCOUNT_ID_COMBINED_VIEW) {
1062                     return Message.ALL_FAVORITE_SELECTION;
1063                 }
1064 
1065                 final StringBuilder selection = new StringBuilder();
1066                 selection.append(MessageColumns.ACCOUNT_KEY).append('=').append(accountId)
1067                         .append(" AND ")
1068                         .append(Message.ALL_FAVORITE_SELECTION);
1069                 return selection.toString();
1070             }
1071 
1072             // Now it's a regular mailbox.
1073             final StringBuilder selection = new StringBuilder();
1074 
1075             selection.append(MessageColumns.MAILBOX_KEY).append('=').append(mailboxId);
1076 
1077             if (Mailbox.getMailboxType(context, mailboxId) != Mailbox.TYPE_OUTBOX) {
1078                 selection.append(" AND ").append(Message.FLAG_LOADED_SELECTION);
1079             }
1080             return selection.toString();
1081         }
1082     }
1083 
1084     public interface AttachmentColumns {
1085         public static final String ID = "_id";
1086         // The display name of the attachment
1087         public static final String FILENAME = "fileName";
1088         // The mime type of the attachment
1089         public static final String MIME_TYPE = "mimeType";
1090         // The size of the attachment in bytes
1091         public static final String SIZE = "size";
1092         // The (internal) contentId of the attachment (inline attachments will have these)
1093         public static final String CONTENT_ID = "contentId";
1094         // The location of the loaded attachment (probably a file)
1095         @SuppressWarnings("hiding")
1096         public static final String CONTENT_URI = "contentUri";
1097         // A foreign key into the Message table (the message owning this attachment)
1098         public static final String MESSAGE_KEY = "messageKey";
1099         // The location of the attachment on the server side
1100         // For IMAP, this is a part number (e.g. 2.1); for EAS, it's the internal file name
1101         public static final String LOCATION = "location";
1102         // The transfer encoding of the attachment
1103         public static final String ENCODING = "encoding";
1104         // Not currently used
1105         public static final String CONTENT = "content";
1106         // Flags
1107         public static final String FLAGS = "flags";
1108         // Content that is actually contained in the Attachment row
1109         public static final String CONTENT_BYTES = "content_bytes";
1110         // A foreign key into the Account table (for the message owning this attachment)
1111         public static final String ACCOUNT_KEY = "accountKey";
1112         // The UIProvider state of the attachment
1113         public static final String UI_STATE = "uiState";
1114         // The UIProvider destination of the attachment
1115         public static final String UI_DESTINATION = "uiDestination";
1116         // The UIProvider downloaded size of the attachment
1117         public static final String UI_DOWNLOADED_SIZE = "uiDownloadedSize";
1118     }
1119 
1120     public static final class Attachment extends EmailContent
1121             implements AttachmentColumns, Parcelable {
1122         public static final String TABLE_NAME = "Attachment";
1123         @SuppressWarnings("hiding")
1124         public static final Uri CONTENT_URI = Uri.parse(EmailContent.CONTENT_URI + "/attachment");
1125         // This must be used with an appended id: ContentUris.withAppendedId(MESSAGE_ID_URI, id)
1126         public static final Uri MESSAGE_ID_URI = Uri.parse(
1127                 EmailContent.CONTENT_URI + "/attachment/message");
1128 
1129         public String mFileName;
1130         public String mMimeType;
1131         public long mSize;
1132         public String mContentId;
1133         public String mContentUri;
1134         public long mMessageKey;
1135         public String mLocation;
1136         public String mEncoding;
1137         public String mContent; // Not currently used
1138         public int mFlags;
1139         public byte[] mContentBytes;
1140         public long mAccountKey;
1141         public int mUiState;
1142         public int mUiDestination;
1143         public int mUiDownloadedSize;
1144 
1145         public static final int CONTENT_ID_COLUMN = 0;
1146         public static final int CONTENT_FILENAME_COLUMN = 1;
1147         public static final int CONTENT_MIME_TYPE_COLUMN = 2;
1148         public static final int CONTENT_SIZE_COLUMN = 3;
1149         public static final int CONTENT_CONTENT_ID_COLUMN = 4;
1150         public static final int CONTENT_CONTENT_URI_COLUMN = 5;
1151         public static final int CONTENT_MESSAGE_ID_COLUMN = 6;
1152         public static final int CONTENT_LOCATION_COLUMN = 7;
1153         public static final int CONTENT_ENCODING_COLUMN = 8;
1154         public static final int CONTENT_CONTENT_COLUMN = 9; // Not currently used
1155         public static final int CONTENT_FLAGS_COLUMN = 10;
1156         public static final int CONTENT_CONTENT_BYTES_COLUMN = 11;
1157         public static final int CONTENT_ACCOUNT_KEY_COLUMN = 12;
1158         public static final int CONTENT_UI_STATE_COLUMN = 13;
1159         public static final int CONTENT_UI_DESTINATION_COLUMN = 14;
1160         public static final int CONTENT_UI_DOWNLOADED_SIZE_COLUMN = 15;
1161         public static final String[] CONTENT_PROJECTION = new String[] {
1162             RECORD_ID, AttachmentColumns.FILENAME, AttachmentColumns.MIME_TYPE,
1163             AttachmentColumns.SIZE, AttachmentColumns.CONTENT_ID, AttachmentColumns.CONTENT_URI,
1164             AttachmentColumns.MESSAGE_KEY, AttachmentColumns.LOCATION, AttachmentColumns.ENCODING,
1165             AttachmentColumns.CONTENT, AttachmentColumns.FLAGS, AttachmentColumns.CONTENT_BYTES,
1166             AttachmentColumns.ACCOUNT_KEY, AttachmentColumns.UI_STATE,
1167             AttachmentColumns.UI_DESTINATION, AttachmentColumns.UI_DOWNLOADED_SIZE
1168         };
1169 
1170         // All attachments with an empty URI, regardless of mailbox
1171         public static final String PRECACHE_SELECTION =
1172             AttachmentColumns.CONTENT_URI + " isnull AND " + Attachment.FLAGS + "=0";
1173         // Attachments with an empty URI that are in an inbox
1174         public static final String PRECACHE_INBOX_SELECTION =
1175             PRECACHE_SELECTION + " AND " + AttachmentColumns.MESSAGE_KEY + " IN ("
1176             +     "SELECT " + MessageColumns.ID + " FROM " + Message.TABLE_NAME
1177             +     " WHERE " + Message.ALL_INBOX_SELECTION
1178             +     ")";
1179 
1180         // Bits used in mFlags
1181         // WARNING: AttachmentDownloadService relies on the fact that ALL of the flags below
1182         // disqualify attachments for precaching.  If you add a flag that does NOT disqualify an
1183         // attachment for precaching, you MUST change the PRECACHE_SELECTION definition above
1184 
1185         // Instruct Rfc822Output to 1) not use Content-Disposition and 2) use multipart/alternative
1186         // with this attachment.  This is only valid if there is one and only one attachment and
1187         // that attachment has this flag set
1188         public static final int FLAG_ICS_ALTERNATIVE_PART = 1<<0;
1189         // Indicate that this attachment has been requested for downloading by the user; this is
1190         // the highest priority for attachment downloading
1191         public static final int FLAG_DOWNLOAD_USER_REQUEST = 1<<1;
1192         // Indicate that this attachment needs to be downloaded as part of an outgoing forwarded
1193         // message
1194         public static final int FLAG_DOWNLOAD_FORWARD = 1<<2;
1195         // Indicates that the attachment download failed in a non-recoverable manner
1196         public static final int FLAG_DOWNLOAD_FAILED = 1<<3;
1197         // Allow "room" for some additional download-related flags here
1198         // Indicates that the attachment will be smart-forwarded
1199         public static final int FLAG_SMART_FORWARD = 1<<8;
1200         // Indicates that the attachment cannot be forwarded due to a policy restriction
1201         public static final int FLAG_POLICY_DISALLOWS_DOWNLOAD = 1<<9;
1202         /**
1203          * no public constructor since this is a utility class
1204          */
Attachment()1205         public Attachment() {
1206             mBaseUri = CONTENT_URI;
1207         }
1208 
1209          /**
1210          * Restore an Attachment from the database, given its unique id
1211          * @param context
1212          * @param id
1213          * @return the instantiated Attachment
1214          */
restoreAttachmentWithId(Context context, long id)1215         public static Attachment restoreAttachmentWithId(Context context, long id) {
1216             return EmailContent.restoreContentWithId(context, Attachment.class,
1217                     Attachment.CONTENT_URI, Attachment.CONTENT_PROJECTION, id);
1218         }
1219 
1220         /**
1221          * Restore all the Attachments of a message given its messageId
1222          */
restoreAttachmentsWithMessageId(Context context, long messageId)1223         public static Attachment[] restoreAttachmentsWithMessageId(Context context,
1224                 long messageId) {
1225             Uri uri = ContentUris.withAppendedId(MESSAGE_ID_URI, messageId);
1226             Cursor c = context.getContentResolver().query(uri, CONTENT_PROJECTION,
1227                     null, null, null);
1228             try {
1229                 int count = c.getCount();
1230                 Attachment[] attachments = new Attachment[count];
1231                 for (int i = 0; i < count; ++i) {
1232                     c.moveToNext();
1233                     Attachment attach = new Attachment();
1234                     attach.restore(c);
1235                     attachments[i] = attach;
1236                 }
1237                 return attachments;
1238             } finally {
1239                 c.close();
1240             }
1241         }
1242 
1243         /**
1244          * Creates a unique file in the external store by appending a hyphen
1245          * and a number to the given filename.
1246          * @param filename
1247          * @return a new File object, or null if one could not be created
1248          */
createUniqueFile(String filename)1249         public static File createUniqueFile(String filename) {
1250             // TODO Handle internal storage, as required
1251             if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
1252                 File directory = Environment.getExternalStorageDirectory();
1253                 File file = new File(directory, filename);
1254                 if (!file.exists()) {
1255                     return file;
1256                 }
1257                 // Get the extension of the file, if any.
1258                 int index = filename.lastIndexOf('.');
1259                 String name = filename;
1260                 String extension = "";
1261                 if (index != -1) {
1262                     name = filename.substring(0, index);
1263                     extension = filename.substring(index);
1264                 }
1265                 for (int i = 2; i < Integer.MAX_VALUE; i++) {
1266                     file = new File(directory, name + '-' + i + extension);
1267                     if (!file.exists()) {
1268                         return file;
1269                     }
1270                 }
1271                 return null;
1272             }
1273             return null;
1274         }
1275 
1276         @Override
restore(Cursor cursor)1277         public void restore(Cursor cursor) {
1278             mBaseUri = CONTENT_URI;
1279             mId = cursor.getLong(CONTENT_ID_COLUMN);
1280             mFileName= cursor.getString(CONTENT_FILENAME_COLUMN);
1281             mMimeType = cursor.getString(CONTENT_MIME_TYPE_COLUMN);
1282             mSize = cursor.getLong(CONTENT_SIZE_COLUMN);
1283             mContentId = cursor.getString(CONTENT_CONTENT_ID_COLUMN);
1284             mContentUri = cursor.getString(CONTENT_CONTENT_URI_COLUMN);
1285             mMessageKey = cursor.getLong(CONTENT_MESSAGE_ID_COLUMN);
1286             mLocation = cursor.getString(CONTENT_LOCATION_COLUMN);
1287             mEncoding = cursor.getString(CONTENT_ENCODING_COLUMN);
1288             mContent = cursor.getString(CONTENT_CONTENT_COLUMN);
1289             mFlags = cursor.getInt(CONTENT_FLAGS_COLUMN);
1290             mContentBytes = cursor.getBlob(CONTENT_CONTENT_BYTES_COLUMN);
1291             mAccountKey = cursor.getLong(CONTENT_ACCOUNT_KEY_COLUMN);
1292             mUiState = cursor.getInt(CONTENT_UI_STATE_COLUMN);
1293             mUiDestination = cursor.getInt(CONTENT_UI_DESTINATION_COLUMN);
1294             mUiDownloadedSize = cursor.getInt(CONTENT_UI_DOWNLOADED_SIZE_COLUMN);
1295         }
1296 
1297         @Override
toContentValues()1298         public ContentValues toContentValues() {
1299             ContentValues values = new ContentValues();
1300             values.put(AttachmentColumns.FILENAME, mFileName);
1301             values.put(AttachmentColumns.MIME_TYPE, mMimeType);
1302             values.put(AttachmentColumns.SIZE, mSize);
1303             values.put(AttachmentColumns.CONTENT_ID, mContentId);
1304             values.put(AttachmentColumns.CONTENT_URI, mContentUri);
1305             values.put(AttachmentColumns.MESSAGE_KEY, mMessageKey);
1306             values.put(AttachmentColumns.LOCATION, mLocation);
1307             values.put(AttachmentColumns.ENCODING, mEncoding);
1308             values.put(AttachmentColumns.CONTENT, mContent);
1309             values.put(AttachmentColumns.FLAGS, mFlags);
1310             values.put(AttachmentColumns.CONTENT_BYTES, mContentBytes);
1311             values.put(AttachmentColumns.ACCOUNT_KEY, mAccountKey);
1312             values.put(AttachmentColumns.UI_STATE, mUiState);
1313             values.put(AttachmentColumns.UI_DESTINATION, mUiDestination);
1314             values.put(AttachmentColumns.UI_DOWNLOADED_SIZE, mUiDownloadedSize);
1315             return values;
1316         }
1317 
1318         @Override
describeContents()1319         public int describeContents() {
1320              return 0;
1321         }
1322 
1323         @Override
writeToParcel(Parcel dest, int flags)1324         public void writeToParcel(Parcel dest, int flags) {
1325             // mBaseUri is not parceled
1326             dest.writeLong(mId);
1327             dest.writeString(mFileName);
1328             dest.writeString(mMimeType);
1329             dest.writeLong(mSize);
1330             dest.writeString(mContentId);
1331             dest.writeString(mContentUri);
1332             dest.writeLong(mMessageKey);
1333             dest.writeString(mLocation);
1334             dest.writeString(mEncoding);
1335             dest.writeString(mContent);
1336             dest.writeInt(mFlags);
1337             dest.writeLong(mAccountKey);
1338             if (mContentBytes == null) {
1339                 dest.writeInt(-1);
1340             } else {
1341                 dest.writeInt(mContentBytes.length);
1342                 dest.writeByteArray(mContentBytes);
1343             }
1344             dest.writeInt(mUiState);
1345             dest.writeInt(mUiDestination);
1346             dest.writeInt(mUiDownloadedSize);
1347         }
1348 
Attachment(Parcel in)1349         public Attachment(Parcel in) {
1350             mBaseUri = EmailContent.Attachment.CONTENT_URI;
1351             mId = in.readLong();
1352             mFileName = in.readString();
1353             mMimeType = in.readString();
1354             mSize = in.readLong();
1355             mContentId = in.readString();
1356             mContentUri = in.readString();
1357             mMessageKey = in.readLong();
1358             mLocation = in.readString();
1359             mEncoding = in.readString();
1360             mContent = in.readString();
1361             mFlags = in.readInt();
1362             mAccountKey = in.readLong();
1363             final int contentBytesLen = in.readInt();
1364             if (contentBytesLen == -1) {
1365                 mContentBytes = null;
1366             } else {
1367                 mContentBytes = new byte[contentBytesLen];
1368                 in.readByteArray(mContentBytes);
1369             }
1370             mUiState = in.readInt();
1371             mUiDestination = in.readInt();
1372             mUiDownloadedSize = in.readInt();
1373          }
1374 
1375         public static final Parcelable.Creator<EmailContent.Attachment> CREATOR
1376                 = new Parcelable.Creator<EmailContent.Attachment>() {
1377             @Override
1378             public EmailContent.Attachment createFromParcel(Parcel in) {
1379                 return new EmailContent.Attachment(in);
1380             }
1381 
1382             @Override
1383             public EmailContent.Attachment[] newArray(int size) {
1384                 return new EmailContent.Attachment[size];
1385             }
1386         };
1387 
1388         @Override
toString()1389         public String toString() {
1390             return "[" + mFileName + ", " + mMimeType + ", " + mSize + ", " + mContentId + ", "
1391                     + mContentUri + ", " + mMessageKey + ", " + mLocation + ", " + mEncoding  + ", "
1392                     + mFlags + ", " + mContentBytes + ", " + mAccountKey +  "," + mUiState + ","
1393                     + mUiDestination + "," + mUiDownloadedSize + "]";
1394         }
1395     }
1396 
1397     public interface AccountColumns {
1398         public static final String ID = "_id";
1399         // The display name of the account (user-settable)
1400         public static final String DISPLAY_NAME = "displayName";
1401         // The email address corresponding to this account
1402         public static final String EMAIL_ADDRESS = "emailAddress";
1403         // A server-based sync key on an account-wide basis (EAS needs this)
1404         public static final String SYNC_KEY = "syncKey";
1405         // The default sync lookback period for this account
1406         public static final String SYNC_LOOKBACK = "syncLookback";
1407         // The default sync frequency for this account, in minutes
1408         public static final String SYNC_INTERVAL = "syncInterval";
1409         // A foreign key into the account manager, having host, login, password, port, and ssl flags
1410         public static final String HOST_AUTH_KEY_RECV = "hostAuthKeyRecv";
1411         // (optional) A foreign key into the account manager, having host, login, password, port,
1412         // and ssl flags
1413         public static final String HOST_AUTH_KEY_SEND = "hostAuthKeySend";
1414         // Flags
1415         public static final String FLAGS = "flags";
1416         // Default account
1417         public static final String IS_DEFAULT = "isDefault";
1418         // Old-Style UUID for compatibility with previous versions
1419         public static final String COMPATIBILITY_UUID = "compatibilityUuid";
1420         // User name (for outgoing messages)
1421         public static final String SENDER_NAME = "senderName";
1422         // Ringtone
1423         public static final String RINGTONE_URI = "ringtoneUri";
1424         // Protocol version (arbitrary string, used by EAS currently)
1425         public static final String PROTOCOL_VERSION = "protocolVersion";
1426         // The number of new messages (reported by the sync/download engines
1427         public static final String NEW_MESSAGE_COUNT = "newMessageCount";
1428         // Legacy flags defining security (provisioning) requirements of this account; this
1429         // information is now found in the Policy table; POLICY_KEY (below) is the foreign key
1430         @Deprecated
1431         public static final String SECURITY_FLAGS = "securityFlags";
1432         // Server-based sync key for the security policies currently enforced
1433         public static final String SECURITY_SYNC_KEY = "securitySyncKey";
1434         // Signature to use with this account
1435         public static final String SIGNATURE = "signature";
1436         // A foreign key into the Policy table
1437         public static final String POLICY_KEY = "policyKey";
1438         // For compatibility w/ Email1
1439         public static final String NOTIFIED_MESSAGE_ID = "notifiedMessageId";
1440         // For compatibility w/ Email1
1441         public static final String NOTIFIED_MESSAGE_COUNT = "notifiedMessageCount";
1442     }
1443 
1444     public interface QuickResponseColumns {
1445         // The QuickResponse text
1446         static final String TEXT = "quickResponse";
1447         // A foreign key into the Account table owning the QuickResponse
1448         static final String ACCOUNT_KEY = "accountKey";
1449     }
1450 
1451     public interface MailboxColumns {
1452         public static final String ID = "_id";
1453         // The display name of this mailbox [INDEX]
1454         static final String DISPLAY_NAME = "displayName";
1455         // The server's identifier for this mailbox
1456         public static final String SERVER_ID = "serverId";
1457         // The server's identifier for the parent of this mailbox (null = top-level)
1458         public static final String PARENT_SERVER_ID = "parentServerId";
1459         // A foreign key for the parent of this mailbox (-1 = top-level, 0=uninitialized)
1460         public static final String PARENT_KEY = "parentKey";
1461         // A foreign key to the Account that owns this mailbox
1462         public static final String ACCOUNT_KEY = "accountKey";
1463         // The type (role) of this mailbox
1464         public static final String TYPE = "type";
1465         // The hierarchy separator character
1466         public static final String DELIMITER = "delimiter";
1467         // Server-based sync key or validity marker (e.g. "SyncKey" for EAS, "uidvalidity" for IMAP)
1468         public static final String SYNC_KEY = "syncKey";
1469         // The sync lookback period for this mailbox (or null if using the account default)
1470         public static final String SYNC_LOOKBACK = "syncLookback";
1471         // The sync frequency for this mailbox (or null if using the account default)
1472         public static final String SYNC_INTERVAL = "syncInterval";
1473         // The time of last successful sync completion (millis)
1474         public static final String SYNC_TIME = "syncTime";
1475         // Cached unread count
1476         public static final String UNREAD_COUNT = "unreadCount";
1477         // Visibility of this folder in a list of folders [INDEX]
1478         public static final String FLAG_VISIBLE = "flagVisible";
1479         // Other states, as a bit field, e.g. CHILDREN_VISIBLE, HAS_CHILDREN
1480         public static final String FLAGS = "flags";
1481         // Backward compatible
1482         public static final String VISIBLE_LIMIT = "visibleLimit";
1483         // Sync status (can be used as desired by sync services)
1484         public static final String SYNC_STATUS = "syncStatus";
1485         // Number of messages in the mailbox.
1486         public static final String MESSAGE_COUNT = "messageCount";
1487         // The last time a message in this mailbox has been read (in millis)
1488         public static final String LAST_TOUCHED_TIME = "lastTouchedTime";
1489         // The UIProvider sync status
1490         public static final String UI_SYNC_STATUS = "uiSyncStatus";
1491         // The UIProvider last sync result
1492         public static final String UI_LAST_SYNC_RESULT = "uiLastSyncResult";
1493         // The UIProvider sync status
1494         public static final String LAST_NOTIFIED_MESSAGE_KEY = "lastNotifiedMessageKey";
1495         // The UIProvider last sync result
1496         public static final String LAST_NOTIFIED_MESSAGE_COUNT = "lastNotifiedMessageCount";
1497         // The total number of messages in the remote mailbox
1498         public static final String TOTAL_COUNT = "totalCount";
1499         // For compatibility with Email1
1500         public static final String LAST_SEEN_MESSAGE_KEY = "lastSeenMessageKey";
1501     }
1502 
1503     public interface HostAuthColumns {
1504         public static final String ID = "_id";
1505         // The protocol (e.g. "imap", "pop3", "eas", "smtp"
1506         static final String PROTOCOL = "protocol";
1507         // The host address
1508         static final String ADDRESS = "address";
1509         // The port to use for the connection
1510         static final String PORT = "port";
1511         // General purpose flags
1512         static final String FLAGS = "flags";
1513         // The login (user name)
1514         static final String LOGIN = "login";
1515         // Password
1516         static final String PASSWORD = "password";
1517         // A domain or path, if required (used in IMAP and EAS)
1518         static final String DOMAIN = "domain";
1519         // An alias to a local client certificate for SSL
1520         static final String CLIENT_CERT_ALIAS = "certAlias";
1521         // DEPRECATED - Will not be set or stored
1522         static final String ACCOUNT_KEY = "accountKey";
1523     }
1524 
1525     public interface PolicyColumns {
1526         public static final String ID = "_id";
1527         public static final String PASSWORD_MODE = "passwordMode";
1528         public static final String PASSWORD_MIN_LENGTH = "passwordMinLength";
1529         public static final String PASSWORD_EXPIRATION_DAYS = "passwordExpirationDays";
1530         public static final String PASSWORD_HISTORY = "passwordHistory";
1531         public static final String PASSWORD_COMPLEX_CHARS = "passwordComplexChars";
1532         public static final String PASSWORD_MAX_FAILS = "passwordMaxFails";
1533         public static final String MAX_SCREEN_LOCK_TIME = "maxScreenLockTime";
1534         public static final String REQUIRE_REMOTE_WIPE = "requireRemoteWipe";
1535         public static final String REQUIRE_ENCRYPTION = "requireEncryption";
1536         public static final String REQUIRE_ENCRYPTION_EXTERNAL = "requireEncryptionExternal";
1537         // ICS additions
1538         // Note: the appearance of these columns does not imply that we support these features; only
1539         // that we store them in the Policy structure
1540         public static final String REQUIRE_MANUAL_SYNC_WHEN_ROAMING = "requireManualSyncRoaming";
1541         public static final String DONT_ALLOW_CAMERA = "dontAllowCamera";
1542         public static final String DONT_ALLOW_ATTACHMENTS = "dontAllowAttachments";
1543         public static final String DONT_ALLOW_HTML = "dontAllowHtml";
1544         public static final String MAX_ATTACHMENT_SIZE = "maxAttachmentSize";
1545         public static final String MAX_TEXT_TRUNCATION_SIZE = "maxTextTruncationSize";
1546         public static final String MAX_HTML_TRUNCATION_SIZE = "maxHTMLTruncationSize";
1547         public static final String MAX_EMAIL_LOOKBACK = "maxEmailLookback";
1548         public static final String MAX_CALENDAR_LOOKBACK = "maxCalendarLookback";
1549         // Indicates that the server allows password recovery, not that we support it
1550         public static final String PASSWORD_RECOVERY_ENABLED = "passwordRecoveryEnabled";
1551         // Tokenized strings indicating protocol specific policies enforced/unsupported
1552         public static final String PROTOCOL_POLICIES_ENFORCED = "protocolPoliciesEnforced";
1553         public static final String PROTOCOL_POLICIES_UNSUPPORTED = "protocolPoliciesUnsupported";
1554     }
1555 }
1556