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.AccountBackupRestore;
     20 import com.android.email.Email;
     21 import com.android.email.EmailAddressValidator;
     22 import com.android.email.R;
     23 import com.android.email.Utility;
     24 import com.android.email.VendorPolicyLoader;
     25 import com.android.email.activity.Debug;
     26 import com.android.email.activity.MessageList;
     27 import com.android.email.activity.setup.AccountSettingsUtils.Provider;
     28 import com.android.email.provider.EmailContent;
     29 import com.android.email.provider.EmailContent.Account;
     30 import com.android.email.provider.EmailContent.Mailbox;
     31 
     32 import android.app.Activity;
     33 import android.app.AlertDialog;
     34 import android.app.Dialog;
     35 import android.content.Context;
     36 import android.content.DialogInterface;
     37 import android.content.Intent;
     38 import android.database.Cursor;
     39 import android.os.Bundle;
     40 import android.text.Editable;
     41 import android.text.TextWatcher;
     42 import android.view.View;
     43 import android.view.View.OnClickListener;
     44 import android.widget.Button;
     45 import android.widget.CheckBox;
     46 import android.widget.EditText;
     47 import android.widget.TextView;
     48 import android.widget.Toast;
     49 
     50 import java.net.URI;
     51 import java.net.URISyntaxException;
     52 
     53 /**
     54  * Prompts the user for the email address and password. Also prompts for
     55  * "Use this account as default" if this is the 2nd+ account being set up.
     56  * Attempts to lookup default settings for the domain the user specified. If the
     57  * domain is known the settings are handed off to the AccountSetupCheckSettings
     58  * activity. If no settings are found the settings are handed off to the
     59  * AccountSetupAccountType activity.
     60  */
     61 public class AccountSetupBasics extends Activity
     62         implements OnClickListener, TextWatcher {
     63     private final static boolean ENTER_DEBUG_SCREEN = true;
     64 
     65     private final static String EXTRA_ACCOUNT = "com.android.email.AccountSetupBasics.account";
     66     public final static String EXTRA_EAS_FLOW = "com.android.email.extra.eas_flow";
     67 
     68     // Action asking us to return to our original caller (i.e. finish)
     69     private static final String ACTION_RETURN_TO_CALLER =
     70         "com.android.email.AccountSetupBasics.return";
     71     // Action asking us to restart the task from the Welcome activity (which will figure out the
     72     // right place to go) and finish
     73     private static final String ACTION_START_AT_MESSAGE_LIST =
     74         "com.android.email.AccountSetupBasics.messageList";
     75 
     76     private final static String EXTRA_USERNAME = "com.android.email.AccountSetupBasics.username";
     77     private final static String EXTRA_PASSWORD = "com.android.email.AccountSetupBasics.password";
     78 
     79     private final static int DIALOG_NOTE = 1;
     80     private final static int DIALOG_DUPLICATE_ACCOUNT = 2;
     81 
     82     private final static String STATE_KEY_PROVIDER =
     83         "com.android.email.AccountSetupBasics.provider";
     84 
     85     // NOTE: If you change this value, confirm that the new interval exists in arrays.xml
     86     private final static int DEFAULT_ACCOUNT_CHECK_INTERVAL = 15;
     87 
     88     private EditText mEmailView;
     89     private EditText mPasswordView;
     90     private CheckBox mDefaultView;
     91     private Button mNextButton;
     92     private Button mManualSetupButton;
     93     private EmailContent.Account mAccount;
     94     private Provider mProvider;
     95     private boolean mEasFlowMode;
     96     private String mDuplicateAccountName;
     97 
     98     private EmailAddressValidator mEmailValidator = new EmailAddressValidator();
     99 
    100     public static void actionNewAccount(Activity fromActivity) {
    101         Intent i = new Intent(fromActivity, AccountSetupBasics.class);
    102         fromActivity.startActivity(i);
    103     }
    104 
    105     public static void actionNewAccountWithCredentials(Activity fromActivity,
    106             String username, String password, boolean easFlow) {
    107         Intent i = new Intent(fromActivity, AccountSetupBasics.class);
    108         i.putExtra(EXTRA_USERNAME, username);
    109         i.putExtra(EXTRA_PASSWORD, password);
    110         i.putExtra(EXTRA_EAS_FLOW, easFlow);
    111         fromActivity.startActivity(i);
    112     }
    113 
    114     /**
    115      * This creates an intent that can be used to start a self-contained account creation flow
    116      * for exchange accounts.
    117      */
    118     public static Intent actionSetupExchangeIntent(Context context) {
    119         Intent i = new Intent(context, AccountSetupBasics.class);
    120         i.putExtra(EXTRA_EAS_FLOW, true);
    121         return i;
    122     }
    123 
    124     public static void actionAccountCreateFinishedEas(Activity fromActivity) {
    125         Intent i= new Intent(fromActivity, AccountSetupBasics.class);
    126         // If we're in the "eas flow" (from AccountManager), we want to return to the caller
    127         // (in the settings app)
    128         i.putExtra(AccountSetupBasics.ACTION_RETURN_TO_CALLER, true);
    129         i.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
    130         fromActivity.startActivity(i);
    131     }
    132 
    133     public static void actionAccountCreateFinished(Activity fromActivity, long accountId) {
    134         Intent i= new Intent(fromActivity, AccountSetupBasics.class);
    135         // If we're not in the "eas flow" (from AccountManager), we want to show the message list
    136         // for the new inbox
    137         i.putExtra(AccountSetupBasics.ACTION_START_AT_MESSAGE_LIST, accountId);
    138         i.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
    139         fromActivity.startActivity(i);
    140     }
    141 
    142     @Override
    143     public void onCreate(Bundle savedInstanceState) {
    144         super.onCreate(savedInstanceState);
    145 
    146         Intent intent = getIntent();
    147         if (intent.getBooleanExtra(ACTION_RETURN_TO_CALLER, false)) {
    148             // Return to the caller who initiated account creation
    149             finish();
    150             return;
    151         } else {
    152             long accountId = intent.getLongExtra(ACTION_START_AT_MESSAGE_LIST, -1);
    153             if (accountId >= 0) {
    154                 // Show the message list for the new account
    155                 MessageList.actionHandleAccount(this, accountId, Mailbox.TYPE_INBOX);
    156                 finish();
    157                 return;
    158             }
    159         }
    160 
    161         setContentView(R.layout.account_setup_basics);
    162 
    163         mEmailView = (EditText)findViewById(R.id.account_email);
    164         mPasswordView = (EditText)findViewById(R.id.account_password);
    165         mDefaultView = (CheckBox)findViewById(R.id.account_default);
    166         mNextButton = (Button)findViewById(R.id.next);
    167         mManualSetupButton = (Button)findViewById(R.id.manual_setup);
    168 
    169         mNextButton.setOnClickListener(this);
    170         mManualSetupButton.setOnClickListener(this);
    171 
    172         mEmailView.addTextChangedListener(this);
    173         mPasswordView.addTextChangedListener(this);
    174 
    175         // Find out how many accounts we have, and if there one or more, then we have a choice
    176         // about being default or not.
    177         Cursor c = null;
    178         try {
    179             c = getContentResolver().query(
    180                     EmailContent.Account.CONTENT_URI,
    181                     EmailContent.Account.ID_PROJECTION,
    182                     null, null, null);
    183             if (c.getCount() > 0) {
    184                 mDefaultView.setVisibility(View.VISIBLE);
    185             }
    186         } finally {
    187             if (c != null) {
    188                 c.close();
    189             }
    190         }
    191 
    192         mEasFlowMode = intent.getBooleanExtra(EXTRA_EAS_FLOW, false);
    193         if (mEasFlowMode) {
    194             // No need for manual button -> next is appropriate
    195             mManualSetupButton.setVisibility(View.GONE);
    196             // Swap welcome text for EAS-specific text
    197             TextView welcomeView = (TextView) findViewById(R.id.instructions);
    198             final boolean alternateStrings =
    199                     VendorPolicyLoader.getInstance(this).useAlternateExchangeStrings();
    200             setTitle(alternateStrings
    201                     ? R.string.account_setup_basics_exchange_title_alternate
    202                     : R.string.account_setup_basics_exchange_title);
    203             welcomeView.setText(alternateStrings
    204                     ? R.string.accounts_welcome_exchange_alternate
    205                     : R.string.accounts_welcome_exchange);
    206         }
    207 
    208         if (intent.hasExtra(EXTRA_USERNAME)) {
    209             mEmailView.setText(intent.getStringExtra(EXTRA_USERNAME));
    210         }
    211         if (intent.hasExtra(EXTRA_PASSWORD)) {
    212             mPasswordView.setText(intent.getStringExtra(EXTRA_PASSWORD));
    213         }
    214 
    215         if (savedInstanceState != null && savedInstanceState.containsKey(EXTRA_ACCOUNT)) {
    216             mAccount = (EmailContent.Account)savedInstanceState.getParcelable(EXTRA_ACCOUNT);
    217         }
    218 
    219         if (savedInstanceState != null && savedInstanceState.containsKey(STATE_KEY_PROVIDER)) {
    220             mProvider = (Provider)savedInstanceState.getSerializable(STATE_KEY_PROVIDER);
    221         }
    222     }
    223 
    224     @Override
    225     public void onResume() {
    226         super.onResume();
    227         validateFields();
    228     }
    229 
    230     @Override
    231     public void onSaveInstanceState(Bundle outState) {
    232         super.onSaveInstanceState(outState);
    233         outState.putParcelable(EXTRA_ACCOUNT, mAccount);
    234         if (mProvider != null) {
    235             outState.putSerializable(STATE_KEY_PROVIDER, mProvider);
    236         }
    237     }
    238 
    239     public void afterTextChanged(Editable s) {
    240         validateFields();
    241     }
    242 
    243     public void beforeTextChanged(CharSequence s, int start, int count, int after) {
    244     }
    245 
    246     public void onTextChanged(CharSequence s, int start, int before, int count) {
    247     }
    248 
    249     private void validateFields() {
    250         boolean valid = Utility.requiredFieldValid(mEmailView)
    251                 && Utility.requiredFieldValid(mPasswordView)
    252                 && mEmailValidator.isValid(mEmailView.getText().toString().trim());
    253         mNextButton.setEnabled(valid);
    254         mManualSetupButton.setEnabled(valid);
    255         /*
    256          * Dim the next button's icon to 50% if the button is disabled.
    257          * TODO this can probably be done with a stateful drawable. Check into it.
    258          * android:state_enabled
    259          */
    260         Utility.setCompoundDrawablesAlpha(mNextButton, mNextButton.isEnabled() ? 255 : 128);
    261     }
    262 
    263     private String getOwnerName() {
    264         String name = null;
    265 /* TODO figure out another way to get the owner name
    266         String projection[] = {
    267             ContactMethods.NAME
    268         };
    269         Cursor c = getContentResolver().query(
    270                 Uri.withAppendedPath(Contacts.People.CONTENT_URI, "owner"), projection, null, null,
    271                 null);
    272         if (c != null) {
    273             if (c.moveToFirst()) {
    274                 name = c.getString(0);
    275             }
    276             c.close();
    277         }
    278 */
    279 
    280         if (name == null || name.length() == 0) {
    281             long defaultId = Account.getDefaultAccountId(this);
    282             if (defaultId != -1) {
    283                 Account account = Account.restoreAccountWithId(this, defaultId);
    284                 if (account != null) {
    285                     name = account.getSenderName();
    286                 }
    287             }
    288         }
    289         return name;
    290     }
    291 
    292     @Override
    293     public Dialog onCreateDialog(int id) {
    294         if (id == DIALOG_NOTE) {
    295             if (mProvider != null && mProvider.note != null) {
    296                 return new AlertDialog.Builder(this)
    297                 .setIcon(android.R.drawable.ic_dialog_alert)
    298                 .setTitle(android.R.string.dialog_alert_title)
    299                 .setMessage(mProvider.note)
    300                 .setPositiveButton(
    301                         getString(R.string.okay_action),
    302                         new DialogInterface.OnClickListener() {
    303                             public void onClick(DialogInterface dialog, int which) {
    304                                 finishAutoSetup();
    305                             }
    306                         })
    307                         .setNegativeButton(
    308                                 getString(R.string.cancel_action),
    309                                 null)
    310                                 .create();
    311             }
    312         } else if (id == DIALOG_DUPLICATE_ACCOUNT) {
    313             return new AlertDialog.Builder(this)
    314             .setIcon(android.R.drawable.ic_dialog_alert)
    315             .setTitle(R.string.account_duplicate_dlg_title)
    316             .setMessage(getString(R.string.account_duplicate_dlg_message_fmt,
    317                     mDuplicateAccountName))
    318                     .setPositiveButton(R.string.okay_action,
    319                             new DialogInterface.OnClickListener() {
    320                         public void onClick(DialogInterface dialog, int which) {
    321                             dismissDialog(DIALOG_DUPLICATE_ACCOUNT);
    322                         }
    323                     })
    324                     .create();
    325         }
    326         return null;
    327     }
    328 
    329     /**
    330      * Update a cached dialog with current values (e.g. account name)
    331      */
    332     @Override
    333     public void onPrepareDialog(int id, Dialog dialog) {
    334         switch (id) {
    335             case DIALOG_NOTE:
    336                 if (mProvider != null && mProvider.note != null) {
    337                     AlertDialog alert = (AlertDialog) dialog;
    338                     alert.setMessage(mProvider.note);
    339                 }
    340                 break;
    341             case DIALOG_DUPLICATE_ACCOUNT:
    342                 if (mDuplicateAccountName != null) {
    343                     AlertDialog alert = (AlertDialog) dialog;
    344                     alert.setMessage(getString(R.string.account_duplicate_dlg_message_fmt,
    345                             mDuplicateAccountName));
    346                 }
    347                 break;
    348         }
    349     }
    350 
    351     private void finishAutoSetup() {
    352         String email = mEmailView.getText().toString().trim();
    353         String password = mPasswordView.getText().toString();
    354         String[] emailParts = email.split("@");
    355         String user = emailParts[0];
    356         String domain = emailParts[1];
    357         URI incomingUri = null;
    358         URI outgoingUri = null;
    359         try {
    360             String incomingUsername = mProvider.incomingUsernameTemplate;
    361             incomingUsername = incomingUsername.replaceAll("\\$email", email);
    362             incomingUsername = incomingUsername.replaceAll("\\$user", user);
    363             incomingUsername = incomingUsername.replaceAll("\\$domain", domain);
    364 
    365             URI incomingUriTemplate = mProvider.incomingUriTemplate;
    366             incomingUri = new URI(incomingUriTemplate.getScheme(), incomingUsername + ":"
    367                     + password, incomingUriTemplate.getHost(), incomingUriTemplate.getPort(),
    368                     incomingUriTemplate.getPath(), null, null);
    369 
    370             String outgoingUsername = mProvider.outgoingUsernameTemplate;
    371             outgoingUsername = outgoingUsername.replaceAll("\\$email", email);
    372             outgoingUsername = outgoingUsername.replaceAll("\\$user", user);
    373             outgoingUsername = outgoingUsername.replaceAll("\\$domain", domain);
    374 
    375             URI outgoingUriTemplate = mProvider.outgoingUriTemplate;
    376             outgoingUri = new URI(outgoingUriTemplate.getScheme(), outgoingUsername + ":"
    377                     + password, outgoingUriTemplate.getHost(), outgoingUriTemplate.getPort(),
    378                     outgoingUriTemplate.getPath(), null, null);
    379 
    380             // Stop here if the login credentials duplicate an existing account
    381             mDuplicateAccountName = Utility.findDuplicateAccount(this, -1,
    382                     incomingUri.getHost(), incomingUsername);
    383             if (mDuplicateAccountName != null) {
    384                 this.showDialog(DIALOG_DUPLICATE_ACCOUNT);
    385                 return;
    386             }
    387 
    388         } catch (URISyntaxException use) {
    389             /*
    390              * If there is some problem with the URI we give up and go on to
    391              * manual setup.  Technically speaking, AutoDiscover is OK here, since user clicked
    392              * "Next" to get here.  This would never happen in practice because we don't expect
    393              * to find any EAS accounts in the providers list.
    394              */
    395             onManualSetup(true);
    396             return;
    397         }
    398 
    399         mAccount = new EmailContent.Account();
    400         mAccount.setSenderName(getOwnerName());
    401         mAccount.setEmailAddress(email);
    402         mAccount.setStoreUri(this, incomingUri.toString());
    403         mAccount.setSenderUri(this, outgoingUri.toString());
    404 /* TODO figure out the best way to implement this concept
    405         mAccount.setDraftsFolderName(getString(R.string.special_mailbox_name_drafts));
    406         mAccount.setTrashFolderName(getString(R.string.special_mailbox_name_trash));
    407         mAccount.setOutboxFolderName(getString(R.string.special_mailbox_name_outbox));
    408         mAccount.setSentFolderName(getString(R.string.special_mailbox_name_sent));
    409 */
    410         if (incomingUri.toString().startsWith("imap")) {
    411             // Delete policy must be set explicitly, because IMAP does not provide a UI selection
    412             // for it. This logic needs to be followed in the auto setup flow as well.
    413             mAccount.setDeletePolicy(EmailContent.Account.DELETE_POLICY_ON_DELETE);
    414         }
    415         mAccount.setSyncInterval(DEFAULT_ACCOUNT_CHECK_INTERVAL);
    416         AccountSetupCheckSettings.actionValidateSettings(this, mAccount, true, true);
    417     }
    418 
    419     private void onNext() {
    420         // If this is EAS flow, don't try to find a provider for the domain!
    421         if (!mEasFlowMode) {
    422             String email = mEmailView.getText().toString().trim();
    423             String[] emailParts = email.split("@");
    424             String domain = emailParts[1].trim();
    425             mProvider = AccountSettingsUtils.findProviderForDomain(this, domain);
    426             if (mProvider != null) {
    427                 if (mProvider.note != null) {
    428                     showDialog(DIALOG_NOTE);
    429                 } else {
    430                     finishAutoSetup();
    431                 }
    432                 return;
    433             }
    434         }
    435         // Can't use auto setup (although EAS accounts may still be able to AutoDiscover)
    436         onManualSetup(true);
    437     }
    438 
    439     /**
    440      * This is used in automatic setup mode to jump directly down to the names screen.
    441      *
    442      * NOTE:  With this organization, it is *not* possible to auto-create an exchange account,
    443      * because certain necessary actions happen during AccountSetupOptions (which we are
    444      * skipping here).
    445      */
    446     @Override
    447     public void onActivityResult(int requestCode, int resultCode, Intent data) {
    448         if (resultCode == RESULT_OK) {
    449             String email = mAccount.getEmailAddress();
    450             boolean isDefault = mDefaultView.isChecked();
    451             mAccount.setDisplayName(email);
    452             mAccount.setDefaultAccount(isDefault);
    453             // At this point we write the Account object to the DB for the first time.
    454             // From now on we'll only pass the accountId around.
    455             mAccount.save(this);
    456             // Update the backup (side copy) of the accounts
    457             AccountBackupRestore.backupAccounts(this);
    458             Email.setServicesEnabled(this);
    459             AccountSetupNames.actionSetNames(this, mAccount.mId, false);
    460             finish();
    461         }
    462     }
    463 
    464     /**
    465      * @param allowAutoDiscover - true if the user clicked 'next' and (if the account is EAS)
    466      * it's OK to use autodiscover.  false to prevent autodiscover and go straight to manual setup.
    467      * Ignored for IMAP & POP accounts.
    468      */
    469     private void onManualSetup(boolean allowAutoDiscover) {
    470         String email = mEmailView.getText().toString().trim();
    471         String password = mPasswordView.getText().toString();
    472         String[] emailParts = email.split("@");
    473         String user = emailParts[0].trim();
    474         String domain = emailParts[1].trim();
    475 
    476         // Alternate entry to the debug options screen (for devices without a physical keyboard:
    477         //  Username: d (at) d.d
    478         //  Password: debug
    479         if (ENTER_DEBUG_SCREEN && "d (at) d.d".equals(email) && "debug".equals(password)) {
    480             mEmailView.setText("");
    481             mPasswordView.setText("");
    482             startActivity(new Intent(this, Debug.class));
    483             return;
    484         }
    485 
    486         mAccount = new EmailContent.Account();
    487         mAccount.setSenderName(getOwnerName());
    488         mAccount.setEmailAddress(email);
    489         try {
    490             URI uri = new URI("placeholder", user + ":" + password, domain, -1, null, null, null);
    491             mAccount.setStoreUri(this, uri.toString());
    492             mAccount.setSenderUri(this, uri.toString());
    493         } catch (URISyntaxException use) {
    494             // If we can't set up the URL, don't continue - account setup pages will fail too
    495             Toast.makeText(this, R.string.account_setup_username_password_toast, Toast.LENGTH_LONG)
    496                     .show();
    497             mAccount = null;
    498             return;
    499         }
    500 /* TODO figure out the best way to implement this concept
    501         mAccount.setDraftsFolderName(getString(R.string.special_mailbox_name_drafts));
    502         mAccount.setTrashFolderName(getString(R.string.special_mailbox_name_trash));
    503         mAccount.setOutboxFolderName(getString(R.string.special_mailbox_name_outbox));
    504         mAccount.setSentFolderName(getString(R.string.special_mailbox_name_sent));
    505 */
    506         mAccount.setSyncInterval(DEFAULT_ACCOUNT_CHECK_INTERVAL);
    507 
    508         AccountSetupAccountType.actionSelectAccountType(this, mAccount, mDefaultView.isChecked(),
    509                 mEasFlowMode, allowAutoDiscover);
    510     }
    511 
    512     public void onClick(View v) {
    513         switch (v.getId()) {
    514             case R.id.next:
    515                 onNext();
    516                 break;
    517             case R.id.manual_setup:
    518                 // no AutoDiscover - user clicked "manual"
    519                 onManualSetup(false);
    520                 break;
    521         }
    522     }
    523 }
    524