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