AccountSetupExchange.java revision acfd155c1202ebde4f9d8f19d5308a4da08d108f
1/* 2 * Copyright (C) 2009 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; 22import com.android.email.provider.EmailContent.Account; 23 24import android.app.Activity; 25import android.app.AlertDialog; 26import android.app.Dialog; 27import android.content.DialogInterface; 28import android.content.Intent; 29import android.os.Bundle; 30import android.text.Editable; 31import android.text.TextWatcher; 32import android.view.View; 33import android.view.View.OnClickListener; 34import android.widget.Button; 35import android.widget.CheckBox; 36import android.widget.CompoundButton; 37import android.widget.EditText; 38import android.widget.CompoundButton.OnCheckedChangeListener; 39 40import java.net.URI; 41import java.net.URISyntaxException; 42 43/** 44 * Provides generic setup for Exchange accounts. The following fields are supported: 45 * 46 * Email Address (from previous setup screen) 47 * Server 48 * Domain 49 * Requires SSL? 50 * User (login) 51 * Password 52 */ 53public class AccountSetupExchange extends Activity implements OnClickListener, 54 OnCheckedChangeListener { 55 private static final String EXTRA_ACCOUNT = "account"; 56 private static final String EXTRA_MAKE_DEFAULT = "makeDefault"; 57 private static final String EXTRA_EAS_FLOW = "easFlow"; 58 59 private final static int DIALOG_DUPLICATE_ACCOUNT = 1; 60 61 private EditText mUsernameView; 62 private EditText mPasswordView; 63 private EditText mServerView; 64 private CheckBox mSslSecurityView; 65 private CheckBox mTrustCertificatesView; 66 67 private Button mNextButton; 68 private Account mAccount; 69 private boolean mMakeDefault; 70 private String mCacheLoginCredential; 71 private String mDuplicateAccountName; 72 73 public static void actionIncomingSettings(Activity fromActivity, Account account, 74 boolean makeDefault, boolean easFlowMode) { 75 Intent i = new Intent(fromActivity, AccountSetupExchange.class); 76 i.putExtra(EXTRA_ACCOUNT, account); 77 i.putExtra(EXTRA_MAKE_DEFAULT, makeDefault); 78 i.putExtra(EXTRA_EAS_FLOW, easFlowMode); 79 fromActivity.startActivity(i); 80 } 81 82 public static void actionEditIncomingSettings(Activity fromActivity, Account account) 83 { 84 Intent i = new Intent(fromActivity, AccountSetupExchange.class); 85 i.setAction(Intent.ACTION_EDIT); 86 i.putExtra(EXTRA_ACCOUNT, account); 87 fromActivity.startActivity(i); 88 } 89 90 /** 91 * For now, we'll simply replicate outgoing, for the purpose of satisfying the 92 * account settings flow. 93 */ 94 public static void actionEditOutgoingSettings(Activity fromActivity, Account account) 95 { 96 Intent i = new Intent(fromActivity, AccountSetupExchange.class); 97 i.setAction(Intent.ACTION_EDIT); 98 i.putExtra(EXTRA_ACCOUNT, account); 99 fromActivity.startActivity(i); 100 } 101 102 @Override 103 public void onCreate(Bundle savedInstanceState) { 104 super.onCreate(savedInstanceState); 105 setContentView(R.layout.account_setup_exchange); 106 107 mUsernameView = (EditText) findViewById(R.id.account_username); 108 mPasswordView = (EditText) findViewById(R.id.account_password); 109 mServerView = (EditText) findViewById(R.id.account_server); 110 mSslSecurityView = (CheckBox) findViewById(R.id.account_ssl); 111 mSslSecurityView.setOnCheckedChangeListener(this); 112 mTrustCertificatesView = (CheckBox) findViewById(R.id.account_trust_certificates); 113 114 mNextButton = (Button)findViewById(R.id.next); 115 mNextButton.setOnClickListener(this); 116 117 /* 118 * Calls validateFields() which enables or disables the Next button 119 * based on the fields' validity. 120 */ 121 TextWatcher validationTextWatcher = new TextWatcher() { 122 public void afterTextChanged(Editable s) { 123 validateFields(); 124 } 125 126 public void beforeTextChanged(CharSequence s, int start, int count, int after) { 127 } 128 129 public void onTextChanged(CharSequence s, int start, int before, int count) { 130 } 131 }; 132 mUsernameView.addTextChangedListener(validationTextWatcher); 133 mPasswordView.addTextChangedListener(validationTextWatcher); 134 mServerView.addTextChangedListener(validationTextWatcher); 135 136 mAccount = (EmailContent.Account) getIntent().getParcelableExtra(EXTRA_ACCOUNT); 137 mMakeDefault = getIntent().getBooleanExtra(EXTRA_MAKE_DEFAULT, false); 138 139 /* 140 * If we're being reloaded we override the original account with the one 141 * we saved 142 */ 143 if (savedInstanceState != null && savedInstanceState.containsKey(EXTRA_ACCOUNT)) { 144 mAccount = (EmailContent.Account) savedInstanceState.getParcelable(EXTRA_ACCOUNT); 145 } 146 147 try { 148 URI uri = new URI(mAccount.getStoreUri(this)); 149 String username = null; 150 String password = null; 151 if (uri.getUserInfo() != null) { 152 String[] userInfoParts = uri.getUserInfo().split(":", 2); 153 username = userInfoParts[0]; 154 if (userInfoParts.length > 1) { 155 password = userInfoParts[1]; 156 } 157 } 158 159 if (username != null) { 160 // Add a backslash to the start of the username, but only if the username has no 161 // backslash in it. 162 if (username.indexOf('\\') < 0) { 163 username = "\\" + username; 164 } 165 mUsernameView.setText(username); 166 } 167 168 if (password != null) { 169 mPasswordView.setText(password); 170 } 171 172 if (uri.getScheme().startsWith("eas")) { 173 // any other setup from mAccount can go here 174 } else { 175 throw new Error("Unknown account type: " + mAccount.getStoreUri(this)); 176 } 177 178 if (uri.getHost() != null) { 179 mServerView.setText(uri.getHost()); 180 } 181 182 boolean ssl = uri.getScheme().contains("ssl"); 183 mSslSecurityView.setChecked(ssl); 184 mTrustCertificatesView.setChecked(uri.getScheme().contains("tssl")); 185 mTrustCertificatesView.setVisibility(ssl ? View.VISIBLE : View.GONE); 186 187 } catch (URISyntaxException use) { 188 /* 189 * We should always be able to parse our own settings. 190 */ 191 throw new Error(use); 192 } 193 194 validateFields(); 195 } 196 197 @Override 198 public void onSaveInstanceState(Bundle outState) { 199 super.onSaveInstanceState(outState); 200 outState.putParcelable(EXTRA_ACCOUNT, mAccount); 201 } 202 203 private boolean usernameFieldValid(EditText usernameView) { 204 return Utility.requiredFieldValid(usernameView) && 205 !usernameView.getText().toString().equals("\\"); 206 } 207 208 /** 209 * Prepare a cached dialog with current values (e.g. account name) 210 */ 211 @Override 212 public Dialog onCreateDialog(int id) { 213 switch (id) { 214 case DIALOG_DUPLICATE_ACCOUNT: 215 return new AlertDialog.Builder(this) 216 .setIcon(android.R.drawable.ic_dialog_alert) 217 .setTitle(R.string.account_duplicate_dlg_title) 218 .setMessage(getString(R.string.account_duplicate_dlg_message_fmt, 219 mDuplicateAccountName)) 220 .setPositiveButton(R.string.okay_action, 221 new DialogInterface.OnClickListener() { 222 public void onClick(DialogInterface dialog, int which) { 223 dismissDialog(DIALOG_DUPLICATE_ACCOUNT); 224 } 225 }) 226 .create(); 227 } 228 return null; 229 } 230 231 /** 232 * Update a cached dialog with current values (e.g. account name) 233 */ 234 @Override 235 public void onPrepareDialog(int id, Dialog dialog) { 236 switch (id) { 237 case DIALOG_DUPLICATE_ACCOUNT: 238 if (mDuplicateAccountName != null) { 239 AlertDialog alert = (AlertDialog) dialog; 240 alert.setMessage(getString(R.string.account_duplicate_dlg_message_fmt, 241 mDuplicateAccountName)); 242 } 243 break; 244 } 245 } 246 247 /** 248 * Check the values in the fields and decide if it makes sense to enable the "next" button 249 * NOTE: Does it make sense to extract & combine with similar code in AccountSetupIncoming? 250 */ 251 private void validateFields() { 252 boolean enabled = usernameFieldValid(mUsernameView) 253 && Utility.requiredFieldValid(mPasswordView) 254 && Utility.requiredFieldValid(mServerView); 255 if (enabled) { 256 try { 257 URI uri = getUri(); 258 } catch (URISyntaxException use) { 259 enabled = false; 260 } 261 } 262 mNextButton.setEnabled(enabled); 263 Utility.setCompoundDrawablesAlpha(mNextButton, enabled ? 255 : 128); 264 } 265 266 @Override 267 public void onActivityResult(int requestCode, int resultCode, Intent data) { 268 if (resultCode == RESULT_OK) { 269 if (Intent.ACTION_EDIT.equals(getIntent().getAction())) { 270 // TODO Review carefully to make sure this is bulletproof 271 if (mAccount.isSaved()) { 272 mAccount.update(this, mAccount.toContentValues()); 273 } else { 274 mAccount.save(this); 275 } 276 finish(); 277 } else { 278 // Go directly to end - there is no 2nd screen for incoming settings 279 boolean easFlowMode = getIntent().getBooleanExtra(EXTRA_EAS_FLOW, false); 280 AccountSetupOptions.actionOptions(this, mAccount, mMakeDefault, easFlowMode); 281 finish(); 282 } 283 } 284 } 285 286 /** 287 * Attempt to create a URI from the fields provided. Throws URISyntaxException if there's 288 * a problem with the user input. 289 * @return a URI built from the account setup fields 290 */ 291 private URI getUri() throws URISyntaxException { 292 boolean sslRequired = mSslSecurityView.isChecked(); 293 boolean trustCertificates = mTrustCertificatesView.isChecked(); 294 String scheme = (sslRequired) ? (trustCertificates ? "eas+tssl+" : "eas+ssl+") : "eas"; 295 String userName = mUsernameView.getText().toString().trim(); 296 // Remove a leading backslash, if there is one, since we now automatically put one at 297 // the start of the username field 298 if (userName.startsWith("\\")) { 299 userName = userName.substring(1); 300 } 301 mCacheLoginCredential = userName; 302 String userInfo = userName + ":" + mPasswordView.getText().toString().trim(); 303 String host = mServerView.getText().toString().trim(); 304 String path = null; 305 306 URI uri = new URI( 307 scheme, 308 userInfo, 309 host, 310 0, 311 path, 312 null, 313 null); 314 315 return uri; 316 } 317 318 /** 319 * Note, in EAS, store & sender are the same, so we always populate them together 320 */ 321 private void onNext() { 322 try { 323 URI uri = getUri(); 324 mAccount.setStoreUri(this, uri.toString()); 325 mAccount.setSenderUri(this, uri.toString()); 326 327 // Stop here if the login credentials duplicate an existing account 328 // (unless they duplicate the existing account, as they of course will) 329 mDuplicateAccountName = Utility.findDuplicateAccount(this, mAccount.mId, 330 uri.getHost(), mCacheLoginCredential); 331 if (mDuplicateAccountName != null) { 332 this.showDialog(DIALOG_DUPLICATE_ACCOUNT); 333 return; 334 } 335 } catch (URISyntaxException use) { 336 /* 337 * It's unrecoverable if we cannot create a URI from components that 338 * we validated to be safe. 339 */ 340 throw new Error(use); 341 } 342 343 AccountSetupCheckSettings.actionCheckSettings(this, mAccount, true, false); 344 } 345 346 public void onClick(View v) { 347 switch (v.getId()) { 348 case R.id.next: 349 onNext(); 350 break; 351 } 352 } 353 354 public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { 355 if (buttonView.getId() == R.id.account_ssl) { 356 mTrustCertificatesView.setVisibility(isChecked ? View.VISIBLE : View.GONE); 357 } 358 } 359} 360