Java tutorial
/* * 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. */ /* ========================================================================== */ /* Modifications on Features list / Changes Request / Problems Report */ /* -------------------------------------------------------------------------- */ /* date | author | Key | comment */ /* ----------|----------------------|----------------------|----------------- */ /* 04/14/2014| Chao Zhang | FR 635028 |[Email]Download */ /* | |porting from FR472914 |options to be im- */ /* | | |plemented */ /* ----------|----------------------|----------------------|----------------- */ /* 05/28/2014| zhonghua.tuo | FR 670064 |email search fun- */ /* | | |ction */ /* ----------|----------------------|----------------------|----------------- */ /* 04/08/2014|fu.zhang |622697 |[HOMO][HOMO][Oran */ /* | | |ge][Homologation] */ /* | | | Exchange Active */ /* | | |Sync Priority */ /* ----------|----------------------|----------------------|----------------- */ /******************************************************************************/ /* ========================================================================== *HISTORY * *Tag Date Author Description *============== ============ =============== ============================== *CONFLICT-50015 2014/10/24 zhaotianyong Modify the package conflict *BUGFIX-888775 2015/01/08 xiaolin.li [Email]Flash back when search keyword *BUGFIX-936728 2015/3/3 zhonghua.tuo [Email]Can not open vcs and apk file in draft box and outbox *BUGFIX-964415 2015/04/04 zheng.zou [HOMO][EMAIL] [ALWE] other received emails text are added to email when you answer one email *BUGFIX-978092 2015/04/16 zheng.zou [Android5.0][Monitor][Crash][FOTA][Email]Email crash and pop up ANR message after input characters in search field. *BUGFIX-969850 2015/4/17 gengkexue [HOMO][ALWE] Corporate email synchronized again all folders instead of updating only new emails *BUGFIX-1029375 2015/7/23 Gantao [Android 5.0][Email]Some mails with exchange account can't display completely after taping download remaining *FEATURE-ID 2015/08/12 Gantao FEATURE--Always show pictures *BUGFIX-571177 2015/09/14 kaifeng.lu [Android L][Email][Force close][Monitor]Email receive a new mail when tcl account syncing. *CR_585337 2015-09-16 chao.zhang Exchange Email resend mechanism *BUGFIX-570084 2015/09/19 Gantao [Android L][Email]POP/IMAP account cannot display the attachment which send from TCL account *BUGFIX-1093309 2015/09/29 junwei-xu <13340Track><26><CDR-EAS-030>Synchronization ScopeCalendar Events *BUGFIX-861247 2015-12-17 zheng.zou Receive a mail with many times bell ring *BUGFIX-1111576 2015/12/08 chao.zhang [Android L][Email][Monitor]FC happened when tapping the attachments option icon *BUGFIX-1039046 2015/12/25 chao.zhang [Android L][Email][Force close][Monitor]Swipe mail to read next one,force close happened ============================================================================ */ package com.tct.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; //TS: MOD by zhaotianyong for CONFLICT_50003 START //TS: MOD by zhaotianyong for CONFLICT_50003 END import android.text.TextUtils; import com.tct.emailcommon.Logging; import com.tct.emailcommon.mail.Address; import com.tct.emailcommon.service.SearchParams; import com.tct.emailcommon.utility.HtmlConverter; import com.tct.emailcommon.utility.TextUtilities; import com.tct.emailcommon.utility.Utility; //TS: MOD by zhaotianyong for CONFLICT_50015 START import com.tct.fw.google.common.annotations.VisibleForTesting; //import com.google.common.annotations.VisibleForTesting; //import com.tct.emailcommon.R; import com.tct.fw.R; import com.tct.mail.utils.IOUtils; import com.tct.mail.utils.LogTag; import com.tct.mail.utils.LogUtils; import com.tct.mail.utils.UIProvider; //TS: MOD by zhaotianyong for CONFLICT_50015 END //TS: MOD by zhaotianyong for CONFLICT_50003 START //import org.apache.commons.io.IOUtils; //TS: MOD by zhaotianyong for CONFLICT_50003 END import java.io.File; import java.io.IOException; import java.io.InputStream; import java.lang.ref.WeakReference; import java.text.SimpleDateFormat; import java.util.*; //[FEATURE]-Add-BEGIN by TSCD.zhonghua.tuo,05/28/2014,FR 670064 /** * 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; public static final int MAILBOX_MOST_RECENT_UNREAD_ID_COULUM = 0; //TS:zheng.zou 2015-12-17 EMAIL BUGFIX_861247 ADD // 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.UIPROVIDER_SYNCSTATUS_NO_SYNC; public static final int SYNC_STATUS_USER = UIProvider.UIPROVIDER_SYNCSTATUS_USER_REFRESH; public static final int SYNC_STATUS_BACKGROUND = UIProvider.UIPROVIDER_SYNCSTATUS_BACKGROUND_SYNC; public static final int SYNC_STATUS_LIVE = UIProvider.UIPROVIDER_SYNCSTATUS_LIVE_QUERY; public static final int SYNC_STATUS_INITIAL_SYNC_NEEDED = UIProvider.SyncStatus.INITIAL_SYNC_NEEDED; public static final int LAST_SYNC_RESULT_SUCCESS = UIProvider.UIPROVIDER_LASTSYNCRESULT_SUCCESS; public static final int LAST_SYNC_RESULT_AUTH_ERROR = UIProvider.UIPROVIDER_LASTSYNCRESULT_AUTH_ERROR; public static final int LAST_SYNC_RESULT_SECURITY_ERROR = UIProvider.UIPROVIDER_LASTSYNCRESULTAUTH_SECURITY_ERROR; public static final int LAST_SYNC_RESULT_CONNECTION_ERROR = UIProvider.UIPROVIDER_LASTSYNCRESULTAUTH_CONNECTION_ERROR; public static final int LAST_SYNC_RESULT_INTERNAL_ERROR = UIProvider.UIPROVIDER_LASTSYNCRESULTAUTH_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; //[FEATURE]-Add-BEGIN by TSNJ.qinglian.zhang,10/28/2014,FR 736417 public boolean mIsUseproxy; public String mProxyAddr; public int mProxyPort; public String mProxyUsername; public String mProxyUserpass; public static String PROXY_ADDRESS = ""; public static int PROXY_PORT = 0; public static boolean USE_PROXY = false; public static String PROXY_USERNAME = ""; public static String PROXY_USERPASS = ""; public static boolean USE_STATIC_PROXY = false; //[FEATURE]-Add-END by TSNJ.qinglian.zhang // 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 MAILBOX_MOST_RECENT_UNREAD_MESSAGE_URI; //TS:zheng.zou 2015-12-17 EMAIL BUGFIX_861247 ADD 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"); MAILBOX_MOST_RECENT_UNREAD_MESSAGE_URI = Uri .parse("content://" + AUTHORITY + "/mailboxMostRecentUnread"); //TS:zheng.zou 2015-12-17 EMAIL BUGFIX_861247 ADD 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; // TS: Gantao 2015-07-23 EMAIL BUGFIX-1029375 ADD_S public static Uri CONTENT_LARGE_URI; // TS: Gantao 2015-07-23 EMAIL BUGFIX-1029375 ADD_E public static void initBody() { CONTENT_URI = Uri.parse(EmailContent.CONTENT_URI + "/body"); // TS: Gantao 2015-07-23 EMAIL BUGFIX-1029375 ADD_S CONTENT_LARGE_URI = Uri.parse(EmailContent.CONTENT_URI + "/bodyLarge"); // TS: Gantao 2015-07-23 EMAIL BUGFIX-1029375 ADD_E } 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 }; /** * TCT: using to restore the body for local search @{ */ public static final String[] LOCALSEARCH_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, BodyColumns.TEXT_CONTENT, }; /** * @} */ 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; /** * TCT: save text body into body table(only for localsearch) * * @{ */ public static final int CONTENT_TEXT_COLUMN = 6; /** * @} */ 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, 1, 0L); // TS: zheng.zou 2015-04-04 EMAIL BUGFIX-964415 MOD // note: change 0 to 1 to get the right column of SOURCE_MESSAGE_KEY // or else the content of email will mismatched with other email } 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; InputStream bodyInput = null; try { bodyInput = context.getContentResolver().openInputStream(Uri.parse(uri)); try { content = IOUtils.toString(bodyInput); } finally { bodyInput.close(); bodyInput = null; } } catch (final IOException e) { LogUtils.v(LogUtils.TAG, e, "Exception while reading body content"); // TS: chao.zhang 2015-09-14 EMAIL FEATURE-1111576 ADD_S //NOTE:Why catch here? cause query Body, have IO operations(such File copy...),this operations need enough //memory,when in low memory status,OutOfMemoryError thrown and FC happen. catch it !!! //No need care null result return,no NullPointerExcetion will happen too.... } catch (final OutOfMemoryError oom) { LogUtils.v(LogUtils.TAG, oom, "OOM happen while reading body content"); } // TS: chao.zhang 2015-09-14 EMAIL FEATURE-1111576 ADD_E // TS: chao.zhang 2015-09-14 EMAIL BUGFIX-1039046 ADD_S //NOTE:Remember that this just a protective code,for better release Not used Resources. try { if (bodyInput != null) { bodyInput.close(); } } catch (IOException e1) { LogUtils.v(LogUtils.TAG, e1, "Exception while closing input connection"); } // TS: chao.zhang 2015-09-14 EMAIL BUGFIX-1039046 ADD_E 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"; // AM: Kexue.Geng 2015-04-17 EMAIL BUGFIX_969850 MOD_S /// M: Indicate whether the message is a dirty one (used by bad sync key recovery) public static final String DIRTY = "dirty"; // AM: Kexue.Geng 2015-04-17 EMAIL BUGFIX_969850 MOD_E // 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 String FLAG_PRIORITY = "flagPriority";//[FEATURE]-Add-BEGIN by TCTNj.fu.zhang,04/08/2014,622697 // TS: chao.zhang 2015-09-14 EMAIL FEATURE-585337 ADD_S public static final String SENDING_STATUS = "sendingStatus"; // TS: chao.zhang 2015-09-14 EMAIL FEATURE-585337 ADD_E public static final String MESSAGE_SIZE = "messageSize"; //TS: zheng.zou 2016-1-14 EMAIL TASK_1431225 ADD } 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 int CONTENT_FLAG_PRIORITY_COLUMN = 27;//[FEATURE]-Add-BEGIN by TCTNj.fu.zhang,04/08/2014,622697 // AM: Kexue.Geng 2015-04-17 EMAIL BUGFIX_969850 MOD_S /// M: For bad sync key recovery public static final int CONTENT_DIRTY_COLUMN = 28; // AM: Kexue.Geng 2015-04-17 EMAIL BUGFIX_969850 MOD_E // TS: chao.zhang 2015-09-14 EMAIL FEATURE-585337 ADD_S public static final int CONTENT_SENDING_STATUS_COLUMN = 29; // TS: chao.zhang 2015-09-14 EMAIL FEATURE-585337 ADD_E 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, MessageColumns.FLAG_PRIORITY, //[FEATURE]-Add-BEGIN by TCTNj.fu.zhang,04/08/2014,622697 // AM: Kexue.Geng 2015-04-17 EMAIL BUGFIX_969850 MOD_S /// M: For bad sync key recovery MessageColumns.DIRTY, // AM: Kexue.Geng 2015-04-17 EMAIL BUGFIX_969850 MOD_E // TS: chao.zhang 2015-09-14 EMAIL FEATURE-585337 ADD_S MessageColumns.SENDING_STATUS // TS: chao.zhang 2015-09-14 EMAIL FEATURE-585337 ADD_E }; 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, // TS: chao.zhang 2015-09-14 EMAIL FEATURE-585337 ADD_S MessageColumns.SENDING_STATUS // TS: chao.zhang 2015-09-14 EMAIL FEATURE-585337 ADD_E }; 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 }; // TS: chao.zhang 2015-09-14 EMAIL FEATURE-585337 ADD_S public static final String[] ID_COLUMN_WITH_STATUS_PROJECTION = { MessageColumns._ID, MessageColumns.SENDING_STATUS }; // TS: chao.zhang 2015-09-14 EMAIL FEATURE-585337 ADD_E 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 * <p/> * 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 + ")"; // TS: tao.gan 2015-08-12 EMAIL FEATURE-ID ADD_S public static final String FLAG_LOADED_COMPLETE_SELECTION = MessageColumns.FLAG_LOADED + " = " + Message.FLAG_LOADED_COMPLETE; // TS: tao.gan 2015-08-12 EMAIL FEATURE-ID ADD_E 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; // TS: tao.gan 2015-08-12 EMAIL FEATURE-ID ADD_S //Selection to retreve all messages in "inbox" which is loaded complete for any account public static final String ALL_INBOX_COMPLETE_SELECTION = MessageColumns.MAILBOX_KEY + " IN (" + "SELECT " + MailboxColumns._ID + " FROM " + Mailbox.TABLE_NAME + " WHERE " + MailboxColumns.TYPE + " = " + Mailbox.TYPE_INBOX + ")" + " AND " + FLAG_LOADED_COMPLETE_SELECTION; // TS: tao.gan 2015-08-12 EMAIL FEATURE-ID ADD_E /** * 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 + "=?"; //[FEATURE]-Add-BEGIN by TCTNj.fu.zhang,04/08/2014,622697 public static final String SORT_ORDER_BY_PRIORITY = MessageColumns.FLAG_PRIORITY; //[FEATURE]-Add-END by TCTNB.fu.zhang // _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; // AM: Kexue.Geng 2015-04-17 EMAIL BUGFIX_969850 MOD_S /// M: For bad sync key recovery public String mDirty; // AM: Kexue.Geng 2015-04-17 EMAIL BUGFIX_969850 MOD_E public int mPriority;//[FEATURE]-Add-BEGIN by TCTNj.fu.zhang,04/08/2014,622697 // TS: chao.zhang 2015-09-14 EMAIL FEATURE-585337D ADD_S public int mSendStatus; // TS: chao.zhang 2015-09-14 EMAIL FEATURE-585337 ADD_E /** * 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; //[FEATURE]-Add-BEGIN by TSCD.chao zhang,04/17/2014,FR 631895(porting from FR514398) public static final int FLAG_PRIORITY_HIGH = 0; public static final int FLAG_PRIORITY_NORMAL = 1; public static final int FLAG_PRIORITY_LOW = 2; //[FEATURE]-Add-END by TSCD.chao zhang //[FEATURE]-Add-BEGIN by TSCD.zhonghua.tuo,05/28/2014,FR 670064 public static boolean sNewLocalSearchStarted = false; // TS: chao.zhang 2015-09-14 EMAIL FEATURE-585337 ADD_S public static final int MAIL_IN_NONE_STATUS = 0; public static final int MAIL_IN_SENDING_STATUS = 1; public static final int MAIL_IN_QUEUE_STATUS = 2; public static final int MAIL_IN_FAILED_STATUS = 3; // TS: chao.zhang 2015-09-14 EMAIL FEATURE-585337 ADD_E public static String buildLocalSearchSelection(Context context, long accountId, long mailboxId, String query, int localSearchField) { 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); } // TS: xiaolin.li 2015-01-08 EMAIL BUGFIX-888775 ADD_S if (query.contains("'")) { query = query.replace("'", "''"); } // TS: xiaolin.li 2015-01-08 EMAIL BUGFIX-888775 ADD_E if (localSearchField == UIProvider.UIPROVIDER_LOCAL_SEARCH_ALL) { String messageIdSelection = buildMessageIdSelection(context, accountId, mailboxId, query, localSearchField); selection.append(" AND (").append(MessageColumns.SUBJECT).append(" LIKE '%").append(query) .append("%' OR ").append(messageIdSelection).append(")"); } else if (localSearchField == UIProvider.UIPROVIDER_LOCAL_SEARCH_FROM) { return buildMessageIdSelection(context, accountId, mailboxId, query, localSearchField); } else if (localSearchField == UIProvider.UIPROVIDER_LOCAL_SEARCH_TO) { return buildMessageIdSelection(context, accountId, mailboxId, query, localSearchField); } else if (localSearchField == UIProvider.UIPROVIDER_LOCAL_SEARCH_SUBJECT) { selection.append(" AND ").append(MessageColumns.SUBJECT).append(" LIKE '%").append(query) .append("%'"); } return selection.toString(); } public static String buildMessageIdSelection(Context context, long accountId, long mailboxId, String query, int localSearchField) { query = query.toLowerCase(); Set<Integer> messageIds = new HashSet<Integer>(); Cursor c = null; String[] projection = null; String messageListSelection = Message.buildMessageListSelection(context, accountId, mailboxId); if (localSearchField == UIProvider.UIPROVIDER_LOCAL_SEARCH_ALL) { projection = new String[] { MessageColumns._ID, MessageColumns.FROM_LIST, MessageColumns.TO_LIST, MessageColumns.CC_LIST, MessageColumns.BCC_LIST }; } else if (localSearchField == UIProvider.UIPROVIDER_LOCAL_SEARCH_FROM) { projection = new String[] { MessageColumns._ID, MessageColumns.FROM_LIST }; } else if (localSearchField == UIProvider.UIPROVIDER_LOCAL_SEARCH_TO) { projection = new String[] { MessageColumns._ID, MessageColumns.TO_LIST }; } try { c = context.getContentResolver().query(Message.CONTENT_URI, projection, messageListSelection, null, null); while (c.moveToNext()) { // A new search started (user changed the query term in UI), so stop this loop // ASAP for releasing CPU resource if (sNewLocalSearchStarted) { return null; } int messageId = c.getInt(0); if (localSearchField == UIProvider.UIPROVIDER_LOCAL_SEARCH_ALL) { Address[] fromList = Address.fromHeader(c.getString(1)); String address = Address.toString(fromList); if (address != null && address.toLowerCase().contains(query)) { messageIds.add(messageId); continue; } Address[] toList = Address.fromHeader(c.getString(2)); address = Address.toString(toList); if (address != null && address.toLowerCase().contains(query)) { messageIds.add(messageId); continue; } Address[] ccList = Address.fromHeader(c.getString(3)); address = Address.toString(ccList); if (address != null && address.toLowerCase().contains(query)) { messageIds.add(messageId); continue; } Address[] bccList = Address.fromHeader(c.getString(4)); address = Address.toString(bccList); if (address != null && address.toLowerCase().contains(query)) { messageIds.add(messageId); } } else if (localSearchField == UIProvider.UIPROVIDER_LOCAL_SEARCH_FROM) { Address[] fromList = Address.fromHeader(c.getString(1)); String address = Address.toString(fromList); if (address != null && address.toLowerCase().contains(query)) { messageIds.add(messageId); } } else if (localSearchField == UIProvider.UIPROVIDER_LOCAL_SEARCH_TO) { Address[] toList = Address.fromHeader(c.getString(1)); String address = Address.toString(toList); if (address != null && address.toLowerCase().contains(query)) { messageIds.add(messageId); } } } } finally { if (c != null) { c.close(); } } final String MESSAGE_KEY_IN = MessageColumns._ID + " IN ("; StringBuilder selection = new StringBuilder(MESSAGE_KEY_IN); boolean first = true; Iterator<Integer> itor = messageIds.iterator(); while (itor.hasNext()) { if (!first) { selection.append(','); } else { first = false; } selection.append(itor.next()); } selection.append(')'); return selection.toString(); } //[FEATURE]-Add-END by TCTNB.chen caixia public Message() { mBaseUri = CONTENT_URI; mPriority = FLAG_PRIORITY_NORMAL;//[FEATURE]-Add-BEGIN by TCTNj.fu.zhang,04/08/2014,622697 } @Override public ContentValues toContentValues() { ///TCT: Generate text if the text is null for local search. generateTextFromHtmlIfNeeded(); 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); // AM: Kexue.Geng 2015-04-17 EMAIL BUGFIX_969850 MOD_S /// M: For bad sync key recovery values.put(MessageColumns.DIRTY, mDirty); // AM: Kexue.Geng 2015-04-17 EMAIL BUGFIX_969850 MOD_E values.put(MessageColumns.MAIN_MAILBOX_KEY, mMainMailboxKey); values.put(MessageColumns.FLAG_PRIORITY, mPriority);//[FEATURE]-Add-BEGIN by TCTNj.fu.zhang,04/08/2014,622697 // TS: chao.zhang 2015-09-14 EMAIL FEATURE-585337 ADD_S values.put(MessageColumns.SENDING_STATUS, mSendStatus); // TS: chao.zhang 2015-09-14 EMAIL FEATURE-585337 ADD_E 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); mPriority = cursor.getInt(CONTENT_FLAG_PRIORITY_COLUMN);//[FEATURE]-Add-BEGIN by TCTNj.fu.zhang,04/08/2014,622697 // AM: Kexue.Geng 2015-04-17 EMAIL BUGFIX_969850 MOD_S /// M: For bad sync key recovery mDirty = cursor.getString(CONTENT_DIRTY_COLUMN); // AM: Kexue.Geng 2015-04-17 EMAIL BUGFIX_969850 MOD_E // TS: chao.zhang 2015-09-14 EMAIL FEATURE-585337 ADD_S mSendStatus = cursor.getInt(CONTENT_SENDING_STATUS_COLUMN); // TS: chao.zhang 2015-09-14 EMAIL FEATURE-585337 ADD_E } /* * 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.tct.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>(); 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); } ///TCT: Generate text if the text is null for local search. generateTextFromHtmlIfNeeded(); 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()); } } } //TS: junwei-xu 2015-09-17 EMAIL BUGFIX-569939 ADD-S /** * add recipients in this message * * @param ops an array of CPOs that we'll add to */ public void addInnerRecipinetsOps(Context context, ArrayList<ContentProviderOperation> ops) { ContentProviderOperation.Builder b; Address[] fromAddresses = Address.fromHeader(mFrom); Address[] toAddresses = Address.fromHeader(mTo); Address[] ccAddresses = Address.fromHeader(mCc); Address[] bccAddresses = Address.fromHeader(mBcc); // combined all arrays List<Address> allAddresses = new ArrayList<Address>(Arrays.asList(fromAddresses)); allAddresses.addAll(Arrays.asList(toAddresses)); allAddresses.addAll(Arrays.asList(ccAddresses)); allAddresses.addAll(Arrays.asList(bccAddresses)); for (int i = 0; i < allAddresses.size(); i++) { b = ContentProviderOperation.newInsert(Recipient.CONTENT_URI); ContentValues cv = new ContentValues(); cv.put(RecipientColumns.EMAIL_ADDRESS, allAddresses.get(i).getAddress()); String name = allAddresses.get(i).getPersonal(); cv.put(RecipientColumns.DISPLAY_NAME, TextUtils.isEmpty(name) ? "" : name); cv.put(RecipientColumns.ACCOUNT_KEY, mAccountKey); b.withValues(cv); ops.add(b.build()); } } /** execute db operation */ public void applyBatchOperations(Context context, ArrayList<ContentProviderOperation> ops) { try { if (ops.size() > 0) { context.getContentResolver().applyBatch(EmailContent.AUTHORITY, ops); } } catch (RemoteException e) { e.printStackTrace(); } catch (OperationApplicationException e) { e.printStackTrace(); } finally { ops.clear(); } } //TS: junwei-xu 2015-09-17 EMAIL BUGFIX-569939 ADD-E /** * TCT: Generate text from html content if the text is null. * This can be used to support local search. */ private void generateTextFromHtmlIfNeeded() { if (TextUtils.isEmpty(mText) && !TextUtils.isEmpty(mHtml)) { //TS: kaifeng.lu 2015-09-14 EMAIL BUGFIX_571177 MOD_S try { mText = HtmlConverter.htmlToText(mHtml); } catch (Exception e) { LogUtils.d(Logging.LOG_TAG, e, "generateTextFromHtmlIfNeeded error"); } //TS: kaifeng.lu 2015-09-14 EMAIL BUGFIX_571177 MOD_E } } /** * @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. * <p/> * 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; } } /// TCT: support for local search. @{ /// Those methods copy form EmailProvider. public static final long COMBINED_ACCOUNT_ID = 0x10000000; public static boolean isVirtualMailbox(long mailboxId) { return mailboxId >= 0x100000000L; } public static String getVirtualMailboxAccountIdString(long mailboxId) { return Long.toString(mailboxId >> 32); } private static int getVirtualMailboxType(long mailboxId) { return (int) (mailboxId & 0xF); } private static boolean isCombinedMailbox(long mailboxId) { return (mailboxId >> 32) == COMBINED_ACCOUNT_ID; } /** * TCT: Support for local Search. * Returns the where clause for a message list selection. * Combined Mailbox: Inbox, Unread, Starred. * Not Combined Mailbox: Unread, Starred. */ public static String buildMessageListSelection(Context context, long mailboxId) { final StringBuilder selection = new StringBuilder(); if (isVirtualMailbox(mailboxId)) { // 1. virtual mailbox append mailbox type. int mailType = getVirtualMailboxType(mailboxId); String accountId = getVirtualMailboxAccountIdString(mailboxId); if (mailType == Mailbox.TYPE_INBOX) { selection.append(Message.ALL_INBOX_SELECTION); } else if (mailType == Mailbox.TYPE_UNREAD) { selection.append(Message.ALL_UNREAD_SELECTION); } else if (mailType == Mailbox.TYPE_STARRED) { selection.append(Message.ALL_FAVORITE_SELECTION); } else { // This is an exception. as a normal mailbox handled. // Handle self defined VirtualMailbox here selection.append(MessageColumns.MAILBOX_KEY).append('=').append(mailboxId); } // 2. not combined virtual mailbox need append account key too. if (!isCombinedMailbox(mailboxId)) { selection.append(" AND " + MessageColumns.ACCOUNT_KEY).append("= ").append(accountId); } } else { // 3. normal mailbox, only append mailbox key. selection.append(MessageColumns.MAILBOX_KEY).append('=').append(mailboxId); if (Mailbox.getMailboxType(context, mailboxId) != Mailbox.TYPE_OUTBOX) { selection.append(" AND ").append(Message.FLAG_LOADED_SELECTION); } } LogUtils.logFeature(LogTag.SEARCH_TAG, "buildMessageListSelection [%s]", selection.toString()); return selection.toString(); } /** * TCT: Build the SQL selection clause for searching "subject", "sender" or "receiver" * * @param queryTerms the query term for searching * @param queryField by which field to search, null for "receiver" * @return */ private static String buildSelectionClause(String[] queryTerms, String queryField) { StringBuilder term = new StringBuilder(); term.append('('); if (!TextUtils.isEmpty(queryField)) { // subject, sender for (int i = 0; i < queryTerms.length; i++) { term.append("LOWER(").append(queryField).append(") like \'%") .append(queryTerms[i].toLowerCase()).append("%\' ESCAPE \'\\'").append(" AND "); } } else { // receiver for (int i = 0; i < queryTerms.length; i++) { term.append('('); term.append("LOWER(").append(MessageColumns.TO_LIST).append(") like \'%") .append(queryTerms[i].toLowerCase()).append("%\' ESCAPE \'\\'").append(" OR ") .append("LOWER(").append(MessageColumns.CC_LIST).append(") like \'%") .append(queryTerms[i].toLowerCase()).append("%\' ESCAPE \'\\') AND "); } } term.delete(term.length() - " AND ".length(), term.length()); term.append(')'); term.append(" OR "); return term.toString(); } /** * TCT: build local search selection from body text with query term * * @param context context using to do the db query * @param mailboxId the mail box id under searching * @param queryTerm the keywords need to query */ public static String buildBodyLocalSearchSelection(Context context, long mailboxId, String queryTerm) { // Get message keys of the downloaded messages in the mailbox being searched long[] messageIds = null; StringBuilder selection = new StringBuilder(); // For body search, do nothing special for '%' and '_' because we use // regex matching instead of SQL sentence String[] queryTermsForBody = queryTerm.split(" +"); Set<String> resultMessageIds = null; Cursor c = null; try { c = context.getContentResolver().query(Message.CONTENT_URI, new String[] { MessageColumns._ID }, Message.buildMessageListSelection(context, mailboxId), null, null); messageIds = new long[c.getCount()]; int i = 0; while (c.moveToNext()) { messageIds[i++] = c.getLong(0); } } catch (Exception e) { LogUtils.d(LogTag.getLogTag(), e, "buildLocalSearchSelection query exception: "); } finally { if (c != null) { c.close(); } } resultMessageIds = matchBodyQueryFromText(context, messageIds, queryTermsForBody); boolean first = true; selection.append(MessageColumns._ID + " IN ("); Iterator<String> itor = resultMessageIds.iterator(); while (itor.hasNext()) { if (!first) { selection.append(','); } else { first = false; } selection.append(itor.next()); } selection.append(") OR "); return selection.toString(); } //TS: zheng.zou 2016-01-26 EMAIL BUGFIX-1247256 ADD_S private static String buildAttachmentSearchSelection(String[] queryTerms) { StringBuilder term = new StringBuilder(); term.append(" _id IN (select messageKey from attachment where "); for (int i = 0; i < queryTerms.length; i++) { term.append("fileName like \'%").append(queryTerms[i].toLowerCase()).append("%\' ESCAPE \'\\'") .append(" AND "); } term.delete(term.length() - " AND ".length(), term.length()); term.append(')'); term.append(" OR "); return term.toString(); } //TS: zheng.zou 2016-01-26 EMAIL BUGFIX-1247256 ADD_E /** * TCT: Match the query terms from body text * * @param messageIds the message id array to query * @param queryTermsForBody query terms need to be queried */ private static Set<String> matchBodyQueryFromText(Context context, long[] messageIds, String[] queryTermsForBody) { LogUtils.logFeature(LogTag.SEARCH_TAG, "matchBody messageIds [%s] or queryTermsForBody [%s] ", queryTermsForBody.length, queryTermsForBody.length); Set<String> resultMessageIds = new HashSet<String>(); // Get the plain text content of the messages and one-by-one // checking whether each content contains the query terms final String MESSAGE_KEY_IN = BodyColumns.MESSAGE_KEY + " IN ("; StringBuilder messageIdRange = new StringBuilder(); boolean first = true; for (int i = 0; i < messageIds.length; i++) { if (!first) { messageIdRange.append(','); } else { first = false; } messageIdRange.append(messageIds[i]); } messageIdRange.append(')'); StringBuilder bodySelection = new StringBuilder(MESSAGE_KEY_IN); bodySelection.append(messageIdRange); Cursor largeMessgeCursor = null; Cursor c = null; try { /** TCT: Find the messages with compressed body @{ */ /*String largeMessgeSelection = "(" + MessageColumns.FLAGS + "&" + Message.FLAG_BODY _COMPRESSED + ") !=0 AND " + MessageColumns._ID + " IN (" + messageIdRange.toString(); largeMessgeCursor = context.getContentResolver().query(Message.CONTENT_URI, new Stri ng[]{ MessageColumns._ID}, largeMessgeSelection, null, null); ArrayList<Long> largeMessgeIds = new ArrayList<Long>(); if (largeMessgeCursor != null) { while (largeMessgeCursor.moveToNext()) { largeMessgeIds.add(largeMessgeCursor.getLong(0)); } }*/ /** @} */ c = context.getContentResolver().query(Body.CONTENT_URI, Body.LOCALSEARCH_CONTENT_PROJECTION, bodySelection.toString(), null, null); String messageContent; boolean flag; LogUtils.logFeature(LogTag.SEARCH_TAG, "matchBody cursor data len %d", c.getCount()); while (c.moveToNext()) { messageContent = c.getString(Body.CONTENT_TEXT_COLUMN); LogUtils.logFeature(LogTag.SEARCH_TAG, "matchBody cursor data[1] %s ", messageContent); if (!TextUtils.isEmpty(messageContent)) { /** TCT: Find the compressed body content and depress it. @{ */ /*if (largeMessgeIds.contains(c.getLong(Body.CONTENT_MESSAGE_KEY_COLUMN))) { messageContent = StringCompressor.decompressFromString(messageContent); }*/ /** @} */ flag = true; String lowerContent = messageContent.toLowerCase(); for (int i = 0; i < queryTermsForBody.length; i++) { // We only check if the original input query exist in body or not, no need use regex expression. // Regex expression is too complex for user, it will make user confused. // e.g input some char '$', but it is a keyword in expression with specail function. // It is same to other fields (From, To, Subject), case insensitive. LogUtils.logFeature(LogTag.SEARCH_TAG, "matchBody queryTermsforBody[%d]: %s contents: %s", i, queryTermsForBody[i], lowerContent); if (!lowerContent.contains(queryTermsForBody[i].toLowerCase())) { flag = false; break; } } // flag is true indicates the message contains all the query terms if (flag) { resultMessageIds.add(c.getString(Body.CONTENT_MESSAGE_KEY_COLUMN)); } } } } catch (Exception e) {//TS: zheng.zou 2015-04-16 EMAIL BUGFIX_978092 ADD //the content in textContent column is too large, cause exception. need to optimize the sql //limit the projection to not return textContent column LogUtils.e(LOG_TAG, "matchBodyQueryFromText error=" + e); } finally { if (c != null) { c.close(); } /// TCT: Close the large message cursor if (largeMessgeCursor != null) { largeMessgeCursor.close(); } } return resultMessageIds; } /** * TCT: Build the local search SQL selection clause * e.g: * String emailSelection = Message.buildLocalSearchSelection(getContext(), Account.NO_ACCOUN * T, * Mailbox.QUERY_ALL_INBOXES, query, SearchParams.SEARCH_FIELD_ALL); * it will return query a search all field and all the email inbox selection. */ public static String buildLocalSearchSelection(Context context, long mailboxId, String queryTerm, String queryField) { StringBuilder selection = new StringBuilder(); selection.append(" ("); // For body search, do nothing special for '%' and '_' because we use // regex matching instead of SQL sentence String[] queryTermsForBody = queryTerm.split(" +"); // For other kinds of search, use "ESCAPE" SQL clause to cope with the // SQL wildcard ('%', '_') queryTerm = queryTerm.replaceAll("\\\\", "\\\\\\\\"); queryTerm = queryTerm.replaceAll("%", "\\\\%"); queryTerm = queryTerm.replaceAll("_", "\\\\_"); queryTerm = queryTerm.replaceAll("'", "''"); String[] queryTerms = queryTerm.split(" +"); boolean isAll = false; if (queryField.contains(SearchParams.SEARCH_FIELD_ALL)) { isAll = true; } if (queryField.contains(SearchParams.SEARCH_FIELD_SUBJECT) || isAll) { selection.append(buildSelectionClause(queryTerms, MessageColumns.SUBJECT)); } if (queryField.contains(SearchParams.SEARCH_FIELD_FROM) || isAll) { selection.append(buildSelectionClause(queryTerms, MessageColumns.FROM_LIST)); } if (queryField.contains(SearchParams.SEARCH_FIELD_TO) || isAll) { selection.append(buildSelectionClause(queryTerms, null)); } /// TCT: Support local search body @{ if (queryField.contains(SearchParams.SEARCH_FIELD_BODY) || isAll) { selection.append(buildBodyLocalSearchSelection(context, mailboxId, queryTerm)); } /// Support local search @} //TS: zheng.zou 2016-01-26 EMAIL BUGFIX-1247256 ADD_S if (queryField.contains(SearchParams.SEARCH_FIELD_ATTACHMENT) || isAll) { selection.append(buildAttachmentSearchSelection(queryTerms)); } //TS: zheng.zou 2016-01-26 EMAIL BUGFIX-1247256 ADD_E selection.delete(selection.length() - " OR ".length(), selection.length()); selection.append(")"); return selection.toString(); } /** * TCT: add for log and debug. */ public String toString() { StringBuilder sb = new StringBuilder(); sb.append("ID :[" + mId + "]"); sb.append(" From :[" + mFrom + "]"); sb.append(" To :[" + mTo + "]"); sb.append(" Subject :[" + mSubject + "]"); return sb.toString(); } /** * TCT: log a received mail, query mailbox information from db. * * @param context * @param messages */ public static void logMessageReceived(Context context, Message[] messages) { if (LogUtils.isLoggable(LogTag.RECEIVEMAIL_TAG, LogUtils.DEBUG)) { for (Message message : messages) { LogUtils.logFeature(LogTag.RECEIVEMAIL_TAG, " Message received [%s][%s], mailbox key[%d]", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()), message.toString(), message.mMailboxKey); } } } } 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"; //TS: zhonghua.tuo 2015-3-3 EMAIL BUGFIX_936728 ADD_S // The Real Uri for the attachments public static final String REAL_URI = "realUri"; //TS: zhonghua.tuo 2015-3-3 EMAIL BUGFIX_936728 ADD_E // TS: Gantao 2015-09-19 EMAIL BUGFIX_570084 ADD_S public static final String ISINLINE = "isInline"; // TS: Gantao 2015-09-19 EMAIL BUGFIX_570084 ADD_E } 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.tct.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; //TS: zhonghua.tuo 2015-3-3 EMAIL BUGFIX_936728 ADD_S public String mRealUri; //TS: zhonghua.tuo 2015-3-3 EMAIL BUGFIX_936728 ADD_E // TS: Gantao 2015-09-19 EMAIL BUGFIX_570084 ADD_S public int mIsInline; // TS: Gantao 2015-09-19 EMAIL BUGFIX_570084 ADD_E 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; //TS: zhonghua.tuo 2015-3-3 EMAIL BUGFIX_936728 ADD_S public static final int CONTENT_REAL_URI = 17; //TS: zhonghua.tuo 2015-3-3 EMAIL BUGFIX_936728 ADD_E // TS: Gantao 2015-09-19 EMAIL BUGFIX_570084 ADD_S public static final int CONTENT_ISINLINE = 18; // TS: Gantao 2015-09-19 EMAIL BUGFIX_570084 ADD_E 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, //TS: zhonghua.tuo 2015-3-3 EMAIL BUGFIX_936728 ADD_S AttachmentColumns.UI_DOWNLOADED_SIZE, AttachmentColumns.REAL_URI, //TS: zhonghua.tuo 2015-3-3 EMAIL BUGFIX_936728 ADD_E // TS: Gantao 2015-09-19 EMAIL BUGFIX_570084 ADD_S AttachmentColumns.ISINLINE // TS: Gantao 2015-09-19 EMAIL BUGFIX_570084 ADD_E }; // 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 + ")"; // TS: tao.gan 2015-08-12 EMAIL FEATURE-ID ADD_S // All attachments with an empty URI and contentID is null, regardless of mailbox public static final String PRECACHE_SELECTION_NOT_INLINE = AttachmentColumns.CONTENT_URI + " isnull AND " + AttachmentColumns.FLAGS + "=0" + " AND " + AttachmentColumns.CONTENT_ID + " isnull"; // Attachments with an empty URI and is not inline that are in an inbox //and its message is loaded complete public static final String PRECACHE_INBOX_SELECTION_NOT_INLINE = PRECACHE_SELECTION_NOT_INLINE + " AND " + AttachmentColumns.MESSAGE_KEY + " IN (" + "SELECT " + MessageColumns._ID + " FROM " + Message.TABLE_NAME + " WHERE " + Message.ALL_INBOX_COMPLETE_SELECTION + ")"; // All attachments with an empty URI and contentID is not null, regardless of mailbox public static final String PRECACHE_SELECTION_INLINE = AttachmentColumns.CONTENT_URI + " isnull AND " + AttachmentColumns.FLAGS + "=0" + " AND " + AttachmentColumns.CONTENT_ID + " is not null"; // Attachments with an empty URI and is inline that are in an inbox //and its message is loaded complete public static final String PRECACHE_INBOX_SELECTION_INLINE = PRECACHE_SELECTION_INLINE + " AND " + AttachmentColumns.MESSAGE_KEY + " IN (" + "SELECT " + MessageColumns._ID + " FROM " + Message.TABLE_NAME + " WHERE " + Message.ALL_INBOX_COMPLETE_SELECTION + ")"; // TS: tao.gan 2015-08-12 EMAIL FEATURE-ID ADD_S // TS: xinlei.sheng 2016-3-3 EMAIL BUGFIX_1664255 ADD_S public static final String PRECACHE_SELECTION_NOT_DOWNLOADED_INLINE = PRECACHE_SELECTION_INLINE + " AND " + AttachmentColumns.ISINLINE + "=1"; // TS: xinlei.sheng 2016-3-3 EMAIL BUGFIX_1664255 ADD_E // Bits used in mFlags // WARNING: AttachmentService 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); //TS: zhonghua.tuo 2015-3-3 EMAIL BUGFIX_936728 ADD_S mRealUri = cursor.getString(CONTENT_REAL_URI); //TS: zhonghua.tuo 2015-3-3 EMAIL BUGFIX_936728 ADD_E // TS: Gantao 2015-09-19 EMAIL BUGFIX_570084 ADD_S mIsInline = cursor.getInt(CONTENT_ISINLINE); // TS: Gantao 2015-09-19 EMAIL BUGFIX_570084 ADD_E } @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); //TS: zhonghua.tuo 2015-3-3 EMAIL BUGFIX_936728 ADD_S values.put(AttachmentColumns.REAL_URI, mRealUri); //TS: zhonghua.tuo 2015-3-3 EMAIL BUGFIX_936728 ADD_E // TS: Gantao 2015-09-19 EMAIL BUGFIX_570084 ADD_S values.put(AttachmentColumns.ISINLINE, mIsInline); // TS: Gantao 2015-09-19 EMAIL BUGFIX_570084 ADD_E 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); //TS: zhonghua.tuo 2015-3-3 EMAIL BUGFIX_936728 ADD_S dest.writeString(mRealUri); //TS: zhonghua.tuo 2015-3-3 EMAIL BUGFIX_936728 ADD_E // TS: Gantao 2015-09-19 EMAIL BUGFIX_570084 ADD_S dest.writeInt(mIsInline); // TS: Gantao 2015-09-19 EMAIL BUGFIX_570084 ADD_E } 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(); //TS: zhonghua.tuo 2015-3-3 EMAIL BUGFIX_936728 ADD_S mRealUri = in.readString(); //TS: zhonghua.tuo 2015-3-3 EMAIL BUGFIX_936728 ADD_E // TS: Gantao 2015-09-19 EMAIL BUGFIX_570084 ADD_S mIsInline = in.readInt(); // TS: Gantao 2015-09-19 EMAIL BUGFIX_570084 ADD_E } 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"; //[FEATURE]-Add-BEGIN by TSCD.Chao Zhang,04/14/2014,FR 631895(porting from FR 472914) public static final String DOWNLOAD_OPTIONS = "DownloadOptions"; //[FEATURE]-Add-END by TSCD.Chao Zhang // TS: tao.gan 2015-08-12 EMAIL FEATURE-ID ADD_S public static final String INLINE_IMAGES = "inlineImages"; // TS: tao.gan 2015-08-12 EMAIL FEATURE-ID ADD_E //TS: junwei-xu 2015-09-29 EMAIL FEATURE-1093309 ADD_S public static final String SYNC_CALENDAR_LOOKBACK = "syncCalendarLookback"; //TS: junwei-xu 2015-09-29 EMAIL FEATURE-1093309 ADD_E // TS: tao.gan 2015-12-25 EMAIL FEATURE-1239148 ADD_S //Reply-to address for account public static final String REPLY_TO = "replyTo"; // TS: tao.gan 2015-12-25 EMAIL FEATURE-1239148 ADD_E } 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"; } //TS: junwei-xu 2015-09-17 EMAIL BUGFIX-569939 ADD-S public interface RecipientColumns { public static final String ID = "_id"; public static final String EMAIL_ADDRESS = "emailAddress"; public static final String DISPLAY_NAME = "displayName"; public static final String ACCOUNT_KEY = "accountKey"; } public static final class Recipient extends EmailContent implements RecipientColumns { public static final String TABLE_NAME = "Recipient"; public static final Uri CONTENT_URI = Uri.parse(EmailContent.CONTENT_URI + "/recipient"); public static final Uri CONTENT_FILTER_URI = CONTENT_URI; @Override public ContentValues toContentValues() { return null; } @Override public void restore(Cursor cursor) { } } //TS: junwei-xu 2015-09-17 EMAIL BUGFIX-569939 ADD-E //[FEATURE]-Add-BEGIN by TSNJ.qinglian.zhang,10/28/2014,FR 736417 public interface AccountInfoColumns { public static final String ID = "_id"; // point to the account's id static final String ACCOUNT_ID = "accountId"; static final String IS_USEPROXY = "isUseproxy"; static final String PROXY_ADDRESS = "proxyAddr"; static final String PROXY_PORT = "proxyPort"; static final String PROXY_USERNAME = "proxyUsername"; static final String PROXY_USERPASS = "proxyUserpass"; } public static final class AccountInfo extends EmailContent implements AccountInfoColumns { public static final String TABLE_NAME = "AccountInfo"; @SuppressWarnings("hiding") public static final Uri CONTENT_URI = Uri.parse(EmailContent.CONTENT_URI + "/accountinfo"); public static final String ACCOUNT_TYPE_IMAP = "imap"; public static final String ACCOUNT_TYPE_POP = "pop3"; public static final String ACCOUNT_TYPE_EAS = "eas"; public static final int CONTENT_ID_COLUMN = 0; public static final int CONTENT_ACCOUNT_ID_COLUMN = 1; public static final int CONTENT_USE_PROXY_COLUMN = 2; public static final int CONTENT_PROXY_ADDR_COLUMN = 3; public static final int CONTENT_PROXY_PORT_COLUMN = 4; public static final int CONTENT_PROXY_USERNAME_COLUMN = 5; public static final int CONTENT_PROXY_USERPASS_COLUMN = 6; public static final String[] CONTENT_PROJECTION = new String[] { RECORD_ID, AccountInfoColumns.ACCOUNT_ID, AccountInfoColumns.IS_USEPROXY, AccountInfoColumns.PROXY_ADDRESS, AccountInfoColumns.PROXY_PORT, AccountInfoColumns.PROXY_USERNAME, AccountInfoColumns.PROXY_USERPASS }; public static final String[] ID_PROJECTION = new String[] { RECORD_ID }; public static final String[] ACCOUNT_ID_PROJECTION = new String[] { AccountInfoColumns.ACCOUNT_ID }; public long mAccountId; public static Uri insertAccountInfo(Context context, long accountId, String protocal, int nDownloadLimit) { ContentValues cv = new ContentValues(); cv.put(AccountInfoColumns.ACCOUNT_ID, accountId); return context.getContentResolver().insert(AccountInfo.CONTENT_URI, cv); } public static Uri insertProxyInfo(Context context, long accountId, boolean bUseProxy, String proxyAddr, int ProxyPort, String proxyUsername, String proxyUserpass) { ContentValues cv = new ContentValues(); cv.put(AccountInfoColumns.ACCOUNT_ID, accountId); cv.put(AccountInfoColumns.IS_USEPROXY, bUseProxy ? 1 : 0); cv.put(AccountInfoColumns.PROXY_ADDRESS, proxyAddr); cv.put(AccountInfoColumns.PROXY_PORT, ProxyPort); cv.put(AccountInfoColumns.PROXY_USERNAME, proxyUsername); cv.put(AccountInfoColumns.PROXY_USERPASS, proxyUserpass); return context.getContentResolver().insert(AccountInfo.CONTENT_URI, cv); } public static boolean querryProxyInfo(Context context, long accountId, Account account) { Cursor c = context.getContentResolver().query(AccountInfo.CONTENT_URI, AccountInfo.CONTENT_PROJECTION, AccountInfoColumns.ACCOUNT_ID + "=" + Integer.toString((int) accountId), null, null); try { if (c.moveToFirst()) { account.mIsUseproxy = c.getInt(CONTENT_USE_PROXY_COLUMN) == 1 ? true : false; account.mProxyAddr = c.getString(CONTENT_PROXY_ADDR_COLUMN); account.mProxyPort = c.getInt(CONTENT_PROXY_PORT_COLUMN); account.mProxyUsername = c.getString(CONTENT_PROXY_USERNAME_COLUMN); account.mProxyUserpass = c.getString(CONTENT_PROXY_USERPASS_COLUMN); return true; } else { account.mIsUseproxy = false; return false; } } finally { c.close(); } } public static int updateProxyInfo(Context context, long accountId, boolean bUseProxy, String ProxyAddr, int ProxyPort, String Proxyname, String password) { ContentValues cv = new ContentValues(); cv.put(AccountInfoColumns.IS_USEPROXY, bUseProxy ? 1 : 0); cv.put(AccountInfoColumns.PROXY_ADDRESS, ProxyAddr); cv.put(AccountInfoColumns.PROXY_PORT, ProxyPort); cv.put(AccountInfoColumns.PROXY_USERNAME, Proxyname); cv.put(AccountInfoColumns.PROXY_USERPASS, password); return context.getContentResolver().update(AccountInfo.CONTENT_URI, cv, AccountInfoColumns.ACCOUNT_ID + "=" + Integer.toString((int) accountId), null); } @Override public ContentValues toContentValues() { ContentValues values = new ContentValues(); values.put(AccountInfoColumns.ACCOUNT_ID, mAccountId); values.put(AccountInfoColumns.IS_USEPROXY, mIsUseproxy ? 1 : 0); if (mIsUseproxy) { values.put(AccountInfoColumns.PROXY_ADDRESS, mProxyAddr); values.put(AccountInfoColumns.PROXY_PORT, mProxyPort); values.put(AccountInfoColumns.PROXY_USERNAME, mProxyUsername); values.put(AccountInfoColumns.PROXY_USERPASS, mProxyUserpass); } return values; } @Override public void restore(Cursor cursor) { mId = cursor.getLong(CONTENT_ID_COLUMN); mAccountId = cursor.getLong(CONTENT_ACCOUNT_ID_COLUMN); mIsUseproxy = cursor.getInt(CONTENT_USE_PROXY_COLUMN) == 1; mProxyAddr = cursor.getString(CONTENT_PROXY_ADDR_COLUMN); mProxyPort = cursor.getInt(CONTENT_PROXY_PORT_COLUMN); mProxyUsername = cursor.getString(CONTENT_PROXY_PORT_COLUMN); mProxyUserpass = cursor.getString(CONTENT_PROXY_USERPASS_COLUMN); } } //[FEATURE]-Add-END by TSNJ.qinglian.zhang public interface HostAuthColumns extends BaseColumns { public static final String ID = "_id"; // 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"; // TS: tao.gan 2015-09-22 EMAIL BUGFIX-1068066 ADD_S public static final String DISALLOW_SIMPLE_DEVICE_PASSWORD = "disallowSimpleDevicePassword"; // TS: tao.gan 2015-09-22 EMAIL BUGFIX-1068066 ADD_E } }