com.tct.email.NotificationController.java Source code

Java tutorial

Introduction

Here is the source code for com.tct.email.NotificationController.java

Source

/*
 * Copyright (C) 2010 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.
 */
/*
 ==========================================================================
 *HISTORY
 *
 *Tag               Date         Author          Description
 *============== ============ =============== ==============================
 *BUGFIX-991085     2015/03/30   jin.dong      [Email]After the web side to change the password, MS without prompting
 *CR_585337      2015-09-21  chao.zhang       Exchange Email resend mechanism
 *BUGFIX_709873     2015/11/20   kaifeng.lu   [Android L][Email]One account sent more than one mails failed,the notification display "Multiple accounts"
 *BUGFIX-944797  2015-11-26   jian.xu         [Android L][Email]Retry notification not disappear after reconnect wifi
 *BUGFIX-861247  2015-12-17   zheng.zou       Receive a mail with many times bell ring
 *BUGFIX-1190892  2015-12-18   kaifeng.lu       [Android 6.0][Email][Monitor]Email crash by FATAL EXCEPTION: EmailNotification
 *BUGFIX-1118361 2015/12/08    jian.xu        [Google CTS][GTS] Many application of FC Problem during test
 *BUGFIX-1272060 2016/01/04    kaifeng.lu     [Android 6.0][Email][Ergo]The sent failed emails number and current email account name should be removed.
 *BUGFIX_1162996 2015/1/20     yanhua.chen    [Android 6.0][Email]TCL account pop up permission needed window continuously if disable contact/calendar permission of exchange
 *BUGFIX-1128322 2015/12/30    junwei-xu      [GTS-2.1_R2]Unfortunately, Google Play services,Weather, Google Playservices, Google APP,Email has stopped.
 ===========================================================================
 */
package com.tct.email;

import android.*;
import android.Manifest;
import android.app.Notification;
import android.app.Notification.Builder;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.res.Resources;
import android.database.ContentObserver;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Handler;
import android.os.Looper;
import android.os.Process;
import android.provider.Settings;
import android.support.v4.app.NotificationCompat;
import android.text.TextUtils;
import android.text.format.DateUtils;

import com.tct.email.R;
import com.tct.emailcommon.provider.Account;
import com.tct.emailcommon.provider.EmailContent;
import com.tct.emailcommon.provider.EmailContent.Attachment;
import com.tct.emailcommon.provider.EmailContent.Message;
import com.tct.emailcommon.provider.Mailbox;
import com.tct.emailcommon.utility.EmailAsyncTask;
import com.tct.mail.NotificationActionIntentService;
import com.tct.mail.utils.LogTag;
import com.tct.mail.utils.LogUtils;
import com.tct.mail.utils.SparseLongArray;
import com.tct.mail.utils.Utils;
import com.tct.email.activity.setup.AccountSecurity;
import com.tct.email.activity.setup.HeadlessAccountSettingsLoader;
import com.tct.email.provider.EmailProvider;
import com.tct.email.service.EmailServiceUtils;
import com.tct.mail.preferences.FolderPreferences;
import com.tct.mail.providers.Conversation;
import com.tct.mail.providers.Folder;
import com.tct.mail.providers.UIProvider;
import com.tct.mail.utils.Clock;
import com.tct.mail.utils.NotificationUtils;

import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * Class that manages notifications.
 */
public class NotificationController {
    private static final String LOG_TAG = LogTag.getLogTag();

    private static final int NOTIFICATION_ID_ATTACHMENT_WARNING = 3;
    private static final int NOTIFICATION_ID_PASSWORD_EXPIRING = 4;
    private static final int NOTIFICATION_ID_PASSWORD_EXPIRED = 5;
    private static final int NOTIFICATION_ID_SEND_FAIL_WARNING = 6;
    private static final int NOTIFICATION_ID_BASE_MASK = 0xF0000000;
    private static final int NOTIFICATION_ID_BASE_LOGIN_WARNING = 0x20000000;
    private static final int NOTIFICATION_ID_BASE_SECURITY_NEEDED = 0x30000000;
    private static final int NOTIFICATION_ID_BASE_SECURITY_CHANGED = 0x40000000;
    private static final SparseLongArray sLastUnreadIds = new SparseLongArray(); //TS:zheng.zou 2015-12-17 EMAIL BUGFIX_861247  ADD
    //TS: yanhua.chen 2015-1-20 EMAIL BUGFIX_1162996 ADD_S
    public static final String TCT_ACTION_MANAGE_APP = "android.intent.action.tct.MANAGE_PERMISSIONS";
    public static final String TCT_EXTRA_PACKAGE_NAME = "android.intent.extra.tct.PACKAGE_NAME";
    public static final int EXCHANGE_NEWCALENDAR_NOTIFICATION_ID = 11111111;
    public static final int EXCHANGE_NEWCONTACTS_NOTIFICATION_ID = 11111112;
    public static final int EXCHANGE_NEWSTORAGE_NOTIFICATION_ID = 11111113; //TS: zheng.zou 2016-1-22 EMAIL BUGFIX-1431088 ADD
    //TS: yanhua.chen 2015-1-20 EMAIL BUGFIX_1162996 ADD_E

    private static NotificationThread sNotificationThread;
    private static Handler sNotificationHandler;
    private static NotificationController sInstance;
    private final Context mContext;
    private final NotificationManager mNotificationManager;
    private final Clock mClock;
    /** Maps account id to its observer */
    private final Map<Long, ContentObserver> mNotificationMap = new HashMap<Long, ContentObserver>();
    private ContentObserver mAccountObserver;

    /** Constructor */
    protected NotificationController(Context context, Clock clock) {
        mContext = context.getApplicationContext();
        EmailContent.init(context);
        mNotificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
        mClock = clock;
    }

    /** Singleton access */
    public static synchronized NotificationController getInstance(Context context) {
        if (sInstance == null) {
            sInstance = new NotificationController(context, Clock.INSTANCE);
        }
        return sInstance;
    }

    /**
     * Return whether or not a notification, based on the passed-in id, needs to be "ongoing"
     * @param notificationId the notification id to check
     * @return whether or not the notification must be "ongoing"
     */
    private static boolean needsOngoingNotification(int notificationId) {
        // "Security needed" must be ongoing so that the user doesn't close it; otherwise, sync will
        // be prevented until a reboot.  Consider also doing this for password expired.
        return (notificationId & NOTIFICATION_ID_BASE_MASK) == NOTIFICATION_ID_BASE_SECURITY_NEEDED;
    }

