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