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