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