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