AccountSetupIncoming.java revision da8836a76cd8a6eaa7e3693eeacc6393870b2066
1/* 2 * Copyright (C) 2008 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.R; 20import com.android.email.Utility; 21import com.android.email.provider.EmailContent; 22 23import android.app.Activity; 24import android.app.AlertDialog; 25import android.app.Dialog; 26import android.content.DialogInterface; 27import android.content.Intent; 28import android.os.Bundle; 29import android.text.Editable; 30import android.text.TextWatcher; 31import android.text.method.DigitsKeyListener; 32import android.view.View; 33import android.view.View.OnClickListener; 34import android.widget.AdapterView; 35import android.widget.ArrayAdapter; 36import android.widget.Button; 37import android.widget.EditText; 38import android.widget.Spinner; 39import android.widget.TextView; 40 41import java.net.URI; 42import java.net.URISyntaxException; 43 44public class AccountSetupIncoming extends Activity implements OnClickListener { 45 private static final String EXTRA_ACCOUNT = "account"; 46 private static final String EXTRA_MAKE_DEFAULT = "makeDefault"; 47 48 private static final int popPorts[] = { 49 110, 995, 995, 110, 110 50 }; 51 private static final String popSchemes[] = { 52 "pop3", "pop3+ssl", "pop3+ssl+", "pop3+tls", "pop3+tls+" 53 }; 54 private static final int imapPorts[] = { 55 143, 993, 993, 143, 143 56 }; 57 private static final String imapSchemes[] = { 58 "imap", "imap+ssl", "imap+ssl+", "imap+tls", "imap+tls+" 59 }; 60 61 private final static int DIALOG_DUPLICATE_ACCOUNT = 1; 62 63 private int mAccountPorts[]; 64 private String mAccountSchemes[]; 65 private EditText mUsernameView; 66 private EditText mPasswordView; 67 private EditText mServerView; 68 private EditText mPortView; 69 private Spinner mSecurityTypeView; 70 private Spinner mDeletePolicyView; 71 private EditText mImapPathPrefixView; 72 private Button mNextButton; 73 private EmailContent.Account mAccount; 74 private boolean mMakeDefault; 75 private String mCacheLoginCredential; 76 private String mDuplicateAccountName; 77 78 public static void actionIncomingSettings(Activity fromActivity, EmailContent.Account account, 79 boolean makeDefault) { 80 Intent i = new Intent(fromActivity, AccountSetupIncoming.class); 81 i.putExtra(EXTRA_ACCOUNT, account); 82 i.putExtra(EXTRA_MAKE_DEFAULT, makeDefault); 83 fromActivity.startActivity(i); 84 } 85 86 public static void actionEditIncomingSettings(Activity fromActivity, EmailContent.Account account) 87 { 88 Intent i = new Intent(fromActivity, AccountSetupIncoming.class); 89 i.setAction(Intent.ACTION_EDIT); 90 i.putExtra(EXTRA_ACCOUNT, account); 91 fromActivity.startActivity(i); 92 } 93 94 @Override 95 public void onCreate(Bundle savedInstanceState) { 96 super.onCreate(savedInstanceState); 97 setContentView(R.layout.account_setup_incoming); 98 99 mUsernameView = (EditText)findViewById(R.id.account_username); 100 mPasswordView = (EditText)findViewById(R.id.account_password); 101 TextView serverLabelView = (TextView) findViewById(R.id.account_server_label); 102 mServerView = (EditText)findViewById(R.id.account_server); 103 mPortView = (EditText)findViewById(R.id.account_port); 104 mSecurityTypeView = (Spinner)findViewById(R.id.account_security_type); 105 mDeletePolicyView = (Spinner)findViewById(R.id.account_delete_policy); 106 mImapPathPrefixView = (EditText)findViewById(R.id.imap_path_prefix); 107 mNextButton = (Button)findViewById(R.id.next); 108 109 mNextButton.setOnClickListener(this); 110 111 SpinnerOption securityTypes[] = { 112 new SpinnerOption(0, getString(R.string.account_setup_incoming_security_none_label)), 113 new SpinnerOption(1, 114 getString(R.string.account_setup_incoming_security_ssl_optional_label)), 115 new SpinnerOption(2, getString(R.string.account_setup_incoming_security_ssl_label)), 116 new SpinnerOption(3, 117 getString(R.string.account_setup_incoming_security_tls_optional_label)), 118 new SpinnerOption(4, getString(R.string.account_setup_incoming_security_tls_label)), 119 }; 120 121 SpinnerOption deletePolicies[] = { 122 new SpinnerOption(0, 123 getString(R.string.account_setup_incoming_delete_policy_never_label)), 124 new SpinnerOption(1, 125 getString(R.string.account_setup_incoming_delete_policy_7days_label)), 126 new SpinnerOption(2, 127 getString(R.string.account_setup_incoming_delete_policy_delete_label)), 128 }; 129 130 ArrayAdapter<SpinnerOption> securityTypesAdapter = new ArrayAdapter<SpinnerOption>(this, 131 android.R.layout.simple_spinner_item, securityTypes); 132 securityTypesAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); 133 mSecurityTypeView.setAdapter(securityTypesAdapter); 134 135 ArrayAdapter<SpinnerOption> deletePoliciesAdapter = new ArrayAdapter<SpinnerOption>(this, 136 android.R.layout.simple_spinner_item, deletePolicies); 137 deletePoliciesAdapter 138 .setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); 139 mDeletePolicyView.setAdapter(deletePoliciesAdapter); 140 141 /* 142 * Updates the port when the user changes the security type. This allows 143 * us to show a reasonable default which the user can change. 144 */ 145 mSecurityTypeView.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { 146 public void onItemSelected(AdapterView arg0, View arg1, int arg2, long arg3) { 147 updatePortFromSecurityType(); 148 } 149 150 public void onNothingSelected(AdapterView<?> arg0) { 151 } 152 }); 153 154 /* 155 * Calls validateFields() which enables or disables the Next button 156 * based on the fields' validity. 157 */ 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 } 165 166 public void onTextChanged(CharSequence s, int start, int before, int count) { 167 } 168 }; 169 mUsernameView.addTextChangedListener(validationTextWatcher); 170 mPasswordView.addTextChangedListener(validationTextWatcher); 171 mServerView.addTextChangedListener(validationTextWatcher); 172 mPortView.addTextChangedListener(validationTextWatcher); 173 174 /* 175 * Only allow digits in the port field. 176 */ 177 mPortView.setKeyListener(DigitsKeyListener.getInstance("0123456789")); 178 179 mAccount = (EmailContent.Account)getIntent().getParcelableExtra(EXTRA_ACCOUNT); 180 mMakeDefault = getIntent().getBooleanExtra(EXTRA_MAKE_DEFAULT, false); 181 182 /* 183 * If we're being reloaded we override the original account with the one 184 * we saved 185 */ 186 if (savedInstanceState != null && savedInstanceState.containsKey(EXTRA_ACCOUNT)) { 187 mAccount = (EmailContent.Account)savedInstanceState.getParcelable(EXTRA_ACCOUNT); 188 } 189 190 try { 191 // TODO this should be accessed directly via the HostAuth structure 192 URI uri = new URI(mAccount.getStoreUri(this)); 193 String username = null; 194 String password = null; 195 if (uri.getUserInfo() != null) { 196 String[] userInfoParts = uri.getUserInfo().split(":", 2); 197 username = userInfoParts[0]; 198 if (userInfoParts.length > 1) { 199 password = userInfoParts[1]; 200 } 201 } 202 203 if (username != null) { 204 mUsernameView.setText(username); 205 } 206 207 if (password != null) { 208 mPasswordView.setText(password); 209 } 210 211 if (uri.getScheme().startsWith("pop3")) { 212 serverLabelView.setText(R.string.account_setup_incoming_pop_server_label); 213 mAccountPorts = popPorts; 214 mAccountSchemes = popSchemes; 215 216 findViewById(R.id.imap_path_prefix_section).setVisibility(View.GONE); 217 } else if (uri.getScheme().startsWith("imap")) { 218 serverLabelView.setText(R.string.account_setup_incoming_imap_server_label); 219 mAccountPorts = imapPorts; 220 mAccountSchemes = imapSchemes; 221 222 findViewById(R.id.account_delete_policy_label).setVisibility(View.GONE); 223 mDeletePolicyView.setVisibility(View.GONE); 224 if (uri.getPath() != null && uri.getPath().length() > 0) { 225 mImapPathPrefixView.setText(uri.getPath().substring(1)); 226 } 227 } else { 228 throw new Error("Unknown account type: " + mAccount.getStoreUri(this)); 229 } 230 231 for (int i = 0; i < mAccountSchemes.length; i++) { 232 if (mAccountSchemes[i].equals(uri.getScheme())) { 233 SpinnerOption.setSpinnerOptionValue(mSecurityTypeView, i); 234 } 235 } 236 237 SpinnerOption.setSpinnerOptionValue(mDeletePolicyView, mAccount.getDeletePolicy()); 238 239 if (uri.getHost() != null) { 240 mServerView.setText(uri.getHost()); 241 } 242 243 if (uri.getPort() != -1) { 244 mPortView.setText(Integer.toString(uri.getPort())); 245 } else { 246 updatePortFromSecurityType(); 247 } 248 } catch (URISyntaxException use) { 249 /* 250 * We should always be able to parse our own settings. 251 */ 252 throw new Error(use); 253 } 254 255 validateFields(); 256 } 257 258 @Override 259 public void onSaveInstanceState(Bundle outState) { 260 super.onSaveInstanceState(outState); 261 outState.putParcelable(EXTRA_ACCOUNT, mAccount); 262 } 263 264 /** 265 * Prepare a cached dialog with current values (e.g. account name) 266 */ 267 @Override 268 public Dialog onCreateDialog(int id) { 269 switch (id) { 270 case DIALOG_DUPLICATE_ACCOUNT: 271 return new AlertDialog.Builder(this) 272 .setIcon(android.R.drawable.ic_dialog_alert) 273 .setTitle(R.string.account_duplicate_dlg_title) 274 .setMessage(getString(R.string.account_duplicate_dlg_message_fmt, 275 mDuplicateAccountName)) 276 .setPositiveButton(R.string.okay_action, 277 new DialogInterface.OnClickListener() { 278 public void onClick(DialogInterface dialog, int which) { 279 dismissDialog(DIALOG_DUPLICATE_ACCOUNT); 280 } 281 }) 282 .create(); 283 } 284 return null; 285 } 286 287 /** 288 * Update a cached dialog with current values (e.g. account name) 289 */ 290 @Override 291 public void onPrepareDialog(int id, Dialog dialog) { 292 switch (id) { 293 case DIALOG_DUPLICATE_ACCOUNT: 294 if (mDuplicateAccountName != null) { 295 AlertDialog alert = (AlertDialog) dialog; 296 alert.setMessage(getString(R.string.account_duplicate_dlg_message_fmt, 297 mDuplicateAccountName)); 298 } 299 break; 300 } 301 } 302 303 /** 304 * Check the values in the fields and decide if it makes sense to enable the "next" button 305 * NOTE: Does it make sense to extract & combine with similar code in AccountSetupIncoming? 306 */ 307 private void validateFields() { 308 boolean enabled = Utility.requiredFieldValid(mUsernameView) 309 && Utility.requiredFieldValid(mPasswordView) 310 && Utility.requiredFieldValid(mServerView) 311 && Utility.requiredFieldValid(mPortView); 312 if (enabled) { 313 try { 314 URI uri = getUri(); 315 } catch (URISyntaxException use) { 316 enabled = false; 317 } 318 } 319 mNextButton.setEnabled(enabled); 320 Utility.setCompoundDrawablesAlpha(mNextButton, enabled ? 255 : 128); 321 } 322 323 private void updatePortFromSecurityType() { 324 int securityType = (Integer)((SpinnerOption)mSecurityTypeView.getSelectedItem()).value; 325 mPortView.setText(Integer.toString(mAccountPorts[securityType])); 326 } 327 328 @Override 329 public void onActivityResult(int requestCode, int resultCode, Intent data) { 330 if (resultCode == RESULT_OK) { 331 if (Intent.ACTION_EDIT.equals(getIntent().getAction())) { 332 // TODO Review carefully to make sure this is bulletproof 333 if (mAccount.isSaved()) { 334 mAccount.update(this, mAccount.toContentValues()); 335 } else { 336 mAccount.save(this); 337 } 338 finish(); 339 } else { 340 /* 341 * Set the username and password for the outgoing settings to the username and 342 * password the user just set for incoming. 343 */ 344 try { 345 URI oldUri = new URI(mAccount.getSenderUri(this)); 346 URI uri = new URI( 347 oldUri.getScheme(), 348 mUsernameView.getText().toString().trim() + ":" 349 + mPasswordView.getText().toString().trim(), 350 oldUri.getHost(), 351 oldUri.getPort(), 352 null, 353 null, 354 null); 355 mAccount.setSenderUri(this, uri.toString()); 356 } catch (URISyntaxException use) { 357 /* 358 * If we can't set up the URL we just continue. It's only for 359 * convenience. 360 */ 361 } 362 363 AccountSetupOutgoing.actionOutgoingSettings(this, mAccount, mMakeDefault); 364 finish(); 365 } 366 } 367 } 368 369 /** 370 * Attempt to create a URI from the fields provided. Throws URISyntaxException if there's 371 * a problem with the user input. 372 * @return a URI built from the account setup fields 373 */ 374 private URI getUri() throws URISyntaxException { 375 int securityType = (Integer)((SpinnerOption)mSecurityTypeView.getSelectedItem()).value; 376 String path = null; 377 if (mAccountSchemes[securityType].startsWith("imap")) { 378 path = "/" + mImapPathPrefixView.getText().toString().trim(); 379 } 380 String userName = mUsernameView.getText().toString().trim(); 381 mCacheLoginCredential = userName; 382 URI uri = new URI( 383 mAccountSchemes[securityType], 384 userName + ":" + mPasswordView.getText().toString().trim(), 385 mServerView.getText().toString().trim(), 386 Integer.parseInt(mPortView.getText().toString().trim()), 387 path, // path 388 null, // query 389 null); 390 391 return uri; 392 } 393 394 private void onNext() { 395 try { 396 URI uri = getUri(); 397 mAccount.setStoreUri(this, uri.toString()); 398 399 // Stop here if the login credentials duplicate an existing account 400 // (unless they duplicate the existing account, as they of course will) 401 mDuplicateAccountName = Utility.findDuplicateAccount(this, mAccount.mId, 402 uri.getHost(), mCacheLoginCredential); 403 if (mDuplicateAccountName != null) { 404 this.showDialog(DIALOG_DUPLICATE_ACCOUNT); 405 return; 406 } 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 mAccount.setDeletePolicy((Integer)((SpinnerOption)mDeletePolicyView.getSelectedItem()).value); 416 AccountSetupCheckSettings.actionCheckSettings(this, mAccount, true, false); 417 } 418 419 public void onClick(View v) { 420 switch (v.getId()) { 421 case R.id.next: 422 onNext(); 423 break; 424 } 425 } 426} 427