/* * Copyright (C) 2010 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.email.activity; import com.android.email.Controller; import com.android.email.ControllerResultUiThreadWrapper; import com.android.email.Email; import com.android.emailcommon.Logging; import com.android.emailcommon.mail.MessagingException; import com.android.emailcommon.provider.Account; import com.android.emailcommon.provider.Mailbox; import com.android.emailcommon.utility.EmailAsyncTask; import com.android.emailcommon.utility.Utility; import android.content.Context; import android.os.Handler; import android.util.Log; /** * A class that finds a mailbox ID by account ID and mailbox type. * * If an account doesn't have a mailbox of a specified type, it refreshes the mailbox list and * try looking for again. * * This is a "one-shot" class. You create an instance, call {@link #startLookup}, get a result * or call {@link #cancel}, and that's it. The instance can't be re-used. */ public class MailboxFinder { private final Context mContext; private final Controller mController; // Actual Controller.Result that will wrapped by ControllerResultUiThreadWrapper. // Unit tests directly use it to avoid asynchronicity caused by ControllerResultUiThreadWrapper. private final ControllerResults mInnerControllerResults; private Controller.Result mControllerResults; // Not final, we null it out when done. private final long mAccountId; private final int mMailboxType; private final Callback mCallback; private FindMailboxTask mTask; private boolean mStarted; private boolean mClosed; /** * Callback for results. */ public interface Callback { public void onAccountNotFound(); public void onMailboxNotFound(long accountId); public void onAccountSecurityHold(long accountId); public void onMailboxFound(long accountId, long mailboxId); } /** * Creates an instance for {@code accountId} and {@code mailboxType}. (But won't start yet) * * Must be called on the UI thread. */ public MailboxFinder(Context context, long accountId, int mailboxType, Callback callback) { if (accountId == -1) { throw new UnsupportedOperationException(); } mContext = context.getApplicationContext(); mController = Controller.getInstance(context); mAccountId = accountId; mMailboxType = mailboxType; mCallback = callback; mInnerControllerResults = new ControllerResults(); mControllerResults = new ControllerResultUiThreadWrapper<ControllerResults>( new Handler(), mInnerControllerResults); mController.addResultCallback(mControllerResults); } /** * Start looking up. * * Must be called on the UI thread. */ public void startLookup() { if (mStarted) { throw new IllegalStateException(); // Can't start twice. } mStarted = true; mTask = new FindMailboxTask(true); mTask.executeParallel(); } /** * Cancel the operation. It's safe to call it multiple times, or even if the operation is * already finished. */ public void cancel() { if (!mClosed) { close(); } } /** * Stop the running task, if exists, and clean up internal resources. */ private void close() { mClosed = true; if (mControllerResults != null) { mController.removeResultCallback(mControllerResults); mControllerResults = null; } Utility.cancelTaskInterrupt(mTask); mTask = null; } private class ControllerResults extends Controller.Result { @Override public void updateMailboxListCallback(MessagingException result, long accountId, int progress) { if (mClosed || (accountId != mAccountId)) { return; // Already closed, or non-target account. } Log.i(Logging.LOG_TAG, "MailboxFinder: updateMailboxListCallback"); if (result != null) { // Error while updating the mailbox list. Notify the UI... try { mCallback.onMailboxNotFound(mAccountId); } finally { close(); } } else if (progress == 100) { // Mailbox list updated, look for mailbox again... mTask = new FindMailboxTask(false); mTask.executeParallel(); } } } /** * Async task for finding a single mailbox by type. If a mailbox of a type is not found, * and {@code okToRecurse} is true, we update the mailbox list and try looking again. */ private class FindMailboxTask extends EmailAsyncTask<Void, Void, Long> { private final boolean mOkToRecurse; private static final int RESULT_MAILBOX_FOUND = 0; private static final int RESULT_ACCOUNT_SECURITY_HOLD = 1; private static final int RESULT_ACCOUNT_NOT_FOUND = 2; private static final int RESULT_MAILBOX_NOT_FOUND = 3; private static final int RESULT_START_NETWORK_LOOK_UP = 4; private int mResult = -1; /** * Special constructor to cache some local info */ public FindMailboxTask(boolean okToRecurse) { super(null); mOkToRecurse = okToRecurse; } @Override protected Long doInBackground(Void... params) { // Quick check that account is not in security hold if (Account.isSecurityHold(mContext, mAccountId)) { mResult = RESULT_ACCOUNT_SECURITY_HOLD; return Mailbox.NO_MAILBOX; } // See if we can find the requested mailbox in the DB. long mailboxId = Mailbox.findMailboxOfType(mContext, mAccountId, mMailboxType); if (mailboxId != Mailbox.NO_MAILBOX) { mResult = RESULT_MAILBOX_FOUND; return mailboxId; // Found } // Mailbox not found. Does the account really exists? final boolean accountExists = Account.isValidId(mContext, mAccountId); if (accountExists) { if (mOkToRecurse) { // launch network lookup mResult = RESULT_START_NETWORK_LOOK_UP; } else { mResult = RESULT_MAILBOX_NOT_FOUND; } } else { mResult = RESULT_ACCOUNT_NOT_FOUND; } return Mailbox.NO_MAILBOX; } @Override protected void onSuccess(Long mailboxId) { switch (mResult) { case RESULT_ACCOUNT_SECURITY_HOLD: Log.w(Logging.LOG_TAG, "MailboxFinder: Account security hold."); try { mCallback.onAccountSecurityHold(mAccountId); } finally { close(); } return; case RESULT_ACCOUNT_NOT_FOUND: Log.w(Logging.LOG_TAG, "MailboxFinder: Account not found."); try { mCallback.onAccountNotFound(); } finally { close(); } return; case RESULT_MAILBOX_NOT_FOUND: Log.w(Logging.LOG_TAG, "MailboxFinder: Mailbox not found."); try { mCallback.onMailboxNotFound(mAccountId); } finally { close(); } return; case RESULT_MAILBOX_FOUND: if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) { Log.d(Logging.LOG_TAG, "MailboxFinder: mailbox found: id=" + mailboxId); } try { mCallback.onMailboxFound(mAccountId, mailboxId); } finally { close(); } return; case RESULT_START_NETWORK_LOOK_UP: // Not found locally. Let's sync the mailbox list... Log.i(Logging.LOG_TAG, "MailboxFinder: Starting network lookup."); mController.updateMailboxList(mAccountId); return; default: throw new RuntimeException(); } } } /* package */ boolean isStartedForTest() { return mStarted; } /** * Called by unit test. Return true if all the internal resources are really released. */ /* package */ boolean isReallyClosedForTest() { return mClosed && (mTask == null) && (mControllerResults == null); } /* package */ Controller.Result getControllerResultsForTest() { return mInnerControllerResults; } }