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