blob: ace59c3fb1a48edd2707ada2d6965d48213c5c01 [file] [log] [blame]
Andrew Stadler55110ca2010-09-07 22:20:51 -07001/*
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.setup;
18
Andrew Stadlerfd144962010-09-10 15:50:55 -070019import android.app.Activity;
Andrew Stadler55110ca2010-09-07 22:20:51 -070020import android.app.AlertDialog;
21import android.app.Dialog;
22import android.app.DialogFragment;
23import android.app.Fragment;
24import android.app.FragmentManager;
25import android.app.ProgressDialog;
26import android.content.Context;
27import android.content.DialogInterface;
28import android.os.AsyncTask;
29import android.os.Bundle;
Ben Komalo5a2d7da2011-04-13 16:07:50 -070030import android.text.TextUtils;
Andrew Stadler55110ca2010-09-07 22:20:51 -070031import android.util.Log;
32
Marc Blank35b0e952011-06-29 12:47:56 -070033import com.android.email.R;
34import com.android.email.mail.Sender;
35import com.android.email.mail.Store;
36import com.android.emailcommon.Logging;
37import com.android.emailcommon.mail.MessagingException;
38import com.android.emailcommon.provider.Account;
39import com.android.emailcommon.provider.HostAuth;
40import com.android.emailcommon.provider.Policy;
41import com.android.emailcommon.service.EmailServiceProxy;
42import com.android.emailcommon.utility.Utility;
43
Andrew Stadler55110ca2010-09-07 22:20:51 -070044/**
45 * Check incoming or outgoing settings, or perform autodiscovery.
46 *
47 * There are three components that work together. 1. This fragment is retained and non-displayed,
48 * and controls the overall process. 2. An AsyncTask that works with the stores/services to
49 * check the accounts settings. 3. A stateless progress dialog (which will be recreated on
50 * orientation changes).
51 *
52 * There are also two lightweight error dialogs which are used for notification of terminal
53 * conditions.
Andrew Stadler55110ca2010-09-07 22:20:51 -070054 */
55public class AccountCheckSettingsFragment extends Fragment {
Makoto Onuki49518bb2011-01-18 10:14:14 -080056
Andrew Stadler55110ca2010-09-07 22:20:51 -070057 public final static String TAG = "AccountCheckSettingsFragment";
58
59 // Debugging flags - for debugging the UI
Andrew Stadler2731aef2010-09-13 14:04:45 -070060 // If true, use a "fake" account check cycle
61 private static final boolean DEBUG_FAKE_CHECK_CYCLE = false; // DO NOT CHECK IN TRUE
62 // If true, use fake check cycle, return failure
63 private static final boolean DEBUG_FAKE_CHECK_ERR = false; // DO NOT CHECK IN TRUE
64 // If true, use fake check cycle, return "security required"
65 private static final boolean DEBUG_FORCE_SECURITY_REQUIRED = false; // DO NOT CHECK IN TRUE
Andrew Stadler55110ca2010-09-07 22:20:51 -070066
67 // State
68 private final static int STATE_START = 0;
69 private final static int STATE_CHECK_AUTODISCOVER = 1;
70 private final static int STATE_CHECK_INCOMING = 2;
71 private final static int STATE_CHECK_OUTGOING = 3;
Andrew Stadler2731aef2010-09-13 14:04:45 -070072 private final static int STATE_CHECK_OK = 4; // terminal
73 private final static int STATE_CHECK_SHOW_SECURITY = 5; // terminal
74 private final static int STATE_CHECK_ERROR = 6; // terminal
75 private final static int STATE_AUTODISCOVER_AUTH_DIALOG = 7; // terminal
76 private final static int STATE_AUTODISCOVER_RESULT = 8; // terminal
Andrew Stadler55110ca2010-09-07 22:20:51 -070077 private int mState = STATE_START;
78
79 // Support for UI
80 private boolean mAttached;
81 private CheckingDialog mCheckingDialog;
Ben Komalo715ee4e2011-09-13 11:34:22 -070082 private MessagingException mProgressException;
Andrew Stadlerfd144962010-09-10 15:50:55 -070083
Andrew Stadler55110ca2010-09-07 22:20:51 -070084 // Support for AsyncTask and account checking
85 AccountCheckTask mAccountCheckTask;
Andrew Stadler2731aef2010-09-13 14:04:45 -070086
87 // Result codes returned by onCheckSettingsComplete.
88 /** Check settings returned successfully */
89 public final static int CHECK_SETTINGS_OK = 0;
90 /** Check settings failed due to connection, authentication, or other server error */
91 public final static int CHECK_SETTINGS_SERVER_ERROR = 1;
92 /** Check settings failed due to user refusing to accept security requirements */
93 public final static int CHECK_SETTINGS_SECURITY_USER_DENY = 2;
Ben Komalo715ee4e2011-09-13 11:34:22 -070094 /** Check settings failed due to certificate being required - user needs to pick immediately. */
95 public final static int CHECK_SETTINGS_CLIENT_CERTIFICATE_NEEDED = 3;
Andrew Stadler2731aef2010-09-13 14:04:45 -070096
97 // Result codes returned by onAutoDiscoverComplete.
98 /** AutoDiscover completed successfully with server setup data */
99 public final static int AUTODISCOVER_OK = 0;
100 /** AutoDiscover completed with no data (no server or AD not supported) */
101 public final static int AUTODISCOVER_NO_DATA = 1;
102 /** AutoDiscover reported authentication error */
103 public final static int AUTODISCOVER_AUTHENTICATION = 2;
104
Andrew Stadler55110ca2010-09-07 22:20:51 -0700105 /**
Andrew Stadlerfd144962010-09-10 15:50:55 -0700106 * Callback interface for any target or activity doing account check settings
107 */
108 public interface Callbacks {
109 /**
Andrew Stadler2731aef2010-09-13 14:04:45 -0700110 * Called when CheckSettings completed
111 * @param result check settings result code - success is CHECK_SETTINGS_OK
Andrew Stadlerfd144962010-09-10 15:50:55 -0700112 */
Andrew Stadler2731aef2010-09-13 14:04:45 -0700113 public void onCheckSettingsComplete(int result);
114
115 /**
116 * Called when autodiscovery completes.
117 * @param result autodiscovery result code - success is AUTODISCOVER_OK
118 * @param hostAuth configuration data returned by AD server, or null if no data available
119 */
120 public void onAutoDiscoverComplete(int result, HostAuth hostAuth);
Andrew Stadlerfd144962010-09-10 15:50:55 -0700121 }
Andrew Stadler2731aef2010-09-13 14:04:45 -0700122
Andrew Stadlerfd144962010-09-10 15:50:55 -0700123 /**
Andrew Stadler55110ca2010-09-07 22:20:51 -0700124 * Create a retained, invisible fragment that checks accounts
125 *
126 * @param mode incoming or outgoing
127 */
128 public static AccountCheckSettingsFragment newInstance(int mode, Fragment parentFragment) {
129 AccountCheckSettingsFragment f = new AccountCheckSettingsFragment();
130 f.setTargetFragment(parentFragment, mode);
131 return f;
132 }
133
134 /**
135 * Fragment initialization. Because we never implement onCreateView, and call
136 * setRetainInstance here, this creates an invisible, persistent, "worker" fragment.
137 */
138 @Override
139 public void onCreate(Bundle savedInstanceState) {
140 super.onCreate(savedInstanceState);
141 setRetainInstance(true);
142 }
143
144 /**
145 * This is called when the Fragment's Activity is ready to go, after
146 * its content view has been installed; it is called both after
147 * the initial fragment creation and after the fragment is re-attached
148 * to a new activity.
149 */
150 @Override
151 public void onActivityCreated(Bundle savedInstanceState) {
152 super.onActivityCreated(savedInstanceState);
153 mAttached = true;
154
155 // If this is the first time, start the AsyncTask
156 if (mAccountCheckTask == null) {
157 int checkMode = getTargetRequestCode();
158 Account checkAccount = SetupData.getAccount();
Andrew Stadler55110ca2010-09-07 22:20:51 -0700159 mAccountCheckTask = (AccountCheckTask)
Andrew Stadler2731aef2010-09-13 14:04:45 -0700160 new AccountCheckTask(checkMode, checkAccount)
Makoto Onukibc2eaad2011-06-30 16:10:06 -0700161 .executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
Andrew Stadler55110ca2010-09-07 22:20:51 -0700162 }
Andy Stadlerf946ff02010-12-13 13:58:40 -0800163 }
Andrew Stadler55110ca2010-09-07 22:20:51 -0700164
Andy Stadlerf946ff02010-12-13 13:58:40 -0800165 /**
166 * When resuming, restart the progress/error UI if necessary by re-reporting previous values
167 */
168 @Override
169 public void onResume() {
170 super.onResume();
171
Andrew Stadler55110ca2010-09-07 22:20:51 -0700172 if (mState != STATE_START) {
Ben Komalo715ee4e2011-09-13 11:34:22 -0700173 reportProgress(mState, mProgressException);
Andrew Stadler55110ca2010-09-07 22:20:51 -0700174 }
175 }
176
177 /**
178 * This is called when the fragment is going away. It is NOT called
179 * when the fragment is being propagated between activity instances.
180 */
181 @Override
182 public void onDestroy() {
183 super.onDestroy();
184 Utility.cancelTaskInterrupt(mAccountCheckTask);
185 mAccountCheckTask = null;
186 }
187
188 /**
189 * This is called right before the fragment is detached from its current activity instance.
190 * All reporting and callbacks are halted until we reattach.
191 */
192 @Override
193 public void onDetach() {
194 super.onDetach();
195 mAttached = false;
196 }
197
198 /**
199 * The worker (AsyncTask) will call this (in the UI thread) to report progress. If we are
200 * attached to an activity, update the progress immediately; If not, simply hold the
201 * progress for later.
202 * @param newState The new progress state being reported
Andrew Stadler55110ca2010-09-07 22:20:51 -0700203 */
Ben Komalo715ee4e2011-09-13 11:34:22 -0700204 private void reportProgress(int newState, MessagingException ex) {
Andrew Stadler55110ca2010-09-07 22:20:51 -0700205 mState = newState;
Ben Komalo715ee4e2011-09-13 11:34:22 -0700206 mProgressException = ex;
Andrew Stadler55110ca2010-09-07 22:20:51 -0700207
208 // If we are attached, create, recover, and/or update the dialog
209 if (mAttached) {
210 FragmentManager fm = getFragmentManager();
211
Andrew Stadler2731aef2010-09-13 14:04:45 -0700212 switch (newState) {
213 case STATE_CHECK_OK:
214 // immediately terminate, clean up, and report back
215 // 1. get rid of progress dialog (if any)
216 recoverAndDismissCheckingDialog();
217 // 2. exit self
218 fm.popBackStack();
219 // 3. report OK back to target fragment or activity
220 getCallbackTarget().onCheckSettingsComplete(CHECK_SETTINGS_OK);
221 break;
222 case STATE_CHECK_SHOW_SECURITY:
223 // 1. get rid of progress dialog (if any)
224 recoverAndDismissCheckingDialog();
Andy Stadlerf946ff02010-12-13 13:58:40 -0800225 // 2. launch the error dialog, if needed
226 if (fm.findFragmentByTag(SecurityRequiredDialog.TAG) == null) {
Ben Komalo715ee4e2011-09-13 11:34:22 -0700227 String message = ex.getMessage();
228 if (message != null) {
229 message = message.trim();
230 }
Andy Stadlerf946ff02010-12-13 13:58:40 -0800231 SecurityRequiredDialog securityRequiredDialog =
Ben Komalo715ee4e2011-09-13 11:34:22 -0700232 SecurityRequiredDialog.newInstance(this, message);
Dianne Hackborn31e25762011-01-17 12:30:51 -0800233 fm.beginTransaction()
Andy Stadlerf946ff02010-12-13 13:58:40 -0800234 .add(securityRequiredDialog, SecurityRequiredDialog.TAG)
235 .commit();
236 }
Andrew Stadler2731aef2010-09-13 14:04:45 -0700237 break;
238 case STATE_CHECK_ERROR:
239 case STATE_AUTODISCOVER_AUTH_DIALOG:
240 // 1. get rid of progress dialog (if any)
241 recoverAndDismissCheckingDialog();
Andy Stadlerf946ff02010-12-13 13:58:40 -0800242 // 2. launch the error dialog, if needed
243 if (fm.findFragmentByTag(ErrorDialog.TAG) == null) {
Ben Komalo715ee4e2011-09-13 11:34:22 -0700244 ErrorDialog errorDialog = ErrorDialog.newInstance(
245 getActivity(), this, mProgressException);
Dianne Hackborn31e25762011-01-17 12:30:51 -0800246 fm.beginTransaction()
Andy Stadlerf946ff02010-12-13 13:58:40 -0800247 .add(errorDialog, ErrorDialog.TAG)
248 .commit();
249 }
Andrew Stadler2731aef2010-09-13 14:04:45 -0700250 break;
251 case STATE_AUTODISCOVER_RESULT:
Ben Komalo715ee4e2011-09-13 11:34:22 -0700252 HostAuth autoDiscoverResult = ((AutoDiscoverResults) ex).mHostAuth;
Andrew Stadler2731aef2010-09-13 14:04:45 -0700253 // 1. get rid of progress dialog (if any)
254 recoverAndDismissCheckingDialog();
255 // 2. exit self
256 fm.popBackStack();
257 // 3. report back to target fragment or activity
258 getCallbackTarget().onAutoDiscoverComplete(
Ben Komalo715ee4e2011-09-13 11:34:22 -0700259 (autoDiscoverResult != null) ? AUTODISCOVER_OK : AUTODISCOVER_NO_DATA,
260 autoDiscoverResult);
Andrew Stadler2731aef2010-09-13 14:04:45 -0700261 break;
262 default:
263 // Display a normal progress message
264 mCheckingDialog = (CheckingDialog) fm.findFragmentByTag(CheckingDialog.TAG);
Andrew Stadler55110ca2010-09-07 22:20:51 -0700265
Andrew Stadler2731aef2010-09-13 14:04:45 -0700266 if (mCheckingDialog == null) {
267 mCheckingDialog = CheckingDialog.newInstance(this, mState);
Dianne Hackborn31e25762011-01-17 12:30:51 -0800268 fm.beginTransaction()
Andrew Stadler2731aef2010-09-13 14:04:45 -0700269 .add(mCheckingDialog, CheckingDialog.TAG)
270 .commit();
271 } else {
272 mCheckingDialog.updateProgress(mState);
273 }
274 break;
Andrew Stadler55110ca2010-09-07 22:20:51 -0700275 }
276 }
277 }
278
279 /**
Andrew Stadlerfd144962010-09-10 15:50:55 -0700280 * Find the callback target, either a target fragment or the activity
281 */
282 private Callbacks getCallbackTarget() {
283 Fragment target = getTargetFragment();
284 if (target instanceof Callbacks) {
285 return (Callbacks) target;
286 }
287 Activity activity = getActivity();
288 if (activity instanceof Callbacks) {
289 return (Callbacks) activity;
290 }
291 throw new IllegalStateException();
Andrew Stadler2731aef2010-09-13 14:04:45 -0700292 }
Andrew Stadlerfd144962010-09-10 15:50:55 -0700293
294 /**
Andrew Stadler55110ca2010-09-07 22:20:51 -0700295 * Recover and dismiss the progress dialog fragment
296 */
297 private void recoverAndDismissCheckingDialog() {
298 if (mCheckingDialog == null) {
299 mCheckingDialog = (CheckingDialog)
300 getFragmentManager().findFragmentByTag(CheckingDialog.TAG);
301 }
302 if (mCheckingDialog != null) {
Andy Stadlera685d3b2011-03-15 15:02:48 -0700303 mCheckingDialog.dismissAllowingStateLoss();
Andrew Stadler55110ca2010-09-07 22:20:51 -0700304 mCheckingDialog = null;
305 }
306 }
307
308 /**
309 * This is called when the user clicks "cancel" on the progress dialog. Shuts everything
310 * down and dismisses everything.
311 * This should cause us to remain in the current screen (not accepting the settings)
312 */
313 private void onCheckingDialogCancel() {
314 // 1. kill the checker
315 Utility.cancelTaskInterrupt(mAccountCheckTask);
316 mAccountCheckTask = null;
317 // 2. kill self with no report - this is "cancel"
Ben Komalo7683a432011-09-15 10:56:34 -0700318 finish();
Andrew Stadler55110ca2010-09-07 22:20:51 -0700319 }
320
Ben Komalo715ee4e2011-09-13 11:34:22 -0700321 private void onEditCertificateOk() {
322 Callbacks callbackTarget = getCallbackTarget();
323 getCallbackTarget().onCheckSettingsComplete(CHECK_SETTINGS_CLIENT_CERTIFICATE_NEEDED);
324 finish();
325 }
326
Andrew Stadler55110ca2010-09-07 22:20:51 -0700327 /**
328 * This is called when the user clicks "edit" from the error dialog. The error dialog
329 * should have already dismissed itself.
Andrew Stadler2731aef2010-09-13 14:04:45 -0700330 * Depending on the context, the target will remain in the current activity (e.g. editing
331 * settings) or return to its own parent (e.g. enter new credentials).
Andrew Stadler55110ca2010-09-07 22:20:51 -0700332 */
333 private void onErrorDialogEditButton() {
Andy Stadler5f830f82011-03-10 14:56:10 -0800334 // 1. handle "edit" - notify callback that we had a problem with the test
Andrew Stadler2731aef2010-09-13 14:04:45 -0700335 Callbacks callbackTarget = getCallbackTarget();
336 if (mState == STATE_AUTODISCOVER_AUTH_DIALOG) {
337 // report auth error to target fragment or activity
338 callbackTarget.onAutoDiscoverComplete(AUTODISCOVER_AUTHENTICATION, null);
339 } else {
340 // report check settings failure to target fragment or activity
341 callbackTarget.onCheckSettingsComplete(CHECK_SETTINGS_SERVER_ERROR);
342 }
Ben Komalo715ee4e2011-09-13 11:34:22 -0700343 finish();
344 }
345
346 /** Kill self if not already killed. */
347 private void finish() {
Andy Stadler5f830f82011-03-10 14:56:10 -0800348 FragmentManager fm = getFragmentManager();
349 if (fm != null) {
350 fm.popBackStack();
351 }
Andrew Stadler55110ca2010-09-07 22:20:51 -0700352 }
353
354 /**
355 * This is called when the user clicks "ok" or "cancel" on the "security required" dialog.
356 * Shuts everything down and dismisses everything, and reports the result appropriately.
357 */
Andrew Stadler2731aef2010-09-13 14:04:45 -0700358 private void onSecurityRequiredDialogResultOk(boolean okPressed) {
359 // 1. handle OK/cancel - notify that security is OK and we can proceed
360 Callbacks callbackTarget = getCallbackTarget();
361 callbackTarget.onCheckSettingsComplete(
362 okPressed ? CHECK_SETTINGS_OK : CHECK_SETTINGS_SECURITY_USER_DENY);
363
Andy Stadlerd8f1d8f2010-12-22 19:01:08 -0800364 // 2. kill self if not already killed by callback
365 FragmentManager fm = getFragmentManager();
366 if (fm != null) {
367 fm.popBackStack();
368 }
Andrew Stadler55110ca2010-09-07 22:20:51 -0700369 }
370
371 /**
Andrew Stadler2731aef2010-09-13 14:04:45 -0700372 * This exception class is used to report autodiscover results via the reporting mechanism.
373 */
374 public static class AutoDiscoverResults extends MessagingException {
375 public final HostAuth mHostAuth;
376
377 /**
378 * @param authenticationError true if auth failure, false for result (or no response)
379 * @param hostAuth null for "no autodiscover", non-null for server info to return
380 */
381 public AutoDiscoverResults(boolean authenticationError, HostAuth hostAuth) {
382 super(null);
383 if (authenticationError) {
384 mExceptionType = AUTODISCOVER_AUTHENTICATION_FAILED;
385 } else {
386 mExceptionType = AUTODISCOVER_AUTHENTICATION_RESULT;
387 }
388 mHostAuth = hostAuth;
389 }
390 }
391
392 /**
Andrew Stadler55110ca2010-09-07 22:20:51 -0700393 * This AsyncTask does the actual account checking
394 *
395 * TODO: It would be better to remove the UI complete from here (the exception->string
396 * conversions).
397 */
398 private class AccountCheckTask extends AsyncTask<Void, Integer, MessagingException> {
399
400 final Context mContext;
401 final int mMode;
Todd Kennedya50fc992011-04-19 11:32:06 -0700402 final Account mAccount;
Andrew Stadler55110ca2010-09-07 22:20:51 -0700403 final String mStoreHost;
Andrew Stadler2731aef2010-09-13 14:04:45 -0700404 final String mCheckEmail;
405 final String mCheckPassword;
Andrew Stadler55110ca2010-09-07 22:20:51 -0700406
407 /**
408 * Create task and parameterize it
409 * @param mode bits request operations
Andrew Stadler2731aef2010-09-13 14:04:45 -0700410 * @param checkAccount account holding values to be checked
Andrew Stadler55110ca2010-09-07 22:20:51 -0700411 */
Andrew Stadler2731aef2010-09-13 14:04:45 -0700412 public AccountCheckTask(int mode, Account checkAccount) {
Andrew Stadler55110ca2010-09-07 22:20:51 -0700413 mContext = getActivity().getApplicationContext();
414 mMode = mode;
Todd Kennedya50fc992011-04-19 11:32:06 -0700415 mAccount = checkAccount;
Andrew Stadler2731aef2010-09-13 14:04:45 -0700416 mStoreHost = checkAccount.mHostAuthRecv.mAddress;
Andrew Stadler2731aef2010-09-13 14:04:45 -0700417 mCheckEmail = checkAccount.mEmailAddress;
418 mCheckPassword = checkAccount.mHostAuthRecv.mPassword;
Andrew Stadler55110ca2010-09-07 22:20:51 -0700419 }
420
421 @Override
422 protected MessagingException doInBackground(Void... params) {
423 if (DEBUG_FAKE_CHECK_CYCLE) {
424 return fakeChecker();
425 }
426
427 try {
Andrew Stadler55110ca2010-09-07 22:20:51 -0700428 if ((mMode & SetupData.CHECK_AUTODISCOVER) != 0) {
429 if (isCancelled()) return null;
430 publishProgress(STATE_CHECK_AUTODISCOVER);
Marc Blank31d9acb2011-02-11 15:05:17 -0800431 Log.d(Logging.LOG_TAG, "Begin auto-discover for " + mCheckEmail);
Marc Blank35b0e952011-06-29 12:47:56 -0700432 Store store = Store.getInstance(mAccount, mContext);
Andrew Stadler2731aef2010-09-13 14:04:45 -0700433 Bundle result = store.autoDiscover(mContext, mCheckEmail, mCheckPassword);
434 // Result will be one of:
435 // null: remote exception - proceed to manual setup
436 // MessagingException.AUTHENTICATION_FAILED: username/password rejected
437 // Other error: proceed to manual setup
438 // No error: return autodiscover results
439 if (result == null) {
440 return new AutoDiscoverResults(false, null);
441 }
442 int errorCode =
443 result.getInt(EmailServiceProxy.AUTO_DISCOVER_BUNDLE_ERROR_CODE);
444 if (errorCode == MessagingException.AUTHENTICATION_FAILED) {
445 return new AutoDiscoverResults(true, null);
446 } else if (errorCode != MessagingException.NO_ERROR) {
447 return new AutoDiscoverResults(false, null);
448 } else {
449 HostAuth serverInfo = (HostAuth)
450 result.getParcelable(EmailServiceProxy.AUTO_DISCOVER_BUNDLE_HOST_AUTH);
451 return new AutoDiscoverResults(false, serverInfo);
452 }
Andrew Stadler55110ca2010-09-07 22:20:51 -0700453 }
454
455 // Check Incoming Settings
456 if ((mMode & SetupData.CHECK_INCOMING) != 0) {
457 if (isCancelled()) return null;
Marc Blank31d9acb2011-02-11 15:05:17 -0800458 Log.d(Logging.LOG_TAG, "Begin check of incoming email settings");
Andrew Stadler55110ca2010-09-07 22:20:51 -0700459 publishProgress(STATE_CHECK_INCOMING);
Marc Blank35b0e952011-06-29 12:47:56 -0700460 Store store = Store.getInstance(mAccount, mContext);
Andrew Stadler55110ca2010-09-07 22:20:51 -0700461 Bundle bundle = store.checkSettings();
462 int resultCode = MessagingException.UNSPECIFIED_EXCEPTION;
463 if (bundle != null) {
464 resultCode = bundle.getInt(
465 EmailServiceProxy.VALIDATE_BUNDLE_RESULT_CODE);
466 }
467 if (resultCode == MessagingException.SECURITY_POLICIES_REQUIRED) {
Marc Blankaeee10e2011-04-27 17:12:06 -0700468 SetupData.setPolicy((Policy)bundle.getParcelable(
Andrew Stadler55110ca2010-09-07 22:20:51 -0700469 EmailServiceProxy.VALIDATE_BUNDLE_POLICY_SET));
Marc Blank26553072011-04-26 12:54:33 -0700470 return new MessagingException(resultCode, mStoreHost);
471 } else if (resultCode == MessagingException.SECURITY_POLICIES_UNSUPPORTED) {
472 String[] data = bundle.getStringArray(
473 EmailServiceProxy.VALIDATE_BUNDLE_UNSUPPORTED_POLICIES);
474 return new MessagingException(resultCode, mStoreHost, data);
475 } else if (resultCode != MessagingException.NO_ERROR) {
Andrew Stadler55110ca2010-09-07 22:20:51 -0700476 String errorMessage =
477 bundle.getString(EmailServiceProxy.VALIDATE_BUNDLE_ERROR_MESSAGE);
478 return new MessagingException(resultCode, errorMessage);
479 }
480 }
481
482 // Check Outgoing Settings
483 if ((mMode & SetupData.CHECK_OUTGOING) != 0) {
484 if (isCancelled()) return null;
Marc Blank31d9acb2011-02-11 15:05:17 -0800485 Log.d(Logging.LOG_TAG, "Begin check of outgoing email settings");
Andrew Stadler55110ca2010-09-07 22:20:51 -0700486 publishProgress(STATE_CHECK_OUTGOING);
Todd Kennedydaf869c2011-04-20 08:04:46 -0700487 Sender sender = Sender.getInstance(mContext, mAccount);
Andrew Stadler55110ca2010-09-07 22:20:51 -0700488 sender.close();
489 sender.open();
490 sender.close();
491 }
492
493 // If we reached the end, we completed the check(s) successfully
494 return null;
495 } catch (final MessagingException me) {
496 // Some of the legacy account checkers return errors by throwing MessagingException,
497 // which we catch and return here.
498 return me;
499 }
500 }
501
502 /**
Andrew Stadler2731aef2010-09-13 14:04:45 -0700503 * Dummy background worker, for testing UI only.
Andrew Stadler55110ca2010-09-07 22:20:51 -0700504 */
505 private MessagingException fakeChecker() {
506 // Dummy: Publish a series of progress setups, 2 sec delays between them;
507 // then return "ok" (null)
508 final int DELAY = 2*1000;
509 if (isCancelled()) return null;
510 if ((mMode & SetupData.CHECK_AUTODISCOVER) != 0) {
511 publishProgress(STATE_CHECK_AUTODISCOVER);
Andrew Stadler55110ca2010-09-07 22:20:51 -0700512 try {
513 Thread.sleep(DELAY);
514 } catch (InterruptedException e) { }
Andrew Stadler2731aef2010-09-13 14:04:45 -0700515 if (DEBUG_FAKE_CHECK_ERR) {
516 return new MessagingException(MessagingException.AUTHENTICATION_FAILED);
517 }
518 // Return "real" AD results
Todd Kennedy040ddf62011-02-04 09:29:13 -0800519 HostAuth auth = new HostAuth();
520 auth.setLogin("user", "password");
Marc Blank6fea0212011-06-29 14:30:03 -0700521 auth.setConnection(HostAuth.SCHEME_EAS, "testserver.com", 0);
Todd Kennedy040ddf62011-02-04 09:29:13 -0800522 return new AutoDiscoverResults(false, auth);
Andrew Stadler55110ca2010-09-07 22:20:51 -0700523 }
524 if (isCancelled()) return null;
525 if ((mMode & SetupData.CHECK_INCOMING) != 0) {
526 publishProgress(STATE_CHECK_INCOMING);
Andrew Stadler55110ca2010-09-07 22:20:51 -0700527 try {
528 Thread.sleep(DELAY);
529 } catch (InterruptedException e) { }
Andrew Stadler2731aef2010-09-13 14:04:45 -0700530 if (DEBUG_FAKE_CHECK_ERR) {
531 return new MessagingException(MessagingException.IOERROR);
532 } else if (DEBUG_FORCE_SECURITY_REQUIRED) {
Marc Blank26553072011-04-26 12:54:33 -0700533 return new MessagingException(MessagingException.SECURITY_POLICIES_REQUIRED);
Andrew Stadler2731aef2010-09-13 14:04:45 -0700534 }
Andrew Stadler55110ca2010-09-07 22:20:51 -0700535 }
536 if (isCancelled()) return null;
537 if ((mMode & SetupData.CHECK_OUTGOING) != 0) {
538 publishProgress(STATE_CHECK_OUTGOING);
Andrew Stadler55110ca2010-09-07 22:20:51 -0700539 try {
540 Thread.sleep(DELAY);
541 } catch (InterruptedException e) { }
Andrew Stadler2731aef2010-09-13 14:04:45 -0700542 if (DEBUG_FAKE_CHECK_ERR) {
543 return new MessagingException(MessagingException.TLS_REQUIRED);
544 }
Andrew Stadler55110ca2010-09-07 22:20:51 -0700545 }
546 return null;
547 }
548
549 /**
550 * Progress reports (runs in UI thread). This should be used for real progress only
551 * (not for errors).
552 */
553 @Override
554 protected void onProgressUpdate(Integer... progress) {
555 if (isCancelled()) return;
Ben Komalo715ee4e2011-09-13 11:34:22 -0700556 reportProgress(progress[0], null);
Andrew Stadler55110ca2010-09-07 22:20:51 -0700557 }
558
559 /**
Andrew Stadler2731aef2010-09-13 14:04:45 -0700560 * Result handler (runs in UI thread).
561 *
562 * AutoDiscover authentication errors are handled a bit differently than the
563 * other errors; If encountered, we display the error dialog, but we return with
564 * a different callback used only for AutoDiscover.
565 *
Andrew Stadler55110ca2010-09-07 22:20:51 -0700566 * @param result null for a successful check; exception for various errors
567 */
568 @Override
569 protected void onPostExecute(MessagingException result) {
570 if (isCancelled()) return;
571 if (result == null) {
Ben Komalo715ee4e2011-09-13 11:34:22 -0700572 reportProgress(STATE_CHECK_OK, null);
Andrew Stadler55110ca2010-09-07 22:20:51 -0700573 } else {
Andrew Stadler2731aef2010-09-13 14:04:45 -0700574 int progressState = STATE_CHECK_ERROR;
Andrew Stadler55110ca2010-09-07 22:20:51 -0700575 int exceptionType = result.getExceptionType();
Andrew Stadler2731aef2010-09-13 14:04:45 -0700576
Andrew Stadler55110ca2010-09-07 22:20:51 -0700577 switch (exceptionType) {
Andrew Stadler2731aef2010-09-13 14:04:45 -0700578 // NOTE: AutoDiscover reports have their own reporting state, handle differently
579 // from the other exception types
580 case MessagingException.AUTODISCOVER_AUTHENTICATION_FAILED:
Andrew Stadler2731aef2010-09-13 14:04:45 -0700581 progressState = STATE_AUTODISCOVER_AUTH_DIALOG;
582 break;
583 case MessagingException.AUTODISCOVER_AUTHENTICATION_RESULT:
Andrew Stadler2731aef2010-09-13 14:04:45 -0700584 progressState = STATE_AUTODISCOVER_RESULT;
585 break;
Andrew Stadler55110ca2010-09-07 22:20:51 -0700586 // NOTE: Security policies required has its own report state, handle it a bit
587 // differently from the other exception types.
588 case MessagingException.SECURITY_POLICIES_REQUIRED:
Andrew Stadler2731aef2010-09-13 14:04:45 -0700589 progressState = STATE_CHECK_SHOW_SECURITY;
590 break;
Andrew Stadler55110ca2010-09-07 22:20:51 -0700591 }
Ben Komalo715ee4e2011-09-13 11:34:22 -0700592 reportProgress(progressState, result);
Andrew Stadler55110ca2010-09-07 22:20:51 -0700593 }
594 }
595 }
596
Ben Komalo715ee4e2011-09-13 11:34:22 -0700597 private static String getErrorString(Context context, MessagingException ex) {
598 int id;
599 String message = ex.getMessage();
600 if (message != null) {
601 message = message.trim();
602 }
603 switch (ex.getExceptionType()) {
604 // The remaining exception types are handled by setting the state to
605 // STATE_CHECK_ERROR (above, default) and conversion to specific error strings.
606 case MessagingException.CERTIFICATE_VALIDATION_ERROR:
607 id = TextUtils.isEmpty(message)
608 ? R.string.account_setup_failed_dlg_certificate_message
609 : R.string.account_setup_failed_dlg_certificate_message_fmt;
610 break;
611 case MessagingException.AUTHENTICATION_FAILED:
612 id = TextUtils.isEmpty(message)
613 ? R.string.account_setup_failed_dlg_auth_message
614 : R.string.account_setup_failed_dlg_auth_message_fmt;
615 break;
616 case MessagingException.AUTHENTICATION_FAILED_OR_SERVER_ERROR:
617 id = R.string.account_setup_failed_check_credentials_message;
618 break;
619 case MessagingException.IOERROR:
620 id = R.string.account_setup_failed_ioerror;
621 break;
622 case MessagingException.TLS_REQUIRED:
623 id = R.string.account_setup_failed_tls_required;
624 break;
625 case MessagingException.AUTH_REQUIRED:
626 id = R.string.account_setup_failed_auth_required;
627 break;
628 case MessagingException.SECURITY_POLICIES_UNSUPPORTED:
629 id = R.string.account_setup_failed_security_policies_unsupported;
630 // Belt and suspenders here; there should always be a non-empty array here
631 String[] unsupportedPolicies = (String[]) ex.getExceptionData();
632 if (unsupportedPolicies == null) {
633 Log.w(TAG, "No data for unsupported policies?");
634 break;
635 }
636 // Build a string, concatenating policies we don't support
637 StringBuilder sb = new StringBuilder();
638 boolean first = true;
639 for (String policyName: unsupportedPolicies) {
640 if (first) {
641 first = false;
642 } else {
643 sb.append(", ");
644 }
645 sb.append(policyName);
646 }
647 message = sb.toString();
648 break;
649 case MessagingException.ACCESS_DENIED:
650 id = R.string.account_setup_failed_access_denied;
651 break;
652 case MessagingException.PROTOCOL_VERSION_UNSUPPORTED:
653 id = R.string.account_setup_failed_protocol_unsupported;
654 break;
655 case MessagingException.GENERAL_SECURITY:
656 id = R.string.account_setup_failed_security;
657 break;
658 case MessagingException.CLIENT_CERTIFICATE_REQUIRED:
659 id = R.string.account_setup_failed_certificate_required;
660 break;
661 case MessagingException.CLIENT_CERTIFICATE_ERROR:
662 id = R.string.account_setup_failed_certificate_inaccessible;
663 break;
664 default:
665 id = TextUtils.isEmpty(message)
666 ? R.string.account_setup_failed_dlg_server_message
667 : R.string.account_setup_failed_dlg_server_message_fmt;
668 break;
669 }
670 return TextUtils.isEmpty(message)
671 ? context.getString(id)
672 : context.getString(id, message);
673 }
674
Andrew Stadler55110ca2010-09-07 22:20:51 -0700675 /**
676 * Simple dialog that shows progress as we work through the settings checks.
677 * This is stateless except for its UI (e.g. current strings) and can be torn down or
678 * recreated at any time without affecting the account checking progress.
Andrew Stadler55110ca2010-09-07 22:20:51 -0700679 */
680 public static class CheckingDialog extends DialogFragment {
Todd Kennedy040ddf62011-02-04 09:29:13 -0800681 @SuppressWarnings("hiding")
Andrew Stadler55110ca2010-09-07 22:20:51 -0700682 public final static String TAG = "CheckProgressDialog";
683
684 // Extras for saved instance state
685 private final String EXTRA_PROGRESS_STRING = "CheckProgressDialog.Progress";
686
687 // UI
688 private String mProgressString;
689
690 /**
691 * Create a dialog that reports progress
692 * @param progress initial progress indication
693 */
694 public static CheckingDialog newInstance(AccountCheckSettingsFragment parentFragment,
695 int progress) {
696 CheckingDialog f = new CheckingDialog();
697 f.setTargetFragment(parentFragment, progress);
698 return f;
699 }
700
701 /**
702 * Update the progress of an existing dialog
703 * @param progress latest progress to be displayed
704 */
705 public void updateProgress(int progress) {
706 mProgressString = getProgressString(progress);
707 AlertDialog dialog = (AlertDialog) getDialog();
708 dialog.setMessage(mProgressString);
709 }
710
711 @Override
712 public Dialog onCreateDialog(Bundle savedInstanceState) {
713 Context context = getActivity();
714 if (savedInstanceState != null) {
715 mProgressString = savedInstanceState.getString(EXTRA_PROGRESS_STRING);
716 }
717 if (mProgressString == null) {
718 mProgressString = getProgressString(getTargetRequestCode());
719 }
720 final AccountCheckSettingsFragment target =
721 (AccountCheckSettingsFragment) getTargetFragment();
722
723 ProgressDialog dialog = new ProgressDialog(context);
724 dialog.setIndeterminate(true);
725 dialog.setMessage(mProgressString);
726 dialog.setButton(DialogInterface.BUTTON_NEGATIVE,
727 context.getString(R.string.cancel_action),
728 new DialogInterface.OnClickListener() {
729 public void onClick(DialogInterface dialog, int which) {
730 dismiss();
731 target.onCheckingDialogCancel();
732 }
733 });
734 return dialog;
735 }
736
Andy Stadler992b7e72010-11-05 15:54:28 -0700737 /**
738 * Listen for cancellation, which can happen from places other than the
739 * negative button (e.g. touching outside the dialog), and stop the checker
740 */
741 @Override
742 public void onCancel(DialogInterface dialog) {
743 AccountCheckSettingsFragment target =
744 (AccountCheckSettingsFragment) getTargetFragment();
745 target.onCheckingDialogCancel();
746 super.onCancel(dialog);
747 }
748
Andrew Stadler55110ca2010-09-07 22:20:51 -0700749 @Override
750 public void onSaveInstanceState(Bundle outState) {
751 super.onSaveInstanceState(outState);
752 outState.putString(EXTRA_PROGRESS_STRING, mProgressString);
753 }
754
755 /**
756 * Convert progress to message
757 */
758 private String getProgressString(int progress) {
759 int stringId = 0;
760 switch (progress) {
761 case STATE_CHECK_AUTODISCOVER:
762 stringId = R.string.account_setup_check_settings_retr_info_msg;
763 break;
764 case STATE_CHECK_INCOMING:
765 stringId = R.string.account_setup_check_settings_check_incoming_msg;
766 break;
767 case STATE_CHECK_OUTGOING:
768 stringId = R.string.account_setup_check_settings_check_outgoing_msg;
769 break;
770 }
771 return getActivity().getString(stringId);
772 }
773 }
774
775 /**
776 * The standard error dialog. Calls back to onErrorDialogButton().
777 */
778 public static class ErrorDialog extends DialogFragment {
Todd Kennedy040ddf62011-02-04 09:29:13 -0800779 @SuppressWarnings("hiding")
Andrew Stadler55110ca2010-09-07 22:20:51 -0700780 public final static String TAG = "ErrorDialog";
781
782 // Bundle keys for arguments
Ben Komalo715ee4e2011-09-13 11:34:22 -0700783 private final static String ARGS_MESSAGE = "ErrorDialog.Message";
784 private final static String ARGS_EXCEPTION_ID = "ErrorDialog.ExceptionId";
Andrew Stadler55110ca2010-09-07 22:20:51 -0700785
Mindy Pereira92922ea2011-09-26 11:05:04 -0700786 /**
787 * Use {@link #newInstance} This public constructor is still required so
788 * that DialogFragment state can be automatically restored by the
789 * framework.
790 */
791 public ErrorDialog() {
Ben Komalo715ee4e2011-09-13 11:34:22 -0700792 }
793
794 public static ErrorDialog newInstance(Context context, AccountCheckSettingsFragment target,
795 MessagingException ex) {
Andrew Stadler55110ca2010-09-07 22:20:51 -0700796 ErrorDialog fragment = new ErrorDialog();
797 Bundle arguments = new Bundle();
Ben Komalo715ee4e2011-09-13 11:34:22 -0700798 arguments.putString(ARGS_MESSAGE, getErrorString(context, ex));
799 arguments.putInt(ARGS_EXCEPTION_ID, ex.getExceptionType());
Andrew Stadler55110ca2010-09-07 22:20:51 -0700800 fragment.setArguments(arguments);
801 fragment.setTargetFragment(target, 0);
802 return fragment;
803 }
804
805 @Override
806 public Dialog onCreateDialog(Bundle savedInstanceState) {
807 final Context context = getActivity();
808 final Bundle arguments = getArguments();
Ben Komalo715ee4e2011-09-13 11:34:22 -0700809 final String message = arguments.getString(ARGS_MESSAGE);
810 final int exceptionId = arguments.getInt(ARGS_EXCEPTION_ID);
Andrew Stadler55110ca2010-09-07 22:20:51 -0700811 final AccountCheckSettingsFragment target =
812 (AccountCheckSettingsFragment) getTargetFragment();
813
Ben Komalo715ee4e2011-09-13 11:34:22 -0700814 AlertDialog.Builder builder = new AlertDialog.Builder(context)
Makoto Onuki49518bb2011-01-18 10:14:14 -0800815 .setIconAttribute(android.R.attr.alertDialogIcon)
Andrew Stadler55110ca2010-09-07 22:20:51 -0700816 .setTitle(context.getString(R.string.account_setup_failed_dlg_title))
Ben Komalo715ee4e2011-09-13 11:34:22 -0700817 .setMessage(message)
818 .setCancelable(true);
819
820 if (exceptionId == MessagingException.CLIENT_CERTIFICATE_REQUIRED) {
821 // Certificate error - show two buttons so the host fragment can auto pop
822 // into the appropriate flow.
823 builder.setPositiveButton(
824 context.getString(android.R.string.ok),
825 new DialogInterface.OnClickListener() {
826 public void onClick(DialogInterface dialog, int which) {
827 dismiss();
828 target.onEditCertificateOk();
829 }
830 });
831 builder.setNegativeButton(
832 context.getString(android.R.string.cancel),
833 new DialogInterface.OnClickListener() {
834 public void onClick(DialogInterface dialog, int which) {
835 dismiss();
836 target.onErrorDialogEditButton();
837 }
838 });
839
840 } else {
841 // "Normal" error - just use a single "Edit details" button.
842 builder.setPositiveButton(
Andrew Stadler55110ca2010-09-07 22:20:51 -0700843 context.getString(R.string.account_setup_failed_dlg_edit_details_action),
844 new DialogInterface.OnClickListener() {
845 public void onClick(DialogInterface dialog, int which) {
846 dismiss();
847 target.onErrorDialogEditButton();
848 }
Ben Komalo715ee4e2011-09-13 11:34:22 -0700849 });
850 }
851
852 return builder.create();
Andrew Stadler55110ca2010-09-07 22:20:51 -0700853 }
854
855 }
856
857 /**
Andrew Stadler2731aef2010-09-13 14:04:45 -0700858 * The "security required" error dialog. This is presented whenever an exchange account
859 * reports that it will require security policy control, and provide the user with the
860 * opportunity to accept or deny this.
861 *
862 * If the user clicks OK, calls onSecurityRequiredDialogResultOk(true) which reports back
863 * to the target as if the settings check was "ok". If the user clicks "cancel", calls
864 * onSecurityRequiredDialogResultOk(false) which simply closes the checker (this is the
865 * same as any other failed check.)
Andrew Stadler55110ca2010-09-07 22:20:51 -0700866 */
867 public static class SecurityRequiredDialog extends DialogFragment {
Todd Kennedy040ddf62011-02-04 09:29:13 -0800868 @SuppressWarnings("hiding")
Andrew Stadler55110ca2010-09-07 22:20:51 -0700869 public final static String TAG = "SecurityRequiredDialog";
870
871 // Bundle keys for arguments
872 private final static String ARGS_HOST_NAME = "SecurityRequiredDialog.HostName";
873
874 public static SecurityRequiredDialog newInstance(AccountCheckSettingsFragment target,
875 String hostName) {
876 SecurityRequiredDialog fragment = new SecurityRequiredDialog();
877 Bundle arguments = new Bundle();
878 arguments.putString(ARGS_HOST_NAME, hostName);
879 fragment.setArguments(arguments);
880 fragment.setTargetFragment(target, 0);
881 return fragment;
882 }
883
884 @Override
885 public Dialog onCreateDialog(Bundle savedInstanceState) {
886 final Context context = getActivity();
887 final Bundle arguments = getArguments();
888 final String hostName = arguments.getString(ARGS_HOST_NAME);
889 final AccountCheckSettingsFragment target =
890 (AccountCheckSettingsFragment) getTargetFragment();
891
892 return new AlertDialog.Builder(context)
Makoto Onuki49518bb2011-01-18 10:14:14 -0800893 .setIconAttribute(android.R.attr.alertDialogIcon)
Andrew Stadler55110ca2010-09-07 22:20:51 -0700894 .setTitle(context.getString(R.string.account_setup_security_required_title))
895 .setMessage(context.getString(
896 R.string.account_setup_security_policies_required_fmt, hostName))
897 .setCancelable(true)
898 .setPositiveButton(
899 context.getString(R.string.okay_action),
900 new DialogInterface.OnClickListener() {
901 public void onClick(DialogInterface dialog, int which) {
902 dismiss();
Andrew Stadler2731aef2010-09-13 14:04:45 -0700903 target.onSecurityRequiredDialogResultOk(true);
Andrew Stadler55110ca2010-09-07 22:20:51 -0700904 }
905 })
906 .setNegativeButton(
907 context.getString(R.string.cancel_action),
908 new DialogInterface.OnClickListener() {
909 public void onClick(DialogInterface dialog, int which) {
910 dismiss();
Andrew Stadler2731aef2010-09-13 14:04:45 -0700911 target.onSecurityRequiredDialogResultOk(false);
Andrew Stadler55110ca2010-09-07 22:20:51 -0700912 }
913 })
914 .create();
915 }
916
917 }
918
919}