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