1bcec088320fc56cd5b5e1bbc706e4b7e304b95bfAndrew Stadler/*
2bcec088320fc56cd5b5e1bbc706e4b7e304b95bfAndrew Stadler * Copyright (C) 2009 The Android Open Source Project
3bcec088320fc56cd5b5e1bbc706e4b7e304b95bfAndrew Stadler *
4bcec088320fc56cd5b5e1bbc706e4b7e304b95bfAndrew Stadler * Licensed under the Apache License, Version 2.0 (the "License");
5bcec088320fc56cd5b5e1bbc706e4b7e304b95bfAndrew Stadler * you may not use this file except in compliance with the License.
6bcec088320fc56cd5b5e1bbc706e4b7e304b95bfAndrew Stadler * You may obtain a copy of the License at
7bcec088320fc56cd5b5e1bbc706e4b7e304b95bfAndrew Stadler *
8bcec088320fc56cd5b5e1bbc706e4b7e304b95bfAndrew Stadler *      http://www.apache.org/licenses/LICENSE-2.0
9bcec088320fc56cd5b5e1bbc706e4b7e304b95bfAndrew Stadler *
10bcec088320fc56cd5b5e1bbc706e4b7e304b95bfAndrew Stadler * Unless required by applicable law or agreed to in writing, software
11bcec088320fc56cd5b5e1bbc706e4b7e304b95bfAndrew Stadler * distributed under the License is distributed on an "AS IS" BASIS,
12bcec088320fc56cd5b5e1bbc706e4b7e304b95bfAndrew Stadler * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13bcec088320fc56cd5b5e1bbc706e4b7e304b95bfAndrew Stadler * See the License for the specific language governing permissions and
14bcec088320fc56cd5b5e1bbc706e4b7e304b95bfAndrew Stadler * limitations under the License.
15bcec088320fc56cd5b5e1bbc706e4b7e304b95bfAndrew Stadler */
16bcec088320fc56cd5b5e1bbc706e4b7e304b95bfAndrew Stadler
17bcec088320fc56cd5b5e1bbc706e4b7e304b95bfAndrew Stadlerpackage com.android.email;
18bcec088320fc56cd5b5e1bbc706e4b7e304b95bfAndrew Stadler
1985e4c101b014857fe40f87c3837b82564cfc5b6cMarc Blankimport android.app.Service;
2085e4c101b014857fe40f87c3837b82564cfc5b6cMarc Blankimport android.content.ContentResolver;
2185e4c101b014857fe40f87c3837b82564cfc5b6cMarc Blankimport android.content.ContentUris;
2285e4c101b014857fe40f87c3837b82564cfc5b6cMarc Blankimport android.content.ContentValues;
2385e4c101b014857fe40f87c3837b82564cfc5b6cMarc Blankimport android.content.Context;
2485e4c101b014857fe40f87c3837b82564cfc5b6cMarc Blankimport android.content.Intent;
2585e4c101b014857fe40f87c3837b82564cfc5b6cMarc Blankimport android.database.Cursor;
2685e4c101b014857fe40f87c3837b82564cfc5b6cMarc Blankimport android.net.Uri;
2785e4c101b014857fe40f87c3837b82564cfc5b6cMarc Blankimport android.os.Bundle;
2885e4c101b014857fe40f87c3837b82564cfc5b6cMarc Blankimport android.os.IBinder;
2985e4c101b014857fe40f87c3837b82564cfc5b6cMarc Blankimport android.os.RemoteCallbackList;
3085e4c101b014857fe40f87c3837b82564cfc5b6cMarc Blankimport android.os.RemoteException;
3185e4c101b014857fe40f87c3837b82564cfc5b6cMarc Blankimport android.util.Log;
3285e4c101b014857fe40f87c3837b82564cfc5b6cMarc Blank
33391ae25c43a38829cc0990af18c36bdc7cc374cfMarc Blankimport com.android.email.mail.store.Pop3Store.Pop3Message;
340993190cafebc107bd27a26996b5d63d4a4ede10Marc Blankimport com.android.email.provider.AccountBackupRestore;
3566a47b8dac5e97e37c30b928bc5a227d74baada9Marc Blankimport com.android.email.service.EmailServiceUtils;
364e4aba9ebc43c6a83190f3a883fa05bb7d5100b3Marc Blankimport com.android.email.service.MailService;
370d4fc55861ed4393aa82f124f2865695ef564641Marc Blankimport com.android.emailcommon.Api;
3831d9acbf0623872f9d4a2b3210b5970854b654c7Marc Blankimport com.android.emailcommon.Logging;
392193962ca2b3157e79f731736afa2a0c972e778aMarc Blankimport com.android.emailcommon.mail.AuthenticationFailedException;
402193962ca2b3157e79f731736afa2a0c972e778aMarc Blankimport com.android.emailcommon.mail.Folder.MessageRetrievalListener;
413a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blankimport com.android.emailcommon.mail.MessagingException;
42f5418f1f93b02e7fab9f15eb201800b65510998eMarc Blankimport com.android.emailcommon.provider.Account;
43a7bc0319a75184ad706bb35c049af107ac3688e6Marc Blankimport com.android.emailcommon.provider.EmailContent;
44a7bc0319a75184ad706bb35c049af107ac3688e6Marc Blankimport com.android.emailcommon.provider.EmailContent.Attachment;
45a7bc0319a75184ad706bb35c049af107ac3688e6Marc Blankimport com.android.emailcommon.provider.EmailContent.Body;
46a7bc0319a75184ad706bb35c049af107ac3688e6Marc Blankimport com.android.emailcommon.provider.EmailContent.MailboxColumns;
47a7bc0319a75184ad706bb35c049af107ac3688e6Marc Blankimport com.android.emailcommon.provider.EmailContent.Message;
48a7bc0319a75184ad706bb35c049af107ac3688e6Marc Blankimport com.android.emailcommon.provider.EmailContent.MessageColumns;
4985e4c101b014857fe40f87c3837b82564cfc5b6cMarc Blankimport com.android.emailcommon.provider.HostAuth;
5053ea83ebf91f820692e8fa8e781f5cc982dd94dbBen Komaloimport com.android.emailcommon.provider.Mailbox;
510d4fc55861ed4393aa82f124f2865695ef564641Marc Blankimport com.android.emailcommon.service.EmailServiceStatus;
520d4fc55861ed4393aa82f124f2865695ef564641Marc Blankimport com.android.emailcommon.service.IEmailService;
530d4fc55861ed4393aa82f124f2865695ef564641Marc Blankimport com.android.emailcommon.service.IEmailServiceCallback;
5475a754660e33c5e18cacffff193983ba22a7b9b0Marc Blankimport com.android.emailcommon.service.SearchParams;
558a574694606f0e5d781334d0d426fc379c51f3edMarc Blankimport com.android.emailcommon.utility.AttachmentUtilities;
56e87ff6c3cbbfc5e3636f9827b58820652e3ea1c5Todd Kennedyimport com.android.emailcommon.utility.EmailAsyncTask;
5731d9acbf0623872f9d4a2b3210b5970854b654c7Marc Blankimport com.android.emailcommon.utility.Utility;
58aeee10e57ef4d931e7708fde218d590453a82aeaMarc Blankimport com.google.common.annotations.VisibleForTesting;
59bcec088320fc56cd5b5e1bbc706e4b7e304b95bfAndrew Stadler
60391ae25c43a38829cc0990af18c36bdc7cc374cfMarc Blankimport java.io.FileNotFoundException;
61391ae25c43a38829cc0990af18c36bdc7cc374cfMarc Blankimport java.io.IOException;
62391ae25c43a38829cc0990af18c36bdc7cc374cfMarc Blankimport java.io.InputStream;
63c4cdb11d24c19428dd39f986b00c1a29e75e1505Todd Kennedyimport java.util.ArrayList;
64086aac2386a575a0e2c78b1fa8dd8ea13825ae2bMakoto Onukiimport java.util.Collection;
65ed1dc9ee72410e07de24bc4a072d066981611a48Marc Blankimport java.util.HashMap;
66bcec088320fc56cd5b5e1bbc706e4b7e304b95bfAndrew Stadlerimport java.util.HashSet;
67768aff4c88a13c1663b1c33b1ab03637e5ce375bMakoto Onukiimport java.util.concurrent.ConcurrentHashMap;
68bcec088320fc56cd5b5e1bbc706e4b7e304b95bfAndrew Stadler
69bcec088320fc56cd5b5e1bbc706e4b7e304b95bfAndrew Stadler/**
70bcec088320fc56cd5b5e1bbc706e4b7e304b95bfAndrew Stadler * New central controller/dispatcher for Email activities that may require remote operations.
71bcec088320fc56cd5b5e1bbc706e4b7e304b95bfAndrew Stadler * Handles disambiguating between legacy MessagingController operations and newer provider/sync
727894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank * based code.  We implement Service to allow loadAttachment calls to be sent in a consistent manner
737894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank * to IMAP, POP3, and EAS by AttachmentDownloadService
74bcec088320fc56cd5b5e1bbc706e4b7e304b95bfAndrew Stadler */
7545f530ba5553dcbe3e548930945c40e13736deb3Makoto Onukipublic class Controller {
767894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank    private static final String TAG = "Controller";
77b3f7dd0169a35221184b9327c8ce337b09dc6d1fMakoto Onuki    private static Controller sInstance;
78c184f36c2df16431693d7709e28ded593efc3da7Marc Blank    private final Context mContext;
797c3cca80a0b4dacb7bcb48c65a9999138b5df82bAndrew Stadler    private Context mProviderContext;
80c184f36c2df16431693d7709e28ded593efc3da7Marc Blank    private final MessagingController mLegacyController;
81b3f7dd0169a35221184b9327c8ce337b09dc6d1fMakoto Onuki    private final LegacyListener mLegacyListener = new LegacyListener();
82b3f7dd0169a35221184b9327c8ce337b09dc6d1fMakoto Onuki    private final ServiceCallback mServiceCallback = new ServiceCallback();
83b3f7dd0169a35221184b9327c8ce337b09dc6d1fMakoto Onuki    private final HashSet<Result> mListeners = new HashSet<Result>();
848d8f86e899165772a7d91250b98dfc3c0d78b538Marc Blank    /*package*/ final ConcurrentHashMap<Long, Boolean> mLegacyControllerMap =
858d8f86e899165772a7d91250b98dfc3c0d78b538Marc Blank        new ConcurrentHashMap<Long, Boolean>();
867b0b463477ef7172bd4b9e19c6338634ebedd8eeAndrew Stadler
87391ae25c43a38829cc0990af18c36bdc7cc374cfMarc Blank    // Note that 0 is a syntactically valid account key; however there can never be an account
88391ae25c43a38829cc0990af18c36bdc7cc374cfMarc Blank    // with id = 0, so attempts to restore the account will return null.  Null values are
89391ae25c43a38829cc0990af18c36bdc7cc374cfMarc Blank    // handled properly within the code, so this won't cause any issues.
90c60b8d0529168edecf2376a6f421a0ae1e10fe29Marc Blank    private static final long GLOBAL_MAILBOX_ACCOUNT_KEY = 0;
91391ae25c43a38829cc0990af18c36bdc7cc374cfMarc Blank    /*package*/ static final String ATTACHMENT_MAILBOX_SERVER_ID = "__attachment_mailbox__";
92391ae25c43a38829cc0990af18c36bdc7cc374cfMarc Blank    /*package*/ static final String ATTACHMENT_MESSAGE_UID_PREFIX = "__attachment_message__";
93c60b8d0529168edecf2376a6f421a0ae1e10fe29Marc Blank    /*package*/ static final String SEARCH_MAILBOX_SERVER_ID = "__search_mailbox__";
94391ae25c43a38829cc0990af18c36bdc7cc374cfMarc Blank    private static final String WHERE_TYPE_ATTACHMENT =
95391ae25c43a38829cc0990af18c36bdc7cc374cfMarc Blank        MailboxColumns.TYPE + "=" + Mailbox.TYPE_ATTACHMENT;
96391ae25c43a38829cc0990af18c36bdc7cc374cfMarc Blank    private static final String WHERE_MAILBOX_KEY = MessageColumns.MAILBOX_KEY + "=?";
97391ae25c43a38829cc0990af18c36bdc7cc374cfMarc Blank
987894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank    private static final String[] MESSAGEID_TO_ACCOUNTID_PROJECTION = new String[] {
997c3cca80a0b4dacb7bcb48c65a9999138b5df82bAndrew Stadler        EmailContent.RECORD_ID,
1007c3cca80a0b4dacb7bcb48c65a9999138b5df82bAndrew Stadler        EmailContent.MessageColumns.ACCOUNT_KEY
1017c3cca80a0b4dacb7bcb48c65a9999138b5df82bAndrew Stadler    };
1027894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank    private static final int MESSAGEID_TO_ACCOUNTID_COLUMN_ACCOUNTID = 1;
1037894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank
1047894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank    private static final String[] BODY_SOURCE_KEY_PROJECTION =
1057894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank        new String[] {Body.SOURCE_MESSAGE_KEY};
1067894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank    private static final int BODY_SOURCE_KEY_COLUMN = 0;
1077894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank    private static final String WHERE_MESSAGE_KEY = Body.MESSAGE_KEY + "=?";
1087c3cca80a0b4dacb7bcb48c65a9999138b5df82bAndrew Stadler
109e29189e3eeea9c629777b3deed6ea2be67caa737Andy Stadler    private static final String MAILBOXES_FOR_ACCOUNT_SELECTION = MailboxColumns.ACCOUNT_KEY + "=?";
11069af769688acc28a20cdbad046cea2565456801cMarc Blank    private static final String MAILBOXES_FOR_ACCOUNT_EXCEPT_ACCOUNT_MAILBOX_SELECTION =
11169af769688acc28a20cdbad046cea2565456801cMarc Blank        MAILBOXES_FOR_ACCOUNT_SELECTION + " AND " + MailboxColumns.TYPE + "!=" +
11269af769688acc28a20cdbad046cea2565456801cMarc Blank        Mailbox.TYPE_EAS_ACCOUNT_MAILBOX;
113d3a9ab946caef4cdc8ef81f7fd8f445fdba3782fAndy Stadler    private static final String MESSAGES_FOR_ACCOUNT_SELECTION = MessageColumns.ACCOUNT_KEY + "=?";
1147894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank
1157894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank    // Service callbacks as set up via setCallback
1167894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank    private static RemoteCallbackList<IEmailServiceCallback> sCallbackList =
1177894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank        new RemoteCallbackList<IEmailServiceCallback>();
118f9ab857a5599faac2896394180fcd4ed56b09941Andrew Stadler
1191ecfb5311b0315edf86a450398a8712dbfd86772Ben Komalo    private volatile boolean mInUnitTests = false;
1201ecfb5311b0315edf86a450398a8712dbfd86772Ben Komalo
121c184f36c2df16431693d7709e28ded593efc3da7Marc Blank    protected Controller(Context _context) {
122c184f36c2df16431693d7709e28ded593efc3da7Marc Blank        mContext = _context.getApplicationContext();
123c184f36c2df16431693d7709e28ded593efc3da7Marc Blank        mProviderContext = _context;
1247894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank        mLegacyController = MessagingController.getInstance(mProviderContext, this);
12546d7d7f1b6387d144c3f9e7c987418dc8f55fad4Andrew Stadler        mLegacyController.addListener(mLegacyListener);
126bcec088320fc56cd5b5e1bbc706e4b7e304b95bfAndrew Stadler    }
127bcec088320fc56cd5b5e1bbc706e4b7e304b95bfAndrew Stadler
128bcec088320fc56cd5b5e1bbc706e4b7e304b95bfAndrew Stadler    /**
1291ecfb5311b0315edf86a450398a8712dbfd86772Ben Komalo     * Mark this controller as being in use in a unit test.
1301ecfb5311b0315edf86a450398a8712dbfd86772Ben Komalo     * This is a kludge vs having proper mocks and dependency injection; since the Controller is a
1311ecfb5311b0315edf86a450398a8712dbfd86772Ben Komalo     * global singleton there isn't much else we can do.
1321ecfb5311b0315edf86a450398a8712dbfd86772Ben Komalo     */
133cb1d65c47825dc7a45c7954cd93669296b35305eBen Komalo    public void markForTest(boolean inUnitTests) {
1341ecfb5311b0315edf86a450398a8712dbfd86772Ben Komalo        mInUnitTests = inUnitTests;
1351ecfb5311b0315edf86a450398a8712dbfd86772Ben Komalo    }
1361ecfb5311b0315edf86a450398a8712dbfd86772Ben Komalo
1371ecfb5311b0315edf86a450398a8712dbfd86772Ben Komalo    /**
1384be3bc1ec8a52308ee1998f0a31d7e491e06b3c1Makoto Onuki     * Cleanup for test.  Mustn't be called for the regular {@link Controller}, as it's a
1394be3bc1ec8a52308ee1998f0a31d7e491e06b3c1Makoto Onuki     * singleton and lives till the process finishes.
1404be3bc1ec8a52308ee1998f0a31d7e491e06b3c1Makoto Onuki     *
1414be3bc1ec8a52308ee1998f0a31d7e491e06b3c1Makoto Onuki     * <p>However, this method MUST be called for mock instances.
1424be3bc1ec8a52308ee1998f0a31d7e491e06b3c1Makoto Onuki     */
1434be3bc1ec8a52308ee1998f0a31d7e491e06b3c1Makoto Onuki    public void cleanupForTest() {
1444be3bc1ec8a52308ee1998f0a31d7e491e06b3c1Makoto Onuki        mLegacyController.removeListener(mLegacyListener);
1454be3bc1ec8a52308ee1998f0a31d7e491e06b3c1Makoto Onuki    }
1464be3bc1ec8a52308ee1998f0a31d7e491e06b3c1Makoto Onuki
1474be3bc1ec8a52308ee1998f0a31d7e491e06b3c1Makoto Onuki    /**
148c184f36c2df16431693d7709e28ded593efc3da7Marc Blank     * Gets or creates the singleton instance of Controller.
149c184f36c2df16431693d7709e28ded593efc3da7Marc Blank     */
150c184f36c2df16431693d7709e28ded593efc3da7Marc Blank    public synchronized static Controller getInstance(Context _context) {
151c184f36c2df16431693d7709e28ded593efc3da7Marc Blank        if (sInstance == null) {
152c184f36c2df16431693d7709e28ded593efc3da7Marc Blank            sInstance = new Controller(_context);
153c184f36c2df16431693d7709e28ded593efc3da7Marc Blank        }
154c184f36c2df16431693d7709e28ded593efc3da7Marc Blank        return sInstance;
155c184f36c2df16431693d7709e28ded593efc3da7Marc Blank    }
156c184f36c2df16431693d7709e28ded593efc3da7Marc Blank
157c184f36c2df16431693d7709e28ded593efc3da7Marc Blank    /**
158ec15f2356e47d621584cc3fc84c9c02557e0a0dfMakoto Onuki     * Inject a mock controller.  Used only for testing.  Affects future calls to getInstance().
159ec15f2356e47d621584cc3fc84c9c02557e0a0dfMakoto Onuki     *
160ec15f2356e47d621584cc3fc84c9c02557e0a0dfMakoto Onuki     * Tests that use this method MUST clean it up by calling this method again with null.
161ec15f2356e47d621584cc3fc84c9c02557e0a0dfMakoto Onuki     */
162ec15f2356e47d621584cc3fc84c9c02557e0a0dfMakoto Onuki    public synchronized static void injectMockControllerForTest(Controller mockController) {
163ec15f2356e47d621584cc3fc84c9c02557e0a0dfMakoto Onuki        sInstance = mockController;
164ec15f2356e47d621584cc3fc84c9c02557e0a0dfMakoto Onuki    }
165ec15f2356e47d621584cc3fc84c9c02557e0a0dfMakoto Onuki
166ec15f2356e47d621584cc3fc84c9c02557e0a0dfMakoto Onuki    /**
1677c3cca80a0b4dacb7bcb48c65a9999138b5df82bAndrew Stadler     * For testing only:  Inject a different context for provider access.  This will be
1687c3cca80a0b4dacb7bcb48c65a9999138b5df82bAndrew Stadler     * used internally for access the underlying provider (e.g. getContentResolver().query()).
1697c3cca80a0b4dacb7bcb48c65a9999138b5df82bAndrew Stadler     * @param providerContext the provider context to be used by this instance
1707c3cca80a0b4dacb7bcb48c65a9999138b5df82bAndrew Stadler     */
1717c3cca80a0b4dacb7bcb48c65a9999138b5df82bAndrew Stadler    public void setProviderContext(Context providerContext) {
1727c3cca80a0b4dacb7bcb48c65a9999138b5df82bAndrew Stadler        mProviderContext = providerContext;
1737c3cca80a0b4dacb7bcb48c65a9999138b5df82bAndrew Stadler    }
1747c3cca80a0b4dacb7bcb48c65a9999138b5df82bAndrew Stadler
1757c3cca80a0b4dacb7bcb48c65a9999138b5df82bAndrew Stadler    /**
176bcec088320fc56cd5b5e1bbc706e4b7e304b95bfAndrew Stadler     * Any UI code that wishes for callback results (on async ops) should register their callback
177bcec088320fc56cd5b5e1bbc706e4b7e304b95bfAndrew Stadler     * here (typically from onResume()).  Unregistered callbacks will never be called, to prevent
178bcec088320fc56cd5b5e1bbc706e4b7e304b95bfAndrew Stadler     * problems when the command completes and the activity has already paused or finished.
179bcec088320fc56cd5b5e1bbc706e4b7e304b95bfAndrew Stadler     * @param listener The callback that may be used in action methods
180bcec088320fc56cd5b5e1bbc706e4b7e304b95bfAndrew Stadler     */
181bcec088320fc56cd5b5e1bbc706e4b7e304b95bfAndrew Stadler    public void addResultCallback(Result listener) {
182bcec088320fc56cd5b5e1bbc706e4b7e304b95bfAndrew Stadler        synchronized (mListeners) {
183e069246d48bf5f90dfe20dacd709d495173ebb47Makoto Onuki            listener.setRegistered(true);
184bcec088320fc56cd5b5e1bbc706e4b7e304b95bfAndrew Stadler            mListeners.add(listener);
185bcec088320fc56cd5b5e1bbc706e4b7e304b95bfAndrew Stadler        }
186bcec088320fc56cd5b5e1bbc706e4b7e304b95bfAndrew Stadler    }
187bcec088320fc56cd5b5e1bbc706e4b7e304b95bfAndrew Stadler
188bcec088320fc56cd5b5e1bbc706e4b7e304b95bfAndrew Stadler    /**
189bcec088320fc56cd5b5e1bbc706e4b7e304b95bfAndrew Stadler     * Any UI code that no longer wishes for callback results (on async ops) should unregister
190bcec088320fc56cd5b5e1bbc706e4b7e304b95bfAndrew Stadler     * their callback here (typically from onPause()).  Unregistered callbacks will never be called,
191bcec088320fc56cd5b5e1bbc706e4b7e304b95bfAndrew Stadler     * to prevent problems when the command completes and the activity has already paused or
192bcec088320fc56cd5b5e1bbc706e4b7e304b95bfAndrew Stadler     * finished.
193bcec088320fc56cd5b5e1bbc706e4b7e304b95bfAndrew Stadler     * @param listener The callback that may no longer be used
194bcec088320fc56cd5b5e1bbc706e4b7e304b95bfAndrew Stadler     */
195bcec088320fc56cd5b5e1bbc706e4b7e304b95bfAndrew Stadler    public void removeResultCallback(Result listener) {
196bcec088320fc56cd5b5e1bbc706e4b7e304b95bfAndrew Stadler        synchronized (mListeners) {
197e069246d48bf5f90dfe20dacd709d495173ebb47Makoto Onuki            listener.setRegistered(false);
198bcec088320fc56cd5b5e1bbc706e4b7e304b95bfAndrew Stadler            mListeners.remove(listener);
199bcec088320fc56cd5b5e1bbc706e4b7e304b95bfAndrew Stadler        }
200bcec088320fc56cd5b5e1bbc706e4b7e304b95bfAndrew Stadler    }
2017b0b463477ef7172bd4b9e19c6338634ebedd8eeAndrew Stadler
202086aac2386a575a0e2c78b1fa8dd8ea13825ae2bMakoto Onuki    public Collection<Result> getResultCallbacksForTest() {
203086aac2386a575a0e2c78b1fa8dd8ea13825ae2bMakoto Onuki        return mListeners;
204086aac2386a575a0e2c78b1fa8dd8ea13825ae2bMakoto Onuki    }
205086aac2386a575a0e2c78b1fa8dd8ea13825ae2bMakoto Onuki
206bcec088320fc56cd5b5e1bbc706e4b7e304b95bfAndrew Stadler    /**
207391ae25c43a38829cc0990af18c36bdc7cc374cfMarc Blank     * Delete all Messages that live in the attachment mailbox
208391ae25c43a38829cc0990af18c36bdc7cc374cfMarc Blank     */
209391ae25c43a38829cc0990af18c36bdc7cc374cfMarc Blank    public void deleteAttachmentMessages() {
210391ae25c43a38829cc0990af18c36bdc7cc374cfMarc Blank        // Note: There should only be one attachment mailbox at present
211391ae25c43a38829cc0990af18c36bdc7cc374cfMarc Blank        ContentResolver resolver = mProviderContext.getContentResolver();
212391ae25c43a38829cc0990af18c36bdc7cc374cfMarc Blank        Cursor c = null;
213391ae25c43a38829cc0990af18c36bdc7cc374cfMarc Blank        try {
214391ae25c43a38829cc0990af18c36bdc7cc374cfMarc Blank            c = resolver.query(Mailbox.CONTENT_URI, EmailContent.ID_PROJECTION,
215391ae25c43a38829cc0990af18c36bdc7cc374cfMarc Blank                    WHERE_TYPE_ATTACHMENT, null, null);
216391ae25c43a38829cc0990af18c36bdc7cc374cfMarc Blank            while (c.moveToNext()) {
217391ae25c43a38829cc0990af18c36bdc7cc374cfMarc Blank                long mailboxId = c.getLong(EmailContent.ID_PROJECTION_COLUMN);
218391ae25c43a38829cc0990af18c36bdc7cc374cfMarc Blank                // Must delete attachments BEFORE messages
2198a574694606f0e5d781334d0d426fc379c51f3edMarc Blank                AttachmentUtilities.deleteAllMailboxAttachmentFiles(mProviderContext, 0,
2208a574694606f0e5d781334d0d426fc379c51f3edMarc Blank                        mailboxId);
221391ae25c43a38829cc0990af18c36bdc7cc374cfMarc Blank                resolver.delete(Message.CONTENT_URI, WHERE_MAILBOX_KEY,
222391ae25c43a38829cc0990af18c36bdc7cc374cfMarc Blank                        new String[] {Long.toString(mailboxId)});
223391ae25c43a38829cc0990af18c36bdc7cc374cfMarc Blank           }
224391ae25c43a38829cc0990af18c36bdc7cc374cfMarc Blank        } finally {
225391ae25c43a38829cc0990af18c36bdc7cc374cfMarc Blank            if (c != null) {
226391ae25c43a38829cc0990af18c36bdc7cc374cfMarc Blank                c.close();
227391ae25c43a38829cc0990af18c36bdc7cc374cfMarc Blank            }
228391ae25c43a38829cc0990af18c36bdc7cc374cfMarc Blank        }
229391ae25c43a38829cc0990af18c36bdc7cc374cfMarc Blank    }
230391ae25c43a38829cc0990af18c36bdc7cc374cfMarc Blank
231391ae25c43a38829cc0990af18c36bdc7cc374cfMarc Blank    /**
232c60b8d0529168edecf2376a6f421a0ae1e10fe29Marc Blank     * Get a mailbox based on a sqlite WHERE clause
233391ae25c43a38829cc0990af18c36bdc7cc374cfMarc Blank     */
234c60b8d0529168edecf2376a6f421a0ae1e10fe29Marc Blank    private Mailbox getGlobalMailboxWhere(String where) {
235391ae25c43a38829cc0990af18c36bdc7cc374cfMarc Blank        Cursor c = mProviderContext.getContentResolver().query(Mailbox.CONTENT_URI,
236c60b8d0529168edecf2376a6f421a0ae1e10fe29Marc Blank                Mailbox.CONTENT_PROJECTION, where, null, null);
237391ae25c43a38829cc0990af18c36bdc7cc374cfMarc Blank        try {
238391ae25c43a38829cc0990af18c36bdc7cc374cfMarc Blank            if (c.moveToFirst()) {
2395e39f90e9d4665713eba38586b08546f1d581adbTodd Kennedy                Mailbox m = new Mailbox();
2405e39f90e9d4665713eba38586b08546f1d581adbTodd Kennedy                m.restore(c);
2415e39f90e9d4665713eba38586b08546f1d581adbTodd Kennedy                return m;
242391ae25c43a38829cc0990af18c36bdc7cc374cfMarc Blank            }
243391ae25c43a38829cc0990af18c36bdc7cc374cfMarc Blank        } finally {
244391ae25c43a38829cc0990af18c36bdc7cc374cfMarc Blank            c.close();
245391ae25c43a38829cc0990af18c36bdc7cc374cfMarc Blank        }
246c60b8d0529168edecf2376a6f421a0ae1e10fe29Marc Blank        return null;
247c60b8d0529168edecf2376a6f421a0ae1e10fe29Marc Blank    }
248c60b8d0529168edecf2376a6f421a0ae1e10fe29Marc Blank
249c60b8d0529168edecf2376a6f421a0ae1e10fe29Marc Blank    /**
250c60b8d0529168edecf2376a6f421a0ae1e10fe29Marc Blank     * Returns the attachment mailbox (where we store eml attachment Emails), creating one
251c60b8d0529168edecf2376a6f421a0ae1e10fe29Marc Blank     * if necessary
252c60b8d0529168edecf2376a6f421a0ae1e10fe29Marc Blank     * @return the global attachment mailbox
253c60b8d0529168edecf2376a6f421a0ae1e10fe29Marc Blank     */
254c60b8d0529168edecf2376a6f421a0ae1e10fe29Marc Blank    public Mailbox getAttachmentMailbox() {
255c60b8d0529168edecf2376a6f421a0ae1e10fe29Marc Blank        Mailbox m = getGlobalMailboxWhere(WHERE_TYPE_ATTACHMENT);
256c60b8d0529168edecf2376a6f421a0ae1e10fe29Marc Blank        if (m == null) {
257c60b8d0529168edecf2376a6f421a0ae1e10fe29Marc Blank            m = new Mailbox();
258c60b8d0529168edecf2376a6f421a0ae1e10fe29Marc Blank            m.mAccountKey = GLOBAL_MAILBOX_ACCOUNT_KEY;
259c60b8d0529168edecf2376a6f421a0ae1e10fe29Marc Blank            m.mServerId = ATTACHMENT_MAILBOX_SERVER_ID;
260c60b8d0529168edecf2376a6f421a0ae1e10fe29Marc Blank            m.mFlagVisible = false;
261c60b8d0529168edecf2376a6f421a0ae1e10fe29Marc Blank            m.mDisplayName = ATTACHMENT_MAILBOX_SERVER_ID;
262c60b8d0529168edecf2376a6f421a0ae1e10fe29Marc Blank            m.mSyncInterval = Mailbox.CHECK_INTERVAL_NEVER;
263c60b8d0529168edecf2376a6f421a0ae1e10fe29Marc Blank            m.mType = Mailbox.TYPE_ATTACHMENT;
264c60b8d0529168edecf2376a6f421a0ae1e10fe29Marc Blank            m.save(mProviderContext);
265c60b8d0529168edecf2376a6f421a0ae1e10fe29Marc Blank        }
266c60b8d0529168edecf2376a6f421a0ae1e10fe29Marc Blank        return m;
267c60b8d0529168edecf2376a6f421a0ae1e10fe29Marc Blank    }
268c60b8d0529168edecf2376a6f421a0ae1e10fe29Marc Blank
269c60b8d0529168edecf2376a6f421a0ae1e10fe29Marc Blank    /**
270c60b8d0529168edecf2376a6f421a0ae1e10fe29Marc Blank     * Returns the search mailbox for the specified account, creating one if necessary
271c60b8d0529168edecf2376a6f421a0ae1e10fe29Marc Blank     * @return the search mailbox for the passed in account
272c60b8d0529168edecf2376a6f421a0ae1e10fe29Marc Blank     */
273c60b8d0529168edecf2376a6f421a0ae1e10fe29Marc Blank    public Mailbox getSearchMailbox(long accountId) {
274c60b8d0529168edecf2376a6f421a0ae1e10fe29Marc Blank        Mailbox m = Mailbox.restoreMailboxOfType(mContext, accountId, Mailbox.TYPE_SEARCH);
275c60b8d0529168edecf2376a6f421a0ae1e10fe29Marc Blank        if (m == null) {
276c60b8d0529168edecf2376a6f421a0ae1e10fe29Marc Blank            m = new Mailbox();
277c60b8d0529168edecf2376a6f421a0ae1e10fe29Marc Blank            m.mAccountKey = accountId;
278c60b8d0529168edecf2376a6f421a0ae1e10fe29Marc Blank            m.mServerId = SEARCH_MAILBOX_SERVER_ID;
27929c89ad17a0983ddc4eb2b4922c93a53aff2d735Ben Komalo            m.mFlagVisible = false;
280c60b8d0529168edecf2376a6f421a0ae1e10fe29Marc Blank            m.mDisplayName = SEARCH_MAILBOX_SERVER_ID;
281c60b8d0529168edecf2376a6f421a0ae1e10fe29Marc Blank            m.mSyncInterval = Mailbox.CHECK_INTERVAL_NEVER;
282c60b8d0529168edecf2376a6f421a0ae1e10fe29Marc Blank            m.mType = Mailbox.TYPE_SEARCH;
283c60b8d0529168edecf2376a6f421a0ae1e10fe29Marc Blank            m.mFlags = Mailbox.FLAG_HOLDS_MAIL;
284f62fd3fd177a79579cde30e43a85eb9709eb348bBen Komalo            m.mParentKey = Mailbox.NO_MAILBOX;
285c60b8d0529168edecf2376a6f421a0ae1e10fe29Marc Blank            m.save(mProviderContext);
286c60b8d0529168edecf2376a6f421a0ae1e10fe29Marc Blank        }
287391ae25c43a38829cc0990af18c36bdc7cc374cfMarc Blank        return m;
288391ae25c43a38829cc0990af18c36bdc7cc374cfMarc Blank    }
289391ae25c43a38829cc0990af18c36bdc7cc374cfMarc Blank
290391ae25c43a38829cc0990af18c36bdc7cc374cfMarc Blank    /**
291391ae25c43a38829cc0990af18c36bdc7cc374cfMarc Blank     * Create a Message from the Uri and store it in the attachment mailbox
292391ae25c43a38829cc0990af18c36bdc7cc374cfMarc Blank     * @param uri the uri containing message content
293391ae25c43a38829cc0990af18c36bdc7cc374cfMarc Blank     * @return the Message or null
294391ae25c43a38829cc0990af18c36bdc7cc374cfMarc Blank     */
295391ae25c43a38829cc0990af18c36bdc7cc374cfMarc Blank    public Message loadMessageFromUri(Uri uri) {
296391ae25c43a38829cc0990af18c36bdc7cc374cfMarc Blank        Mailbox mailbox = getAttachmentMailbox();
297391ae25c43a38829cc0990af18c36bdc7cc374cfMarc Blank        if (mailbox == null) return null;
298391ae25c43a38829cc0990af18c36bdc7cc374cfMarc Blank        try {
299391ae25c43a38829cc0990af18c36bdc7cc374cfMarc Blank            InputStream is = mProviderContext.getContentResolver().openInputStream(uri);
300391ae25c43a38829cc0990af18c36bdc7cc374cfMarc Blank            try {
301391ae25c43a38829cc0990af18c36bdc7cc374cfMarc Blank                // First, create a Pop3Message from the attachment and then parse it
302391ae25c43a38829cc0990af18c36bdc7cc374cfMarc Blank                Pop3Message pop3Message = new Pop3Message(
303391ae25c43a38829cc0990af18c36bdc7cc374cfMarc Blank                        ATTACHMENT_MESSAGE_UID_PREFIX + System.currentTimeMillis(), null);
304391ae25c43a38829cc0990af18c36bdc7cc374cfMarc Blank                pop3Message.parse(is);
305391ae25c43a38829cc0990af18c36bdc7cc374cfMarc Blank                // Now, pull out the header fields
306391ae25c43a38829cc0990af18c36bdc7cc374cfMarc Blank                Message msg = new Message();
307391ae25c43a38829cc0990af18c36bdc7cc374cfMarc Blank                LegacyConversions.updateMessageFields(msg, pop3Message, 0, mailbox.mId);
308391ae25c43a38829cc0990af18c36bdc7cc374cfMarc Blank                // Commit the message to the local store
309391ae25c43a38829cc0990af18c36bdc7cc374cfMarc Blank                msg.save(mProviderContext);
310391ae25c43a38829cc0990af18c36bdc7cc374cfMarc Blank                // Setup the rest of the message and mark it completely loaded
311391ae25c43a38829cc0990af18c36bdc7cc374cfMarc Blank                mLegacyController.copyOneMessageToProvider(pop3Message, msg,
312391ae25c43a38829cc0990af18c36bdc7cc374cfMarc Blank                        Message.FLAG_LOADED_COMPLETE, mProviderContext);
313391ae25c43a38829cc0990af18c36bdc7cc374cfMarc Blank                // Restore the complete message and return it
314391ae25c43a38829cc0990af18c36bdc7cc374cfMarc Blank                return Message.restoreMessageWithId(mProviderContext, msg.mId);
315391ae25c43a38829cc0990af18c36bdc7cc374cfMarc Blank            } catch (MessagingException e) {
316391ae25c43a38829cc0990af18c36bdc7cc374cfMarc Blank            } catch (IOException e) {
317391ae25c43a38829cc0990af18c36bdc7cc374cfMarc Blank            }
318391ae25c43a38829cc0990af18c36bdc7cc374cfMarc Blank        } catch (FileNotFoundException e) {
319391ae25c43a38829cc0990af18c36bdc7cc374cfMarc Blank        }
320391ae25c43a38829cc0990af18c36bdc7cc374cfMarc Blank        return null;
321391ae25c43a38829cc0990af18c36bdc7cc374cfMarc Blank    }
322391ae25c43a38829cc0990af18c36bdc7cc374cfMarc Blank
323391ae25c43a38829cc0990af18c36bdc7cc374cfMarc Blank    /**
3243a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank     * Set logging flags for external sync services
325f00dccd32125c727cc18d837b59c15c95f5d78bcMarc Blank     *
32677898e14e96feb5d107c4e69b5241169e7425058Andrew Stadler     * Generally this should be called by anybody who changes Email.DEBUG
32777898e14e96feb5d107c4e69b5241169e7425058Andrew Stadler     */
3283a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank    public void serviceLogging(int debugFlags) {
32966a47b8dac5e97e37c30b928bc5a227d74baada9Marc Blank        IEmailService service = EmailServiceUtils.getExchangeService(mContext, mServiceCallback);
33077898e14e96feb5d107c4e69b5241169e7425058Andrew Stadler        try {
3313a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank            service.setLogging(debugFlags);
33277898e14e96feb5d107c4e69b5241169e7425058Andrew Stadler        } catch (RemoteException e) {
33377898e14e96feb5d107c4e69b5241169e7425058Andrew Stadler            // TODO Change exception handling to be consistent with however this method
33477898e14e96feb5d107c4e69b5241169e7425058Andrew Stadler            // is implemented for other protocols
3353a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank            Log.d("setLogging", "RemoteException" + e);
33677898e14e96feb5d107c4e69b5241169e7425058Andrew Stadler        }
33777898e14e96feb5d107c4e69b5241169e7425058Andrew Stadler    }
33877898e14e96feb5d107c4e69b5241169e7425058Andrew Stadler
33977898e14e96feb5d107c4e69b5241169e7425058Andrew Stadler    /**
340bcec088320fc56cd5b5e1bbc706e4b7e304b95bfAndrew Stadler     * Request a remote update of mailboxes for an account.
341bcec088320fc56cd5b5e1bbc706e4b7e304b95bfAndrew Stadler     */
3423f545a4060982b8a5d715905c7818d59056c1ee0Makoto Onuki    public void updateMailboxList(final long accountId) {
343ec15f2356e47d621584cc3fc84c9c02557e0a0dfMakoto Onuki        Utility.runAsync(new Runnable() {
344ec15f2356e47d621584cc3fc84c9c02557e0a0dfMakoto Onuki            @Override
345ec15f2356e47d621584cc3fc84c9c02557e0a0dfMakoto Onuki            public void run() {
346ec15f2356e47d621584cc3fc84c9c02557e0a0dfMakoto Onuki                final IEmailService service = getServiceForAccount(accountId);
347ec15f2356e47d621584cc3fc84c9c02557e0a0dfMakoto Onuki                if (service != null) {
348ec15f2356e47d621584cc3fc84c9c02557e0a0dfMakoto Onuki                    // Service implementation
349ec15f2356e47d621584cc3fc84c9c02557e0a0dfMakoto Onuki                    try {
350ec15f2356e47d621584cc3fc84c9c02557e0a0dfMakoto Onuki                        service.updateFolderList(accountId);
351ec15f2356e47d621584cc3fc84c9c02557e0a0dfMakoto Onuki                    } catch (RemoteException e) {
352ec15f2356e47d621584cc3fc84c9c02557e0a0dfMakoto Onuki                        // TODO Change exception handling to be consistent with however this method
353ec15f2356e47d621584cc3fc84c9c02557e0a0dfMakoto Onuki                        // is implemented for other protocols
354ec15f2356e47d621584cc3fc84c9c02557e0a0dfMakoto Onuki                        Log.d("updateMailboxList", "RemoteException" + e);
355ec15f2356e47d621584cc3fc84c9c02557e0a0dfMakoto Onuki                    }
356ec15f2356e47d621584cc3fc84c9c02557e0a0dfMakoto Onuki                } else {
357ec15f2356e47d621584cc3fc84c9c02557e0a0dfMakoto Onuki                    // MessagingController implementation
35846d7d7f1b6387d144c3f9e7c987418dc8f55fad4Andrew Stadler                    mLegacyController.listFolders(accountId, mLegacyListener);
35946d7d7f1b6387d144c3f9e7c987418dc8f55fad4Andrew Stadler                }
360ec15f2356e47d621584cc3fc84c9c02557e0a0dfMakoto Onuki            }
361ec15f2356e47d621584cc3fc84c9c02557e0a0dfMakoto Onuki        });
36246d7d7f1b6387d144c3f9e7c987418dc8f55fad4Andrew Stadler    }
36346d7d7f1b6387d144c3f9e7c987418dc8f55fad4Andrew Stadler
36446d7d7f1b6387d144c3f9e7c987418dc8f55fad4Andrew Stadler    /**
36546d7d7f1b6387d144c3f9e7c987418dc8f55fad4Andrew Stadler     * Request a remote update of a mailbox.  For use by the timed service.
36646d7d7f1b6387d144c3f9e7c987418dc8f55fad4Andrew Stadler     *
36746d7d7f1b6387d144c3f9e7c987418dc8f55fad4Andrew Stadler     * Functionally this is quite similar to updateMailbox(), but it's a separate API and
36846d7d7f1b6387d144c3f9e7c987418dc8f55fad4Andrew Stadler     * separate callback in order to keep UI callbacks from affecting the service loop.
36946d7d7f1b6387d144c3f9e7c987418dc8f55fad4Andrew Stadler     */
3703f545a4060982b8a5d715905c7818d59056c1ee0Makoto Onuki    public void serviceCheckMail(final long accountId, final long mailboxId, final long tag) {
37146d7d7f1b6387d144c3f9e7c987418dc8f55fad4Andrew Stadler        IEmailService service = getServiceForAccount(accountId);
37246d7d7f1b6387d144c3f9e7c987418dc8f55fad4Andrew Stadler        if (service != null) {
37346d7d7f1b6387d144c3f9e7c987418dc8f55fad4Andrew Stadler            // Service implementation
37446d7d7f1b6387d144c3f9e7c987418dc8f55fad4Andrew Stadler//            try {
375d366346a640c93d70b78ddadfbc3a3b74944359bMarc Blank                // TODO this isn't quite going to work, because we're going to get the
37646d7d7f1b6387d144c3f9e7c987418dc8f55fad4Andrew Stadler                // generic (UI) callbacks and not the ones we need to restart the ol' service.
37746d7d7f1b6387d144c3f9e7c987418dc8f55fad4Andrew Stadler                // service.startSync(mailboxId, tag);
3783f545a4060982b8a5d715905c7818d59056c1ee0Makoto Onuki            mLegacyListener.checkMailFinished(mContext, accountId, mailboxId, tag);
37946d7d7f1b6387d144c3f9e7c987418dc8f55fad4Andrew Stadler//            } catch (RemoteException e) {
38046d7d7f1b6387d144c3f9e7c987418dc8f55fad4Andrew Stadler                // TODO Change exception handling to be consistent with however this method
38146d7d7f1b6387d144c3f9e7c987418dc8f55fad4Andrew Stadler                // is implemented for other protocols
38246d7d7f1b6387d144c3f9e7c987418dc8f55fad4Andrew Stadler//                Log.d("updateMailbox", "RemoteException" + e);
38346d7d7f1b6387d144c3f9e7c987418dc8f55fad4Andrew Stadler//            }
38446d7d7f1b6387d144c3f9e7c987418dc8f55fad4Andrew Stadler        } else {
38546d7d7f1b6387d144c3f9e7c987418dc8f55fad4Andrew Stadler            // MessagingController implementation
3863f545a4060982b8a5d715905c7818d59056c1ee0Makoto Onuki            Utility.runAsync(new Runnable() {
38746d7d7f1b6387d144c3f9e7c987418dc8f55fad4Andrew Stadler                public void run() {
38846d7d7f1b6387d144c3f9e7c987418dc8f55fad4Andrew Stadler                    mLegacyController.checkMail(accountId, tag, mLegacyListener);
3898f7f93a7b36d873d5adba65f4da54819880c0285Andrew Stadler                }
3903f545a4060982b8a5d715905c7818d59056c1ee0Makoto Onuki            });
391bcec088320fc56cd5b5e1bbc706e4b7e304b95bfAndrew Stadler        }
392bcec088320fc56cd5b5e1bbc706e4b7e304b95bfAndrew Stadler    }
3937b0b463477ef7172bd4b9e19c6338634ebedd8eeAndrew Stadler
3947b0b463477ef7172bd4b9e19c6338634ebedd8eeAndrew Stadler    /**
3957b0b463477ef7172bd4b9e19c6338634ebedd8eeAndrew Stadler     * Request a remote update of a mailbox.
3967b0b463477ef7172bd4b9e19c6338634ebedd8eeAndrew Stadler     *
3977b0b463477ef7172bd4b9e19c6338634ebedd8eeAndrew Stadler     * The contract here should be to try and update the headers ASAP, in order to populate
3987b0b463477ef7172bd4b9e19c6338634ebedd8eeAndrew Stadler     * a simple message list.  We should also at this point queue up a background task of
3997b0b463477ef7172bd4b9e19c6338634ebedd8eeAndrew Stadler     * downloading some/all of the messages in this mailbox, but that should be interruptable.
4007b0b463477ef7172bd4b9e19c6338634ebedd8eeAndrew Stadler     */
401cbdd9f78b2605e87e45e4f6761b0a8c444a8cd4cMarc Blank    public void updateMailbox(final long accountId, final long mailboxId, boolean userRequest) {
4027b0b463477ef7172bd4b9e19c6338634ebedd8eeAndrew Stadler
4038f7f93a7b36d873d5adba65f4da54819880c0285Andrew Stadler        IEmailService service = getServiceForAccount(accountId);
4048f7f93a7b36d873d5adba65f4da54819880c0285Andrew Stadler        if (service != null) {
405ed1dc9ee72410e07de24bc4a072d066981611a48Marc Blank           try {
406cbdd9f78b2605e87e45e4f6761b0a8c444a8cd4cMarc Blank                service.startSync(mailboxId, userRequest);
4078f7f93a7b36d873d5adba65f4da54819880c0285Andrew Stadler            } catch (RemoteException e) {
4088f7f93a7b36d873d5adba65f4da54819880c0285Andrew Stadler                // TODO Change exception handling to be consistent with however this method
4098f7f93a7b36d873d5adba65f4da54819880c0285Andrew Stadler                // is implemented for other protocols
4108f7f93a7b36d873d5adba65f4da54819880c0285Andrew Stadler                Log.d("updateMailbox", "RemoteException" + e);
4117b0b463477ef7172bd4b9e19c6338634ebedd8eeAndrew Stadler            }
4128f7f93a7b36d873d5adba65f4da54819880c0285Andrew Stadler        } else {
4138f7f93a7b36d873d5adba65f4da54819880c0285Andrew Stadler            // MessagingController implementation
4143f545a4060982b8a5d715905c7818d59056c1ee0Makoto Onuki            Utility.runAsync(new Runnable() {
4158f7f93a7b36d873d5adba65f4da54819880c0285Andrew Stadler                public void run() {
41646d7d7f1b6387d144c3f9e7c987418dc8f55fad4Andrew Stadler                    // TODO shouldn't be passing fully-build accounts & mailboxes into APIs
4178f7f93a7b36d873d5adba65f4da54819880c0285Andrew Stadler                    Account account =
418f5418f1f93b02e7fab9f15eb201800b65510998eMarc Blank                        Account.restoreAccountWithId(mProviderContext, accountId);
41946d7d7f1b6387d144c3f9e7c987418dc8f55fad4Andrew Stadler                    Mailbox mailbox =
42053ea83ebf91f820692e8fa8e781f5cc982dd94dbBen Komalo                        Mailbox.restoreMailboxWithId(mProviderContext, mailboxId);
421ed1dc9ee72410e07de24bc4a072d066981611a48Marc Blank                    if (account == null || mailbox == null ||
422ed1dc9ee72410e07de24bc4a072d066981611a48Marc Blank                            mailbox.mType == Mailbox.TYPE_SEARCH) {
42322d29c67dacf4e8155a588d59815f1f97ecf0588Makoto Onuki                        return;
42422d29c67dacf4e8155a588d59815f1f97ecf0588Makoto Onuki                    }
42546d7d7f1b6387d144c3f9e7c987418dc8f55fad4Andrew Stadler                    mLegacyController.synchronizeMailbox(account, mailbox, mLegacyListener);
4268f7f93a7b36d873d5adba65f4da54819880c0285Andrew Stadler                }
4273f545a4060982b8a5d715905c7818d59056c1ee0Makoto Onuki            });
4287b0b463477ef7172bd4b9e19c6338634ebedd8eeAndrew Stadler        }
4297b0b463477ef7172bd4b9e19c6338634ebedd8eeAndrew Stadler    }
4307b0b463477ef7172bd4b9e19c6338634ebedd8eeAndrew Stadler
431bcec088320fc56cd5b5e1bbc706e4b7e304b95bfAndrew Stadler    /**
432df86adf87328a439347260331592509787020420Andrew Stadler     * Request that any final work necessary be done, to load a message.
433df86adf87328a439347260331592509787020420Andrew Stadler     *
434df86adf87328a439347260331592509787020420Andrew Stadler     * Note, this assumes that the caller has already checked message.mFlagLoaded and that
435df86adf87328a439347260331592509787020420Andrew Stadler     * additional work is needed.  There is no optimization here for a message which is already
436df86adf87328a439347260331592509787020420Andrew Stadler     * loaded.
437df86adf87328a439347260331592509787020420Andrew Stadler     *
438df86adf87328a439347260331592509787020420Andrew Stadler     * @param messageId the message to load
439df86adf87328a439347260331592509787020420Andrew Stadler     * @param callback the Controller callback by which results will be reported
440df86adf87328a439347260331592509787020420Andrew Stadler     */
4413f545a4060982b8a5d715905c7818d59056c1ee0Makoto Onuki    public void loadMessageForView(final long messageId) {
442df86adf87328a439347260331592509787020420Andrew Stadler
443df86adf87328a439347260331592509787020420Andrew Stadler        // Split here for target type (Service or MessagingController)
444df86adf87328a439347260331592509787020420Andrew Stadler        IEmailService service = getServiceForMessage(messageId);
445df86adf87328a439347260331592509787020420Andrew Stadler        if (service != null) {
446df86adf87328a439347260331592509787020420Andrew Stadler            // There is no service implementation, so we'll just jam the value, log the error,
447df86adf87328a439347260331592509787020420Andrew Stadler            // and get out of here.
448df86adf87328a439347260331592509787020420Andrew Stadler            Uri uri = ContentUris.withAppendedId(Message.CONTENT_URI, messageId);
449df86adf87328a439347260331592509787020420Andrew Stadler            ContentValues cv = new ContentValues();
4506c21942ec45f561d711b3d74ecca8e62afb735c4Andrew Stadler            cv.put(MessageColumns.FLAG_LOADED, Message.FLAG_LOADED_COMPLETE);
451fa52e6c95674aef6461a5cfc670a052e1c5b7f2fAndrew Stadler            mProviderContext.getContentResolver().update(uri, cv, null, null);
45231d9acbf0623872f9d4a2b3210b5970854b654c7Marc Blank            Log.d(Logging.LOG_TAG, "Unexpected loadMessageForView() for service-based message.");
453aef9515ee70f1f0b6cc4fa601078597b55831331Makoto Onuki            final long accountId = Account.getAccountIdForMessageId(mProviderContext, messageId);
454df86adf87328a439347260331592509787020420Andrew Stadler            synchronized (mListeners) {
455df86adf87328a439347260331592509787020420Andrew Stadler                for (Result listener : mListeners) {
456aef9515ee70f1f0b6cc4fa601078597b55831331Makoto Onuki                    listener.loadMessageForViewCallback(null, accountId, messageId, 100);
457df86adf87328a439347260331592509787020420Andrew Stadler                }
458df86adf87328a439347260331592509787020420Andrew Stadler            }
459df86adf87328a439347260331592509787020420Andrew Stadler        } else {
460df86adf87328a439347260331592509787020420Andrew Stadler            // MessagingController implementation
4613f545a4060982b8a5d715905c7818d59056c1ee0Makoto Onuki            Utility.runAsync(new Runnable() {
462df86adf87328a439347260331592509787020420Andrew Stadler                public void run() {
463df86adf87328a439347260331592509787020420Andrew Stadler                    mLegacyController.loadMessageForView(messageId, mLegacyListener);
464df86adf87328a439347260331592509787020420Andrew Stadler                }
4653f545a4060982b8a5d715905c7818d59056c1ee0Makoto Onuki            });
466df86adf87328a439347260331592509787020420Andrew Stadler        }
467df86adf87328a439347260331592509787020420Andrew Stadler    }
468df86adf87328a439347260331592509787020420Andrew Stadler
469df86adf87328a439347260331592509787020420Andrew Stadler
470df86adf87328a439347260331592509787020420Andrew Stadler    /**
471c6893ddf0fc1a647ca13a2b3aac2c68ca345de37Mihai Preda     * Saves the message to a mailbox of given type.
4722917e895c7e7c33407a77c76232569d4bd4905cfMihai Preda     * This is a synchronous operation taking place in the same thread as the caller.
4732917e895c7e7c33407a77c76232569d4bd4905cfMihai Preda     * Upon return the message.mId is set.
474c6893ddf0fc1a647ca13a2b3aac2c68ca345de37Mihai Preda     * @param message the message (must have the mAccountId set).
475c6893ddf0fc1a647ca13a2b3aac2c68ca345de37Mihai Preda     * @param mailboxType the mailbox type (e.g. Mailbox.TYPE_DRAFTS).
476c6893ddf0fc1a647ca13a2b3aac2c68ca345de37Mihai Preda     */
477c6893ddf0fc1a647ca13a2b3aac2c68ca345de37Mihai Preda    public void saveToMailbox(final EmailContent.Message message, final int mailboxType) {
4782917e895c7e7c33407a77c76232569d4bd4905cfMihai Preda        long accountId = message.mAccountKey;
4792917e895c7e7c33407a77c76232569d4bd4905cfMihai Preda        long mailboxId = findOrCreateMailboxOfType(accountId, mailboxType);
4802917e895c7e7c33407a77c76232569d4bd4905cfMihai Preda        message.mMailboxKey = mailboxId;
481fa52e6c95674aef6461a5cfc670a052e1c5b7f2fAndrew Stadler        message.save(mProviderContext);
482c6893ddf0fc1a647ca13a2b3aac2c68ca345de37Mihai Preda    }
483c6893ddf0fc1a647ca13a2b3aac2c68ca345de37Mihai Preda
484c6893ddf0fc1a647ca13a2b3aac2c68ca345de37Mihai Preda    /**
4851ecfb5311b0315edf86a450398a8712dbfd86772Ben Komalo     * Look for a specific system mailbox, creating it if necessary, and return the mailbox id.
486900021dbeeb93409c069a04a740c2436e30d1b2aAndy Stadler     * This is a blocking operation and should not be called from the UI thread.
487900021dbeeb93409c069a04a740c2436e30d1b2aAndy Stadler     *
488900021dbeeb93409c069a04a740c2436e30d1b2aAndy Stadler     * Synchronized so multiple threads can call it (and not risk creating duplicate boxes).
489900021dbeeb93409c069a04a740c2436e30d1b2aAndy Stadler     *
490c6893ddf0fc1a647ca13a2b3aac2c68ca345de37Mihai Preda     * @param accountId the account id
491c6893ddf0fc1a647ca13a2b3aac2c68ca345de37Mihai Preda     * @param mailboxType the mailbox type (e.g.  EmailContent.Mailbox.TYPE_TRASH)
492c6893ddf0fc1a647ca13a2b3aac2c68ca345de37Mihai Preda     * @return the id of the mailbox. The mailbox is created if not existing.
493c6893ddf0fc1a647ca13a2b3aac2c68ca345de37Mihai Preda     * Returns Mailbox.NO_MAILBOX if the accountId or mailboxType are negative.
494c6893ddf0fc1a647ca13a2b3aac2c68ca345de37Mihai Preda     * Does not validate the input in other ways (e.g. does not verify the existence of account).
495c6893ddf0fc1a647ca13a2b3aac2c68ca345de37Mihai Preda     */
496900021dbeeb93409c069a04a740c2436e30d1b2aAndy Stadler    public synchronized long findOrCreateMailboxOfType(long accountId, int mailboxType) {
497c6893ddf0fc1a647ca13a2b3aac2c68ca345de37Mihai Preda        if (accountId < 0 || mailboxType < 0) {
498c6893ddf0fc1a647ca13a2b3aac2c68ca345de37Mihai Preda            return Mailbox.NO_MAILBOX;
499c6893ddf0fc1a647ca13a2b3aac2c68ca345de37Mihai Preda        }
500c6893ddf0fc1a647ca13a2b3aac2c68ca345de37Mihai Preda        long mailboxId =
501c6893ddf0fc1a647ca13a2b3aac2c68ca345de37Mihai Preda            Mailbox.findMailboxOfType(mProviderContext, accountId, mailboxType);
502c6893ddf0fc1a647ca13a2b3aac2c68ca345de37Mihai Preda        return mailboxId == Mailbox.NO_MAILBOX ? createMailbox(accountId, mailboxType) : mailboxId;
503c6893ddf0fc1a647ca13a2b3aac2c68ca345de37Mihai Preda    }
504c6893ddf0fc1a647ca13a2b3aac2c68ca345de37Mihai Preda
505c6893ddf0fc1a647ca13a2b3aac2c68ca345de37Mihai Preda    /**
506fa52e6c95674aef6461a5cfc670a052e1c5b7f2fAndrew Stadler     * Returns the server-side name for a specific mailbox.
507fa52e6c95674aef6461a5cfc670a052e1c5b7f2fAndrew Stadler     *
508c6893ddf0fc1a647ca13a2b3aac2c68ca345de37Mihai Preda     * @return the resource string corresponding to the mailbox type, empty if not found.
509c6893ddf0fc1a647ca13a2b3aac2c68ca345de37Mihai Preda     */
510e71a19a52313a0fb615700e52a336b65f3c305daBen Komalo    public static String getMailboxServerName(Context context, int mailboxType) {
511c6893ddf0fc1a647ca13a2b3aac2c68ca345de37Mihai Preda        int resId = -1;
512c6893ddf0fc1a647ca13a2b3aac2c68ca345de37Mihai Preda        switch (mailboxType) {
5136ae38e4a3937898c5d3fec11a9dc2fb4bab8eab6Andrew Stadler            case Mailbox.TYPE_INBOX:
514fa52e6c95674aef6461a5cfc670a052e1c5b7f2fAndrew Stadler                resId = R.string.mailbox_name_server_inbox;
5156ae38e4a3937898c5d3fec11a9dc2fb4bab8eab6Andrew Stadler                break;
5166ae38e4a3937898c5d3fec11a9dc2fb4bab8eab6Andrew Stadler            case Mailbox.TYPE_OUTBOX:
517fa52e6c95674aef6461a5cfc670a052e1c5b7f2fAndrew Stadler                resId = R.string.mailbox_name_server_outbox;
5186ae38e4a3937898c5d3fec11a9dc2fb4bab8eab6Andrew Stadler                break;
5196ae38e4a3937898c5d3fec11a9dc2fb4bab8eab6Andrew Stadler            case Mailbox.TYPE_DRAFTS:
520fa52e6c95674aef6461a5cfc670a052e1c5b7f2fAndrew Stadler                resId = R.string.mailbox_name_server_drafts;
5216ae38e4a3937898c5d3fec11a9dc2fb4bab8eab6Andrew Stadler                break;
5226ae38e4a3937898c5d3fec11a9dc2fb4bab8eab6Andrew Stadler            case Mailbox.TYPE_TRASH:
523fa52e6c95674aef6461a5cfc670a052e1c5b7f2fAndrew Stadler                resId = R.string.mailbox_name_server_trash;
5246ae38e4a3937898c5d3fec11a9dc2fb4bab8eab6Andrew Stadler                break;
5256ae38e4a3937898c5d3fec11a9dc2fb4bab8eab6Andrew Stadler            case Mailbox.TYPE_SENT:
526fa52e6c95674aef6461a5cfc670a052e1c5b7f2fAndrew Stadler                resId = R.string.mailbox_name_server_sent;
527fa52e6c95674aef6461a5cfc670a052e1c5b7f2fAndrew Stadler                break;
528fa52e6c95674aef6461a5cfc670a052e1c5b7f2fAndrew Stadler            case Mailbox.TYPE_JUNK:
529fa52e6c95674aef6461a5cfc670a052e1c5b7f2fAndrew Stadler                resId = R.string.mailbox_name_server_junk;
5306ae38e4a3937898c5d3fec11a9dc2fb4bab8eab6Andrew Stadler                break;
531c6893ddf0fc1a647ca13a2b3aac2c68ca345de37Mihai Preda        }
532e71a19a52313a0fb615700e52a336b65f3c305daBen Komalo        return resId != -1 ? context.getString(resId) : "";
533c6893ddf0fc1a647ca13a2b3aac2c68ca345de37Mihai Preda    }
534c6893ddf0fc1a647ca13a2b3aac2c68ca345de37Mihai Preda
535c6893ddf0fc1a647ca13a2b3aac2c68ca345de37Mihai Preda    /**
536c6893ddf0fc1a647ca13a2b3aac2c68ca345de37Mihai Preda     * Create a mailbox given the account and mailboxType.
537c6893ddf0fc1a647ca13a2b3aac2c68ca345de37Mihai Preda     * TODO: Does this need to be signaled explicitly to the sync engines?
538c6893ddf0fc1a647ca13a2b3aac2c68ca345de37Mihai Preda     */
5399d2dae67506983c64f72350a4fb5967cfd85b9a8Todd Kennedy    @VisibleForTesting
5409d2dae67506983c64f72350a4fb5967cfd85b9a8Todd Kennedy    long createMailbox(long accountId, int mailboxType) {
541c6893ddf0fc1a647ca13a2b3aac2c68ca345de37Mihai Preda        if (accountId < 0 || mailboxType < 0) {
542c6893ddf0fc1a647ca13a2b3aac2c68ca345de37Mihai Preda            String mes = "Invalid arguments " + accountId + ' ' + mailboxType;
54331d9acbf0623872f9d4a2b3210b5970854b654c7Marc Blank            Log.e(Logging.LOG_TAG, mes);
544f00dccd32125c727cc18d837b59c15c95f5d78bcMarc Blank            throw new RuntimeException(mes);
545c6893ddf0fc1a647ca13a2b3aac2c68ca345de37Mihai Preda        }
546e71a19a52313a0fb615700e52a336b65f3c305daBen Komalo        Mailbox box = Mailbox.newSystemMailbox(
547e71a19a52313a0fb615700e52a336b65f3c305daBen Komalo                accountId, mailboxType, getMailboxServerName(mContext, mailboxType));
5489e2c6bd5f21f2d19eef7ebfe30e6fdf94ede0857Andrew Stadler        box.save(mProviderContext);
549c6893ddf0fc1a647ca13a2b3aac2c68ca345de37Mihai Preda        return box.mId;
550c6893ddf0fc1a647ca13a2b3aac2c68ca345de37Mihai Preda    }
551c6893ddf0fc1a647ca13a2b3aac2c68ca345de37Mihai Preda
552c6893ddf0fc1a647ca13a2b3aac2c68ca345de37Mihai Preda    /**
553334903369e1ea8f032d7ffd67f57f4e7004dc2cfMihai Preda     * Send a message:
554334903369e1ea8f032d7ffd67f57f4e7004dc2cfMihai Preda     * - move the message to Outbox (the message is assumed to be in Drafts).
5554b41bae270ea4c49ec8403084db43ee9b37cdda4Andrew Stadler     * - EAS service will take it from there
5567891106a7d6b4c406792a8a9144e5148e6bf5ddbBen Komalo     * - mark reply/forward state in source message (if any)
5574b41bae270ea4c49ec8403084db43ee9b37cdda4Andrew Stadler     * - trigger send for POP/IMAP
5587891106a7d6b4c406792a8a9144e5148e6bf5ddbBen Komalo     * @param message the fully populated Message (usually retrieved from the Draft box). Note that
5597891106a7d6b4c406792a8a9144e5148e6bf5ddbBen Komalo     *     all transient fields (e.g. Body related fields) are also expected to be fully loaded
560334903369e1ea8f032d7ffd67f57f4e7004dc2cfMihai Preda     */
5617891106a7d6b4c406792a8a9144e5148e6bf5ddbBen Komalo    public void sendMessage(Message message) {
562334903369e1ea8f032d7ffd67f57f4e7004dc2cfMihai Preda        ContentResolver resolver = mProviderContext.getContentResolver();
5637891106a7d6b4c406792a8a9144e5148e6bf5ddbBen Komalo        long accountId = message.mAccountKey;
5647891106a7d6b4c406792a8a9144e5148e6bf5ddbBen Komalo        long messageId = message.mId;
5657891106a7d6b4c406792a8a9144e5148e6bf5ddbBen Komalo        if (accountId == Account.NO_ACCOUNT) {
566334903369e1ea8f032d7ffd67f57f4e7004dc2cfMihai Preda            accountId = lookupAccountForMessage(messageId);
567334903369e1ea8f032d7ffd67f57f4e7004dc2cfMihai Preda        }
5687891106a7d6b4c406792a8a9144e5148e6bf5ddbBen Komalo        if (accountId == Account.NO_ACCOUNT) {
569334903369e1ea8f032d7ffd67f57f4e7004dc2cfMihai Preda            // probably the message was not found
570bfac9f2e8a13f6c719608a6948203bbef921c99fMakoto Onuki            if (Logging.LOGD) {
571334903369e1ea8f032d7ffd67f57f4e7004dc2cfMihai Preda                Email.log("no account found for message " + messageId);
572334903369e1ea8f032d7ffd67f57f4e7004dc2cfMihai Preda            }
573334903369e1ea8f032d7ffd67f57f4e7004dc2cfMihai Preda            return;
574334903369e1ea8f032d7ffd67f57f4e7004dc2cfMihai Preda        }
575334903369e1ea8f032d7ffd67f57f4e7004dc2cfMihai Preda
576334903369e1ea8f032d7ffd67f57f4e7004dc2cfMihai Preda        // Move to Outbox
577334903369e1ea8f032d7ffd67f57f4e7004dc2cfMihai Preda        long outboxId = findOrCreateMailboxOfType(accountId, Mailbox.TYPE_OUTBOX);
578334903369e1ea8f032d7ffd67f57f4e7004dc2cfMihai Preda        ContentValues cv = new ContentValues();
579334903369e1ea8f032d7ffd67f57f4e7004dc2cfMihai Preda        cv.put(EmailContent.MessageColumns.MAILBOX_KEY, outboxId);
580334903369e1ea8f032d7ffd67f57f4e7004dc2cfMihai Preda
581334903369e1ea8f032d7ffd67f57f4e7004dc2cfMihai Preda        // does this need to be SYNCED_CONTENT_URI instead?
5827891106a7d6b4c406792a8a9144e5148e6bf5ddbBen Komalo        Uri uri = ContentUris.withAppendedId(Message.CONTENT_URI, messageId);
583334903369e1ea8f032d7ffd67f57f4e7004dc2cfMihai Preda        resolver.update(uri, cv, null, null);
584334903369e1ea8f032d7ffd67f57f4e7004dc2cfMihai Preda
5857891106a7d6b4c406792a8a9144e5148e6bf5ddbBen Komalo        // If this is a reply/forward, indicate it as such on the source.
5867891106a7d6b4c406792a8a9144e5148e6bf5ddbBen Komalo        long sourceKey = message.mSourceKey;
5877891106a7d6b4c406792a8a9144e5148e6bf5ddbBen Komalo        if (sourceKey != Message.NO_MESSAGE) {
58850a092c3d6155fcd704990450640cf159072b1a2Marc Blank            boolean isReply = (message.mFlags & Message.FLAG_TYPE_REPLY) != 0;
58950a092c3d6155fcd704990450640cf159072b1a2Marc Blank            int flagUpdate = isReply ? Message.FLAG_REPLIED_TO : Message.FLAG_FORWARDED;
59050a092c3d6155fcd704990450640cf159072b1a2Marc Blank            setMessageAnsweredOrForwarded(sourceKey, flagUpdate);
5917891106a7d6b4c406792a8a9144e5148e6bf5ddbBen Komalo        }
5927891106a7d6b4c406792a8a9144e5148e6bf5ddbBen Komalo
5937894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank        sendPendingMessages(accountId);
5947894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank    }
5957894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank
5967894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank    private void sendPendingMessagesSmtp(long accountId) {
5977894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank        // for IMAP & POP only, (attempt to) send the message now
598f5418f1f93b02e7fab9f15eb201800b65510998eMarc Blank        final Account account =
599f5418f1f93b02e7fab9f15eb201800b65510998eMarc Blank                Account.restoreAccountWithId(mProviderContext, accountId);
6007894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank        if (account == null) {
6017894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank            return;
6024b41bae270ea4c49ec8403084db43ee9b37cdda4Andrew Stadler        }
6037894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank        final long sentboxId = findOrCreateMailboxOfType(accountId, Mailbox.TYPE_SENT);
6047894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank        Utility.runAsync(new Runnable() {
6057894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank            public void run() {
6067894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank                mLegacyController.sendPendingMessages(account, sentboxId, mLegacyListener);
6077894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank            }
6087894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank        });
609334903369e1ea8f032d7ffd67f57f4e7004dc2cfMihai Preda    }
610334903369e1ea8f032d7ffd67f57f4e7004dc2cfMihai Preda
611334903369e1ea8f032d7ffd67f57f4e7004dc2cfMihai Preda    /**
612e346193f06c6c3ffd8412ce151109df8b7f45999Andrew Stadler     * Try to send all pending messages for a given account
613d366346a640c93d70b78ddadfbc3a3b74944359bMarc Blank     *
6147894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank     * @param accountId the account for which to send messages
615e346193f06c6c3ffd8412ce151109df8b7f45999Andrew Stadler     */
6163f545a4060982b8a5d715905c7818d59056c1ee0Makoto Onuki    public void sendPendingMessages(long accountId) {
6174b41bae270ea4c49ec8403084db43ee9b37cdda4Andrew Stadler        // 1. make sure we even have an outbox, exit early if not
6184b41bae270ea4c49ec8403084db43ee9b37cdda4Andrew Stadler        final long outboxId =
6194b41bae270ea4c49ec8403084db43ee9b37cdda4Andrew Stadler            Mailbox.findMailboxOfType(mProviderContext, accountId, Mailbox.TYPE_OUTBOX);
6204b41bae270ea4c49ec8403084db43ee9b37cdda4Andrew Stadler        if (outboxId == Mailbox.NO_MAILBOX) {
6214b41bae270ea4c49ec8403084db43ee9b37cdda4Andrew Stadler            return;
6224b41bae270ea4c49ec8403084db43ee9b37cdda4Andrew Stadler        }
6234b41bae270ea4c49ec8403084db43ee9b37cdda4Andrew Stadler
6244b41bae270ea4c49ec8403084db43ee9b37cdda4Andrew Stadler        // 2. dispatch as necessary
6254b41bae270ea4c49ec8403084db43ee9b37cdda4Andrew Stadler        IEmailService service = getServiceForAccount(accountId);
6264b41bae270ea4c49ec8403084db43ee9b37cdda4Andrew Stadler        if (service != null) {
6274b41bae270ea4c49ec8403084db43ee9b37cdda4Andrew Stadler            // Service implementation
6284b41bae270ea4c49ec8403084db43ee9b37cdda4Andrew Stadler            try {
629cbdd9f78b2605e87e45e4f6761b0a8c444a8cd4cMarc Blank                service.startSync(outboxId, false);
6304b41bae270ea4c49ec8403084db43ee9b37cdda4Andrew Stadler            } catch (RemoteException e) {
6314b41bae270ea4c49ec8403084db43ee9b37cdda4Andrew Stadler                // TODO Change exception handling to be consistent with however this method
6324b41bae270ea4c49ec8403084db43ee9b37cdda4Andrew Stadler                // is implemented for other protocols
6334b41bae270ea4c49ec8403084db43ee9b37cdda4Andrew Stadler                Log.d("updateMailbox", "RemoteException" + e);
6344b41bae270ea4c49ec8403084db43ee9b37cdda4Andrew Stadler            }
6354b41bae270ea4c49ec8403084db43ee9b37cdda4Andrew Stadler        } else {
6364b41bae270ea4c49ec8403084db43ee9b37cdda4Andrew Stadler            // MessagingController implementation
6377894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank            sendPendingMessagesSmtp(accountId);
6384b41bae270ea4c49ec8403084db43ee9b37cdda4Andrew Stadler        }
639e346193f06c6c3ffd8412ce151109df8b7f45999Andrew Stadler    }
640e346193f06c6c3ffd8412ce151109df8b7f45999Andrew Stadler
641e346193f06c6c3ffd8412ce151109df8b7f45999Andrew Stadler    /**
64277398c42899a383680005b92955a29ab3d872c5cAndrew Stadler     * Reset visible limits for all accounts.
64377398c42899a383680005b92955a29ab3d872c5cAndrew Stadler     * For each account:
64477398c42899a383680005b92955a29ab3d872c5cAndrew Stadler     *   look up limit
64577398c42899a383680005b92955a29ab3d872c5cAndrew Stadler     *   write limit into all mailboxes for that account
64677398c42899a383680005b92955a29ab3d872c5cAndrew Stadler     */
64777398c42899a383680005b92955a29ab3d872c5cAndrew Stadler    public void resetVisibleLimits() {
6483f545a4060982b8a5d715905c7818d59056c1ee0Makoto Onuki        Utility.runAsync(new Runnable() {
64977398c42899a383680005b92955a29ab3d872c5cAndrew Stadler            public void run() {
650fa52e6c95674aef6461a5cfc670a052e1c5b7f2fAndrew Stadler                ContentResolver resolver = mProviderContext.getContentResolver();
65177398c42899a383680005b92955a29ab3d872c5cAndrew Stadler                Cursor c = null;
65277398c42899a383680005b92955a29ab3d872c5cAndrew Stadler                try {
65377398c42899a383680005b92955a29ab3d872c5cAndrew Stadler                    c = resolver.query(
65477398c42899a383680005b92955a29ab3d872c5cAndrew Stadler                            Account.CONTENT_URI,
65577398c42899a383680005b92955a29ab3d872c5cAndrew Stadler                            Account.ID_PROJECTION,
65677398c42899a383680005b92955a29ab3d872c5cAndrew Stadler                            null, null, null);
65777398c42899a383680005b92955a29ab3d872c5cAndrew Stadler                    while (c.moveToNext()) {
65877398c42899a383680005b92955a29ab3d872c5cAndrew Stadler                        long accountId = c.getLong(Account.ID_PROJECTION_COLUMN);
65985e4c101b014857fe40f87c3837b82564cfc5b6cMarc Blank                        String protocol = Account.getProtocol(mProviderContext, accountId);
66085e4c101b014857fe40f87c3837b82564cfc5b6cMarc Blank                        if (!HostAuth.SCHEME_EAS.equals(protocol)) {
66185e4c101b014857fe40f87c3837b82564cfc5b6cMarc Blank                            ContentValues cv = new ContentValues();
66285e4c101b014857fe40f87c3837b82564cfc5b6cMarc Blank                            cv.put(MailboxColumns.VISIBLE_LIMIT, Email.VISIBLE_LIMIT_DEFAULT);
66385e4c101b014857fe40f87c3837b82564cfc5b6cMarc Blank                            resolver.update(Mailbox.CONTENT_URI, cv,
66485e4c101b014857fe40f87c3837b82564cfc5b6cMarc Blank                                    MailboxColumns.ACCOUNT_KEY + "=?",
66585e4c101b014857fe40f87c3837b82564cfc5b6cMarc Blank                                    new String[] { Long.toString(accountId) });
66677398c42899a383680005b92955a29ab3d872c5cAndrew Stadler                        }
66777398c42899a383680005b92955a29ab3d872c5cAndrew Stadler                    }
66877398c42899a383680005b92955a29ab3d872c5cAndrew Stadler                } finally {
669f5c3db12de89365c6d52c6d75c7b684e2c0d16ffMarc Blank                    if (c != null) {
670f5c3db12de89365c6d52c6d75c7b684e2c0d16ffMarc Blank                        c.close();
671f5c3db12de89365c6d52c6d75c7b684e2c0d16ffMarc Blank                    }
67277398c42899a383680005b92955a29ab3d872c5cAndrew Stadler                }
67377398c42899a383680005b92955a29ab3d872c5cAndrew Stadler            }
6743f545a4060982b8a5d715905c7818d59056c1ee0Makoto Onuki        });
67577398c42899a383680005b92955a29ab3d872c5cAndrew Stadler    }
67677398c42899a383680005b92955a29ab3d872c5cAndrew Stadler
67777398c42899a383680005b92955a29ab3d872c5cAndrew Stadler    /**
678e346193f06c6c3ffd8412ce151109df8b7f45999Andrew Stadler     * Increase the load count for a given mailbox, and trigger a refresh.  Applies only to
679bad39b2e00dcd9127f5e0aed96ab31cf0817be76Marc Blank     * IMAP and POP mailboxes, with the exception of the EAS search mailbox.
680e346193f06c6c3ffd8412ce151109df8b7f45999Andrew Stadler     *
681e346193f06c6c3ffd8412ce151109df8b7f45999Andrew Stadler     * @param mailboxId the mailbox
682e346193f06c6c3ffd8412ce151109df8b7f45999Andrew Stadler     */
6833f545a4060982b8a5d715905c7818d59056c1ee0Makoto Onuki    public void loadMoreMessages(final long mailboxId) {
684ed1dc9ee72410e07de24bc4a072d066981611a48Marc Blank        EmailAsyncTask.runAsyncParallel(new Runnable() {
68577398c42899a383680005b92955a29ab3d872c5cAndrew Stadler            public void run() {
686fa52e6c95674aef6461a5cfc670a052e1c5b7f2fAndrew Stadler                Mailbox mailbox = Mailbox.restoreMailboxWithId(mProviderContext, mailboxId);
68777398c42899a383680005b92955a29ab3d872c5cAndrew Stadler                if (mailbox == null) {
68877398c42899a383680005b92955a29ab3d872c5cAndrew Stadler                    return;
68977398c42899a383680005b92955a29ab3d872c5cAndrew Stadler                }
690ed1dc9ee72410e07de24bc4a072d066981611a48Marc Blank                if (mailbox.mType == Mailbox.TYPE_SEARCH) {
691ed1dc9ee72410e07de24bc4a072d066981611a48Marc Blank                    try {
692ed1dc9ee72410e07de24bc4a072d066981611a48Marc Blank                        searchMore(mailbox.mAccountKey);
693ed1dc9ee72410e07de24bc4a072d066981611a48Marc Blank                    } catch (MessagingException e) {
694ed1dc9ee72410e07de24bc4a072d066981611a48Marc Blank                        // Nothing to be done
695ed1dc9ee72410e07de24bc4a072d066981611a48Marc Blank                    }
696ed1dc9ee72410e07de24bc4a072d066981611a48Marc Blank                    return;
697ed1dc9ee72410e07de24bc4a072d066981611a48Marc Blank                }
698fa52e6c95674aef6461a5cfc670a052e1c5b7f2fAndrew Stadler                Account account = Account.restoreAccountWithId(mProviderContext,
699fa52e6c95674aef6461a5cfc670a052e1c5b7f2fAndrew Stadler                        mailbox.mAccountKey);
70077398c42899a383680005b92955a29ab3d872c5cAndrew Stadler                if (account == null) {
70177398c42899a383680005b92955a29ab3d872c5cAndrew Stadler                    return;
70277398c42899a383680005b92955a29ab3d872c5cAndrew Stadler                }
70385e4c101b014857fe40f87c3837b82564cfc5b6cMarc Blank                // Use provider math to increment the field
70485e4c101b014857fe40f87c3837b82564cfc5b6cMarc Blank                ContentValues cv = new ContentValues();;
70585e4c101b014857fe40f87c3837b82564cfc5b6cMarc Blank                cv.put(EmailContent.FIELD_COLUMN_NAME, MailboxColumns.VISIBLE_LIMIT);
70685e4c101b014857fe40f87c3837b82564cfc5b6cMarc Blank                cv.put(EmailContent.ADD_COLUMN_NAME, Email.VISIBLE_LIMIT_INCREMENT);
70785e4c101b014857fe40f87c3837b82564cfc5b6cMarc Blank                Uri uri = ContentUris.withAppendedId(Mailbox.ADD_TO_FIELD_URI, mailboxId);
70885e4c101b014857fe40f87c3837b82564cfc5b6cMarc Blank                mProviderContext.getContentResolver().update(uri, cv, null, null);
70985e4c101b014857fe40f87c3837b82564cfc5b6cMarc Blank                // Trigger a refresh using the new, longer limit
71085e4c101b014857fe40f87c3837b82564cfc5b6cMarc Blank                mailbox.mVisibleLimit += Email.VISIBLE_LIMIT_INCREMENT;
71185e4c101b014857fe40f87c3837b82564cfc5b6cMarc Blank                mLegacyController.synchronizeMailbox(account, mailbox, mLegacyListener);
71277398c42899a383680005b92955a29ab3d872c5cAndrew Stadler            }
7133f545a4060982b8a5d715905c7818d59056c1ee0Makoto Onuki        });
714e346193f06c6c3ffd8412ce151109df8b7f45999Andrew Stadler    }
715e346193f06c6c3ffd8412ce151109df8b7f45999Andrew Stadler
716e346193f06c6c3ffd8412ce151109df8b7f45999Andrew Stadler    /**
717334903369e1ea8f032d7ffd67f57f4e7004dc2cfMihai Preda     * @param messageId the id of message
718334903369e1ea8f032d7ffd67f57f4e7004dc2cfMihai Preda     * @return the accountId corresponding to the given messageId, or -1 if not found.
719334903369e1ea8f032d7ffd67f57f4e7004dc2cfMihai Preda     */
720334903369e1ea8f032d7ffd67f57f4e7004dc2cfMihai Preda    private long lookupAccountForMessage(long messageId) {
721334903369e1ea8f032d7ffd67f57f4e7004dc2cfMihai Preda        ContentResolver resolver = mProviderContext.getContentResolver();
722334903369e1ea8f032d7ffd67f57f4e7004dc2cfMihai Preda        Cursor c = resolver.query(EmailContent.Message.CONTENT_URI,
723334903369e1ea8f032d7ffd67f57f4e7004dc2cfMihai Preda                                  MESSAGEID_TO_ACCOUNTID_PROJECTION, EmailContent.RECORD_ID + "=?",
724334903369e1ea8f032d7ffd67f57f4e7004dc2cfMihai Preda                                  new String[] { Long.toString(messageId) }, null);
725334903369e1ea8f032d7ffd67f57f4e7004dc2cfMihai Preda        try {
726334903369e1ea8f032d7ffd67f57f4e7004dc2cfMihai Preda            return c.moveToFirst()
727334903369e1ea8f032d7ffd67f57f4e7004dc2cfMihai Preda                ? c.getLong(MESSAGEID_TO_ACCOUNTID_COLUMN_ACCOUNTID)
728334903369e1ea8f032d7ffd67f57f4e7004dc2cfMihai Preda                : -1;
729334903369e1ea8f032d7ffd67f57f4e7004dc2cfMihai Preda        } finally {
730334903369e1ea8f032d7ffd67f57f4e7004dc2cfMihai Preda            c.close();
731334903369e1ea8f032d7ffd67f57f4e7004dc2cfMihai Preda        }
732334903369e1ea8f032d7ffd67f57f4e7004dc2cfMihai Preda    }
733334903369e1ea8f032d7ffd67f57f4e7004dc2cfMihai Preda
734334903369e1ea8f032d7ffd67f57f4e7004dc2cfMihai Preda    /**
7357768ce276ede4ee979df28ee514ddca79bdb90abMihai Preda     * Delete a single attachment entry from the DB given its id.
7365de54008e58ff63d388e4d448b50a47950990e22Marc Blank     * Does not delete any eventual associated files.
7377768ce276ede4ee979df28ee514ddca79bdb90abMihai Preda     */
7387768ce276ede4ee979df28ee514ddca79bdb90abMihai Preda    public void deleteAttachment(long attachmentId) {
7397768ce276ede4ee979df28ee514ddca79bdb90abMihai Preda        ContentResolver resolver = mProviderContext.getContentResolver();
7407768ce276ede4ee979df28ee514ddca79bdb90abMihai Preda        Uri uri = ContentUris.withAppendedId(Attachment.CONTENT_URI, attachmentId);
7417768ce276ede4ee979df28ee514ddca79bdb90abMihai Preda        resolver.delete(uri, null, null);
7427768ce276ede4ee979df28ee514ddca79bdb90abMihai Preda    }
7437768ce276ede4ee979df28ee514ddca79bdb90abMihai Preda
7447768ce276ede4ee979df28ee514ddca79bdb90abMihai Preda    /**
7453096b4ae18f55c9ebf04d83c534da4d5e3370932Makoto Onuki     * Async version of {@link #deleteMessageSync}.
7467c3cca80a0b4dacb7bcb48c65a9999138b5df82bAndrew Stadler     */
7473096b4ae18f55c9ebf04d83c534da4d5e3370932Makoto Onuki    public void deleteMessage(final long messageId) {
7483096b4ae18f55c9ebf04d83c534da4d5e3370932Makoto Onuki        EmailAsyncTask.runAsyncParallel(new Runnable() {
7493096b4ae18f55c9ebf04d83c534da4d5e3370932Makoto Onuki            public void run() {
7503096b4ae18f55c9ebf04d83c534da4d5e3370932Makoto Onuki                deleteMessageSync(messageId);
7513096b4ae18f55c9ebf04d83c534da4d5e3370932Makoto Onuki            }
7523096b4ae18f55c9ebf04d83c534da4d5e3370932Makoto Onuki        });
7533096b4ae18f55c9ebf04d83c534da4d5e3370932Makoto Onuki    }
7543096b4ae18f55c9ebf04d83c534da4d5e3370932Makoto Onuki
7553096b4ae18f55c9ebf04d83c534da4d5e3370932Makoto Onuki    /**
7563096b4ae18f55c9ebf04d83c534da4d5e3370932Makoto Onuki     * Batch & async version of {@link #deleteMessageSync}.
7573096b4ae18f55c9ebf04d83c534da4d5e3370932Makoto Onuki     */
7583096b4ae18f55c9ebf04d83c534da4d5e3370932Makoto Onuki    public void deleteMessages(final long[] messageIds) {
7593096b4ae18f55c9ebf04d83c534da4d5e3370932Makoto Onuki        if (messageIds == null || messageIds.length == 0) {
7603096b4ae18f55c9ebf04d83c534da4d5e3370932Makoto Onuki            throw new IllegalArgumentException();
7613096b4ae18f55c9ebf04d83c534da4d5e3370932Makoto Onuki        }
7623096b4ae18f55c9ebf04d83c534da4d5e3370932Makoto Onuki        EmailAsyncTask.runAsyncParallel(new Runnable() {
763b53b1501055cbf5040bfd7b88a9cda084574c398Marc Blank            public void run() {
7643096b4ae18f55c9ebf04d83c534da4d5e3370932Makoto Onuki                for (long messageId: messageIds) {
7653096b4ae18f55c9ebf04d83c534da4d5e3370932Makoto Onuki                    deleteMessageSync(messageId);
7663096b4ae18f55c9ebf04d83c534da4d5e3370932Makoto Onuki                }
76777aabd951992057a543f1699150971bf1f2d4394Makoto Onuki            }
76877aabd951992057a543f1699150971bf1f2d4394Makoto Onuki        });
76977aabd951992057a543f1699150971bf1f2d4394Makoto Onuki    }
7707c3cca80a0b4dacb7bcb48c65a9999138b5df82bAndrew Stadler
77177aabd951992057a543f1699150971bf1f2d4394Makoto Onuki    /**
7723096b4ae18f55c9ebf04d83c534da4d5e3370932Makoto Onuki     * Delete a single message by moving it to the trash, or really delete it if it's already in
7733096b4ae18f55c9ebf04d83c534da4d5e3370932Makoto Onuki     * trash or a draft message.
7743096b4ae18f55c9ebf04d83c534da4d5e3370932Makoto Onuki     *
7753096b4ae18f55c9ebf04d83c534da4d5e3370932Makoto Onuki     * This function has no callback, no result reporting, because the desired outcome
7763096b4ae18f55c9ebf04d83c534da4d5e3370932Makoto Onuki     * is reflected entirely by changes to one or more cursors.
7773096b4ae18f55c9ebf04d83c534da4d5e3370932Makoto Onuki     *
7783096b4ae18f55c9ebf04d83c534da4d5e3370932Makoto Onuki     * @param messageId The id of the message to "delete".
77977aabd951992057a543f1699150971bf1f2d4394Makoto Onuki     */
7803096b4ae18f55c9ebf04d83c534da4d5e3370932Makoto Onuki    /* package */ void deleteMessageSync(long messageId) {
78177aabd951992057a543f1699150971bf1f2d4394Makoto Onuki        // 1. Get the message's account
78277aabd951992057a543f1699150971bf1f2d4394Makoto Onuki        Account account = Account.getAccountForMessageId(mProviderContext, messageId);
7837c3cca80a0b4dacb7bcb48c65a9999138b5df82bAndrew Stadler
7841daf8766937e0d8dda6a35870b3e27435c92df9aMakoto Onuki        if (account == null) return;
7851daf8766937e0d8dda6a35870b3e27435c92df9aMakoto Onuki
78677aabd951992057a543f1699150971bf1f2d4394Makoto Onuki        // 2. Confirm that there is a trash mailbox available.  If not, create one
78777aabd951992057a543f1699150971bf1f2d4394Makoto Onuki        long trashMailboxId = findOrCreateMailboxOfType(account.mId, Mailbox.TYPE_TRASH);
7887c3cca80a0b4dacb7bcb48c65a9999138b5df82bAndrew Stadler
78977aabd951992057a543f1699150971bf1f2d4394Makoto Onuki        // 3. Get the message's original mailbox
79077aabd951992057a543f1699150971bf1f2d4394Makoto Onuki        Mailbox mailbox = Mailbox.getMailboxForMessageId(mProviderContext, messageId);
7917c3cca80a0b4dacb7bcb48c65a9999138b5df82bAndrew Stadler
7921daf8766937e0d8dda6a35870b3e27435c92df9aMakoto Onuki        if (mailbox == null) return;
7931daf8766937e0d8dda6a35870b3e27435c92df9aMakoto Onuki
79477aabd951992057a543f1699150971bf1f2d4394Makoto Onuki        // 4.  Drop non-essential data for the message (e.g. attachment files)
7958a574694606f0e5d781334d0d426fc379c51f3edMarc Blank        AttachmentUtilities.deleteAllAttachmentFiles(mProviderContext, account.mId,
79677aabd951992057a543f1699150971bf1f2d4394Makoto Onuki                messageId);
7977c3cca80a0b4dacb7bcb48c65a9999138b5df82bAndrew Stadler
79877aabd951992057a543f1699150971bf1f2d4394Makoto Onuki        Uri uri = ContentUris.withAppendedId(EmailContent.Message.SYNCED_CONTENT_URI,
79977aabd951992057a543f1699150971bf1f2d4394Makoto Onuki                messageId);
80077aabd951992057a543f1699150971bf1f2d4394Makoto Onuki        ContentResolver resolver = mProviderContext.getContentResolver();
801f9ab857a5599faac2896394180fcd4ed56b09941Andrew Stadler
80277aabd951992057a543f1699150971bf1f2d4394Makoto Onuki        // 5. Perform "delete" as appropriate
80377aabd951992057a543f1699150971bf1f2d4394Makoto Onuki        if ((mailbox.mId == trashMailboxId) || (mailbox.mType == Mailbox.TYPE_DRAFTS)) {
80477aabd951992057a543f1699150971bf1f2d4394Makoto Onuki            // 5a. Really delete it
80577aabd951992057a543f1699150971bf1f2d4394Makoto Onuki            resolver.delete(uri, null, null);
80677aabd951992057a543f1699150971bf1f2d4394Makoto Onuki        } else {
80777aabd951992057a543f1699150971bf1f2d4394Makoto Onuki            // 5b. Move to trash
80877aabd951992057a543f1699150971bf1f2d4394Makoto Onuki            ContentValues cv = new ContentValues();
80977aabd951992057a543f1699150971bf1f2d4394Makoto Onuki            cv.put(EmailContent.MessageColumns.MAILBOX_KEY, trashMailboxId);
81077aabd951992057a543f1699150971bf1f2d4394Makoto Onuki            resolver.update(uri, cv, null, null);
81177aabd951992057a543f1699150971bf1f2d4394Makoto Onuki        }
81277aabd951992057a543f1699150971bf1f2d4394Makoto Onuki
81377aabd951992057a543f1699150971bf1f2d4394Makoto Onuki        if (isMessagingController(account)) {
81477aabd951992057a543f1699150971bf1f2d4394Makoto Onuki            mLegacyController.processPendingActions(account.mId);
81577aabd951992057a543f1699150971bf1f2d4394Makoto Onuki        }
816b53b1501055cbf5040bfd7b88a9cda084574c398Marc Blank    }
817f9ab857a5599faac2896394180fcd4ed56b09941Andrew Stadler
818b53b1501055cbf5040bfd7b88a9cda084574c398Marc Blank    /**
819e87ff6c3cbbfc5e3636f9827b58820652e3ea1c5Todd Kennedy     * Moves messages to a new mailbox.
820b53b1501055cbf5040bfd7b88a9cda084574c398Marc Blank     *
821b53b1501055cbf5040bfd7b88a9cda084574c398Marc Blank     * This function has no callback, no result reporting, because the desired outcome
822b53b1501055cbf5040bfd7b88a9cda084574c398Marc Blank     * is reflected entirely by changes to one or more cursors.
823b53b1501055cbf5040bfd7b88a9cda084574c398Marc Blank     *
824e87ff6c3cbbfc5e3636f9827b58820652e3ea1c5Todd Kennedy     * Note this method assumes all of the given message and mailbox IDs belong to the same
82511aea1efe45b23c1217b31be029a527c9dc460b8Makoto Onuki     * account.
82611aea1efe45b23c1217b31be029a527c9dc460b8Makoto Onuki     *
827e87ff6c3cbbfc5e3636f9827b58820652e3ea1c5Todd Kennedy     * @param messageIds IDs of the messages that are to be moved
828e87ff6c3cbbfc5e3636f9827b58820652e3ea1c5Todd Kennedy     * @param newMailboxId ID of the new mailbox that the messages will be moved to
829e87ff6c3cbbfc5e3636f9827b58820652e3ea1c5Todd Kennedy     * @return an asynchronous task that executes the move (for testing only)
830b53b1501055cbf5040bfd7b88a9cda084574c398Marc Blank     */
831e87ff6c3cbbfc5e3636f9827b58820652e3ea1c5Todd Kennedy    public EmailAsyncTask<Void, Void, Void> moveMessages(final long[] messageIds,
83211aea1efe45b23c1217b31be029a527c9dc460b8Makoto Onuki            final long newMailboxId) {
83311aea1efe45b23c1217b31be029a527c9dc460b8Makoto Onuki        if (messageIds == null || messageIds.length == 0) {
83454c91f00d7f967690a80b992062e75c40182d088Makoto Onuki            throw new IllegalArgumentException();
83511aea1efe45b23c1217b31be029a527c9dc460b8Makoto Onuki        }
836e87ff6c3cbbfc5e3636f9827b58820652e3ea1c5Todd Kennedy        return EmailAsyncTask.runAsyncParallel(new Runnable() {
837b53b1501055cbf5040bfd7b88a9cda084574c398Marc Blank            public void run() {
83811aea1efe45b23c1217b31be029a527c9dc460b8Makoto Onuki                Account account = Account.getAccountForMessageId(mProviderContext, messageIds[0]);
839b53b1501055cbf5040bfd7b88a9cda084574c398Marc Blank                if (account != null) {
840b53b1501055cbf5040bfd7b88a9cda084574c398Marc Blank                    ContentValues cv = new ContentValues();
84111aea1efe45b23c1217b31be029a527c9dc460b8Makoto Onuki                    cv.put(EmailContent.MessageColumns.MAILBOX_KEY, newMailboxId);
84211aea1efe45b23c1217b31be029a527c9dc460b8Makoto Onuki                    ContentResolver resolver = mProviderContext.getContentResolver();
84311aea1efe45b23c1217b31be029a527c9dc460b8Makoto Onuki                    for (long messageId : messageIds) {
84411aea1efe45b23c1217b31be029a527c9dc460b8Makoto Onuki                        Uri uri = ContentUris.withAppendedId(
84511aea1efe45b23c1217b31be029a527c9dc460b8Makoto Onuki                                EmailContent.Message.SYNCED_CONTENT_URI, messageId);
84611aea1efe45b23c1217b31be029a527c9dc460b8Makoto Onuki                        resolver.update(uri, cv, null, null);
84711aea1efe45b23c1217b31be029a527c9dc460b8Makoto Onuki                    }
848b53b1501055cbf5040bfd7b88a9cda084574c398Marc Blank                    if (isMessagingController(account)) {
849b53b1501055cbf5040bfd7b88a9cda084574c398Marc Blank                        mLegacyController.processPendingActions(account.mId);
850b53b1501055cbf5040bfd7b88a9cda084574c398Marc Blank                    }
8516c21942ec45f561d711b3d74ecca8e62afb735c4Andrew Stadler                }
852b53b1501055cbf5040bfd7b88a9cda084574c398Marc Blank            }
853b53b1501055cbf5040bfd7b88a9cda084574c398Marc Blank        });
8547c3cca80a0b4dacb7bcb48c65a9999138b5df82bAndrew Stadler    }
8557c3cca80a0b4dacb7bcb48c65a9999138b5df82bAndrew Stadler
8567c3cca80a0b4dacb7bcb48c65a9999138b5df82bAndrew Stadler    /**
8576ae38e4a3937898c5d3fec11a9dc2fb4bab8eab6Andrew Stadler     * Set/clear the unread status of a message
8586ae38e4a3937898c5d3fec11a9dc2fb4bab8eab6Andrew Stadler     *
8596ae38e4a3937898c5d3fec11a9dc2fb4bab8eab6Andrew Stadler     * @param messageId the message to update
8606ae38e4a3937898c5d3fec11a9dc2fb4bab8eab6Andrew Stadler     * @param isRead the new value for the isRead flag
8616ae38e4a3937898c5d3fec11a9dc2fb4bab8eab6Andrew Stadler     */
862b81a31b29b22b1b11e8ad636638d2b8213e9f199Marc Blank    public void setMessageReadSync(long messageId, boolean isRead) {
863b81a31b29b22b1b11e8ad636638d2b8213e9f199Marc Blank        setMessageBooleanSync(messageId, EmailContent.MessageColumns.FLAG_READ, isRead);
864b81a31b29b22b1b11e8ad636638d2b8213e9f199Marc Blank    }
865b81a31b29b22b1b11e8ad636638d2b8213e9f199Marc Blank
866b81a31b29b22b1b11e8ad636638d2b8213e9f199Marc Blank    /**
867b81a31b29b22b1b11e8ad636638d2b8213e9f199Marc Blank     * Set/clear the unread status of a message from UI thread
868b81a31b29b22b1b11e8ad636638d2b8213e9f199Marc Blank     *
869b81a31b29b22b1b11e8ad636638d2b8213e9f199Marc Blank     * @param messageId the message to update
870b81a31b29b22b1b11e8ad636638d2b8213e9f199Marc Blank     * @param isRead the new value for the isRead flag
871b81a31b29b22b1b11e8ad636638d2b8213e9f199Marc Blank     * @return the EmailAsyncTask created
872b81a31b29b22b1b11e8ad636638d2b8213e9f199Marc Blank     */
873b81a31b29b22b1b11e8ad636638d2b8213e9f199Marc Blank    public EmailAsyncTask<Void, Void, Void> setMessageRead(final long messageId,
874b81a31b29b22b1b11e8ad636638d2b8213e9f199Marc Blank            final boolean isRead) {
875b81a31b29b22b1b11e8ad636638d2b8213e9f199Marc Blank        return EmailAsyncTask.runAsyncParallel(new Runnable() {
876b81a31b29b22b1b11e8ad636638d2b8213e9f199Marc Blank            @Override
877b81a31b29b22b1b11e8ad636638d2b8213e9f199Marc Blank            public void run() {
878b81a31b29b22b1b11e8ad636638d2b8213e9f199Marc Blank                setMessageBooleanSync(messageId, EmailContent.MessageColumns.FLAG_READ, isRead);
879b81a31b29b22b1b11e8ad636638d2b8213e9f199Marc Blank            }});
8806ae38e4a3937898c5d3fec11a9dc2fb4bab8eab6Andrew Stadler    }
8816ae38e4a3937898c5d3fec11a9dc2fb4bab8eab6Andrew Stadler
8826ae38e4a3937898c5d3fec11a9dc2fb4bab8eab6Andrew Stadler    /**
88350a092c3d6155fcd704990450640cf159072b1a2Marc Blank     * Update a message record and ping MessagingController, if necessary
88450a092c3d6155fcd704990450640cf159072b1a2Marc Blank     *
88550a092c3d6155fcd704990450640cf159072b1a2Marc Blank     * @param messageId the message to update
88650a092c3d6155fcd704990450640cf159072b1a2Marc Blank     * @param cv the ContentValues used in the update
88750a092c3d6155fcd704990450640cf159072b1a2Marc Blank     */
88850a092c3d6155fcd704990450640cf159072b1a2Marc Blank    private void updateMessageSync(long messageId, ContentValues cv) {
88950a092c3d6155fcd704990450640cf159072b1a2Marc Blank        Uri uri = ContentUris.withAppendedId(EmailContent.Message.SYNCED_CONTENT_URI, messageId);
89050a092c3d6155fcd704990450640cf159072b1a2Marc Blank        mProviderContext.getContentResolver().update(uri, cv, null, null);
89150a092c3d6155fcd704990450640cf159072b1a2Marc Blank
89250a092c3d6155fcd704990450640cf159072b1a2Marc Blank        // Service runs automatically, MessagingController needs a kick
89350a092c3d6155fcd704990450640cf159072b1a2Marc Blank        long accountId = Account.getAccountIdForMessageId(mProviderContext, messageId);
89450a092c3d6155fcd704990450640cf159072b1a2Marc Blank        if (accountId == Account.NO_ACCOUNT) return;
89550a092c3d6155fcd704990450640cf159072b1a2Marc Blank        if (isMessagingController(accountId)) {
89650a092c3d6155fcd704990450640cf159072b1a2Marc Blank            mLegacyController.processPendingActions(accountId);
89750a092c3d6155fcd704990450640cf159072b1a2Marc Blank        }
89850a092c3d6155fcd704990450640cf159072b1a2Marc Blank    }
89950a092c3d6155fcd704990450640cf159072b1a2Marc Blank
90050a092c3d6155fcd704990450640cf159072b1a2Marc Blank    /**
90150a092c3d6155fcd704990450640cf159072b1a2Marc Blank     * Set the answered status of a message
90250a092c3d6155fcd704990450640cf159072b1a2Marc Blank     *
90350a092c3d6155fcd704990450640cf159072b1a2Marc Blank     * @param messageId the message to update
90450a092c3d6155fcd704990450640cf159072b1a2Marc Blank     * @return the AsyncTask that will execute the changes (for testing only)
90550a092c3d6155fcd704990450640cf159072b1a2Marc Blank     */
90650a092c3d6155fcd704990450640cf159072b1a2Marc Blank    public void setMessageAnsweredOrForwarded(final long messageId,
90750a092c3d6155fcd704990450640cf159072b1a2Marc Blank            final int flag) {
90850a092c3d6155fcd704990450640cf159072b1a2Marc Blank        EmailAsyncTask.runAsyncParallel(new Runnable() {
90950a092c3d6155fcd704990450640cf159072b1a2Marc Blank            public void run() {
91050a092c3d6155fcd704990450640cf159072b1a2Marc Blank                Message msg = Message.restoreMessageWithId(mProviderContext, messageId);
91150a092c3d6155fcd704990450640cf159072b1a2Marc Blank                if (msg == null) {
91250a092c3d6155fcd704990450640cf159072b1a2Marc Blank                    Log.w(Logging.LOG_TAG, "Unable to find source message for a reply/forward");
91350a092c3d6155fcd704990450640cf159072b1a2Marc Blank                    return;
91450a092c3d6155fcd704990450640cf159072b1a2Marc Blank                }
91550a092c3d6155fcd704990450640cf159072b1a2Marc Blank                ContentValues cv = new ContentValues();
91650a092c3d6155fcd704990450640cf159072b1a2Marc Blank                cv.put(MessageColumns.FLAGS, msg.mFlags | flag);
91750a092c3d6155fcd704990450640cf159072b1a2Marc Blank                updateMessageSync(messageId, cv);
91850a092c3d6155fcd704990450640cf159072b1a2Marc Blank            }
91950a092c3d6155fcd704990450640cf159072b1a2Marc Blank        });
92050a092c3d6155fcd704990450640cf159072b1a2Marc Blank    }
92150a092c3d6155fcd704990450640cf159072b1a2Marc Blank
92250a092c3d6155fcd704990450640cf159072b1a2Marc Blank    /**
923b81a31b29b22b1b11e8ad636638d2b8213e9f199Marc Blank     * Set/clear the favorite status of a message from UI thread
9246ae38e4a3937898c5d3fec11a9dc2fb4bab8eab6Andrew Stadler     *
9256ae38e4a3937898c5d3fec11a9dc2fb4bab8eab6Andrew Stadler     * @param messageId the message to update
9266ae38e4a3937898c5d3fec11a9dc2fb4bab8eab6Andrew Stadler     * @param isFavorite the new value for the isFavorite flag
927b81a31b29b22b1b11e8ad636638d2b8213e9f199Marc Blank     * @return the EmailAsyncTask created
9286ae38e4a3937898c5d3fec11a9dc2fb4bab8eab6Andrew Stadler     */
929b81a31b29b22b1b11e8ad636638d2b8213e9f199Marc Blank    public EmailAsyncTask<Void, Void, Void> setMessageFavorite(final long messageId,
9303b1cccf234cd95c3e1c4c568bc588ab06b06d3aaAndy Stadler            final boolean isFavorite) {
931b81a31b29b22b1b11e8ad636638d2b8213e9f199Marc Blank        return EmailAsyncTask.runAsyncParallel(new Runnable() {
932b81a31b29b22b1b11e8ad636638d2b8213e9f199Marc Blank            @Override
933b81a31b29b22b1b11e8ad636638d2b8213e9f199Marc Blank            public void run() {
934b81a31b29b22b1b11e8ad636638d2b8213e9f199Marc Blank                setMessageBooleanSync(messageId, EmailContent.MessageColumns.FLAG_FAVORITE,
935b81a31b29b22b1b11e8ad636638d2b8213e9f199Marc Blank                        isFavorite);
936b81a31b29b22b1b11e8ad636638d2b8213e9f199Marc Blank            }});
937b81a31b29b22b1b11e8ad636638d2b8213e9f199Marc Blank    }
938b81a31b29b22b1b11e8ad636638d2b8213e9f199Marc Blank    /**
939b81a31b29b22b1b11e8ad636638d2b8213e9f199Marc Blank     * Set/clear the favorite status of a message
940b81a31b29b22b1b11e8ad636638d2b8213e9f199Marc Blank     *
941b81a31b29b22b1b11e8ad636638d2b8213e9f199Marc Blank     * @param messageId the message to update
942b81a31b29b22b1b11e8ad636638d2b8213e9f199Marc Blank     * @param isFavorite the new value for the isFavorite flag
943b81a31b29b22b1b11e8ad636638d2b8213e9f199Marc Blank     */
944b81a31b29b22b1b11e8ad636638d2b8213e9f199Marc Blank    public void setMessageFavoriteSync(long messageId, boolean isFavorite) {
945b81a31b29b22b1b11e8ad636638d2b8213e9f199Marc Blank        setMessageBooleanSync(messageId, EmailContent.MessageColumns.FLAG_FAVORITE, isFavorite);
9463b1cccf234cd95c3e1c4c568bc588ab06b06d3aaAndy Stadler    }
947423206653fc1841153f6c6c00599a65d5c5f2191Andrew Stadler
9483b1cccf234cd95c3e1c4c568bc588ab06b06d3aaAndy Stadler    /**
9493b1cccf234cd95c3e1c4c568bc588ab06b06d3aaAndy Stadler     * Set/clear boolean columns of a message
9503b1cccf234cd95c3e1c4c568bc588ab06b06d3aaAndy Stadler     *
9513b1cccf234cd95c3e1c4c568bc588ab06b06d3aaAndy Stadler     * @param messageId the message to update
9523b1cccf234cd95c3e1c4c568bc588ab06b06d3aaAndy Stadler     * @param columnName the column to update
9533b1cccf234cd95c3e1c4c568bc588ab06b06d3aaAndy Stadler     * @param columnValue the new value for the column
9543b1cccf234cd95c3e1c4c568bc588ab06b06d3aaAndy Stadler     */
955b81a31b29b22b1b11e8ad636638d2b8213e9f199Marc Blank    private void setMessageBooleanSync(long messageId, String columnName, boolean columnValue) {
956b81a31b29b22b1b11e8ad636638d2b8213e9f199Marc Blank        ContentValues cv = new ContentValues();
957b81a31b29b22b1b11e8ad636638d2b8213e9f199Marc Blank        cv.put(columnName, columnValue);
958b81a31b29b22b1b11e8ad636638d2b8213e9f199Marc Blank        updateMessageSync(messageId, cv);
9596ae38e4a3937898c5d3fec11a9dc2fb4bab8eab6Andrew Stadler    }
9606ae38e4a3937898c5d3fec11a9dc2fb4bab8eab6Andrew Stadler
961b81a31b29b22b1b11e8ad636638d2b8213e9f199Marc Blank
962ed1dc9ee72410e07de24bc4a072d066981611a48Marc Blank    private static final HashMap<Long, SearchParams> sSearchParamsMap =
963ed1dc9ee72410e07de24bc4a072d066981611a48Marc Blank        new HashMap<Long, SearchParams>();
96429c89ad17a0983ddc4eb2b4922c93a53aff2d735Ben Komalo
965ed1dc9ee72410e07de24bc4a072d066981611a48Marc Blank    public void searchMore(long accountId) throws MessagingException {
966ed1dc9ee72410e07de24bc4a072d066981611a48Marc Blank        SearchParams params = sSearchParamsMap.get(accountId);
967ed1dc9ee72410e07de24bc4a072d066981611a48Marc Blank        if (params == null) return;
968ed1dc9ee72410e07de24bc4a072d066981611a48Marc Blank        params.mOffset += params.mLimit;
969ed1dc9ee72410e07de24bc4a072d066981611a48Marc Blank        searchMessages(accountId, params);
970ed1dc9ee72410e07de24bc4a072d066981611a48Marc Blank    }
97129c89ad17a0983ddc4eb2b4922c93a53aff2d735Ben Komalo
9726ae38e4a3937898c5d3fec11a9dc2fb4bab8eab6Andrew Stadler    /**
973627bc6ed57ee06cc588e64ff959bfd7870b659b6Marc Blank     * Search for messages on the (IMAP) server; do not call this on the UI thread!
974627bc6ed57ee06cc588e64ff959bfd7870b659b6Marc Blank     * @param accountId the id of the account to be searched
975627bc6ed57ee06cc588e64ff959bfd7870b659b6Marc Blank     * @param searchParams the parameters for this search
976627bc6ed57ee06cc588e64ff959bfd7870b659b6Marc Blank     * @throws MessagingException
977627bc6ed57ee06cc588e64ff959bfd7870b659b6Marc Blank     */
9785a9c95f94eaaed326d3116b539b7005ab4f3f8c7Marc Blank    public int searchMessages(final long accountId, final SearchParams searchParams)
979627bc6ed57ee06cc588e64ff959bfd7870b659b6Marc Blank            throws MessagingException {
980627bc6ed57ee06cc588e64ff959bfd7870b659b6Marc Blank        // Find/create our search mailbox
981627bc6ed57ee06cc588e64ff959bfd7870b659b6Marc Blank        Mailbox searchMailbox = getSearchMailbox(accountId);
9825a9c95f94eaaed326d3116b539b7005ab4f3f8c7Marc Blank        if (searchMailbox == null) return 0;
983627bc6ed57ee06cc588e64ff959bfd7870b659b6Marc Blank        final long searchMailboxId = searchMailbox.mId;
984ed1dc9ee72410e07de24bc4a072d066981611a48Marc Blank        // Save this away (per account)
985ed1dc9ee72410e07de24bc4a072d066981611a48Marc Blank        sSearchParamsMap.put(accountId, searchParams);
986bad39b2e00dcd9127f5e0aed96ab31cf0817be76Marc Blank
987bad39b2e00dcd9127f5e0aed96ab31cf0817be76Marc Blank        if (searchParams.mOffset == 0) {
988bad39b2e00dcd9127f5e0aed96ab31cf0817be76Marc Blank            // Delete existing contents of search mailbox
989bad39b2e00dcd9127f5e0aed96ab31cf0817be76Marc Blank            ContentResolver resolver = mContext.getContentResolver();
990bad39b2e00dcd9127f5e0aed96ab31cf0817be76Marc Blank            resolver.delete(Message.CONTENT_URI, Message.MAILBOX_KEY + "=" + searchMailboxId,
991bad39b2e00dcd9127f5e0aed96ab31cf0817be76Marc Blank                    null);
992bad39b2e00dcd9127f5e0aed96ab31cf0817be76Marc Blank            ContentValues cv = new ContentValues();
993bad39b2e00dcd9127f5e0aed96ab31cf0817be76Marc Blank            // For now, use the actual query as the name of the mailbox
994bad39b2e00dcd9127f5e0aed96ab31cf0817be76Marc Blank            cv.put(Mailbox.DISPLAY_NAME, searchParams.mFilter);
995bad39b2e00dcd9127f5e0aed96ab31cf0817be76Marc Blank            resolver.update(ContentUris.withAppendedId(Mailbox.CONTENT_URI, searchMailboxId),
996bad39b2e00dcd9127f5e0aed96ab31cf0817be76Marc Blank                    cv, null, null);
997bad39b2e00dcd9127f5e0aed96ab31cf0817be76Marc Blank        }
998bad39b2e00dcd9127f5e0aed96ab31cf0817be76Marc Blank
999c60b8d0529168edecf2376a6f421a0ae1e10fe29Marc Blank        IEmailService service = getServiceForAccount(accountId);
1000c60b8d0529168edecf2376a6f421a0ae1e10fe29Marc Blank        if (service != null) {
1001c60b8d0529168edecf2376a6f421a0ae1e10fe29Marc Blank            // Service implementation
1002c60b8d0529168edecf2376a6f421a0ae1e10fe29Marc Blank            try {
10035a9c95f94eaaed326d3116b539b7005ab4f3f8c7Marc Blank                return service.searchMessages(accountId, searchParams, searchMailboxId);
1004c60b8d0529168edecf2376a6f421a0ae1e10fe29Marc Blank            } catch (RemoteException e) {
1005c60b8d0529168edecf2376a6f421a0ae1e10fe29Marc Blank                // TODO Change exception handling to be consistent with however this method
1006c60b8d0529168edecf2376a6f421a0ae1e10fe29Marc Blank                // is implemented for other protocols
100778684ccc795c0d5211dfc04a834cb452dccb1058Marc Blank                Log.e("searchMessages", "RemoteException", e);
10085a9c95f94eaaed326d3116b539b7005ab4f3f8c7Marc Blank                return 0;
1009c60b8d0529168edecf2376a6f421a0ae1e10fe29Marc Blank            }
1010627bc6ed57ee06cc588e64ff959bfd7870b659b6Marc Blank        } else {
1011627bc6ed57ee06cc588e64ff959bfd7870b659b6Marc Blank            // This is the actual mailbox we'll be searching
1012627bc6ed57ee06cc588e64ff959bfd7870b659b6Marc Blank            Mailbox actualMailbox = Mailbox.restoreMailboxWithId(mContext, searchParams.mMailboxId);
1013f62fd3fd177a79579cde30e43a85eb9709eb348bBen Komalo            if (actualMailbox == null) {
1014f62fd3fd177a79579cde30e43a85eb9709eb348bBen Komalo                Log.e(Logging.LOG_TAG, "Unable to find mailbox " + searchParams.mMailboxId
1015f62fd3fd177a79579cde30e43a85eb9709eb348bBen Komalo                        + " to search in with " + searchParams);
10165a9c95f94eaaed326d3116b539b7005ab4f3f8c7Marc Blank                return 0;
1017f62fd3fd177a79579cde30e43a85eb9709eb348bBen Komalo            }
1018627bc6ed57ee06cc588e64ff959bfd7870b659b6Marc Blank            // Do the search
1019627bc6ed57ee06cc588e64ff959bfd7870b659b6Marc Blank            if (Email.DEBUG) {
1020627bc6ed57ee06cc588e64ff959bfd7870b659b6Marc Blank                Log.d(Logging.LOG_TAG, "Search: " + searchParams.mFilter);
1021627bc6ed57ee06cc588e64ff959bfd7870b659b6Marc Blank            }
10225a9c95f94eaaed326d3116b539b7005ab4f3f8c7Marc Blank            return mLegacyController.searchMailbox(accountId, searchParams, searchMailboxId);
1023c60b8d0529168edecf2376a6f421a0ae1e10fe29Marc Blank        }
1024c60b8d0529168edecf2376a6f421a0ae1e10fe29Marc Blank    }
1025c60b8d0529168edecf2376a6f421a0ae1e10fe29Marc Blank
1026c60b8d0529168edecf2376a6f421a0ae1e10fe29Marc Blank    /**
10275de54008e58ff63d388e4d448b50a47950990e22Marc Blank     * Respond to a meeting invitation.
10285de54008e58ff63d388e4d448b50a47950990e22Marc Blank     *
10295de54008e58ff63d388e4d448b50a47950990e22Marc Blank     * @param messageId the id of the invitation being responded to
10305de54008e58ff63d388e4d448b50a47950990e22Marc Blank     * @param response the code representing the response to the invitation
10315de54008e58ff63d388e4d448b50a47950990e22Marc Blank     */
10323f545a4060982b8a5d715905c7818d59056c1ee0Makoto Onuki    public void sendMeetingResponse(final long messageId, final int response) {
10335de54008e58ff63d388e4d448b50a47950990e22Marc Blank         // Split here for target type (Service or MessagingController)
10345de54008e58ff63d388e4d448b50a47950990e22Marc Blank        IEmailService service = getServiceForMessage(messageId);
10355de54008e58ff63d388e4d448b50a47950990e22Marc Blank        if (service != null) {
10365de54008e58ff63d388e4d448b50a47950990e22Marc Blank            // Service implementation
10375de54008e58ff63d388e4d448b50a47950990e22Marc Blank            try {
10385de54008e58ff63d388e4d448b50a47950990e22Marc Blank                service.sendMeetingResponse(messageId, response);
10395de54008e58ff63d388e4d448b50a47950990e22Marc Blank            } catch (RemoteException e) {
10405de54008e58ff63d388e4d448b50a47950990e22Marc Blank                // TODO Change exception handling to be consistent with however this method
10415de54008e58ff63d388e4d448b50a47950990e22Marc Blank                // is implemented for other protocols
10425de54008e58ff63d388e4d448b50a47950990e22Marc Blank                Log.e("onDownloadAttachment", "RemoteException", e);
10435de54008e58ff63d388e4d448b50a47950990e22Marc Blank            }
10445de54008e58ff63d388e4d448b50a47950990e22Marc Blank        }
10455de54008e58ff63d388e4d448b50a47950990e22Marc Blank    }
10465de54008e58ff63d388e4d448b50a47950990e22Marc Blank
10475de54008e58ff63d388e4d448b50a47950990e22Marc Blank    /**
1048a98de7e55e91229c35352b496fad3fbb108a9609Andrew Stadler     * Request that an attachment be loaded.  It will be stored at a location controlled
1049a98de7e55e91229c35352b496fad3fbb108a9609Andrew Stadler     * by the AttachmentProvider.
10506ae38e4a3937898c5d3fec11a9dc2fb4bab8eab6Andrew Stadler     *
10516ae38e4a3937898c5d3fec11a9dc2fb4bab8eab6Andrew Stadler     * @param attachmentId the attachment to load
10526ae38e4a3937898c5d3fec11a9dc2fb4bab8eab6Andrew Stadler     * @param messageId the owner message
1053a98de7e55e91229c35352b496fad3fbb108a9609Andrew Stadler     * @param accountId the owner account
10546ae38e4a3937898c5d3fec11a9dc2fb4bab8eab6Andrew Stadler     */
1055b8efc2d048175f4d12630a036f9aed4e9be0f982Makoto Onuki    public void loadAttachment(final long attachmentId, final long messageId,
10563f545a4060982b8a5d715905c7818d59056c1ee0Makoto Onuki            final long accountId) {
10579ef6f645f57d869a600113f555389b5d5e368c21Mihai Preda        Attachment attachInfo = Attachment.restoreAttachmentWithId(mProviderContext, attachmentId);
1058edd1c9ac7e92da3ca5459f4c7c3154b98ff68eacTodd Kennedy        if (attachInfo == null) {
1059edd1c9ac7e92da3ca5459f4c7c3154b98ff68eacTodd Kennedy            return;
1060edd1c9ac7e92da3ca5459f4c7c3154b98ff68eacTodd Kennedy        }
1061edd1c9ac7e92da3ca5459f4c7c3154b98ff68eacTodd Kennedy
10624dcb1c5fdaacc40309b77af2a32532bc60218523Marc Blank        if (Utility.attachmentExists(mProviderContext, attachInfo)) {
10633f1ac4da947f426775c9546f2e37206f58ce1a6eAndrew Stadler            // The attachment has already been downloaded, so we will just "pretend" to download it
10647894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank            // This presumably is for POP3 messages
10653f1ac4da947f426775c9546f2e37206f58ce1a6eAndrew Stadler            synchronized (mListeners) {
10663f1ac4da947f426775c9546f2e37206f58ce1a6eAndrew Stadler                for (Result listener : mListeners) {
1067aef9515ee70f1f0b6cc4fa601078597b55831331Makoto Onuki                    listener.loadAttachmentCallback(null, accountId, messageId, attachmentId, 0);
10683f1ac4da947f426775c9546f2e37206f58ce1a6eAndrew Stadler                }
10693f1ac4da947f426775c9546f2e37206f58ce1a6eAndrew Stadler                for (Result listener : mListeners) {
1070aef9515ee70f1f0b6cc4fa601078597b55831331Makoto Onuki                    listener.loadAttachmentCallback(null, accountId, messageId, attachmentId, 100);
10713f1ac4da947f426775c9546f2e37206f58ce1a6eAndrew Stadler                }
10723f1ac4da947f426775c9546f2e37206f58ce1a6eAndrew Stadler            }
10733f1ac4da947f426775c9546f2e37206f58ce1a6eAndrew Stadler            return;
10743f1ac4da947f426775c9546f2e37206f58ce1a6eAndrew Stadler        }
10753f1ac4da947f426775c9546f2e37206f58ce1a6eAndrew Stadler
10767894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank        // Flag the attachment as needing download at the user's request
10777894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank        ContentValues cv = new ContentValues();
10787894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank        cv.put(Attachment.FLAGS, attachInfo.mFlags | Attachment.FLAG_DOWNLOAD_USER_REQUEST);
1079e29189e3eeea9c629777b3deed6ea2be67caa737Andy Stadler        attachInfo.update(mProviderContext, cv);
10806ae38e4a3937898c5d3fec11a9dc2fb4bab8eab6Andrew Stadler    }
10816ae38e4a3937898c5d3fec11a9dc2fb4bab8eab6Andrew Stadler
10826ae38e4a3937898c5d3fec11a9dc2fb4bab8eab6Andrew Stadler    /**
10836ae38e4a3937898c5d3fec11a9dc2fb4bab8eab6Andrew Stadler     * For a given message id, return a service proxy if applicable, or null.
10846ae38e4a3937898c5d3fec11a9dc2fb4bab8eab6Andrew Stadler     *
10856ae38e4a3937898c5d3fec11a9dc2fb4bab8eab6Andrew Stadler     * @param messageId the message of interest
10866ae38e4a3937898c5d3fec11a9dc2fb4bab8eab6Andrew Stadler     * @result service proxy, or null if n/a
10876ae38e4a3937898c5d3fec11a9dc2fb4bab8eab6Andrew Stadler     */
10886ae38e4a3937898c5d3fec11a9dc2fb4bab8eab6Andrew Stadler    private IEmailService getServiceForMessage(long messageId) {
10896ae38e4a3937898c5d3fec11a9dc2fb4bab8eab6Andrew Stadler        // TODO make this more efficient, caching the account, smaller lookup here, etc.
10906ae38e4a3937898c5d3fec11a9dc2fb4bab8eab6Andrew Stadler        Message message = Message.restoreMessageWithId(mProviderContext, messageId);
10917e1de3c484ad0faa5f2d789649c1b4979e071044Makoto Onuki        if (message == null) {
10927e1de3c484ad0faa5f2d789649c1b4979e071044Makoto Onuki            return null;
10937e1de3c484ad0faa5f2d789649c1b4979e071044Makoto Onuki        }
10948f7f93a7b36d873d5adba65f4da54819880c0285Andrew Stadler        return getServiceForAccount(message.mAccountKey);
10958f7f93a7b36d873d5adba65f4da54819880c0285Andrew Stadler    }
10968f7f93a7b36d873d5adba65f4da54819880c0285Andrew Stadler
10978f7f93a7b36d873d5adba65f4da54819880c0285Andrew Stadler    /**
10988f7f93a7b36d873d5adba65f4da54819880c0285Andrew Stadler     * For a given account id, return a service proxy if applicable, or null.
10998f7f93a7b36d873d5adba65f4da54819880c0285Andrew Stadler     *
11008f7f93a7b36d873d5adba65f4da54819880c0285Andrew Stadler     * @param accountId the message of interest
11018f7f93a7b36d873d5adba65f4da54819880c0285Andrew Stadler     * @result service proxy, or null if n/a
11028f7f93a7b36d873d5adba65f4da54819880c0285Andrew Stadler     */
11038f7f93a7b36d873d5adba65f4da54819880c0285Andrew Stadler    private IEmailService getServiceForAccount(long accountId) {
11048d8f86e899165772a7d91250b98dfc3c0d78b538Marc Blank        if (isMessagingController(accountId)) return null;
11058d8f86e899165772a7d91250b98dfc3c0d78b538Marc Blank        return getExchangeEmailService();
11066ae38e4a3937898c5d3fec11a9dc2fb4bab8eab6Andrew Stadler    }
11076ae38e4a3937898c5d3fec11a9dc2fb4bab8eab6Andrew Stadler
1108c7b6145c125cf0f9bdd6426db11ee796fe8b1cf1Makoto Onuki    private IEmailService getExchangeEmailService() {
110966a47b8dac5e97e37c30b928bc5a227d74baada9Marc Blank        return EmailServiceUtils.getExchangeService(mContext, mServiceCallback);
1110c7b6145c125cf0f9bdd6426db11ee796fe8b1cf1Makoto Onuki    }
1111c7b6145c125cf0f9bdd6426db11ee796fe8b1cf1Makoto Onuki
11126ae38e4a3937898c5d3fec11a9dc2fb4bab8eab6Andrew Stadler    /**
1113bcec088320fc56cd5b5e1bbc706e4b7e304b95bfAndrew Stadler     * Simple helper to determine if legacy MessagingController should be used
1114bcec088320fc56cd5b5e1bbc706e4b7e304b95bfAndrew Stadler     */
1115f5418f1f93b02e7fab9f15eb201800b65510998eMarc Blank    public boolean isMessagingController(Account account) {
1116e892f6f978c18d1d43f94681f4d73f36d5f8a2cdMarc Blank        if (account == null) return false;
11178d8f86e899165772a7d91250b98dfc3c0d78b538Marc Blank        return isMessagingController(account.mId);
11188d8f86e899165772a7d91250b98dfc3c0d78b538Marc Blank    }
11197b0b463477ef7172bd4b9e19c6338634ebedd8eeAndrew Stadler
11208d8f86e899165772a7d91250b98dfc3c0d78b538Marc Blank    public boolean isMessagingController(long accountId) {
11218d8f86e899165772a7d91250b98dfc3c0d78b538Marc Blank        Boolean isLegacyController = mLegacyControllerMap.get(accountId);
11228d8f86e899165772a7d91250b98dfc3c0d78b538Marc Blank        if (isLegacyController == null) {
11238d8f86e899165772a7d91250b98dfc3c0d78b538Marc Blank            String protocol = Account.getProtocol(mProviderContext, accountId);
11248d8f86e899165772a7d91250b98dfc3c0d78b538Marc Blank            isLegacyController = ("pop3".equals(protocol) || "imap".equals(protocol));
11258d8f86e899165772a7d91250b98dfc3c0d78b538Marc Blank            mLegacyControllerMap.put(accountId, isLegacyController);
11268d8f86e899165772a7d91250b98dfc3c0d78b538Marc Blank        }
11278d8f86e899165772a7d91250b98dfc3c0d78b538Marc Blank        return isLegacyController;
1128bcec088320fc56cd5b5e1bbc706e4b7e304b95bfAndrew Stadler    }
11297b0b463477ef7172bd4b9e19c6338634ebedd8eeAndrew Stadler
1130bcec088320fc56cd5b5e1bbc706e4b7e304b95bfAndrew Stadler    /**
1131954bcd45b0c9febfb41bb0dc3dde2eca7559a290Makoto Onuki     * Delete an account.
1132768aff4c88a13c1663b1c33b1ab03637e5ce375bMakoto Onuki     */
1133954bcd45b0c9febfb41bb0dc3dde2eca7559a290Makoto Onuki    public void deleteAccount(final long accountId) {
11344e4aba9ebc43c6a83190f3a883fa05bb7d5100b3Marc Blank        EmailAsyncTask.runAsyncParallel(new Runnable() {
11354e4aba9ebc43c6a83190f3a883fa05bb7d5100b3Marc Blank            @Override
1136954bcd45b0c9febfb41bb0dc3dde2eca7559a290Makoto Onuki            public void run() {
1137e29189e3eeea9c629777b3deed6ea2be67caa737Andy Stadler                deleteAccountSync(accountId, mProviderContext);
1138291b90fb24214f767485c427739d25842936dff7Makoto Onuki            }
1139291b90fb24214f767485c427739d25842936dff7Makoto Onuki        });
1140291b90fb24214f767485c427739d25842936dff7Makoto Onuki    }
1141291b90fb24214f767485c427739d25842936dff7Makoto Onuki
1142291b90fb24214f767485c427739d25842936dff7Makoto Onuki    /**
1143a50fc99b0c433f0cde31ba1c7ab87fb9ea86345dTodd Kennedy     * Delete an account synchronously.
1144291b90fb24214f767485c427739d25842936dff7Makoto Onuki     */
114502d59d21949a77c60859b615312f02e6d8003490Marc Blank    public void deleteAccountSync(long accountId, Context context) {
1146291b90fb24214f767485c427739d25842936dff7Makoto Onuki        try {
11478d8f86e899165772a7d91250b98dfc3c0d78b538Marc Blank            mLegacyControllerMap.remove(accountId);
1148291b90fb24214f767485c427739d25842936dff7Makoto Onuki            // Get the account URI.
114902d59d21949a77c60859b615312f02e6d8003490Marc Blank            final Account account = Account.restoreAccountWithId(context, accountId);
1150291b90fb24214f767485c427739d25842936dff7Makoto Onuki            if (account == null) {
1151291b90fb24214f767485c427739d25842936dff7Makoto Onuki                return; // Already deleted?
1152291b90fb24214f767485c427739d25842936dff7Makoto Onuki            }
1153954bcd45b0c9febfb41bb0dc3dde2eca7559a290Makoto Onuki
11542a33dde4d16f0468890a4852f5e4017a973a2342Marc Blank            // Delete account data, attachments, PIM data, etc.
11552a33dde4d16f0468890a4852f5e4017a973a2342Marc Blank            deleteSyncedDataSync(accountId);
11562a33dde4d16f0468890a4852f5e4017a973a2342Marc Blank
11572a33dde4d16f0468890a4852f5e4017a973a2342Marc Blank            // Now delete the account itself
11582a33dde4d16f0468890a4852f5e4017a973a2342Marc Blank            Uri uri = ContentUris.withAppendedId(Account.CONTENT_URI, accountId);
1159e29189e3eeea9c629777b3deed6ea2be67caa737Andy Stadler            context.getContentResolver().delete(uri, null, null);
1160954bcd45b0c9febfb41bb0dc3dde2eca7559a290Makoto Onuki
11611ecfb5311b0315edf86a450398a8712dbfd86772Ben Komalo            // For unit tests, don't run backup, security, and ui pieces.
11621ecfb5311b0315edf86a450398a8712dbfd86772Ben Komalo            if (mInUnitTests) {
11631ecfb5311b0315edf86a450398a8712dbfd86772Ben Komalo                return;
11641ecfb5311b0315edf86a450398a8712dbfd86772Ben Komalo            }
1165954bcd45b0c9febfb41bb0dc3dde2eca7559a290Makoto Onuki
11664e4aba9ebc43c6a83190f3a883fa05bb7d5100b3Marc Blank            // Clean up
11674e4aba9ebc43c6a83190f3a883fa05bb7d5100b3Marc Blank            AccountBackupRestore.backup(context);
116802d59d21949a77c60859b615312f02e6d8003490Marc Blank            SecurityPolicy.getInstance(context).reducePolicies();
11692959a7e073c87e2fa5fab42ec543b352a91cf187Andy Stadler            Email.setServicesEnabledSync(context);
11704e4aba9ebc43c6a83190f3a883fa05bb7d5100b3Marc Blank            Email.setNotifyUiAccountsChanged(true);
11714e4aba9ebc43c6a83190f3a883fa05bb7d5100b3Marc Blank            MailService.actionReschedule(context);
1172291b90fb24214f767485c427739d25842936dff7Makoto Onuki        } catch (Exception e) {
117331d9acbf0623872f9d4a2b3210b5970854b654c7Marc Blank            Log.w(Logging.LOG_TAG, "Exception while deleting account", e);
1174291b90fb24214f767485c427739d25842936dff7Makoto Onuki        }
1175768aff4c88a13c1663b1c33b1ab03637e5ce375bMakoto Onuki    }
1176768aff4c88a13c1663b1c33b1ab03637e5ce375bMakoto Onuki
1177768aff4c88a13c1663b1c33b1ab03637e5ce375bMakoto Onuki    /**
1178e29189e3eeea9c629777b3deed6ea2be67caa737Andy Stadler     * Delete all synced data, but don't delete the actual account.  This is used when security
1179e29189e3eeea9c629777b3deed6ea2be67caa737Andy Stadler     * policy requirements are not met, and we don't want to reveal any synced data, but we do
1180e29189e3eeea9c629777b3deed6ea2be67caa737Andy Stadler     * wish to keep the account configured (e.g. to accept remote wipe commands).
1181e29189e3eeea9c629777b3deed6ea2be67caa737Andy Stadler     *
118269af769688acc28a20cdbad046cea2565456801cMarc Blank     * The only mailbox not deleted is the account mailbox (if any)
118369af769688acc28a20cdbad046cea2565456801cMarc Blank     * Also, clear the sync keys on the remaining account, since the data is gone.
1184d3a9ab946caef4cdc8ef81f7fd8f445fdba3782fAndy Stadler     *
1185e29189e3eeea9c629777b3deed6ea2be67caa737Andy Stadler     * SYNCHRONOUS - do not call from UI thread.
1186e29189e3eeea9c629777b3deed6ea2be67caa737Andy Stadler     *
1187e29189e3eeea9c629777b3deed6ea2be67caa737Andy Stadler     * @param accountId The account to wipe.
1188e29189e3eeea9c629777b3deed6ea2be67caa737Andy Stadler     */
1189e29189e3eeea9c629777b3deed6ea2be67caa737Andy Stadler    public void deleteSyncedDataSync(long accountId) {
1190e29189e3eeea9c629777b3deed6ea2be67caa737Andy Stadler        try {
1191e29189e3eeea9c629777b3deed6ea2be67caa737Andy Stadler            // Delete synced attachments
11928a574694606f0e5d781334d0d426fc379c51f3edMarc Blank            AttachmentUtilities.deleteAllAccountAttachmentFiles(mProviderContext,
11938a574694606f0e5d781334d0d426fc379c51f3edMarc Blank                    accountId);
1194d3a9ab946caef4cdc8ef81f7fd8f445fdba3782fAndy Stadler
1195d3a9ab946caef4cdc8ef81f7fd8f445fdba3782fAndy Stadler            // Delete synced email, leaving only an empty inbox.  We do this in two phases:
1196d3a9ab946caef4cdc8ef81f7fd8f445fdba3782fAndy Stadler            // 1. Delete all non-inbox mailboxes (which will delete all of their messages)
1197d3a9ab946caef4cdc8ef81f7fd8f445fdba3782fAndy Stadler            // 2. Delete all remaining messages (which will be the inbox messages)
1198d3a9ab946caef4cdc8ef81f7fd8f445fdba3782fAndy Stadler            ContentResolver resolver = mProviderContext.getContentResolver();
1199d3a9ab946caef4cdc8ef81f7fd8f445fdba3782fAndy Stadler            String[] accountIdArgs = new String[] { Long.toString(accountId) };
120069af769688acc28a20cdbad046cea2565456801cMarc Blank            resolver.delete(Mailbox.CONTENT_URI,
120169af769688acc28a20cdbad046cea2565456801cMarc Blank                    MAILBOXES_FOR_ACCOUNT_EXCEPT_ACCOUNT_MAILBOX_SELECTION,
1202d3a9ab946caef4cdc8ef81f7fd8f445fdba3782fAndy Stadler                    accountIdArgs);
1203d3a9ab946caef4cdc8ef81f7fd8f445fdba3782fAndy Stadler            resolver.delete(Message.CONTENT_URI, MESSAGES_FOR_ACCOUNT_SELECTION, accountIdArgs);
1204d3a9ab946caef4cdc8ef81f7fd8f445fdba3782fAndy Stadler
1205d3a9ab946caef4cdc8ef81f7fd8f445fdba3782fAndy Stadler            // Delete sync keys on remaining items
1206d3a9ab946caef4cdc8ef81f7fd8f445fdba3782fAndy Stadler            ContentValues cv = new ContentValues();
1207d3a9ab946caef4cdc8ef81f7fd8f445fdba3782fAndy Stadler            cv.putNull(Account.SYNC_KEY);
1208d3a9ab946caef4cdc8ef81f7fd8f445fdba3782fAndy Stadler            resolver.update(Account.CONTENT_URI, cv, Account.ID_SELECTION, accountIdArgs);
1209d3a9ab946caef4cdc8ef81f7fd8f445fdba3782fAndy Stadler            cv.clear();
1210d3a9ab946caef4cdc8ef81f7fd8f445fdba3782fAndy Stadler            cv.putNull(Mailbox.SYNC_KEY);
1211d3a9ab946caef4cdc8ef81f7fd8f445fdba3782fAndy Stadler            resolver.update(Mailbox.CONTENT_URI, cv,
1212d3a9ab946caef4cdc8ef81f7fd8f445fdba3782fAndy Stadler                    MAILBOXES_FOR_ACCOUNT_SELECTION, accountIdArgs);
1213d3a9ab946caef4cdc8ef81f7fd8f445fdba3782fAndy Stadler
121469af769688acc28a20cdbad046cea2565456801cMarc Blank            // Delete PIM data (contacts, calendar), stop syncs, etc. if applicable
1215e29189e3eeea9c629777b3deed6ea2be67caa737Andy Stadler            IEmailService service = getServiceForAccount(accountId);
1216e29189e3eeea9c629777b3deed6ea2be67caa737Andy Stadler            if (service != null) {
1217e29189e3eeea9c629777b3deed6ea2be67caa737Andy Stadler                service.deleteAccountPIMData(accountId);
1218e29189e3eeea9c629777b3deed6ea2be67caa737Andy Stadler            }
1219e29189e3eeea9c629777b3deed6ea2be67caa737Andy Stadler        } catch (Exception e) {
122031d9acbf0623872f9d4a2b3210b5970854b654c7Marc Blank            Log.w(Logging.LOG_TAG, "Exception while deleting account synced data", e);
1221e29189e3eeea9c629777b3deed6ea2be67caa737Andy Stadler        }
1222e29189e3eeea9c629777b3deed6ea2be67caa737Andy Stadler    }
1223e29189e3eeea9c629777b3deed6ea2be67caa737Andy Stadler
1224e29189e3eeea9c629777b3deed6ea2be67caa737Andy Stadler    /**
1225bcec088320fc56cd5b5e1bbc706e4b7e304b95bfAndrew Stadler     * Simple callback for synchronous commands.  For many commands, this can be largely ignored
1226bcec088320fc56cd5b5e1bbc706e4b7e304b95bfAndrew Stadler     * and the result is observed via provider cursors.  The callback will *not* necessarily be
1227bcec088320fc56cd5b5e1bbc706e4b7e304b95bfAndrew Stadler     * made from the UI thread, so you may need further handlers to safely make UI updates.
1228bcec088320fc56cd5b5e1bbc706e4b7e304b95bfAndrew Stadler     */
12293f545a4060982b8a5d715905c7818d59056c1ee0Makoto Onuki    public static abstract class Result {
1230e069246d48bf5f90dfe20dacd709d495173ebb47Makoto Onuki        private volatile boolean mRegistered;
1231e069246d48bf5f90dfe20dacd709d495173ebb47Makoto Onuki
123245e04b009d570235c542f3c97eaa7e1d00e6cc7bMakoto Onuki        protected void setRegistered(boolean registered) {
1233e069246d48bf5f90dfe20dacd709d495173ebb47Makoto Onuki            mRegistered = registered;
1234e069246d48bf5f90dfe20dacd709d495173ebb47Makoto Onuki        }
1235e069246d48bf5f90dfe20dacd709d495173ebb47Makoto Onuki
1236e069246d48bf5f90dfe20dacd709d495173ebb47Makoto Onuki        protected final boolean isRegistered() {
1237e069246d48bf5f90dfe20dacd709d495173ebb47Makoto Onuki            return mRegistered;
1238e069246d48bf5f90dfe20dacd709d495173ebb47Makoto Onuki        }
1239e069246d48bf5f90dfe20dacd709d495173ebb47Makoto Onuki
12407b0b463477ef7172bd4b9e19c6338634ebedd8eeAndrew Stadler        /**
12417b0b463477ef7172bd4b9e19c6338634ebedd8eeAndrew Stadler         * Callback for updateMailboxList
12427b0b463477ef7172bd4b9e19c6338634ebedd8eeAndrew Stadler         *
12437b0b463477ef7172bd4b9e19c6338634ebedd8eeAndrew Stadler         * @param result If null, the operation completed without error
12446ae38e4a3937898c5d3fec11a9dc2fb4bab8eab6Andrew Stadler         * @param accountId The account being operated on
12458f7f93a7b36d873d5adba65f4da54819880c0285Andrew Stadler         * @param progress 0 for "starting", 1..99 for updates (if needed in UI), 100 for complete
12467b0b463477ef7172bd4b9e19c6338634ebedd8eeAndrew Stadler         */
12478f7f93a7b36d873d5adba65f4da54819880c0285Andrew Stadler        public void updateMailboxListCallback(MessagingException result, long accountId,
12483f545a4060982b8a5d715905c7818d59056c1ee0Makoto Onuki                int progress) {
12493f545a4060982b8a5d715905c7818d59056c1ee0Makoto Onuki        }
12507b0b463477ef7172bd4b9e19c6338634ebedd8eeAndrew Stadler
1251bcec088320fc56cd5b5e1bbc706e4b7e304b95bfAndrew Stadler        /**
125246d7d7f1b6387d144c3f9e7c987418dc8f55fad4Andrew Stadler         * Callback for updateMailbox.  Note:  This looks a lot like checkMailCallback, but
125346d7d7f1b6387d144c3f9e7c987418dc8f55fad4Andrew Stadler         * it's a separate call used only by UI's, so we can keep things separate.
12547b0b463477ef7172bd4b9e19c6338634ebedd8eeAndrew Stadler         *
1255bcec088320fc56cd5b5e1bbc706e4b7e304b95bfAndrew Stadler         * @param result If null, the operation completed without error
12566ae38e4a3937898c5d3fec11a9dc2fb4bab8eab6Andrew Stadler         * @param accountId The account being operated on
12576ae38e4a3937898c5d3fec11a9dc2fb4bab8eab6Andrew Stadler         * @param mailboxId The mailbox being operated on
12588f7f93a7b36d873d5adba65f4da54819880c0285Andrew Stadler         * @param progress 0 for "starting", 1..99 for updates (if needed in UI), 100 for complete
125946d7d7f1b6387d144c3f9e7c987418dc8f55fad4Andrew Stadler         * @param numNewMessages the number of new messages delivered
1260bcec088320fc56cd5b5e1bbc706e4b7e304b95bfAndrew Stadler         */
12616ae38e4a3937898c5d3fec11a9dc2fb4bab8eab6Andrew Stadler        public void updateMailboxCallback(MessagingException result, long accountId,
1262c4cdb11d24c19428dd39f986b00c1a29e75e1505Todd Kennedy                long mailboxId, int progress, int numNewMessages, ArrayList<Long> addedMessages) {
12633f545a4060982b8a5d715905c7818d59056c1ee0Makoto Onuki        }
12646ae38e4a3937898c5d3fec11a9dc2fb4bab8eab6Andrew Stadler
12656ae38e4a3937898c5d3fec11a9dc2fb4bab8eab6Andrew Stadler        /**
1266df86adf87328a439347260331592509787020420Andrew Stadler         * Callback for loadMessageForView
1267df86adf87328a439347260331592509787020420Andrew Stadler         *
1268df86adf87328a439347260331592509787020420Andrew Stadler         * @param result if null, the attachment completed - if non-null, terminating with failure
1269df86adf87328a439347260331592509787020420Andrew Stadler         * @param messageId the message which contains the attachment
1270df86adf87328a439347260331592509787020420Andrew Stadler         * @param progress 0 for "starting", 1..99 for updates (if needed in UI), 100 for complete
1271df86adf87328a439347260331592509787020420Andrew Stadler         */
1272aef9515ee70f1f0b6cc4fa601078597b55831331Makoto Onuki        public void loadMessageForViewCallback(MessagingException result, long accountId,
1273aef9515ee70f1f0b6cc4fa601078597b55831331Makoto Onuki                long messageId, int progress) {
12743f545a4060982b8a5d715905c7818d59056c1ee0Makoto Onuki        }
1275df86adf87328a439347260331592509787020420Andrew Stadler
1276df86adf87328a439347260331592509787020420Andrew Stadler        /**
12776ae38e4a3937898c5d3fec11a9dc2fb4bab8eab6Andrew Stadler         * Callback for loadAttachment
12786ae38e4a3937898c5d3fec11a9dc2fb4bab8eab6Andrew Stadler         *
12796ae38e4a3937898c5d3fec11a9dc2fb4bab8eab6Andrew Stadler         * @param result if null, the attachment completed - if non-null, terminating with failure
12806ae38e4a3937898c5d3fec11a9dc2fb4bab8eab6Andrew Stadler         * @param messageId the message which contains the attachment
12816ae38e4a3937898c5d3fec11a9dc2fb4bab8eab6Andrew Stadler         * @param attachmentId the attachment being loaded
12826ae38e4a3937898c5d3fec11a9dc2fb4bab8eab6Andrew Stadler         * @param progress 0 for "starting", 1..99 for updates (if needed in UI), 100 for complete
12836ae38e4a3937898c5d3fec11a9dc2fb4bab8eab6Andrew Stadler         */
1284aef9515ee70f1f0b6cc4fa601078597b55831331Makoto Onuki        public void loadAttachmentCallback(MessagingException result, long accountId,
1285aef9515ee70f1f0b6cc4fa601078597b55831331Makoto Onuki                long messageId, long attachmentId, int progress) {
12863f545a4060982b8a5d715905c7818d59056c1ee0Makoto Onuki        }
128746d7d7f1b6387d144c3f9e7c987418dc8f55fad4Andrew Stadler
128846d7d7f1b6387d144c3f9e7c987418dc8f55fad4Andrew Stadler        /**
128946d7d7f1b6387d144c3f9e7c987418dc8f55fad4Andrew Stadler         * Callback for checkmail.  Note:  This looks a lot like updateMailboxCallback, but
129046d7d7f1b6387d144c3f9e7c987418dc8f55fad4Andrew Stadler         * it's a separate call used only by the automatic checker service, so we can keep
129146d7d7f1b6387d144c3f9e7c987418dc8f55fad4Andrew Stadler         * things separate.
129246d7d7f1b6387d144c3f9e7c987418dc8f55fad4Andrew Stadler         *
129346d7d7f1b6387d144c3f9e7c987418dc8f55fad4Andrew Stadler         * @param result If null, the operation completed without error
129446d7d7f1b6387d144c3f9e7c987418dc8f55fad4Andrew Stadler         * @param accountId The account being operated on
129546d7d7f1b6387d144c3f9e7c987418dc8f55fad4Andrew Stadler         * @param mailboxId The mailbox being operated on (may be unknown at start)
129646d7d7f1b6387d144c3f9e7c987418dc8f55fad4Andrew Stadler         * @param progress 0 for "starting", no updates, 100 for complete
129746d7d7f1b6387d144c3f9e7c987418dc8f55fad4Andrew Stadler         * @param tag the same tag that was passed to serviceCheckMail()
129846d7d7f1b6387d144c3f9e7c987418dc8f55fad4Andrew Stadler         */
129946d7d7f1b6387d144c3f9e7c987418dc8f55fad4Andrew Stadler        public void serviceCheckMailCallback(MessagingException result, long accountId,
13003f545a4060982b8a5d715905c7818d59056c1ee0Makoto Onuki                long mailboxId, int progress, long tag) {
13013f545a4060982b8a5d715905c7818d59056c1ee0Makoto Onuki        }
13024b41bae270ea4c49ec8403084db43ee9b37cdda4Andrew Stadler
13034b41bae270ea4c49ec8403084db43ee9b37cdda4Andrew Stadler        /**
13044b41bae270ea4c49ec8403084db43ee9b37cdda4Andrew Stadler         * Callback for sending pending messages.  This will be called once to start the
13054b41bae270ea4c49ec8403084db43ee9b37cdda4Andrew Stadler         * group, multiple times for messages, and once to complete the group.
13064b41bae270ea4c49ec8403084db43ee9b37cdda4Andrew Stadler         *
1307be1aa37dc516a9c3dd4af65b11f92a5951f5c5c3Makoto Onuki         * Unfortunately this callback works differently on SMTP and EAS.
1308be1aa37dc516a9c3dd4af65b11f92a5951f5c5c3Makoto Onuki         *
1309be1aa37dc516a9c3dd4af65b11f92a5951f5c5c3Makoto Onuki         * On SMTP:
1310be1aa37dc516a9c3dd4af65b11f92a5951f5c5c3Makoto Onuki         *
1311be1aa37dc516a9c3dd4af65b11f92a5951f5c5c3Makoto Onuki         * First, we get this.
1312be1aa37dc516a9c3dd4af65b11f92a5951f5c5c3Makoto Onuki         *  result == null, messageId == -1, progress == 0:     start batch send
1313be1aa37dc516a9c3dd4af65b11f92a5951f5c5c3Makoto Onuki         *
1314be1aa37dc516a9c3dd4af65b11f92a5951f5c5c3Makoto Onuki         * Then we get these callbacks per message.
1315be1aa37dc516a9c3dd4af65b11f92a5951f5c5c3Makoto Onuki         * (Exchange backend may skip "start sending one message".)
1316be1aa37dc516a9c3dd4af65b11f92a5951f5c5c3Makoto Onuki         *  result == null, messageId == xx, progress == 0:     start sending one message
1317be1aa37dc516a9c3dd4af65b11f92a5951f5c5c3Makoto Onuki         *  result == xxxx, messageId == xx, progress == 0;     failed sending one message
1318be1aa37dc516a9c3dd4af65b11f92a5951f5c5c3Makoto Onuki         *
1319be1aa37dc516a9c3dd4af65b11f92a5951f5c5c3Makoto Onuki         * Finally we get this.
1320be1aa37dc516a9c3dd4af65b11f92a5951f5c5c3Makoto Onuki         *  result == null, messageId == -1, progres == 100;    finish sending batch
1321be1aa37dc516a9c3dd4af65b11f92a5951f5c5c3Makoto Onuki         *
1322be1aa37dc516a9c3dd4af65b11f92a5951f5c5c3Makoto Onuki         * On EAS: Almost same as above, except:
1323be1aa37dc516a9c3dd4af65b11f92a5951f5c5c3Makoto Onuki         *
1324be1aa37dc516a9c3dd4af65b11f92a5951f5c5c3Makoto Onuki         * - There's no first ("start batch send") callback.
1325be1aa37dc516a9c3dd4af65b11f92a5951f5c5c3Makoto Onuki         * - accountId is always -1.
1326be1aa37dc516a9c3dd4af65b11f92a5951f5c5c3Makoto Onuki         *
13274b41bae270ea4c49ec8403084db43ee9b37cdda4Andrew Stadler         * @param result If null, the operation completed without error
13284b41bae270ea4c49ec8403084db43ee9b37cdda4Andrew Stadler         * @param accountId The account being operated on
13294b41bae270ea4c49ec8403084db43ee9b37cdda4Andrew Stadler         * @param messageId The being sent (may be unknown at start)
13304b41bae270ea4c49ec8403084db43ee9b37cdda4Andrew Stadler         * @param progress 0 for "starting", 100 for complete
13314b41bae270ea4c49ec8403084db43ee9b37cdda4Andrew Stadler         */
13324b41bae270ea4c49ec8403084db43ee9b37cdda4Andrew Stadler        public void sendMailCallback(MessagingException result, long accountId,
13333f545a4060982b8a5d715905c7818d59056c1ee0Makoto Onuki                long messageId, int progress) {
13343f545a4060982b8a5d715905c7818d59056c1ee0Makoto Onuki        }
1335bcec088320fc56cd5b5e1bbc706e4b7e304b95bfAndrew Stadler    }
13367b0b463477ef7172bd4b9e19c6338634ebedd8eeAndrew Stadler
1337bcec088320fc56cd5b5e1bbc706e4b7e304b95bfAndrew Stadler    /**
1338aef9515ee70f1f0b6cc4fa601078597b55831331Makoto Onuki     * Bridge to intercept {@link MessageRetrievalListener#loadAttachmentProgress} and
1339aef9515ee70f1f0b6cc4fa601078597b55831331Makoto Onuki     * pass down to {@link Result}.
1340bcec088320fc56cd5b5e1bbc706e4b7e304b95bfAndrew Stadler     */
1341aef9515ee70f1f0b6cc4fa601078597b55831331Makoto Onuki    public class MessageRetrievalListenerBridge implements MessageRetrievalListener {
1342aef9515ee70f1f0b6cc4fa601078597b55831331Makoto Onuki        private final long mMessageId;
1343aef9515ee70f1f0b6cc4fa601078597b55831331Makoto Onuki        private final long mAttachmentId;
1344aef9515ee70f1f0b6cc4fa601078597b55831331Makoto Onuki        private final long mAccountId;
1345aef9515ee70f1f0b6cc4fa601078597b55831331Makoto Onuki
1346aef9515ee70f1f0b6cc4fa601078597b55831331Makoto Onuki        public MessageRetrievalListenerBridge(long messageId, long attachmentId) {
1347aef9515ee70f1f0b6cc4fa601078597b55831331Makoto Onuki            mMessageId = messageId;
1348aef9515ee70f1f0b6cc4fa601078597b55831331Makoto Onuki            mAttachmentId = attachmentId;
1349aef9515ee70f1f0b6cc4fa601078597b55831331Makoto Onuki            mAccountId = Account.getAccountIdForMessageId(mProviderContext, mMessageId);
13507894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank        }
13517894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank
1352aef9515ee70f1f0b6cc4fa601078597b55831331Makoto Onuki        @Override
1353aef9515ee70f1f0b6cc4fa601078597b55831331Makoto Onuki        public void loadAttachmentProgress(int progress) {
1354aef9515ee70f1f0b6cc4fa601078597b55831331Makoto Onuki              synchronized (mListeners) {
1355aef9515ee70f1f0b6cc4fa601078597b55831331Makoto Onuki                  for (Result listener : mListeners) {
1356aef9515ee70f1f0b6cc4fa601078597b55831331Makoto Onuki                      listener.loadAttachmentCallback(null, mAccountId, mMessageId, mAttachmentId,
1357aef9515ee70f1f0b6cc4fa601078597b55831331Makoto Onuki                              progress);
1358aef9515ee70f1f0b6cc4fa601078597b55831331Makoto Onuki                 }
1359aef9515ee70f1f0b6cc4fa601078597b55831331Makoto Onuki              }
1360aef9515ee70f1f0b6cc4fa601078597b55831331Makoto Onuki        }
1361aef9515ee70f1f0b6cc4fa601078597b55831331Makoto Onuki
1362aef9515ee70f1f0b6cc4fa601078597b55831331Makoto Onuki        @Override
13632193962ca2b3157e79f731736afa2a0c972e778aMarc Blank        public void messageRetrieved(com.android.emailcommon.mail.Message message) {
1364aef9515ee70f1f0b6cc4fa601078597b55831331Makoto Onuki        }
1365aef9515ee70f1f0b6cc4fa601078597b55831331Makoto Onuki    }
1366aef9515ee70f1f0b6cc4fa601078597b55831331Makoto Onuki
1367aef9515ee70f1f0b6cc4fa601078597b55831331Makoto Onuki    /**
1368aef9515ee70f1f0b6cc4fa601078597b55831331Makoto Onuki     * Support for receiving callbacks from MessagingController and dealing with UI going
1369aef9515ee70f1f0b6cc4fa601078597b55831331Makoto Onuki     * out of scope.
1370aef9515ee70f1f0b6cc4fa601078597b55831331Makoto Onuki     */
1371aef9515ee70f1f0b6cc4fa601078597b55831331Makoto Onuki    public class LegacyListener extends MessagingListener {
13727894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank        public LegacyListener() {
13737894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank        }
13747b0b463477ef7172bd4b9e19c6338634ebedd8eeAndrew Stadler
1375bcec088320fc56cd5b5e1bbc706e4b7e304b95bfAndrew Stadler        @Override
13760c4dc85190028f389f0dcbc3dbed0b344d20a313Andrew Stadler        public void listFoldersStarted(long accountId) {
137746d7d7f1b6387d144c3f9e7c987418dc8f55fad4Andrew Stadler            synchronized (mListeners) {
137846d7d7f1b6387d144c3f9e7c987418dc8f55fad4Andrew Stadler                for (Result l : mListeners) {
13790c4dc85190028f389f0dcbc3dbed0b344d20a313Andrew Stadler                    l.updateMailboxListCallback(null, accountId, 0);
138046d7d7f1b6387d144c3f9e7c987418dc8f55fad4Andrew Stadler                }
13818f7f93a7b36d873d5adba65f4da54819880c0285Andrew Stadler            }
13828f7f93a7b36d873d5adba65f4da54819880c0285Andrew Stadler        }
13838f7f93a7b36d873d5adba65f4da54819880c0285Andrew Stadler
13848f7f93a7b36d873d5adba65f4da54819880c0285Andrew Stadler        @Override
13850c4dc85190028f389f0dcbc3dbed0b344d20a313Andrew Stadler        public void listFoldersFailed(long accountId, String message) {
138646d7d7f1b6387d144c3f9e7c987418dc8f55fad4Andrew Stadler            synchronized (mListeners) {
138746d7d7f1b6387d144c3f9e7c987418dc8f55fad4Andrew Stadler                for (Result l : mListeners) {
13880c4dc85190028f389f0dcbc3dbed0b344d20a313Andrew Stadler                    l.updateMailboxListCallback(new MessagingException(message), accountId, 0);
138946d7d7f1b6387d144c3f9e7c987418dc8f55fad4Andrew Stadler                }
1390bcec088320fc56cd5b5e1bbc706e4b7e304b95bfAndrew Stadler            }
1391bcec088320fc56cd5b5e1bbc706e4b7e304b95bfAndrew Stadler        }
1392bcec088320fc56cd5b5e1bbc706e4b7e304b95bfAndrew Stadler
1393bcec088320fc56cd5b5e1bbc706e4b7e304b95bfAndrew Stadler        @Override
13940c4dc85190028f389f0dcbc3dbed0b344d20a313Andrew Stadler        public void listFoldersFinished(long accountId) {
139546d7d7f1b6387d144c3f9e7c987418dc8f55fad4Andrew Stadler            synchronized (mListeners) {
139646d7d7f1b6387d144c3f9e7c987418dc8f55fad4Andrew Stadler                for (Result l : mListeners) {
13970c4dc85190028f389f0dcbc3dbed0b344d20a313Andrew Stadler                    l.updateMailboxListCallback(null, accountId, 100);
139846d7d7f1b6387d144c3f9e7c987418dc8f55fad4Andrew Stadler                }
13997b0b463477ef7172bd4b9e19c6338634ebedd8eeAndrew Stadler            }
14007b0b463477ef7172bd4b9e19c6338634ebedd8eeAndrew Stadler        }
14017b0b463477ef7172bd4b9e19c6338634ebedd8eeAndrew Stadler
14027b0b463477ef7172bd4b9e19c6338634ebedd8eeAndrew Stadler        @Override
14030c4dc85190028f389f0dcbc3dbed0b344d20a313Andrew Stadler        public void synchronizeMailboxStarted(long accountId, long mailboxId) {
140446d7d7f1b6387d144c3f9e7c987418dc8f55fad4Andrew Stadler            synchronized (mListeners) {
140546d7d7f1b6387d144c3f9e7c987418dc8f55fad4Andrew Stadler                for (Result l : mListeners) {
1406c4cdb11d24c19428dd39f986b00c1a29e75e1505Todd Kennedy                    l.updateMailboxCallback(null, accountId, mailboxId, 0, 0, null);
140746d7d7f1b6387d144c3f9e7c987418dc8f55fad4Andrew Stadler                }
14088f7f93a7b36d873d5adba65f4da54819880c0285Andrew Stadler            }
14098f7f93a7b36d873d5adba65f4da54819880c0285Andrew Stadler        }
14108f7f93a7b36d873d5adba65f4da54819880c0285Andrew Stadler
14118f7f93a7b36d873d5adba65f4da54819880c0285Andrew Stadler        @Override
14120c4dc85190028f389f0dcbc3dbed0b344d20a313Andrew Stadler        public void synchronizeMailboxFinished(long accountId, long mailboxId,
1413c4cdb11d24c19428dd39f986b00c1a29e75e1505Todd Kennedy                int totalMessagesInMailbox, int numNewMessages, ArrayList<Long> addedMessages) {
141446d7d7f1b6387d144c3f9e7c987418dc8f55fad4Andrew Stadler            synchronized (mListeners) {
141546d7d7f1b6387d144c3f9e7c987418dc8f55fad4Andrew Stadler                for (Result l : mListeners) {
1416c4cdb11d24c19428dd39f986b00c1a29e75e1505Todd Kennedy                    l.updateMailboxCallback(null, accountId, mailboxId, 100, numNewMessages,
1417c4cdb11d24c19428dd39f986b00c1a29e75e1505Todd Kennedy                            addedMessages);
141846d7d7f1b6387d144c3f9e7c987418dc8f55fad4Andrew Stadler                }
14197b0b463477ef7172bd4b9e19c6338634ebedd8eeAndrew Stadler            }
14207b0b463477ef7172bd4b9e19c6338634ebedd8eeAndrew Stadler        }
14217b0b463477ef7172bd4b9e19c6338634ebedd8eeAndrew Stadler
14227b0b463477ef7172bd4b9e19c6338634ebedd8eeAndrew Stadler        @Override
14230c4dc85190028f389f0dcbc3dbed0b344d20a313Andrew Stadler        public void synchronizeMailboxFailed(long accountId, long mailboxId, Exception e) {
14240d1078363581db8caded06cf94e729e88a88761aAndrew Stadler            MessagingException me;
14250d1078363581db8caded06cf94e729e88a88761aAndrew Stadler            if (e instanceof MessagingException) {
14260d1078363581db8caded06cf94e729e88a88761aAndrew Stadler                me = (MessagingException) e;
14270d1078363581db8caded06cf94e729e88a88761aAndrew Stadler            } else {
14280d1078363581db8caded06cf94e729e88a88761aAndrew Stadler                me = new MessagingException(e.toString());
14290d1078363581db8caded06cf94e729e88a88761aAndrew Stadler            }
143046d7d7f1b6387d144c3f9e7c987418dc8f55fad4Andrew Stadler            synchronized (mListeners) {
143146d7d7f1b6387d144c3f9e7c987418dc8f55fad4Andrew Stadler                for (Result l : mListeners) {
1432c4cdb11d24c19428dd39f986b00c1a29e75e1505Todd Kennedy                    l.updateMailboxCallback(me, accountId, mailboxId, 0, 0, null);
14337b0b463477ef7172bd4b9e19c6338634ebedd8eeAndrew Stadler                }
1434bcec088320fc56cd5b5e1bbc706e4b7e304b95bfAndrew Stadler            }
1435bcec088320fc56cd5b5e1bbc706e4b7e304b95bfAndrew Stadler        }
14367b0b463477ef7172bd4b9e19c6338634ebedd8eeAndrew Stadler
143746d7d7f1b6387d144c3f9e7c987418dc8f55fad4Andrew Stadler        @Override
143846d7d7f1b6387d144c3f9e7c987418dc8f55fad4Andrew Stadler        public void checkMailStarted(Context context, long accountId, long tag) {
143946d7d7f1b6387d144c3f9e7c987418dc8f55fad4Andrew Stadler            synchronized (mListeners) {
144046d7d7f1b6387d144c3f9e7c987418dc8f55fad4Andrew Stadler                for (Result l : mListeners) {
144146d7d7f1b6387d144c3f9e7c987418dc8f55fad4Andrew Stadler                    l.serviceCheckMailCallback(null, accountId, -1, 0, tag);
144246d7d7f1b6387d144c3f9e7c987418dc8f55fad4Andrew Stadler                }
144346d7d7f1b6387d144c3f9e7c987418dc8f55fad4Andrew Stadler            }
144446d7d7f1b6387d144c3f9e7c987418dc8f55fad4Andrew Stadler        }
14457b0b463477ef7172bd4b9e19c6338634ebedd8eeAndrew Stadler
144646d7d7f1b6387d144c3f9e7c987418dc8f55fad4Andrew Stadler        @Override
144746d7d7f1b6387d144c3f9e7c987418dc8f55fad4Andrew Stadler        public void checkMailFinished(Context context, long accountId, long folderId, long tag) {
144846d7d7f1b6387d144c3f9e7c987418dc8f55fad4Andrew Stadler            synchronized (mListeners) {
144946d7d7f1b6387d144c3f9e7c987418dc8f55fad4Andrew Stadler                for (Result l : mListeners) {
145046d7d7f1b6387d144c3f9e7c987418dc8f55fad4Andrew Stadler                    l.serviceCheckMailCallback(null, accountId, folderId, 100, tag);
145146d7d7f1b6387d144c3f9e7c987418dc8f55fad4Andrew Stadler                }
145246d7d7f1b6387d144c3f9e7c987418dc8f55fad4Andrew Stadler            }
145346d7d7f1b6387d144c3f9e7c987418dc8f55fad4Andrew Stadler        }
14540d1078363581db8caded06cf94e729e88a88761aAndrew Stadler
14550d1078363581db8caded06cf94e729e88a88761aAndrew Stadler        @Override
1456df86adf87328a439347260331592509787020420Andrew Stadler        public void loadMessageForViewStarted(long messageId) {
1457aef9515ee70f1f0b6cc4fa601078597b55831331Makoto Onuki            final long accountId = Account.getAccountIdForMessageId(mProviderContext, messageId);
1458df86adf87328a439347260331592509787020420Andrew Stadler            synchronized (mListeners) {
1459df86adf87328a439347260331592509787020420Andrew Stadler                for (Result listener : mListeners) {
1460aef9515ee70f1f0b6cc4fa601078597b55831331Makoto Onuki                    listener.loadMessageForViewCallback(null, accountId, messageId, 0);
1461df86adf87328a439347260331592509787020420Andrew Stadler                }
1462df86adf87328a439347260331592509787020420Andrew Stadler            }
1463df86adf87328a439347260331592509787020420Andrew Stadler        }
1464df86adf87328a439347260331592509787020420Andrew Stadler
1465df86adf87328a439347260331592509787020420Andrew Stadler        @Override
1466df86adf87328a439347260331592509787020420Andrew Stadler        public void loadMessageForViewFinished(long messageId) {
1467aef9515ee70f1f0b6cc4fa601078597b55831331Makoto Onuki            final long accountId = Account.getAccountIdForMessageId(mProviderContext, messageId);
1468df86adf87328a439347260331592509787020420Andrew Stadler            synchronized (mListeners) {
1469df86adf87328a439347260331592509787020420Andrew Stadler                for (Result listener : mListeners) {
1470aef9515ee70f1f0b6cc4fa601078597b55831331Makoto Onuki                    listener.loadMessageForViewCallback(null, accountId, messageId, 100);
1471df86adf87328a439347260331592509787020420Andrew Stadler                }
1472df86adf87328a439347260331592509787020420Andrew Stadler            }
1473df86adf87328a439347260331592509787020420Andrew Stadler        }
1474df86adf87328a439347260331592509787020420Andrew Stadler
1475df86adf87328a439347260331592509787020420Andrew Stadler        @Override
1476df86adf87328a439347260331592509787020420Andrew Stadler        public void loadMessageForViewFailed(long messageId, String message) {
1477aef9515ee70f1f0b6cc4fa601078597b55831331Makoto Onuki            final long accountId = Account.getAccountIdForMessageId(mProviderContext, messageId);
1478df86adf87328a439347260331592509787020420Andrew Stadler            synchronized (mListeners) {
1479df86adf87328a439347260331592509787020420Andrew Stadler                for (Result listener : mListeners) {
1480df86adf87328a439347260331592509787020420Andrew Stadler                    listener.loadMessageForViewCallback(new MessagingException(message),
1481aef9515ee70f1f0b6cc4fa601078597b55831331Makoto Onuki                            accountId, messageId, 0);
1482df86adf87328a439347260331592509787020420Andrew Stadler                }
1483df86adf87328a439347260331592509787020420Andrew Stadler            }
1484df86adf87328a439347260331592509787020420Andrew Stadler        }
1485df86adf87328a439347260331592509787020420Andrew Stadler
1486df86adf87328a439347260331592509787020420Andrew Stadler        @Override
14870d1078363581db8caded06cf94e729e88a88761aAndrew Stadler        public void loadAttachmentStarted(long accountId, long messageId, long attachmentId,
14880d1078363581db8caded06cf94e729e88a88761aAndrew Stadler                boolean requiresDownload) {
14897894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank            try {
14907894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank                mCallbackProxy.loadAttachmentStatus(messageId, attachmentId,
14917894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank                        EmailServiceStatus.IN_PROGRESS, 0);
14927894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank            } catch (RemoteException e) {
14937894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank            }
14940d1078363581db8caded06cf94e729e88a88761aAndrew Stadler            synchronized (mListeners) {
14950d1078363581db8caded06cf94e729e88a88761aAndrew Stadler                for (Result listener : mListeners) {
1496aef9515ee70f1f0b6cc4fa601078597b55831331Makoto Onuki                    listener.loadAttachmentCallback(null, accountId, messageId, attachmentId, 0);
14970d1078363581db8caded06cf94e729e88a88761aAndrew Stadler                }
14980d1078363581db8caded06cf94e729e88a88761aAndrew Stadler            }
14990d1078363581db8caded06cf94e729e88a88761aAndrew Stadler        }
15000d1078363581db8caded06cf94e729e88a88761aAndrew Stadler
15010d1078363581db8caded06cf94e729e88a88761aAndrew Stadler        @Override
15020d1078363581db8caded06cf94e729e88a88761aAndrew Stadler        public void loadAttachmentFinished(long accountId, long messageId, long attachmentId) {
15037894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank            try {
15047894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank                mCallbackProxy.loadAttachmentStatus(messageId, attachmentId,
15057894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank                        EmailServiceStatus.SUCCESS, 100);
15067894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank            } catch (RemoteException e) {
15077894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank            }
15080d1078363581db8caded06cf94e729e88a88761aAndrew Stadler            synchronized (mListeners) {
15090d1078363581db8caded06cf94e729e88a88761aAndrew Stadler                for (Result listener : mListeners) {
1510aef9515ee70f1f0b6cc4fa601078597b55831331Makoto Onuki                    listener.loadAttachmentCallback(null, accountId, messageId, attachmentId, 100);
15110d1078363581db8caded06cf94e729e88a88761aAndrew Stadler                }
15120d1078363581db8caded06cf94e729e88a88761aAndrew Stadler            }
15130d1078363581db8caded06cf94e729e88a88761aAndrew Stadler        }
15140d1078363581db8caded06cf94e729e88a88761aAndrew Stadler
15150d1078363581db8caded06cf94e729e88a88761aAndrew Stadler        @Override
15160d1078363581db8caded06cf94e729e88a88761aAndrew Stadler        public void loadAttachmentFailed(long accountId, long messageId, long attachmentId,
1517f92dd2bf3ea445db9b9a0eb9a447b5cbdb1a6e05Todd Kennedy                MessagingException me, boolean background) {
15187894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank            try {
15197894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank                // If the cause of the MessagingException is an IOException, we send a status of
15207894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank                // CONNECTION_ERROR; in this case, AttachmentDownloadService will try again to
15217894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank                // download the attachment.  Otherwise, the error is considered non-recoverable.
15227894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank                int status = EmailServiceStatus.ATTACHMENT_NOT_FOUND;
1523f92dd2bf3ea445db9b9a0eb9a447b5cbdb1a6e05Todd Kennedy                if (me != null && me.getCause() instanceof IOException) {
15247894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank                    status = EmailServiceStatus.CONNECTION_ERROR;
15257894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank                }
15267894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank                mCallbackProxy.loadAttachmentStatus(messageId, attachmentId, status, 0);
15277894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank            } catch (RemoteException e) {
15287894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank            }
15290d1078363581db8caded06cf94e729e88a88761aAndrew Stadler            synchronized (mListeners) {
15300d1078363581db8caded06cf94e729e88a88761aAndrew Stadler                for (Result listener : mListeners) {
1531f92dd2bf3ea445db9b9a0eb9a447b5cbdb1a6e05Todd Kennedy                    // TODO We are overloading the exception here. The UI listens for this
1532f92dd2bf3ea445db9b9a0eb9a447b5cbdb1a6e05Todd Kennedy                    // callback and displays a toast if the exception is not null. Since we
1533f92dd2bf3ea445db9b9a0eb9a447b5cbdb1a6e05Todd Kennedy                    // want to avoid displaying toast for background operations, we force
1534f92dd2bf3ea445db9b9a0eb9a447b5cbdb1a6e05Todd Kennedy                    // the exception to be null. This needs to be re-worked so the UI will
1535f92dd2bf3ea445db9b9a0eb9a447b5cbdb1a6e05Todd Kennedy                    // only receive (or at least pays attention to) responses for requests
1536f92dd2bf3ea445db9b9a0eb9a447b5cbdb1a6e05Todd Kennedy                    // it explicitly cares about. Then we would not need to overload the
1537f92dd2bf3ea445db9b9a0eb9a447b5cbdb1a6e05Todd Kennedy                    // exception parameter.
1538f92dd2bf3ea445db9b9a0eb9a447b5cbdb1a6e05Todd Kennedy                    listener.loadAttachmentCallback(background ? null : me, accountId, messageId,
1539f92dd2bf3ea445db9b9a0eb9a447b5cbdb1a6e05Todd Kennedy                            attachmentId, 0);
15400d1078363581db8caded06cf94e729e88a88761aAndrew Stadler                }
15410d1078363581db8caded06cf94e729e88a88761aAndrew Stadler            }
15420d1078363581db8caded06cf94e729e88a88761aAndrew Stadler        }
15434b41bae270ea4c49ec8403084db43ee9b37cdda4Andrew Stadler
15444b41bae270ea4c49ec8403084db43ee9b37cdda4Andrew Stadler        @Override
15454b41bae270ea4c49ec8403084db43ee9b37cdda4Andrew Stadler        synchronized public void sendPendingMessagesStarted(long accountId, long messageId) {
15464b41bae270ea4c49ec8403084db43ee9b37cdda4Andrew Stadler            synchronized (mListeners) {
15474b41bae270ea4c49ec8403084db43ee9b37cdda4Andrew Stadler                for (Result listener : mListeners) {
15484b41bae270ea4c49ec8403084db43ee9b37cdda4Andrew Stadler                    listener.sendMailCallback(null, accountId, messageId, 0);
15494b41bae270ea4c49ec8403084db43ee9b37cdda4Andrew Stadler                }
15504b41bae270ea4c49ec8403084db43ee9b37cdda4Andrew Stadler            }
15514b41bae270ea4c49ec8403084db43ee9b37cdda4Andrew Stadler        }
15524b41bae270ea4c49ec8403084db43ee9b37cdda4Andrew Stadler
15534b41bae270ea4c49ec8403084db43ee9b37cdda4Andrew Stadler        @Override
15544b41bae270ea4c49ec8403084db43ee9b37cdda4Andrew Stadler        synchronized public void sendPendingMessagesCompleted(long accountId) {
15554b41bae270ea4c49ec8403084db43ee9b37cdda4Andrew Stadler            synchronized (mListeners) {
15564b41bae270ea4c49ec8403084db43ee9b37cdda4Andrew Stadler                for (Result listener : mListeners) {
15574b41bae270ea4c49ec8403084db43ee9b37cdda4Andrew Stadler                    listener.sendMailCallback(null, accountId, -1, 100);
15584b41bae270ea4c49ec8403084db43ee9b37cdda4Andrew Stadler                }
15594b41bae270ea4c49ec8403084db43ee9b37cdda4Andrew Stadler            }
15604b41bae270ea4c49ec8403084db43ee9b37cdda4Andrew Stadler        }
15614b41bae270ea4c49ec8403084db43ee9b37cdda4Andrew Stadler
15624b41bae270ea4c49ec8403084db43ee9b37cdda4Andrew Stadler        @Override
15634b41bae270ea4c49ec8403084db43ee9b37cdda4Andrew Stadler        synchronized public void sendPendingMessagesFailed(long accountId, long messageId,
15644b41bae270ea4c49ec8403084db43ee9b37cdda4Andrew Stadler                Exception reason) {
15654b41bae270ea4c49ec8403084db43ee9b37cdda4Andrew Stadler            MessagingException me;
15664b41bae270ea4c49ec8403084db43ee9b37cdda4Andrew Stadler            if (reason instanceof MessagingException) {
15674b41bae270ea4c49ec8403084db43ee9b37cdda4Andrew Stadler                me = (MessagingException) reason;
15684b41bae270ea4c49ec8403084db43ee9b37cdda4Andrew Stadler            } else {
15694b41bae270ea4c49ec8403084db43ee9b37cdda4Andrew Stadler                me = new MessagingException(reason.toString());
15704b41bae270ea4c49ec8403084db43ee9b37cdda4Andrew Stadler            }
15714b41bae270ea4c49ec8403084db43ee9b37cdda4Andrew Stadler            synchronized (mListeners) {
15724b41bae270ea4c49ec8403084db43ee9b37cdda4Andrew Stadler                for (Result listener : mListeners) {
15734b41bae270ea4c49ec8403084db43ee9b37cdda4Andrew Stadler                    listener.sendMailCallback(me, accountId, messageId, 0);
15744b41bae270ea4c49ec8403084db43ee9b37cdda4Andrew Stadler                }
15754b41bae270ea4c49ec8403084db43ee9b37cdda4Andrew Stadler            }
15764b41bae270ea4c49ec8403084db43ee9b37cdda4Andrew Stadler        }
1577bcec088320fc56cd5b5e1bbc706e4b7e304b95bfAndrew Stadler    }
15787b0b463477ef7172bd4b9e19c6338634ebedd8eeAndrew Stadler
15796ae38e4a3937898c5d3fec11a9dc2fb4bab8eab6Andrew Stadler    /**
15808f7f93a7b36d873d5adba65f4da54819880c0285Andrew Stadler     * Service callback for service operations
15816ae38e4a3937898c5d3fec11a9dc2fb4bab8eab6Andrew Stadler     */
1582ea69fc40d75f5fcf38872f836cdea9fb3aa9c991Andrew Stadler    private class ServiceCallback extends IEmailServiceCallback.Stub {
15836ae38e4a3937898c5d3fec11a9dc2fb4bab8eab6Andrew Stadler
15846ae38e4a3937898c5d3fec11a9dc2fb4bab8eab6Andrew Stadler        private final static boolean DEBUG_FAIL_DOWNLOADS = false;       // do not check in "true"
1585bcec088320fc56cd5b5e1bbc706e4b7e304b95bfAndrew Stadler
1586ea69fc40d75f5fcf38872f836cdea9fb3aa9c991Andrew Stadler        public void loadAttachmentStatus(long messageId, long attachmentId, int statusCode,
15878f7f93a7b36d873d5adba65f4da54819880c0285Andrew Stadler                int progress) {
15889312faea437854f075df57b9ec23a5d57cb8db0cAndrew Stadler            MessagingException result = mapStatusToException(statusCode);
1589ea69fc40d75f5fcf38872f836cdea9fb3aa9c991Andrew Stadler            switch (statusCode) {
1590ea69fc40d75f5fcf38872f836cdea9fb3aa9c991Andrew Stadler                case EmailServiceStatus.SUCCESS:
1591ea69fc40d75f5fcf38872f836cdea9fb3aa9c991Andrew Stadler                    progress = 100;
15926ae38e4a3937898c5d3fec11a9dc2fb4bab8eab6Andrew Stadler                    break;
1593ea69fc40d75f5fcf38872f836cdea9fb3aa9c991Andrew Stadler                case EmailServiceStatus.IN_PROGRESS:
1594ea69fc40d75f5fcf38872f836cdea9fb3aa9c991Andrew Stadler                    if (DEBUG_FAIL_DOWNLOADS && progress > 75) {
1595ea69fc40d75f5fcf38872f836cdea9fb3aa9c991Andrew Stadler                        result = new MessagingException(
1596ea69fc40d75f5fcf38872f836cdea9fb3aa9c991Andrew Stadler                                String.valueOf(EmailServiceStatus.CONNECTION_ERROR));
1597ea69fc40d75f5fcf38872f836cdea9fb3aa9c991Andrew Stadler                    }
1598ea69fc40d75f5fcf38872f836cdea9fb3aa9c991Andrew Stadler                    // discard progress reports that look like sentinels
1599ea69fc40d75f5fcf38872f836cdea9fb3aa9c991Andrew Stadler                    if (progress < 0 || progress >= 100) {
1600ea69fc40d75f5fcf38872f836cdea9fb3aa9c991Andrew Stadler                        return;
1601ea69fc40d75f5fcf38872f836cdea9fb3aa9c991Andrew Stadler                    }
1602ea69fc40d75f5fcf38872f836cdea9fb3aa9c991Andrew Stadler                    break;
1603ea69fc40d75f5fcf38872f836cdea9fb3aa9c991Andrew Stadler            }
1604aef9515ee70f1f0b6cc4fa601078597b55831331Makoto Onuki            final long accountId = Account.getAccountIdForMessageId(mProviderContext, messageId);
1605ea69fc40d75f5fcf38872f836cdea9fb3aa9c991Andrew Stadler            synchronized (mListeners) {
1606ea69fc40d75f5fcf38872f836cdea9fb3aa9c991Andrew Stadler                for (Result listener : mListeners) {
1607aef9515ee70f1f0b6cc4fa601078597b55831331Makoto Onuki                    listener.loadAttachmentCallback(result, accountId, messageId, attachmentId,
1608aef9515ee70f1f0b6cc4fa601078597b55831331Makoto Onuki                            progress);
16096ae38e4a3937898c5d3fec11a9dc2fb4bab8eab6Andrew Stadler                }
16106ae38e4a3937898c5d3fec11a9dc2fb4bab8eab6Andrew Stadler            }
16116ae38e4a3937898c5d3fec11a9dc2fb4bab8eab6Andrew Stadler        }
1612c449cba5101f083d4cef8acd9972bc05598bad44Marc Blank
1613f9ccc0ba88e00c4175f6720aca07876d50b9f22fAndrew Stadler        /**
1614f9ccc0ba88e00c4175f6720aca07876d50b9f22fAndrew Stadler         * Note, this is an incomplete implementation of this callback, because we are
1615f9ccc0ba88e00c4175f6720aca07876d50b9f22fAndrew Stadler         * not getting things back from Service in quite the same way as from MessagingController.
1616f9ccc0ba88e00c4175f6720aca07876d50b9f22fAndrew Stadler         * However, this is sufficient for basic "progress=100" notification that message send
1617f9ccc0ba88e00c4175f6720aca07876d50b9f22fAndrew Stadler         * has just completed.
1618f9ccc0ba88e00c4175f6720aca07876d50b9f22fAndrew Stadler         */
1619d366346a640c93d70b78ddadfbc3a3b74944359bMarc Blank        public void sendMessageStatus(long messageId, String subject, int statusCode,
1620d366346a640c93d70b78ddadfbc3a3b74944359bMarc Blank                int progress) {
1621f9ccc0ba88e00c4175f6720aca07876d50b9f22fAndrew Stadler            long accountId = -1;        // This should be in the callback
1622f9ccc0ba88e00c4175f6720aca07876d50b9f22fAndrew Stadler            MessagingException result = mapStatusToException(statusCode);
1623f9ccc0ba88e00c4175f6720aca07876d50b9f22fAndrew Stadler            switch (statusCode) {
1624f9ccc0ba88e00c4175f6720aca07876d50b9f22fAndrew Stadler                case EmailServiceStatus.SUCCESS:
1625f9ccc0ba88e00c4175f6720aca07876d50b9f22fAndrew Stadler                    progress = 100;
1626f9ccc0ba88e00c4175f6720aca07876d50b9f22fAndrew Stadler                    break;
1627f9ccc0ba88e00c4175f6720aca07876d50b9f22fAndrew Stadler                case EmailServiceStatus.IN_PROGRESS:
1628f9ccc0ba88e00c4175f6720aca07876d50b9f22fAndrew Stadler                    // discard progress reports that look like sentinels
1629f9ccc0ba88e00c4175f6720aca07876d50b9f22fAndrew Stadler                    if (progress < 0 || progress >= 100) {
1630f9ccc0ba88e00c4175f6720aca07876d50b9f22fAndrew Stadler                        return;
1631f9ccc0ba88e00c4175f6720aca07876d50b9f22fAndrew Stadler                    }
1632f9ccc0ba88e00c4175f6720aca07876d50b9f22fAndrew Stadler                    break;
1633f9ccc0ba88e00c4175f6720aca07876d50b9f22fAndrew Stadler            }
1634f9ccc0ba88e00c4175f6720aca07876d50b9f22fAndrew Stadler            synchronized(mListeners) {
1635f9ccc0ba88e00c4175f6720aca07876d50b9f22fAndrew Stadler                for (Result listener : mListeners) {
1636f9ccc0ba88e00c4175f6720aca07876d50b9f22fAndrew Stadler                    listener.sendMailCallback(result, accountId, messageId, progress);
1637f9ccc0ba88e00c4175f6720aca07876d50b9f22fAndrew Stadler                }
1638f9ccc0ba88e00c4175f6720aca07876d50b9f22fAndrew Stadler            }
1639c449cba5101f083d4cef8acd9972bc05598bad44Marc Blank        }
1640c449cba5101f083d4cef8acd9972bc05598bad44Marc Blank
16418f7f93a7b36d873d5adba65f4da54819880c0285Andrew Stadler        public void syncMailboxListStatus(long accountId, int statusCode, int progress) {
16429312faea437854f075df57b9ec23a5d57cb8db0cAndrew Stadler            MessagingException result = mapStatusToException(statusCode);
16438f7f93a7b36d873d5adba65f4da54819880c0285Andrew Stadler            switch (statusCode) {
16448f7f93a7b36d873d5adba65f4da54819880c0285Andrew Stadler                case EmailServiceStatus.SUCCESS:
16458f7f93a7b36d873d5adba65f4da54819880c0285Andrew Stadler                    progress = 100;
16468f7f93a7b36d873d5adba65f4da54819880c0285Andrew Stadler                    break;
16478f7f93a7b36d873d5adba65f4da54819880c0285Andrew Stadler                case EmailServiceStatus.IN_PROGRESS:
16488f7f93a7b36d873d5adba65f4da54819880c0285Andrew Stadler                    // discard progress reports that look like sentinels
16498f7f93a7b36d873d5adba65f4da54819880c0285Andrew Stadler                    if (progress < 0 || progress >= 100) {
16508f7f93a7b36d873d5adba65f4da54819880c0285Andrew Stadler                        return;
16518f7f93a7b36d873d5adba65f4da54819880c0285Andrew Stadler                    }
16528f7f93a7b36d873d5adba65f4da54819880c0285Andrew Stadler                    break;
16538f7f93a7b36d873d5adba65f4da54819880c0285Andrew Stadler            }
16548f7f93a7b36d873d5adba65f4da54819880c0285Andrew Stadler            synchronized(mListeners) {
16558f7f93a7b36d873d5adba65f4da54819880c0285Andrew Stadler                for (Result listener : mListeners) {
16568f7f93a7b36d873d5adba65f4da54819880c0285Andrew Stadler                    listener.updateMailboxListCallback(result, accountId, progress);
16578f7f93a7b36d873d5adba65f4da54819880c0285Andrew Stadler                }
16588f7f93a7b36d873d5adba65f4da54819880c0285Andrew Stadler            }
1659c449cba5101f083d4cef8acd9972bc05598bad44Marc Blank        }
1660c449cba5101f083d4cef8acd9972bc05598bad44Marc Blank
16618f7f93a7b36d873d5adba65f4da54819880c0285Andrew Stadler        public void syncMailboxStatus(long mailboxId, int statusCode, int progress) {
16629312faea437854f075df57b9ec23a5d57cb8db0cAndrew Stadler            MessagingException result = mapStatusToException(statusCode);
16638f7f93a7b36d873d5adba65f4da54819880c0285Andrew Stadler            switch (statusCode) {
16648f7f93a7b36d873d5adba65f4da54819880c0285Andrew Stadler                case EmailServiceStatus.SUCCESS:
16658f7f93a7b36d873d5adba65f4da54819880c0285Andrew Stadler                    progress = 100;
16668f7f93a7b36d873d5adba65f4da54819880c0285Andrew Stadler                    break;
16678f7f93a7b36d873d5adba65f4da54819880c0285Andrew Stadler                case EmailServiceStatus.IN_PROGRESS:
16688f7f93a7b36d873d5adba65f4da54819880c0285Andrew Stadler                    // discard progress reports that look like sentinels
16698f7f93a7b36d873d5adba65f4da54819880c0285Andrew Stadler                    if (progress < 0 || progress >= 100) {
16708f7f93a7b36d873d5adba65f4da54819880c0285Andrew Stadler                        return;
16718f7f93a7b36d873d5adba65f4da54819880c0285Andrew Stadler                    }
16728f7f93a7b36d873d5adba65f4da54819880c0285Andrew Stadler                    break;
16738f7f93a7b36d873d5adba65f4da54819880c0285Andrew Stadler            }
16748f7f93a7b36d873d5adba65f4da54819880c0285Andrew Stadler            // TODO should pass this back instead of looking it up here
1675fa52e6c95674aef6461a5cfc670a052e1c5b7f2fAndrew Stadler            Mailbox mbx = Mailbox.restoreMailboxWithId(mProviderContext, mailboxId);
1676f00dccd32125c727cc18d837b59c15c95f5d78bcMarc Blank            // The mailbox could have disappeared if the server commanded it
1677f00dccd32125c727cc18d837b59c15c95f5d78bcMarc Blank            if (mbx == null) return;
16788f7f93a7b36d873d5adba65f4da54819880c0285Andrew Stadler            long accountId = mbx.mAccountKey;
16798f7f93a7b36d873d5adba65f4da54819880c0285Andrew Stadler            synchronized(mListeners) {
16808f7f93a7b36d873d5adba65f4da54819880c0285Andrew Stadler                for (Result listener : mListeners) {
1681c4cdb11d24c19428dd39f986b00c1a29e75e1505Todd Kennedy                    listener.updateMailboxCallback(result, accountId, mailboxId, progress, 0, null);
16828f7f93a7b36d873d5adba65f4da54819880c0285Andrew Stadler                }
16838f7f93a7b36d873d5adba65f4da54819880c0285Andrew Stadler            }
1684c449cba5101f083d4cef8acd9972bc05598bad44Marc Blank        }
16859312faea437854f075df57b9ec23a5d57cb8db0cAndrew Stadler
16869312faea437854f075df57b9ec23a5d57cb8db0cAndrew Stadler        private MessagingException mapStatusToException(int statusCode) {
16879312faea437854f075df57b9ec23a5d57cb8db0cAndrew Stadler            switch (statusCode) {
16889312faea437854f075df57b9ec23a5d57cb8db0cAndrew Stadler                case EmailServiceStatus.SUCCESS:
16899312faea437854f075df57b9ec23a5d57cb8db0cAndrew Stadler                case EmailServiceStatus.IN_PROGRESS:
1690aeea97275d27f42d724b17e37aed2d0302271007Marc Blank                // Don't generate error if the account is uninitialized
1691aeea97275d27f42d724b17e37aed2d0302271007Marc Blank                case EmailServiceStatus.ACCOUNT_UNINITIALIZED:
16929312faea437854f075df57b9ec23a5d57cb8db0cAndrew Stadler                    return null;
16939312faea437854f075df57b9ec23a5d57cb8db0cAndrew Stadler
16949312faea437854f075df57b9ec23a5d57cb8db0cAndrew Stadler                case EmailServiceStatus.LOGIN_FAILED:
16959312faea437854f075df57b9ec23a5d57cb8db0cAndrew Stadler                    return new AuthenticationFailedException("");
16969312faea437854f075df57b9ec23a5d57cb8db0cAndrew Stadler
16979312faea437854f075df57b9ec23a5d57cb8db0cAndrew Stadler                case EmailServiceStatus.CONNECTION_ERROR:
16989312faea437854f075df57b9ec23a5d57cb8db0cAndrew Stadler                    return new MessagingException(MessagingException.IOERROR);
16999312faea437854f075df57b9ec23a5d57cb8db0cAndrew Stadler
1700605371e793c7038cf24c15ca5090f27ab431ce79Andrew Stadler                case EmailServiceStatus.SECURITY_FAILURE:
1701605371e793c7038cf24c15ca5090f27ab431ce79Andrew Stadler                    return new MessagingException(MessagingException.SECURITY_POLICIES_REQUIRED);
1702605371e793c7038cf24c15ca5090f27ab431ce79Andrew Stadler
1703d2fd1252d504fa61db9eb8362442cf20f1198e0fMarc Blank                case EmailServiceStatus.ACCESS_DENIED:
1704d2fd1252d504fa61db9eb8362442cf20f1198e0fMarc Blank                    return new MessagingException(MessagingException.ACCESS_DENIED);
1705d2fd1252d504fa61db9eb8362442cf20f1198e0fMarc Blank
17069312faea437854f075df57b9ec23a5d57cb8db0cAndrew Stadler                case EmailServiceStatus.ATTACHMENT_NOT_FOUND:
1707b221cbc785d7c868a69a144da31756c50c0e11b4Marc Blank                    return new MessagingException(MessagingException.ATTACHMENT_NOT_FOUND);
1708b221cbc785d7c868a69a144da31756c50c0e11b4Marc Blank
1709cb24e515b7983133133ca38bd3e3e6354daaab76Ben Komalo                case EmailServiceStatus.CLIENT_CERTIFICATE_ERROR:
1710cb24e515b7983133133ca38bd3e3e6354daaab76Ben Komalo                    return new MessagingException(MessagingException.CLIENT_CERTIFICATE_ERROR);
1711cb24e515b7983133133ca38bd3e3e6354daaab76Ben Komalo
1712b221cbc785d7c868a69a144da31756c50c0e11b4Marc Blank                case EmailServiceStatus.MESSAGE_NOT_FOUND:
17139312faea437854f075df57b9ec23a5d57cb8db0cAndrew Stadler                case EmailServiceStatus.FOLDER_NOT_DELETED:
17149312faea437854f075df57b9ec23a5d57cb8db0cAndrew Stadler                case EmailServiceStatus.FOLDER_NOT_RENAMED:
17159312faea437854f075df57b9ec23a5d57cb8db0cAndrew Stadler                case EmailServiceStatus.FOLDER_NOT_CREATED:
17169312faea437854f075df57b9ec23a5d57cb8db0cAndrew Stadler                case EmailServiceStatus.REMOTE_EXCEPTION:
17179312faea437854f075df57b9ec23a5d57cb8db0cAndrew Stadler                    // TODO: define exception code(s) & UI string(s) for server-side errors
17189312faea437854f075df57b9ec23a5d57cb8db0cAndrew Stadler                default:
17199312faea437854f075df57b9ec23a5d57cb8db0cAndrew Stadler                    return new MessagingException(String.valueOf(statusCode));
17209312faea437854f075df57b9ec23a5d57cb8db0cAndrew Stadler            }
17219312faea437854f075df57b9ec23a5d57cb8db0cAndrew Stadler        }
17226ae38e4a3937898c5d3fec11a9dc2fb4bab8eab6Andrew Stadler    }
17237894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank
17247894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank    private interface ServiceCallbackWrapper {
17257894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank        public void call(IEmailServiceCallback cb) throws RemoteException;
17267894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank    }
17277894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank
17287894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank    /**
17297894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank     * Proxy that can be used to broadcast service callbacks; we currently use this only for
17307894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank     * loadAttachment callbacks
17317894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank     */
17327894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank    private final IEmailServiceCallback.Stub mCallbackProxy =
17337894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank        new IEmailServiceCallback.Stub() {
17347894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank
17357894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank        /**
17367894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank         * Broadcast a callback to the everyone that's registered
17377894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank         *
17387894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank         * @param wrapper the ServiceCallbackWrapper used in the broadcast
17397894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank         */
17407894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank        private synchronized void broadcastCallback(ServiceCallbackWrapper wrapper) {
17417894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank            if (sCallbackList != null) {
17427894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank                // Call everyone on our callback list
17437894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank                // Exceptions can be safely ignored
17447894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank                int count = sCallbackList.beginBroadcast();
17457894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank                for (int i = 0; i < count; i++) {
17467894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank                    try {
17477894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank                        wrapper.call(sCallbackList.getBroadcastItem(i));
17487894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank                    } catch (RemoteException e) {
17497894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank                    }
17507894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank                }
17517894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank                sCallbackList.finishBroadcast();
17527894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank            }
17537894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank        }
17547894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank
17557894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank        public void loadAttachmentStatus(final long messageId, final long attachmentId,
17567894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank                final int status, final int progress) {
17577894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank            broadcastCallback(new ServiceCallbackWrapper() {
17587894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank                @Override
17597894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank                public void call(IEmailServiceCallback cb) throws RemoteException {
17607894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank                    cb.loadAttachmentStatus(messageId, attachmentId, status, progress);
17617894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank                }
17627894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank            });
17637894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank        }
17647894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank
17657894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank        @Override
1766e87ff6c3cbbfc5e3636f9827b58820652e3ea1c5Todd Kennedy        public void sendMessageStatus(long messageId, String subject, int statusCode, int progress){
17677894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank        }
17687894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank
17697894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank        @Override
1770e87ff6c3cbbfc5e3636f9827b58820652e3ea1c5Todd Kennedy        public void syncMailboxListStatus(long accountId, int statusCode, int progress) {
17717894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank        }
17727894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank
17737894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank        @Override
1774e87ff6c3cbbfc5e3636f9827b58820652e3ea1c5Todd Kennedy        public void syncMailboxStatus(long mailboxId, int statusCode, int progress) {
17757894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank        }
17767894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank    };
17777894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank
177845f530ba5553dcbe3e548930945c40e13736deb3Makoto Onuki    public static class ControllerService extends Service {
177945f530ba5553dcbe3e548930945c40e13736deb3Makoto Onuki        /**
17803b1cccf234cd95c3e1c4c568bc588ab06b06d3aaAndy Stadler         * Create our EmailService implementation here.  For now, only loadAttachment is supported;
17813b1cccf234cd95c3e1c4c568bc588ab06b06d3aaAndy Stadler         * the intention, however, is to move more functionality to the service interface
178245f530ba5553dcbe3e548930945c40e13736deb3Makoto Onuki         */
178345f530ba5553dcbe3e548930945c40e13736deb3Makoto Onuki        private final IEmailService.Stub mBinder = new IEmailService.Stub() {
17847894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank
178522409fcffae4c6e551fb3e6ead4cdc92e33fded1Ben Komalo            public Bundle validate(HostAuth hostAuth) {
178645f530ba5553dcbe3e548930945c40e13736deb3Makoto Onuki                return null;
178745f530ba5553dcbe3e548930945c40e13736deb3Makoto Onuki            }
17887894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank
1789e87ff6c3cbbfc5e3636f9827b58820652e3ea1c5Todd Kennedy            public Bundle autoDiscover(String userName, String password) {
179045f530ba5553dcbe3e548930945c40e13736deb3Makoto Onuki                return null;
179145f530ba5553dcbe3e548930945c40e13736deb3Makoto Onuki            }
17927894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank
1793e87ff6c3cbbfc5e3636f9827b58820652e3ea1c5Todd Kennedy            public void startSync(long mailboxId, boolean userRequest) {
179445f530ba5553dcbe3e548930945c40e13736deb3Makoto Onuki            }
17957894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank
1796e87ff6c3cbbfc5e3636f9827b58820652e3ea1c5Todd Kennedy            public void stopSync(long mailboxId) {
17977894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank            }
179845f530ba5553dcbe3e548930945c40e13736deb3Makoto Onuki
1799dc78a769fce18d259eccc602c4623fa74cdf5319Marc Blank            public void loadAttachment(long attachmentId, boolean background)
1800dc78a769fce18d259eccc602c4623fa74cdf5319Marc Blank                    throws RemoteException {
180145f530ba5553dcbe3e548930945c40e13736deb3Makoto Onuki                Attachment att = Attachment.restoreAttachmentWithId(ControllerService.this,
180245f530ba5553dcbe3e548930945c40e13736deb3Makoto Onuki                        attachmentId);
180345f530ba5553dcbe3e548930945c40e13736deb3Makoto Onuki                if (att != null) {
1804dc78a769fce18d259eccc602c4623fa74cdf5319Marc Blank                    if (Email.DEBUG) {
1805dc78a769fce18d259eccc602c4623fa74cdf5319Marc Blank                        Log.d(TAG, "loadAttachment " + attachmentId + ": " + att.mFileName);
1806dc78a769fce18d259eccc602c4623fa74cdf5319Marc Blank                    }
180745f530ba5553dcbe3e548930945c40e13736deb3Makoto Onuki                    Message msg = Message.restoreMessageWithId(ControllerService.this,
180845f530ba5553dcbe3e548930945c40e13736deb3Makoto Onuki                            att.mMessageKey);
180945f530ba5553dcbe3e548930945c40e13736deb3Makoto Onuki                    if (msg != null) {
181045f530ba5553dcbe3e548930945c40e13736deb3Makoto Onuki                        // If the message is a forward and the attachment needs downloading, we need
181145f530ba5553dcbe3e548930945c40e13736deb3Makoto Onuki                        // to retrieve the message from the source, rather than from the message
181245f530ba5553dcbe3e548930945c40e13736deb3Makoto Onuki                        // itself
181345f530ba5553dcbe3e548930945c40e13736deb3Makoto Onuki                        if ((msg.mFlags & Message.FLAG_TYPE_FORWARD) != 0) {
181445f530ba5553dcbe3e548930945c40e13736deb3Makoto Onuki                            String[] cols = Utility.getRowColumns(ControllerService.this,
181545f530ba5553dcbe3e548930945c40e13736deb3Makoto Onuki                                    Body.CONTENT_URI, BODY_SOURCE_KEY_PROJECTION, WHERE_MESSAGE_KEY,
181645f530ba5553dcbe3e548930945c40e13736deb3Makoto Onuki                                    new String[] {Long.toString(msg.mId)});
181745f530ba5553dcbe3e548930945c40e13736deb3Makoto Onuki                            if (cols != null) {
181845f530ba5553dcbe3e548930945c40e13736deb3Makoto Onuki                                msg = Message.restoreMessageWithId(ControllerService.this,
181945f530ba5553dcbe3e548930945c40e13736deb3Makoto Onuki                                        Long.parseLong(cols[BODY_SOURCE_KEY_COLUMN]));
182045f530ba5553dcbe3e548930945c40e13736deb3Makoto Onuki                                if (msg == null) {
18213b1cccf234cd95c3e1c4c568bc588ab06b06d3aaAndy Stadler                                    // TODO: We can try restoring from the deleted table here...
182245f530ba5553dcbe3e548930945c40e13736deb3Makoto Onuki                                    return;
182345f530ba5553dcbe3e548930945c40e13736deb3Makoto Onuki                                }
18247894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank                            }
18257894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank                        }
182645f530ba5553dcbe3e548930945c40e13736deb3Makoto Onuki                        MessagingController legacyController = sInstance.mLegacyController;
182745f530ba5553dcbe3e548930945c40e13736deb3Makoto Onuki                        LegacyListener legacyListener = sInstance.mLegacyListener;
182845f530ba5553dcbe3e548930945c40e13736deb3Makoto Onuki                        legacyController.loadAttachment(msg.mAccountKey, msg.mId, msg.mMailboxKey,
1829f92dd2bf3ea445db9b9a0eb9a447b5cbdb1a6e05Todd Kennedy                                attachmentId, legacyListener, background);
18302ac18339439631f2539a4cd35056b8ae65d5a24fMarc Blank                    } else {
18312ac18339439631f2539a4cd35056b8ae65d5a24fMarc Blank                        // Send back the specific error status for this case
18322ac18339439631f2539a4cd35056b8ae65d5a24fMarc Blank                        sInstance.mCallbackProxy.loadAttachmentStatus(att.mMessageKey, attachmentId,
18332ac18339439631f2539a4cd35056b8ae65d5a24fMarc Blank                                EmailServiceStatus.MESSAGE_NOT_FOUND, 0);
18347894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank                    }
18357894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank                }
18367894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank            }
18377894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank
1838e87ff6c3cbbfc5e3636f9827b58820652e3ea1c5Todd Kennedy            public void updateFolderList(long accountId) {
183945f530ba5553dcbe3e548930945c40e13736deb3Makoto Onuki            }
18407894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank
1841e87ff6c3cbbfc5e3636f9827b58820652e3ea1c5Todd Kennedy            public void hostChanged(long accountId) {
184245f530ba5553dcbe3e548930945c40e13736deb3Makoto Onuki            }
18437894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank
1844e87ff6c3cbbfc5e3636f9827b58820652e3ea1c5Todd Kennedy            public void setLogging(int flags) {
184545f530ba5553dcbe3e548930945c40e13736deb3Makoto Onuki            }
18467894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank
1847e87ff6c3cbbfc5e3636f9827b58820652e3ea1c5Todd Kennedy            public void sendMeetingResponse(long messageId, int response) {
184845f530ba5553dcbe3e548930945c40e13736deb3Makoto Onuki            }
18497894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank
1850e87ff6c3cbbfc5e3636f9827b58820652e3ea1c5Todd Kennedy            public void loadMore(long messageId) {
185145f530ba5553dcbe3e548930945c40e13736deb3Makoto Onuki            }
18527894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank
185345f530ba5553dcbe3e548930945c40e13736deb3Makoto Onuki            // The following three methods are not implemented in this version
1854e87ff6c3cbbfc5e3636f9827b58820652e3ea1c5Todd Kennedy            public boolean createFolder(long accountId, String name) {
185545f530ba5553dcbe3e548930945c40e13736deb3Makoto Onuki                return false;
185645f530ba5553dcbe3e548930945c40e13736deb3Makoto Onuki            }
18577894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank
1858e87ff6c3cbbfc5e3636f9827b58820652e3ea1c5Todd Kennedy            public boolean deleteFolder(long accountId, String name) {
185945f530ba5553dcbe3e548930945c40e13736deb3Makoto Onuki                return false;
186045f530ba5553dcbe3e548930945c40e13736deb3Makoto Onuki            }
18617894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank
1862e87ff6c3cbbfc5e3636f9827b58820652e3ea1c5Todd Kennedy            public boolean renameFolder(long accountId, String oldName, String newName) {
186345f530ba5553dcbe3e548930945c40e13736deb3Makoto Onuki                return false;
186445f530ba5553dcbe3e548930945c40e13736deb3Makoto Onuki            }
18657894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank
1866e87ff6c3cbbfc5e3636f9827b58820652e3ea1c5Todd Kennedy            public void setCallback(IEmailServiceCallback cb) {
186745f530ba5553dcbe3e548930945c40e13736deb3Makoto Onuki                sCallbackList.register(cb);
186845f530ba5553dcbe3e548930945c40e13736deb3Makoto Onuki            }
186964dcd7700e134d112c3bd7460539fc1c97a13eb2Marc Blank
1870e87ff6c3cbbfc5e3636f9827b58820652e3ea1c5Todd Kennedy            public void deleteAccountPIMData(long accountId) {
1871cf3fb71bc6fd795c10dc1d7467292532c19041aeMarc Blank            }
18720d4fc55861ed4393aa82f124f2865695ef564641Marc Blank
187375a754660e33c5e18cacffff193983ba22a7b9b0Marc Blank            public int searchMessages(long accountId, SearchParams searchParams,
187475a754660e33c5e18cacffff193983ba22a7b9b0Marc Blank                    long destMailboxId) {
1875c60b8d0529168edecf2376a6f421a0ae1e10fe29Marc Blank                return 0;
1876c60b8d0529168edecf2376a6f421a0ae1e10fe29Marc Blank            }
1877c60b8d0529168edecf2376a6f421a0ae1e10fe29Marc Blank
18780d4fc55861ed4393aa82f124f2865695ef564641Marc Blank            @Override
1879e87ff6c3cbbfc5e3636f9827b58820652e3ea1c5Todd Kennedy            public int getApiLevel() {
18800d4fc55861ed4393aa82f124f2865695ef564641Marc Blank                return Api.LEVEL;
18810d4fc55861ed4393aa82f124f2865695ef564641Marc Blank            }
188245f530ba5553dcbe3e548930945c40e13736deb3Makoto Onuki        };
18837894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank
188445f530ba5553dcbe3e548930945c40e13736deb3Makoto Onuki        @Override
188545f530ba5553dcbe3e548930945c40e13736deb3Makoto Onuki        public IBinder onBind(Intent intent) {
188645f530ba5553dcbe3e548930945c40e13736deb3Makoto Onuki            return mBinder;
188745f530ba5553dcbe3e548930945c40e13736deb3Makoto Onuki        }
18887894ee82b3a9f22d460a0c6f79e87be27686a649Marc Blank    }
1889bcec088320fc56cd5b5e1bbc706e4b7e304b95bfAndrew Stadler}
1890