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