AccountCheckSettingsFragment.java revision 31d9acbf0623872f9d4a2b3210b5970854b654c7
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.EmailContent.Account; 25import com.android.emailcommon.provider.EmailContent.HostAuth; 26import com.android.emailcommon.service.EmailServiceProxy; 27import com.android.emailcommon.service.PolicySet; 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.util.Log; 42 43/** 44 * Check incoming or outgoing settings, or perform autodiscovery. 45 * 46 * There are three components that work together. 1. This fragment is retained and non-displayed, 47 * and controls the overall process. 2. An AsyncTask that works with the stores/services to 48 * check the accounts settings. 3. A stateless progress dialog (which will be recreated on 49 * orientation changes). 50 * 51 * There are also two lightweight error dialogs which are used for notification of terminal 52 * conditions. 53 */ 54public class AccountCheckSettingsFragment extends Fragment { 55 56 public final static String TAG = "AccountCheckSettingsFragment"; 57 58 // Debugging flags - for debugging the UI 59 // If true, use a "fake" account check cycle 60 private static final boolean DEBUG_FAKE_CHECK_CYCLE = false; // DO NOT CHECK IN TRUE 61 // If true, use fake check cycle, return failure 62 private static final boolean DEBUG_FAKE_CHECK_ERR = false; // DO NOT CHECK IN TRUE 63 // If true, use fake check cycle, return "security required" 64 private static final boolean DEBUG_FORCE_SECURITY_REQUIRED = false; // DO NOT CHECK IN TRUE 65 66 // State 67 private final static int STATE_START = 0; 68 private final static int STATE_CHECK_AUTODISCOVER = 1; 69 private final static int STATE_CHECK_INCOMING = 2; 70 private final static int STATE_CHECK_OUTGOING = 3; 71 private final static int STATE_CHECK_OK = 4; // terminal 72 private final static int STATE_CHECK_SHOW_SECURITY = 5; // terminal 73 private final static int STATE_CHECK_ERROR = 6; // terminal 74 private final static int STATE_AUTODISCOVER_AUTH_DIALOG = 7; // terminal 75 private final static int STATE_AUTODISCOVER_RESULT = 8; // terminal 76 private int mState = STATE_START; 77 78 // Support for UI 79 private boolean mAttached; 80 private CheckingDialog mCheckingDialog; 81 private int mErrorStringId; 82 private String mErrorMessage; 83 private HostAuth mAutoDiscoverResult; 84 85 // Support for AsyncTask and account checking 86 AccountCheckTask mAccountCheckTask; 87 88 // Result codes returned by onCheckSettingsComplete. 89 /** Check settings returned successfully */ 90 public final static int CHECK_SETTINGS_OK = 0; 91 /** Check settings failed due to connection, authentication, or other server error */ 92 public final static int CHECK_SETTINGS_SERVER_ERROR = 1; 93 /** Check settings failed due to user refusing to accept security requirements */ 94 public final static int CHECK_SETTINGS_SECURITY_USER_DENY = 2; 95 96 // Result codes returned by onAutoDiscoverComplete. 97 /** AutoDiscover completed successfully with server setup data */ 98 public final static int AUTODISCOVER_OK = 0; 99 /** AutoDiscover completed with no data (no server or AD not supported) */ 100 public final static int AUTODISCOVER_NO_DATA = 1; 101 /** AutoDiscover reported authentication error */ 102 public final static int AUTODISCOVER_AUTHENTICATION = 2; 103 104 /** 105 * Callback interface for any target or activity doing account check settings 106 */ 107 public interface Callbacks { 108 /** 109 * Called when CheckSettings completed 110 * @param result check settings result code - success is CHECK_SETTINGS_OK 111 */ 112 public void onCheckSettingsComplete(int result); 113 114 /** 115 * Called when autodiscovery completes. 116 * @param result autodiscovery result code - success is AUTODISCOVER_OK 117 * @param hostAuth configuration data returned by AD server, or null if no data available 118 */ 119 public void onAutoDiscoverComplete(int result, HostAuth hostAuth); 120 } 121 122 /** 123 * Create a retained, invisible fragment that checks accounts 124 * 125 * @param mode incoming or outgoing 126 */ 127 public static AccountCheckSettingsFragment newInstance(int mode, Fragment parentFragment) { 128 AccountCheckSettingsFragment f = new AccountCheckSettingsFragment(); 129 f.setTargetFragment(parentFragment, mode); 130 return f; 131 } 132 133 /** 134 * Fragment initialization. Because we never implement onCreateView, and call 135 * setRetainInstance here, this creates an invisible, persistent, "worker" fragment. 136 */ 137 @Override 138 public void onCreate(Bundle savedInstanceState) { 139 super.onCreate(savedInstanceState); 140 setRetainInstance(true); 141 } 142 143 /** 144 * This is called when the Fragment's Activity is ready to go, after 145 * its content view has been installed; it is called both after 146 * the initial fragment creation and after the fragment is re-attached 147 * to a new activity. 148 */ 149 @Override 150 public void onActivityCreated(Bundle savedInstanceState) { 151 super.onActivityCreated(savedInstanceState); 152 mAttached = true; 153 154 // If this is the first time, start the AsyncTask 155 if (mAccountCheckTask == null) { 156 int checkMode = getTargetRequestCode(); 157 Account checkAccount = SetupData.getAccount(); 158 mAccountCheckTask = (AccountCheckTask) 159 new AccountCheckTask(checkMode, checkAccount) 160 .execute(); 161 } 162 } 163 164 /** 165 * When resuming, restart the progress/error UI if necessary by re-reporting previous values 166 */ 167 @Override 168 public void onResume() { 169 super.onResume(); 170 171 if (mState != STATE_START) { 172 reportProgress(mState, mErrorStringId, mErrorMessage, mAutoDiscoverResult); 173 } 174 } 175 176 /** 177 * This is called when the fragment is going away. It is NOT called 178 * when the fragment is being propagated between activity instances. 179 */ 180 @Override 181 public void onDestroy() { 182 super.onDestroy(); 183 Utility.cancelTaskInterrupt(mAccountCheckTask); 184 mAccountCheckTask = null; 185 } 186 187 /** 188 * This is called right before the fragment is detached from its current activity instance. 189 * All reporting and callbacks are halted until we reattach. 190 */ 191 @Override 192 public void onDetach() { 193 super.onDetach(); 194 mAttached = false; 195 } 196 197 /** 198 * The worker (AsyncTask) will call this (in the UI thread) to report progress. If we are 199 * attached to an activity, update the progress immediately; If not, simply hold the 200 * progress for later. 201 * @param newState The new progress state being reported 202 * @param errorStringId Resource Id of an error string to display 203 * @param errorMessage Additional string to insert if the resource string takes a parameter. 204 * @param autoDiscoverResult If doing autodiscovery, the setup info returned from AD server 205 */ 206 public void reportProgress(int newState, int errorStringId, String errorMessage, 207 HostAuth autoDiscoverResult) { 208 mState = newState; 209 mErrorStringId = errorStringId; 210 mErrorMessage = errorMessage; 211 mAutoDiscoverResult = autoDiscoverResult; 212 213 // If we are attached, create, recover, and/or update the dialog 214 if (mAttached) { 215 FragmentManager fm = getFragmentManager(); 216 217 switch (newState) { 218 case STATE_CHECK_OK: 219 // immediately terminate, clean up, and report back 220 // 1. get rid of progress dialog (if any) 221 recoverAndDismissCheckingDialog(); 222 // 2. exit self 223 fm.popBackStack(); 224 // 3. report OK back to target fragment or activity 225 getCallbackTarget().onCheckSettingsComplete(CHECK_SETTINGS_OK); 226 break; 227 case STATE_CHECK_SHOW_SECURITY: 228 // 1. get rid of progress dialog (if any) 229 recoverAndDismissCheckingDialog(); 230 // 2. launch the error dialog, if needed 231 if (fm.findFragmentByTag(SecurityRequiredDialog.TAG) == null) { 232 SecurityRequiredDialog securityRequiredDialog = 233 SecurityRequiredDialog.newInstance(this, mErrorMessage); 234 fm.beginTransaction() 235 .add(securityRequiredDialog, SecurityRequiredDialog.TAG) 236 .commit(); 237 } 238 break; 239 case STATE_CHECK_ERROR: 240 case STATE_AUTODISCOVER_AUTH_DIALOG: 241 // 1. get rid of progress dialog (if any) 242 recoverAndDismissCheckingDialog(); 243 // 2. launch the error dialog, if needed 244 if (fm.findFragmentByTag(ErrorDialog.TAG) == null) { 245 ErrorDialog errorDialog = 246 ErrorDialog.newInstance(this, mErrorStringId, mErrorMessage); 247 fm.beginTransaction() 248 .add(errorDialog, ErrorDialog.TAG) 249 .commit(); 250 } 251 break; 252 case STATE_AUTODISCOVER_RESULT: 253 // 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( 259 (mAutoDiscoverResult != null) ? AUTODISCOVER_OK : AUTODISCOVER_NO_DATA, 260 mAutoDiscoverResult); 261 break; 262 default: 263 // Display a normal progress message 264 mCheckingDialog = (CheckingDialog) fm.findFragmentByTag(CheckingDialog.TAG); 265 266 if (mCheckingDialog == null) { 267 mCheckingDialog = CheckingDialog.newInstance(this, mState); 268 fm.beginTransaction() 269 .add(mCheckingDialog, CheckingDialog.TAG) 270 .commit(); 271 } else { 272 mCheckingDialog.updateProgress(mState); 273 } 274 break; 275 } 276 } 277 } 278 279 /** 280 * 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(); 292 } 293 294 /** 295 * 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) { 303 mCheckingDialog.dismiss(); 304 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" 318 getFragmentManager().popBackStack(); 319 } 320 321 /** 322 * This is called when the user clicks "edit" from the error dialog. The error dialog 323 * should have already dismissed itself. 324 * Depending on the context, the target will remain in the current activity (e.g. editing 325 * settings) or return to its own parent (e.g. enter new credentials). 326 */ 327 private void onErrorDialogEditButton() { 328 Callbacks callbackTarget = getCallbackTarget(); 329 if (mState == STATE_AUTODISCOVER_AUTH_DIALOG) { 330 // report auth error to target fragment or activity 331 callbackTarget.onAutoDiscoverComplete(AUTODISCOVER_AUTHENTICATION, null); 332 } else { 333 // report check settings failure to target fragment or activity 334 callbackTarget.onCheckSettingsComplete(CHECK_SETTINGS_SERVER_ERROR); 335 } 336 getFragmentManager().popBackStack(); 337 } 338 339 /** 340 * This is called when the user clicks "ok" or "cancel" on the "security required" dialog. 341 * Shuts everything down and dismisses everything, and reports the result appropriately. 342 */ 343 private void onSecurityRequiredDialogResultOk(boolean okPressed) { 344 // 1. handle OK/cancel - notify that security is OK and we can proceed 345 Callbacks callbackTarget = getCallbackTarget(); 346 callbackTarget.onCheckSettingsComplete( 347 okPressed ? CHECK_SETTINGS_OK : CHECK_SETTINGS_SECURITY_USER_DENY); 348 349 // 2. kill self if not already killed by callback 350 FragmentManager fm = getFragmentManager(); 351 if (fm != null) { 352 fm.popBackStack(); 353 } 354 } 355 356 /** 357 * This exception class is used to report autodiscover results via the reporting mechanism. 358 */ 359 public static class AutoDiscoverResults extends MessagingException { 360 public final HostAuth mHostAuth; 361 362 /** 363 * @param authenticationError true if auth failure, false for result (or no response) 364 * @param hostAuth null for "no autodiscover", non-null for server info to return 365 */ 366 public AutoDiscoverResults(boolean authenticationError, HostAuth hostAuth) { 367 super(null); 368 if (authenticationError) { 369 mExceptionType = AUTODISCOVER_AUTHENTICATION_FAILED; 370 } else { 371 mExceptionType = AUTODISCOVER_AUTHENTICATION_RESULT; 372 } 373 mHostAuth = hostAuth; 374 } 375 } 376 377 /** 378 * This AsyncTask does the actual account checking 379 * 380 * TODO: It would be better to remove the UI complete from here (the exception->string 381 * conversions). 382 */ 383 private class AccountCheckTask extends AsyncTask<Void, Integer, MessagingException> { 384 385 final Context mContext; 386 final int mMode; 387 final String mStoreUri; 388 final String mStoreHost; 389 final String mSenderUri; 390 final String mCheckEmail; 391 final String mCheckPassword; 392 393 /** 394 * Create task and parameterize it 395 * @param mode bits request operations 396 * @param checkAccount account holding values to be checked 397 */ 398 public AccountCheckTask(int mode, Account checkAccount) { 399 mContext = getActivity().getApplicationContext(); 400 mMode = mode; 401 mStoreUri = checkAccount.getStoreUri(mContext); 402 mStoreHost = checkAccount.mHostAuthRecv.mAddress; 403 mSenderUri = checkAccount.getSenderUri(mContext); 404 mCheckEmail = checkAccount.mEmailAddress; 405 mCheckPassword = checkAccount.mHostAuthRecv.mPassword; 406 } 407 408 @Override 409 protected MessagingException doInBackground(Void... params) { 410 if (DEBUG_FAKE_CHECK_CYCLE) { 411 return fakeChecker(); 412 } 413 414 try { 415 if ((mMode & SetupData.CHECK_AUTODISCOVER) != 0) { 416 if (isCancelled()) return null; 417 publishProgress(STATE_CHECK_AUTODISCOVER); 418 Log.d(Logging.LOG_TAG, "Begin auto-discover for " + mCheckEmail); 419 Store store = Store.getInstance(mStoreUri, mContext, null); 420 Bundle result = store.autoDiscover(mContext, mCheckEmail, mCheckPassword); 421 // Result will be one of: 422 // null: remote exception - proceed to manual setup 423 // MessagingException.AUTHENTICATION_FAILED: username/password rejected 424 // Other error: proceed to manual setup 425 // No error: return autodiscover results 426 if (result == null) { 427 return new AutoDiscoverResults(false, null); 428 } 429 int errorCode = 430 result.getInt(EmailServiceProxy.AUTO_DISCOVER_BUNDLE_ERROR_CODE); 431 if (errorCode == MessagingException.AUTHENTICATION_FAILED) { 432 return new AutoDiscoverResults(true, null); 433 } else if (errorCode != MessagingException.NO_ERROR) { 434 return new AutoDiscoverResults(false, null); 435 } else { 436 HostAuth serverInfo = (HostAuth) 437 result.getParcelable(EmailServiceProxy.AUTO_DISCOVER_BUNDLE_HOST_AUTH); 438 return new AutoDiscoverResults(false, serverInfo); 439 } 440 } 441 442 // Check Incoming Settings 443 if ((mMode & SetupData.CHECK_INCOMING) != 0) { 444 if (isCancelled()) return null; 445 Log.d(Logging.LOG_TAG, "Begin check of incoming email settings"); 446 publishProgress(STATE_CHECK_INCOMING); 447 Store store = Store.getInstance(mStoreUri, mContext, null); 448 Bundle bundle = store.checkSettings(); 449 int resultCode = MessagingException.UNSPECIFIED_EXCEPTION; 450 if (bundle != null) { 451 resultCode = bundle.getInt( 452 EmailServiceProxy.VALIDATE_BUNDLE_RESULT_CODE); 453 } 454 if (resultCode == MessagingException.SECURITY_POLICIES_REQUIRED) { 455 SetupData.setPolicySet((PolicySet)bundle.getParcelable( 456 EmailServiceProxy.VALIDATE_BUNDLE_POLICY_SET)); 457 return new MessagingException( 458 MessagingException.SECURITY_POLICIES_REQUIRED, mStoreHost); 459 } 460 if (resultCode != MessagingException.NO_ERROR) { 461 String errorMessage = 462 bundle.getString(EmailServiceProxy.VALIDATE_BUNDLE_ERROR_MESSAGE); 463 return new MessagingException(resultCode, errorMessage); 464 } 465 } 466 467 // Check Outgoing Settings 468 if ((mMode & SetupData.CHECK_OUTGOING) != 0) { 469 if (isCancelled()) return null; 470 Log.d(Logging.LOG_TAG, "Begin check of outgoing email settings"); 471 publishProgress(STATE_CHECK_OUTGOING); 472 Sender sender = Sender.getInstance(mContext, mSenderUri); 473 sender.close(); 474 sender.open(); 475 sender.close(); 476 } 477 478 // If we reached the end, we completed the check(s) successfully 479 return null; 480 } catch (final MessagingException me) { 481 // Some of the legacy account checkers return errors by throwing MessagingException, 482 // which we catch and return here. 483 return me; 484 } 485 } 486 487 /** 488 * Dummy background worker, for testing UI only. 489 */ 490 private MessagingException fakeChecker() { 491 // Dummy: Publish a series of progress setups, 2 sec delays between them; 492 // then return "ok" (null) 493 final int DELAY = 2*1000; 494 if (isCancelled()) return null; 495 if ((mMode & SetupData.CHECK_AUTODISCOVER) != 0) { 496 publishProgress(STATE_CHECK_AUTODISCOVER); 497 try { 498 Thread.sleep(DELAY); 499 } catch (InterruptedException e) { } 500 if (DEBUG_FAKE_CHECK_ERR) { 501 return new MessagingException(MessagingException.AUTHENTICATION_FAILED); 502 } 503 // Return "real" AD results 504 HostAuth serverInfo = new HostAuth(); 505 serverInfo.setStoreUri("eas://user:password@testserver.com"); 506 return new AutoDiscoverResults(false, serverInfo); 507 } 508 if (isCancelled()) return null; 509 if ((mMode & SetupData.CHECK_INCOMING) != 0) { 510 publishProgress(STATE_CHECK_INCOMING); 511 try { 512 Thread.sleep(DELAY); 513 } catch (InterruptedException e) { } 514 if (DEBUG_FAKE_CHECK_ERR) { 515 return new MessagingException(MessagingException.IOERROR); 516 } else if (DEBUG_FORCE_SECURITY_REQUIRED) { 517 return new MessagingException( 518 MessagingException.SECURITY_POLICIES_REQUIRED); 519 } 520 } 521 if (isCancelled()) return null; 522 if ((mMode & SetupData.CHECK_OUTGOING) != 0) { 523 publishProgress(STATE_CHECK_OUTGOING); 524 try { 525 Thread.sleep(DELAY); 526 } catch (InterruptedException e) { } 527 if (DEBUG_FAKE_CHECK_ERR) { 528 return new MessagingException(MessagingException.TLS_REQUIRED); 529 } 530 } 531 return null; 532 } 533 534 /** 535 * Progress reports (runs in UI thread). This should be used for real progress only 536 * (not for errors). 537 */ 538 @Override 539 protected void onProgressUpdate(Integer... progress) { 540 if (isCancelled()) return; 541 reportProgress(progress[0], 0, null, null); 542 } 543 544 /** 545 * Result handler (runs in UI thread). 546 * 547 * AutoDiscover authentication errors are handled a bit differently than the 548 * other errors; If encountered, we display the error dialog, but we return with 549 * a different callback used only for AutoDiscover. 550 * 551 * @param result null for a successful check; exception for various errors 552 */ 553 @Override 554 protected void onPostExecute(MessagingException result) { 555 if (isCancelled()) return; 556 if (result == null) { 557 reportProgress(STATE_CHECK_OK, 0, null, null); 558 } else { 559 int progressState = STATE_CHECK_ERROR; 560 int exceptionType = result.getExceptionType(); 561 String message = result.getMessage(); 562 HostAuth hostAuth = null; 563 int id = 0; 564 565 switch (exceptionType) { 566 // NOTE: AutoDiscover reports have their own reporting state, handle differently 567 // from the other exception types 568 case MessagingException.AUTODISCOVER_AUTHENTICATION_FAILED: 569 id = (message == null) 570 ? R.string.account_setup_failed_dlg_auth_message 571 : R.string.account_setup_failed_dlg_auth_message_fmt; 572 progressState = STATE_AUTODISCOVER_AUTH_DIALOG; 573 break; 574 case MessagingException.AUTODISCOVER_AUTHENTICATION_RESULT: 575 hostAuth = ((AutoDiscoverResults)result).mHostAuth; 576 progressState = STATE_AUTODISCOVER_RESULT; 577 break; 578 579 // NOTE: Security policies required has its own report state, handle it a bit 580 // differently from the other exception types. 581 case MessagingException.SECURITY_POLICIES_REQUIRED: 582 progressState = STATE_CHECK_SHOW_SECURITY; 583 break; 584 585 // The remaining exception types are handled by setting the state to 586 // STATE_CHECK_ERROR (above, default) and conversion to specific error strings. 587 case MessagingException.CERTIFICATE_VALIDATION_ERROR: 588 id = (message == null) 589 ? R.string.account_setup_failed_dlg_certificate_message 590 : R.string.account_setup_failed_dlg_certificate_message_fmt; 591 break; 592 case MessagingException.AUTHENTICATION_FAILED: 593 id = (message == null) 594 ? R.string.account_setup_failed_dlg_auth_message 595 : R.string.account_setup_failed_dlg_auth_message_fmt; 596 break; 597 case MessagingException.AUTHENTICATION_FAILED_OR_SERVER_ERROR: 598 id = R.string.account_setup_failed_check_credentials_message; 599 break; 600 case MessagingException.IOERROR: 601 id = R.string.account_setup_failed_ioerror; 602 break; 603 case MessagingException.TLS_REQUIRED: 604 id = R.string.account_setup_failed_tls_required; 605 break; 606 case MessagingException.AUTH_REQUIRED: 607 id = R.string.account_setup_failed_auth_required; 608 break; 609 case MessagingException.SECURITY_POLICIES_UNSUPPORTED: 610 id = R.string.account_setup_failed_security_policies_unsupported; 611 break; 612 case MessagingException.PROTOCOL_VERSION_UNSUPPORTED: 613 id = R.string.account_setup_failed_protocol_unsupported; 614 break; 615 case MessagingException.GENERAL_SECURITY: 616 id = R.string.account_setup_failed_security; 617 break; 618 default: 619 id = (message == null) 620 ? R.string.account_setup_failed_dlg_server_message 621 : R.string.account_setup_failed_dlg_server_message_fmt; 622 break; 623 } 624 reportProgress(progressState, id, message, hostAuth); 625 } 626 } 627 } 628 629 /** 630 * Simple dialog that shows progress as we work through the settings checks. 631 * This is stateless except for its UI (e.g. current strings) and can be torn down or 632 * recreated at any time without affecting the account checking progress. 633 */ 634 public static class CheckingDialog extends DialogFragment { 635 public final static String TAG = "CheckProgressDialog"; 636 637 // Extras for saved instance state 638 private final String EXTRA_PROGRESS_STRING = "CheckProgressDialog.Progress"; 639 640 // UI 641 private String mProgressString; 642 643 /** 644 * Create a dialog that reports progress 645 * @param progress initial progress indication 646 */ 647 public static CheckingDialog newInstance(AccountCheckSettingsFragment parentFragment, 648 int progress) { 649 CheckingDialog f = new CheckingDialog(); 650 f.setTargetFragment(parentFragment, progress); 651 return f; 652 } 653 654 /** 655 * Update the progress of an existing dialog 656 * @param progress latest progress to be displayed 657 */ 658 public void updateProgress(int progress) { 659 mProgressString = getProgressString(progress); 660 AlertDialog dialog = (AlertDialog) getDialog(); 661 dialog.setMessage(mProgressString); 662 } 663 664 @Override 665 public Dialog onCreateDialog(Bundle savedInstanceState) { 666 Context context = getActivity(); 667 if (savedInstanceState != null) { 668 mProgressString = savedInstanceState.getString(EXTRA_PROGRESS_STRING); 669 } 670 if (mProgressString == null) { 671 mProgressString = getProgressString(getTargetRequestCode()); 672 } 673 final AccountCheckSettingsFragment target = 674 (AccountCheckSettingsFragment) getTargetFragment(); 675 676 ProgressDialog dialog = new ProgressDialog(context); 677 dialog.setIndeterminate(true); 678 dialog.setMessage(mProgressString); 679 dialog.setButton(DialogInterface.BUTTON_NEGATIVE, 680 context.getString(R.string.cancel_action), 681 new DialogInterface.OnClickListener() { 682 public void onClick(DialogInterface dialog, int which) { 683 dismiss(); 684 target.onCheckingDialogCancel(); 685 } 686 }); 687 return dialog; 688 } 689 690 /** 691 * Listen for cancellation, which can happen from places other than the 692 * negative button (e.g. touching outside the dialog), and stop the checker 693 */ 694 @Override 695 public void onCancel(DialogInterface dialog) { 696 AccountCheckSettingsFragment target = 697 (AccountCheckSettingsFragment) getTargetFragment(); 698 target.onCheckingDialogCancel(); 699 super.onCancel(dialog); 700 } 701 702 @Override 703 public void onSaveInstanceState(Bundle outState) { 704 super.onSaveInstanceState(outState); 705 outState.putString(EXTRA_PROGRESS_STRING, mProgressString); 706 } 707 708 /** 709 * Convert progress to message 710 */ 711 private String getProgressString(int progress) { 712 int stringId = 0; 713 switch (progress) { 714 case STATE_CHECK_AUTODISCOVER: 715 stringId = R.string.account_setup_check_settings_retr_info_msg; 716 break; 717 case STATE_CHECK_INCOMING: 718 stringId = R.string.account_setup_check_settings_check_incoming_msg; 719 break; 720 case STATE_CHECK_OUTGOING: 721 stringId = R.string.account_setup_check_settings_check_outgoing_msg; 722 break; 723 } 724 return getActivity().getString(stringId); 725 } 726 } 727 728 /** 729 * The standard error dialog. Calls back to onErrorDialogButton(). 730 */ 731 public static class ErrorDialog extends DialogFragment { 732 public final static String TAG = "ErrorDialog"; 733 734 // Bundle keys for arguments 735 private final static String ARGS_MESSAGE_ID = "ErrorDialog.Message.Id"; 736 private final static String ARGS_MESSAGE_ARGS = "ErrorDialog.Message.Args"; 737 738 public static ErrorDialog newInstance(AccountCheckSettingsFragment target, 739 int messageId, String... messageArguments) { 740 ErrorDialog fragment = new ErrorDialog(); 741 Bundle arguments = new Bundle(); 742 arguments.putInt(ARGS_MESSAGE_ID, messageId); 743 arguments.putStringArray(ARGS_MESSAGE_ARGS, messageArguments); 744 fragment.setArguments(arguments); 745 fragment.setTargetFragment(target, 0); 746 return fragment; 747 } 748 749 @Override 750 public Dialog onCreateDialog(Bundle savedInstanceState) { 751 final Context context = getActivity(); 752 final Bundle arguments = getArguments(); 753 final int messageId = arguments.getInt(ARGS_MESSAGE_ID); 754 final Object[] messageArguments = arguments.getStringArray(ARGS_MESSAGE_ARGS); 755 final AccountCheckSettingsFragment target = 756 (AccountCheckSettingsFragment) getTargetFragment(); 757 758 return new AlertDialog.Builder(context) 759 .setIconAttribute(android.R.attr.alertDialogIcon) 760 .setTitle(context.getString(R.string.account_setup_failed_dlg_title)) 761 .setMessage(context.getString(messageId, messageArguments)) 762 .setCancelable(true) 763 .setPositiveButton( 764 context.getString(R.string.account_setup_failed_dlg_edit_details_action), 765 new DialogInterface.OnClickListener() { 766 public void onClick(DialogInterface dialog, int which) { 767 dismiss(); 768 target.onErrorDialogEditButton(); 769 } 770 }) 771 .create(); 772 } 773 774 } 775 776 /** 777 * The "security required" error dialog. This is presented whenever an exchange account 778 * reports that it will require security policy control, and provide the user with the 779 * opportunity to accept or deny this. 780 * 781 * If the user clicks OK, calls onSecurityRequiredDialogResultOk(true) which reports back 782 * to the target as if the settings check was "ok". If the user clicks "cancel", calls 783 * onSecurityRequiredDialogResultOk(false) which simply closes the checker (this is the 784 * same as any other failed check.) 785 */ 786 public static class SecurityRequiredDialog extends DialogFragment { 787 public final static String TAG = "SecurityRequiredDialog"; 788 789 // Bundle keys for arguments 790 private final static String ARGS_HOST_NAME = "SecurityRequiredDialog.HostName"; 791 792 public static SecurityRequiredDialog newInstance(AccountCheckSettingsFragment target, 793 String hostName) { 794 SecurityRequiredDialog fragment = new SecurityRequiredDialog(); 795 Bundle arguments = new Bundle(); 796 arguments.putString(ARGS_HOST_NAME, hostName); 797 fragment.setArguments(arguments); 798 fragment.setTargetFragment(target, 0); 799 return fragment; 800 } 801 802 @Override 803 public Dialog onCreateDialog(Bundle savedInstanceState) { 804 final Context context = getActivity(); 805 final Bundle arguments = getArguments(); 806 final String hostName = arguments.getString(ARGS_HOST_NAME); 807 final AccountCheckSettingsFragment target = 808 (AccountCheckSettingsFragment) getTargetFragment(); 809 810 return new AlertDialog.Builder(context) 811 .setIconAttribute(android.R.attr.alertDialogIcon) 812 .setTitle(context.getString(R.string.account_setup_security_required_title)) 813 .setMessage(context.getString( 814 R.string.account_setup_security_policies_required_fmt, hostName)) 815 .setCancelable(true) 816 .setPositiveButton( 817 context.getString(R.string.okay_action), 818 new DialogInterface.OnClickListener() { 819 public void onClick(DialogInterface dialog, int which) { 820 dismiss(); 821 target.onSecurityRequiredDialogResultOk(true); 822 } 823 }) 824 .setNegativeButton( 825 context.getString(R.string.cancel_action), 826 new DialogInterface.OnClickListener() { 827 public void onClick(DialogInterface dialog, int which) { 828 dismiss(); 829 target.onSecurityRequiredDialogResultOk(false); 830 } 831 }) 832 .create(); 833 } 834 835 } 836 837} 838