AccountSetupIncomingFragment.java revision 3432bdb757c4195fb4726c8b401f2261e78d75a7
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 TextView lastView = mImapPathPrefixView; 289 mBaseScheme = account.mHostAuthRecv.mProtocol; 290 if (HostAuth.SCHEME_POP3.equals(mBaseScheme)) { 291 mServerLabelView.setText(R.string.account_setup_incoming_pop_server_label); 292 mServerView.setContentDescription( 293 getResources().getString(R.string.account_setup_incoming_pop_server_label)); 294 mImapPathPrefixSectionView.setVisibility(View.GONE); 295 lastView = mPortView; 296 } else if (HostAuth.SCHEME_IMAP.equals(mBaseScheme)) { 297 mServerLabelView.setText(R.string.account_setup_incoming_imap_server_label); 298 mServerView.setContentDescription( 299 getResources().getString(R.string.account_setup_incoming_imap_server_label)); 300 mDeletePolicyLabelView.setVisibility(View.GONE); 301 mDeletePolicyView.setVisibility(View.GONE); 302 mPortView.setImeOptions(EditorInfo.IME_ACTION_NEXT); 303 } else { 304 throw new Error("Unknown account type: " + account); 305 } 306 lastView.setOnEditorActionListener(mDismissImeOnDoneListener); 307 mConfigured = true; 308 } 309 310 /** 311 * Load the current settings into the UI 312 */ 313 private void loadSettings() { 314 if (mLoaded) return; 315 316 Account account = SetupData.getAccount(); 317 HostAuth recvAuth = account.getOrCreateHostAuthRecv(mContext); 318 319 String username = recvAuth.mLogin; 320 if (username != null) { 321 mUsernameView.setText(username); 322 } 323 String password = recvAuth.mPassword; 324 if (password != null) { 325 mPasswordView.setText(password); 326 // Since username is uneditable, focus on the next editable field 327 if (mSettingsMode) { 328 mPasswordView.requestFocus(); 329 } 330 } 331 332 if (HostAuth.SCHEME_IMAP.equals(recvAuth.mProtocol)) { 333 String prefix = recvAuth.mDomain; 334 if (prefix != null && prefix.length() > 0) { 335 mImapPathPrefixView.setText(prefix.substring(1)); 336 } 337 } else if (!HostAuth.SCHEME_POP3.equals(recvAuth.mProtocol)) { 338 // Account must either be IMAP or POP3 339 throw new Error("Unknown account type: " + recvAuth.mProtocol); 340 } 341 342 // The delete policy is set for all legacy accounts. For POP3 accounts, the user sets 343 // the policy explicitly. For IMAP accounts, the policy is set when the Account object 344 // is created. @see AccountSetupBasics#populateSetupData 345 mLoadedDeletePolicy = account.getDeletePolicy(); 346 SpinnerOption.setSpinnerOptionValue(mDeletePolicyView, mLoadedDeletePolicy); 347 348 int flags = recvAuth.mFlags; 349 flags &= ~HostAuth.FLAG_AUTHENTICATE; 350 SpinnerOption.setSpinnerOptionValue(mSecurityTypeView, flags); 351 352 String hostname = recvAuth.mAddress; 353 if (hostname != null) { 354 mServerView.setText(hostname); 355 } 356 357 int port = recvAuth.mPort; 358 if (port != HostAuth.PORT_UNKNOWN) { 359 mPortView.setText(Integer.toString(port)); 360 } else { 361 updatePortFromSecurityType(); 362 } 363 364 mLoadedRecvAuth = recvAuth; 365 mLoaded = true; 366 validateFields(); 367 } 368 369 /** 370 * Check the values in the fields and decide if it makes sense to enable the "next" button 371 */ 372 private void validateFields() { 373 if (!mConfigured || !mLoaded) return; 374 boolean enabled = Utility.isTextViewNotEmpty(mUsernameView) 375 && Utility.isTextViewNotEmpty(mPasswordView) 376 && Utility.isServerNameValid(mServerView) 377 && Utility.isPortFieldValid(mPortView); 378 enableNextButton(enabled); 379 380 String userName = mUsernameView.getText().toString().trim(); 381 mCacheLoginCredential = userName; 382 383 // Warn (but don't prevent) if password has leading/trailing spaces 384 AccountSettingsUtils.checkPasswordSpaces(mContext, mPasswordView); 385 } 386 387 private int getPortFromSecurityType() { 388 int securityType = (Integer)((SpinnerOption)mSecurityTypeView.getSelectedItem()).value; 389 boolean useSsl = ((securityType & HostAuth.FLAG_SSL) != 0); 390 int port = useSsl ? IMAP_PORT_SSL : IMAP_PORT_NORMAL; // default to IMAP 391 if (HostAuth.SCHEME_POP3.equals(mBaseScheme)) { 392 port = useSsl ? POP3_PORT_SSL : POP3_PORT_NORMAL; 393 } 394 return port; 395 } 396 397 private void updatePortFromSecurityType() { 398 int port = getPortFromSecurityType(); 399 mPortView.setText(Integer.toString(port)); 400 } 401 402 /** 403 * Entry point from Activity after editing settings and verifying them. Must be FLOW_MODE_EDIT. 404 * Note, we update account here (as well as the account.mHostAuthRecv) because we edit 405 * account's delete policy here. 406 * Blocking - do not call from UI Thread. 407 */ 408 @Override 409 public void saveSettingsAfterEdit() { 410 Account account = SetupData.getAccount(); 411 account.update(mContext, account.toContentValues()); 412 account.mHostAuthRecv.update(mContext, account.mHostAuthRecv.toContentValues()); 413 // Update the backup (side copy) of the accounts 414 AccountBackupRestore.backup(mContext); 415 } 416 417 /** 418 * Entry point from Activity after entering new settings and verifying them. For setup mode. 419 */ 420 @Override 421 public void saveSettingsAfterSetup() { 422 Account account = SetupData.getAccount(); 423 HostAuth recvAuth = account.getOrCreateHostAuthRecv(mContext); 424 HostAuth sendAuth = account.getOrCreateHostAuthSend(mContext); 425 426 // Set the username and password for the outgoing settings to the username and 427 // password the user just set for incoming. Use the verified host address to try and 428 // pick a smarter outgoing address. 429 String hostName = AccountSettingsUtils.inferServerName(recvAuth.mAddress, null, "smtp"); 430 sendAuth.setLogin(recvAuth.mLogin, recvAuth.mPassword); 431 sendAuth.setConnection(sendAuth.mProtocol, hostName, sendAuth.mPort, sendAuth.mFlags); 432 } 433 434 /** 435 * Entry point from Activity, when "next" button is clicked 436 */ 437 @Override 438 public void onNext() { 439 Account account = SetupData.getAccount(); 440 441 // Make sure delete policy is an valid option before using it; otherwise, the results are 442 // indeterminate, I suspect... 443 if (mDeletePolicyView.getVisibility() == View.VISIBLE) { 444 account.setDeletePolicy( 445 (Integer) ((SpinnerOption) mDeletePolicyView.getSelectedItem()).value); 446 } 447 448 HostAuth recvAuth = account.getOrCreateHostAuthRecv(mContext); 449 String userName = mUsernameView.getText().toString().trim(); 450 String userPassword = mPasswordView.getText().toString(); 451 recvAuth.setLogin(userName, userPassword); 452 453 String serverAddress = mServerView.getText().toString().trim(); 454 int serverPort; 455 try { 456 serverPort = Integer.parseInt(mPortView.getText().toString().trim()); 457 } catch (NumberFormatException e) { 458 serverPort = getPortFromSecurityType(); 459 Log.d(Logging.LOG_TAG, "Non-integer server port; using '" + serverPort + "'"); 460 } 461 int securityType = (Integer) ((SpinnerOption) mSecurityTypeView.getSelectedItem()).value; 462 recvAuth.setConnection(mBaseScheme, serverAddress, serverPort, securityType); 463 if (HostAuth.SCHEME_IMAP.equals(recvAuth.mProtocol)) { 464 String prefix = mImapPathPrefixView.getText().toString().trim(); 465 recvAuth.mDomain = TextUtils.isEmpty(prefix) ? null : ("/" + prefix); 466 } else { 467 recvAuth.mDomain = null; 468 } 469 470 // Check for a duplicate account (requires async DB work) and if OK, 471 // proceed with check 472 startDuplicateTaskCheck( 473 account.mId, serverAddress, mCacheLoginCredential, SetupData.CHECK_INCOMING); 474 } 475 476 @Override 477 public boolean haveSettingsChanged() { 478 boolean deletePolicyChanged = false; 479 480 // Only verify the delete policy if the control is visible (i.e. is a pop3 account) 481 if (mDeletePolicyView.getVisibility() == View.VISIBLE) { 482 int newDeletePolicy = 483 (Integer)((SpinnerOption)mDeletePolicyView.getSelectedItem()).value; 484 deletePolicyChanged = mLoadedDeletePolicy != newDeletePolicy; 485 } 486 487 return deletePolicyChanged || super.haveSettingsChanged(); 488 } 489} 490