k-9/k9mail/src/main/java/com/fsck/k9/activity/setup/AccountSetupBasics.java

536 lines
21 KiB
Java

package com.fsck.k9.activity.setup;
import java.io.Serializable;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Locale;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.res.XmlResourceParser;
import android.os.Bundle;
import android.text.Editable;
import android.text.InputType;
import android.text.TextWatcher;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.EditText;
import com.fsck.k9.Account;
import com.fsck.k9.Account.DeletePolicy;
import com.fsck.k9.EmailAddressValidator;
import com.fsck.k9.K9;
import com.fsck.k9.Preferences;
import com.fsck.k9.R;
import com.fsck.k9.activity.K9Activity;
import com.fsck.k9.activity.setup.AccountSetupCheckSettings.CheckDirection;
import com.fsck.k9.helper.UrlEncodingHelper;
import com.fsck.k9.helper.Utility;
import com.fsck.k9.mail.AuthType;
import com.fsck.k9.mail.ConnectionSecurity;
import com.fsck.k9.mail.ServerSettings;
import com.fsck.k9.mail.Transport;
import com.fsck.k9.mail.store.imap.ImapStore;
import com.fsck.k9.mail.store.RemoteStore;
import com.fsck.k9.mail.transport.SmtpTransport;
import com.fsck.k9.view.ClientCertificateSpinner;
import com.fsck.k9.view.ClientCertificateSpinner.OnClientCertificateChangedListener;
/**
* Prompts the user for the email address and password.
* Attempts to lookup default settings for the domain the user specified. If the
* domain is known the settings are handed off to the AccountSetupCheckSettings
* activity. If no settings are found the settings are handed off to the
* AccountSetupAccountType activity.
*/
public class AccountSetupBasics extends K9Activity
implements OnClickListener, TextWatcher, OnCheckedChangeListener, OnClientCertificateChangedListener {
private final static String EXTRA_ACCOUNT = "com.fsck.k9.AccountSetupBasics.account";
private final static int DIALOG_NOTE = 1;
private final static String STATE_KEY_PROVIDER =
"com.fsck.k9.AccountSetupBasics.provider";
private final static String STATE_KEY_CHECKED_INCOMING =
"com.fsck.k9.AccountSetupBasics.checkedIncoming";
private EditText mEmailView;
private EditText mPasswordView;
private CheckBox mClientCertificateCheckBox;
private ClientCertificateSpinner mClientCertificateSpinner;
private Button mNextButton;
private Button mManualSetupButton;
private Account mAccount;
private Provider mProvider;
private EmailAddressValidator mEmailValidator = new EmailAddressValidator();
private boolean mCheckedIncoming = false;
private CheckBox mShowPasswordCheckBox;
public static void actionNewAccount(Context context) {
Intent i = new Intent(context, AccountSetupBasics.class);
context.startActivity(i);
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.account_setup_basics);
mEmailView = (EditText)findViewById(R.id.account_email);
mPasswordView = (EditText)findViewById(R.id.account_password);
mClientCertificateCheckBox = (CheckBox)findViewById(R.id.account_client_certificate);
mClientCertificateSpinner = (ClientCertificateSpinner)findViewById(R.id.account_client_certificate_spinner);
mNextButton = (Button)findViewById(R.id.next);
mManualSetupButton = (Button)findViewById(R.id.manual_setup);
mShowPasswordCheckBox = (CheckBox) findViewById(R.id.show_password);
mNextButton.setOnClickListener(this);
mManualSetupButton.setOnClickListener(this);
}
private void initializeViewListeners() {
mEmailView.addTextChangedListener(this);
mPasswordView.addTextChangedListener(this);
mClientCertificateCheckBox.setOnCheckedChangeListener(this);
mClientCertificateSpinner.setOnClientCertificateChangedListener(this);
mShowPasswordCheckBox.setOnCheckedChangeListener (new OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
showPassword(isChecked);
}
});
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
if (mAccount != null) {
outState.putString(EXTRA_ACCOUNT, mAccount.getUuid());
}
if (mProvider != null) {
outState.putSerializable(STATE_KEY_PROVIDER, mProvider);
}
outState.putBoolean(STATE_KEY_CHECKED_INCOMING, mCheckedIncoming);
}
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
if (savedInstanceState.containsKey(EXTRA_ACCOUNT)) {
String accountUuid = savedInstanceState.getString(EXTRA_ACCOUNT);
mAccount = Preferences.getPreferences(this).getAccount(accountUuid);
}
if (savedInstanceState.containsKey(STATE_KEY_PROVIDER)) {
mProvider = (Provider) savedInstanceState.getSerializable(STATE_KEY_PROVIDER);
}
mCheckedIncoming = savedInstanceState.getBoolean(STATE_KEY_CHECKED_INCOMING);
updateViewVisibility(mClientCertificateCheckBox.isChecked());
showPassword(mShowPasswordCheckBox.isChecked());
}
@Override
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
/*
* We wait until now to initialize the listeners because we didn't want
* the OnCheckedChangeListener active while the
* mClientCertificateCheckBox state was being restored because it could
* trigger the pop-up of a ClientCertificateSpinner.chooseCertificate()
* dialog.
*/
initializeViewListeners();
validateFields();
}
public void afterTextChanged(Editable s) {
validateFields();
}
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public void onClientCertificateChanged(String alias) {
validateFields();
}
/**
* Called when checking the client certificate CheckBox
*/
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
updateViewVisibility(isChecked);
validateFields();
// Have the user select (or confirm) the client certificate
if (isChecked) {
mClientCertificateSpinner.chooseCertificate();
}
}
private void updateViewVisibility(boolean usingCertificates) {
if (usingCertificates) {
// hide password fields, show client certificate spinner
mPasswordView.setVisibility(View.GONE);
mShowPasswordCheckBox.setVisibility(View.GONE);
mClientCertificateSpinner.setVisibility(View.VISIBLE);
} else {
// show password fields, hide client certificate spinner
mPasswordView.setVisibility(View.VISIBLE);
mShowPasswordCheckBox.setVisibility(View.VISIBLE);
mClientCertificateSpinner.setVisibility(View.GONE);
}
}
private void showPassword(boolean show) {
if (show) {
mPasswordView.setInputType(InputType.TYPE_TEXT_VARIATION_PASSWORD);
} else {
mPasswordView.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
}
}
private void validateFields() {
boolean clientCertificateChecked = mClientCertificateCheckBox.isChecked();
String clientCertificateAlias = mClientCertificateSpinner.getAlias();
String email = mEmailView.getText().toString();
boolean valid = Utility.requiredFieldValid(mEmailView)
&& ((!clientCertificateChecked && Utility.requiredFieldValid(mPasswordView))
|| (clientCertificateChecked && clientCertificateAlias != null))
&& mEmailValidator.isValidAddressOnly(email);
mNextButton.setEnabled(valid);
mManualSetupButton.setEnabled(valid);
/*
* Dim the next button's icon to 50% if the button is disabled.
* TODO this can probably be done with a stateful drawable. Check into it.
* android:state_enabled
*/
Utility.setCompoundDrawablesAlpha(mNextButton, mNextButton.isEnabled() ? 255 : 128);
}
private String getOwnerName() {
String name = null;
try {
name = getDefaultAccountName();
} catch (Exception e) {
Log.e(K9.LOG_TAG, "Could not get default account name", e);
}
if (name == null) {
name = "";
}
return name;
}
private String getDefaultAccountName() {
String name = null;
Account account = Preferences.getPreferences(this).getDefaultAccount();
if (account != null) {
name = account.getName();
}
return name;
}
@Override
public Dialog onCreateDialog(int id) {
if (id == DIALOG_NOTE) {
if (mProvider != null && mProvider.note != null) {
return new AlertDialog.Builder(this)
.setMessage(mProvider.note)
.setPositiveButton(
getString(R.string.okay_action),
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
finishAutoSetup();
}
})
.setNegativeButton(
getString(R.string.cancel_action),
null)
.create();
}
}
return null;
}
private void finishAutoSetup() {
String email = mEmailView.getText().toString();
String password = mPasswordView.getText().toString();
String[] emailParts = splitEmail(email);
String user = emailParts[0];
String domain = emailParts[1];
try {
String userEnc = UrlEncodingHelper.encodeUtf8(user);
String passwordEnc = UrlEncodingHelper.encodeUtf8(password);
String incomingUsername = mProvider.incomingUsernameTemplate;
incomingUsername = incomingUsername.replaceAll("\\$email", email);
incomingUsername = incomingUsername.replaceAll("\\$user", userEnc);
incomingUsername = incomingUsername.replaceAll("\\$domain", domain);
URI incomingUriTemplate = mProvider.incomingUriTemplate;
URI incomingUri = new URI(incomingUriTemplate.getScheme(), incomingUsername + ":" + passwordEnc,
incomingUriTemplate.getHost(), incomingUriTemplate.getPort(), null, null, null);
String outgoingUsername = mProvider.outgoingUsernameTemplate;
URI outgoingUriTemplate = mProvider.outgoingUriTemplate;
URI outgoingUri;
if (outgoingUsername != null) {
outgoingUsername = outgoingUsername.replaceAll("\\$email", email);
outgoingUsername = outgoingUsername.replaceAll("\\$user", userEnc);
outgoingUsername = outgoingUsername.replaceAll("\\$domain", domain);
outgoingUri = new URI(outgoingUriTemplate.getScheme(), outgoingUsername + ":"
+ passwordEnc, outgoingUriTemplate.getHost(), outgoingUriTemplate.getPort(), null,
null, null);
} else {
outgoingUri = new URI(outgoingUriTemplate.getScheme(),
null, outgoingUriTemplate.getHost(), outgoingUriTemplate.getPort(), null,
null, null);
}
if (mAccount == null) {
mAccount = Preferences.getPreferences(this).newAccount();
}
mAccount.setName(getOwnerName());
mAccount.setEmail(email);
mAccount.setStoreUri(incomingUri.toString());
mAccount.setTransportUri(outgoingUri.toString());
mAccount.setDraftsFolderName(getString(R.string.special_mailbox_name_drafts));
mAccount.setTrashFolderName(getString(R.string.special_mailbox_name_trash));
mAccount.setArchiveFolderName(getString(R.string.special_mailbox_name_archive));
// Yahoo! has a special folder for Spam, called "Bulk Mail".
if (incomingUriTemplate.getHost().toLowerCase(Locale.US).endsWith(".yahoo.com")) {
mAccount.setSpamFolderName("Bulk Mail");
} else {
mAccount.setSpamFolderName(getString(R.string.special_mailbox_name_spam));
}
mAccount.setSentFolderName(getString(R.string.special_mailbox_name_sent));
if (incomingUri.toString().startsWith("imap")) {
mAccount.setDeletePolicy(DeletePolicy.ON_DELETE);
} else if (incomingUri.toString().startsWith("pop3")) {
mAccount.setDeletePolicy(DeletePolicy.NEVER);
}
// Check incoming here. Then check outgoing in onActivityResult()
AccountSetupCheckSettings.actionCheckSettings(this, mAccount, CheckDirection.INCOMING);
} catch (URISyntaxException use) {
/*
* If there is some problem with the URI we give up and go on to
* manual setup.
*/
onManualSetup();
}
}
private void onNext() {
if (mClientCertificateCheckBox.isChecked()) {
// Auto-setup doesn't support client certificates.
onManualSetup();
return;
}
String email = mEmailView.getText().toString();
String[] emailParts = splitEmail(email);
String domain = emailParts[1];
mProvider = findProviderForDomain(domain);
if (mProvider == null) {
/*
* We don't have default settings for this account, start the manual
* setup process.
*/
onManualSetup();
return;
}
if (mProvider.note != null) {
showDialog(DIALOG_NOTE);
} else {
finishAutoSetup();
}
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode == RESULT_OK) {
if (!mCheckedIncoming) {
//We've successfully checked incoming. Now check outgoing.
mCheckedIncoming = true;
AccountSetupCheckSettings.actionCheckSettings(this, mAccount, CheckDirection.OUTGOING);
} else {
//We've successfully checked outgoing as well.
mAccount.setDescription(mAccount.getEmail());
mAccount.save(Preferences.getPreferences(this));
K9.setServicesEnabled(this);
AccountSetupNames.actionSetNames(this, mAccount);
finish();
}
}
}
private void onManualSetup() {
String email = mEmailView.getText().toString();
String[] emailParts = splitEmail(email);
String user = emailParts[0];
String domain = emailParts[1];
String password = null;
String clientCertificateAlias = null;
AuthType authenticationType;
if (mClientCertificateCheckBox.isChecked()) {
authenticationType = AuthType.EXTERNAL;
clientCertificateAlias = mClientCertificateSpinner.getAlias();
} else {
authenticationType = AuthType.PLAIN;
password = mPasswordView.getText().toString();
}
if (mAccount == null) {
mAccount = Preferences.getPreferences(this).newAccount();
}
mAccount.setName(getOwnerName());
mAccount.setEmail(email);
// set default uris
// NOTE: they will be changed again in AccountSetupAccountType!
ServerSettings storeServer = new ServerSettings(ImapStore.STORE_TYPE, "mail." + domain, -1,
ConnectionSecurity.SSL_TLS_REQUIRED, authenticationType, user, password, clientCertificateAlias);
ServerSettings transportServer = new ServerSettings(SmtpTransport.TRANSPORT_TYPE, "mail." + domain, -1,
ConnectionSecurity.SSL_TLS_REQUIRED, authenticationType, user, password, clientCertificateAlias);
String storeUri = RemoteStore.createStoreUri(storeServer);
String transportUri = Transport.createTransportUri(transportServer);
mAccount.setStoreUri(storeUri);
mAccount.setTransportUri(transportUri);
mAccount.setDraftsFolderName(getString(R.string.special_mailbox_name_drafts));
mAccount.setTrashFolderName(getString(R.string.special_mailbox_name_trash));
mAccount.setSentFolderName(getString(R.string.special_mailbox_name_sent));
mAccount.setArchiveFolderName(getString(R.string.special_mailbox_name_archive));
// Yahoo! has a special folder for Spam, called "Bulk Mail".
if (domain.endsWith(".yahoo.com")) {
mAccount.setSpamFolderName("Bulk Mail");
} else {
mAccount.setSpamFolderName(getString(R.string.special_mailbox_name_spam));
}
AccountSetupAccountType.actionSelectAccountType(this, mAccount, false);
finish();
}
public void onClick(View v) {
switch (v.getId()) {
case R.id.next:
onNext();
break;
case R.id.manual_setup:
onManualSetup();
break;
}
}
/**
* Attempts to get the given attribute as a String resource first, and if it fails
* returns the attribute as a simple String value.
* @param xml
* @param name
* @return
*/
private String getXmlAttribute(XmlResourceParser xml, String name) {
int resId = xml.getAttributeResourceValue(null, name, 0);
if (resId == 0) {
return xml.getAttributeValue(null, name);
} else {
return getString(resId);
}
}
private Provider findProviderForDomain(String domain) {
try {
XmlResourceParser xml = getResources().getXml(R.xml.providers);
int xmlEventType;
Provider provider = null;
while ((xmlEventType = xml.next()) != XmlResourceParser.END_DOCUMENT) {
if (xmlEventType == XmlResourceParser.START_TAG
&& "provider".equals(xml.getName())
&& domain.equalsIgnoreCase(getXmlAttribute(xml, "domain"))) {
provider = new Provider();
provider.id = getXmlAttribute(xml, "id");
provider.label = getXmlAttribute(xml, "label");
provider.domain = getXmlAttribute(xml, "domain");
provider.note = getXmlAttribute(xml, "note");
} else if (xmlEventType == XmlResourceParser.START_TAG
&& "incoming".equals(xml.getName())
&& provider != null) {
provider.incomingUriTemplate = new URI(getXmlAttribute(xml, "uri"));
provider.incomingUsernameTemplate = getXmlAttribute(xml, "username");
} else if (xmlEventType == XmlResourceParser.START_TAG
&& "outgoing".equals(xml.getName())
&& provider != null) {
provider.outgoingUriTemplate = new URI(getXmlAttribute(xml, "uri"));
provider.outgoingUsernameTemplate = getXmlAttribute(xml, "username");
} else if (xmlEventType == XmlResourceParser.END_TAG
&& "provider".equals(xml.getName())
&& provider != null) {
return provider;
}
}
} catch (Exception e) {
Log.e(K9.LOG_TAG, "Error while trying to load provider settings.", e);
}
return null;
}
private String[] splitEmail(String email) {
String[] retParts = new String[2];
String[] emailParts = email.split("@");
retParts[0] = (emailParts.length > 0) ? emailParts[0] : "";
retParts[1] = (emailParts.length > 1) ? emailParts[1] : "";
return retParts;
}
static class Provider implements Serializable {
private static final long serialVersionUID = 8511656164616538989L;
public String id;
public String label;
public String domain;
public URI incomingUriTemplate;
public String incomingUsernameTemplate;
public URI outgoingUriTemplate;
public String outgoingUsernameTemplate;
public String note;
}
}