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