Home | History | Annotate | Download | only in setup
      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.AccountBackupRestore;
     21 import com.android.email.R;
     22 import com.android.email.Utility;
     23 import com.android.email.provider.EmailContent;
     24 
     25 import android.app.Activity;
     26 import android.app.AlertDialog;
     27 import android.app.Dialog;
     28 import android.content.DialogInterface;
     29 import android.content.Intent;
     30 import android.os.Bundle;
     31 import android.text.Editable;
     32 import android.text.TextWatcher;
     33 import android.text.method.DigitsKeyListener;
     34 import android.view.View;
     35 import android.view.View.OnClickListener;
     36 import android.widget.AdapterView;
     37 import android.widget.ArrayAdapter;
     38 import android.widget.Button;
     39 import android.widget.EditText;
     40 import android.widget.Spinner;
     41 import android.widget.TextView;
     42 
     43 import java.net.URI;
     44 import java.net.URISyntaxException;
     45 
     46 public class AccountSetupIncoming extends Activity implements OnClickListener {
     47     private static final String EXTRA_ACCOUNT = "account";
     48     private static final String EXTRA_MAKE_DEFAULT = "makeDefault";
     49 
     50     private static final int POP_PORTS[] = {
     51             110, 995, 995, 110, 110
     52     };
     53     private static final String POP_SCHEMES[] = {
     54             "pop3", "pop3+ssl+", "pop3+ssl+trustallcerts", "pop3+tls+", "pop3+tls+trustallcerts"
     55     };
     56     private static final int IMAP_PORTS[] = {
     57             143, 993, 993, 143, 143
     58     };
     59     private static final String IMAP_SCHEMES[] = {
     60             "imap", "imap+ssl+", "imap+ssl+trustallcerts", "imap+tls+", "imap+tls+trustallcerts"
     61     };
     62 
     63     private final static int DIALOG_DUPLICATE_ACCOUNT = 1;
     64 
     65     private int mAccountPorts[];
     66     private String mAccountSchemes[];
     67     private EditText mUsernameView;
     68     private EditText mPasswordView;
     69     private EditText mServerView;
     70     private EditText mPortView;
     71     private Spinner mSecurityTypeView;
     72     private Spinner mDeletePolicyView;
     73     private EditText mImapPathPrefixView;
     74     private Button mNextButton;
     75     private EmailContent.Account mAccount;
     76     private boolean mMakeDefault;
     77     private String mCacheLoginCredential;
     78     private String mDuplicateAccountName;
     79 
     80     public static void actionIncomingSettings(Activity fromActivity, EmailContent.Account account,
     81             boolean makeDefault) {
     82         Intent i = new Intent(fromActivity, AccountSetupIncoming.class);
     83         i.putExtra(EXTRA_ACCOUNT, account);
     84         i.putExtra(EXTRA_MAKE_DEFAULT, makeDefault);
     85         fromActivity.startActivity(i);
     86     }
     87 
     88     public static void actionEditIncomingSettings(Activity fromActivity, EmailContent.Account account)
     89             {
     90         Intent i = new Intent(fromActivity, AccountSetupIncoming.class);
     91         i.setAction(Intent.ACTION_EDIT);
     92         i.putExtra(EXTRA_ACCOUNT, account);
     93         fromActivity.startActivity(i);
     94     }
     95 
     96     @Override
     97     public void onCreate(Bundle savedInstanceState) {
     98         super.onCreate(savedInstanceState);
     99         setContentView(R.layout.account_setup_incoming);
    100 
    101         mUsernameView = (EditText)findViewById(R.id.account_username);
    102         mPasswordView = (EditText)findViewById(R.id.account_password);
    103         TextView serverLabelView = (TextView) findViewById(R.id.account_server_label);
    104         mServerView = (EditText)findViewById(R.id.account_server);
    105         mPortView = (EditText)findViewById(R.id.account_port);
    106         mSecurityTypeView = (Spinner)findViewById(R.id.account_security_type);
    107         mDeletePolicyView = (Spinner)findViewById(R.id.account_delete_policy);
    108         mImapPathPrefixView = (EditText)findViewById(R.id.imap_path_prefix);
    109         mNextButton = (Button)findViewById(R.id.next);
    110 
    111         mNextButton.setOnClickListener(this);
    112 
    113         SpinnerOption securityTypes[] = {
    114             new SpinnerOption(0, getString(R.string.account_setup_incoming_security_none_label)),
    115             new SpinnerOption(1, getString(R.string.account_setup_incoming_security_ssl_label)),
    116             new SpinnerOption(2, getString(
    117                     R.string.account_setup_incoming_security_ssl_trust_certificates_label)),
    118             new SpinnerOption(3, getString(R.string.account_setup_incoming_security_tls_label)),
    119             new SpinnerOption(4, getString(
    120                     R.string.account_setup_incoming_security_tls_trust_certificates_label)),
    121         };
    122 
    123         SpinnerOption deletePolicies[] = {
    124                 new SpinnerOption(Account.DELETE_POLICY_NEVER,
    125                         getString(R.string.account_setup_incoming_delete_policy_never_label)),
    126                 new SpinnerOption(Account.DELETE_POLICY_ON_DELETE,
    127                         getString(R.string.account_setup_incoming_delete_policy_delete_label)),
    128         };
    129 
    130         ArrayAdapter<SpinnerOption> securityTypesAdapter = new ArrayAdapter<SpinnerOption>(this,
    131                 android.R.layout.simple_spinner_item, securityTypes);
    132         securityTypesAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
    133         mSecurityTypeView.setAdapter(securityTypesAdapter);
    134 
    135         ArrayAdapter<SpinnerOption> deletePoliciesAdapter = new ArrayAdapter<SpinnerOption>(this,
    136                 android.R.layout.simple_spinner_item, deletePolicies);
    137         deletePoliciesAdapter
    138                 .setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
    139         mDeletePolicyView.setAdapter(deletePoliciesAdapter);
    140 
    141         /*
    142          * Updates the port when the user changes the security type. This allows
    143          * us to show a reasonable default which the user can change.
    144          */
    145         mSecurityTypeView.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
    146             public void onItemSelected(AdapterView arg0, View arg1, int arg2, long arg3) {
    147                 updatePortFromSecurityType();
    148             }
    149 
    150             public void onNothingSelected(AdapterView<?> arg0) {
    151             }
    152         });
    153 
    154         /*
    155          * Calls validateFields() which enables or disables the Next button
    156          * based on the fields' validity.
    157          */
    158         TextWatcher validationTextWatcher = new TextWatcher() {
    159             public void afterTextChanged(Editable s) {
    160                 validateFields();
    161             }
    162 
    163             public void beforeTextChanged(CharSequence s, int start, int count, int after) {
    164             }
    165 
    166             public void onTextChanged(CharSequence s, int start, int before, int count) {
    167             }
    168         };
    169         mUsernameView.addTextChangedListener(validationTextWatcher);
    170         mPasswordView.addTextChangedListener(validationTextWatcher);
    171         mServerView.addTextChangedListener(validationTextWatcher);
    172         mPortView.addTextChangedListener(validationTextWatcher);
    173 
    174         /*
    175          * Only allow digits in the port field.
    176          */
    177         mPortView.setKeyListener(DigitsKeyListener.getInstance("0123456789"));
    178 
    179         mAccount = (EmailContent.Account)getIntent().getParcelableExtra(EXTRA_ACCOUNT);
    180         mMakeDefault = getIntent().getBooleanExtra(EXTRA_MAKE_DEFAULT, false);
    181 
    182         /*
    183          * If we're being reloaded we override the original account with the one
    184          * we saved
    185          */
    186         if (savedInstanceState != null && savedInstanceState.containsKey(EXTRA_ACCOUNT)) {
    187             mAccount = (EmailContent.Account)savedInstanceState.getParcelable(EXTRA_ACCOUNT);
    188         }
    189 
    190         try {
    191             // TODO this should be accessed directly via the HostAuth structure
    192             URI uri = new URI(mAccount.getStoreUri(this));
    193             String username = null;
    194             String password = null;
    195             if (uri.getUserInfo() != null) {
    196                 String[] userInfoParts = uri.getUserInfo().split(":", 2);
    197                 username = userInfoParts[0];
    198                 if (userInfoParts.length > 1) {
    199                     password = userInfoParts[1];
    200                 }
    201             }
    202 
    203             if (username != null) {
    204                 mUsernameView.setText(username);
    205             }
    206 
    207             if (password != null) {
    208                 mPasswordView.setText(password);
    209             }
    210 
    211             if (uri.getScheme().startsWith("pop3")) {
    212                 serverLabelView.setText(R.string.account_setup_incoming_pop_server_label);
    213                 mAccountPorts = POP_PORTS;
    214                 mAccountSchemes = POP_SCHEMES;
    215 
    216                 findViewById(R.id.imap_path_prefix_section).setVisibility(View.GONE);
    217             } else if (uri.getScheme().startsWith("imap")) {
    218                 serverLabelView.setText(R.string.account_setup_incoming_imap_server_label);
    219                 mAccountPorts = IMAP_PORTS;
    220                 mAccountSchemes = IMAP_SCHEMES;
    221 
    222                 findViewById(R.id.account_delete_policy_label).setVisibility(View.GONE);
    223                 mDeletePolicyView.setVisibility(View.GONE);
    224                 if (uri.getPath() != null && uri.getPath().length() > 0) {
    225                     mImapPathPrefixView.setText(uri.getPath().substring(1));
    226                 }
    227             } else {
    228                 throw new Error("Unknown account type: " + mAccount.getStoreUri(this));
    229             }
    230 
    231             for (int i = 0; i < mAccountSchemes.length; i++) {
    232                 if (mAccountSchemes[i].equals(uri.getScheme())) {
    233                     SpinnerOption.setSpinnerOptionValue(mSecurityTypeView, i);
    234                 }
    235             }
    236 
    237             SpinnerOption.setSpinnerOptionValue(mDeletePolicyView, mAccount.getDeletePolicy());
    238 
    239             if (uri.getHost() != null) {
    240                 mServerView.setText(uri.getHost());
    241             }
    242 
    243             if (uri.getPort() != -1) {
    244                 mPortView.setText(Integer.toString(uri.getPort()));
    245             } else {
    246                 updatePortFromSecurityType();
    247             }
    248         } catch (URISyntaxException use) {
    249             /*
    250              * We should always be able to parse our own settings.
    251              */
    252             throw new Error(use);
    253         }
    254 
    255         validateFields();
    256     }
    257 
    258     @Override
    259     public void onSaveInstanceState(Bundle outState) {
    260         super.onSaveInstanceState(outState);
    261         outState.putParcelable(EXTRA_ACCOUNT, mAccount);
    262     }
    263 
    264     /**
    265      * Prepare a cached dialog with current values (e.g. account name)
    266      */
    267     @Override
    268     public Dialog onCreateDialog(int id) {
    269         switch (id) {
    270             case DIALOG_DUPLICATE_ACCOUNT:
    271                 return new AlertDialog.Builder(this)
    272                     .setIcon(android.R.drawable.ic_dialog_alert)
    273                     .setTitle(R.string.account_duplicate_dlg_title)
    274                     .setMessage(getString(R.string.account_duplicate_dlg_message_fmt,
    275                             mDuplicateAccountName))
    276                     .setPositiveButton(R.string.okay_action,
    277                             new DialogInterface.OnClickListener() {
    278                         public void onClick(DialogInterface dialog, int which) {
    279                             dismissDialog(DIALOG_DUPLICATE_ACCOUNT);
    280                         }
    281                     })
    282                     .create();
    283         }
    284         return null;
    285     }
    286 
    287     /**
    288      * Update a cached dialog with current values (e.g. account name)
    289      */
    290     @Override
    291     public void onPrepareDialog(int id, Dialog dialog) {
    292         switch (id) {
    293             case DIALOG_DUPLICATE_ACCOUNT:
    294                 if (mDuplicateAccountName != null) {
    295                     AlertDialog alert = (AlertDialog) dialog;
    296                     alert.setMessage(getString(R.string.account_duplicate_dlg_message_fmt,
    297                             mDuplicateAccountName));
    298                 }
    299                 break;
    300         }
    301     }
    302 
    303     /**
    304      * Check the values in the fields and decide if it makes sense to enable the "next" button
    305      * NOTE:  Does it make sense to extract & combine with similar code in AccountSetupIncoming?
    306      */
    307     private void validateFields() {
    308         boolean enabled = Utility.requiredFieldValid(mUsernameView)
    309                 && Utility.requiredFieldValid(mPasswordView)
    310                 && Utility.requiredFieldValid(mServerView)
    311                 && Utility.isPortFieldValid(mPortView);
    312         if (enabled) {
    313             try {
    314                 URI uri = getUri();
    315             } catch (URISyntaxException use) {
    316                 enabled = false;
    317             }
    318         }
    319         mNextButton.setEnabled(enabled);
    320         Utility.setCompoundDrawablesAlpha(mNextButton, enabled ? 255 : 128);
    321     }
    322 
    323     private void updatePortFromSecurityType() {
    324         int securityType = (Integer)((SpinnerOption)mSecurityTypeView.getSelectedItem()).value;
    325         mPortView.setText(Integer.toString(mAccountPorts[securityType]));
    326     }
    327 
    328     @Override
    329     public void onActivityResult(int requestCode, int resultCode, Intent data) {
    330         if (resultCode == RESULT_OK) {
    331             if (Intent.ACTION_EDIT.equals(getIntent().getAction())) {
    332                 if (mAccount.isSaved()) {
    333                     mAccount.update(this, mAccount.toContentValues());
    334                     mAccount.mHostAuthRecv.update(this, mAccount.mHostAuthRecv.toContentValues());
    335                 } else {
    336                     mAccount.save(this);
    337                 }
    338                 // Update the backup (side copy) of the accounts
    339                 AccountBackupRestore.backupAccounts(this);
    340                 finish();
    341             } else {
    342                 /*
    343                  * Set the username and password for the outgoing settings to the username and
    344                  * password the user just set for incoming.
    345                  */
    346                 try {
    347                     URI oldUri = new URI(mAccount.getSenderUri(this));
    348                     URI uri = new URI(
    349                             oldUri.getScheme(),
    350                             mUsernameView.getText().toString().trim() + ":"
    351                                     + mPasswordView.getText().toString().trim(),
    352                             oldUri.getHost(),
    353                             oldUri.getPort(),
    354                             null,
    355                             null,
    356                             null);
    357                     mAccount.setSenderUri(this, uri.toString());
    358                 } catch (URISyntaxException use) {
    359                     /*
    360                      * If we can't set up the URL we just continue. It's only for
    361                      * convenience.
    362                      */
    363                 }
    364 
    365                 AccountSetupOutgoing.actionOutgoingSettings(this, mAccount, mMakeDefault);
    366                 finish();
    367             }
    368         }
    369     }
    370 
    371     /**
    372      * Attempt to create a URI from the fields provided.  Throws URISyntaxException if there's
    373      * a problem with the user input.
    374      * @return a URI built from the account setup fields
    375      */
    376     /* package */ URI getUri() throws URISyntaxException {
    377         int securityType = (Integer)((SpinnerOption)mSecurityTypeView.getSelectedItem()).value;
    378         String path = null;
    379         if (mAccountSchemes[securityType].startsWith("imap")) {
    380             path = "/" + mImapPathPrefixView.getText().toString().trim();
    381         }
    382         String userName = mUsernameView.getText().toString().trim();
    383         mCacheLoginCredential = userName;
    384         URI uri = new URI(
    385                 mAccountSchemes[securityType],
    386                 userName + ":" + mPasswordView.getText(),
    387                 mServerView.getText().toString().trim(),
    388                 Integer.parseInt(mPortView.getText().toString().trim()),
    389                 path, // path
    390                 null, // query
    391                 null);
    392 
    393         return uri;
    394     }
    395 
    396     private void onNext() {
    397         try {
    398             URI uri = getUri();
    399             mAccount.setStoreUri(this, uri.toString());
    400 
    401             // Stop here if the login credentials duplicate an existing account
    402             // (unless they duplicate the existing account, as they of course will)
    403             mDuplicateAccountName = Utility.findDuplicateAccount(this, mAccount.mId,
    404                     uri.getHost(), mCacheLoginCredential);
    405             if (mDuplicateAccountName != null) {
    406                 this.showDialog(DIALOG_DUPLICATE_ACCOUNT);
    407                 return;
    408             }
    409         } catch (URISyntaxException use) {
    410             /*
    411              * It's unrecoverable if we cannot create a URI from components that
    412              * we validated to be safe.
    413              */
    414             throw new Error(use);
    415         }
    416 
    417         mAccount.setDeletePolicy((Integer)((SpinnerOption)mDeletePolicyView.getSelectedItem()).value);
    418         AccountSetupCheckSettings.actionValidateSettings(this, mAccount, true, false);
    419     }
    420 
    421     public void onClick(View v) {
    422         switch (v.getId()) {
    423             case R.id.next:
    424                 onNext();
    425                 break;
    426         }
    427     }
    428 }
    429