AccountSetupBasics.java revision a5af05f1a94ba9d0a91bd6507492cf6946f88a4d
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.Email;
20import com.android.email.EmailAddressValidator;
21import com.android.email.Preferences;
22import com.android.email.R;
23import com.android.email.Utility;
24import com.android.email.activity.Debug;
25import com.android.email.provider.EmailContent;
26import com.android.email.provider.EmailContent.Account;
27import com.android.email.provider.EmailContent.AccountColumns;
28
29import android.app.Activity;
30import android.app.AlertDialog;
31import android.app.Dialog;
32import android.content.ContentValues;
33import android.content.DialogInterface;
34import android.content.Intent;
35import android.content.res.XmlResourceParser;
36import android.database.Cursor;
37import android.net.Uri;
38import android.os.Bundle;
39import android.provider.Contacts;
40import android.provider.Contacts.People.ContactMethods;
41import android.text.Editable;
42import android.text.TextWatcher;
43import android.util.Log;
44import android.view.View;
45import android.view.View.OnClickListener;
46import android.widget.Button;
47import android.widget.CheckBox;
48import android.widget.EditText;
49import android.widget.Toast;
50
51import java.io.Serializable;
52import java.net.URI;
53import java.net.URISyntaxException;
54
55/**
56 * Prompts the user for the email address and password. Also prompts for
57 * "Use this account as default" if this is the 2nd+ account being set up.
58 * Attempts to lookup default settings for the domain the user specified. If the
59 * domain is known the settings are handed off to the AccountSetupCheckSettings
60 * activity. If no settings are found the settings are handed off to the
61 * AccountSetupAccountType activity.
62 */
63public class AccountSetupBasics extends Activity
64        implements OnClickListener, TextWatcher {
65    private final static boolean ENTER_DEBUG_SCREEN = true;
66    private final static String EXTRA_ACCOUNT = "com.android.email.AccountSetupBasics.account";
67    private final static int DIALOG_NOTE = 1;
68    private final static String STATE_KEY_PROVIDER =
69        "com.android.email.AccountSetupBasics.provider";
70
71    // NOTE: If you change this value, confirm that the new interval exists in arrays.xml
72    private final static int DEFAULT_ACCOUNT_CHECK_INTERVAL = 15;
73
74    private Preferences mPrefs;
75    private EditText mEmailView;
76    private EditText mPasswordView;
77    private CheckBox mDefaultView;
78    private Button mNextButton;
79    private Button mManualSetupButton;
80    private EmailContent.Account mAccount;
81    private Provider mProvider;
82
83    private EmailAddressValidator mEmailValidator = new EmailAddressValidator();
84
85    public static void actionNewAccount(Activity fromActivity) {
86        Intent i = new Intent(fromActivity, AccountSetupBasics.class);
87        fromActivity.startActivity(i);
88    }
89
90    @Override
91    public void onCreate(Bundle savedInstanceState) {
92        super.onCreate(savedInstanceState);
93        setContentView(R.layout.account_setup_basics);
94        mPrefs = Preferences.getPreferences(this);
95        mEmailView = (EditText)findViewById(R.id.account_email);
96        mPasswordView = (EditText)findViewById(R.id.account_password);
97        mDefaultView = (CheckBox)findViewById(R.id.account_default);
98        mNextButton = (Button)findViewById(R.id.next);
99        mManualSetupButton = (Button)findViewById(R.id.manual_setup);
100
101        mNextButton.setOnClickListener(this);
102        mManualSetupButton.setOnClickListener(this);
103
104        mEmailView.addTextChangedListener(this);
105        mPasswordView.addTextChangedListener(this);
106
107        // Find out how many accounts we have, and if there one or more, then we have a choice
108        // about being default or not.
109        Cursor c = null;
110        try {
111            c = getContentResolver().query(
112                    EmailContent.Account.CONTENT_URI,
113                    EmailContent.Account.ID_PROJECTION,
114                    null, null, null);
115            if (c.getCount() > 0) {
116                mDefaultView.setVisibility(View.VISIBLE);
117            }
118        } finally {
119            if (c != null) {
120                c.close();
121            }
122        }
123
124        if (savedInstanceState != null && savedInstanceState.containsKey(EXTRA_ACCOUNT)) {
125            mAccount = (EmailContent.Account)savedInstanceState.getParcelable(EXTRA_ACCOUNT);
126        }
127
128        if (savedInstanceState != null && savedInstanceState.containsKey(STATE_KEY_PROVIDER)) {
129            mProvider = (Provider)savedInstanceState.getSerializable(STATE_KEY_PROVIDER);
130        }
131    }
132
133    @Override
134    public void onResume() {
135        super.onResume();
136        validateFields();
137    }
138
139    @Override
140    public void onSaveInstanceState(Bundle outState) {
141        super.onSaveInstanceState(outState);
142        outState.putParcelable(EXTRA_ACCOUNT, mAccount);
143        if (mProvider != null) {
144            outState.putSerializable(STATE_KEY_PROVIDER, mProvider);
145        }
146    }
147
148    public void afterTextChanged(Editable s) {
149        validateFields();
150    }
151
152    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
153    }
154
155    public void onTextChanged(CharSequence s, int start, int before, int count) {
156    }
157
158    private void validateFields() {
159        boolean valid = Utility.requiredFieldValid(mEmailView)
160                && Utility.requiredFieldValid(mPasswordView)
161                && mEmailValidator.isValid(mEmailView.getText().toString().trim());
162        mNextButton.setEnabled(valid);
163        mManualSetupButton.setEnabled(valid);
164        /*
165         * Dim the next button's icon to 50% if the button is disabled.
166         * TODO this can probably be done with a stateful drawable. Check into it.
167         * android:state_enabled
168         */
169        Utility.setCompoundDrawablesAlpha(mNextButton, mNextButton.isEnabled() ? 255 : 128);
170    }
171
172    private String getOwnerName() {
173        String name = null;
174        String projection[] = {
175            ContactMethods.NAME
176        };
177        Cursor c = getContentResolver().query(
178                Uri.withAppendedPath(Contacts.People.CONTENT_URI, "owner"), projection, null, null,
179                null);
180        if (c.getCount() > 0) {
181            c.moveToFirst();
182            name = c.getString(0);
183            c.close();
184        }
185
186        if (name == null || name.length() == 0) {
187            long defaultId = Account.getDefaultAccountId(this);
188            if (defaultId != -1) {
189                Account account = Account.restoreAccountWithId(this, defaultId);
190                if (account != null) {
191                    name = account.getSenderName();
192                }
193            }
194        }
195        return name;
196    }
197
198    @Override
199    public Dialog onCreateDialog(int id) {
200        if (id == DIALOG_NOTE) {
201            if (mProvider != null && mProvider.note != null) {
202                return new AlertDialog.Builder(this)
203                    .setIcon(android.R.drawable.ic_dialog_alert)
204                    .setTitle(android.R.string.dialog_alert_title)
205                    .setMessage(mProvider.note)
206                    .setPositiveButton(
207                            getString(R.string.okay_action),
208                            new DialogInterface.OnClickListener() {
209                                public void onClick(DialogInterface dialog, int which) {
210                                    finishAutoSetup();
211                                }
212                            })
213                    .setNegativeButton(
214                            getString(R.string.cancel_action),
215                            null)
216                    .create();
217            }
218        }
219        return null;
220    }
221
222    private void finishAutoSetup() {
223        String email = mEmailView.getText().toString().trim();
224        String password = mPasswordView.getText().toString().trim();
225        String[] emailParts = email.split("@");
226        String user = emailParts[0];
227        String domain = emailParts[1];
228        URI incomingUri = null;
229        URI outgoingUri = null;
230        try {
231            String incomingUsername = mProvider.incomingUsernameTemplate;
232            incomingUsername = incomingUsername.replaceAll("\\$email", email);
233            incomingUsername = incomingUsername.replaceAll("\\$user", user);
234            incomingUsername = incomingUsername.replaceAll("\\$domain", domain);
235
236            URI incomingUriTemplate = mProvider.incomingUriTemplate;
237            incomingUri = new URI(incomingUriTemplate.getScheme(), incomingUsername + ":"
238                    + password, incomingUriTemplate.getHost(), incomingUriTemplate.getPort(),
239                    incomingUriTemplate.getPath(), null, null);
240
241            String outgoingUsername = mProvider.outgoingUsernameTemplate;
242            outgoingUsername = outgoingUsername.replaceAll("\\$email", email);
243            outgoingUsername = outgoingUsername.replaceAll("\\$user", user);
244            outgoingUsername = outgoingUsername.replaceAll("\\$domain", domain);
245
246            URI outgoingUriTemplate = mProvider.outgoingUriTemplate;
247            outgoingUri = new URI(outgoingUriTemplate.getScheme(), outgoingUsername + ":"
248                    + password, outgoingUriTemplate.getHost(), outgoingUriTemplate.getPort(),
249                    outgoingUriTemplate.getPath(), null, null);
250        } catch (URISyntaxException use) {
251            /*
252             * If there is some problem with the URI we give up and go on to
253             * manual setup.
254             */
255            onManualSetup();
256            return;
257        }
258
259        mAccount = new EmailContent.Account();
260        mAccount.setSenderName(getOwnerName());
261        mAccount.setEmailAddress(email);
262        mAccount.setStoreUri(this, incomingUri.toString());
263        mAccount.setSenderUri(this, outgoingUri.toString());
264/* TODO figure out the best way to implement this concept
265        mAccount.setDraftsFolderName(getString(R.string.special_mailbox_name_drafts));
266        mAccount.setTrashFolderName(getString(R.string.special_mailbox_name_trash));
267        mAccount.setOutboxFolderName(getString(R.string.special_mailbox_name_outbox));
268        mAccount.setSentFolderName(getString(R.string.special_mailbox_name_sent));
269*/
270        if (incomingUri.toString().startsWith("imap")) {
271            // Delete policy must be set explicitly, because IMAP does not provide a UI selection
272            // for it. This logic needs to be followed in the auto setup flow as well.
273            mAccount.setDeletePolicy(EmailContent.Account.DELETE_POLICY_ON_DELETE);
274        }
275        mAccount.setSyncInterval(DEFAULT_ACCOUNT_CHECK_INTERVAL);
276        AccountSetupCheckSettings.actionCheckSettings(this, mAccount, true, true);
277    }
278
279    private void onNext() {
280        String email = mEmailView.getText().toString().trim();
281        String[] emailParts = email.split("@");
282        String domain = emailParts[1].trim();
283        mProvider = findProviderForDomain(domain);
284        if (mProvider == null) {
285            /*
286             * We don't have default settings for this account, start the manual
287             * setup process.
288             */
289            onManualSetup();
290            return;
291        }
292
293        if (mProvider.note != null) {
294            showDialog(DIALOG_NOTE);
295        }
296        else {
297            finishAutoSetup();
298        }
299    }
300
301    @Override
302    public void onActivityResult(int requestCode, int resultCode, Intent data) {
303        if (resultCode == RESULT_OK) {
304            String email = mAccount.getEmailAddress();
305            boolean isDefault = mDefaultView.isChecked();
306            mAccount.setDisplayName(email);
307            mAccount.setDefaultAccount(isDefault);
308            // At this point we write the Account object to the DB for the first time.
309            // From now on we'll only pass the accountId around.
310            mAccount.save(this);
311            Email.setServicesEnabled(this);
312            AccountSetupNames.actionSetNames(this, mAccount.mId);
313            finish();
314        }
315    }
316
317    private void onManualSetup() {
318        String email = mEmailView.getText().toString().trim();
319        String password = mPasswordView.getText().toString().trim();
320        String[] emailParts = email.split("@");
321        String user = emailParts[0].trim();
322        String domain = emailParts[1].trim();
323
324        // Alternate entry to the debug options screen (for devices without a physical keyboard:
325        //  Username: d@d.d
326        //  Password: debug
327        if (ENTER_DEBUG_SCREEN && "d@d.d".equals(email) && "debug".equals(password)) {
328            startActivity(new Intent(this, Debug.class));
329            return;
330        }
331
332        mAccount = new EmailContent.Account();
333        mAccount.setSenderName(getOwnerName());
334        mAccount.setEmailAddress(email);
335        try {
336            URI uri = new URI("placeholder", user + ":" + password, domain, -1, null, null, null);
337            mAccount.setStoreUri(this, uri.toString());
338            mAccount.setSenderUri(this, uri.toString());
339        } catch (URISyntaxException use) {
340            // If we can't set up the URL, don't continue - account setup pages will fail too
341            Toast.makeText(this, R.string.account_setup_username_password_toast, Toast.LENGTH_LONG)
342                    .show();
343            mAccount = null;
344            return;
345        }
346/* TODO figure out the best way to implement this concept
347        mAccount.setDraftsFolderName(getString(R.string.special_mailbox_name_drafts));
348        mAccount.setTrashFolderName(getString(R.string.special_mailbox_name_trash));
349        mAccount.setOutboxFolderName(getString(R.string.special_mailbox_name_outbox));
350        mAccount.setSentFolderName(getString(R.string.special_mailbox_name_sent));
351*/
352        mAccount.setSyncInterval(DEFAULT_ACCOUNT_CHECK_INTERVAL);
353
354        AccountSetupAccountType.actionSelectAccountType(this, mAccount, mDefaultView.isChecked());
355        finish();
356    }
357
358    public void onClick(View v) {
359        switch (v.getId()) {
360            case R.id.next:
361                onNext();
362                break;
363            case R.id.manual_setup:
364                onManualSetup();
365                break;
366        }
367    }
368
369    /**
370     * Attempts to get the given attribute as a String resource first, and if it fails
371     * returns the attribute as a simple String value.
372     * @param xml
373     * @param name
374     * @return the requested resource
375     */
376    private String getXmlAttribute(XmlResourceParser xml, String name) {
377        int resId = xml.getAttributeResourceValue(null, name, 0);
378        if (resId == 0) {
379            return xml.getAttributeValue(null, name);
380        }
381        else {
382            return getString(resId);
383        }
384    }
385
386    /**
387     * Search the list of known Email providers looking for one that matches the user's email
388     * domain.  We look in providers_product.xml first, followed by the entries in
389     * platform providers.xml.  This provides a nominal override capability.
390     *
391     * A match is defined as any provider entry for which the "domain" attribute matches.
392     *
393     * @param domain The domain portion of the user's email address
394     * @return suitable Provider definition, or null if no match found
395     */
396    private Provider findProviderForDomain(String domain) {
397        Provider p = findProviderForDomain(domain, R.xml.providers_product);
398        if (p == null) {
399            p = findProviderForDomain(domain, R.xml.providers);
400        }
401        return p;
402    }
403
404    /**
405     * Search a single resource containing known Email provider definitions.
406     *
407     * @param domain The domain portion of the user's email address
408     * @param resourceId Id of the provider resource to scan
409     * @return suitable Provider definition, or null if no match found
410     */
411    private Provider findProviderForDomain(String domain, int resourceId) {
412        try {
413            XmlResourceParser xml = getResources().getXml(resourceId);
414            int xmlEventType;
415            Provider provider = null;
416            while ((xmlEventType = xml.next()) != XmlResourceParser.END_DOCUMENT) {
417                if (xmlEventType == XmlResourceParser.START_TAG
418                        && "provider".equals(xml.getName())
419                        && domain.equalsIgnoreCase(getXmlAttribute(xml, "domain"))) {
420                    provider = new Provider();
421                    provider.id = getXmlAttribute(xml, "id");
422                    provider.label = getXmlAttribute(xml, "label");
423                    provider.domain = getXmlAttribute(xml, "domain");
424                    provider.note = getXmlAttribute(xml, "note");
425                }
426                else if (xmlEventType == XmlResourceParser.START_TAG
427                        && "incoming".equals(xml.getName())
428                        && provider != null) {
429                    provider.incomingUriTemplate = new URI(getXmlAttribute(xml, "uri"));
430                    provider.incomingUsernameTemplate = getXmlAttribute(xml, "username");
431                }
432                else if (xmlEventType == XmlResourceParser.START_TAG
433                        && "outgoing".equals(xml.getName())
434                        && provider != null) {
435                    provider.outgoingUriTemplate = new URI(getXmlAttribute(xml, "uri"));
436                    provider.outgoingUsernameTemplate = getXmlAttribute(xml, "username");
437                }
438                else if (xmlEventType == XmlResourceParser.END_TAG
439                        && "provider".equals(xml.getName())
440                        && provider != null) {
441                    return provider;
442                }
443            }
444        }
445        catch (Exception e) {
446            Log.e(Email.LOG_TAG, "Error while trying to load provider settings.", e);
447        }
448        return null;
449    }
450
451    static class Provider implements Serializable {
452        private static final long serialVersionUID = 8511656164616538989L;
453
454        public String id;
455
456        public String label;
457
458        public String domain;
459
460        public URI incomingUriTemplate;
461
462        public String incomingUsernameTemplate;
463
464        public URI outgoingUriTemplate;
465
466        public String outgoingUsernameTemplate;
467
468        public String note;
469    }
470}
471