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 17 package com.android.email.activity.setup; 18 19 import android.app.Activity; 20 import android.content.Context; 21 import android.content.Intent; 22 import android.net.Uri; 23 import android.os.Bundle; 24 import android.text.Editable; 25 import android.text.TextUtils; 26 import android.text.TextWatcher; 27 import android.text.method.DigitsKeyListener; 28 import android.view.LayoutInflater; 29 import android.view.View; 30 import android.view.ViewGroup; 31 import android.view.inputmethod.EditorInfo; 32 import android.widget.AdapterView; 33 import android.widget.ArrayAdapter; 34 import android.widget.EditText; 35 import android.widget.Spinner; 36 import android.widget.TextView; 37 38 import com.android.email.R; 39 import com.android.email.activity.UiUtilities; 40 import com.android.email.provider.AccountBackupRestore; 41 import com.android.email.service.EmailServiceUtils; 42 import com.android.email.service.EmailServiceUtils.EmailServiceInfo; 43 import com.android.email.view.CertificateSelector; 44 import com.android.email.view.CertificateSelector.HostCallback; 45 import com.android.email2.ui.MailActivityEmail; 46 import com.android.emailcommon.Device; 47 import com.android.emailcommon.Logging; 48 import com.android.emailcommon.provider.Account; 49 import com.android.emailcommon.provider.HostAuth; 50 import com.android.emailcommon.utility.CertificateRequestor; 51 import com.android.emailcommon.utility.Utility; 52 import com.android.mail.utils.LogUtils; 53 54 import java.io.IOException; 55 import java.util.ArrayList; 56 57 /** 58 * Provides UI for IMAP/POP account settings. 59 * 60 * This fragment is used by AccountSetupIncoming (for creating accounts) and by AccountSettingsXL 61 * (for editing existing accounts). 62 */ 63 public class AccountSetupIncomingFragment extends AccountServerBaseFragment 64 implements HostCallback { 65 66 private static final int CERTIFICATE_REQUEST = 0; 67 private final static String STATE_KEY_CREDENTIAL = "AccountSetupIncomingFragment.credential"; 68 private final static String STATE_KEY_LOADED = "AccountSetupIncomingFragment.loaded"; 69 70 private EditText mUsernameView; 71 private EditText mPasswordView; 72 private TextView mServerLabelView; 73 private EditText mServerView; 74 private EditText mPortView; 75 private Spinner mSecurityTypeView; 76 private TextView mDeletePolicyLabelView; 77 private Spinner mDeletePolicyView; 78 private View mImapPathPrefixSectionView; 79 private View mDeviceIdSectionView; 80 private EditText mImapPathPrefixView; 81 private CertificateSelector mClientCertificateSelector; 82 // Delete policy as loaded from the device 83 private int mLoadedDeletePolicy; 84 85 private TextWatcher mValidationTextWatcher; 86 87 // Support for lifecycle 88 private boolean mStarted; 89 private boolean mLoaded; 90 private String mCacheLoginCredential; 91 private EmailServiceInfo mServiceInfo; 92 93 // Public no-args constructor needed for fragment re-instantiation 94 public AccountSetupIncomingFragment() {} 95 96 /** 97 * Called to do initial creation of a fragment. This is called after 98 * {@link #onAttach(Activity)} and before {@link #onActivityCreated(Bundle)}. 99 */ 100 @Override 101 public void onCreate(Bundle savedInstanceState) { 102 if (Logging.DEBUG_LIFECYCLE && MailActivityEmail.DEBUG) { 103 LogUtils.d(Logging.LOG_TAG, "AccountSetupIncomingFragment onCreate"); 104 } 105 super.onCreate(savedInstanceState); 106 107 if (savedInstanceState != null) { 108 mCacheLoginCredential = savedInstanceState.getString(STATE_KEY_CREDENTIAL); 109 mLoaded = savedInstanceState.getBoolean(STATE_KEY_LOADED, false); 110 } 111 } 112 113 @Override 114 public View onCreateView(LayoutInflater inflater, ViewGroup container, 115 Bundle savedInstanceState) { 116 if (Logging.DEBUG_LIFECYCLE && MailActivityEmail.DEBUG) { 117 LogUtils.d(Logging.LOG_TAG, "AccountSetupIncomingFragment onCreateView"); 118 } 119 final int layoutId = mSettingsMode 120 ? R.layout.account_settings_incoming_fragment 121 : R.layout.account_setup_incoming_fragment; 122 123 final View view = inflater.inflate(layoutId, container, false); 124 125 mUsernameView = UiUtilities.getView(view, R.id.account_username); 126 mPasswordView = UiUtilities.getView(view, R.id.account_password); 127 mServerLabelView = UiUtilities.getView(view, R.id.account_server_label); 128 mServerView = UiUtilities.getView(view, R.id.account_server); 129 mPortView = UiUtilities.getView(view, R.id.account_port); 130 mSecurityTypeView = UiUtilities.getView(view, R.id.account_security_type); 131 mDeletePolicyLabelView = UiUtilities.getView(view, R.id.account_delete_policy_label); 132 mDeletePolicyView = UiUtilities.getView(view, R.id.account_delete_policy); 133 mImapPathPrefixSectionView = UiUtilities.getView(view, R.id.imap_path_prefix_section); 134 mDeviceIdSectionView = UiUtilities.getView(view, R.id.device_id_section); 135 mImapPathPrefixView = UiUtilities.getView(view, R.id.imap_path_prefix); 136 mClientCertificateSelector = UiUtilities.getView(view, R.id.client_certificate_selector); 137 138 // Updates the port when the user changes the security type. This allows 139 // us to show a reasonable default which the user can change. 140 mSecurityTypeView.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { 141 @Override 142 public void onItemSelected(AdapterView<?> arg0, View arg1, int arg2, long arg3) { 143 updatePortFromSecurityType(); 144 } 145 146 @Override 147 public void onNothingSelected(AdapterView<?> arg0) { } 148 }); 149 150 // After any text edits, call validateFields() which enables or disables the Next button 151 mValidationTextWatcher = new TextWatcher() { 152 @Override 153 public void afterTextChanged(Editable s) { 154 validateFields(); 155 } 156 157 @Override 158 public void beforeTextChanged(CharSequence s, int start, int count, int after) { } 159 @Override 160 public void onTextChanged(CharSequence s, int start, int before, int count) { } 161 }; 162 // We're editing an existing account; don't allow modification of the user name 163 if (mSettingsMode) { 164 makeTextViewUneditable(mUsernameView, 165 getString(R.string.account_setup_username_uneditable_error)); 166 } 167 mUsernameView.addTextChangedListener(mValidationTextWatcher); 168 mPasswordView.addTextChangedListener(mValidationTextWatcher); 169 mServerView.addTextChangedListener(mValidationTextWatcher); 170 mPortView.addTextChangedListener(mValidationTextWatcher); 171 172 // Only allow digits in the port field. 173 mPortView.setKeyListener(DigitsKeyListener.getInstance("0123456789")); 174 175 // Additional setup only used while in "settings" mode 176 onCreateViewSettingsMode(view); 177 178 return view; 179 } 180 181 @Override 182 public void onActivityCreated(Bundle savedInstanceState) { 183 if (Logging.DEBUG_LIFECYCLE && MailActivityEmail.DEBUG) { 184 LogUtils.d(Logging.LOG_TAG, "AccountSetupIncomingFragment onActivityCreated"); 185 } 186 super.onActivityCreated(savedInstanceState); 187 mClientCertificateSelector.setHostActivity(this); 188 189 final Context context = getActivity(); 190 final SetupData.SetupDataContainer container = (SetupData.SetupDataContainer) context; 191 mSetupData = container.getSetupData(); 192 193 final HostAuth recvAuth = mSetupData.getAccount().mHostAuthRecv; 194 mServiceInfo = EmailServiceUtils.getServiceInfo(mContext, recvAuth.mProtocol); 195 196 if (mServiceInfo.offerLocalDeletes) { 197 SpinnerOption deletePolicies[] = { 198 new SpinnerOption(Account.DELETE_POLICY_NEVER, 199 context.getString( 200 R.string.account_setup_incoming_delete_policy_never_label)), 201 new SpinnerOption(Account.DELETE_POLICY_ON_DELETE, 202 context.getString( 203 R.string.account_setup_incoming_delete_policy_delete_label)), 204 }; 205 ArrayAdapter<SpinnerOption> deletePoliciesAdapter = 206 new ArrayAdapter<SpinnerOption>(context, 207 android.R.layout.simple_spinner_item, deletePolicies); 208 deletePoliciesAdapter.setDropDownViewResource( 209 android.R.layout.simple_spinner_dropdown_item); 210 mDeletePolicyView.setAdapter(deletePoliciesAdapter); 211 } 212 213 // Set up security type spinner 214 ArrayList<SpinnerOption> securityTypes = new ArrayList<SpinnerOption>(); 215 securityTypes.add( 216 new SpinnerOption(HostAuth.FLAG_NONE, context.getString( 217 R.string.account_setup_incoming_security_none_label))); 218 securityTypes.add( 219 new SpinnerOption(HostAuth.FLAG_SSL, context.getString( 220 R.string.account_setup_incoming_security_ssl_label))); 221 securityTypes.add( 222 new SpinnerOption(HostAuth.FLAG_SSL | HostAuth.FLAG_TRUST_ALL, context.getString( 223 R.string.account_setup_incoming_security_ssl_trust_certificates_label))); 224 if (mServiceInfo.offerTls) { 225 securityTypes.add( 226 new SpinnerOption(HostAuth.FLAG_TLS, context.getString( 227 R.string.account_setup_incoming_security_tls_label))); 228 securityTypes.add(new SpinnerOption(HostAuth.FLAG_TLS | HostAuth.FLAG_TRUST_ALL, 229 context.getString(R.string 230 .account_setup_incoming_security_tls_trust_certificates_label))); 231 } 232 ArrayAdapter<SpinnerOption> securityTypesAdapter = new ArrayAdapter<SpinnerOption>( 233 context, android.R.layout.simple_spinner_item, securityTypes); 234 securityTypesAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); 235 mSecurityTypeView.setAdapter(securityTypesAdapter); 236 } 237 238 /** 239 * Called when the Fragment is visible to the user. 240 */ 241 @Override 242 public void onStart() { 243 if (Logging.DEBUG_LIFECYCLE && MailActivityEmail.DEBUG) { 244 LogUtils.d(Logging.LOG_TAG, "AccountSetupIncomingFragment onStart"); 245 } 246 super.onStart(); 247 mStarted = true; 248 configureEditor(); 249 loadSettings(); 250 } 251 252 /** 253 * Called when the fragment is visible to the user and actively running. 254 */ 255 @Override 256 public void onResume() { 257 if (Logging.DEBUG_LIFECYCLE && MailActivityEmail.DEBUG) { 258 LogUtils.d(Logging.LOG_TAG, "AccountSetupIncomingFragment onResume"); 259 } 260 super.onResume(); 261 validateFields(); 262 } 263 264 @Override 265 public void onPause() { 266 if (Logging.DEBUG_LIFECYCLE && MailActivityEmail.DEBUG) { 267 LogUtils.d(Logging.LOG_TAG, "AccountSetupIncomingFragment onPause"); 268 } 269 super.onPause(); 270 } 271 272 /** 273 * Called when the Fragment is no longer started. 274 */ 275 @Override 276 public void onStop() { 277 if (Logging.DEBUG_LIFECYCLE && MailActivityEmail.DEBUG) { 278 LogUtils.d(Logging.LOG_TAG, "AccountSetupIncomingFragment onStop"); 279 } 280 super.onStop(); 281 mStarted = false; 282 } 283 284 @Override 285 public void onDestroyView() { 286 // Make sure we don't get callbacks after the views are supposed to be destroyed 287 // and also don't hold onto them longer than we need 288 if (mUsernameView != null) { 289 mUsernameView.removeTextChangedListener(mValidationTextWatcher); 290 } 291 mUsernameView = null; 292 if (mPasswordView != null) { 293 mPasswordView.removeTextChangedListener(mValidationTextWatcher); 294 } 295 mPasswordView = null; 296 mServerLabelView = null; 297 if (mServerView != null) { 298 mServerView.removeTextChangedListener(mValidationTextWatcher); 299 } 300 mServerView = null; 301 if (mPortView != null) { 302 mPortView.removeTextChangedListener(mValidationTextWatcher); 303 } 304 mPortView = null; 305 if (mSecurityTypeView != null) { 306 mSecurityTypeView.setOnItemSelectedListener(null); 307 } 308 mSecurityTypeView = null; 309 mDeletePolicyLabelView = null; 310 mDeletePolicyView = null; 311 mImapPathPrefixSectionView = null; 312 mDeviceIdSectionView = null; 313 mImapPathPrefixView = null; 314 mClientCertificateSelector = null; 315 316 super.onDestroyView(); 317 } 318 319 /** 320 * Called when the fragment is no longer in use. 321 */ 322 @Override 323 public void onDestroy() { 324 if (Logging.DEBUG_LIFECYCLE && MailActivityEmail.DEBUG) { 325 LogUtils.d(Logging.LOG_TAG, "AccountSetupIncomingFragment onDestroy"); 326 } 327 super.onDestroy(); 328 } 329 330 @Override 331 public void onSaveInstanceState(Bundle outState) { 332 if (Logging.DEBUG_LIFECYCLE && MailActivityEmail.DEBUG) { 333 LogUtils.d(Logging.LOG_TAG, "AccountSetupIncomingFragment onSaveInstanceState"); 334 } 335 super.onSaveInstanceState(outState); 336 337 outState.putString(STATE_KEY_CREDENTIAL, mCacheLoginCredential); 338 outState.putBoolean(STATE_KEY_LOADED, mLoaded); 339 } 340 341 /** 342 * Activity provides callbacks here. This also triggers loading and setting up the UX 343 */ 344 @Override 345 public void setCallback(Callback callback) { 346 super.setCallback(callback); 347 if (mStarted) { 348 configureEditor(); 349 loadSettings(); 350 } 351 } 352 353 /** 354 * Configure the editor for the account type 355 */ 356 private void configureEditor() { 357 final Account account = mSetupData.getAccount(); 358 if (account == null || account.mHostAuthRecv == null) { 359 LogUtils.e(Logging.LOG_TAG, 360 "null account or host auth. account null: %b host auth null: %b", 361 account == null, account == null || account.mHostAuthRecv == null); 362 return; 363 } 364 TextView lastView = mImapPathPrefixView; 365 mBaseScheme = account.mHostAuthRecv.mProtocol; 366 mServerLabelView.setText(R.string.account_setup_incoming_server_label); 367 mServerView.setContentDescription(getResources().getText( 368 R.string.account_setup_incoming_server_label)); 369 if (!mServiceInfo.offerPrefix) { 370 mImapPathPrefixSectionView.setVisibility(View.GONE); 371 lastView = mPortView; 372 } 373 if (!mServiceInfo.offerLocalDeletes) { 374 mDeletePolicyLabelView.setVisibility(View.GONE); 375 mDeletePolicyView.setVisibility(View.GONE); 376 mPortView.setImeOptions(EditorInfo.IME_ACTION_NEXT); 377 } 378 lastView.setOnEditorActionListener(mDismissImeOnDoneListener); 379 } 380 381 /** 382 * Load the current settings into the UI 383 */ 384 private void loadSettings() { 385 if (mLoaded) return; 386 387 final Account account = mSetupData.getAccount(); 388 final HostAuth recvAuth = account.getOrCreateHostAuthRecv(mContext); 389 390 final String username = recvAuth.mLogin; 391 if (username != null) { 392 //*** For eas? 393 // Add a backslash to the start of the username, but only if the username has no 394 // backslash in it. 395 //if (userName.indexOf('\\') < 0) { 396 // userName = "\\" + userName; 397 //} 398 mUsernameView.setText(username); 399 } 400 final String password = recvAuth.mPassword; 401 if (password != null) { 402 mPasswordView.setText(password); 403 // Since username is uneditable, focus on the next editable field 404 if (mSettingsMode) { 405 mPasswordView.requestFocus(); 406 } 407 } 408 409 if (mServiceInfo.offerPrefix) { 410 final String prefix = recvAuth.mDomain; 411 if (prefix != null && prefix.length() > 0) { 412 mImapPathPrefixView.setText(prefix.substring(1)); 413 } 414 } 415 416 // The delete policy is set for all legacy accounts. For POP3 accounts, the user sets 417 // the policy explicitly. For IMAP accounts, the policy is set when the Account object 418 // is created. @see AccountSetupBasics#populateSetupData 419 mLoadedDeletePolicy = account.getDeletePolicy(); 420 SpinnerOption.setSpinnerOptionValue(mDeletePolicyView, mLoadedDeletePolicy); 421 422 int flags = recvAuth.mFlags; 423 flags &= ~HostAuth.FLAG_AUTHENTICATE; 424 if (mServiceInfo.defaultSsl) { 425 flags |= HostAuth.FLAG_SSL; 426 } 427 SpinnerOption.setSpinnerOptionValue(mSecurityTypeView, flags); 428 429 final String hostname = recvAuth.mAddress; 430 if (hostname != null) { 431 mServerView.setText(hostname); 432 } 433 434 final int port = recvAuth.mPort; 435 if (port != HostAuth.PORT_UNKNOWN) { 436 mPortView.setText(Integer.toString(port)); 437 } else { 438 updatePortFromSecurityType(); 439 } 440 441 mLoadedRecvAuth = recvAuth; 442 mLoaded = true; 443 validateFields(); 444 } 445 446 /** 447 * Check the values in the fields and decide if it makes sense to enable the "next" button 448 */ 449 private void validateFields() { 450 if (!mLoaded) return; 451 enableNextButton(!TextUtils.isEmpty(mUsernameView.getText()) 452 && !TextUtils.isEmpty(mPasswordView.getText()) 453 && Utility.isServerNameValid(mServerView) 454 && Utility.isPortFieldValid(mPortView)); 455 456 mCacheLoginCredential = mUsernameView.getText().toString().trim(); 457 458 // Warn (but don't prevent) if password has leading/trailing spaces 459 AccountSettingsUtils.checkPasswordSpaces(mContext, mPasswordView); 460 } 461 462 private int getPortFromSecurityType(boolean useSsl) { 463 final EmailServiceInfo info = EmailServiceUtils.getServiceInfo(mContext, 464 mSetupData.getAccount().mHostAuthRecv.mProtocol); 465 return useSsl ? info.portSsl : info.port; 466 } 467 468 private boolean getSslSelected() { 469 final int securityType = 470 (Integer)((SpinnerOption)mSecurityTypeView.getSelectedItem()).value; 471 return ((securityType & HostAuth.FLAG_SSL) != 0); 472 } 473 474 public void onUseSslChanged(boolean useSsl) { 475 if (mServiceInfo.offerCerts) { 476 final int mode = useSsl ? View.VISIBLE : View.GONE; 477 mClientCertificateSelector.setVisibility(mode); 478 String deviceId = ""; 479 try { 480 deviceId = Device.getDeviceId(mContext); 481 } catch (IOException e) { 482 // Not required 483 } 484 ((TextView) UiUtilities.getView(getView(), R.id.device_id)).setText(deviceId); 485 486 mDeviceIdSectionView.setVisibility(mode); 487 //UiUtilities.setVisibilitySafe(getView(), R.id.client_certificate_divider, mode); 488 } 489 } 490 491 private void updatePortFromSecurityType() { 492 final boolean sslSelected = getSslSelected(); 493 final int port = getPortFromSecurityType(sslSelected); 494 mPortView.setText(Integer.toString(port)); 495 onUseSslChanged(sslSelected); 496 } 497 498 /** 499 * Entry point from Activity after editing settings and verifying them. Must be FLOW_MODE_EDIT. 500 * Note, we update account here (as well as the account.mHostAuthRecv) because we edit 501 * account's delete policy here. 502 * Blocking - do not call from UI Thread. 503 */ 504 @Override 505 public void saveSettingsAfterEdit() { 506 final Account account = mSetupData.getAccount(); 507 account.update(mContext, account.toContentValues()); 508 account.mHostAuthRecv.update(mContext, account.mHostAuthRecv.toContentValues()); 509 // Update the backup (side copy) of the accounts 510 AccountBackupRestore.backup(mContext); 511 } 512 513 /** 514 * Entry point from Activity after entering new settings and verifying them. For setup mode. 515 */ 516 @Override 517 public void saveSettingsAfterSetup() { 518 final Account account = mSetupData.getAccount(); 519 final HostAuth recvAuth = account.getOrCreateHostAuthRecv(mContext); 520 final HostAuth sendAuth = account.getOrCreateHostAuthSend(mContext); 521 522 // Set the username and password for the outgoing settings to the username and 523 // password the user just set for incoming. Use the verified host address to try and 524 // pick a smarter outgoing address. 525 final String hostName = 526 AccountSettingsUtils.inferServerName(mContext, recvAuth.mAddress, null, "smtp"); 527 sendAuth.setLogin(recvAuth.mLogin, recvAuth.mPassword); 528 sendAuth.setConnection(sendAuth.mProtocol, hostName, sendAuth.mPort, sendAuth.mFlags); 529 } 530 531 /** 532 * Entry point from Activity, when "next" button is clicked 533 */ 534 @Override 535 public void onNext() { 536 final Account account = mSetupData.getAccount(); 537 538 // Make sure delete policy is an valid option before using it; otherwise, the results are 539 // indeterminate, I suspect... 540 if (mDeletePolicyView.getVisibility() == View.VISIBLE) { 541 account.setDeletePolicy( 542 (Integer) ((SpinnerOption) mDeletePolicyView.getSelectedItem()).value); 543 } 544 545 final HostAuth recvAuth = account.getOrCreateHostAuthRecv(mContext); 546 final String userName = mUsernameView.getText().toString().trim(); 547 final String userPassword = mPasswordView.getText().toString(); 548 recvAuth.setLogin(userName, userPassword); 549 550 final String serverAddress = mServerView.getText().toString().trim(); 551 int serverPort; 552 try { 553 serverPort = Integer.parseInt(mPortView.getText().toString().trim()); 554 } catch (NumberFormatException e) { 555 serverPort = getPortFromSecurityType(getSslSelected()); 556 LogUtils.d(Logging.LOG_TAG, "Non-integer server port; using '" + serverPort + "'"); 557 } 558 final int securityType = 559 (Integer) ((SpinnerOption) mSecurityTypeView.getSelectedItem()).value; 560 recvAuth.setConnection(mBaseScheme, serverAddress, serverPort, securityType); 561 if (mServiceInfo.offerPrefix) { 562 final String prefix = mImapPathPrefixView.getText().toString().trim(); 563 recvAuth.mDomain = TextUtils.isEmpty(prefix) ? null : ("/" + prefix); 564 } else { 565 recvAuth.mDomain = null; 566 } 567 recvAuth.mClientCertAlias = mClientCertificateSelector.getCertificate(); 568 569 mCallback.onProceedNext(SetupData.CHECK_INCOMING, this); 570 clearButtonBounce(); 571 } 572 573 @Override 574 public boolean haveSettingsChanged() { 575 final boolean deletePolicyChanged; 576 577 // Only verify the delete policy if the control is visible (i.e. is a pop3 account) 578 if (mDeletePolicyView != null && mDeletePolicyView.getVisibility() == View.VISIBLE) { 579 int newDeletePolicy = 580 (Integer)((SpinnerOption)mDeletePolicyView.getSelectedItem()).value; 581 deletePolicyChanged = mLoadedDeletePolicy != newDeletePolicy; 582 } else { 583 deletePolicyChanged = false; 584 } 585 586 return deletePolicyChanged || super.haveSettingsChanged(); 587 } 588 589 /** 590 * Implements AccountCheckSettingsFragment.Callbacks 591 */ 592 @Override 593 public void onAutoDiscoverComplete(int result, SetupData setupData) { 594 mSetupData = setupData; 595 final AccountSetupIncoming activity = (AccountSetupIncoming) getActivity(); 596 activity.onAutoDiscoverComplete(result, setupData); 597 } 598 599 @Override 600 public void onCertificateRequested() { 601 final Intent intent = new Intent(CertificateRequestor.ACTION_REQUEST_CERT); 602 intent.setData(Uri.parse("eas://com.android.emailcommon/certrequest")); 603 startActivityForResult(intent, CERTIFICATE_REQUEST); 604 } 605 606 @Override 607 public void onActivityResult(int requestCode, int resultCode, Intent data) { 608 if (requestCode == CERTIFICATE_REQUEST && resultCode == Activity.RESULT_OK) { 609 final String certAlias = data.getStringExtra(CertificateRequestor.RESULT_ALIAS); 610 if (certAlias != null) { 611 mClientCertificateSelector.setCertificate(certAlias); 612 } 613 } 614 } 615 } 616