AccountSetupOutgoingFragment.java revision 112ed496f817ebeab6b1ee1d5117259ef80342b2
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.email.provider.EmailContent; 24 25import android.app.Activity; 26import android.content.Context; 27import android.os.Bundle; 28import android.preference.PreferenceActivity; 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 * Create the fragment with parameters - used mainly to force into settings mode (with buttons) 81 * @param settingsMode if true, alters appearance for use in settings (default is "setup") 82 */ 83 public static AccountSetupOutgoingFragment newInstance(boolean settingsMode) { 84 AccountSetupOutgoingFragment f = new AccountSetupOutgoingFragment(); 85 f.setSetupArguments(settingsMode); 86 return f; 87 } 88 89 /** 90 * Called to do initial creation of a fragment. This is called after 91 * {@link #onAttach(Activity)} and before {@link #onActivityCreated(Bundle)}. 92 */ 93 @Override 94 public void onCreate(Bundle savedInstanceState) { 95 if (Email.DEBUG_LIFECYCLE && Email.DEBUG) { 96 Log.d(Email.LOG_TAG, "AccountSetupOutgoingFragment onCreate"); 97 } 98 super.onCreate(savedInstanceState); 99 100 if (savedInstanceState != null) { 101 mLoaded = savedInstanceState.getBoolean(STATE_KEY_LOADED, false); 102 } 103 } 104 105 @Override 106 public View onCreateView(LayoutInflater inflater, ViewGroup container, 107 Bundle savedInstanceState) { 108 if (Email.DEBUG_LIFECYCLE && Email.DEBUG) { 109 Log.d(Email.LOG_TAG, "AccountSetupOutgoingFragment onCreateView"); 110 } 111 int layoutId = mSettingsMode 112 ? R.layout.account_settings_outgoing_fragment 113 : R.layout.account_setup_outgoing_fragment; 114 115 View view = inflater.inflate(layoutId, container, false); 116 Context context = getActivity(); 117 118 mUsernameView = (EditText) view.findViewById(R.id.account_username); 119 mPasswordView = (EditText) view.findViewById(R.id.account_password); 120 mServerView = (EditText) view.findViewById(R.id.account_server); 121 mPortView = (EditText) view.findViewById(R.id.account_port); 122 mRequireLoginView = (CheckBox) view.findViewById(R.id.account_require_login); 123 mRequireLoginSettingsView = view.findViewById(R.id.account_require_login_settings); 124 mRequireLoginSettingsView2 = view.findViewById(R.id.account_require_login_settings_2); 125 mSecurityTypeView = (Spinner) view.findViewById(R.id.account_security_type); 126 mRequireLoginView.setOnCheckedChangeListener(this); 127 128 // Note: Strings are shared with AccountSetupIncomingFragment 129 SpinnerOption securityTypes[] = { 130 new SpinnerOption(0, context.getString( 131 R.string.account_setup_incoming_security_none_label)), 132 new SpinnerOption(1, context.getString( 133 R.string.account_setup_incoming_security_ssl_label)), 134 new SpinnerOption(2, context.getString( 135 R.string.account_setup_incoming_security_ssl_trust_certificates_label)), 136 new SpinnerOption(3, context.getString( 137 R.string.account_setup_incoming_security_tls_label)), 138 new SpinnerOption(4, context.getString( 139 R.string.account_setup_incoming_security_tls_trust_certificates_label)), 140 }; 141 142 ArrayAdapter<SpinnerOption> securityTypesAdapter = new ArrayAdapter<SpinnerOption>(context, 143 android.R.layout.simple_spinner_item, securityTypes); 144 securityTypesAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); 145 mSecurityTypeView.setAdapter(securityTypesAdapter); 146 147 // Updates the port when the user changes the security type. This allows 148 // us to show a reasonable default which the user can change. 149 mSecurityTypeView.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { 150 public void onItemSelected(AdapterView<?> arg0, View arg1, int arg2, long arg3) { 151 updatePortFromSecurityType(); 152 } 153 154 public void onNothingSelected(AdapterView<?> arg0) { } 155 }); 156 157 // Calls validateFields() which enables or disables the Next button 158 TextWatcher validationTextWatcher = new TextWatcher() { 159 public void afterTextChanged(Editable s) { 160 validateFields(); 161 } 162 163 public void beforeTextChanged(CharSequence s, int start, int count, int after) { } 164 public void onTextChanged(CharSequence s, int start, int before, int count) { } 165 }; 166 mUsernameView.addTextChangedListener(validationTextWatcher); 167 mPasswordView.addTextChangedListener(validationTextWatcher); 168 mServerView.addTextChangedListener(validationTextWatcher); 169 mPortView.addTextChangedListener(validationTextWatcher); 170 171 // Only allow digits in the port field. 172 mPortView.setKeyListener(DigitsKeyListener.getInstance("0123456789")); 173 174 // Additional setup only used while in "settings" mode 175 onCreateViewSettingsMode(view); 176 177 return view; 178 } 179 180 @Override 181 public void onActivityCreated(Bundle savedInstanceState) { 182 if (Email.DEBUG_LIFECYCLE && Email.DEBUG) { 183 Log.d(Email.LOG_TAG, "AccountSetupOutgoingFragment onActivityCreated"); 184 } 185 super.onActivityCreated(savedInstanceState); 186 } 187 188 /** 189 * Called when the Fragment is visible to the user. 190 */ 191 @Override 192 public void onStart() { 193 if (Email.DEBUG_LIFECYCLE && Email.DEBUG) { 194 Log.d(Email.LOG_TAG, "AccountSetupOutgoingFragment onStart"); 195 } 196 super.onStart(); 197 mStarted = true; 198 loadSettings(); 199 } 200 201 /** 202 * Called when the fragment is visible to the user and actively running. 203 */ 204 @Override 205 public void onResume() { 206 if (Email.DEBUG_LIFECYCLE && Email.DEBUG) { 207 Log.d(Email.LOG_TAG, "AccountSetupOutgoingFragment onResume"); 208 } 209 super.onResume(); 210 validateFields(); 211 } 212 213 @Override 214 public void onPause() { 215 if (Email.DEBUG_LIFECYCLE && Email.DEBUG) { 216 Log.d(Email.LOG_TAG, "AccountSetupOutgoingFragment onPause"); 217 } 218 super.onPause(); 219 } 220 221 /** 222 * Called when the Fragment is no longer started. 223 */ 224 @Override 225 public void onStop() { 226 if (Email.DEBUG_LIFECYCLE && Email.DEBUG) { 227 Log.d(Email.LOG_TAG, "AccountSetupOutgoingFragment onStop"); 228 } 229 super.onStop(); 230 mStarted = false; 231 } 232 233 /** 234 * Called when the fragment is no longer in use. 235 */ 236 @Override 237 public void onDestroy() { 238 if (Email.DEBUG_LIFECYCLE && Email.DEBUG) { 239 Log.d(Email.LOG_TAG, "AccountSetupOutgoingFragment onDestroy"); 240 } 241 super.onDestroy(); 242 } 243 244 @Override 245 public void onSaveInstanceState(Bundle outState) { 246 if (Email.DEBUG_LIFECYCLE && Email.DEBUG) { 247 Log.d(Email.LOG_TAG, "AccountSetupOutgoingFragment onSaveInstanceState"); 248 } 249 super.onSaveInstanceState(outState); 250 251 outState.putBoolean(STATE_KEY_LOADED, mLoaded); 252 } 253 254 /** 255 * Activity provides callbacks here. This also triggers loading and setting up the UX 256 */ 257 @Override 258 public void setCallback(Callback callback) { 259 super.setCallback(callback); 260 if (mStarted) { 261 loadSettings(); 262 } 263 } 264 265 /** 266 * Load the current settings into the UI 267 */ 268 private void loadSettings() { 269 if (mLoaded) return; 270 try { 271 // TODO this should be accessed directly via the HostAuth structure 272 URI uri = new URI(SetupData.getAccount().getSenderUri(mContext)); 273 String username = null; 274 String password = null; 275 if (uri.getUserInfo() != null) { 276 String[] userInfoParts = uri.getUserInfo().split(":", 2); 277 username = userInfoParts[0]; 278 if (userInfoParts.length > 1) { 279 password = userInfoParts[1]; 280 } 281 } 282 283 if (username != null) { 284 mUsernameView.setText(username); 285 mRequireLoginView.setChecked(true); 286 } 287 288 if (password != null) { 289 mPasswordView.setText(password); 290 } 291 292 for (int i = 0; i < SMTP_SCHEMES.length; i++) { 293 if (SMTP_SCHEMES[i].equals(uri.getScheme())) { 294 SpinnerOption.setSpinnerOptionValue(mSecurityTypeView, i); 295 } 296 } 297 298 if (uri.getHost() != null) { 299 mServerView.setText(uri.getHost()); 300 } 301 302 if (uri.getPort() != -1) { 303 mPortView.setText(Integer.toString(uri.getPort())); 304 } else { 305 updatePortFromSecurityType(); 306 } 307 } catch (URISyntaxException use) { 308 /* 309 * We should always be able to parse our own settings. 310 */ 311 throw new Error(use); 312 } 313 mLoaded = true; 314 validateFields(); 315 } 316 317 /** 318 * Preflight the values in the fields and decide if it makes sense to enable the "next" button 319 */ 320 private void validateFields() { 321 if (!mLoaded) return; 322 boolean enabled = 323 Utility.isTextViewNotEmpty(mServerView) && Utility.isPortFieldValid(mPortView); 324 325 if (enabled && mRequireLoginView.isChecked()) { 326 enabled = (Utility.isTextViewNotEmpty(mUsernameView) 327 && Utility.isTextViewNotEmpty(mPasswordView)); 328 } 329 330 if (enabled) { 331 try { 332 URI uri = getUri(); 333 } catch (URISyntaxException use) { 334 enabled = false; 335 } 336 } 337 enableNextButton(enabled); 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 */ 360 @Override 361 public void saveSettingsAfterEdit() { 362 EmailContent.Account account = SetupData.getAccount(); 363 if (account.isSaved()) { 364 account.update(mContext, account.toContentValues()); 365 account.mHostAuthSend.update(mContext, account.mHostAuthSend.toContentValues()); 366 } else { 367 account.save(mContext); 368 } 369 // Update the backup (side copy) of the accounts 370 AccountBackupRestore.backupAccounts(mContext); 371 } 372 373 /** 374 * Entry point from Activity after entering new settings and verifying them. For setup mode. 375 */ 376 @Override 377 public void saveSettingsAfterSetup() { 378 } 379 380 /** 381 * Attempt to create a URI from the fields provided. Throws URISyntaxException if there's 382 * a problem with the user input. 383 * @return a URI built from the account setup fields 384 */ 385 /* package */ URI getUri() throws URISyntaxException { 386 int securityType = (Integer)((SpinnerOption)mSecurityTypeView.getSelectedItem()).value; 387 String userInfo = null; 388 if (mRequireLoginView.isChecked()) { 389 userInfo = mUsernameView.getText().toString().trim() + ":" + mPasswordView.getText(); 390 } 391 URI uri = new URI( 392 SMTP_SCHEMES[securityType], 393 userInfo, 394 mServerView.getText().toString().trim(), 395 Integer.parseInt(mPortView.getText().toString().trim()), 396 null, null, null); 397 return uri; 398 } 399 400 /** 401 * Entry point from Activity, when "next" button is clicked 402 */ 403 @Override 404 public void onNext() { 405 EmailContent.Account account = SetupData.getAccount(); 406 try { 407 // TODO this should be accessed directly via the HostAuth structure 408 URI uri = getUri(); 409 account.setSenderUri(mContext, uri.toString()); 410 } catch (URISyntaxException use) { 411 /* 412 * It's unrecoverable if we cannot create a URI from components that 413 * we validated to be safe. 414 */ 415 throw new Error(use); 416 } 417 418 // STOPSHIP - use new checker fragment only during account settings (TODO: account setup) 419 Activity activity = getActivity(); 420 if (activity instanceof PreferenceActivity) { 421 AccountCheckSettingsFragment checkerFragment = 422 AccountCheckSettingsFragment.newInstance(SetupData.CHECK_OUTGOING, this); 423 ((PreferenceActivity)activity).startPreferenceFragment(checkerFragment, true); 424 } else { 425 // STOPSHIP remove this old code 426 mCallback.onProceedNext(SetupData.CHECK_OUTGOING, this); 427 } 428 } 429} 430