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