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 17 package com.android.email.activity; 18 19 import com.android.email.Controller; 20 import com.android.email.ControllerResultUiThreadWrapper; 21 import com.android.email.Email; 22 import com.android.emailcommon.Logging; 23 import com.android.emailcommon.mail.MessagingException; 24 import com.android.emailcommon.provider.Account; 25 import com.android.emailcommon.provider.Mailbox; 26 import com.android.emailcommon.utility.EmailAsyncTask; 27 import com.android.emailcommon.utility.Utility; 28 29 import android.content.Context; 30 import android.os.Handler; 31 import 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 */ 42 public 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 { onAccountNotFound()63 public void onAccountNotFound(); onMailboxNotFound(long accountId)64 public void onMailboxNotFound(long accountId); onAccountSecurityHold(long accountId)65 public void onAccountSecurityHold(long accountId); onMailboxFound(long accountId, long mailboxId)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 */ MailboxFinder(Context context, long accountId, int mailboxType, Callback callback)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 */ startLookup()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.executeParallel(); 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 */ cancel()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 */ close()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 updateMailboxListCallback(MessagingException result, long accountId, int progress)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.executeParallel(); 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 EmailAsyncTask<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 */ FindMailboxTask(boolean okToRecurse)167 public FindMailboxTask(boolean okToRecurse) { 168 super(null); 169 mOkToRecurse = okToRecurse; 170 } 171 172 @Override doInBackground(Void... params)173 protected Long doInBackground(Void... params) { 174 // Quick check that account is not in security hold 175 if (Account.isSecurityHold(mContext, mAccountId)) { 176 mResult = RESULT_ACCOUNT_SECURITY_HOLD; 177 return Mailbox.NO_MAILBOX; 178 } 179 180 // See if we can find the requested mailbox in the DB. 181 long mailboxId = Mailbox.findMailboxOfType(mContext, mAccountId, mMailboxType); 182 if (mailboxId != Mailbox.NO_MAILBOX) { 183 mResult = RESULT_MAILBOX_FOUND; 184 return mailboxId; // Found 185 } 186 187 // Mailbox not found. Does the account really exists? 188 final boolean accountExists = Account.isValidId(mContext, mAccountId); 189 if (accountExists) { 190 if (mOkToRecurse) { 191 // launch network lookup 192 mResult = RESULT_START_NETWORK_LOOK_UP; 193 } else { 194 mResult = RESULT_MAILBOX_NOT_FOUND; 195 } 196 } else { 197 mResult = RESULT_ACCOUNT_NOT_FOUND; 198 } 199 return Mailbox.NO_MAILBOX; 200 } 201 202 @Override onSuccess(Long mailboxId)203 protected void onSuccess(Long mailboxId) { 204 switch (mResult) { 205 case RESULT_ACCOUNT_SECURITY_HOLD: 206 Log.w(Logging.LOG_TAG, "MailboxFinder: Account security hold."); 207 try { 208 mCallback.onAccountSecurityHold(mAccountId); 209 } finally { 210 close(); 211 } 212 return; 213 case RESULT_ACCOUNT_NOT_FOUND: 214 Log.w(Logging.LOG_TAG, "MailboxFinder: Account not found."); 215 try { 216 mCallback.onAccountNotFound(); 217 } finally { 218 close(); 219 } 220 return; 221 case RESULT_MAILBOX_NOT_FOUND: 222 Log.w(Logging.LOG_TAG, "MailboxFinder: Mailbox not found."); 223 try { 224 mCallback.onMailboxNotFound(mAccountId); 225 } finally { 226 close(); 227 } 228 return; 229 case RESULT_MAILBOX_FOUND: 230 if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) { 231 Log.d(Logging.LOG_TAG, "MailboxFinder: mailbox found: id=" + mailboxId); 232 } 233 try { 234 mCallback.onMailboxFound(mAccountId, mailboxId); 235 } finally { 236 close(); 237 } 238 return; 239 case RESULT_START_NETWORK_LOOK_UP: 240 // Not found locally. Let's sync the mailbox list... 241 Log.i(Logging.LOG_TAG, "MailboxFinder: Starting network lookup."); 242 mController.updateMailboxList(mAccountId); 243 return; 244 default: 245 throw new RuntimeException(); 246 } 247 } 248 } 249 isStartedForTest()250 /* package */ boolean isStartedForTest() { 251 return mStarted; 252 } 253 254 /** 255 * Called by unit test. Return true if all the internal resources are really released. 256 */ isReallyClosedForTest()257 /* package */ boolean isReallyClosedForTest() { 258 return mClosed && (mTask == null) && (mControllerResults == null); 259 } 260 getControllerResultsForTest()261 /* package */ Controller.Result getControllerResultsForTest() { 262 return mInnerControllerResults; 263 } 264 } 265