• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
actionValidateSettings(Activity fromActivity, EmailContent.Account account, boolean checkIncoming, boolean checkOutgoing)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 
actionAutoDiscover(Activity fromActivity, EmailContent.Account account, String userName, String password)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 
AutoDiscoverAuthenticationException(String message)111         public AutoDiscoverAuthenticationException(String message) {
112             super(message);
113         }
114     }
115 
116     @Override
onCreate(Bundle savedInstanceState)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
onDestroy()274     public void onDestroy() {
275         super.onDestroy();
276         mDestroyed = true;
277         mCanceled = true;
278     }
279 
setMessage(final int resId)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      */
showErrorDialog(final boolean autoDiscoverAuthException, final int msgResId, final Object... args)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      */
showSecurityRequiredDialog()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 
onCancel()369     private void onCancel() {
370         mCanceled = true;
371         setMessage(R.string.account_setup_check_settings_canceling_msg);
372     }
373 
onClick(View v)374     public void onClick(View v) {
375         switch (v.getId()) {
376             case R.id.cancel:
377                 onCancel();
378                 break;
379         }
380     }
381 }
382