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