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