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; } }