AccountSetupIncomingFragment.java revision fd14496c494a0d38c35c3788c9cc55f1984592e4
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; 24import com.android.email.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.EditText; 39import android.widget.Spinner; 40import android.widget.TextView; 41 42import java.net.URI; 43import java.net.URISyntaxException; 44 45/** 46 * Provides UI for IMAP/POP account settings. 47 * 48 * This fragment is used by AccountSetupIncoming (for creating accounts) and by AccountSettingsXL 49 * (for editing existing accounts). 50 */ 51public class AccountSetupIncomingFragment extends AccountServerBaseFragment { 52 53 private final static String STATE_KEY_CREDENTIAL = 54 "AccountSetupIncomingFragment.loginCredential"; 55 56 private static final int POP_PORTS[] = { 57 110, 995, 995, 110, 110 58 }; 59 private static final String POP_SCHEMES[] = { 60 "pop3", "pop3+ssl+", "pop3+ssl+trustallcerts", "pop3+tls+", "pop3+tls+trustallcerts" 61 }; 62 private static final int IMAP_PORTS[] = { 63 143, 993, 993, 143, 143 64 }; 65 private static final String IMAP_SCHEMES[] = { 66 "imap", "imap+ssl+", "imap+ssl+trustallcerts", "imap+tls+", "imap+tls+trustallcerts" 67 }; 68 69 private int mAccountPorts[]; 70 private String mAccountSchemes[]; 71 private EditText mUsernameView; 72 private EditText mPasswordView; 73 private TextView mServerLabelView; 74 private EditText mServerView; 75 private EditText mPortView; 76 private Spinner mSecurityTypeView; 77 private TextView mDeletePolicyLabelView; 78 private Spinner mDeletePolicyView; 79 private View mImapPathPrefixSectionView; 80 private EditText mImapPathPrefixView; 81 82 // Support for lifecycle 83 private boolean mStarted; 84 private boolean mLoaded; 85 private String mCacheLoginCredential; 86 87 /** 88 * Called to do initial creation of a fragment. This is called after 89 * {@link #onAttach(Activity)} and before {@link #onActivityCreated(Bundle)}. 90 */ 91 @Override 92 public void onCreate(Bundle savedInstanceState) { 93 if (Email.DEBUG_LIFECYCLE && Email.DEBUG) { 94 Log.d(Email.LOG_TAG, "AccountSetupIncomingFragment onCreate"); 95 } 96 super.onCreate(savedInstanceState); 97 98 if (savedInstanceState != null) { 99 mCacheLoginCredential = savedInstanceState.getString(STATE_KEY_CREDENTIAL); 100 } 101 } 102 103 @Override 104 public View onCreateView(LayoutInflater inflater, ViewGroup container, 105 Bundle savedInstanceState) { 106 if (Email.DEBUG_LIFECYCLE && Email.DEBUG) { 107 Log.d(Email.LOG_TAG, "AccountSetupIncomingFragment onCreateView"); 108 } 109 View view = inflater.inflate(R.layout.account_setup_incoming_fragment, container, false); 110 Context context = getActivity(); 111 112 mUsernameView = (EditText) view.findViewById(R.id.account_username); 113 mPasswordView = (EditText) view.findViewById(R.id.account_password); 114 mServerLabelView = (TextView) view.findViewById(R.id.account_server_label); 115 mServerView = (EditText) view.findViewById(R.id.account_server); 116 mPortView = (EditText) view.findViewById(R.id.account_port); 117 mSecurityTypeView = (Spinner) view.findViewById(R.id.account_security_type); 118 mDeletePolicyLabelView = (TextView) view.findViewById(R.id.account_delete_policy_label); 119 mDeletePolicyView = (Spinner) view.findViewById(R.id.account_delete_policy); 120 mImapPathPrefixSectionView = view.findViewById(R.id.imap_path_prefix_section); 121 mImapPathPrefixView = (EditText) view.findViewById(R.id.imap_path_prefix); 122 123 // Set up spinners 124 SpinnerOption securityTypes[] = { 125 new SpinnerOption(0, 126 context.getString(R.string.account_setup_incoming_security_none_label)), 127 new SpinnerOption(1, 128 context.getString(R.string.account_setup_incoming_security_ssl_label)), 129 new SpinnerOption(2, 130 context.getString( 131 R.string.account_setup_incoming_security_ssl_trust_certificates_label)), 132 new SpinnerOption(3, 133 context.getString(R.string.account_setup_incoming_security_tls_label)), 134 new SpinnerOption(4, 135 context.getString( 136 R.string.account_setup_incoming_security_tls_trust_certificates_label)), 137 }; 138 139 SpinnerOption deletePolicies[] = { 140 new SpinnerOption(Account.DELETE_POLICY_NEVER, 141 context.getString(R.string.account_setup_incoming_delete_policy_never_label)), 142 new SpinnerOption(Account.DELETE_POLICY_ON_DELETE, 143 context.getString(R.string.account_setup_incoming_delete_policy_delete_label)), 144 }; 145 146 ArrayAdapter<SpinnerOption> securityTypesAdapter = new ArrayAdapter<SpinnerOption>(context, 147 android.R.layout.simple_spinner_item, securityTypes); 148 securityTypesAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); 149 mSecurityTypeView.setAdapter(securityTypesAdapter); 150 151 ArrayAdapter<SpinnerOption> deletePoliciesAdapter = new ArrayAdapter<SpinnerOption>(context, 152 android.R.layout.simple_spinner_item, deletePolicies); 153 deletePoliciesAdapter.setDropDownViewResource( 154 android.R.layout.simple_spinner_dropdown_item); 155 mDeletePolicyView.setAdapter(deletePoliciesAdapter); 156 157 // Updates the port when the user changes the security type. This allows 158 // us to show a reasonable default which the user can change. 159 mSecurityTypeView.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { 160 public void onItemSelected(AdapterView<?> arg0, View arg1, int arg2, long arg3) { 161 updatePortFromSecurityType(); 162 } 163 164 public void onNothingSelected(AdapterView<?> arg0) { } 165 }); 166 167 // After any text edits, call validateFields() which enables or disables the Next button 168 TextWatcher validationTextWatcher = new TextWatcher() { 169 public void afterTextChanged(Editable s) { 170 validateFields(); 171 } 172 173 public void beforeTextChanged(CharSequence s, int start, int count, int after) { } 174 public void onTextChanged(CharSequence s, int start, int before, int count) { } 175 }; 176 mUsernameView.addTextChangedListener(validationTextWatcher); 177 mPasswordView.addTextChangedListener(validationTextWatcher); 178 mServerView.addTextChangedListener(validationTextWatcher); 179 mPortView.addTextChangedListener(validationTextWatcher); 180 181 // Only allow digits in the port field. 182 mPortView.setKeyListener(DigitsKeyListener.getInstance("0123456789")); 183 184 return view; 185 } 186 187 @Override 188 public void onActivityCreated(Bundle savedInstanceState) { 189 if (Email.DEBUG_LIFECYCLE && Email.DEBUG) { 190 Log.d(Email.LOG_TAG, "AccountSetupIncomingFragment onActivityCreated"); 191 } 192 super.onActivityCreated(savedInstanceState); 193 } 194 195 /** 196 * Called when the Fragment is visible to the user. 197 */ 198 @Override 199 public void onStart() { 200 if (Email.DEBUG_LIFECYCLE && Email.DEBUG) { 201 Log.d(Email.LOG_TAG, "AccountSetupIncomingFragment onStart"); 202 } 203 super.onStart(); 204 mStarted = true; 205 if (!mLoaded) { 206 loadSettings(); 207 } 208 } 209 210 /** 211 * Called when the fragment is visible to the user and actively running. 212 */ 213 @Override 214 public void onResume() { 215 if (Email.DEBUG_LIFECYCLE && Email.DEBUG) { 216 Log.d(Email.LOG_TAG, "AccountSetupIncomingFragment onResume"); 217 } 218 super.onResume(); 219 validateFields(); 220 } 221 222 @Override 223 public void onPause() { 224 if (Email.DEBUG_LIFECYCLE && Email.DEBUG) { 225 Log.d(Email.LOG_TAG, "AccountSetupIncomingFragment onPause"); 226 } 227 super.onPause(); 228 } 229 230 /** 231 * Called when the Fragment is no longer started. 232 */ 233 @Override 234 public void onStop() { 235 if (Email.DEBUG_LIFECYCLE && Email.DEBUG) { 236 Log.d(Email.LOG_TAG, "AccountSetupIncomingFragment onStop"); 237 } 238 super.onStop(); 239 mStarted = false; 240 } 241 242 /** 243 * Called when the fragment is no longer in use. 244 */ 245 @Override 246 public void onDestroy() { 247 if (Email.DEBUG_LIFECYCLE && Email.DEBUG) { 248 Log.d(Email.LOG_TAG, "AccountSetupIncomingFragment onDestroy"); 249 } 250 super.onDestroy(); 251 } 252 253 @Override 254 public void onSaveInstanceState(Bundle outState) { 255 if (Email.DEBUG_LIFECYCLE && Email.DEBUG) { 256 Log.d(Email.LOG_TAG, "AccountSetupIncomingFragment onSaveInstanceState"); 257 } 258 super.onSaveInstanceState(outState); 259 260 outState.putString(STATE_KEY_CREDENTIAL, mCacheLoginCredential); 261 } 262 263 /** 264 * Activity provides callbacks here. This also triggers loading and setting up the UX 265 */ 266 @Override 267 public void setCallback(Callback callback) { 268 super.setCallback(callback); 269 if (mStarted && !mLoaded) { 270 loadSettings(); 271 } 272 } 273 274 /** 275 * Load the current settings into the UI 276 */ 277 private void loadSettings() { 278 try { 279 // TODO this should be accessed directly via the HostAuth structure 280 EmailContent.Account account = SetupData.getAccount(); 281 URI uri = new URI(account.getStoreUri(mContext)); 282 String username = null; 283 String password = null; 284 if (uri.getUserInfo() != null) { 285 String[] userInfoParts = uri.getUserInfo().split(":", 2); 286 username = userInfoParts[0]; 287 if (userInfoParts.length > 1) { 288 password = userInfoParts[1]; 289 } 290 } 291 292 if (username != null) { 293 mUsernameView.setText(username); 294 } 295 296 if (password != null) { 297 mPasswordView.setText(password); 298 } 299 300 if (uri.getScheme().startsWith("pop3")) { 301 mServerLabelView.setText(R.string.account_setup_incoming_pop_server_label); 302 mAccountPorts = POP_PORTS; 303 mAccountSchemes = POP_SCHEMES; 304 305 mImapPathPrefixSectionView.setVisibility(View.GONE); 306 } else if (uri.getScheme().startsWith("imap")) { 307 mServerLabelView.setText(R.string.account_setup_incoming_imap_server_label); 308 mAccountPorts = IMAP_PORTS; 309 mAccountSchemes = IMAP_SCHEMES; 310 311 mDeletePolicyLabelView.setVisibility(View.GONE); 312 mDeletePolicyView.setVisibility(View.GONE); 313 if (uri.getPath() != null && uri.getPath().length() > 0) { 314 mImapPathPrefixView.setText(uri.getPath().substring(1)); 315 } 316 } else { 317 throw new Error("Unknown account type: " + account.getStoreUri(mContext)); 318 } 319 320 for (int i = 0; i < mAccountSchemes.length; i++) { 321 if (mAccountSchemes[i].equals(uri.getScheme())) { 322 SpinnerOption.setSpinnerOptionValue(mSecurityTypeView, i); 323 } 324 } 325 326 SpinnerOption.setSpinnerOptionValue(mDeletePolicyView, account.getDeletePolicy()); 327 328 if (uri.getHost() != null) { 329 mServerView.setText(uri.getHost()); 330 } 331 332 if (uri.getPort() != -1) { 333 mPortView.setText(Integer.toString(uri.getPort())); 334 } else { 335 updatePortFromSecurityType(); 336 } 337 } catch (URISyntaxException use) { 338 /* 339 * We should always be able to parse our own settings. 340 */ 341 throw new Error(use); 342 } 343 344 validateFields(); 345 } 346 347 /** 348 * Check the values in the fields and decide if it makes sense to enable the "next" button 349 */ 350 private void validateFields() { 351 boolean enabled = Utility.isTextViewNotEmpty(mUsernameView) 352 && Utility.isTextViewNotEmpty(mPasswordView) 353 && Utility.isTextViewNotEmpty(mServerView) 354 && Utility.isPortFieldValid(mPortView); 355 if (enabled) { 356 try { 357 URI uri = getUri(); 358 } catch (URISyntaxException use) { 359 enabled = false; 360 } 361 } 362 enableNextButton(enabled); 363 } 364 365 private void updatePortFromSecurityType() { 366 int securityType = (Integer)((SpinnerOption)mSecurityTypeView.getSelectedItem()).value; 367 mPortView.setText(Integer.toString(mAccountPorts[securityType])); 368 } 369 370 /** 371 * Entry point from Activity after editing settings and verifying them. Must be FLOW_MODE_EDIT. 372 */ 373 @Override 374 public void saveSettingsAfterEdit() { 375 EmailContent.Account account = SetupData.getAccount(); 376 if (account.isSaved()) { 377 account.update(mContext, account.toContentValues()); 378 account.mHostAuthRecv.update(mContext, account.mHostAuthRecv.toContentValues()); 379 } else { 380 account.save(mContext); 381 } 382 // Update the backup (side copy) of the accounts 383 AccountBackupRestore.backupAccounts(mContext); 384 } 385 386 /** 387 * Entry point from Activity after entering new settings and verifying them. For setup mode. 388 */ 389 @Override 390 public void saveSettingsAfterSetup() { 391 EmailContent.Account account = SetupData.getAccount(); 392 393 // Set the username and password for the outgoing settings to the username and 394 // password the user just set for incoming. 395 try { 396 URI oldUri = new URI(account.getSenderUri(mContext)); 397 URI uri = new URI( 398 oldUri.getScheme(), 399 mUsernameView.getText().toString().trim() + ":" 400 + mPasswordView.getText().toString().trim(), 401 oldUri.getHost(), 402 oldUri.getPort(), 403 null, 404 null, 405 null); 406 account.setSenderUri(mContext, uri.toString()); 407 } catch (URISyntaxException use) { 408 // If we can't set up the URL we just continue. It's only for convenience. 409 } 410 } 411 412 /** 413 * Attempt to create a URI from the fields provided. Throws URISyntaxException if there's 414 * a problem with the user input. 415 * @return a URI built from the account setup fields 416 */ 417 private URI getUri() throws URISyntaxException { 418 int securityType = (Integer)((SpinnerOption)mSecurityTypeView.getSelectedItem()).value; 419 String path = null; 420 if (mAccountSchemes[securityType].startsWith("imap")) { 421 path = "/" + mImapPathPrefixView.getText().toString().trim(); 422 } 423 String userName = mUsernameView.getText().toString().trim(); 424 mCacheLoginCredential = userName; 425 URI uri = new URI( 426 mAccountSchemes[securityType], 427 userName + ":" + mPasswordView.getText().toString().trim(), 428 mServerView.getText().toString().trim(), 429 Integer.parseInt(mPortView.getText().toString().trim()), 430 path, // path 431 null, // query 432 null); 433 434 return uri; 435 } 436 437 /** 438 * Entry point from Activity, when "next" button is clicked 439 */ 440 @Override 441 public void onNext() { 442 EmailContent.Account setupAccount = SetupData.getAccount(); 443 try { 444 URI uri = getUri(); 445 setupAccount.setStoreUri(mContext, uri.toString()); 446 447 // Stop here if the login credentials duplicate an existing account 448 // (unless they duplicate the existing account, as they of course will) 449 EmailContent.Account account = Utility.findExistingAccount(mContext, setupAccount.mId, 450 uri.getHost(), mCacheLoginCredential); 451 if (account != null) { 452 DuplicateAccountDialogFragment dialogFragment = 453 DuplicateAccountDialogFragment.newInstance(account.mDisplayName); 454 dialogFragment.show(getActivity(), DuplicateAccountDialogFragment.TAG); 455 return; 456 } 457 } catch (URISyntaxException use) { 458 /* 459 * It's unrecoverable if we cannot create a URI from components that 460 * we validated to be safe. 461 */ 462 throw new Error(use); 463 } 464 465 setupAccount.setDeletePolicy( 466 (Integer)((SpinnerOption)mDeletePolicyView.getSelectedItem()).value); 467 468 mCallback.onProceedNext(SetupData.CHECK_INCOMING, this); 469 } 470} 471