    /**
     * Returns a {@link android.support.v4.app.NotificationCompat.Builder} for an event with the
     * given account. The account contains specific rules on ring tone usage and these will be used
     * to modify the notification behaviour.
     *
     * @param accountId The id of the account this notification is being built for.
     * @param ticker Text displayed when the notification is first shown. May be {@code null}.
     * @param title The first line of text. May NOT be {@code null}.
     * @param contentText The second line of text. May NOT be {@code null}.
     * @param intent The intent to start if the user clicks on the notification.
     * @param largeIcon A large icon. May be {@code null}
     * @param number A number to display using {@link Builder#setNumber(int)}. May be {@code null}.
     * @param enableAudio If {@code false}, do not play any sound. Otherwise, play sound according
     *        to the settings for the given account.
     * @return A {@link Notification} that can be sent to the notification service.
     */
    private NotificationCompat.Builder createBaseAccountNotificationBuilder(long accountId, String ticker,
            CharSequence title, String contentText, Intent intent, Bitmap largeIcon, Integer number,
            boolean enableAudio, boolean ongoing) {
        // Pending Intent
        PendingIntent pending = null;
        if (intent != null) {
            pending = PendingIntent.getActivity(mContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
        }

        // NOTE: the ticker is not shown for notifications in the Holo UX
        final NotificationCompat.Builder builder = new NotificationCompat.Builder(mContext).setContentTitle(title)
                .setContentText(contentText).setContentIntent(pending).setLargeIcon(largeIcon)
                .setNumber(number == null ? 0 : number).setSmallIcon(R.drawable.ic_notification_mail_24dp)
                .setWhen(mClock.getTime()).setTicker(ticker).setOngoing(ongoing);

        if (enableAudio) {
            Account account = Account.restoreAccountWithId(mContext, accountId);
            setupSoundAndVibration(builder, account);
        }

        return builder;
    }

    /**
     * Generic notifier for any account.  Uses notification rules from account.
     *
     * @param accountId The account id this notification is being built for.
     * @param ticker Text displayed when the notification is first shown. May be {@code null}.
     * @param title The first line of text. May NOT be {@code null}.
     * @param contentText The second line of text. May NOT be {@code null}.
     * @param intent The intent to start if the user clicks on the notification.
     * @param notificationId The ID of the notification to register with the service.
     */
    private void showNotification(long accountId, String ticker, String title, String contentText, Intent intent,
            int notificationId) {
        final NotificationCompat.Builder builder = createBaseAccountNotificationBuilder(accountId, ticker, title,
                contentText, intent, null, null, true, needsOngoingNotification(notificationId));
        mNotificationManager.notify(notificationId, builder.build());
    }

    /**
     * Tells the notification controller if it should be watching for changes to the message table.
     * This is the main life cycle method for message notifications. When we stop observing
     * database changes, we save the state [e.g. message ID and count] of the most recent
     * notification shown to the user. And, when we start observing database changes, we restore
     * the saved state.
     */
    public void watchForMessages() {
        ensureHandlerExists();
        // Run this on the message notification handler
        sNotificationHandler.post(new Runnable() {
            @Override
            public void run() {
                ContentResolver resolver = mContext.getContentResolver();

                // otherwise, start new observers for all notified accounts
                registerMessageNotification(Account.ACCOUNT_ID_COMBINED_VIEW);
                // If we're already observing account changes, don't do anything else
                if (mAccountObserver == null) {
                    LogUtils.i(LOG_TAG, "Observing account changes for notifications");
                    mAccountObserver = new AccountContentObserver(sNotificationHandler, mContext);
                    resolver.registerContentObserver(Account.NOTIFIER_URI, true, mAccountObserver);
                }
            }
        });
    }

    /**
     * Ensures the notification handler exists and is ready to handle requests.
     */

    /**
     * TODO: Notifications jump around too much because we get too many content updates.
     * We should try to make the provider generate fewer updates instead.
     */

    private static final int NOTIFICATION_DELAYED_MESSAGE = 0;
    private static final long NOTIFICATION_DELAY = 15 * DateUtils.SECOND_IN_MILLIS;
    // True if we're coalescing notification updates
    private static boolean sNotificationDelayedMessagePending;
    // True if accounts have changed and we need to refresh everything
    private static boolean sRefreshAllNeeded;
    // Set of accounts we need to regenerate notifications for
    private static final HashSet<Long> sRefreshAccountSet = new HashSet<Long>();
    // These should all be accessed on-thread, but just in case...
    private static final Object sNotificationDelayedMessageLock = new Object();

    private static synchronized void ensureHandlerExists() {
        if (sNotificationThread == null) {
            sNotificationThread = new NotificationThread();
            sNotificationHandler = new Handler(sNotificationThread.getLooper(), new Handler.Callback() {
                @Override
                public boolean handleMessage(final android.os.Message message) {
                    /**
                     * To reduce spamming the notifications, we quiesce updates for a few
                     * seconds to batch them up, then handle them here.
                     */
                    LogUtils.d(LOG_TAG, "Delayed notification processing");
                    synchronized (sNotificationDelayedMessageLock) {
                        sNotificationDelayedMessagePending = false;
                        final Context context = (Context) message.obj;
                        if (sRefreshAllNeeded) {
                            sRefreshAllNeeded = false;
                            refreshAllNotificationsInternal(context);
                        }
                        for (final Long accountId : sRefreshAccountSet) {
                            refreshNotificationsForAccountInternal(context, accountId);
                        }
                        sRefreshAccountSet.clear();
                    }
                    return true;
                }
            });
        }
    }

    /**
     * Registers an observer for changes to mailboxes in the given account.
     * NOTE: This must be called on the notification handler thread.
     * @param accountId The ID of the account to register the observer for. May be
     *                  {@link Account#ACCOUNT_ID_COMBINED_VIEW} to register observers for all
     *                  accounts that allow for user notification.
     */
    private void registerMessageNotification(final long accountId) {
        ContentResolver resolver = mContext.getContentResolver();
        if (accountId == Account.ACCOUNT_ID_COMBINED_VIEW) {
            Cursor c = resolver.query(Account.CONTENT_URI, EmailContent.ID_PROJECTION, null, null, null);
            try {
                //TS: junwei-xu 2015-12-30 EMAIL BUGFIX-1128322 MOD_S
                while (c != null && c.moveToNext()) {
                    long id = c.getLong(EmailContent.ID_PROJECTION_COLUMN);
                    registerMessageNotification(id);
                }
                //TS: junwei-xu 2015-12-30 EMAIL BUGFIX-1128322 MOD_E
            } finally {
                //TS: jian.xu 2015-12-08 EMAIL BUGFIX-1118361 MOD_S
                if (c != null) {
                    c.close();
                }
                //TS: jian.xu 2015-12-08 EMAIL BUGFIX-1118361 MOD_E
            }
        } else {
            ContentObserver obs = mNotificationMap.get(accountId);
            if (obs != null)
                return; // we're already observing; nothing to do
            LogUtils.i(LOG_TAG, "Registering for notifications for account " + accountId);
            ContentObserver observer = new MessageContentObserver(sNotificationHandler, mContext, accountId);
            resolver.registerContentObserver(Message.NOTIFIER_URI, true, observer);
            mNotificationMap.put(accountId, observer);
            // Now, ping the observer for any initial notifications
            observer.onChange(true);
        }
    }

    /**
     * Unregisters the observer for the given account. If the specified account does not have
     * a registered observer, no action is performed. This will not clear any existing notification
     * for the specified account. Use {@link NotificationManager#cancel(int)}.
     * NOTE: This must be called on the notification handler thread.
     * @param accountId The ID of the account to unregister from. To unregister all accounts that
     *                  have observers, specify an ID of {@link Account#ACCOUNT_ID_COMBINED_VIEW}.
     */
    private void unregisterMessageNotification(final long accountId) {
        ContentResolver resolver = mContext.getContentResolver();
        if (accountId == Account.ACCOUNT_ID_COMBINED_VIEW) {
            LogUtils.i(LOG_TAG, "Unregistering notifications for all accounts");
            // cancel all existing message observers
            for (ContentObserver observer : mNotificationMap.values()) {
                resolver.unregisterContentObserver(observer);
            }
            mNotificationMap.clear();
        } else {
            LogUtils.i(LOG_TAG, "Unregistering notifications for account " + accountId);
            ContentObserver observer = mNotificationMap.remove(accountId);
            if (observer != null) {
                resolver.unregisterContentObserver(observer);
            }
        }
    }

    public static final String EXTRA_ACCOUNT = "account";
    public static final String EXTRA_CONVERSATION = "conversationUri";
    public static final String EXTRA_FOLDER = "folder";

    /** Sets up the notification's sound and vibration based upon account details. */
    private void setupSoundAndVibration(NotificationCompat.Builder builder, Account account) {
        String ringtoneUri = Settings.System.DEFAULT_NOTIFICATION_URI.toString();
        boolean vibrate = false;

        // Use the Inbox notification preferences
        final Cursor accountCursor = mContext.getContentResolver().query(
                EmailProvider.uiUri("uiaccount", account.mId), UIProvider.ACCOUNTS_PROJECTION, null, null, null);

        com.tct.mail.providers.Account uiAccount = null;
        try {
            if (accountCursor.moveToFirst()) {
                uiAccount = com.tct.mail.providers.Account.builder().buildFrom(accountCursor);
            }
        } finally {
            accountCursor.close();
        }

        if (uiAccount != null) {
            final Cursor folderCursor = mContext.getContentResolver().query(uiAccount.settings.defaultInbox,
                    UIProvider.FOLDERS_PROJECTION, null, null, null);

            if (folderCursor == null) {
                // This can happen when the notification is for the security policy notification
                // that happens before the account is setup
                LogUtils.w(LOG_TAG, "Null folder cursor for mailbox %s", uiAccount.settings.defaultInbox);
            } else {
                Folder folder = null;
                try {
                    if (folderCursor.moveToFirst()) {
                        folder = new Folder(folderCursor);
                    }
                } finally {
                    folderCursor.close();
                }

                if (folder != null) {
                    final FolderPreferences folderPreferences = new FolderPreferences(mContext,
                            uiAccount.getEmailAddress(), folder, true /* inbox */);

                    ringtoneUri = folderPreferences.getNotificationRingtoneUri();
                    vibrate = folderPreferences.isNotificationVibrateEnabled();
                } else {
                    LogUtils.e(LOG_TAG, "Null folder for mailbox %s", uiAccount.settings.defaultInbox);
                }
            }
        } else {
            LogUtils.e(LOG_TAG, "Null uiAccount for account id %d", account.mId);
        }

        int defaults = Notification.DEFAULT_LIGHTS;
        if (vibrate) {
            defaults |= Notification.DEFAULT_VIBRATE;
        }

        builder.setSound(TextUtils.isEmpty(ringtoneUri) ? null : Uri.parse(ringtoneUri)).setDefaults(defaults);
    }

    /**
     * Show (or update) a notification that the given attachment could not be forwarded. This
     * is a very unusual case, and perhaps we shouldn't even send a notification. For now,
     * it's helpful for debugging.
     *
     * NOTE: DO NOT CALL THIS METHOD FROM THE UI THREAD (DATABASE ACCESS)
     */
    public void showDownloadForwardFailedNotificationSynchronous(Attachment attachment) {
        final Message message = Message.restoreMessageWithId(mContext, attachment.mMessageKey);
        if (message == null)
            return;
        final Mailbox mailbox = Mailbox.restoreMailboxWithId(mContext, message.mMailboxKey);
        showNotification(mailbox.mAccountKey, mContext.getString(R.string.forward_download_failed_ticker),
                mContext.getString(R.string.forward_download_failed_title), attachment.mFileName, null,
                NOTIFICATION_ID_ATTACHMENT_WARNING);
    }

    /**
     * Returns a notification ID for login failed notifications for the given account account.
     */
    private static int getLoginFailedNotificationId(long accountId) {
        return NOTIFICATION_ID_BASE_LOGIN_WARNING + (int) accountId;
    }

    //TS:kaifeng.lu 2015-11-20 EMAIL BUGFIX_709873  ADD_S
    private static int getSendFailedNotificationId(long accountId) {
        return NOTIFICATION_ID_SEND_FAIL_WARNING + (int) accountId;
    }
    //TS:kaifeng.lu 2015-11-20 EMAIL BUGFIX_709873  ADD_E

    /**
     * Show (or update) a notification that there was a login failure for the given account.
     *
     * NOTE: DO NOT CALL THIS METHOD FROM THE UI THREAD (DATABASE ACCESS)
     */
    public void showLoginFailedNotificationSynchronous(long accountId, boolean incoming) {
        final Account account = Account.restoreAccountWithId(mContext, accountId);
        if (account == null)
            return;
        final Mailbox mailbox = Mailbox.restoreMailboxOfType(mContext, accountId, Mailbox.TYPE_INBOX);
        if (mailbox == null)
            return;

        final Intent settingsIntent;
        if (incoming) {
            settingsIntent = new Intent(Intent.ACTION_VIEW,
                    HeadlessAccountSettingsLoader.getIncomingSettingsUri(accountId));
            //TS: jin.dong 2015-05-29 EMAIL BUGFIX_991085 ADD_S
            // NOTE: Add the flag that HeadlessAccountSettingsLoader may use it and send it to
            // AccountServerSettingsActivity.
            // where we want show incoming view,after user do the password updated,Auto go to
            // outgoing view.
            settingsIntent.putExtra("AUTHENTICATIONFAILED", true);
            // TS: jin.dong 2015-05-29 EMAIL BUGFIX_991085 ADD_E
        } else {
            settingsIntent = new Intent(Intent.ACTION_VIEW,
                    HeadlessAccountSettingsLoader.getOutgoingSettingsUri(accountId));
        }
        showNotification(mailbox.mAccountKey,
                mContext.getString(R.string.login_failed_ticker, account.mDisplayName),
                mContext.getString(R.string.login_failed_title), account.getDisplayName(), settingsIntent,
                getLoginFailedNotificationId(accountId));
    }

    /**
     * Cancels the login failed notification for the given account.
     */
    public void cancelLoginFailedNotification(long accountId) {
        mNotificationManager.cancel(getLoginFailedNotificationId(accountId));
    }

    /**
     * Show (or update) a notification that the user's password is expiring. The given account
     * is used to update the display text, but, all accounts share the same notification ID.
     *
     * NOTE: DO NOT CALL THIS METHOD FROM THE UI THREAD (DATABASE ACCESS)
     */
    public void showPasswordExpiringNotificationSynchronous(long accountId) {
        final Account account = Account.restoreAccountWithId(mContext, accountId);
        if (account == null)
            return;

        final Intent intent = AccountSecurity.actionDevicePasswordExpirationIntent(mContext, accountId, false);
        final String accountName = account.getDisplayName();
        final String ticker = mContext.getString(R.string.password_expire_warning_ticker_fmt, accountName);
        final String title = mContext.getString(R.string.password_expire_warning_content_title);
        showNotification(accountId, ticker, title, accountName, intent, NOTIFICATION_ID_PASSWORD_EXPIRING);
    }

    /**
     * Show (or update) a notification that the user's password has expired. The given account
     * is used to update the display text, but, all accounts share the same notification ID.
     *
     * NOTE: DO NOT CALL THIS METHOD FROM THE UI THREAD (DATABASE ACCESS)
     */
    public void showPasswordExpiredNotificationSynchronous(long accountId) {
        final Account account = Account.restoreAccountWithId(mContext, accountId);
        if (account == null)
            return;

        final Intent intent = AccountSecurity.actionDevicePasswordExpirationIntent(mContext, accountId, true);
        final String accountName = account.getDisplayName();
        final String ticker = mContext.getString(R.string.password_expired_ticker);
        final String title = mContext.getString(R.string.password_expired_content_title);
        showNotification(accountId, ticker, title, accountName, intent, NOTIFICATION_ID_PASSWORD_EXPIRED);
    }

    /**
     * Cancels any password expire notifications [both expired & expiring].
     */
    public void cancelPasswordExpirationNotifications() {
        mNotificationManager.cancel(NOTIFICATION_ID_PASSWORD_EXPIRING);
        mNotificationManager.cancel(NOTIFICATION_ID_PASSWORD_EXPIRED);
    }

    /**
     * Show (or update) a security needed notification. If tapped, the user is taken to a
     * dialog asking whether he wants to update his settings.
     */
    public void showSecurityNeededNotification(Account account) {
        Intent intent = AccountSecurity.actionUpdateSecurityIntent(mContext, account.mId, true);
        String accountName = account.getDisplayName();
        String ticker = mContext.getString(R.string.security_needed_ticker_fmt, accountName);
        String title = mContext.getString(R.string.security_notification_content_update_title);
        showNotification(account.mId, ticker, title, accountName, intent,
                (int) (NOTIFICATION_ID_BASE_SECURITY_NEEDED + account.mId));
    }

    /**
     * Show (or update) a security changed notification. If tapped, the user is taken to the
     * account settings screen where he can view the list of enforced policies
     */
    public void showSecurityChangedNotification(Account account) {
        final Intent intent = new Intent(Intent.ACTION_VIEW,
                HeadlessAccountSettingsLoader.getIncomingSettingsUri(account.getId()));
        final String accountName = account.getDisplayName();
        final String ticker = mContext.getString(R.string.security_changed_ticker_fmt, accountName);
        final String title = mContext.getString(R.string.security_notification_content_change_title);
        showNotification(account.mId, ticker, title, accountName, intent,
                (int) (NOTIFICATION_ID_BASE_SECURITY_CHANGED + account.mId));
    }

    /**
     * Show (or update) a security unsupported notification. If tapped, the user is taken to the
     * account settings screen where he can view the list of unsupported policies
     */
    public void showSecurityUnsupportedNotification(Account account) {
        final Intent intent = new Intent(Intent.ACTION_VIEW,
                HeadlessAccountSettingsLoader.getIncomingSettingsUri(account.getId()));
        final String accountName = account.getDisplayName();
        final String ticker = mContext.getString(R.string.security_unsupported_ticker_fmt, accountName);
        final String title = mContext.getString(R.string.security_notification_content_unsupported_title);
        showNotification(account.mId, ticker, title, accountName, intent,
                (int) (NOTIFICATION_ID_BASE_SECURITY_NEEDED + account.mId));
    }

    // TS: chao.zhang 2015-09-14 EMAIL FEATURE-585337 ADD_S
    /**
     * Show (or update) a send failed notification. If tapped, the user is taken to the OUTBOX view
     * where he can view the list of failed mails. if tap retry,mail will retry to send.
     */
    public void showMailSendFaildNotification(long accountId, int number) {
        Account account = Account.restoreAccountWithId(mContext, accountId);
        if (account == null) {
            LogUtils.e(LOG_TAG, "Null account during notification SEND_FAILED ");
            return;
        }
        NotificationCompat.Builder notification = new NotificationCompat.Builder(mContext);
        ContentResolver conentResolver = mContext.getContentResolver();
        Folder UIFolder = queryUIFolder(conentResolver, accountId);
        Folder UIInboxFolder = queryUIInboxFolder(conentResolver, accountId);
        com.tct.mail.providers.Account UIAccount = queryUIaccount(conentResolver, accountId);
        if (UIFolder == null || UIAccount == null || UIInboxFolder == null) {
            LogUtils.e(LOG_TAG,
                    "Null UIFolder or null UIAccount during showMailSendFaildNotification,do nothing,just return ");
            return;
        }
        //get account address
        String accountAddress = account.getEmailAddress();
        //get account senderName,if null,use address instand
        String senderName = account.getSenderName();
        //get the notification's title(2 emails not send or Email not send)
        String title = createTitle(number);
        //get the warning icon
        Bitmap icon = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.ic_warning_grey);

        //get the conversation
        final Uri.Builder uriBuilder = UIFolder.conversationListUri.buildUpon();
        Cursor conversationsCursor = mContext.getContentResolver().query(uriBuilder.build(),
                UIProvider.CONVERSATION_PROJECTION, null, null, null);

        //The Martro style only supported after android L.
        //TS:kaifeng.lu 2016-01-04 EMAIL BUGFIX_1272060   MOD_S
        if (com.tct.mail.utils.Utils.isRunningLOrLater()) {
            notification.setColor(mContext.getResources().getColor(R.color.notification_icon_mail_orange));
            //TS:kaifeng.lu 2016-01-04 EMAIL BUGFIX_1272060   MOD_E
            NotificationCompat.InboxStyle digest = new NotificationCompat.InboxStyle(notification);

            // query current account's outbox mails.and get the subject.
            // Subject1;
            // Subject2;
            // Subject3;
            if (conversationsCursor != null) {
                try {
                    while (conversationsCursor.moveToNext()) {
                        final Conversation conversation = new Conversation(conversationsCursor);
                        String sub = createSubject(conversation);
                        digest.addLine(sub);
                    }
                } catch (Exception e) {
                    LogUtils.e(LOG_TAG, "exception happen during get notification subject", e.getMessage());
                } finally {
                    if (conversationsCursor != null) {
                        conversationsCursor.close();
                    }
                }
            }

            //only 1 mails failed, show the subject and its senderName and emailAddress.
            //Line1: show the mail's subject ,same with METHOD: notification.setContentText
            //TS:kaifeng.lu 2015-11-20 EMAIL BUGFIX_709873  MOD_S
            if (number > 0) {
                if (TextUtils.isEmpty(senderName)) {
                    //TS:kaifeng.lu 2016-01-04 EMAIL BUGFIX_1272060   DEL
                    //                    digest.addLine(accountAddress);
                } else {
                    digest.addLine(senderName); // Line2: show the senderName,same with METHOD:Notification#setSubText()
                }
                digest.setSummaryText(accountAddress);
            }
            //TS:kaifeng.lu 2015-11-20 EMAIL BUGFIX_709873  MOD_E
            notification.setContentTitle(title);
            notification.setTicker(accountAddress);
            notification.setLargeIcon(icon);
            notification.setSmallIcon(R.drawable.ic_notification_mail_24dp);
            //set the content click/tap intent
            //it will trigger going to OUBOX
            Intent toOutboxAction = Utils.createViewFolderIntent(mContext, UIFolder.folderUri.fullUri, UIAccount);
            PendingIntent contentIntent = PendingIntent.getActivity(mContext, 0, toOutboxAction,
                    PendingIntent.FLAG_UPDATE_CURRENT);
            notification.setContentIntent(contentIntent);
            //set the action click intent.
            //it will trigger the refresh.
            PendingIntent actionIntent = createRefreshIntent(UIFolder, true, account.mId);
            notification.addAction(R.drawable.ic_refresh_grey_24dp,
                    mContext.getResources().getString(R.string.retry), actionIntent);

            //set the failed mails.
            //TS:kaifeng.lu 2016-01-04 EMAIL BUGFIX_1272060   DEL
            //            notification.setNumber(number);

            //set the light and sound
            FolderPreferences folderPreferences = new FolderPreferences(mContext, UIAccount.getAccountId(),
                    UIInboxFolder, true);
            boolean vibrate = folderPreferences.isNotificationVibrateEnabled();
            //not give sound for retry notification
            //String ringtoneUri = folderPreferences.getNotificationRingtoneUri();
            int defaults = 0;
            if (vibrate) {
                defaults |= Notification.DEFAULT_VIBRATE;
            }
            // TS: zheng.zou 2015-09-10 EMAIL BUGFIX-557052 MOD_S
            //defaults |= Notification.FLAG_SHOW_LIGHTS;
            defaults |= Notification.DEFAULT_LIGHTS;
            notification.setDefaults(defaults);
            notification.setLights(0xff00ff00, 280, 2080);
            // TS: zheng.zou 2015-09-10 EMAIL BUGFIX-557052 MOD_E
            // notification.setSound(TextUtils.isEmpty(ringtoneUri) ? null
            //          : Uri.parse(ringtoneUri));
            //  LogUtils.i(LOG_TAG, "failed email in %s vibrateWhen: %s, playing notification: %s",
            //          LogUtils.sanitizeName(LOG_TAG, account.getEmailAddress()), vibrate);
            // TS: chao.zhang 2015-09-24 EMAIL FEATURE-585337 MOD_S
            //NOTE: UE:the notification only can be cancel by click.
            notification.setOngoing(true);
        }
        Notification warnningNotification = notification.build();
        warnningNotification.flags |= Notification.FLAG_AUTO_CANCEL;
        //TS:kaifeng.lu 2015-11-20 EMAIL BUGFIX_709873  MOD_S
        mNotificationManager.notify(getSendFailedNotificationId(accountId), warnningNotification);
        //TS:kaifeng.lu 2015-11-20 EMAIL BUGFIX_709873  MOD_E
        // TS: chao.zhang 2015-09-24 EMAIL FEATURE-585337 MOD_S
    }

