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