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