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