AccountSetupBasics.java revision 42e3f10a9575e277ba6f121e6cac56ddb02fda12
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
17package com.android.email.activity.setup;
18
19import com.android.email.AccountBackupRestore;
20import com.android.email.Email;
21import com.android.email.EmailAddressValidator;
22import com.android.email.R;
23import com.android.email.Utility;
24import com.android.email.VendorPolicyLoader;
25import com.android.email.activity.Debug;
26import com.android.email.activity.MessageList;
27import com.android.email.activity.setup.AccountSettingsUtils.Provider;
28import com.android.email.provider.EmailContent;
29import com.android.email.provider.EmailContent.Account;
30import com.android.email.provider.EmailContent.Mailbox;
31
32import android.app.Activity;
33import android.app.AlertDialog;
34import android.app.Dialog;
35import android.content.Context;
36import android.content.DialogInterface;
37import android.content.Intent;
38import android.database.Cursor;
39import android.os.Bundle;
40import android.text.Editable;
41import android.text.TextWatcher;
42import android.view.View;
43import android.view.View.OnClickListener;
44import android.widget.Button;
45import android.widget.CheckBox;
46import android.widget.EditText;
47import android.widget.TextView;
48import android.widget.Toast;
49
50import java.net.URI;
51import 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 */
61public 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().trim();
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            Account account = Utility.findExistingAccount(this, -1,
382                    incomingUri.getHost(), incomingUsername);
383            if (account != null) {
384                mDuplicateAccountName = account.mDisplayName;
385                this.showDialog(DIALOG_DUPLICATE_ACCOUNT);
386                return;
387            }
388
389        } catch (URISyntaxException use) {
390            /*
391             * If there is some problem with the URI we give up and go on to
392             * manual setup.  Technically speaking, AutoDiscover is OK here, since user clicked
393             * "Next" to get here.  This would never happen in practice because we don't expect
394             * to find any EAS accounts in the providers list.
395             */
396            onManualSetup(true);
397            return;
398        }
399
400        mAccount = new EmailContent.Account();
401        mAccount.setSenderName(getOwnerName());
402        mAccount.setEmailAddress(email);
403        mAccount.setStoreUri(this, incomingUri.toString());
404        mAccount.setSenderUri(this, outgoingUri.toString());
405/* TODO figure out the best way to implement this concept
406        mAccount.setDraftsFolderName(getString(R.string.special_mailbox_name_drafts));
407        mAccount.setTrashFolderName(getString(R.string.special_mailbox_name_trash));
408        mAccount.setOutboxFolderName(getString(R.string.special_mailbox_name_outbox));
409        mAccount.setSentFolderName(getString(R.string.special_mailbox_name_sent));
410*/
411        if (incomingUri.toString().startsWith("imap")) {
412            // Delete policy must be set explicitly, because IMAP does not provide a UI selection
413            // for it. This logic needs to be followed in the auto setup flow as well.
414            mAccount.setDeletePolicy(EmailContent.Account.DELETE_POLICY_ON_DELETE);
415        }
416        mAccount.setSyncInterval(DEFAULT_ACCOUNT_CHECK_INTERVAL);
417        AccountSetupCheckSettings.actionValidateSettings(this, mAccount, true, true);
418    }
419
420    private void onNext() {
421        // If this is EAS flow, don't try to find a provider for the domain!
422        if (!mEasFlowMode) {
423            String email = mEmailView.getText().toString().trim();
424            String[] emailParts = email.split("@");
425            String domain = emailParts[1].trim();
426            mProvider = AccountSettingsUtils.findProviderForDomain(this, domain);
427            if (mProvider != null) {
428                if (mProvider.note != null) {
429                    showDialog(DIALOG_NOTE);
430                } else {
431                    finishAutoSetup();
432                }
433                return;
434            }
435        }
436        // Can't use auto setup (although EAS accounts may still be able to AutoDiscover)
437        onManualSetup(true);
438    }
439
440    /**
441     * This is used in automatic setup mode to jump directly down to the names screen.
442     *
443     * NOTE:  With this organization, it is *not* possible to auto-create an exchange account,
444     * because certain necessary actions happen during AccountSetupOptions (which we are
445     * skipping here).
446     */
447    @Override
448    public void onActivityResult(int requestCode, int resultCode, Intent data) {
449        if (resultCode == RESULT_OK) {
450            String email = mAccount.getEmailAddress();
451            boolean isDefault = mDefaultView.isChecked();
452            mAccount.setDisplayName(email);
453            mAccount.setDefaultAccount(isDefault);
454            // At this point we write the Account object to the DB for the first time.
455            // From now on we'll only pass the accountId around.
456            mAccount.save(this);
457            // Update the backup (side copy) of the accounts
458            AccountBackupRestore.backupAccounts(this);
459            Email.setServicesEnabled(this);
460            AccountSetupNames.actionSetNames(this, mAccount.mId, false);
461            finish();
462        }
463    }
464
465    /**
466     * @param allowAutoDiscover - true if the user clicked 'next' and (if the account is EAS)
467     * it's OK to use autodiscover.  false to prevent autodiscover and go straight to manual setup.
468     * Ignored for IMAP & POP accounts.
469     */
470    private void onManualSetup(boolean allowAutoDiscover) {
471        String email = mEmailView.getText().toString().trim();
472        String password = mPasswordView.getText().toString().trim();
473        String[] emailParts = email.split("@");
474        String user = emailParts[0].trim();
475        String domain = emailParts[1].trim();
476
477        // Alternate entry to the debug options screen (for devices without a physical keyboard:
478        //  Username: d@d.d
479        //  Password: debug
480        if (ENTER_DEBUG_SCREEN && "d@d.d".equals(email) && "debug".equals(password)) {
481            mEmailView.setText("");
482            mPasswordView.setText("");
483            Debug.actionShow(this);
484            return;
485        }
486
487        mAccount = new EmailContent.Account();
488        mAccount.setSenderName(getOwnerName());
489        mAccount.setEmailAddress(email);
490        try {
491            URI uri = new URI("placeholder", user + ":" + password, domain, -1, null, null, null);
492            mAccount.setStoreUri(this, uri.toString());
493            mAccount.setSenderUri(this, uri.toString());
494        } catch (URISyntaxException use) {
495            // If we can't set up the URL, don't continue - account setup pages will fail too
496            Toast.makeText(this, R.string.account_setup_username_password_toast, Toast.LENGTH_LONG)
497                    .show();
498            mAccount = null;
499            return;
500        }
501/* TODO figure out the best way to implement this concept
502        mAccount.setDraftsFolderName(getString(R.string.special_mailbox_name_drafts));
503        mAccount.setTrashFolderName(getString(R.string.special_mailbox_name_trash));
504        mAccount.setOutboxFolderName(getString(R.string.special_mailbox_name_outbox));
505        mAccount.setSentFolderName(getString(R.string.special_mailbox_name_sent));
506*/
507        mAccount.setSyncInterval(DEFAULT_ACCOUNT_CHECK_INTERVAL);
508
509        AccountSetupAccountType.actionSelectAccountType(this, mAccount, mDefaultView.isChecked(),
510                mEasFlowMode, allowAutoDiscover);
511    }
512
513    public void onClick(View v) {
514        switch (v.getId()) {
515            case R.id.next:
516                onNext();
517                break;
518            case R.id.manual_setup:
519                // no AutoDiscover - user clicked "manual"
520                onManualSetup(false);
521                break;
522        }
523    }
524}
525