AccountCheckSettingsFragment.java revision f4dbbf10996e6bca926a5825bbc69e1e172c20c0
1/* 2 * Copyright (C) 2010 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.email.activity.setup; 18 19import com.android.email.R; 20import com.android.email.mail.Sender; 21import com.android.email.mail.Store; 22import com.android.emailcommon.Logging; 23import com.android.emailcommon.mail.MessagingException; 24import com.android.emailcommon.provider.Account; 25import com.android.emailcommon.provider.HostAuth; 26import com.android.emailcommon.provider.Policy; 27import com.android.emailcommon.service.EmailServiceProxy; 28import com.android.emailcommon.utility.Utility; 29 30import android.app.Activity; 31import android.app.AlertDialog; 32import android.app.Dialog; 33import android.app.DialogFragment; 34import android.app.Fragment; 35import android.app.FragmentManager; 36import android.app.ProgressDialog; 37import android.content.Context; 38import android.content.DialogInterface; 39import android.os.AsyncTask; 40import android.os.Bundle; 41import android.text.TextUtils; 42import android.util.Log; 43 44/** 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. 54 */ 55public class AccountCheckSettingsFragment extends Fragment { 56 57 public final static String TAG = "AccountCheckSettingsFragment"; 58 59 // Debugging flags - for debugging the UI 60 // 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 66 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; 72 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 77 private int mState = STATE_START; 78 79 // Support for UI 80 private boolean mAttached; 81 private CheckingDialog mCheckingDialog; 82 private int mErrorStringId; 83 private String mErrorMessage; 84 private HostAuth mAutoDiscoverResult; 85 86 // Support for AsyncTask and account checking 87 AccountCheckTask mAccountCheckTask; 88 89 // Result codes returned by onCheckSettingsComplete. 90 /** Check settings returned successfully */ 91 public final static int CHECK_SETTINGS_OK = 0; 92 /** Check settings failed due to connection, authentication, or other server error */ 93 public final static int CHECK_SETTINGS_SERVER_ERROR = 1; 94 /** Check settings failed due to user refusing to accept security requirements */ 95 public final static int CHECK_SETTINGS_SECURITY_USER_DENY = 2; 96 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 105 /** 106 * Callback interface for any target or activity doing account check settings 107 */ 108 public interface Callbacks { 109 /** 110 * Called when CheckSettings completed 111 * @param result check settings result code - success is CHECK_SETTINGS_OK 112 */ 113 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); 121 } 122 123 /** 124 * 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(); 159 mAccountCheckTask = (AccountCheckTask) 160 new AccountCheckTask(checkMode, checkAccount) 161 .execute(); 162 } 163 } 164 165 /** 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 172 if (mState != STATE_START) { 173 reportProgress(mState, mErrorStringId, mErrorMessage, mAutoDiscoverResult); 174 } 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 203 * @param errorStringId Resource Id of an error string to display 204 * @param errorMessage Additional string to insert if the resource string takes a parameter. 205 * @param autoDiscoverResult If doing autodiscovery, the setup info returned from AD server 206 */ 207 public void reportProgress(int newState, int errorStringId, String errorMessage, 208 HostAuth autoDiscoverResult) { 209 mState = newState; 210 mErrorStringId = errorStringId; 211 mErrorMessage = errorMessage; 212 mAutoDiscoverResult = autoDiscoverResult; 213 214 // If we are attached, create, recover, and/or update the dialog 215 if (mAttached) { 216 FragmentManager fm = getFragmentManager(); 217 218 switch (newState) { 219 case STATE_CHECK_OK: 220 // immediately terminate, clean up, and report back 221 // 1. get rid of progress dialog (if any) 222 recoverAndDismissCheckingDialog(); 223 // 2. exit self 224 fm.popBackStack(); 225 // 3. report OK back to target fragment or activity 226 getCallbackTarget().onCheckSettingsComplete(CHECK_SETTINGS_OK); 227 break; 228 case STATE_CHECK_SHOW_SECURITY: 229 // 1. get rid of progress dialog (if any) 230 recoverAndDismissCheckingDialog(); 231 // 2. launch the error dialog, if needed 232 if (fm.findFragmentByTag(SecurityRequiredDialog.TAG) == null) { 233 SecurityRequiredDialog securityRequiredDialog = 234 SecurityRequiredDialog.newInstance(this, mErrorMessage); 235 fm.beginTransaction() 236 .add(securityRequiredDialog, SecurityRequiredDialog.TAG) 237 .commit(); 238 } 239 break; 240 case STATE_CHECK_ERROR: 241 case STATE_AUTODISCOVER_AUTH_DIALOG: 242 // 1. get rid of progress dialog (if any) 243 recoverAndDismissCheckingDialog(); 244 // 2. launch the error dialog, if needed 245 if (fm.findFragmentByTag(ErrorDialog.TAG) == null) { 246 ErrorDialog errorDialog = 247 ErrorDialog.newInstance(this, mErrorStringId, mErrorMessage); 248 fm.beginTransaction() 249 .add(errorDialog, ErrorDialog.TAG) 250 .commit(); 251 } 252 break; 253 case STATE_AUTODISCOVER_RESULT: 254 // 1. get rid of progress dialog (if any) 255 recoverAndDismissCheckingDialog(); 256 // 2. exit self 257 fm.popBackStack(); 258 // 3. report back to target fragment or activity 259 getCallbackTarget().onAutoDiscoverComplete( 260 (mAutoDiscoverResult != null) ? AUTODISCOVER_OK : AUTODISCOVER_NO_DATA, 261 mAutoDiscoverResult); 262 break; 263 default: 264 // Display a normal progress message 265 mCheckingDialog = (CheckingDialog) fm.findFragmentByTag(CheckingDialog.TAG); 266 267 if (mCheckingDialog == null) { 268 mCheckingDialog = CheckingDialog.newInstance(this, mState); 269 fm.beginTransaction() 270 .add(mCheckingDialog, CheckingDialog.TAG) 271 .commit(); 272 } else { 273 mCheckingDialog.updateProgress(mState); 274 } 275 break; 276 } 277 } 278 } 279 280 /** 281 * Find the callback target, either a target fragment or the activity 282 */ 283 private Callbacks getCallbackTarget() { 284 Fragment target = getTargetFragment(); 285 if (target instanceof Callbacks) { 286 return (Callbacks) target; 287 } 288 Activity activity = getActivity(); 289 if (activity instanceof Callbacks) { 290 return (Callbacks) activity; 291 } 292 throw new IllegalStateException(); 293 } 294 295 /** 296 * Recover and dismiss the progress dialog fragment 297 */ 298 private void recoverAndDismissCheckingDialog() { 299 if (mCheckingDialog == null) { 300 mCheckingDialog = (CheckingDialog) 301 getFragmentManager().findFragmentByTag(CheckingDialog.TAG); 302 } 303 if (mCheckingDialog != null) { 304 mCheckingDialog.dismissAllowingStateLoss(); 305 mCheckingDialog = null; 306 } 307 } 308 309 /** 310 * This is called when the user clicks "cancel" on the progress dialog. Shuts everything 311 * down and dismisses everything. 312 * This should cause us to remain in the current screen (not accepting the settings) 313 */ 314 private void onCheckingDialogCancel() { 315 // 1. kill the checker 316 Utility.cancelTaskInterrupt(mAccountCheckTask); 317 mAccountCheckTask = null; 318 // 2. kill self with no report - this is "cancel" 319 getFragmentManager().popBackStack(); 320 } 321 322 /** 323 * This is called when the user clicks "edit" from the error dialog. The error dialog 324 * should have already dismissed itself. 325 * Depending on the context, the target will remain in the current activity (e.g. editing 326 * settings) or return to its own parent (e.g. enter new credentials). 327 */ 328 private void onErrorDialogEditButton() { 329 // 1. handle "edit" - notify callback that we had a problem with the test 330 Callbacks callbackTarget = getCallbackTarget(); 331 if (mState == STATE_AUTODISCOVER_AUTH_DIALOG) { 332 // report auth error to target fragment or activity 333 callbackTarget.onAutoDiscoverComplete(AUTODISCOVER_AUTHENTICATION, null); 334 } else { 335 // report check settings failure to target fragment or activity 336 callbackTarget.onCheckSettingsComplete(CHECK_SETTINGS_SERVER_ERROR); 337 } 338 // 2. kill self if not already killed by callback 339 FragmentManager fm = getFragmentManager(); 340 if (fm != null) { 341 fm.popBackStack(); 342 } 343 } 344 345 /** 346 * This is called when the user clicks "ok" or "cancel" on the "security required" dialog. 347 * Shuts everything down and dismisses everything, and reports the result appropriately. 348 */ 349 private void onSecurityRequiredDialogResultOk(boolean okPressed) { 350 // 1. handle OK/cancel - notify that security is OK and we can proceed 351 Callbacks callbackTarget = getCallbackTarget(); 352 callbackTarget.onCheckSettingsComplete( 353 okPressed ? CHECK_SETTINGS_OK : CHECK_SETTINGS_SECURITY_USER_DENY); 354 355 // 2. kill self if not already killed by callback 356 FragmentManager fm = getFragmentManager(); 357 if (fm != null) { 358 fm.popBackStack(); 359 } 360 } 361 362 /** 363 * This exception class is used to report autodiscover results via the reporting mechanism. 364 */ 365 public static class AutoDiscoverResults extends MessagingException { 366 public final HostAuth mHostAuth; 367 368 /** 369 * @param authenticationError true if auth failure, false for result (or no response) 370 * @param hostAuth null for "no autodiscover", non-null for server info to return 371 */ 372 public AutoDiscoverResults(boolean authenticationError, HostAuth hostAuth) { 373 super(null); 374 if (authenticationError) { 375 mExceptionType = AUTODISCOVER_AUTHENTICATION_FAILED; 376 } else { 377 mExceptionType = AUTODISCOVER_AUTHENTICATION_RESULT; 378 } 379 mHostAuth = hostAuth; 380 } 381 } 382 383 /** 384 * This AsyncTask does the actual account checking 385 * 386 * TODO: It would be better to remove the UI complete from here (the exception->string 387 * conversions). 388 */ 389 private class AccountCheckTask extends AsyncTask<Void, Integer, MessagingException> { 390 391 final Context mContext; 392 final int mMode; 393 final Account mAccount; 394 final String mStoreHost; 395 final String mCheckEmail; 396 final String mCheckPassword; 397 398 /** 399 * Create task and parameterize it 400 * @param mode bits request operations 401 * @param checkAccount account holding values to be checked 402 */ 403 public AccountCheckTask(int mode, Account checkAccount) { 404 mContext = getActivity().getApplicationContext(); 405 mMode = mode; 406 mAccount = checkAccount; 407 mStoreHost = checkAccount.mHostAuthRecv.mAddress; 408 mCheckEmail = checkAccount.mEmailAddress; 409 mCheckPassword = checkAccount.mHostAuthRecv.mPassword; 410 } 411 412 @Override 413 protected MessagingException doInBackground(Void... params) { 414 if (DEBUG_FAKE_CHECK_CYCLE) { 415 return fakeChecker(); 416 } 417 418 try { 419 if ((mMode & SetupData.CHECK_AUTODISCOVER) != 0) { 420 if (isCancelled()) return null; 421 publishProgress(STATE_CHECK_AUTODISCOVER); 422 Log.d(Logging.LOG_TAG, "Begin auto-discover for " + mCheckEmail); 423 Store store = Store.getInstance(mAccount, mContext, null); 424 Bundle result = store.autoDiscover(mContext, mCheckEmail, mCheckPassword); 425 // Result will be one of: 426 // null: remote exception - proceed to manual setup 427 // MessagingException.AUTHENTICATION_FAILED: username/password rejected 428 // Other error: proceed to manual setup 429 // No error: return autodiscover results 430 if (result == null) { 431 return new AutoDiscoverResults(false, null); 432 } 433 int errorCode = 434 result.getInt(EmailServiceProxy.AUTO_DISCOVER_BUNDLE_ERROR_CODE); 435 if (errorCode == MessagingException.AUTHENTICATION_FAILED) { 436 return new AutoDiscoverResults(true, null); 437 } else if (errorCode != MessagingException.NO_ERROR) { 438 return new AutoDiscoverResults(false, null); 439 } else { 440 HostAuth serverInfo = (HostAuth) 441 result.getParcelable(EmailServiceProxy.AUTO_DISCOVER_BUNDLE_HOST_AUTH); 442 return new AutoDiscoverResults(false, serverInfo); 443 } 444 } 445 446 // Check Incoming Settings 447 if ((mMode & SetupData.CHECK_INCOMING) != 0) { 448 if (isCancelled()) return null; 449 Log.d(Logging.LOG_TAG, "Begin check of incoming email settings"); 450 publishProgress(STATE_CHECK_INCOMING); 451 Store store = Store.getInstance(mAccount, mContext, null); 452 Bundle bundle = store.checkSettings(); 453 int resultCode = MessagingException.UNSPECIFIED_EXCEPTION; 454 if (bundle != null) { 455 resultCode = bundle.getInt( 456 EmailServiceProxy.VALIDATE_BUNDLE_RESULT_CODE); 457 } 458 if (resultCode == MessagingException.SECURITY_POLICIES_REQUIRED) { 459 SetupData.setPolicy((Policy)bundle.getParcelable( 460 EmailServiceProxy.VALIDATE_BUNDLE_POLICY_SET)); 461 return new MessagingException(resultCode, mStoreHost); 462 } else if (resultCode == MessagingException.SECURITY_POLICIES_UNSUPPORTED) { 463 String[] data = bundle.getStringArray( 464 EmailServiceProxy.VALIDATE_BUNDLE_UNSUPPORTED_POLICIES); 465 return new MessagingException(resultCode, mStoreHost, data); 466 } else if (resultCode != MessagingException.NO_ERROR) { 467 String errorMessage = 468 bundle.getString(EmailServiceProxy.VALIDATE_BUNDLE_ERROR_MESSAGE); 469 return new MessagingException(resultCode, errorMessage); 470 } 471 } 472 473 // Check Outgoing Settings 474 if ((mMode & SetupData.CHECK_OUTGOING) != 0) { 475 if (isCancelled()) return null; 476 Log.d(Logging.LOG_TAG, "Begin check of outgoing email settings"); 477 publishProgress(STATE_CHECK_OUTGOING); 478 Sender sender = Sender.getInstance(mContext, mAccount); 479 sender.close(); 480 sender.open(); 481 sender.close(); 482 } 483 484 // If we reached the end, we completed the check(s) successfully 485 return null; 486 } catch (final MessagingException me) { 487 // Some of the legacy account checkers return errors by throwing MessagingException, 488 // which we catch and return here. 489 return me; 490 } 491 } 492 493 /** 494 * Dummy background worker, for testing UI only. 495 */ 496 private MessagingException fakeChecker() { 497 // Dummy: Publish a series of progress setups, 2 sec delays between them; 498 // then return "ok" (null) 499 final int DELAY = 2*1000; 500 if (isCancelled()) return null; 501 if ((mMode & SetupData.CHECK_AUTODISCOVER) != 0) { 502 publishProgress(STATE_CHECK_AUTODISCOVER); 503 try { 504 Thread.sleep(DELAY); 505 } catch (InterruptedException e) { } 506 if (DEBUG_FAKE_CHECK_ERR) { 507 return new MessagingException(MessagingException.AUTHENTICATION_FAILED); 508 } 509 // Return "real" AD results 510 HostAuth auth = new HostAuth(); 511 auth.setLogin("user", "password"); 512 auth.setConnection(Store.STORE_SCHEME_EAS, "testserver.com", 0); 513 return new AutoDiscoverResults(false, auth); 514 } 515 if (isCancelled()) return null; 516 if ((mMode & SetupData.CHECK_INCOMING) != 0) { 517 publishProgress(STATE_CHECK_INCOMING); 518 try { 519 Thread.sleep(DELAY); 520 } catch (InterruptedException e) { } 521 if (DEBUG_FAKE_CHECK_ERR) { 522 return new MessagingException(MessagingException.IOERROR); 523 } else if (DEBUG_FORCE_SECURITY_REQUIRED) { 524 return new MessagingException(MessagingException.SECURITY_POLICIES_REQUIRED); 525 } 526 } 527 if (isCancelled()) return null; 528 if ((mMode & SetupData.CHECK_OUTGOING) != 0) { 529 publishProgress(STATE_CHECK_OUTGOING); 530 try { 531 Thread.sleep(DELAY); 532 } catch (InterruptedException e) { } 533 if (DEBUG_FAKE_CHECK_ERR) { 534 return new MessagingException(MessagingException.TLS_REQUIRED); 535 } 536 } 537 return null; 538 } 539 540 /** 541 * Progress reports (runs in UI thread). This should be used for real progress only 542 * (not for errors). 543 */ 544 @Override 545 protected void onProgressUpdate(Integer... progress) { 546 if (isCancelled()) return; 547 reportProgress(progress[0], 0, null, null); 548 } 549 550 /** 551 * Result handler (runs in UI thread). 552 * 553 * AutoDiscover authentication errors are handled a bit differently than the 554 * other errors; If encountered, we display the error dialog, but we return with 555 * a different callback used only for AutoDiscover. 556 * 557 * @param result null for a successful check; exception for various errors 558 */ 559 @Override 560 protected void onPostExecute(MessagingException result) { 561 if (isCancelled()) return; 562 if (result == null) { 563 reportProgress(STATE_CHECK_OK, 0, null, null); 564 } else { 565 int progressState = STATE_CHECK_ERROR; 566 int exceptionType = result.getExceptionType(); 567 String message = result.getMessage(); 568 if (message != null) { 569 message = message.trim(); 570 } 571 HostAuth hostAuth = null; 572 int id = 0; 573 574 switch (exceptionType) { 575 // NOTE: AutoDiscover reports have their own reporting state, handle differently 576 // from the other exception types 577 case MessagingException.AUTODISCOVER_AUTHENTICATION_FAILED: 578 id = TextUtils.isEmpty(message) 579 ? R.string.account_setup_failed_dlg_auth_message 580 : R.string.account_setup_failed_dlg_auth_message_fmt; 581 progressState = STATE_AUTODISCOVER_AUTH_DIALOG; 582 break; 583 case MessagingException.AUTODISCOVER_AUTHENTICATION_RESULT: 584 hostAuth = ((AutoDiscoverResults)result).mHostAuth; 585 progressState = STATE_AUTODISCOVER_RESULT; 586 break; 587 588 // NOTE: Security policies required has its own report state, handle it a bit 589 // differently from the other exception types. 590 case MessagingException.SECURITY_POLICIES_REQUIRED: 591 progressState = STATE_CHECK_SHOW_SECURITY; 592 break; 593 594 // The remaining exception types are handled by setting the state to 595 // STATE_CHECK_ERROR (above, default) and conversion to specific error strings. 596 case MessagingException.CERTIFICATE_VALIDATION_ERROR: 597 id = TextUtils.isEmpty(message) 598 ? R.string.account_setup_failed_dlg_certificate_message 599 : R.string.account_setup_failed_dlg_certificate_message_fmt; 600 break; 601 case MessagingException.AUTHENTICATION_FAILED: 602 id = TextUtils.isEmpty(message) 603 ? R.string.account_setup_failed_dlg_auth_message 604 : R.string.account_setup_failed_dlg_auth_message_fmt; 605 break; 606 case MessagingException.AUTHENTICATION_FAILED_OR_SERVER_ERROR: 607 id = R.string.account_setup_failed_check_credentials_message; 608 break; 609 case MessagingException.IOERROR: 610 id = R.string.account_setup_failed_ioerror; 611 break; 612 case MessagingException.TLS_REQUIRED: 613 id = R.string.account_setup_failed_tls_required; 614 break; 615 case MessagingException.AUTH_REQUIRED: 616 id = R.string.account_setup_failed_auth_required; 617 break; 618 case MessagingException.SECURITY_POLICIES_UNSUPPORTED: 619 id = R.string.account_setup_failed_security_policies_unsupported; 620 // Belt and suspenders here; there should always be a non-empty array here 621 String[] unsupportedPolicies = (String[])result.getExceptionData(); 622 if (unsupportedPolicies == null) { 623 Log.w(TAG, "No data for unsupported policies?"); 624 break; 625 } 626 // Build a string, concatenating policies we don't support 627 StringBuilder sb = new StringBuilder(); 628 boolean first = true; 629 for (String policyName: unsupportedPolicies) { 630 if (first) { 631 first = false; 632 } else { 633 sb.append(", "); 634 } 635 sb.append(policyName); 636 } 637 message = sb.toString(); 638 break; 639 case MessagingException.ACCESS_DENIED: 640 id = R.string.account_setup_failed_access_denied; 641 break; 642 case MessagingException.PROTOCOL_VERSION_UNSUPPORTED: 643 id = R.string.account_setup_failed_protocol_unsupported; 644 break; 645 case MessagingException.GENERAL_SECURITY: 646 id = R.string.account_setup_failed_security; 647 break; 648 case MessagingException.CLIENT_CERTIFICATE_ERROR: 649 id = R.string.account_setup_failed_certificate_required; 650 break; 651 default: 652 id = TextUtils.isEmpty(message) 653 ? R.string.account_setup_failed_dlg_server_message 654 : R.string.account_setup_failed_dlg_server_message_fmt; 655 break; 656 } 657 reportProgress(progressState, id, message, hostAuth); 658 } 659 } 660 } 661 662 /** 663 * Simple dialog that shows progress as we work through the settings checks. 664 * This is stateless except for its UI (e.g. current strings) and can be torn down or 665 * recreated at any time without affecting the account checking progress. 666 */ 667 public static class CheckingDialog extends DialogFragment { 668 @SuppressWarnings("hiding") 669 public final static String TAG = "CheckProgressDialog"; 670 671 // Extras for saved instance state 672 private final String EXTRA_PROGRESS_STRING = "CheckProgressDialog.Progress"; 673 674 // UI 675 private String mProgressString; 676 677 /** 678 * Create a dialog that reports progress 679 * @param progress initial progress indication 680 */ 681 public static CheckingDialog newInstance(AccountCheckSettingsFragment parentFragment, 682 int progress) { 683 CheckingDialog f = new CheckingDialog(); 684 f.setTargetFragment(parentFragment, progress); 685 return f; 686 } 687 688 /** 689 * Update the progress of an existing dialog 690 * @param progress latest progress to be displayed 691 */ 692 public void updateProgress(int progress) { 693 mProgressString = getProgressString(progress); 694 AlertDialog dialog = (AlertDialog) getDialog(); 695 dialog.setMessage(mProgressString); 696 } 697 698 @Override 699 public Dialog onCreateDialog(Bundle savedInstanceState) { 700 Context context = getActivity(); 701 if (savedInstanceState != null) { 702 mProgressString = savedInstanceState.getString(EXTRA_PROGRESS_STRING); 703 } 704 if (mProgressString == null) { 705 mProgressString = getProgressString(getTargetRequestCode()); 706 } 707 final AccountCheckSettingsFragment target = 708 (AccountCheckSettingsFragment) getTargetFragment(); 709 710 ProgressDialog dialog = new ProgressDialog(context); 711 dialog.setIndeterminate(true); 712 dialog.setMessage(mProgressString); 713 dialog.setButton(DialogInterface.BUTTON_NEGATIVE, 714 context.getString(R.string.cancel_action), 715 new DialogInterface.OnClickListener() { 716 public void onClick(DialogInterface dialog, int which) { 717 dismiss(); 718 target.onCheckingDialogCancel(); 719 } 720 }); 721 return dialog; 722 } 723 724 /** 725 * Listen for cancellation, which can happen from places other than the 726 * negative button (e.g. touching outside the dialog), and stop the checker 727 */ 728 @Override 729 public void onCancel(DialogInterface dialog) { 730 AccountCheckSettingsFragment target = 731 (AccountCheckSettingsFragment) getTargetFragment(); 732 target.onCheckingDialogCancel(); 733 super.onCancel(dialog); 734 } 735 736 @Override 737 public void onSaveInstanceState(Bundle outState) { 738 super.onSaveInstanceState(outState); 739 outState.putString(EXTRA_PROGRESS_STRING, mProgressString); 740 } 741 742 /** 743 * Convert progress to message 744 */ 745 private String getProgressString(int progress) { 746 int stringId = 0; 747 switch (progress) { 748 case STATE_CHECK_AUTODISCOVER: 749 stringId = R.string.account_setup_check_settings_retr_info_msg; 750 break; 751 case STATE_CHECK_INCOMING: 752 stringId = R.string.account_setup_check_settings_check_incoming_msg; 753 break; 754 case STATE_CHECK_OUTGOING: 755 stringId = R.string.account_setup_check_settings_check_outgoing_msg; 756 break; 757 } 758 return getActivity().getString(stringId); 759 } 760 } 761 762 /** 763 * The standard error dialog. Calls back to onErrorDialogButton(). 764 */ 765 public static class ErrorDialog extends DialogFragment { 766 @SuppressWarnings("hiding") 767 public final static String TAG = "ErrorDialog"; 768 769 // Bundle keys for arguments 770 private final static String ARGS_MESSAGE_ID = "ErrorDialog.Message.Id"; 771 private final static String ARGS_MESSAGE_ARGS = "ErrorDialog.Message.Args"; 772 773 public static ErrorDialog newInstance(AccountCheckSettingsFragment target, 774 int messageId, String... messageArguments) { 775 ErrorDialog fragment = new ErrorDialog(); 776 Bundle arguments = new Bundle(); 777 arguments.putInt(ARGS_MESSAGE_ID, messageId); 778 arguments.putStringArray(ARGS_MESSAGE_ARGS, messageArguments); 779 fragment.setArguments(arguments); 780 fragment.setTargetFragment(target, 0); 781 return fragment; 782 } 783 784 @Override 785 public Dialog onCreateDialog(Bundle savedInstanceState) { 786 final Context context = getActivity(); 787 final Bundle arguments = getArguments(); 788 final int messageId = arguments.getInt(ARGS_MESSAGE_ID); 789 final Object[] messageArguments = arguments.getStringArray(ARGS_MESSAGE_ARGS); 790 final AccountCheckSettingsFragment target = 791 (AccountCheckSettingsFragment) getTargetFragment(); 792 793 return new AlertDialog.Builder(context) 794 .setIconAttribute(android.R.attr.alertDialogIcon) 795 .setTitle(context.getString(R.string.account_setup_failed_dlg_title)) 796 .setMessage(context.getString(messageId, messageArguments)) 797 .setCancelable(true) 798 .setPositiveButton( 799 context.getString(R.string.account_setup_failed_dlg_edit_details_action), 800 new DialogInterface.OnClickListener() { 801 public void onClick(DialogInterface dialog, int which) { 802 dismiss(); 803 target.onErrorDialogEditButton(); 804 } 805 }) 806 .create(); 807 } 808 809 } 810 811 /** 812 * The "security required" error dialog. This is presented whenever an exchange account 813 * reports that it will require security policy control, and provide the user with the 814 * opportunity to accept or deny this. 815 * 816 * If the user clicks OK, calls onSecurityRequiredDialogResultOk(true) which reports back 817 * to the target as if the settings check was "ok". If the user clicks "cancel", calls 818 * onSecurityRequiredDialogResultOk(false) which simply closes the checker (this is the 819 * same as any other failed check.) 820 */ 821 public static class SecurityRequiredDialog extends DialogFragment { 822 @SuppressWarnings("hiding") 823 public final static String TAG = "SecurityRequiredDialog"; 824 825 // Bundle keys for arguments 826 private final static String ARGS_HOST_NAME = "SecurityRequiredDialog.HostName"; 827 828 public static SecurityRequiredDialog newInstance(AccountCheckSettingsFragment target, 829 String hostName) { 830 SecurityRequiredDialog fragment = new SecurityRequiredDialog(); 831 Bundle arguments = new Bundle(); 832 arguments.putString(ARGS_HOST_NAME, hostName); 833 fragment.setArguments(arguments); 834 fragment.setTargetFragment(target, 0); 835 return fragment; 836 } 837 838 @Override 839 public Dialog onCreateDialog(Bundle savedInstanceState) { 840 final Context context = getActivity(); 841 final Bundle arguments = getArguments(); 842 final String hostName = arguments.getString(ARGS_HOST_NAME); 843 final AccountCheckSettingsFragment target = 844 (AccountCheckSettingsFragment) getTargetFragment(); 845 846 return new AlertDialog.Builder(context) 847 .setIconAttribute(android.R.attr.alertDialogIcon) 848 .setTitle(context.getString(R.string.account_setup_security_required_title)) 849 .setMessage(context.getString( 850 R.string.account_setup_security_policies_required_fmt, hostName)) 851 .setCancelable(true) 852 .setPositiveButton( 853 context.getString(R.string.okay_action), 854 new DialogInterface.OnClickListener() { 855 public void onClick(DialogInterface dialog, int which) { 856 dismiss(); 857 target.onSecurityRequiredDialogResultOk(true); 858 } 859 }) 860 .setNegativeButton( 861 context.getString(R.string.cancel_action), 862 new DialogInterface.OnClickListener() { 863 public void onClick(DialogInterface dialog, int which) { 864 dismiss(); 865 target.onSecurityRequiredDialogResultOk(false); 866 } 867 }) 868 .create(); 869 } 870 871 } 872 873} 874