    //TS: yanhua.chen 2015-1-20 EMAIL BUGFIX_1162996 ADD_S
    /**
     * Show (or update) a exchange sync new calendar notification. If tapped, the user is taken to the Exchange permission view
     * where he can view the exchange permission. if tap never ask again,notification will not show.
     */
    public void showCalendarNotification(String packageName) {
        NotificationCompat.Builder notification = new NotificationCompat.Builder(mContext);
        //get the warning icon
        Bitmap icon = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.ic_warning_grey);
        //The Martro style only supported after android L.
        if (com.tct.mail.utils.Utils.isRunningLOrLater()) {
            notification.setColor(mContext.getResources().getColor(R.color.notification_icon_mail_orange));
        }
        notification.setContentTitle(mContext.getResources().getString(R.string.sync_calendar_title));
        notification.setContentText(mContext.getResources().getString(R.string.sync_calendar_content));
        notification.setLargeIcon(icon);
        notification.setSmallIcon(R.drawable.ic_notification_mail_24dp);
        notification.setAutoCancel(true);//TS: zheng.zou 2016-3-19 EMAIL BUGFIX-1841389 ADD_S
        //set the content click/tap intent
        //it will trigger going to app permissions settings
        Intent gotoSettingsIntent = gotoSettingsIntent(mContext, packageName);
        PendingIntent contentIntent = PendingIntent.getActivity(mContext, 0, gotoSettingsIntent,
                PendingIntent.FLAG_UPDATE_CURRENT);
        notification.setContentIntent(contentIntent);

