com.tct.emailcommon.provider.EmailContent.java Source code

Java tutorial

Introduction

Here is the source code for com.tct.emailcommon.provider.EmailContent.java

Source

/*
 * 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
    }
}