AccountSetupOutgoingFragment.java revision 12b82d9374947c9268217f45befe8a74bd9b60d7
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 com.android.email.Email; 20import com.android.email.R; 21import com.android.email.activity.UiUtilities; 22import com.android.email.provider.AccountBackupRestore; 23import com.android.emailcommon.Logging; 24import com.android.emailcommon.provider.EmailContent; 25import com.android.emailcommon.provider.EmailContent.Account; 26import com.android.emailcommon.provider.HostAuth; 27import com.android.emailcommon.utility.Utility; 28 29import android.app.Activity; 30import android.content.Context; 31import android.os.Bundle; 32import android.text.Editable; 33import android.text.TextWatcher; 34import android.text.method.DigitsKeyListener; 35import android.util.Log; 36import android.view.LayoutInflater; 37import android.view.View; 38import android.view.ViewGroup; 39import android.widget.AdapterView; 40import android.widget.ArrayAdapter; 41import android.widget.CheckBox; 42import android.widget.CompoundButton; 43import android.widget.CompoundButton.OnCheckedChangeListener; 44import android.widget.EditText; 45import android.widget.Spinner; 46 47import java.net.URI; 48import java.net.URISyntaxException; 49 50/** 51 * Provides UI for SMTP account settings (for IMAP/POP accounts). 52 * 53 * This fragment is used by AccountSetupOutgoing (for creating accounts) and by AccountSettingsXL 54 * (for editing existing accounts). 55 */ 56public class AccountSetupOutgoingFragment extends AccountServerBaseFragment 57 implements OnCheckedChangeListener { 58 59 private final static String STATE_KEY_LOADED = "AccountSetupOutgoingFragment.loaded"; 60 61 private static final int SMTP_PORT_NORMAL = 587; 62 private static final int SMTP_PORT_SSL = 465; 63 64 private EditText mUsernameView; 65 private EditText mPasswordView; 66 private EditText mServerView; 67 private EditText mPortView; 68 private CheckBox mRequireLoginView; 69 private Spinner mSecurityTypeView; 70 71 // Support for lifecycle 72 private boolean mStarted; 73 private boolean mLoaded; 74 75 /** 76 * Called to do initial creation of a fragment. This is called after 77 * {@link #onAttach(Activity)} and before {@link #onActivityCreated(Bundle)}. 78 */ 79 @Override 80 public void onCreate(Bundle savedInstanceState) { 81 if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) { 82 Log.d(Logging.LOG_TAG, "AccountSetupOutgoingFragment onCreate"); 83 } 84 super.onCreate(savedInstanceState); 85 86 if (savedInstanceState != null) { 87 mLoaded = savedInstanceState.getBoolean(STATE_KEY_LOADED, false); 88 } 89 mBaseScheme = "smtp"; 90 } 91 92 @Override 93 public View onCreateView(LayoutInflater inflater, ViewGroup container, 94 Bundle savedInstanceState) { 95 if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) { 96 Log.d(Logging.LOG_TAG, "AccountSetupOutgoingFragment onCreateView"); 97 } 98 int layoutId = mSettingsMode 99 ? R.layout.account_settings_outgoing_fragment 100 : R.layout.account_setup_outgoing_fragment; 101 102 View view = inflater.inflate(layoutId, container, false); 103 Context context = getActivity(); 104 105 mUsernameView = (EditText) UiUtilities.getView(view, R.id.account_username); 106 mPasswordView = (EditText) UiUtilities.getView(view, R.id.account_password); 107 mServerView = (EditText) UiUtilities.getView(view, R.id.account_server); 108 mPortView = (EditText) UiUtilities.getView(view, R.id.account_port); 109 mRequireLoginView = (CheckBox) UiUtilities.getView(view, R.id.account_require_login); 110 mSecurityTypeView = (Spinner) UiUtilities.getView(view, R.id.account_security_type); 111 mRequireLoginView.setOnCheckedChangeListener(this); 112 113 // Note: Strings are shared with AccountSetupIncomingFragment 114 SpinnerOption securityTypes[] = { 115 new SpinnerOption(HostAuth.FLAG_NONE, context.getString( 116 R.string.account_setup_incoming_security_none_label)), 117 new SpinnerOption(HostAuth.FLAG_SSL, context.getString( 118 R.string.account_setup_incoming_security_ssl_label)), 119 new SpinnerOption(HostAuth.FLAG_SSL | HostAuth.FLAG_TRUST_ALL, context.getString( 120 R.string.account_setup_incoming_security_ssl_trust_certificates_label)), 121 new SpinnerOption(HostAuth.FLAG_TLS, context.getString( 122 R.string.account_setup_incoming_security_tls_label)), 123 new SpinnerOption(HostAuth.FLAG_SSL | HostAuth.FLAG_TRUST_ALL, context.getString( 124 R.string.account_setup_incoming_security_tls_trust_certificates_label)), 125 }; 126 127 ArrayAdapter<SpinnerOption> securityTypesAdapter = new ArrayAdapter<SpinnerOption>(context, 128 android.R.layout.simple_spinner_item, securityTypes); 129 securityTypesAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); 130 mSecurityTypeView.setAdapter(securityTypesAdapter); 131 132 // Updates the port when the user changes the security type. This allows 133 // us to show a reasonable default which the user can change. 134 mSecurityTypeView.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { 135 public void onItemSelected(AdapterView<?> arg0, View arg1, int arg2, long arg3) { 136 updatePortFromSecurityType(); 137 } 138 139 public void onNothingSelected(AdapterView<?> arg0) { } 140 }); 141 142 // Calls validateFields() which enables or disables the Next button 143 TextWatcher validationTextWatcher = new TextWatcher() { 144 public void afterTextChanged(Editable s) { 145 validateFields(); 146 } 147 148 public void beforeTextChanged(CharSequence s, int start, int count, int after) { } 149 public void onTextChanged(CharSequence s, int start, int before, int count) { } 150 }; 151 mUsernameView.addTextChangedListener(validationTextWatcher); 152 mPasswordView.addTextChangedListener(validationTextWatcher); 153 mServerView.addTextChangedListener(validationTextWatcher); 154 mPortView.addTextChangedListener(validationTextWatcher); 155 156 // Only allow digits in the port field. 157 mPortView.setKeyListener(DigitsKeyListener.getInstance("0123456789")); 158 159 // Additional setup only used while in "settings" mode 160 onCreateViewSettingsMode(view); 161 162 return view; 163 } 164 165 @Override 166 public void onActivityCreated(Bundle savedInstanceState) { 167 if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) { 168 Log.d(Logging.LOG_TAG, "AccountSetupOutgoingFragment onActivityCreated"); 169 } 170 super.onActivityCreated(savedInstanceState); 171 } 172 173 /** 174 * Called when the Fragment is visible to the user. 175 */ 176 @Override 177 public void onStart() { 178 if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) { 179 Log.d(Logging.LOG_TAG, "AccountSetupOutgoingFragment onStart"); 180 } 181 super.onStart(); 182 mStarted = true; 183 loadSettings(); 184 } 185 186 /** 187 * Called when the fragment is visible to the user and actively running. 188 */ 189 @Override 190 public void onResume() { 191 if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) { 192 Log.d(Logging.LOG_TAG, "AccountSetupOutgoingFragment onResume"); 193 } 194 super.onResume(); 195 validateFields(); 196 } 197 198 @Override 199 public void onPause() { 200 if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) { 201 Log.d(Logging.LOG_TAG, "AccountSetupOutgoingFragment onPause"); 202 } 203 super.onPause(); 204 } 205 206 /** 207 * Called when the Fragment is no longer started. 208 */ 209 @Override 210 public void onStop() { 211 if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) { 212 Log.d(Logging.LOG_TAG, "AccountSetupOutgoingFragment onStop"); 213 } 214 super.onStop(); 215 mStarted = false; 216 } 217 218 /** 219 * Called when the fragment is no longer in use. 220 */ 221 @Override 222 public void onDestroy() { 223 if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) { 224 Log.d(Logging.LOG_TAG, "AccountSetupOutgoingFragment onDestroy"); 225 } 226 super.onDestroy(); 227 } 228 229 @Override 230 public void onSaveInstanceState(Bundle outState) { 231 if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) { 232 Log.d(Logging.LOG_TAG, "AccountSetupOutgoingFragment onSaveInstanceState"); 233 } 234 super.onSaveInstanceState(outState); 235 236 outState.putBoolean(STATE_KEY_LOADED, mLoaded); 237 } 238 239 /** 240 * Activity provides callbacks here. This also triggers loading and setting up the UX 241 */ 242 @Override 243 public void setCallback(Callback callback) { 244 super.setCallback(callback); 245 if (mStarted) { 246 loadSettings(); 247 } 248 } 249 250 /** 251 * Load the current settings into the UI 252 */ 253 private void loadSettings() { 254 if (mLoaded) return; 255 256 HostAuth sendAuth = SetupData.getAccount().getOrCreateHostAuthSend(mContext); 257 if ((sendAuth.mFlags & HostAuth.FLAG_AUTHENTICATE) != 0) { 258 String username = sendAuth.mLogin; 259 if (username != null) { 260 mUsernameView.setText(username); 261 mRequireLoginView.setChecked(true); 262 } 263 264 String password = sendAuth.mPassword; 265 if (password != null) { 266 mPasswordView.setText(password); 267 } 268 } 269 270 int flags = sendAuth.mFlags & ~HostAuth.FLAG_AUTHENTICATE; 271 SpinnerOption.setSpinnerOptionValue(mSecurityTypeView, flags); 272 273 String hostname = sendAuth.mAddress; 274 if (hostname != null) { 275 mServerView.setText(hostname); 276 } 277 278 int port = sendAuth.mPort; 279 if (port != -1) { 280 mPortView.setText(Integer.toString(port)); 281 } else { 282 updatePortFromSecurityType(); 283 } 284 285 mLoadedSendAuth = sendAuth; 286 mLoaded = true; 287 validateFields(); 288 } 289 290 /** 291 * Preflight the values in the fields and decide if it makes sense to enable the "next" button 292 */ 293 private void validateFields() { 294 if (!mLoaded) return; 295 boolean enabled = 296 Utility.isTextViewNotEmpty(mServerView) && Utility.isPortFieldValid(mPortView); 297 298 if (enabled && mRequireLoginView.isChecked()) { 299 enabled = (Utility.isTextViewNotEmpty(mUsernameView) 300 && Utility.isTextViewNotEmpty(mPasswordView)); 301 } 302 303 if (enabled) { 304 try { 305 URI uri = getUri(); 306 } catch (URISyntaxException use) { 307 enabled = false; 308 } 309 } 310 enableNextButton(enabled); 311 312 // Warn (but don't prevent) if password has leading/trailing spaces 313 AccountSettingsUtils.checkPasswordSpaces(mContext, mPasswordView); 314 } 315 316 /** 317 * implements OnCheckedChangeListener 318 */ 319 @Override 320 public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { 321 final int visibility = isChecked ? View.VISIBLE : View.GONE; 322 UiUtilities.setVisibilitySafe(getView(), R.id.account_require_login_settings, visibility); 323 UiUtilities.setVisibilitySafe(getView(), R.id.account_require_login_settings_2, visibility); 324 validateFields(); 325 } 326 327 private int getPortFromSecurityType() { 328 int securityType = (Integer)((SpinnerOption)mSecurityTypeView.getSelectedItem()).value; 329 int port = (securityType & HostAuth.FLAG_SSL) != 0 ? SMTP_PORT_SSL : SMTP_PORT_NORMAL; 330 return port; 331 } 332 333 private void updatePortFromSecurityType() { 334 int port = getPortFromSecurityType(); 335 mPortView.setText(Integer.toString(port)); 336 } 337 338 /** 339 * Entry point from Activity after editing settings and verifying them. Must be FLOW_MODE_EDIT. 340 * Blocking - do not call from UI Thread. 341 */ 342 @Override 343 public void saveSettingsAfterEdit() { 344 Account account = SetupData.getAccount(); 345 account.mHostAuthSend.update(mContext, account.mHostAuthSend.toContentValues()); 346 // Update the backup (side copy) of the accounts 347 AccountBackupRestore.backup(mContext); 348 } 349 350 /** 351 * Entry point from Activity after entering new settings and verifying them. For setup mode. 352 */ 353 @Override 354 public void saveSettingsAfterSetup() { 355 } 356 357 /** 358 * Attempt to create a URI from the fields provided. Throws URISyntaxException if there's 359 * a problem with the user input. 360 * @return a URI built from the account setup fields 361 */ 362 @Override 363 protected URI getUri() throws URISyntaxException { 364 int securityType = (Integer)((SpinnerOption)mSecurityTypeView.getSelectedItem()).value; 365 String userInfo = null; 366 if (mRequireLoginView.isChecked()) { 367 userInfo = mUsernameView.getText().toString().trim() + ":" + mPasswordView.getText(); 368 } 369 String host = mServerView.getText().toString().trim(); 370 String path = null; 371 int port = Integer.parseInt(mPortView.getText().toString().trim()); 372 373 URI uri = new URI( 374 HostAuth.getSchemeString(mBaseScheme, securityType), 375 userInfo, 376 host, 377 port, 378 path, 379 null, 380 null); 381 return uri; 382 } 383 384 /** 385 * Entry point from Activity, when "next" button is clicked 386 */ 387 @Override 388 public void onNext() { 389 EmailContent.Account account = SetupData.getAccount(); 390 HostAuth sendAuth = account.getOrCreateHostAuthSend(mContext); 391 392 String userName = mUsernameView.getText().toString().trim(); 393 String userPassword = mPasswordView.getText().toString(); 394 sendAuth.setLogin(userName, userPassword); 395 396 String serverAddress = mServerView.getText().toString().trim(); 397 int serverPort; 398 try { 399 serverPort = Integer.parseInt(mPortView.getText().toString().trim()); 400 } catch (NumberFormatException e) { 401 serverPort = getPortFromSecurityType(); 402 Log.d(Logging.LOG_TAG, "Non-integer server port; using '" + serverPort + "'"); 403 } 404 int securityType = (Integer)((SpinnerOption)mSecurityTypeView.getSelectedItem()).value; 405 sendAuth.setConnection(mBaseScheme, serverAddress, serverPort, securityType); 406 sendAuth.mDomain = null; 407 408 mCallback.onProceedNext(SetupData.CHECK_OUTGOING, this); 409 } 410} 411