Home | History | Annotate | Download | only in setup
      1 /*
      2  * Copyright (C) 2008 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.setup;
     18 
     19 import com.android.email.Email;
     20 import com.android.email.R;
     21 import com.android.email.mail.AuthenticationFailedException;
     22 import com.android.email.mail.CertificateValidationException;
     23 import com.android.email.mail.MessagingException;
     24 import com.android.email.mail.Sender;
     25 import com.android.email.mail.Store;
     26 import com.android.email.provider.EmailContent;
     27 import com.android.email.service.EmailServiceProxy;
     28 
     29 import android.app.Activity;
     30 import android.app.AlertDialog;
     31 import android.content.DialogInterface;
     32 import android.content.Intent;
     33 import android.os.Bundle;
     34 import android.os.Handler;
     35 import android.os.Process;
     36 import android.util.Log;
     37 import android.view.View;
     38 import android.view.View.OnClickListener;
     39 import android.widget.Button;
     40 import android.widget.ProgressBar;
     41 import android.widget.TextView;
     42 
     43 /**
     44  * Checks the given settings to make sure that they can be used to send and
     45  * receive mail.
     46  *
     47  * XXX NOTE: The manifest for this activity has it ignore config changes, because
     48  * it doesn't correctly deal with restarting while its thread is running.
     49  * Do not attempt to define orientation-specific resources, they won't be loaded.
     50  */
     51 public class AccountSetupCheckSettings extends Activity implements OnClickListener {
     52 
     53     // If true, returns immediately as if account was OK
     54     private static final boolean DBG_SKIP_CHECK_OK = false;     // DO NOT CHECK IN WHILE TRUE
     55     // If true, returns immediately as if account was not OK
     56     private static final boolean DBG_SKIP_CHECK_ERR = false;    // DO NOT CHECK IN WHILE TRUE
     57     // If true, performs real check(s), but always returns fixed OK result
     58     private static final boolean DBG_FORCE_RESULT_OK = false;   // DO NOT CHECK IN WHILE TRUE
     59 
     60     private static final String EXTRA_ACCOUNT = "account";
     61     private static final String EXTRA_CHECK_INCOMING = "checkIncoming";
     62     private static final String EXTRA_CHECK_OUTGOING = "checkOutgoing";
     63     private static final String EXTRA_AUTO_DISCOVER = "autoDiscover";
     64     private static final String EXTRA_AUTO_DISCOVER_USERNAME = "userName";
     65     private static final String EXTRA_AUTO_DISCOVER_PASSWORD = "password";
     66 
     67     public static final int REQUEST_CODE_VALIDATE = 1;
     68     public static final int REQUEST_CODE_AUTO_DISCOVER = 2;
     69 
     70     // We'll define special result codes for certain types of connection results
     71     public static final int RESULT_AUTO_DISCOVER_AUTH_FAILED = Activity.RESULT_FIRST_USER;
     72     public static final int RESULT_SECURITY_REQUIRED_USER_CANCEL = Activity.RESULT_FIRST_USER + 1;
     73 
     74     private final Handler mHandler = new Handler();
     75     private ProgressBar mProgressBar;
     76     private TextView mMessageView;
     77 
     78     private EmailContent.Account mAccount;
     79     private boolean mCheckIncoming;
     80     private boolean mCheckOutgoing;
     81     private boolean mAutoDiscover;
     82     private boolean mCanceled;
     83     private boolean mDestroyed;
     84 
     85     public static void actionValidateSettings(Activity fromActivity, EmailContent.Account account,
     86             boolean checkIncoming, boolean checkOutgoing) {
     87         Intent i = new Intent(fromActivity, AccountSetupCheckSettings.class);
     88         i.putExtra(EXTRA_ACCOUNT, account);
     89         i.putExtra(EXTRA_CHECK_INCOMING, checkIncoming);
     90         i.putExtra(EXTRA_CHECK_OUTGOING, checkOutgoing);
     91         fromActivity.startActivityForResult(i, REQUEST_CODE_VALIDATE);
     92     }
     93 
     94     public static void actionAutoDiscover(Activity fromActivity, EmailContent.Account account,
     95             String userName, String password) {
     96         Intent i = new Intent(fromActivity, AccountSetupCheckSettings.class);
     97         i.putExtra(EXTRA_ACCOUNT, account);
     98         i.putExtra(EXTRA_AUTO_DISCOVER, true);
     99         i.putExtra(EXTRA_AUTO_DISCOVER_USERNAME, userName);
    100         i.putExtra(EXTRA_AUTO_DISCOVER_PASSWORD, password);
    101         fromActivity.startActivityForResult(i, REQUEST_CODE_AUTO_DISCOVER);
    102     }
    103 
    104     /**
    105      * We create this simple class so that showErrorDialog can differentiate between a regular
    106      * auth error and an auth error during the autodiscover sequence and respond appropriately
    107      */
    108     private class AutoDiscoverAuthenticationException extends AuthenticationFailedException {
    109         private static final long serialVersionUID = 1L;
    110 
    111         public AutoDiscoverAuthenticationException(String message) {
    112             super(message);
    113         }
    114     }
    115 
    116     @Override
    117     public void onCreate(Bundle savedInstanceState) {
    118         super.onCreate(savedInstanceState);
    119         setContentView(R.layout.account_setup_check_settings);
    120         mMessageView = (TextView)findViewById(R.id.message);
    121         mProgressBar = (ProgressBar)findViewById(R.id.progress);
    122         ((Button)findViewById(R.id.cancel)).setOnClickListener(this);
    123 
    124         setMessage(R.string.account_setup_check_settings_retr_info_msg);
    125         mProgressBar.setIndeterminate(true);
    126 
    127         // For debugging UI only, force a true or false result - don't actually try connection
    128         if (DBG_SKIP_CHECK_OK || DBG_SKIP_CHECK_ERR) {
    129             setResult(DBG_SKIP_CHECK_OK ? RESULT_OK : RESULT_CANCELED);
    130             finish();
    131             return;
    132         }
    133 
    134         final Intent intent = getIntent();
    135         mAccount = (EmailContent.Account)intent.getParcelableExtra(EXTRA_ACCOUNT);
    136         mCheckIncoming = intent.getBooleanExtra(EXTRA_CHECK_INCOMING, false);
    137         mCheckOutgoing = intent.getBooleanExtra(EXTRA_CHECK_OUTGOING, false);
    138         mAutoDiscover = intent.getBooleanExtra(EXTRA_AUTO_DISCOVER, false);
    139 
    140         new Thread() {
    141             @Override
    142             public void run() {
    143                 Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
    144                 try {
    145                     if (mDestroyed) {
    146                         return;
    147                     }
    148                     if (mCanceled) {
    149                         finish();
    150                         return;
    151                     }
    152                     if (mAutoDiscover) {
    153                         String userName = intent.getStringExtra(EXTRA_AUTO_DISCOVER_USERNAME);
    154                         String password = intent.getStringExtra(EXTRA_AUTO_DISCOVER_PASSWORD);
    155                         Log.d(Email.LOG_TAG, "Begin auto-discover for " + userName);
    156                         Store store = Store.getInstance(
    157                                 mAccount.getStoreUri(AccountSetupCheckSettings.this),
    158                                 getApplication(), null);
    159                         Bundle result = store.autoDiscover(AccountSetupCheckSettings.this,
    160                                 userName, password);
    161                         // Result will be null if there was a remote exception
    162                         // Otherwise, we can check the exception code and handle auth failed
    163                         // Other errors will be ignored, and the user will be taken to manual
    164                         // setup
    165                         if (result != null) {
    166                             int errorCode =
    167                                 result.getInt(EmailServiceProxy.AUTO_DISCOVER_BUNDLE_ERROR_CODE);
    168                             if (errorCode == MessagingException.AUTHENTICATION_FAILED) {
    169                                 throw new AutoDiscoverAuthenticationException(null);
    170                             } else if (errorCode != MessagingException.NO_ERROR) {
    171                                 setResult(RESULT_OK);
    172                                 finish();
    173                             }
    174                             // The success case is here
    175                             Intent resultIntent = new Intent();
    176                             resultIntent.putExtra("HostAuth", result.getParcelable(
    177                                     EmailServiceProxy.AUTO_DISCOVER_BUNDLE_HOST_AUTH));
    178                             setResult(RESULT_OK, resultIntent);
    179                             finish();
    180                             // auto-discover is never combined with other ops, so exit now
    181                             return;
    182                        }
    183                     }
    184                     if (mDestroyed) {
    185                         return;
    186                     }
    187                     if (mCanceled) {
    188                         finish();
    189                         return;
    190                     }
    191                     if (mCheckIncoming) {
    192                         Log.d(Email.LOG_TAG, "Begin check of incoming email settings");
    193                         setMessage(R.string.account_setup_check_settings_check_incoming_msg);
    194                         Store store = Store.getInstance(
    195                                 mAccount.getStoreUri(AccountSetupCheckSettings.this),
    196                                 getApplication(), null);
    197                         store.checkSettings();
    198                     }
    199                     if (mDestroyed) {
    200                         return;
    201                     }
    202                     if (mCanceled) {
    203                         finish();
    204                         return;
    205                     }
    206                     if (mCheckOutgoing) {
    207                         Log.d(Email.LOG_TAG, "Begin check of outgoing email settings");
    208                         setMessage(R.string.account_setup_check_settings_check_outgoing_msg);
    209                         Sender sender = Sender.getInstance(getApplication(),
    210                                 mAccount.getSenderUri(AccountSetupCheckSettings.this));
    211                         sender.close();
    212                         sender.open();
    213                         sender.close();
    214                     }
    215                     if (mDestroyed) {
    216                         return;
    217                     }
    218                     setResult(RESULT_OK);
    219                     finish();
    220                 } catch (final AuthenticationFailedException afe) {
    221                     // Could be two separate blocks (one for AutoDiscover) but this way we save
    222                     // some code
    223                     String message = afe.getMessage();
    224                     int id = (message == null)
    225                             ? R.string.account_setup_failed_dlg_auth_message
    226                             : R.string.account_setup_failed_dlg_auth_message_fmt;
    227                     showErrorDialog(afe instanceof AutoDiscoverAuthenticationException,
    228                             id, message);
    229                 } catch (final CertificateValidationException cve) {
    230                     String message = cve.getMessage();
    231                     int id = (message == null)
    232                         ? R.string.account_setup_failed_dlg_certificate_message
    233                         : R.string.account_setup_failed_dlg_certificate_message_fmt;
    234                     showErrorDialog(false, id, message);
    235                 } catch (final MessagingException me) {
    236                     int exceptionType = me.getExceptionType();
    237                     // Check for non-fatal errors first
    238                     if (exceptionType == MessagingException.SECURITY_POLICIES_REQUIRED) {
    239                         showSecurityRequiredDialog();
    240                         return;
    241                     }
    242                     // Handle fatal errors
    243                     int id;
    244                     String message = me.getMessage();
    245                     switch (exceptionType) {
    246                         case MessagingException.IOERROR:
    247                             id = R.string.account_setup_failed_ioerror;
    248                             break;
    249                         case MessagingException.TLS_REQUIRED:
    250                             id = R.string.account_setup_failed_tls_required;
    251                             break;
    252                         case MessagingException.AUTH_REQUIRED:
    253                             id = R.string.account_setup_failed_auth_required;
    254                             break;
    255                         case MessagingException.SECURITY_POLICIES_UNSUPPORTED:
    256                             id = R.string.account_setup_failed_security_policies_unsupported;
    257                             break;
    258                         case MessagingException.GENERAL_SECURITY:
    259                             id = R.string.account_setup_failed_security;
    260                             break;
    261                         default:
    262                             id = (message == null)
    263                                     ? R.string.account_setup_failed_dlg_server_message
    264                                     : R.string.account_setup_failed_dlg_server_message_fmt;
    265                             break;
    266                     }
    267                     showErrorDialog(false, id, message);
    268                 }
    269             }
    270         }.start();
    271     }
    272 
    273     @Override
    274     public void onDestroy() {
    275         super.onDestroy();
    276         mDestroyed = true;
    277         mCanceled = true;
    278     }
    279 
    280     private void setMessage(final int resId) {
    281         mHandler.post(new Runnable() {
    282             public void run() {
    283                 if (mDestroyed) {
    284                     return;
    285                 }
    286                 mMessageView.setText(getString(resId));
    287             }
    288         });
    289     }
    290 
    291     /**
    292      * The first argument here indicates whether we return an OK result or a cancelled result
    293      * An OK result is used by Exchange to indicate a failed authentication via AutoDiscover
    294      * In that case, we'll end up returning to the AccountSetupBasic screen
    295      */
    296     private void showErrorDialog(final boolean autoDiscoverAuthException, final int msgResId,
    297             final Object... args) {
    298         mHandler.post(new Runnable() {
    299             public void run() {
    300                 if (mDestroyed) {
    301                     return;
    302                 }
    303                 mProgressBar.setIndeterminate(false);
    304                 new AlertDialog.Builder(AccountSetupCheckSettings.this)
    305                         .setIcon(android.R.drawable.ic_dialog_alert)
    306                         .setTitle(getString(R.string.account_setup_failed_dlg_title))
    307                         .setMessage(getString(msgResId, args))
    308                         .setCancelable(true)
    309                         .setPositiveButton(
    310                                 getString(R.string.account_setup_failed_dlg_edit_details_action),
    311                                 new DialogInterface.OnClickListener() {
    312                                     public void onClick(DialogInterface dialog, int which) {
    313                                         if (autoDiscoverAuthException) {
    314                                             setResult(RESULT_AUTO_DISCOVER_AUTH_FAILED);
    315                                         } else if (DBG_FORCE_RESULT_OK) {
    316                                             setResult(RESULT_OK);
    317                                         }
    318                                         finish();
    319                                     }
    320                                 })
    321                         .show();
    322             }
    323         });
    324     }
    325 
    326     /**
    327      * Display a dialog asking the user if they are willing to accept control by the remote
    328      * server.  This converts the MessagingException.SECURITY_POLICIES_REQUIRED exception into an
    329      * Activity result of RESULT_OK, thus hiding the exception from the caller entirely.
    330      *
    331      * TODO: Perhaps use stronger button names than "OK" and "Cancel" (e.g. "Allow" / "Deny")
    332      */
    333     private void showSecurityRequiredDialog() {
    334         mHandler.post(new Runnable() {
    335             public void run() {
    336                 if (mDestroyed) {
    337                     return;
    338                 }
    339                 mProgressBar.setIndeterminate(false);
    340                 String host = mAccount.mHostAuthRecv.mAddress;
    341                 Object[] args = new String[] { host };
    342                 new AlertDialog.Builder(AccountSetupCheckSettings.this)
    343                         .setIcon(android.R.drawable.ic_dialog_alert)
    344                         .setTitle(getString(R.string.account_setup_security_required_title))
    345                         .setMessage(getString(
    346                                 R.string.account_setup_security_policies_required_fmt, args))
    347                         .setCancelable(true)
    348                         .setPositiveButton(
    349                                 getString(R.string.okay_action),
    350                                 new DialogInterface.OnClickListener() {
    351                                     public void onClick(DialogInterface dialog, int which) {
    352                                         setResult(RESULT_OK);
    353                                         finish();
    354                                     }
    355                                 })
    356                         .setNegativeButton(
    357                                 getString(R.string.cancel_action),
    358                                 new DialogInterface.OnClickListener() {
    359                                     public void onClick(DialogInterface dialog, int which) {
    360                                         setResult(RESULT_SECURITY_REQUIRED_USER_CANCEL);
    361                                         finish();
    362                                     }
    363                                 })
    364                         .show();
    365             }
    366         });
    367     }
    368 
    369     private void onCancel() {
    370         mCanceled = true;
    371         setMessage(R.string.account_setup_check_settings_canceling_msg);
    372     }
    373 
    374     public void onClick(View v) {
    375         switch (v.getId()) {
    376             case R.id.cancel:
    377                 onCancel();
    378                 break;
    379         }
    380     }
    381 }
    382