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