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