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