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