Home | History | Annotate | Download | only in activity
      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 {
     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.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      */
    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.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          */
    167         public FindMailboxTask(boolean okToRecurse) {
    168             super(null);
    169             mOkToRecurse = okToRecurse;
    170         }
    171 
    172         @Override
    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
    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 
    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      */
    257     /* package */ boolean isReallyClosedForTest() {
    258         return mClosed && (mTask == null) && (mControllerResults == null);
    259     }
    260 
    261     /* package */ Controller.Result getControllerResultsForTest() {
    262         return mInnerControllerResults;
    263     }
    264 }
    265