replicant-packages_apps_Email/emailcommon/src/com/android/emailcommon/provider/EmailContent.java

1855 lines
84 KiB
Java
Raw Normal View History

/*
* Copyright (C) 2009 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.emailcommon.provider;
import android.content.ContentProviderOperation;
import android.content.ContentProviderResult;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.OperationApplicationException;
import android.content.res.Resources;
import android.database.ContentObservable;
import android.database.ContentObserver;
import android.database.Cursor;
import android.net.Uri;
import android.os.Environment;
import android.os.Looper;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.RemoteException;
import android.provider.BaseColumns;
import com.android.emailcommon.utility.TextUtilities;
import com.android.emailcommon.utility.Utility;
import com.android.emailcommon.Logging;
import com.android.emailcommon.R;
import com.android.mail.providers.UIProvider;
import com.android.mail.utils.LogUtils;
import com.google.common.annotations.VisibleForTesting;
import org.apache.commons.io.IOUtils;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
/**
* EmailContent is the superclass of the various classes of content stored by EmailProvider.
*
* It is intended to include 1) column definitions for use with the Provider, and 2) convenience
* methods for saving and retrieving content from the Provider.
*
* This class will be used by 1) the Email process (which includes the application and
* EmaiLProvider) as well as 2) the Exchange process (which runs independently). It will
* necessarily be cloned for use in these two cases.
*
* Conventions used in naming columns:
* BaseColumns._ID is the primary key for all Email records
* The SyncColumns interface is used by all classes that are synced to the server directly
* (Mailbox and Email)
*
* <name>_KEY always refers to a foreign key
* <name>_ID always refers to a unique identifier (whether on client, server, etc.)
*
*/
public abstract class EmailContent {
public static final int NOTIFICATION_MAILBOX_ID_COLUMN = 0;
public static final int NOTIFICATION_MAILBOX_UNREAD_COUNT_COLUMN = 1;
public static final int NOTIFICATION_MAILBOX_UNSEEN_COUNT_COLUMN = 2;
// All classes share this
// Use BaseColumns._ID instead
@Deprecated
public static final String RECORD_ID = "_id";
public static final String[] COUNT_COLUMNS = {"count(*)"};
/**
* This projection can be used with any of the EmailContent classes, when all you need
* is a list of id's. Use ID_PROJECTION_COLUMN to access the row data.
*/
public static final String[] ID_PROJECTION = { BaseColumns._ID };
public static final int ID_PROJECTION_COLUMN = 0;
public static final String ID_SELECTION = BaseColumns._ID + " =?";
public static final int SYNC_STATUS_NONE = UIProvider.SyncStatus.NO_SYNC;
public static final int SYNC_STATUS_USER = UIProvider.SyncStatus.USER_REFRESH;
public static final int SYNC_STATUS_BACKGROUND = UIProvider.SyncStatus.BACKGROUND_SYNC;
public static final int SYNC_STATUS_LIVE = UIProvider.SyncStatus.LIVE_QUERY;
public static final int LAST_SYNC_RESULT_SUCCESS = UIProvider.LastSyncResult.SUCCESS;
public static final int LAST_SYNC_RESULT_AUTH_ERROR = UIProvider.LastSyncResult.AUTH_ERROR;
public static final int LAST_SYNC_RESULT_SECURITY_ERROR =
UIProvider.LastSyncResult.SECURITY_ERROR;
public static final int LAST_SYNC_RESULT_CONNECTION_ERROR =
UIProvider.LastSyncResult.CONNECTION_ERROR;
public static final int LAST_SYNC_RESULT_INTERNAL_ERROR =
UIProvider.LastSyncResult.INTERNAL_ERROR;
// Newly created objects get this id
public static final int NOT_SAVED = -1;
// The base Uri that this piece of content came from
public Uri mBaseUri;
// Lazily initialized uri for this Content
private Uri mUri = null;
// The id of the Content
public long mId = NOT_SAVED;
/**
* Since we close the cursor we use to generate this object, and possibly create the object
* without using any cursor at all (eg: parcel), we need to handle observing provider changes
* ourselves. This content observer uses a weak reference to keep from rooting this object
* in the ContentResolver in case it is not properly disposed of using {@link #close(Context)}
*/
private SelfContentObserver mSelfObserver;
private ContentObservable mObservable;
// Write the Content into a ContentValues container
public abstract ContentValues toContentValues();
// Read the Content from a ContentCursor
public abstract void restore(Cursor cursor);
// Same as above, with the addition of a context to retrieve extra content.
// Body uses this to fetch the email body html/text from the provider bypassing the cursor
// Not always safe to call on the UI thread.
public void restore(Context context, Cursor cursor) {
restore(cursor);
}
public static String EMAIL_PACKAGE_NAME;
public static String AUTHORITY;
// The notifier authority is used to send notifications regarding changes to messages (insert,
// delete, or update) and is intended as an optimization for use by clients of message list
// cursors (initially, the email AppWidget).
public static String NOTIFIER_AUTHORITY;
public static Uri CONTENT_URI;
public static final String PARAMETER_LIMIT = "limit";
/**
* Query parameter for the UI accounts query to enable suppression of the combined account.
*/
public static final String SUPPRESS_COMBINED_ACCOUNT_PARAM = "suppress_combined";
public static Uri CONTENT_NOTIFIER_URI;
public static Uri PICK_TRASH_FOLDER_URI;
public static Uri PICK_SENT_FOLDER_URI;
public static Uri MAILBOX_NOTIFICATION_URI;
public static Uri MAILBOX_MOST_RECENT_MESSAGE_URI;
public static Uri ACCOUNT_CHECK_URI;
/**
* String for both the EmailProvider call, and the key for the value in the response.
* TODO: Eventually this ought to be a device property, not defined by the app.
*/
public static String DEVICE_FRIENDLY_NAME = "deviceFriendlyName";
public static String PROVIDER_PERMISSION;
public static synchronized void init(Context context) {
if (AUTHORITY == null) {
final Resources res = context.getResources();
EMAIL_PACKAGE_NAME = res.getString(R.string.email_package_name);
AUTHORITY = EMAIL_PACKAGE_NAME + ".provider";
LogUtils.d("EmailContent", "init for " + AUTHORITY);
NOTIFIER_AUTHORITY = EMAIL_PACKAGE_NAME + ".notifier";
CONTENT_URI = Uri.parse("content://" + AUTHORITY);
CONTENT_NOTIFIER_URI = Uri.parse("content://" + NOTIFIER_AUTHORITY);
PICK_TRASH_FOLDER_URI = Uri.parse("content://" + AUTHORITY + "/pickTrashFolder");
PICK_SENT_FOLDER_URI = Uri.parse("content://" + AUTHORITY + "/pickSentFolder");
MAILBOX_NOTIFICATION_URI = Uri.parse("content://" + AUTHORITY + "/mailboxNotification");
MAILBOX_MOST_RECENT_MESSAGE_URI = Uri.parse("content://" + AUTHORITY +
"/mailboxMostRecentMessage");
ACCOUNT_CHECK_URI = Uri.parse("content://" + AUTHORITY + "/accountCheck");
PROVIDER_PERMISSION = EMAIL_PACKAGE_NAME + ".permission.ACCESS_PROVIDER";
// Initialize subclasses
Account.initAccount();
Mailbox.initMailbox();
QuickResponse.initQuickResponse();
HostAuth.initHostAuth();
Credential.initCredential();
Policy.initPolicy();
Message.initMessage();
MessageMove.init();
MessageStateChange.init();
Body.initBody();
Attachment.initAttachment();
}
}
private static void warnIfUiThread() {
if (Looper.getMainLooper().getThread() == Thread.currentThread()) {
LogUtils.w(Logging.LOG_TAG, new Throwable(), "Method called on the UI thread");
}
}
public static boolean isInitialSyncKey(final String syncKey) {
return syncKey == null || syncKey.isEmpty() || syncKey.equals("0");
}
// The Uri is lazily initialized
public Uri getUri() {
if (mUri == null) {
mUri = ContentUris.withAppendedId(mBaseUri, mId);
}
return mUri;
}
public boolean isSaved() {
return mId != NOT_SAVED;
}
/**
* Restore a subclass of EmailContent from the database
* @param context the caller's context
* @param klass the class to restore
* @param contentUri the content uri of the EmailContent subclass
* @param contentProjection the content projection for the EmailContent subclass
* @param id the unique id of the object
* @return the instantiated object
*/
public static <T extends EmailContent> T restoreContentWithId(Context context,
Class<T> klass, Uri contentUri, String[] contentProjection, long id) {
return restoreContentWithId(context, klass, contentUri, contentProjection, id, null);
}
public static <T extends EmailContent> T restoreContentWithId(final Context context,
final Class<T> klass, final Uri contentUri, final String[] contentProjection,
final long id, final ContentObserver observer) {
warnIfUiThread();
final Uri u = ContentUris.withAppendedId(contentUri, id);
final Cursor c = context.getContentResolver().query(u, contentProjection, null, null, null);
if (c == null) throw new ProviderUnavailableException();
try {
if (c.moveToFirst()) {
final T content = getContent(context, c, klass);
if (observer != null) {
content.registerObserver(context, observer);
}
return content;
} else {
return null;
}
} finally {
c.close();
}
}
/**
* Register a content observer to be notified when the data underlying this object changes
* @param observer ContentObserver to register
*/
public synchronized void registerObserver(final Context context, final ContentObserver observer) {
if (mSelfObserver == null) {
mSelfObserver = new SelfContentObserver(this);
context.getContentResolver().registerContentObserver(getContentNotificationUri(),
true, mSelfObserver);
mObservable = new ContentObservable();
}
mObservable.registerObserver(observer);
}
/**
* Unregister a content observer previously registered with
* {@link #registerObserver(Context, ContentObserver)}
* @param observer ContentObserver to unregister
*/
public synchronized void unregisterObserver(final ContentObserver observer) {
if (mObservable == null) {
throw new IllegalStateException("Unregistering with null observable");
}
mObservable.unregisterObserver(observer);
}
/**
* Unregister all content observers previously registered with
* {@link #registerObserver(Context, ContentObserver)}
*/
public synchronized void unregisterAllObservers() {
if (mObservable == null) {
throw new IllegalStateException("Unregistering with null observable");
}
mObservable.unregisterAll();
}
/**
* Unregister all content observers previously registered with
* {@link #registerObserver(Context, ContentObserver)} and release internal resources associated
* with content observing
*/
public synchronized void close(final Context context) {
if (mSelfObserver == null) {
return;
}
unregisterAllObservers();
context.getContentResolver().unregisterContentObserver(mSelfObserver);
mSelfObserver = null;
}
/**
* Returns a Uri for observing the underlying content. Subclasses that wish to implement content
* observing must override this method.
* @return Uri for registering content notifications
*/
protected Uri getContentNotificationUri() {
throw new UnsupportedOperationException(
"Subclasses must override this method for content observation to work");
}
/**
* This method is called when the underlying data has changed, and notifies registered observers
* @param selfChange true if this is a self-change notification
*/
@SuppressWarnings("deprecation")
public synchronized void onChange(final boolean selfChange) {
if (mObservable != null) {
mObservable.dispatchChange(selfChange);
}
}
/**
* A content observer that calls {@link #onChange(boolean)} when triggered
*/
private static class SelfContentObserver extends ContentObserver {
WeakReference<EmailContent> mContent;
public SelfContentObserver(final EmailContent content) {
super(null);
mContent = new WeakReference<EmailContent>(content);
}
@Override
public boolean deliverSelfNotifications() {
return false;
}
@Override
public void onChange(final boolean selfChange) {
EmailContent content = mContent.get();
if (content != null) {
content.onChange(false);
}
}
}
// The Content sub class must have a no-arg constructor
static public <T extends EmailContent> T getContent(final Context context, final Cursor cursor,
final Class<T> klass) {
try {
T content = klass.newInstance();
content.mId = cursor.getLong(0);
content.restore(context, cursor);
return content;
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
return null;
}
public Uri save(Context context) {
if (isSaved()) {
throw new UnsupportedOperationException();
}
Uri res = context.getContentResolver().insert(mBaseUri, toContentValues());
mId = Long.parseLong(res.getPathSegments().get(1));
return res;
}
public int update(Context context, ContentValues contentValues) {
if (!isSaved()) {
throw new UnsupportedOperationException();
}
return context.getContentResolver().update(getUri(), contentValues, null, null);
}
static public int update(Context context, Uri baseUri, long id, ContentValues contentValues) {
return context.getContentResolver()
.update(ContentUris.withAppendedId(baseUri, id), contentValues, null, null);
}
static public int delete(Context context, Uri baseUri, long id) {
return context.getContentResolver()
.delete(ContentUris.withAppendedId(baseUri, id), null, null);
}
/**
* Generic count method that can be used for any ContentProvider
*
* @param context the calling Context
* @param uri the Uri for the provider query
* @param selection as with a query call
* @param selectionArgs as with a query call
* @return the number of items matching the query (or zero)
*/
static public int count(Context context, Uri uri, String selection, String[] selectionArgs) {
return Utility.getFirstRowLong(context,
uri, COUNT_COLUMNS, selection, selectionArgs, null, 0, 0L).intValue();
}
/**
* Same as {@link #count(Context, Uri, String, String[])} without selection.
*/
static public int count(Context context, Uri uri) {
return count(context, uri, null, null);
}
static public Uri uriWithLimit(Uri uri, int limit) {
return uri.buildUpon().appendQueryParameter(EmailContent.PARAMETER_LIMIT,
Integer.toString(limit)).build();
}
/**
* no public constructor since this is a utility class
*/
protected EmailContent() {
}
public interface SyncColumns {
// source id (string) : the source's name of this item
public static final String SERVER_ID = "syncServerId";
// source's timestamp (long) for this item
public static final String SERVER_TIMESTAMP = "syncServerTimeStamp";
}
public interface BodyColumns extends BaseColumns {
// Foreign key to the message corresponding to this body
public static final String MESSAGE_KEY = "messageKey";
// The html content itself, not returned on query
public static final String HTML_CONTENT = "htmlContent";
// The html content URI, for ContentResolver#openFileDescriptor()
public static final String HTML_CONTENT_URI = "htmlContentUri";
// The plain text content itself, not returned on query
public static final String TEXT_CONTENT = "textContent";
// The text content URI, for ContentResolver#openFileDescriptor()
public static final String TEXT_CONTENT_URI = "textContentUri";
// Replied-to or forwarded body (in html form)
@Deprecated
public static final String HTML_REPLY = "htmlReply";
// Replied-to or forwarded body (in text form)
@Deprecated
public static final String TEXT_REPLY = "textReply";
// A reference to a message's unique id used in reply/forward.
// Protocol code can be expected to use this column in determining whether a message can be
// deleted safely (i.e. isn't referenced by other messages)
public static final String SOURCE_MESSAGE_KEY = "sourceMessageKey";
// The text to be placed between a reply/forward response and the original message
@Deprecated
public static final String INTRO_TEXT = "introText";
// The start of quoted text within our text content
public static final String QUOTED_TEXT_START_POS = "quotedTextStartPos";
}
public static final class Body extends EmailContent {
public static final String TABLE_NAME = "Body";
public static final String SELECTION_BY_MESSAGE_KEY = BodyColumns.MESSAGE_KEY + "=?";
public static Uri CONTENT_URI;
public static void initBody() {
CONTENT_URI = Uri.parse(EmailContent.CONTENT_URI + "/body");
}
public static final String[] CONTENT_PROJECTION = new String[] {
BaseColumns._ID,
BodyColumns.MESSAGE_KEY,
BodyColumns.HTML_CONTENT_URI,
BodyColumns.TEXT_CONTENT_URI,
BodyColumns.SOURCE_MESSAGE_KEY,
BodyColumns.QUOTED_TEXT_START_POS
};
public static final int CONTENT_ID_COLUMN = 0;
public static final int CONTENT_MESSAGE_KEY_COLUMN = 1;
public static final int CONTENT_HTML_URI_COLUMN = 2;
public static final int CONTENT_TEXT_URI_COLUMN = 3;
public static final int CONTENT_SOURCE_KEY_COLUMN = 4;
public static final int CONTENT_QUOTED_TEXT_START_POS_COLUMN = 5;
private static final String[] PROJECTION_SOURCE_KEY =
new String[] {BaseColumns._ID, BodyColumns.SOURCE_MESSAGE_KEY};
public long mMessageKey;
public String mHtmlContent;
public String mTextContent;
public int mQuotedTextStartPos;
/**
* Points to the ID of the message being replied to or forwarded. Will always be set.
*/
public long mSourceKey;
public Body() {
mBaseUri = CONTENT_URI;
}
@Override
public ContentValues toContentValues() {
ContentValues values = new ContentValues();
// Assign values for each row.
values.put(BodyColumns.MESSAGE_KEY, mMessageKey);
values.put(BodyColumns.HTML_CONTENT, mHtmlContent);
values.put(BodyColumns.TEXT_CONTENT, mTextContent);
values.put(BodyColumns.SOURCE_MESSAGE_KEY, mSourceKey);
return values;
}
/**
* Given a cursor, restore a Body from it
* @param cursor a cursor which must NOT be null
* @return the Body as restored from the cursor
*/
private static Body restoreBodyWithCursor(final Context context, final Cursor cursor) {
try {
if (cursor.moveToFirst()) {
return getContent(context, cursor, Body.class);
} else {
return null;
}
} finally {
cursor.close();
}
}
public static Body restoreBodyWithMessageId(Context context, long messageId) {
Cursor c = context.getContentResolver().query(Body.CONTENT_URI,
Body.CONTENT_PROJECTION, BodyColumns.MESSAGE_KEY + "=?",
new String[] {Long.toString(messageId)}, null);
if (c == null) throw new ProviderUnavailableException();
return restoreBodyWithCursor(context, c);
}
/**
* Returns the bodyId for the given messageId, or -1 if no body is found.
*/
public static long lookupBodyIdWithMessageId(Context context, long messageId) {
return Utility.getFirstRowLong(context, Body.CONTENT_URI,
ID_PROJECTION, BodyColumns.MESSAGE_KEY + "=?",
new String[] {Long.toString(messageId)}, null, ID_PROJECTION_COLUMN, -1L);
}
/**
* Updates the Body for a messageId with the given ContentValues.
* If the message has no body, a new body is inserted for the message.
* Warning: the argument "values" is modified by this method, setting MESSAGE_KEY.
*/
public static void updateBodyWithMessageId(Context context, long messageId,
ContentValues values) {
ContentResolver resolver = context.getContentResolver();
long bodyId = lookupBodyIdWithMessageId(context, messageId);
values.put(BodyColumns.MESSAGE_KEY, messageId);
if (bodyId == -1) {
resolver.insert(CONTENT_URI, values);
} else {
final Uri uri = ContentUris.withAppendedId(CONTENT_URI, bodyId);
resolver.update(uri, values, null, null);
}
}
@VisibleForTesting
public static long restoreBodySourceKey(Context context, long messageId) {
return Utility.getFirstRowLong(context, Body.CONTENT_URI,
Body.PROJECTION_SOURCE_KEY,
BodyColumns.MESSAGE_KEY + "=?", new String[] {Long.toString(messageId)}, null,
0, 0L);
}
public static Uri getBodyTextUriForMessageWithId(long messageId) {
return EmailContent.CONTENT_URI.buildUpon()
.appendPath("bodyText").appendPath(Long.toString(messageId)).build();
}
public static Uri getBodyHtmlUriForMessageWithId(long messageId) {
return EmailContent.CONTENT_URI.buildUpon()
.appendPath("bodyHtml").appendPath(Long.toString(messageId)).build();
}
public static String restoreBodyTextWithMessageId(Context context, long messageId) {
return readBodyFromProvider(context,
getBodyTextUriForMessageWithId(messageId).toString());
}
public static String restoreBodyHtmlWithMessageId(Context context, long messageId) {
return readBodyFromProvider(context,
getBodyHtmlUriForMessageWithId(messageId).toString());
}
private static String readBodyFromProvider(final Context context, final String uri) {
String content = null;
try {
final InputStream bodyInput =
context.getContentResolver().openInputStream(Uri.parse(uri));
try {
content = IOUtils.toString(bodyInput);
} finally {
bodyInput.close();
}
} catch (final IOException e) {
LogUtils.v(LogUtils.TAG, e, "Exception while reading body content");
}
return content;
}
@Override
public void restore(final Cursor cursor) {
throw new UnsupportedOperationException("Must have context to restore Body object");
}
@Override
public void restore(final Context context, final Cursor cursor) {
warnIfUiThread();
mBaseUri = EmailContent.Body.CONTENT_URI;
mMessageKey = cursor.getLong(CONTENT_MESSAGE_KEY_COLUMN);
// These get overwritten below if we find a file descriptor in the respond() call,
// but we'll keep this here in case we want to construct a matrix cursor or something
// to build a Body object from.
mHtmlContent = readBodyFromProvider(context, cursor.getString(CONTENT_HTML_URI_COLUMN));
mTextContent = readBodyFromProvider(context, cursor.getString(CONTENT_TEXT_URI_COLUMN));
mSourceKey = cursor.getLong(CONTENT_SOURCE_KEY_COLUMN);
mQuotedTextStartPos = cursor.getInt(CONTENT_QUOTED_TEXT_START_POS_COLUMN);
}
}
public interface MessageColumns extends BaseColumns, SyncColumns {
// Basic columns used in message list presentation
// The name as shown to the user in a message list
public static final String DISPLAY_NAME = "displayName";
// The time (millis) as shown to the user in a message list [INDEX]
public static final String TIMESTAMP = "timeStamp";
// Message subject
public static final String SUBJECT = "subject";
// Boolean, unread = 0, read = 1 [INDEX]
public static final String FLAG_READ = "flagRead";
// Load state, see constants below (unloaded, partial, complete, deleted)
public static final String FLAG_LOADED = "flagLoaded";
// Boolean, unflagged = 0, flagged (favorite) = 1
public static final String FLAG_FAVORITE = "flagFavorite";
// Boolean, no attachment = 0, attachment = 1
public static final String FLAG_ATTACHMENT = "flagAttachment";
// Bit field for flags which we'll not be selecting on
public static final String FLAGS = "flags";
// Sync related identifiers
// Saved draft info (reusing the never-used "clientId" column)
public static final String DRAFT_INFO = "clientId";
// The message-id in the message's header
public static final String MESSAGE_ID = "messageId";
// References to other Email objects in the database
// Foreign key to the Mailbox holding this message [INDEX]
// TODO: This column is used in a complicated way: Usually, this refers to the mailbox
// the server considers this message to be in. In the case of search results, this key
// will refer to a special "search" mailbox, which does not exist on the server.
// This is confusing and causes problems, see b/11294681.
public static final String MAILBOX_KEY = "mailboxKey";
// Foreign key to the Account holding this message
public static final String ACCOUNT_KEY = "accountKey";
// Address lists, packed with Address.pack()
public static final String FROM_LIST = "fromList";
public static final String TO_LIST = "toList";
public static final String CC_LIST = "ccList";
public static final String BCC_LIST = "bccList";
public static final String REPLY_TO_LIST = "replyToList";
// Meeting invitation related information (for now, start time in ms)
public static final String MEETING_INFO = "meetingInfo";
// A text "snippet" derived from the body of the message
public static final String SNIPPET = "snippet";
// A column that can be used by sync adapters to store search-related information about
// a retrieved message (the messageKey for search results will be a TYPE_SEARCH mailbox
// and the sync adapter might, for example, need more information about the original source
// of the message)
public static final String PROTOCOL_SEARCH_INFO = "protocolSearchInfo";
// Simple thread topic
public static final String THREAD_TOPIC = "threadTopic";
// For sync adapter use
public static final String SYNC_DATA = "syncData";
/** Boolean, unseen = 0, seen = 1 [INDEX] */
public static final String FLAG_SEEN = "flagSeen";
// References to other Email objects in the database
// Foreign key to the Mailbox holding this message [INDEX]
// In cases where mailboxKey is NOT the real mailbox the server considers this message in,
// this will be set. See b/11294681
// We'd like to get rid of this column when the other changes mentioned in that bug
// can be addressed.
public static final String MAIN_MAILBOX_KEY = "mainMailboxKey";
}
public static final class Message extends EmailContent {
private static final String LOG_TAG = "Email";
public static final String TABLE_NAME = "Message";
public static final String UPDATED_TABLE_NAME = "Message_Updates";
public static final String DELETED_TABLE_NAME = "Message_Deletes";
// To refer to a specific message, use ContentUris.withAppendedId(CONTENT_URI, id)
public static Uri CONTENT_URI;
public static Uri CONTENT_URI_LIMIT_1;
public static Uri SYNCED_CONTENT_URI;
public static Uri SELECTED_MESSAGE_CONTENT_URI ;
public static Uri DELETED_CONTENT_URI;
public static Uri UPDATED_CONTENT_URI;
public static Uri NOTIFIER_URI;
public static void initMessage() {
CONTENT_URI = Uri.parse(EmailContent.CONTENT_URI + "/message");
CONTENT_URI_LIMIT_1 = uriWithLimit(CONTENT_URI, 1);
SYNCED_CONTENT_URI =
Uri.parse(EmailContent.CONTENT_URI + "/syncedMessage");
SELECTED_MESSAGE_CONTENT_URI =
Uri.parse(EmailContent.CONTENT_URI + "/messageBySelection");
DELETED_CONTENT_URI =
Uri.parse(EmailContent.CONTENT_URI + "/deletedMessage");
UPDATED_CONTENT_URI =
Uri.parse(EmailContent.CONTENT_URI + "/updatedMessage");
NOTIFIER_URI =
Uri.parse(EmailContent.CONTENT_NOTIFIER_URI + "/message");
}
public static final int CONTENT_ID_COLUMN = 0;
public static final int CONTENT_DISPLAY_NAME_COLUMN = 1;
public static final int CONTENT_TIMESTAMP_COLUMN = 2;
public static final int CONTENT_SUBJECT_COLUMN = 3;
public static final int CONTENT_FLAG_READ_COLUMN = 4;
public static final int CONTENT_FLAG_LOADED_COLUMN = 5;
public static final int CONTENT_FLAG_FAVORITE_COLUMN = 6;
public static final int CONTENT_FLAG_ATTACHMENT_COLUMN = 7;
public static final int CONTENT_FLAGS_COLUMN = 8;
public static final int CONTENT_SERVER_ID_COLUMN = 9;
public static final int CONTENT_DRAFT_INFO_COLUMN = 10;
public static final int CONTENT_MESSAGE_ID_COLUMN = 11;
public static final int CONTENT_MAILBOX_KEY_COLUMN = 12;
public static final int CONTENT_ACCOUNT_KEY_COLUMN = 13;
public static final int CONTENT_FROM_LIST_COLUMN = 14;
public static final int CONTENT_TO_LIST_COLUMN = 15;
public static final int CONTENT_CC_LIST_COLUMN = 16;
public static final int CONTENT_BCC_LIST_COLUMN = 17;
public static final int CONTENT_REPLY_TO_COLUMN = 18;
public static final int CONTENT_SERVER_TIMESTAMP_COLUMN = 19;
public static final int CONTENT_MEETING_INFO_COLUMN = 20;
public static final int CONTENT_SNIPPET_COLUMN = 21;
public static final int CONTENT_PROTOCOL_SEARCH_INFO_COLUMN = 22;
public static final int CONTENT_THREAD_TOPIC_COLUMN = 23;
public static final int CONTENT_SYNC_DATA_COLUMN = 24;
public static final int CONTENT_FLAG_SEEN_COLUMN = 25;
public static final int CONTENT_MAIN_MAILBOX_KEY_COLUMN = 26;
public static final String[] CONTENT_PROJECTION = {
MessageColumns._ID,
MessageColumns.DISPLAY_NAME, MessageColumns.TIMESTAMP,
MessageColumns.SUBJECT, MessageColumns.FLAG_READ,
MessageColumns.FLAG_LOADED, MessageColumns.FLAG_FAVORITE,
MessageColumns.FLAG_ATTACHMENT, MessageColumns.FLAGS,
SyncColumns.SERVER_ID, MessageColumns.DRAFT_INFO,
MessageColumns.MESSAGE_ID, MessageColumns.MAILBOX_KEY,
MessageColumns.ACCOUNT_KEY, MessageColumns.FROM_LIST,
MessageColumns.TO_LIST, MessageColumns.CC_LIST,
MessageColumns.BCC_LIST, MessageColumns.REPLY_TO_LIST,
SyncColumns.SERVER_TIMESTAMP, MessageColumns.MEETING_INFO,
MessageColumns.SNIPPET, MessageColumns.PROTOCOL_SEARCH_INFO,
MessageColumns.THREAD_TOPIC, MessageColumns.SYNC_DATA,
MessageColumns.FLAG_SEEN, MessageColumns.MAIN_MAILBOX_KEY
};
public static final int LIST_ID_COLUMN = 0;
public static final int LIST_DISPLAY_NAME_COLUMN = 1;
public static final int LIST_TIMESTAMP_COLUMN = 2;
public static final int LIST_SUBJECT_COLUMN = 3;
public static final int LIST_READ_COLUMN = 4;
public static final int LIST_LOADED_COLUMN = 5;
public static final int LIST_FAVORITE_COLUMN = 6;
public static final int LIST_ATTACHMENT_COLUMN = 7;
public static final int LIST_FLAGS_COLUMN = 8;
public static final int LIST_MAILBOX_KEY_COLUMN = 9;
public static final int LIST_ACCOUNT_KEY_COLUMN = 10;
public static final int LIST_SERVER_ID_COLUMN = 11;
public static final int LIST_SNIPPET_COLUMN = 12;
// Public projection for common list columns
public static final String[] LIST_PROJECTION = {
MessageColumns._ID,
MessageColumns.DISPLAY_NAME, MessageColumns.TIMESTAMP,
MessageColumns.SUBJECT, MessageColumns.FLAG_READ,
MessageColumns.FLAG_LOADED, MessageColumns.FLAG_FAVORITE,
MessageColumns.FLAG_ATTACHMENT, MessageColumns.FLAGS,
MessageColumns.MAILBOX_KEY, MessageColumns.ACCOUNT_KEY,
SyncColumns.SERVER_ID, MessageColumns.SNIPPET
};
public static final int ID_COLUMNS_ID_COLUMN = 0;
public static final int ID_COLUMNS_SYNC_SERVER_ID = 1;
public static final String[] ID_COLUMNS_PROJECTION = {
MessageColumns._ID, SyncColumns.SERVER_ID
};
public static final String[] ID_COLUMN_PROJECTION = { MessageColumns._ID };
public static final String ACCOUNT_KEY_SELECTION =
MessageColumns.ACCOUNT_KEY + "=?";
public static final String[] MAILBOX_KEY_PROJECTION = { MessageColumns.MAILBOX_KEY };
/**
* Selection for messages that are loaded
*
* POP messages at the initial stage have very little information. (Server UID only)
* Use this to make sure they're not visible on any UI.
* This means unread counts on the mailbox list can be different from the
* number of messages in the message list, but it should be transient...
*/
public static final String FLAG_LOADED_SELECTION =
MessageColumns.FLAG_LOADED + " IN ("
+ Message.FLAG_LOADED_PARTIAL + "," + Message.FLAG_LOADED_COMPLETE
+ ")";
public static final String ALL_FAVORITE_SELECTION =
MessageColumns.FLAG_FAVORITE + "=1 AND "
+ MessageColumns.MAILBOX_KEY + " NOT IN ("
+ "SELECT " + MailboxColumns._ID + " FROM " + Mailbox.TABLE_NAME + ""
+ " WHERE " + MailboxColumns.TYPE + " = " + Mailbox.TYPE_TRASH
+ ")"
+ " AND " + FLAG_LOADED_SELECTION;
/** Selection to retrieve all messages in "inbox" for any account */
public static final String ALL_INBOX_SELECTION =
MessageColumns.MAILBOX_KEY + " IN ("
+ "SELECT " + MailboxColumns._ID + " FROM " + Mailbox.TABLE_NAME
+ " WHERE " + MailboxColumns.TYPE + " = " + Mailbox.TYPE_INBOX
+ ")"
+ " AND " + FLAG_LOADED_SELECTION;
/** Selection to retrieve all messages in "drafts" for any account */
public static final String ALL_DRAFT_SELECTION =
MessageColumns.MAILBOX_KEY + " IN ("
+ "SELECT " + MailboxColumns._ID + " FROM " + Mailbox.TABLE_NAME
+ " WHERE " + MailboxColumns.TYPE + " = " + Mailbox.TYPE_DRAFTS
+ ")"
+ " AND " + FLAG_LOADED_SELECTION;
/** Selection to retrieve all messages in "outbox" for any account */
public static final String ALL_OUTBOX_SELECTION =
MessageColumns.MAILBOX_KEY + " IN ("
+ "SELECT " + MailboxColumns._ID + " FROM " + Mailbox.TABLE_NAME
+ " WHERE " + MailboxColumns.TYPE + " = " + Mailbox.TYPE_OUTBOX
+ ")"; // NOTE No flag_loaded test for outboxes.
/** Selection to retrieve unread messages in "inbox" for any account */
public static final String ALL_UNREAD_SELECTION =
MessageColumns.FLAG_READ + "=0 AND " + ALL_INBOX_SELECTION;
/** Selection to retrieve unread messages in "inbox" for one account */
public static final String PER_ACCOUNT_UNREAD_SELECTION =
ACCOUNT_KEY_SELECTION + " AND " + ALL_UNREAD_SELECTION;
/** Selection to retrieve all messages in "inbox" for one account */
public static final String PER_ACCOUNT_INBOX_SELECTION =
ACCOUNT_KEY_SELECTION + " AND " + ALL_INBOX_SELECTION;
public static final String PER_ACCOUNT_FAVORITE_SELECTION =
ACCOUNT_KEY_SELECTION + " AND " + ALL_FAVORITE_SELECTION;
public static final String MAILBOX_SELECTION = MessageColumns.MAILBOX_KEY + "=?";
// _id field is in AbstractContent
public String mDisplayName;
public long mTimeStamp;
public String mSubject;
public boolean mFlagRead = false;
public boolean mFlagSeen = false;
public int mFlagLoaded = FLAG_LOADED_UNLOADED;
public boolean mFlagFavorite = false;
public boolean mFlagAttachment = false;
public int mFlags = 0;
public String mServerId;
public long mServerTimeStamp;
public int mDraftInfo;
public String mMessageId;
public long mMailboxKey;
public long mAccountKey;
public long mMainMailboxKey;
public String mFrom;
public String mTo;
public String mCc;
public String mBcc;
public String mReplyTo;
// For now, just the start time of a meeting invite, in ms
public String mMeetingInfo;
public String mSnippet;
public String mProtocolSearchInfo;
public String mThreadTopic;
public String mSyncData;
/**
* Base64-encoded representation of the byte array provided by servers for identifying
* messages belonging to the same conversation thread. Currently unsupported and not
* persisted in the database.
*/
public String mServerConversationId;
// The following transient members may be used while building and manipulating messages,
// but they are NOT persisted directly by EmailProvider. See Body for related fields.
transient public String mText;
transient public String mHtml;
transient public long mSourceKey;
transient public ArrayList<Attachment> mAttachments = null;
transient public int mQuotedTextStartPos;
// Values used in mFlagRead
public static final int UNREAD = 0;
public static final int READ = 1;
// Values used in mFlagLoaded
public static final int FLAG_LOADED_UNLOADED = 0;
public static final int FLAG_LOADED_COMPLETE = 1;
public static final int FLAG_LOADED_PARTIAL = 2;
public static final int FLAG_LOADED_DELETED = 3;
public static final int FLAG_LOADED_UNKNOWN = 4;
// Bits used in mFlags
// The following three states are mutually exclusive, and indicate whether the message is an
// original, a reply, or a forward
public static final int FLAG_TYPE_REPLY = 1<<0;
public static final int FLAG_TYPE_FORWARD = 1<<1;
public static final int FLAG_TYPE_MASK = FLAG_TYPE_REPLY | FLAG_TYPE_FORWARD;
// The following flags indicate messages that are determined to be incoming meeting related
// (e.g. invites from others)
public static final int FLAG_INCOMING_MEETING_INVITE = 1<<2;
public static final int FLAG_INCOMING_MEETING_CANCEL = 1<<3;
public static final int FLAG_INCOMING_MEETING_MASK =
FLAG_INCOMING_MEETING_INVITE | FLAG_INCOMING_MEETING_CANCEL;
// The following flags indicate messages that are outgoing and meeting related
// (e.g. invites TO others)
public static final int FLAG_OUTGOING_MEETING_INVITE = 1<<4;
public static final int FLAG_OUTGOING_MEETING_CANCEL = 1<<5;
public static final int FLAG_OUTGOING_MEETING_ACCEPT = 1<<6;
public static final int FLAG_OUTGOING_MEETING_DECLINE = 1<<7;
public static final int FLAG_OUTGOING_MEETING_TENTATIVE = 1<<8;
public static final int FLAG_OUTGOING_MEETING_MASK =
FLAG_OUTGOING_MEETING_INVITE | FLAG_OUTGOING_MEETING_CANCEL |
FLAG_OUTGOING_MEETING_ACCEPT | FLAG_OUTGOING_MEETING_DECLINE |
FLAG_OUTGOING_MEETING_TENTATIVE;
public static final int FLAG_OUTGOING_MEETING_REQUEST_MASK =
FLAG_OUTGOING_MEETING_INVITE | FLAG_OUTGOING_MEETING_CANCEL;
// 8 general purpose flags (bits) that may be used at the discretion of the sync adapter
public static final int FLAG_SYNC_ADAPTER_SHIFT = 9;
public static final int FLAG_SYNC_ADAPTER_MASK = 255 << FLAG_SYNC_ADAPTER_SHIFT;
/** If set, the outgoing message should *not* include the quoted original message. */
public static final int FLAG_NOT_INCLUDE_QUOTED_TEXT = 1 << 17;
public static final int FLAG_REPLIED_TO = 1 << 18;
public static final int FLAG_FORWARDED = 1 << 19;
// Outgoing, original message
public static final int FLAG_TYPE_ORIGINAL = 1 << 20;
// Outgoing, reply all message; note, FLAG_TYPE_REPLY should also be set for backward
// compatibility
public static final int FLAG_TYPE_REPLY_ALL = 1 << 21;
// Flag used in draftInfo to indicate that the reference message should be appended
public static final int DRAFT_INFO_APPEND_REF_MESSAGE = 1 << 24;
public static final int DRAFT_INFO_QUOTE_POS_MASK = 0xFFFFFF;
/** a pseudo ID for "no message". */
public static final long NO_MESSAGE = -1L;
private static final int ATTACHMENT_INDEX_OFFSET = 2;
public Message() {
mBaseUri = CONTENT_URI;
}
@Override
public ContentValues toContentValues() {
ContentValues values = new ContentValues();
// Assign values for each row.
values.put(MessageColumns.DISPLAY_NAME, mDisplayName);
values.put(MessageColumns.TIMESTAMP, mTimeStamp);
values.put(MessageColumns.SUBJECT, mSubject);
values.put(MessageColumns.FLAG_READ, mFlagRead);
values.put(MessageColumns.FLAG_SEEN, mFlagSeen);
values.put(MessageColumns.FLAG_LOADED, mFlagLoaded);
values.put(MessageColumns.FLAG_FAVORITE, mFlagFavorite);
values.put(MessageColumns.FLAG_ATTACHMENT, mFlagAttachment);
values.put(MessageColumns.FLAGS, mFlags);
values.put(SyncColumns.SERVER_ID, mServerId);
values.put(SyncColumns.SERVER_TIMESTAMP, mServerTimeStamp);
values.put(MessageColumns.DRAFT_INFO, mDraftInfo);
values.put(MessageColumns.MESSAGE_ID, mMessageId);
values.put(MessageColumns.MAILBOX_KEY, mMailboxKey);
values.put(MessageColumns.ACCOUNT_KEY, mAccountKey);
values.put(MessageColumns.FROM_LIST, mFrom);
values.put(MessageColumns.TO_LIST, mTo);
values.put(MessageColumns.CC_LIST, mCc);
values.put(MessageColumns.BCC_LIST, mBcc);
values.put(MessageColumns.REPLY_TO_LIST, mReplyTo);
values.put(MessageColumns.MEETING_INFO, mMeetingInfo);
values.put(MessageColumns.SNIPPET, mSnippet);
values.put(MessageColumns.PROTOCOL_SEARCH_INFO, mProtocolSearchInfo);
values.put(MessageColumns.THREAD_TOPIC, mThreadTopic);
values.put(MessageColumns.SYNC_DATA, mSyncData);
values.put(MessageColumns.MAIN_MAILBOX_KEY, mMainMailboxKey);
return values;
}
public static Message restoreMessageWithId(Context context, long id) {
return EmailContent.restoreContentWithId(context, Message.class,
Message.CONTENT_URI, Message.CONTENT_PROJECTION, id);
}
@Override
public void restore(Cursor cursor) {
mBaseUri = CONTENT_URI;
mId = cursor.getLong(CONTENT_ID_COLUMN);
mDisplayName = cursor.getString(CONTENT_DISPLAY_NAME_COLUMN);
mTimeStamp = cursor.getLong(CONTENT_TIMESTAMP_COLUMN);
mSubject = cursor.getString(CONTENT_SUBJECT_COLUMN);
mFlagRead = cursor.getInt(CONTENT_FLAG_READ_COLUMN) == 1;
mFlagSeen = cursor.getInt(CONTENT_FLAG_SEEN_COLUMN) == 1;
mFlagLoaded = cursor.getInt(CONTENT_FLAG_LOADED_COLUMN);
mFlagFavorite = cursor.getInt(CONTENT_FLAG_FAVORITE_COLUMN) == 1;
mFlagAttachment = cursor.getInt(CONTENT_FLAG_ATTACHMENT_COLUMN) == 1;
mFlags = cursor.getInt(CONTENT_FLAGS_COLUMN);
mServerId = cursor.getString(CONTENT_SERVER_ID_COLUMN);
mServerTimeStamp = cursor.getLong(CONTENT_SERVER_TIMESTAMP_COLUMN);
mDraftInfo = cursor.getInt(CONTENT_DRAFT_INFO_COLUMN);
mMessageId = cursor.getString(CONTENT_MESSAGE_ID_COLUMN);
mMailboxKey = cursor.getLong(CONTENT_MAILBOX_KEY_COLUMN);
mMainMailboxKey = cursor.getLong(CONTENT_MAIN_MAILBOX_KEY_COLUMN);
mAccountKey = cursor.getLong(CONTENT_ACCOUNT_KEY_COLUMN);
mFrom = cursor.getString(CONTENT_FROM_LIST_COLUMN);
mTo = cursor.getString(CONTENT_TO_LIST_COLUMN);
mCc = cursor.getString(CONTENT_CC_LIST_COLUMN);
mBcc = cursor.getString(CONTENT_BCC_LIST_COLUMN);
mReplyTo = cursor.getString(CONTENT_REPLY_TO_COLUMN);
mMeetingInfo = cursor.getString(CONTENT_MEETING_INFO_COLUMN);
mSnippet = cursor.getString(CONTENT_SNIPPET_COLUMN);
mProtocolSearchInfo = cursor.getString(CONTENT_PROTOCOL_SEARCH_INFO_COLUMN);
mThreadTopic = cursor.getString(CONTENT_THREAD_TOPIC_COLUMN);
mSyncData = cursor.getString(CONTENT_SYNC_DATA_COLUMN);
}
/*
* Override this so that we can store the Body first and link it to the Message
* Also, attachments when we get there...
* (non-Javadoc)
* @see com.android.email.provider.EmailContent#save(android.content.Context)
*/
@Override
public Uri save(Context context) {
boolean doSave = !isSaved();
// This logic is in place so I can (a) short circuit the expensive stuff when
// possible, and (b) override (and throw) if anyone tries to call save() or update()
// directly for Message, which are unsupported.
if (mText == null && mHtml == null &&
(mAttachments == null || mAttachments.isEmpty())) {
if (doSave) {
return super.save(context);
} else {
// FLAG: Should we be doing this? In the base class, if someone calls "save" on
// an EmailContent that is already saved, it throws an exception.
// Call update, rather than super.update in case we ever override it
if (update(context, toContentValues()) == 1) {
return getUri();
}
return null;
}
}
final ArrayList<ContentProviderOperation> ops =
new ArrayList<ContentProviderOperation>();
2009-06-16 18:26:40 +00:00
addSaveOps(ops);
try {
final ContentProviderResult[] results =
context.getContentResolver().applyBatch(AUTHORITY, ops);
// If saving, set the mId's of the various saved objects
if (doSave) {
Uri u = results[0].uri;
mId = Long.parseLong(u.getPathSegments().get(1));
if (mAttachments != null) {
// Skip over the first two items in the result array
for (int i = 0; i < mAttachments.size(); i++) {
final Attachment a = mAttachments.get(i);
final int resultIndex = i + ATTACHMENT_INDEX_OFFSET;
// Save the id of the attachment record
if (resultIndex < results.length) {
u = results[resultIndex].uri;
} else {
// We didn't find the expected attachment, log this error
LogUtils.e(LOG_TAG, "Invalid index into ContentProviderResults: " +
resultIndex);
u = null;
}
if (u != null) {
a.mId = Long.parseLong(u.getPathSegments().get(1));
}
a.mMessageKey = mId;
}
}
return u;
} else {
return null;
}
} catch (RemoteException e) {
// There is nothing to be done here; fail by returning null
} catch (OperationApplicationException e) {
// There is nothing to be done here; fail by returning null
}
return null;
}
/**
* Save or update a message
* @param ops an array of CPOs that we'll add to
*/
public void addSaveOps(ArrayList<ContentProviderOperation> ops) {
boolean isNew = !isSaved();
ContentProviderOperation.Builder b;
// First, save/update the message
if (isNew) {
b = ContentProviderOperation.newInsert(mBaseUri);
} else {
b = ContentProviderOperation.newUpdate(mBaseUri)
.withSelection(MessageColumns._ID + "=?",
new String[] {Long.toString(mId)});
}
// Generate the snippet here, before we create the CPO for Message
if (mText != null) {
mSnippet = TextUtilities.makeSnippetFromPlainText(mText);
} else if (mHtml != null) {
mSnippet = TextUtilities.makeSnippetFromHtmlText(mHtml);
}
ops.add(b.withValues(toContentValues()).build());
// Create and save the body
ContentValues cv = new ContentValues();
if (mText != null) {
cv.put(BodyColumns.TEXT_CONTENT, mText);
}
if (mHtml != null) {
cv.put(BodyColumns.HTML_CONTENT, mHtml);
}
if (mSourceKey != 0) {
cv.put(BodyColumns.SOURCE_MESSAGE_KEY, mSourceKey);
}
if (mQuotedTextStartPos != 0) {
cv.put(BodyColumns.QUOTED_TEXT_START_POS, mQuotedTextStartPos);
}
// We'll need this if we're new
int messageBackValue = ops.size() - 1;
// Only create a body if we've got some data
if (!cv.keySet().isEmpty()) {
b = ContentProviderOperation.newInsert(Body.CONTENT_URI);
// Put our message id in the Body
if (!isNew) {
cv.put(BodyColumns.MESSAGE_KEY, mId);
}
b.withValues(cv);
// If we're new, create a back value entry
if (isNew) {
ContentValues backValues = new ContentValues();
backValues.put(BodyColumns.MESSAGE_KEY, messageBackValue);
b.withValueBackReferences(backValues);
}
// And add the Body operation
ops.add(b.build());
}
// Create the attaachments, if any
if (mAttachments != null) {
for (Attachment att: mAttachments) {
if (!isNew) {
att.mMessageKey = mId;
}
b = ContentProviderOperation.newInsert(Attachment.CONTENT_URI)
.withValues(att.toContentValues());
if (isNew) {
b.withValueBackReference(AttachmentColumns.MESSAGE_KEY, messageBackValue);
}
ops.add(b.build());
}
}
}
/**
* @return number of favorite (starred) messages throughout all accounts.
*/
public static int getFavoriteMessageCount(Context context) {
return count(context, Message.CONTENT_URI, ALL_FAVORITE_SELECTION, null);
}
/**
* @return number of favorite (starred) messages for an account
*/
public static int getFavoriteMessageCount(Context context, long accountId) {
return count(context, Message.CONTENT_URI, PER_ACCOUNT_FAVORITE_SELECTION,
new String[]{Long.toString(accountId)});
}
public static long getKeyColumnLong(Context context, long messageId, String column) {
String[] columns =
Utility.getRowColumns(context, Message.CONTENT_URI, messageId, column);
if (columns != null && columns[0] != null) {
return Long.parseLong(columns[0]);
}
return -1;
}
/**
* Returns the where clause for a message list selection.
*
* Accesses the detabase to determine the mailbox type. DO NOT CALL FROM UI THREAD.
*/
public static String buildMessageListSelection(
Context context, long accountId, long mailboxId) {
if (mailboxId == Mailbox.QUERY_ALL_INBOXES) {
return Message.ALL_INBOX_SELECTION;
}
if (mailboxId == Mailbox.QUERY_ALL_DRAFTS) {
return Message.ALL_DRAFT_SELECTION;
}
if (mailboxId == Mailbox.QUERY_ALL_OUTBOX) {
return Message.ALL_OUTBOX_SELECTION;
}
if (mailboxId == Mailbox.QUERY_ALL_UNREAD) {
return Message.ALL_UNREAD_SELECTION;
}
// TODO: we only support per-account starred mailbox right now, but presumably, we
// can surface the same thing for unread.
if (mailboxId == Mailbox.QUERY_ALL_FAVORITES) {
if (accountId == Account.ACCOUNT_ID_COMBINED_VIEW) {
return Message.ALL_FAVORITE_SELECTION;
}
final StringBuilder selection = new StringBuilder();
selection.append(MessageColumns.ACCOUNT_KEY).append('=').append(accountId)
.append(" AND ")
.append(Message.ALL_FAVORITE_SELECTION);
return selection.toString();
}
// Now it's a regular mailbox.
final StringBuilder selection = new StringBuilder();
selection.append(MessageColumns.MAILBOX_KEY).append('=').append(mailboxId);
if (Mailbox.getMailboxType(context, mailboxId) != Mailbox.TYPE_OUTBOX) {
selection.append(" AND ").append(Message.FLAG_LOADED_SELECTION);
}
return selection.toString();
}
public void setFlags(boolean quotedReply, boolean quotedForward) {
// Set message flags as well
if (quotedReply || quotedForward) {
mFlags &= ~Message.FLAG_TYPE_MASK;
mFlags |= quotedReply
? Message.FLAG_TYPE_REPLY
: Message.FLAG_TYPE_FORWARD;
}
}
}
public interface AttachmentColumns extends BaseColumns {
// The display name of the attachment
public static final String FILENAME = "fileName";
// The mime type of the attachment
public static final String MIME_TYPE = "mimeType";
// The size of the attachment in bytes
public static final String SIZE = "size";
// The (internal) contentId of the attachment (inline attachments will have these)
public static final String CONTENT_ID = "contentId";
// The location of the loaded attachment (probably a file)
@SuppressWarnings("hiding")
public static final String CONTENT_URI = "contentUri";
// The cached location of the attachment
public static final String CACHED_FILE = "cachedFile";
// A foreign key into the Message table (the message owning this attachment)
public static final String MESSAGE_KEY = "messageKey";
// The location of the attachment on the server side
// For IMAP, this is a part number (e.g. 2.1); for EAS, it's the internal file name
public static final String LOCATION = "location";
// The transfer encoding of the attachment
public static final String ENCODING = "encoding";
// Not currently used
public static final String CONTENT = "content";
// Flags
public static final String FLAGS = "flags";
// Content that is actually contained in the Attachment row
public static final String CONTENT_BYTES = "content_bytes";
// A foreign key into the Account table (for the message owning this attachment)
public static final String ACCOUNT_KEY = "accountKey";
// The UIProvider state of the attachment
public static final String UI_STATE = "uiState";
// The UIProvider destination of the attachment
public static final String UI_DESTINATION = "uiDestination";
// The UIProvider downloaded size of the attachment
public static final String UI_DOWNLOADED_SIZE = "uiDownloadedSize";
}
public static final class Attachment extends EmailContent implements Parcelable {
public static final String TABLE_NAME = "Attachment";
public static final String ATTACHMENT_PROVIDER_LEGACY_URI_PREFIX =
"content://com.android.email.attachmentprovider";
public static final String CACHED_FILE_QUERY_PARAM = "filePath";
public static Uri CONTENT_URI;
// This must be used with an appended id: ContentUris.withAppendedId(MESSAGE_ID_URI, id)
public static Uri MESSAGE_ID_URI;
public static String ATTACHMENT_PROVIDER_URI_PREFIX;
public static String ATTACHMENT_PROVIDER_AUTHORITY;
public static boolean sUsingLegacyPrefix;
public static void initAttachment() {
CONTENT_URI = Uri.parse(EmailContent.CONTENT_URI + "/attachment");
MESSAGE_ID_URI = Uri.parse(
EmailContent.CONTENT_URI + "/attachment/message");
ATTACHMENT_PROVIDER_AUTHORITY = EmailContent.EMAIL_PACKAGE_NAME +
".attachmentprovider";
ATTACHMENT_PROVIDER_URI_PREFIX = "content://" + ATTACHMENT_PROVIDER_AUTHORITY;
sUsingLegacyPrefix =
ATTACHMENT_PROVIDER_URI_PREFIX.equals(ATTACHMENT_PROVIDER_LEGACY_URI_PREFIX);
}
public String mFileName;
public String mMimeType;
public long mSize;
public String mContentId;
private String mContentUri;
private String mCachedFileUri;
public long mMessageKey;
public String mLocation;
public String mEncoding;
public String mContent; // Not currently used
public int mFlags;
public byte[] mContentBytes;
public long mAccountKey;
public int mUiState;
public int mUiDestination;
public int mUiDownloadedSize;
public static final int CONTENT_ID_COLUMN = 0;
public static final int CONTENT_FILENAME_COLUMN = 1;
public static final int CONTENT_MIME_TYPE_COLUMN = 2;
public static final int CONTENT_SIZE_COLUMN = 3;
public static final int CONTENT_CONTENT_ID_COLUMN = 4;
public static final int CONTENT_CONTENT_URI_COLUMN = 5;
public static final int CONTENT_CACHED_FILE_COLUMN = 6;
public static final int CONTENT_MESSAGE_ID_COLUMN = 7;
public static final int CONTENT_LOCATION_COLUMN = 8;
public static final int CONTENT_ENCODING_COLUMN = 9;
public static final int CONTENT_CONTENT_COLUMN = 10; // Not currently used
public static final int CONTENT_FLAGS_COLUMN = 11;
public static final int CONTENT_CONTENT_BYTES_COLUMN = 12;
public static final int CONTENT_ACCOUNT_KEY_COLUMN = 13;
public static final int CONTENT_UI_STATE_COLUMN = 14;
public static final int CONTENT_UI_DESTINATION_COLUMN = 15;
public static final int CONTENT_UI_DOWNLOADED_SIZE_COLUMN = 16;
public static final String[] CONTENT_PROJECTION = {
AttachmentColumns._ID, AttachmentColumns.FILENAME, AttachmentColumns.MIME_TYPE,
AttachmentColumns.SIZE, AttachmentColumns.CONTENT_ID, AttachmentColumns.CONTENT_URI,
AttachmentColumns.CACHED_FILE, AttachmentColumns.MESSAGE_KEY,
AttachmentColumns.LOCATION, AttachmentColumns.ENCODING, AttachmentColumns.CONTENT,
AttachmentColumns.FLAGS, AttachmentColumns.CONTENT_BYTES, AttachmentColumns.ACCOUNT_KEY,
AttachmentColumns.UI_STATE, AttachmentColumns.UI_DESTINATION,
AttachmentColumns.UI_DOWNLOADED_SIZE
};
// All attachments with an empty URI, regardless of mailbox
public static final String PRECACHE_SELECTION =
AttachmentColumns.CONTENT_URI + " isnull AND " + AttachmentColumns.FLAGS + "=0";
// Attachments with an empty URI that are in an inbox
public static final String PRECACHE_INBOX_SELECTION =
PRECACHE_SELECTION + " AND " + AttachmentColumns.MESSAGE_KEY + " IN ("
+ "SELECT " + MessageColumns._ID + " FROM " + Message.TABLE_NAME
+ " WHERE " + Message.ALL_INBOX_SELECTION
+ ")";
// Bits used in mFlags
// WARNING: AttachmentDownloadService relies on the fact that ALL of the flags below
// disqualify attachments for precaching. If you add a flag that does NOT disqualify an
// attachment for precaching, you MUST change the PRECACHE_SELECTION definition above
// Instruct Rfc822Output to 1) not use Content-Disposition and 2) use multipart/alternative
// with this attachment. This is only valid if there is one and only one attachment and
// that attachment has this flag set
public static final int FLAG_ICS_ALTERNATIVE_PART = 1<<0;
// Indicate that this attachment has been requested for downloading by the user; this is
// the highest priority for attachment downloading
public static final int FLAG_DOWNLOAD_USER_REQUEST = 1<<1;
// Indicate that this attachment needs to be downloaded as part of an outgoing forwarded
// message
public static final int FLAG_DOWNLOAD_FORWARD = 1<<2;
// Indicates that the attachment download failed in a non-recoverable manner
public static final int FLAG_DOWNLOAD_FAILED = 1<<3;
// Allow "room" for some additional download-related flags here
// Indicates that the attachment will be smart-forwarded
public static final int FLAG_SMART_FORWARD = 1<<8;
// Indicates that the attachment cannot be forwarded due to a policy restriction
public static final int FLAG_POLICY_DISALLOWS_DOWNLOAD = 1<<9;
// Indicates that this is a dummy placeholder attachment.
public static final int FLAG_DUMMY_ATTACHMENT = 1<<10;
/**
* no public constructor since this is a utility class
*/
public Attachment() {
mBaseUri = CONTENT_URI;
}
public void setCachedFileUri(String cachedFile) {
mCachedFileUri = cachedFile;
}
public String getCachedFileUri() {
return mCachedFileUri;
}
public void setContentUri(String contentUri) {
mContentUri = contentUri;
}
public String getContentUri() {
if (mContentUri == null) return null; //
// If we're not using the legacy prefix and the uri IS, we need to modify it
if (!Attachment.sUsingLegacyPrefix &&
mContentUri.startsWith(Attachment.ATTACHMENT_PROVIDER_LEGACY_URI_PREFIX)) {
// In an upgrade scenario, we may still have legacy attachment Uri's
// Skip past content://
int prefix = mContentUri.indexOf('/', 10);
if (prefix > 0) {
// Create a proper uri string using the actual provider
return ATTACHMENT_PROVIDER_URI_PREFIX + "/" + mContentUri.substring(prefix);
} else {
LogUtils.e("Attachment", "Improper contentUri format: " + mContentUri);
// Belt & suspenders; can't really happen
return mContentUri;
}
} else {
return mContentUri;
}
}
/**
* Restore an Attachment from the database, given its unique id
* @param context
* @param id
* @return the instantiated Attachment
*/
public static Attachment restoreAttachmentWithId(Context context, long id) {
return EmailContent.restoreContentWithId(context, Attachment.class,
Attachment.CONTENT_URI, Attachment.CONTENT_PROJECTION, id);
}
/**
* Restore all the Attachments of a message given its messageId
*/
public static Attachment[] restoreAttachmentsWithMessageId(Context context,
long messageId) {
Uri uri = ContentUris.withAppendedId(MESSAGE_ID_URI, messageId);
Cursor c = context.getContentResolver().query(uri, CONTENT_PROJECTION,
null, null, null);
try {
int count = c.getCount();
Attachment[] attachments = new Attachment[count];
for (int i = 0; i < count; ++i) {
c.moveToNext();
Attachment attach = new Attachment();
attach.restore(c);
attachments[i] = attach;
}
return attachments;
} finally {
c.close();
}
}
/**
* Creates a unique file in the external store by appending a hyphen
* and a number to the given filename.
* @param filename
* @return a new File object, or null if one could not be created
*/
public static File createUniqueFile(String filename) {
// TODO Handle internal storage, as required
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
File directory = Environment.getExternalStorageDirectory();
File file = new File(directory, filename);
if (!file.exists()) {
return file;
}
// Get the extension of the file, if any.
int index = filename.lastIndexOf('.');
String name = filename;
String extension = "";
if (index != -1) {
name = filename.substring(0, index);
extension = filename.substring(index);
}
for (int i = 2; i < Integer.MAX_VALUE; i++) {
file = new File(directory, name + '-' + i + extension);
if (!file.exists()) {
return file;
}
}
return null;
}
return null;
}
@Override
public void restore(Cursor cursor) {
mBaseUri = CONTENT_URI;
mId = cursor.getLong(CONTENT_ID_COLUMN);
mFileName= cursor.getString(CONTENT_FILENAME_COLUMN);
mMimeType = cursor.getString(CONTENT_MIME_TYPE_COLUMN);
mSize = cursor.getLong(CONTENT_SIZE_COLUMN);
mContentId = cursor.getString(CONTENT_CONTENT_ID_COLUMN);
mContentUri = cursor.getString(CONTENT_CONTENT_URI_COLUMN);
mCachedFileUri = cursor.getString(CONTENT_CACHED_FILE_COLUMN);
mMessageKey = cursor.getLong(CONTENT_MESSAGE_ID_COLUMN);
mLocation = cursor.getString(CONTENT_LOCATION_COLUMN);
mEncoding = cursor.getString(CONTENT_ENCODING_COLUMN);
mContent = cursor.getString(CONTENT_CONTENT_COLUMN);
mFlags = cursor.getInt(CONTENT_FLAGS_COLUMN);
mContentBytes = cursor.getBlob(CONTENT_CONTENT_BYTES_COLUMN);
mAccountKey = cursor.getLong(CONTENT_ACCOUNT_KEY_COLUMN);
mUiState = cursor.getInt(CONTENT_UI_STATE_COLUMN);
mUiDestination = cursor.getInt(CONTENT_UI_DESTINATION_COLUMN);
mUiDownloadedSize = cursor.getInt(CONTENT_UI_DOWNLOADED_SIZE_COLUMN);
}
@Override
public ContentValues toContentValues() {
ContentValues values = new ContentValues();
values.put(AttachmentColumns.FILENAME, mFileName);
values.put(AttachmentColumns.MIME_TYPE, mMimeType);
values.put(AttachmentColumns.SIZE, mSize);
values.put(AttachmentColumns.CONTENT_ID, mContentId);
values.put(AttachmentColumns.CONTENT_URI, mContentUri);
values.put(AttachmentColumns.CACHED_FILE, mCachedFileUri);
values.put(AttachmentColumns.MESSAGE_KEY, mMessageKey);
values.put(AttachmentColumns.LOCATION, mLocation);
values.put(AttachmentColumns.ENCODING, mEncoding);
values.put(AttachmentColumns.CONTENT, mContent);
values.put(AttachmentColumns.FLAGS, mFlags);
values.put(AttachmentColumns.CONTENT_BYTES, mContentBytes);
values.put(AttachmentColumns.ACCOUNT_KEY, mAccountKey);
values.put(AttachmentColumns.UI_STATE, mUiState);
values.put(AttachmentColumns.UI_DESTINATION, mUiDestination);
values.put(AttachmentColumns.UI_DOWNLOADED_SIZE, mUiDownloadedSize);
return values;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
// mBaseUri is not parceled
dest.writeLong(mId);
dest.writeString(mFileName);
dest.writeString(mMimeType);
dest.writeLong(mSize);
dest.writeString(mContentId);
dest.writeString(mContentUri);
dest.writeString(mCachedFileUri);
dest.writeLong(mMessageKey);
dest.writeString(mLocation);
dest.writeString(mEncoding);
dest.writeString(mContent);
dest.writeInt(mFlags);
dest.writeLong(mAccountKey);
if (mContentBytes == null) {
dest.writeInt(-1);
} else {
dest.writeInt(mContentBytes.length);
dest.writeByteArray(mContentBytes);
}
dest.writeInt(mUiState);
dest.writeInt(mUiDestination);
dest.writeInt(mUiDownloadedSize);
}
public Attachment(Parcel in) {
mBaseUri = Attachment.CONTENT_URI;
mId = in.readLong();
mFileName = in.readString();
mMimeType = in.readString();
mSize = in.readLong();
mContentId = in.readString();
mContentUri = in.readString();
mCachedFileUri = in.readString();
mMessageKey = in.readLong();
mLocation = in.readString();
mEncoding = in.readString();
mContent = in.readString();
mFlags = in.readInt();
mAccountKey = in.readLong();
final int contentBytesLen = in.readInt();
if (contentBytesLen == -1) {
mContentBytes = null;
} else {
mContentBytes = new byte[contentBytesLen];
in.readByteArray(mContentBytes);
}
mUiState = in.readInt();
mUiDestination = in.readInt();
mUiDownloadedSize = in.readInt();
}
public static final Parcelable.Creator<EmailContent.Attachment> CREATOR
= new Parcelable.Creator<EmailContent.Attachment>() {
@Override
public EmailContent.Attachment createFromParcel(Parcel in) {
return new EmailContent.Attachment(in);
}
@Override
public EmailContent.Attachment[] newArray(int size) {
return new EmailContent.Attachment[size];
}
};
@Override
public String toString() {
return "[" + mFileName + ", " + mMimeType + ", " + mSize + ", " + mContentId + ", "
+ mContentUri + ", " + mCachedFileUri + ", " + mMessageKey + ", "
+ mLocation + ", " + mEncoding + ", " + mFlags + ", " + mContentBytes + ", "
+ mAccountKey + "," + mUiState + "," + mUiDestination + ","
+ mUiDownloadedSize + "]";
}
}
public interface AccountColumns extends BaseColumns {
// The display name of the account (user-settable)
public static final String DISPLAY_NAME = "displayName";
// The email address corresponding to this account
public static final String EMAIL_ADDRESS = "emailAddress";
// A server-based sync key on an account-wide basis (EAS needs this)
public static final String SYNC_KEY = "syncKey";
// The default sync lookback period for this account
public static final String SYNC_LOOKBACK = "syncLookback";
// The default sync frequency for this account, in minutes
public static final String SYNC_INTERVAL = "syncInterval";
// A foreign key into the account manager, having host, login, password, port, and ssl flags
public static final String HOST_AUTH_KEY_RECV = "hostAuthKeyRecv";
// (optional) A foreign key into the account manager, having host, login, password, port,
// and ssl flags
public static final String HOST_AUTH_KEY_SEND = "hostAuthKeySend";
// Flags
public static final String FLAGS = "flags";
/**
* Default account
*
* @deprecated This should never be used any more, as default accounts are handled
* differently now
*/
@Deprecated
public static final String IS_DEFAULT = "isDefault";
// Old-Style UUID for compatibility with previous versions
@Deprecated
public static final String COMPATIBILITY_UUID = "compatibilityUuid";
// User name (for outgoing messages)
public static final String SENDER_NAME = "senderName";
/**
* Ringtone
*
* @deprecated Only used for creating the database (legacy reasons) and migration.
*/
@Deprecated
public static final String RINGTONE_URI = "ringtoneUri";
// Protocol version (arbitrary string, used by EAS currently)
public static final String PROTOCOL_VERSION = "protocolVersion";
// The number of new messages (reported by the sync/download engines
@Deprecated
public static final String NEW_MESSAGE_COUNT = "newMessageCount";
// Legacy flags defining security (provisioning) requirements of this account; this
// information is now found in the Policy table; POLICY_KEY (below) is the foreign key
@Deprecated
public static final String SECURITY_FLAGS = "securityFlags";
// Server-based sync key for the security policies currently enforced
public static final String SECURITY_SYNC_KEY = "securitySyncKey";
// Signature to use with this account
public static final String SIGNATURE = "signature";
// A foreign key into the Policy table
public static final String POLICY_KEY = "policyKey";
// Max upload attachment size.
public static final String MAX_ATTACHMENT_SIZE = "maxAttachmentSize";
// Current duration of the Exchange ping
public static final String PING_DURATION = "pingDuration";
}
public interface QuickResponseColumns extends BaseColumns {
// The QuickResponse text
static final String TEXT = "quickResponse";
// A foreign key into the Account table owning the QuickResponse
static final String ACCOUNT_KEY = "accountKey";
}
public interface MailboxColumns extends BaseColumns {
// Use _ID instead
@Deprecated
public static final String ID = "_id";
// The display name of this mailbox [INDEX]
static final String DISPLAY_NAME = "displayName";
// The server's identifier for this mailbox
public static final String SERVER_ID = "serverId";
// The server's identifier for the parent of this mailbox (null = top-level)
public static final String PARENT_SERVER_ID = "parentServerId";
// A foreign key for the parent of this mailbox (-1 = top-level, 0=uninitialized)
public static final String PARENT_KEY = "parentKey";
// A foreign key to the Account that owns this mailbox
public static final String ACCOUNT_KEY = "accountKey";
// The type (role) of this mailbox
public static final String TYPE = "type";
// The hierarchy separator character
public static final String DELIMITER = "delimiter";
// Server-based sync key or validity marker (e.g. "SyncKey" for EAS, "uidvalidity" for IMAP)
public static final String SYNC_KEY = "syncKey";
// The sync lookback period for this mailbox (or null if using the account default)
public static final String SYNC_LOOKBACK = "syncLookback";
// The sync frequency for this mailbox (or null if using the account default)
public static final String SYNC_INTERVAL = "syncInterval";
// The time of last successful sync completion (millis)
public static final String SYNC_TIME = "syncTime";
// Cached unread count
public static final String UNREAD_COUNT = "unreadCount";
// Visibility of this folder in a list of folders [INDEX]
public static final String FLAG_VISIBLE = "flagVisible";
// Other states, as a bit field, e.g. CHILDREN_VISIBLE, HAS_CHILDREN
public static final String FLAGS = "flags";
// Backward compatible
@Deprecated
public static final String VISIBLE_LIMIT = "visibleLimit";
// Sync status (can be used as desired by sync services)
public static final String SYNC_STATUS = "syncStatus";
// Number of messages locally available in the mailbox.
public static final String MESSAGE_COUNT = "messageCount";
// The last time a message in this mailbox has been read (in millis)
public static final String LAST_TOUCHED_TIME = "lastTouchedTime";
// The UIProvider sync status
public static final String UI_SYNC_STATUS = "uiSyncStatus";
// The UIProvider last sync result
public static final String UI_LAST_SYNC_RESULT = "uiLastSyncResult";
/**
* The UIProvider sync status
*
* @deprecated This is no longer used by anything except for creating the database.
*/
@Deprecated
public static final String LAST_NOTIFIED_MESSAGE_KEY = "lastNotifiedMessageKey";
/**
* The UIProvider last sync result
*
* @deprecated This is no longer used by anything except for creating the database.
*/
@Deprecated
public static final String LAST_NOTIFIED_MESSAGE_COUNT = "lastNotifiedMessageCount";
// The total number of messages in the remote mailbox
public static final String TOTAL_COUNT = "totalCount";
// The full hierarchical name of this folder, in the form a/b/c
public static final String HIERARCHICAL_NAME = "hierarchicalName";
// The last time that we did a full sync. Set from SystemClock.elapsedRealtime().
public static final String LAST_FULL_SYNC_TIME = "lastFullSyncTime";
}
public interface HostAuthColumns extends BaseColumns {
// The protocol (e.g. "imap", "pop3", "eas", "smtp"
static final String PROTOCOL = "protocol";
// The host address
static final String ADDRESS = "address";
// The port to use for the connection
static final String PORT = "port";
// General purpose flags
static final String FLAGS = "flags";
// The login (user name)
static final String LOGIN = "login";
// Password
static final String PASSWORD = "password";
// A domain or path, if required (used in IMAP and EAS)
static final String DOMAIN = "domain";
// An alias to a local client certificate for SSL
static final String CLIENT_CERT_ALIAS = "certAlias";
// DEPRECATED - Will not be set or stored
static final String ACCOUNT_KEY = "accountKey";
// A blob containing an X509 server certificate
static final String SERVER_CERT = "serverCert";
// The credentials row this hostAuth should use. Currently only set if using OAuth.
static final String CREDENTIAL_KEY = "credentialKey";
}
public interface PolicyColumns extends BaseColumns {
public static final String PASSWORD_MODE = "passwordMode";
public static final String PASSWORD_MIN_LENGTH = "passwordMinLength";
public static final String PASSWORD_EXPIRATION_DAYS = "passwordExpirationDays";
public static final String PASSWORD_HISTORY = "passwordHistory";
public static final String PASSWORD_COMPLEX_CHARS = "passwordComplexChars";
public static final String PASSWORD_MAX_FAILS = "passwordMaxFails";
public static final String MAX_SCREEN_LOCK_TIME = "maxScreenLockTime";
public static final String REQUIRE_REMOTE_WIPE = "requireRemoteWipe";
public static final String REQUIRE_ENCRYPTION = "requireEncryption";
public static final String REQUIRE_ENCRYPTION_EXTERNAL = "requireEncryptionExternal";
// ICS additions
// Note: the appearance of these columns does not imply that we support these features; only
// that we store them in the Policy structure
public static final String REQUIRE_MANUAL_SYNC_WHEN_ROAMING = "requireManualSyncRoaming";
public static final String DONT_ALLOW_CAMERA = "dontAllowCamera";
public static final String DONT_ALLOW_ATTACHMENTS = "dontAllowAttachments";
public static final String DONT_ALLOW_HTML = "dontAllowHtml";
public static final String MAX_ATTACHMENT_SIZE = "maxAttachmentSize";
public static final String MAX_TEXT_TRUNCATION_SIZE = "maxTextTruncationSize";
public static final String MAX_HTML_TRUNCATION_SIZE = "maxHTMLTruncationSize";
public static final String MAX_EMAIL_LOOKBACK = "maxEmailLookback";
public static final String MAX_CALENDAR_LOOKBACK = "maxCalendarLookback";
// Indicates that the server allows password recovery, not that we support it
public static final String PASSWORD_RECOVERY_ENABLED = "passwordRecoveryEnabled";
// Tokenized strings indicating protocol specific policies enforced/unsupported
public static final String PROTOCOL_POLICIES_ENFORCED = "protocolPoliciesEnforced";
public static final String PROTOCOL_POLICIES_UNSUPPORTED = "protocolPoliciesUnsupported";
}
}