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