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