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