RefreshManager.java revision 54c91f00d7f967690a80b992062e75c40182d088
121efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki/*
221efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki * Copyright (C) 2010 The Android Open Source Project
321efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki *
421efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki * Licensed under the Apache License, Version 2.0 (the "License");
521efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki * you may not use this file except in compliance with the License.
621efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki * You may obtain a copy of the License at
721efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki *
821efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki *      http://www.apache.org/licenses/LICENSE-2.0
921efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki *
1021efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki * Unless required by applicable law or agreed to in writing, software
1121efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki * distributed under the License is distributed on an "AS IS" BASIS,
1221efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1321efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki * See the License for the specific language governing permissions and
1421efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki * limitations under the License.
1521efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki */
1621efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki
1721efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onukipackage com.android.email;
1821efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki
1931d9acbf0623872f9d4a2b3210b5970854b654c7Marc Blankimport com.android.emailcommon.Logging;
202193962ca2b3157e79f731736afa2a0c972e778aMarc Blankimport com.android.emailcommon.mail.MessagingException;
2131d9acbf0623872f9d4a2b3210b5970854b654c7Marc Blankimport com.android.emailcommon.utility.Utility;
2221efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki
2321efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onukiimport android.content.Context;
2421efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onukiimport android.os.Handler;
2521efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onukiimport android.util.Log;
2621efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki
2721efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onukiimport java.util.ArrayList;
2821efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onukiimport java.util.Collection;
2921efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onukiimport java.util.HashMap;
3021efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki
3121efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki/**
3221efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki * Class that handles "refresh" (and "send pending messages" for outboxes) related functionalities.
3321efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki *
3421efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki * <p>This class is responsible for two things:
3521efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki * <ul>
3621efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki *   <li>Taking refresh requests of mailbox-lists and message-lists and the "send outgoing
3721efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki *       messages" requests from UI, and calls appropriate methods of {@link Controller}.
3821efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki *       Note at this point the timer-based refresh
3921efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki *       (by {@link com.android.email.service.MailService}) uses {@link Controller} directly.
4021efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki *   <li>Keeping track of which mailbox list/message list is actually being refreshed.
4121efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki * </ul>
4221efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki * Refresh requests will be ignored if a request to the same target is already requested, or is
4321efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki * already being refreshed.
4421efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki *
4521efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki * <p>Conceptually it can be a part of {@link Controller}, but extracted for easy testing.
46715a19be28723908dbf332b9a9029993510dad0eMakoto Onuki *
47e357f5879187124c7af5c2ece5d7d3e4f60f07d2Makoto Onuki * (All public methods must be called on the UI thread.  All callbacks will be called on the UI
48715a19be28723908dbf332b9a9029993510dad0eMakoto Onuki * thread.)
4921efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki */
5021efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onukipublic class RefreshManager {
51295297581c8af9b6ee0a41487b7fded08739890cMakoto Onuki    private static final boolean LOG_ENABLED = false; // DONT SUBMIT WITH TRUE
5221efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki    private static final long MAILBOX_AUTO_REFRESH_INTERVAL = 5 * 60 * 1000; // in milliseconds
536993130a7cb089da642ee689603a2f8487b3fd39Makoto Onuki    private static final long MAILBOX_LIST_AUTO_REFRESH_INTERVAL = 5 * 60 * 1000; // in milliseconds
5421efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki
5521efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki    private static RefreshManager sInstance;
5621efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki
5721efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki    private final Clock mClock;
5821efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki    private final Context mContext;
5921efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki    private final Controller mController;
6021efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki    private final Controller.Result mControllerResult;
6121efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki
6221efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki    /** Last error message */
6321efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki    private String mErrorMessage;
6421efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki
6521efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki    public interface Listener {
66be1aa37dc516a9c3dd4af65b11f92a5951f5c5c3Makoto Onuki        /**
67be1aa37dc516a9c3dd4af65b11f92a5951f5c5c3Makoto Onuki         * Refresh status of a mailbox list or a message list has changed.
68be1aa37dc516a9c3dd4af65b11f92a5951f5c5c3Makoto Onuki         *
69be1aa37dc516a9c3dd4af65b11f92a5951f5c5c3Makoto Onuki         * @param accountId ID of the account.
70be1aa37dc516a9c3dd4af65b11f92a5951f5c5c3Makoto Onuki         * @param mailboxId -1 if it's about the mailbox list, or the ID of the mailbox list in
71be1aa37dc516a9c3dd4af65b11f92a5951f5c5c3Makoto Onuki         * question.
72be1aa37dc516a9c3dd4af65b11f92a5951f5c5c3Makoto Onuki         */
7321efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki        public void onRefreshStatusChanged(long accountId, long mailboxId);
74be1aa37dc516a9c3dd4af65b11f92a5951f5c5c3Makoto Onuki
75be1aa37dc516a9c3dd4af65b11f92a5951f5c5c3Makoto Onuki        /**
76be1aa37dc516a9c3dd4af65b11f92a5951f5c5c3Makoto Onuki         * Error callback.
77be1aa37dc516a9c3dd4af65b11f92a5951f5c5c3Makoto Onuki         *
78be1aa37dc516a9c3dd4af65b11f92a5951f5c5c3Makoto Onuki         * @param accountId ID of the account, or -1 if unknown.
79be1aa37dc516a9c3dd4af65b11f92a5951f5c5c3Makoto Onuki         * @param mailboxId ID of the mailbox, or -1 if unknown.
80be1aa37dc516a9c3dd4af65b11f92a5951f5c5c3Makoto Onuki         * @param message error message which can be shown to the user.
81be1aa37dc516a9c3dd4af65b11f92a5951f5c5c3Makoto Onuki         */
8221efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki        public void onMessagingError(long accountId, long mailboxId, String message);
8321efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki    }
8421efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki
8521efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki    private final ArrayList<Listener> mListeners = new ArrayList<Listener>();
8621efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki
8721efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki    /**
8821efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki     * Status of a mailbox list/message list.
8921efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki     */
9021efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki    /* package */ static class Status {
9121efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki        /**
9221efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki         * True if a refresh of the mailbox is requested, and not finished yet.
9321efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki         */
9421efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki        private boolean mIsRefreshRequested;
9521efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki
9621efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki        /**
9721efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki         * True if the mailbox is being refreshed.
9821efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki         *
9921efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki         * Set true when {@link #onRefreshRequested} is called, i.e. refresh is requested by UI.
10021efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki         * Note refresh can occur without a request from UI as well (e.g. timer based refresh).
10121efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki         * In which case, {@link #mIsRefreshing} will be true with {@link #mIsRefreshRequested}
10221efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki         * being false.
10321efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki         */
10421efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki        private boolean mIsRefreshing;
10521efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki
10621efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki        private long mLastRefreshTime;
10721efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki
10821efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki        public boolean isRefreshing() {
10921efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki            return mIsRefreshRequested || mIsRefreshing;
11021efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki        }
11121efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki
11221efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki        public boolean canRefresh() {
11321efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki            return !isRefreshing();
11421efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki        }
11521efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki
11621efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki        public void onRefreshRequested() {
11721efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki            mIsRefreshRequested = true;
11821efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki        }
11921efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki
12021efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki        public long getLastRefreshTime() {
12121efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki            return mLastRefreshTime;
12221efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki        }
12321efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki
12421efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki        public void onCallback(MessagingException exception, int progress, Clock clock) {
12521efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki            if (exception == null && progress == 0) {
12621efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki                // Refresh started
12721efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki                mIsRefreshing = true;
12821efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki            } else if (exception != null || progress == 100) {
12921efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki                // Refresh finished
13021efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki                mIsRefreshing = false;
13121efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki                mIsRefreshRequested = false;
13221efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki                mLastRefreshTime = clock.getTime();
13321efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki            }
13421efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki        }
13521efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki    }
13621efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki
13721efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki    /**
13821efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki     * Map of accounts/mailboxes to {@link Status}.
13921efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki     */
14021efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki    private static class RefreshStatusMap {
14121efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki        private final HashMap<Long, Status> mMap = new HashMap<Long, Status>();
14221efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki
14321efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki        public Status get(long id) {
14421efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki            Status s = mMap.get(id);
14521efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki            if (s == null) {
14621efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki                s = new Status();
14721efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki                mMap.put(id, s);
14821efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki            }
14921efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki            return s;
15021efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki        }
15121efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki
15221efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki        public boolean isRefreshingAny() {
15321efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki            for (Status s : mMap.values()) {
15421efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki                if (s.isRefreshing()) {
15521efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki                    return true;
15621efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki                }
15721efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki            }
15821efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki            return false;
15921efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki        }
16021efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki    }
16121efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki
16221efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki    private final RefreshStatusMap mMailboxListStatus = new RefreshStatusMap();
16321efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki    private final RefreshStatusMap mMessageListStatus = new RefreshStatusMap();
16421efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki
16521efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki    /**
16621efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki     * @return the singleton instance.
16721efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki     */
16821efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki    public static synchronized RefreshManager getInstance(Context context) {
16921efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki        if (sInstance == null) {
170c184f36c2df16431693d7709e28ded593efc3da7Marc Blank            sInstance = new RefreshManager(context, Controller.getInstance(context),
17121efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki                    Clock.INSTANCE, new Handler());
17221efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki        }
17321efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki        return sInstance;
17421efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki    }
17521efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki
176e357f5879187124c7af5c2ece5d7d3e4f60f07d2Makoto Onuki    protected RefreshManager(Context context, Controller controller, Clock clock,
17721efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki            Handler handler) {
17821efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki        mClock = clock;
17921efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki        mContext = context.getApplicationContext();
18021efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki        mController = controller;
18121efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki        mControllerResult = new ControllerResultUiThreadWrapper<ControllerResult>(
18221efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki                handler, new ControllerResult());
18321efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki        mController.addResultCallback(mControllerResult);
18421efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki    }
18521efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki
186e357f5879187124c7af5c2ece5d7d3e4f60f07d2Makoto Onuki    /**
187e357f5879187124c7af5c2ece5d7d3e4f60f07d2Makoto Onuki     * MUST be called for mock instances.  (The actual instance is a singleton, so no cleanup
188e357f5879187124c7af5c2ece5d7d3e4f60f07d2Makoto Onuki     * is necessary.)
189e357f5879187124c7af5c2ece5d7d3e4f60f07d2Makoto Onuki     */
190e357f5879187124c7af5c2ece5d7d3e4f60f07d2Makoto Onuki    public void cleanUpForTest() {
191e357f5879187124c7af5c2ece5d7d3e4f60f07d2Makoto Onuki        mController.removeResultCallback(mControllerResult);
192e357f5879187124c7af5c2ece5d7d3e4f60f07d2Makoto Onuki    }
193e357f5879187124c7af5c2ece5d7d3e4f60f07d2Makoto Onuki
19421efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki    public void registerListener(Listener listener) {
19521efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki        if (listener == null) {
19654c91f00d7f967690a80b992062e75c40182d088Makoto Onuki            throw new IllegalArgumentException();
19721efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki        }
19821efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki        mListeners.add(listener);
19921efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki    }
20021efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki
20121efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki    public void unregisterListener(Listener listener) {
20221efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki        if (listener == null) {
20354c91f00d7f967690a80b992062e75c40182d088Makoto Onuki            throw new IllegalArgumentException();
20421efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki        }
20521efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki        mListeners.remove(listener);
20621efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki    }
20721efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki
20821efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki    /**
20921efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki     * Refresh the mailbox list of an account.
21021efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki     */
21121efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki    public boolean refreshMailboxList(long accountId) {
21221efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki        final Status status = mMailboxListStatus.get(accountId);
21321efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki        if (!status.canRefresh()) return false;
21421efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki
215295297581c8af9b6ee0a41487b7fded08739890cMakoto Onuki        if (LOG_ENABLED) {
21631d9acbf0623872f9d4a2b3210b5970854b654c7Marc Blank            Log.d(Logging.LOG_TAG, "refreshMailboxList " + accountId);
217295297581c8af9b6ee0a41487b7fded08739890cMakoto Onuki        }
21821efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki        status.onRefreshRequested();
21921efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki        notifyRefreshStatusChanged(accountId, -1);
22021efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki        mController.updateMailboxList(accountId);
22121efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki        return true;
22221efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki    }
22321efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki
22421efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki    public boolean isMailboxStale(long mailboxId) {
22521efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki        return mClock.getTime() >= (mMessageListStatus.get(mailboxId).getLastRefreshTime()
22621efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki                + MAILBOX_AUTO_REFRESH_INTERVAL);
22721efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki    }
22821efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki
2296993130a7cb089da642ee689603a2f8487b3fd39Makoto Onuki    public boolean isMailboxListStale(long accountId) {
2306993130a7cb089da642ee689603a2f8487b3fd39Makoto Onuki        return mClock.getTime() >= (mMailboxListStatus.get(accountId).getLastRefreshTime()
2316993130a7cb089da642ee689603a2f8487b3fd39Makoto Onuki                + MAILBOX_LIST_AUTO_REFRESH_INTERVAL);
2326993130a7cb089da642ee689603a2f8487b3fd39Makoto Onuki    }
2336993130a7cb089da642ee689603a2f8487b3fd39Makoto Onuki
23421efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki    /**
23521efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki     * Refresh messages in a mailbox.
23621efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki     */
237cbdd9f78b2605e87e45e4f6761b0a8c444a8cd4cMarc Blank    public boolean refreshMessageList(long accountId, long mailboxId, boolean userRequest) {
238cbdd9f78b2605e87e45e4f6761b0a8c444a8cd4cMarc Blank        return refreshMessageList(accountId, mailboxId, false, userRequest);
23921efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki    }
24021efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki
24121efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki    /**
24221efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki     * "load more messages" in a mailbox.
24321efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki     */
24421efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki    public boolean loadMoreMessages(long accountId, long mailboxId) {
245a796fbab0237c094aa943de68f85d201dd22503bMakoto Onuki        return refreshMessageList(accountId, mailboxId, true, true);
24621efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki    }
24721efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki
248cbdd9f78b2605e87e45e4f6761b0a8c444a8cd4cMarc Blank    private boolean refreshMessageList(long accountId, long mailboxId, boolean loadMoreMessages,
249cbdd9f78b2605e87e45e4f6761b0a8c444a8cd4cMarc Blank            boolean userRequest) {
25021efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki        final Status status = mMessageListStatus.get(mailboxId);
25121efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki        if (!status.canRefresh()) return false;
25221efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki
253295297581c8af9b6ee0a41487b7fded08739890cMakoto Onuki        if (LOG_ENABLED) {
25431d9acbf0623872f9d4a2b3210b5970854b654c7Marc Blank            Log.d(Logging.LOG_TAG, "refreshMessageList " + accountId + ", " + mailboxId + ", "
255295297581c8af9b6ee0a41487b7fded08739890cMakoto Onuki                    + loadMoreMessages);
256295297581c8af9b6ee0a41487b7fded08739890cMakoto Onuki        }
25721efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki        status.onRefreshRequested();
25821efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki        notifyRefreshStatusChanged(accountId, mailboxId);
2590d21aa395ae21857fb5ab74db7c284eda49729d4Makoto Onuki        if (loadMoreMessages) {
2600d21aa395ae21857fb5ab74db7c284eda49729d4Makoto Onuki            mController.loadMoreMessages(mailboxId);
2610d21aa395ae21857fb5ab74db7c284eda49729d4Makoto Onuki        } else {
262cbdd9f78b2605e87e45e4f6761b0a8c444a8cd4cMarc Blank            mController.updateMailbox(accountId, mailboxId, userRequest);
2630d21aa395ae21857fb5ab74db7c284eda49729d4Makoto Onuki        }
26421efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki        return true;
26521efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki    }
26621efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki
26721efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki    /**
26821efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki     * Send pending messages.
26921efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki     */
27021efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki    public boolean sendPendingMessages(long accountId) {
271295297581c8af9b6ee0a41487b7fded08739890cMakoto Onuki        if (LOG_ENABLED) {
27231d9acbf0623872f9d4a2b3210b5970854b654c7Marc Blank            Log.d(Logging.LOG_TAG, "sendPendingMessages " + accountId);
273295297581c8af9b6ee0a41487b7fded08739890cMakoto Onuki        }
27421efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki        notifyRefreshStatusChanged(accountId, -1);
27521efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki        mController.sendPendingMessages(accountId);
27621efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki        return true;
27721efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki    }
27821efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki
27921efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki    /**
28021efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki     * Call {@link #sendPendingMessages} for all accounts.
28121efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki     */
28221efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki    public void sendPendingMessagesForAllAccounts() {
283295297581c8af9b6ee0a41487b7fded08739890cMakoto Onuki        if (LOG_ENABLED) {
28431d9acbf0623872f9d4a2b3210b5970854b654c7Marc Blank            Log.d(Logging.LOG_TAG, "sendPendingMessagesForAllAccounts");
285295297581c8af9b6ee0a41487b7fded08739890cMakoto Onuki        }
286f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki        new SendPendingMessagesForAllAccountsImpl().execute();
28721efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki    }
28821efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki
289f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki    private class SendPendingMessagesForAllAccountsImpl extends Utility.ForEachAccount {
290f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki        public SendPendingMessagesForAllAccountsImpl() {
291f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki            super(mContext);
292f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki        }
293f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki
294f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki        @Override
295f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki        protected void performAction(long accountId) {
296f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki            sendPendingMessages(accountId);
29721efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki        }
29821efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki    }
29921efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki
300e357f5879187124c7af5c2ece5d7d3e4f60f07d2Makoto Onuki    public long getLastMailboxListRefreshTime(long accountId) {
301e357f5879187124c7af5c2ece5d7d3e4f60f07d2Makoto Onuki        return mMailboxListStatus.get(accountId).getLastRefreshTime();
302e357f5879187124c7af5c2ece5d7d3e4f60f07d2Makoto Onuki    }
303e357f5879187124c7af5c2ece5d7d3e4f60f07d2Makoto Onuki
304e357f5879187124c7af5c2ece5d7d3e4f60f07d2Makoto Onuki    public long getLastMessageListRefreshTime(long mailboxId) {
305e357f5879187124c7af5c2ece5d7d3e4f60f07d2Makoto Onuki        return mMessageListStatus.get(mailboxId).getLastRefreshTime();
306e357f5879187124c7af5c2ece5d7d3e4f60f07d2Makoto Onuki    }
307e357f5879187124c7af5c2ece5d7d3e4f60f07d2Makoto Onuki
30821efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki    public boolean isMailboxListRefreshing(long accountId) {
30921efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki        return mMailboxListStatus.get(accountId).isRefreshing();
31021efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki    }
31121efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki
31221efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki    public boolean isMessageListRefreshing(long mailboxId) {
31321efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki        return mMessageListStatus.get(mailboxId).isRefreshing();
31421efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki    }
31521efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki
316c1e8c95d053464e0821d45347704bd6c27e7e546Andy Stadler    public boolean isRefreshingAnyMailboxListForTest() {
31721efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki        return mMailboxListStatus.isRefreshingAny();
31821efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki    }
31921efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki
320c1e8c95d053464e0821d45347704bd6c27e7e546Andy Stadler    public boolean isRefreshingAnyMessageListForTest() {
32121efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki        return mMessageListStatus.isRefreshingAny();
32221efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki    }
32321efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki
32421efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki    public String getErrorMessage() {
32521efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki        return mErrorMessage;
32621efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki    }
32721efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki
32821efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki    private void notifyRefreshStatusChanged(long accountId, long mailboxId) {
32921efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki        for (Listener l : mListeners) {
33021efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki            l.onRefreshStatusChanged(accountId, mailboxId);
33121efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki        }
33221efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki    }
33321efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki
33421efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki    private void reportError(long accountId, long mailboxId, String errorMessage) {
33521efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki        mErrorMessage = errorMessage;
33621efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki        for (Listener l : mListeners) {
33721efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki            l.onMessagingError(accountId, mailboxId, mErrorMessage);
33821efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki        }
33921efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki    }
34021efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki
34121efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki    /* package */ Collection<Listener> getListenersForTest() {
34221efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki        return mListeners;
34321efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki    }
34421efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki
34521efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki    /* package */ Status getMailboxListStatusForTest(long accountId) {
34621efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki        return mMailboxListStatus.get(accountId);
34721efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki    }
34821efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki
34921efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki    /* package */ Status getMessageListStatusForTest(long mailboxId) {
35021efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki        return mMessageListStatus.get(mailboxId);
35121efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki    }
35221efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki
35321efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki    private class ControllerResult extends Controller.Result {
35421efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki        private boolean mSendMailExceptionReported = false;
35521efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki
35621efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki        private String exceptionToString(MessagingException exception) {
35721efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki            if (exception == null) {
35821efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki                return "(no exception)";
35921efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki            } else {
36031d9acbf0623872f9d4a2b3210b5970854b654c7Marc Blank                return MessagingExceptionStrings.getErrorString(mContext, exception);
36121efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki            }
36221efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki        }
36321efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki
36421efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki        /**
36521efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki         * Callback for mailbox list refresh.
36621efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki         */
36721efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki        @Override
36821efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki        public void updateMailboxListCallback(MessagingException exception, long accountId,
36921efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki                int progress) {
370295297581c8af9b6ee0a41487b7fded08739890cMakoto Onuki            if (LOG_ENABLED) {
37131d9acbf0623872f9d4a2b3210b5970854b654c7Marc Blank                Log.d(Logging.LOG_TAG, "updateMailboxListCallback " + accountId + ", " + progress
37221efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki                        + ", " + exceptionToString(exception));
37321efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki            }
37421efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki            mMailboxListStatus.get(accountId).onCallback(exception, progress, mClock);
37521efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki            if (exception != null) {
37631d9acbf0623872f9d4a2b3210b5970854b654c7Marc Blank                reportError(accountId, -1,
37731d9acbf0623872f9d4a2b3210b5970854b654c7Marc Blank                        MessagingExceptionStrings.getErrorString(mContext, exception));
37821efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki            }
37921efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki            notifyRefreshStatusChanged(accountId, -1);
38021efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki        }
38121efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki
38221efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki        /**
38321efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki         * Callback for explicit (user-driven) mailbox refresh.
38421efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki         */
38521efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki        @Override
38621efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki        public void updateMailboxCallback(MessagingException exception, long accountId,
387c4cdb11d24c19428dd39f986b00c1a29e75e1505Todd Kennedy                long mailboxId, int progress, int dontUseNumNewMessages,
388c4cdb11d24c19428dd39f986b00c1a29e75e1505Todd Kennedy                ArrayList<Long> addedMessages) {
389295297581c8af9b6ee0a41487b7fded08739890cMakoto Onuki            if (LOG_ENABLED) {
39031d9acbf0623872f9d4a2b3210b5970854b654c7Marc Blank                Log.d(Logging.LOG_TAG, "updateMailboxCallback " + accountId + ", "
39121efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki                        + mailboxId + ", " + progress + ", " + exceptionToString(exception));
39221efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki            }
39321efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki            updateMailboxCallbackInternal(exception, accountId, mailboxId, progress, 0);
39421efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki        }
39521efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki
39621efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki        /**
39721efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki         * Callback for implicit (timer-based) mailbox refresh.
39821efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki         *
39921efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki         * Do the same as {@link #updateMailboxCallback}.
40021efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki         * TODO: Figure out if it's really okay to do the same as updateMailboxCallback.
40121efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki         * If both the explicit refresh and the implicit refresh can run at the same time,
40221efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki         * we need to keep track of their status separately.
40321efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki         */
40421efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki        @Override
40521efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki        public void serviceCheckMailCallback(
40621efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki                MessagingException exception, long accountId, long mailboxId, int progress,
40721efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki                long tag) {
408295297581c8af9b6ee0a41487b7fded08739890cMakoto Onuki            if (LOG_ENABLED) {
40931d9acbf0623872f9d4a2b3210b5970854b654c7Marc Blank                Log.d(Logging.LOG_TAG, "serviceCheckMailCallback " + accountId + ", "
41021efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki                        + mailboxId + ", " + progress + ", " + exceptionToString(exception));
41121efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki            }
41221efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki            updateMailboxCallbackInternal(exception, accountId, mailboxId, progress, 0);
41321efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki        }
41421efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki
41521efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki        private void updateMailboxCallbackInternal(MessagingException exception, long accountId,
41621efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki                long mailboxId, int progress, int dontUseNumNewMessages) {
41721efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki            // Don't use dontUseNumNewMessages.  serviceCheckMailCallback() don't set it.
41821efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki            mMessageListStatus.get(mailboxId).onCallback(exception, progress, mClock);
41921efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki            if (exception != null) {
42031d9acbf0623872f9d4a2b3210b5970854b654c7Marc Blank                reportError(accountId, mailboxId,
42131d9acbf0623872f9d4a2b3210b5970854b654c7Marc Blank                        MessagingExceptionStrings.getErrorString(mContext, exception));
42221efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki            }
42321efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki            notifyRefreshStatusChanged(accountId, mailboxId);
42421efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki        }
42521efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki
42621efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki
42721efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki        /**
42821efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki         * Send message progress callback.
42921efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki         *
430be1aa37dc516a9c3dd4af65b11f92a5951f5c5c3Makoto Onuki         * We don't keep track of the status of outboxes, but we monitor this to catch
431be1aa37dc516a9c3dd4af65b11f92a5951f5c5c3Makoto Onuki         * errors.
43221efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki         */
43321efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki        @Override
43421efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki        public void sendMailCallback(MessagingException exception, long accountId, long messageId,
43521efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki                int progress) {
436295297581c8af9b6ee0a41487b7fded08739890cMakoto Onuki            if (LOG_ENABLED) {
43731d9acbf0623872f9d4a2b3210b5970854b654c7Marc Blank                Log.d(Logging.LOG_TAG, "sendMailCallback " + accountId + ", "
43821efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki                        + messageId + ", " + progress + ", " + exceptionToString(exception));
43921efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki            }
44021efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki            if (progress == 0 && messageId == -1) {
44121efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki                mSendMailExceptionReported = false;
44221efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki            }
44321efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki            if (exception != null && !mSendMailExceptionReported) {
44421efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki                // Only the first error in a batch will be reported.
44521efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki                mSendMailExceptionReported = true;
44631d9acbf0623872f9d4a2b3210b5970854b654c7Marc Blank                reportError(accountId, messageId,
44731d9acbf0623872f9d4a2b3210b5970854b654c7Marc Blank                        MessagingExceptionStrings.getErrorString(mContext, exception));
44821efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki            }
449be1aa37dc516a9c3dd4af65b11f92a5951f5c5c3Makoto Onuki            if (progress == 100) {
450be1aa37dc516a9c3dd4af65b11f92a5951f5c5c3Makoto Onuki                mSendMailExceptionReported = false;
451be1aa37dc516a9c3dd4af65b11f92a5951f5c5c3Makoto Onuki            }
45221efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki        }
45321efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki    }
45421efedb67fdfff208cef3a18804771fd1d1fff30Makoto Onuki}
455