        //set the action click intent.
        //it will never receive notification.
        //never ask again
        Intent clickIntent = new Intent(NotificationActionIntentService.ACTION_CALENDAR_NEVER_ASK_AGAIN);
        clickIntent.setPackage(mContext.getPackageName());
        PendingIntent pendButtonIntent = PendingIntent.getService(mContext, 0, clickIntent,
                PendingIntent.FLAG_UPDATE_CURRENT);
        notification.addAction(R.drawable.notification_button,
                mContext.getResources().getString(R.string.notification_never_ask_again).toUpperCase(),
                pendButtonIntent);

        Notification warningNotification = notification.build();
        //        warningNotification.flags |= Notification.FLAG_AUTO_CANCEL;
        //        warningNotification.flags |= Notification.DEFAULT_VIBRATE;
        NotificationManager nm = (NotificationManager) mContext.getSystemService(mContext.NOTIFICATION_SERVICE);
        nm.notify(EXCHANGE_NEWCALENDAR_NOTIFICATION_ID, warningNotification);
    }

    /**
     * Show (or update) a exchange sync new contacts notification. If tapped, the user is taken to the Exchange permission view
     * where he can view the exchange permission. if tap never ask again,notification will not show.
     */
    public void showContactsNotification(String packageName) {
        NotificationCompat.Builder notification = new NotificationCompat.Builder(mContext);
        //get the warning icon
        Bitmap icon = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.ic_warning_grey);
        //The Martro style only supported after android L.
        if (com.tct.mail.utils.Utils.isRunningLOrLater()) {
            notification.setColor(mContext.getResources().getColor(R.color.notification_icon_mail_orange));
        }
        notification.setContentTitle(mContext.getResources().getString(R.string.sync_contacts_title));
        notification.setContentText(mContext.getResources().getString(R.string.sync_contacts_content));
        notification.setLargeIcon(icon);
        notification.setSmallIcon(R.drawable.ic_notification_mail_24dp);
        notification.setAutoCancel(true);//TS: zheng.zou 2016-3-19 EMAIL BUGFIX-1841389 ADD_S
        //set the content click/tap intent
        //it will trigger going to app permissions settings
        Intent gotoSettingsIntent = gotoSettingsIntent(mContext, packageName);
        PendingIntent contentIntent = PendingIntent.getActivity(mContext, 0, gotoSettingsIntent,
                PendingIntent.FLAG_UPDATE_CURRENT);
        notification.setContentIntent(contentIntent);

        //set the action click intent.
        //it will never receive notification.
        //never ask again
        Intent clickIntent = new Intent(NotificationActionIntentService.ACTION_CONTACTS_NEVER_ASK_AGAIN);
        clickIntent.setPackage(mContext.getPackageName());
        PendingIntent pendButtonIntent = PendingIntent.getService(mContext, 0, clickIntent,
                PendingIntent.FLAG_UPDATE_CURRENT);
        notification.addAction(R.drawable.notification_button,
                mContext.getResources().getString(R.string.notification_never_ask_again).toUpperCase(),
                pendButtonIntent);

        Notification warningNotification = notification.build();
        //        warningNotification.flags |= Notification.FLAG_AUTO_CANCEL;
        //        warningNotification.flags |= Notification.DEFAULT_VIBRATE;
        NotificationManager nm = (NotificationManager) mContext.getSystemService(mContext.NOTIFICATION_SERVICE);
        nm.notify(EXCHANGE_NEWCONTACTS_NOTIFICATION_ID, warningNotification);
    }

    //TS: zheng.zou 2016-1-22 EMAIL BUGFIX-1431088 ADD_S
    public void showStoragePermissionNotification(String pgkName) {
        NotificationCompat.Builder notification = new NotificationCompat.Builder(mContext);
        //get the warning icon
        Bitmap icon = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.ic_warning_grey);
        //The Martro style only supported after android L.
        if (com.tct.mail.utils.Utils.isRunningLOrLater()) {
            notification.setColor(mContext.getResources().getColor(R.color.notification_icon_mail_orange));
        }
        notification.setContentTitle(mContext.getResources().getString(R.string.sync_storage_title));
        notification.setContentText(mContext.getResources().getString(R.string.sync_storage_content));
        notification.setLargeIcon(icon);
        notification.setSmallIcon(R.drawable.ic_notification_mail_24dp);
        //set the content click/tap intent
        //it will trigger going to app permissions settings
        Intent gotoSettingsIntent = gotoSettingsIntent(mContext, pgkName);
        PendingIntent contentIntent = PendingIntent.getActivity(mContext, 0, gotoSettingsIntent,
                PendingIntent.FLAG_UPDATE_CURRENT);
        notification.setContentIntent(contentIntent);
        notification.setAutoCancel(true); //TS: zheng.zou 2016-3-19 EMAIL BUGFIX-1841389 ADD_S

        //set the action click intent.
        //it will never receive notification.
        //never ask again
        Intent clickIntent = new Intent(NotificationActionIntentService.ACTION_STORAGE_NEVER_ASK_AGAIN);
        clickIntent.setPackage(mContext.getPackageName());
        PendingIntent pendButtonIntent = PendingIntent.getService(mContext, 0, clickIntent,
                PendingIntent.FLAG_UPDATE_CURRENT);
        notification.addAction(R.drawable.notification_button,
                mContext.getResources().getString(R.string.notification_never_ask_again).toUpperCase(),
                pendButtonIntent);

        Notification warningNotification = notification.build();
        NotificationManager nm = (NotificationManager) mContext.getSystemService(mContext.NOTIFICATION_SERVICE);
        nm.notify(EXCHANGE_NEWSTORAGE_NOTIFICATION_ID, warningNotification);
    }
    //TS: zheng.zou 2016-1-22 EMAIL BUGFIX-1431088 ADD_E

    /**
     * return intent,the user is taken to the Exchange permission view
     */
    public Intent gotoSettingsIntent(Context context, String packageName) {
        Intent intent;
        if (isIntentExisting(context, TCT_ACTION_MANAGE_APP)) {
            //Goto setting application permission
            intent = new Intent(TCT_ACTION_MANAGE_APP);
            intent.putExtra(TCT_EXTRA_PACKAGE_NAME, packageName);
        } else {
            //Goto settings details
            final Uri packageURI = Uri.parse("package:" + packageName);
            intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, packageURI);
        }
        return intent;
    }

    public boolean isIntentExisting(Context context, String action) {
        final PackageManager packageManager = context.getPackageManager();
        final Intent intent = new Intent(action);
        List<ResolveInfo> resolveInfo = packageManager.queryIntentActivities(intent, PackageManager.MATCH_ALL);
        return resolveInfo.size() > 0;
    }
    //TS: yanhua.chen 2015-1-20 EMAIL BUGFIX_1162996 ADD_E

    /**
     * query the oubox's folder.
     * @param conentResolver
     * @param accountId
     * @return Folder
     */
    private Folder queryUIFolder(ContentResolver conentResolver, long accountId) {
        Mailbox outbox = Mailbox.restoreMailboxOfType(mContext, accountId, Mailbox.TYPE_OUTBOX);
        if (outbox == null) {
            LogUtils.e(LOG_TAG, "null oubox in queryUIFolder,may account deleted,return");
            return null;
        }
        Uri folderUri = EmailProvider.uiUri("uifolder", outbox.mId);
        Cursor folderCursor = conentResolver.query(folderUri, UIProvider.FOLDERS_PROJECTION, null, null, null);
        Folder folder = null;
        if (folderCursor != null) {
            try {
                if (folderCursor.moveToFirst()) {
                    folder = new Folder(folderCursor);
                } else {
                    LogUtils.e(LOG_TAG, "Empty folder cursor for account ", "mail uri is", folderUri);
                    return folder;
                }
            } finally {
                folderCursor.close();
            }
        }
        return folder;
    }

    /**
     * query the inbox's folder.
     * @param conentResolver
     * @param accountId
     * @return Folder
     */
    private Folder queryUIInboxFolder(ContentResolver conentResolver, long accountId) {
        Mailbox inbox = Mailbox.restoreMailboxOfType(mContext, accountId, Mailbox.TYPE_INBOX);
        if (inbox == null) {
            LogUtils.e(LOG_TAG, "null inbox in queryUIFolder,may account deleted,return");
            return null;
        }
        Uri folderUri = EmailProvider.uiUri("uifolder", inbox.mId);
        Cursor folderCursor = conentResolver.query(folderUri, UIProvider.FOLDERS_PROJECTION, null, null, null);
        Folder folder = null;
        if (folderCursor != null) {
            try {
                if (folderCursor.moveToFirst()) {
                    folder = new Folder(folderCursor);
                } else {
                    LogUtils.e(LOG_TAG, "Empty folder cursor for account ", "mail uri is", folderUri);
                    return folder;
                }
            } finally {
                folderCursor.close();
            }
        }
        return folder;
    }

    /**
     * query the account's UIaccount.
     * @param conentResolver
     * @param accountId
     * @return com.tct.mail.providers.Account
     */
    private com.tct.mail.providers.Account queryUIaccount(ContentResolver conentResolver, long accountId) {
        com.tct.mail.providers.Account account = null;
        Cursor accountCursor = null;
        Uri accountUri = EmailProvider.uiUri("uiaccount", accountId);
        try {
            accountCursor = conentResolver.query(accountUri, UIProvider.ACCOUNTS_PROJECTION, null, null, null);
            if (accountCursor == null) {
                LogUtils.e(LOG_TAG, "Null account cursor for account " + accountUri);
                return null;
            }
            if (accountCursor.moveToFirst()) {
                account = com.tct.mail.providers.Account.builder().buildFrom(accountCursor);
            }
        } catch (Exception e) {
            LogUtils.e(LOG_TAG, "Exception happen in queryUIaccount." + e);
        } finally {
            accountCursor.close();
        }
        if (account == null) {
            LogUtils.d(LOG_TAG, "Tried to create a notification for a missing account " + accountUri);
        }
        return account;
    }

    /**
     * @param context a context used to construct the title
     * @param failedCount the number of failed messages
     * @return e.g. "1 Email not send" or "2 emails not send"
     */
    private String createTitle(int failedCount) {
        final Resources resources = mContext.getResources();
        return resources.getQuantityString(R.plurals.send_failed_messages, failedCount, failedCount);
    }

    /**
     * create the subjects display in notification. 1 mails:just show the subject 2 mails:
     * subject1;subject2;subject3...
     * @param Conversation
     * @return Strings.
     */
    private String createSubject(Conversation conversation) {
        StringBuilder subject = new StringBuilder();
        if (TextUtils.isEmpty(conversation.subject)) {
            //subject empty,what ?show nothing? "No Subject" is good
            subject.append("No Subject");
        } else {
            subject.append(conversation.subject);
        }
        subject.append(";");
        return subject.toString();
    }

    // TS: chao.zhang 2015-09-14 EMAIL FEATURE-ID ADD_E
    private PendingIntent createRefreshIntent(Folder folder, boolean cleanStatus, long accountId) {
        String packageName = mContext.getPackageName();
        long boxId = -1;
        Mailbox outbox = Mailbox.restoreMailboxOfType(mContext, accountId, Mailbox.TYPE_OUTBOX);
        if (outbox != null) {
            boxId = outbox.mId;
        }
        Intent clickIntent = new Intent(NotificationActionIntentService.ACTION_REFRESH);
        clickIntent.setPackage(packageName);
        clickIntent.putExtra(NotificationUtils.EXTRA_NEED_CLEAN_STATUS, cleanStatus);
        clickIntent.putExtra(NotificationUtils.EXTRA_OUTBOX_ID, boxId);
        // TS: jian.xu 2015-11-26 EMAIL BUGFIX-944797 MOD_S
        //Note: Get truth notification id.
        clickIntent.putExtra(NotificationUtils.EXTRA_FAIL_NOTIFICATION_ID, getSendFailedNotificationId(accountId));
        // TS: jian.xu 2015-11-26 EMAIL BUGFIX-944797 MOD_E
        clickIntent.setData(folder.refreshUri);
        // clickIntent.setData(notificationAction.mConversation.uri);
        PendingIntent actionIntent = PendingIntent.getService(mContext, 0, clickIntent,
                PendingIntent.FLAG_UPDATE_CURRENT);
        return actionIntent;
    }

    // TS: jian.xu 2015-11-26 EMAIL BUGFIX-944797 MOD_S
    public void cancelFailStatusNotification(long accountId) {
        NotificationManager notificationManager = getInstance(mContext).mNotificationManager;
        notificationManager.cancel(getSendFailedNotificationId(accountId));
    }
    // TS: jian.xu 2015-11-26 EMAIL BUGFIX-944797 MOD_E

    //TS: yanhua.chen 2015-1-20 EMAIL BUGFIX_1162996 ADD_S
    public void cancelCalendarOrContactsNotification() {
        NotificationManager notificationManager = getInstance(mContext).mNotificationManager;
        notificationManager.cancel(NotificationController.EXCHANGE_NEWCALENDAR_NOTIFICATION_ID);
        notificationManager.cancel(NotificationController.EXCHANGE_NEWCONTACTS_NOTIFICATION_ID);
        notificationManager.cancel(NotificationController.EXCHANGE_NEWSTORAGE_NOTIFICATION_ID); //TS: zheng.zou 2016-1-22 EMAIL BUGFIX-1431088 ADD
    }

    //TS: yanhua.chen 2015-1-20 EMAIL BUGFIX_1162996 ADD_E
    /**
     * Cancels all security needed notifications.
     */
    public void cancelSecurityNeededNotification() {
        EmailAsyncTask.runAsyncParallel(new Runnable() {
            @Override
            public void run() {
                Cursor c = mContext.getContentResolver().query(Account.CONTENT_URI, Account.ID_PROJECTION, null,
                        null, null);
                try {
                    while (c.moveToNext()) {
                        long id = c.getLong(Account.ID_PROJECTION_COLUMN);
                        mNotificationManager.cancel((int) (NOTIFICATION_ID_BASE_SECURITY_NEEDED + id));
                    }
                } finally {
                    c.close();
                }
            }
        });
    }

    /**
     * Cancels all notifications for the specified account id. This includes new mail notifications,
     * as well as special login/security notifications.
     */
    public static void cancelNotifications(final Context context, final Account account) {
        final EmailServiceUtils.EmailServiceInfo serviceInfo = EmailServiceUtils.getServiceInfoForAccount(context,
                account.mId);
        if (serviceInfo == null) {
            LogUtils.d(LOG_TAG, "Can't cancel notification for missing account %d", account.mId);
            return;
        }
        final android.accounts.Account notifAccount = account.getAccountManagerAccount(serviceInfo.accountType);

        NotificationUtils.clearAccountNotifications(context, notifAccount);

        final NotificationManager notificationManager = getInstance(context).mNotificationManager;

        notificationManager.cancel((int) (NOTIFICATION_ID_BASE_LOGIN_WARNING + account.mId));
        notificationManager.cancel((int) (NOTIFICATION_ID_BASE_SECURITY_NEEDED + account.mId));
        notificationManager.cancel((int) (NOTIFICATION_ID_BASE_SECURITY_CHANGED + account.mId));
    }

    private static void refreshNotificationsForAccount(final Context context, final long accountId) {
        synchronized (sNotificationDelayedMessageLock) {
            if (sNotificationDelayedMessagePending) {
                sRefreshAccountSet.add(accountId);
            } else {
                ensureHandlerExists();
                sNotificationHandler.sendMessageDelayed(
                        android.os.Message.obtain(sNotificationHandler, NOTIFICATION_DELAYED_MESSAGE, context),
                        NOTIFICATION_DELAY);
                sNotificationDelayedMessagePending = true;
                refreshNotificationsForAccountInternal(context, accountId);
            }
        }
    }

    private static void refreshNotificationsForAccountInternal(final Context context, final long accountId) {
        final Uri accountUri = EmailProvider.uiUri("uiaccount", accountId);

        final ContentResolver contentResolver = context.getContentResolver();

        final Cursor mailboxCursor = contentResolver.query(
                ContentUris.withAppendedId(EmailContent.MAILBOX_NOTIFICATION_URI, accountId), null, null, null,
                null);
        try {
            while (mailboxCursor.moveToNext()) {
                final long mailboxId = mailboxCursor.getLong(EmailContent.NOTIFICATION_MAILBOX_ID_COLUMN);
                if (mailboxId == 0)
                    continue;

                final int unseenCount = mailboxCursor.getInt(EmailContent.NOTIFICATION_MAILBOX_UNSEEN_COUNT_COLUMN);

                final int unreadCount;
                // If nothing is unseen, clear the notification
                if (unseenCount == 0) {
                    unreadCount = 0;
                } else {
                    unreadCount = mailboxCursor.getInt(EmailContent.NOTIFICATION_MAILBOX_UNREAD_COUNT_COLUMN);
                }
                //TS:zheng.zou 2015-12-17 EMAIL BUGFIX_861247  ADD_S
                Mailbox mailbox = Mailbox.restoreMailboxWithId(context, mailboxId);
                //TS:kaifeng.lu 2015-12-18 EMAIL BUGFIX_1190892  MOD_S
                if (mailbox != null && mailbox.mType == Mailbox.TYPE_INBOX) {
                    //TS:kaifeng.lu 2015-12-18 EMAIL BUGFIX_1190892  MOD_E
                    final Cursor unreadCursor = contentResolver.query(ContentUris
                            .withAppendedId(EmailContent.MAILBOX_MOST_RECENT_UNREAD_MESSAGE_URI, mailboxId), null,
                            null, null, null);
                    long mostRecentUnreadMsgId = 0;
                    if (unreadCursor != null && unreadCursor.moveToFirst()) {
                        try {
                            mostRecentUnreadMsgId = unreadCursor
                                    .getLong(EmailContent.MAILBOX_MOST_RECENT_UNREAD_ID_COULUM);
                        } finally {
                            unreadCursor.close();
                        }
                    }
                    final int key = getUnreadKey(accountId, mailboxId);
                    long lastMostRecentUnreadMsgId = sLastUnreadIds.get(key);
                    LogUtils.i(LOG_TAG,
                            "key=" + key + " unseenCount=" + unseenCount + " unreadCount=" + unreadCount
                                    + " lastMostRecentUnreadMsgId = " + lastMostRecentUnreadMsgId
                                    + " mostRecentUnreadMsgId=" + mostRecentUnreadMsgId);
                    //no need to send notification if latest Unread id not change
                    if (lastMostRecentUnreadMsgId != 0 && unseenCount != 0
                            && lastMostRecentUnreadMsgId == mostRecentUnreadMsgId) {
                        LogUtils.i(LOG_TAG, "no need to send notification broadcast, continue");
                        continue;
                    }
                    sLastUnreadIds.put(key, mostRecentUnreadMsgId);
                }
                //TS:zheng.zou 2015-12-17 EMAIL BUGFIX_861247  ADD_E

                final Uri folderUri = EmailProvider.uiUri("uifolder", mailboxId);

                LogUtils.d(LOG_TAG, "Changes to account " + accountId + ", folder: " + mailboxId + ", unreadCount: "
                        + unreadCount + ", unseenCount: " + unseenCount);

                final Intent intent = new Intent(UIProvider.ACTION_UPDATE_NOTIFICATION);
                intent.setPackage(context.getPackageName());
                intent.setType(EmailProvider.EMAIL_APP_MIME_TYPE);

                intent.putExtra(UIProvider.UpdateNotificationExtras.EXTRA_ACCOUNT, accountUri);
                intent.putExtra(UIProvider.UpdateNotificationExtras.EXTRA_FOLDER, folderUri);
                intent.putExtra(UIProvider.UpdateNotificationExtras.EXTRA_UPDATED_UNREAD_COUNT, unreadCount);
                intent.putExtra(UIProvider.UpdateNotificationExtras.EXTRA_UPDATED_UNSEEN_COUNT, unseenCount);

                context.sendOrderedBroadcast(intent, null);
            }
        } finally {
            mailboxCursor.close();
        }
    }

    //TS:zheng.zou 2015-12-17 EMAIL BUGFIX_861247  ADD_S
    private static int getUnreadKey(long accountId, long mailboxId) {
        return (int) ((accountId << 16) + mailboxId);
    }
    //TS:zheng.zou 2015-12-17 EMAIL BUGFIX_861247  ADD_E

    public static void handleUpdateNotificationIntent(Context context, Intent intent) {
        final Uri accountUri = intent.getParcelableExtra(UIProvider.UpdateNotificationExtras.EXTRA_ACCOUNT);
        final Uri folderUri = intent.getParcelableExtra(UIProvider.UpdateNotificationExtras.EXTRA_FOLDER);
        final int unreadCount = intent.getIntExtra(UIProvider.UpdateNotificationExtras.EXTRA_UPDATED_UNREAD_COUNT,
                0);
        final int unseenCount = intent.getIntExtra(UIProvider.UpdateNotificationExtras.EXTRA_UPDATED_UNSEEN_COUNT,
                0);

        final ContentResolver contentResolver = context.getContentResolver();

        final Cursor accountCursor = contentResolver.query(accountUri, UIProvider.ACCOUNTS_PROJECTION, null, null,
                null);

        if (accountCursor == null) {
            LogUtils.e(LOG_TAG, "Null account cursor for account " + accountUri);
            return;
        }

        com.tct.mail.providers.Account account = null;
        try {
            if (accountCursor.moveToFirst()) {
                account = com.tct.mail.providers.Account.builder().buildFrom(accountCursor);
            }
        } finally {
            accountCursor.close();
        }

        if (account == null) {
            LogUtils.d(LOG_TAG, "Tried to create a notification for a missing account " + accountUri);
            return;
        }

        final Cursor folderCursor = contentResolver.query(folderUri, UIProvider.FOLDERS_PROJECTION, null, null,
                null);

        if (folderCursor == null) {
            LogUtils.e(LOG_TAG, "Null folder cursor for account " + accountUri + ", mailbox " + folderUri);
            return;
        }

        Folder folder = null;
        try {
            if (folderCursor.moveToFirst()) {
                folder = new Folder(folderCursor);
            } else {
                LogUtils.e(LOG_TAG, "Empty folder cursor for account " + accountUri + ", mailbox " + folderUri);
                return;
            }
        } finally {
            folderCursor.close();
        }

        // TODO: we don't always want getAttention to be true, but we don't necessarily have a
        // good heuristic for when it should or shouldn't be.
        NotificationUtils.sendSetNewEmailIndicatorIntent(context, unreadCount, unseenCount, account, folder,
                true /* getAttention */);
    }

    private static void refreshAllNotifications(final Context context) {
        synchronized (sNotificationDelayedMessageLock) {
            if (sNotificationDelayedMessagePending) {
                sRefreshAllNeeded = true;
            } else {
                ensureHandlerExists();
                sNotificationHandler.sendMessageDelayed(
                        android.os.Message.obtain(sNotificationHandler, NOTIFICATION_DELAYED_MESSAGE, context),
                        NOTIFICATION_DELAY);
                sNotificationDelayedMessagePending = true;
                refreshAllNotificationsInternal(context);
            }
        }
    }

    private static void refreshAllNotificationsInternal(final Context context) {
        NotificationUtils.resendNotifications(context, false, null, null, null /* ContactPhotoFetcher */);
    }

    /**
     * Observer invoked whenever a message we're notifying the user about changes.
     */
    private static class MessageContentObserver extends ContentObserver {
        private final Context mContext;
        private final long mAccountId;

        public MessageContentObserver(final Handler handler, final Context context, final long accountId) {
            super(handler);
            mContext = context;
            mAccountId = accountId;
        }

        @Override
        public void onChange(final boolean selfChange) {
            refreshNotificationsForAccount(mContext, mAccountId);
        }
    }

    /**
     * Observer invoked whenever an account is modified. This could mean the user changed the
     * notification settings.
     */
    private static class AccountContentObserver extends ContentObserver {
        private final Context mContext;

        public AccountContentObserver(final Handler handler, final Context context) {
            super(handler);
            mContext = context;
        }

        @Override
        public void onChange(final boolean selfChange) {
            final ContentResolver resolver = mContext.getContentResolver();
            final Cursor c = resolver.query(Account.CONTENT_URI, EmailContent.ID_PROJECTION, null, null, null);
            final Set<Long> newAccountList = new HashSet<Long>();
            final Set<Long> removedAccountList = new HashSet<Long>();
            if (c == null) {
                // Suspender time ... theoretically, this will never happen
                LogUtils.wtf(LOG_TAG, "#onChange(); NULL response for account id query");
                return;
            }
            try {
                while (c.moveToNext()) {
                    long accountId = c.getLong(EmailContent.ID_PROJECTION_COLUMN);
                    newAccountList.add(accountId);
                }
            } finally {
                c.close();
            }
            // NOTE: Looping over three lists is not necessarily the most efficient. However, the
            // account lists are going to be very small, so, this will not be necessarily bad.
            // Cycle through existing notification list and adjust as necessary
            for (final long accountId : sInstance.mNotificationMap.keySet()) {
                if (!newAccountList.remove(accountId)) {
                    // account id not in the current set of notifiable accounts
                    removedAccountList.add(accountId);
                }
            }
            // A new account was added to the notification list
            for (final long accountId : newAccountList) {
                sInstance.registerMessageNotification(accountId);
            }
            // An account was removed from the notification list
            for (final long accountId : removedAccountList) {
                sInstance.unregisterMessageNotification(accountId);
            }

            refreshAllNotifications(mContext);
        }
    }

    /**
     * Thread to handle all notification actions through its own {@link Looper}.
     */
    private static class NotificationThread implements Runnable {
        /** Lock to ensure proper initialization */
        private final Object mLock = new Object();
        /** The {@link Looper} that handles messages for this thread */
        private Looper mLooper;

        public NotificationThread() {
            new Thread(null, this, "EmailNotification").start();
            synchronized (mLock) {
                while (mLooper == null) {
                    try {
                        mLock.wait();
                    } catch (InterruptedException ex) {
                        // Loop around and wait again
                    }
                }
            }
        }

        @Override
        public void run() {
            synchronized (mLock) {
                Looper.prepare();
                mLooper = Looper.myLooper();
                mLock.notifyAll();
            }
            Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
            Looper.loop();
        }

        public Looper getLooper() {
            return mLooper;
        }
    }
}