MailboxFinder.java revision 53ea83ebf91f820692e8fa8e781f5cc982dd94db
1/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.email.activity;
18
19import com.android.email.Controller;
20import com.android.email.ControllerResultUiThreadWrapper;
21import com.android.email.Email;
22import com.android.emailcommon.Logging;
23import com.android.emailcommon.mail.MessagingException;
24import com.android.emailcommon.provider.EmailContent.Account;
25import com.android.emailcommon.provider.Mailbox;
26import com.android.emailcommon.utility.Utility;
27
28import android.content.Context;
29import android.os.AsyncTask;
30import android.os.Handler;
31import android.util.Log;
32
33/**
34 * A class that finds a mailbox ID by account ID and mailbox type.
35 *
36 * If an account doesn't have a mailbox of a specified type, it refreshes the mailbox list and
37 * try looking for again.
38 *
39 * This is a "one-shot" class.  You create an instance, call {@link #startLookup}, get a result
40 * or call {@link #cancel}, and that's it.  The instance can't be re-used.
41 */
42public class MailboxFinder {
43    private final Context mContext;
44    private final Controller mController;
45
46    // Actual Controller.Result that will wrapped by ControllerResultUiThreadWrapper.
47    // Unit tests directly use it to avoid asynchronicity caused by ControllerResultUiThreadWrapper.
48    private final ControllerResults mInnerControllerResults;
49    private Controller.Result mControllerResults; // Not final, we null it out when done.
50
51    private final long mAccountId;
52    private final int mMailboxType;
53    private final Callback mCallback;
54
55    private FindMailboxTask mTask;
56    private boolean mStarted;
57    private boolean mClosed;
58
59    /**
60     * Callback for results.
61     */
62    public interface Callback {
63        public void onAccountNotFound();
64        public void onMailboxNotFound(long accountId);
65        public void onAccountSecurityHold(long accountId);
66        public void onMailboxFound(long accountId, long mailboxId);
67    }
68
69    /**
70     * Creates an instance for {@code accountId} and {@code mailboxType}.  (But won't start yet)
71     *
72     * Must be called on the UI thread.
73     */
74    public MailboxFinder(Context context, long accountId, int mailboxType, Callback callback) {
75        if (accountId == -1) {
76            throw new UnsupportedOperationException();
77        }
78        mContext = context.getApplicationContext();
79        mController = Controller.getInstance(context);
80        mAccountId = accountId;
81        mMailboxType = mailboxType;
82        mCallback = callback;
83        mInnerControllerResults = new ControllerResults();
84        mControllerResults = new ControllerResultUiThreadWrapper<ControllerResults>(
85                new Handler(), mInnerControllerResults);
86        mController.addResultCallback(mControllerResults);
87    }
88
89    /**
90     * Start looking up.
91     *
92     * Must be called on the UI thread.
93     */
94    public void startLookup() {
95        if (mStarted) {
96            throw new IllegalStateException(); // Can't start twice.
97        }
98        mStarted = true;
99        mTask = new FindMailboxTask(true);
100        mTask.execute();
101    }
102
103    /**
104     * Cancel the operation.  It's safe to call it multiple times, or even if the operation is
105     * already finished.
106     */
107    public void cancel() {
108        if (!mClosed) {
109            close();
110        }
111    }
112
113    /**
114     * Stop the running task, if exists, and clean up internal resources.
115     */
116    private void close() {
117        mClosed = true;
118        if (mControllerResults != null) {
119            mController.removeResultCallback(mControllerResults);
120            mControllerResults = null;
121        }
122        Utility.cancelTaskInterrupt(mTask);
123        mTask = null;
124    }
125
126    private class ControllerResults extends Controller.Result {
127        @Override
128        public void updateMailboxListCallback(MessagingException result, long accountId,
129                int progress) {
130            if (mClosed || (accountId != mAccountId)) {
131                return; // Already closed, or non-target account.
132            }
133            Log.i(Logging.LOG_TAG, "MailboxFinder: updateMailboxListCallback");
134            if (result != null) {
135                // Error while updating the mailbox list.  Notify the UI...
136                try {
137                    mCallback.onMailboxNotFound(mAccountId);
138                } finally {
139                    close();
140                }
141            } else if (progress == 100) {
142                // Mailbox list updated, look for mailbox again...
143                mTask = new FindMailboxTask(false);
144                mTask.execute();
145            }
146        }
147    }
148
149    /**
150     * Async task for finding a single mailbox by type.  If a mailbox of a type is not found,
151     * and {@code okToRecurse} is true, we update the mailbox list and try looking again.
152     */
153    private class FindMailboxTask extends AsyncTask<Void, Void, Long> {
154        private final boolean mOkToRecurse;
155
156        private static final int RESULT_MAILBOX_FOUND = 0;
157        private static final int RESULT_ACCOUNT_SECURITY_HOLD = 1;
158        private static final int RESULT_ACCOUNT_NOT_FOUND = 2;
159        private static final int RESULT_MAILBOX_NOT_FOUND = 3;
160        private static final int RESULT_START_NETWORK_LOOK_UP = 4;
161
162        private int mResult = -1;
163
164        /**
165         * Special constructor to cache some local info
166         */
167        public FindMailboxTask(boolean okToRecurse) {
168            mOkToRecurse = okToRecurse;
169        }
170
171        @Override
172        protected Long doInBackground(Void... params) {
173            // Quick check that account is not in security hold
174            if (Account.isSecurityHold(mContext, mAccountId)) {
175                mResult = RESULT_ACCOUNT_SECURITY_HOLD;
176                return Mailbox.NO_MAILBOX;
177            }
178
179            // See if we can find the requested mailbox in the DB.
180            long mailboxId = Mailbox.findMailboxOfType(mContext, mAccountId, mMailboxType);
181            if (mailboxId != Mailbox.NO_MAILBOX) {
182                mResult = RESULT_MAILBOX_FOUND;
183                return mailboxId; // Found
184            }
185
186            // Mailbox not found.  Does the account really exists?
187            final boolean accountExists = Account.isValidId(mContext, mAccountId);
188            if (accountExists) {
189                if (mOkToRecurse) {
190                    // launch network lookup
191                    mResult = RESULT_START_NETWORK_LOOK_UP;
192                } else {
193                    mResult = RESULT_MAILBOX_NOT_FOUND;
194                }
195            } else {
196                mResult = RESULT_ACCOUNT_NOT_FOUND;
197            }
198            return Mailbox.NO_MAILBOX;
199        }
200
201        @Override
202        protected void onPostExecute(Long mailboxId) {
203            if (isCancelled()) {
204                return;
205            }
206            switch (mResult) {
207                case RESULT_ACCOUNT_SECURITY_HOLD:
208                    Log.w(Logging.LOG_TAG, "MailboxFinder: Account security hold.");
209                    try {
210                        mCallback.onAccountSecurityHold(mAccountId);
211                    } finally {
212                        close();
213                    }
214                    return;
215                case RESULT_ACCOUNT_NOT_FOUND:
216                    Log.w(Logging.LOG_TAG, "MailboxFinder: Account not found.");
217                    try {
218                        mCallback.onAccountNotFound();
219                    } finally {
220                        close();
221                    }
222                    return;
223                case RESULT_MAILBOX_NOT_FOUND:
224                    Log.w(Logging.LOG_TAG, "MailboxFinder: Mailbox not found.");
225                    try {
226                        mCallback.onMailboxNotFound(mAccountId);
227                    } finally {
228                        close();
229                    }
230                    return;
231                case RESULT_MAILBOX_FOUND:
232                    if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) {
233                        Log.d(Logging.LOG_TAG, "MailboxFinder: mailbox found: id=" + mailboxId);
234                    }
235                    try {
236                        mCallback.onMailboxFound(mAccountId, mailboxId);
237                    } finally {
238                        close();
239                    }
240                    return;
241                case RESULT_START_NETWORK_LOOK_UP:
242                    // Not found locally.  Let's sync the mailbox list...
243                    Log.i(Logging.LOG_TAG, "MailboxFinder: Starting network lookup.");
244                    mController.updateMailboxList(mAccountId);
245                    return;
246                default:
247                    throw new RuntimeException();
248            }
249        }
250    }
251
252    /* package */ boolean isStartedForTest() {
253        return mStarted;
254    }
255
256    /**
257     * Called by unit test.  Return true if all the internal resources are really released.
258     */
259    /* package */ boolean isReallyClosedForTest() {
260        return mClosed && (mTask == null) && (mControllerResults == null);
261    }
262
263    /* package */ Controller.Result getControllerResultsForTest() {
264        return mInnerControllerResults;
265    }
266}
267