AccountSetupIncoming.java revision 56e48981f0093c737b38b7757410283be9ed823f
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.Account;
20import com.android.email.R;
21import com.android.email.Utility;
22import com.android.email.provider.EmailContent;
23
24import android.app.Activity;
25import android.app.AlertDialog;
26import android.app.Dialog;
27import android.content.DialogInterface;
28import android.content.Intent;
29import android.os.Bundle;
30import android.text.Editable;
31import android.text.TextWatcher;
32import android.text.method.DigitsKeyListener;
33import android.view.View;
34import android.view.View.OnClickListener;
35import android.widget.AdapterView;
36import android.widget.ArrayAdapter;
37import android.widget.Button;
38import android.widget.EditText;
39import android.widget.Spinner;
40import android.widget.TextView;
41
42import java.net.URI;
43import java.net.URISyntaxException;
44
45public class AccountSetupIncoming extends Activity implements OnClickListener {
46    private static final String EXTRA_ACCOUNT = "account";
47    private static final String EXTRA_MAKE_DEFAULT = "makeDefault";
48
49    private static final int popPorts[] = {
50            110, 995, 995, 110, 110
51    };
52    private static final String popSchemes[] = {
53            "pop3", "pop3+ssl+", "pop3+ssl+trustallcerts", "pop3+tls+", "pop3+tls+trustallcerts"
54    };
55    private static final int imapPorts[] = {
56            143, 993, 993, 143, 143
57    };
58    private static final String imapSchemes[] = {
59            "imap", "imap+ssl+", "imap+ssl+trustallcerts", "imap+tls+", "imap+tls+trustallcerts"
60    };
61
62    private final static int DIALOG_DUPLICATE_ACCOUNT = 1;
63
64    private int mAccountPorts[];
65    private String mAccountSchemes[];
66    private EditText mUsernameView;
67    private EditText mPasswordView;
68    private EditText mServerView;
69    private EditText mPortView;
70    private Spinner mSecurityTypeView;
71    private Spinner mDeletePolicyView;
72    private EditText mImapPathPrefixView;
73    private Button mNextButton;
74    private EmailContent.Account mAccount;
75    private boolean mMakeDefault;
76    private String mCacheLoginCredential;
77    private String mDuplicateAccountName;
78
79    public static void actionIncomingSettings(Activity fromActivity, EmailContent.Account account,
80            boolean makeDefault) {
81        Intent i = new Intent(fromActivity, AccountSetupIncoming.class);
82        i.putExtra(EXTRA_ACCOUNT, account);
83        i.putExtra(EXTRA_MAKE_DEFAULT, makeDefault);
84        fromActivity.startActivity(i);
85    }
86
87    public static void actionEditIncomingSettings(Activity fromActivity, EmailContent.Account account)
88            {
89        Intent i = new Intent(fromActivity, AccountSetupIncoming.class);
90        i.setAction(Intent.ACTION_EDIT);
91        i.putExtra(EXTRA_ACCOUNT, account);
92        fromActivity.startActivity(i);
93    }
94
95    @Override
96    public void onCreate(Bundle savedInstanceState) {
97        super.onCreate(savedInstanceState);
98        setContentView(R.layout.account_setup_incoming);
99
100        mUsernameView = (EditText)findViewById(R.id.account_username);
101        mPasswordView = (EditText)findViewById(R.id.account_password);
102        TextView serverLabelView = (TextView) findViewById(R.id.account_server_label);
103        mServerView = (EditText)findViewById(R.id.account_server);
104        mPortView = (EditText)findViewById(R.id.account_port);
105        mSecurityTypeView = (Spinner)findViewById(R.id.account_security_type);
106        mDeletePolicyView = (Spinner)findViewById(R.id.account_delete_policy);
107        mImapPathPrefixView = (EditText)findViewById(R.id.imap_path_prefix);
108        mNextButton = (Button)findViewById(R.id.next);
109
110        mNextButton.setOnClickListener(this);
111
112        SpinnerOption securityTypes[] = {
113            new SpinnerOption(0, getString(R.string.account_setup_incoming_security_none_label)),
114            new SpinnerOption(1, getString(R.string.account_setup_incoming_security_ssl_label)),
115            new SpinnerOption(2, getString(
116                    R.string.account_setup_incoming_security_ssl_trust_certificates_label)),
117            new SpinnerOption(3, getString(R.string.account_setup_incoming_security_tls_label)),
118            new SpinnerOption(4, getString(
119                    R.string.account_setup_incoming_security_tls_trust_certificates_label)),
120        };
121
122        SpinnerOption deletePolicies[] = {
123                new SpinnerOption(Account.DELETE_POLICY_NEVER,
124                        getString(R.string.account_setup_incoming_delete_policy_never_label)),
125                new SpinnerOption(Account.DELETE_POLICY_ON_DELETE,
126                        getString(R.string.account_setup_incoming_delete_policy_delete_label)),
127        };
128
129        ArrayAdapter<SpinnerOption> securityTypesAdapter = new ArrayAdapter<SpinnerOption>(this,
130                android.R.layout.simple_spinner_item, securityTypes);
131        securityTypesAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
132        mSecurityTypeView.setAdapter(securityTypesAdapter);
133
134        ArrayAdapter<SpinnerOption> deletePoliciesAdapter = new ArrayAdapter<SpinnerOption>(this,
135                android.R.layout.simple_spinner_item, deletePolicies);
136        deletePoliciesAdapter
137                .setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
138        mDeletePolicyView.setAdapter(deletePoliciesAdapter);
139
140        /*
141         * Updates the port when the user changes the security type. This allows
142         * us to show a reasonable default which the user can change.
143         */
144        mSecurityTypeView.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
145            public void onItemSelected(AdapterView arg0, View arg1, int arg2, long arg3) {
146                updatePortFromSecurityType();
147            }
148
149            public void onNothingSelected(AdapterView<?> arg0) {
150            }
151        });
152
153        /*
154         * Calls validateFields() which enables or disables the Next button
155         * based on the fields' validity.
156         */
157        TextWatcher validationTextWatcher = new TextWatcher() {
158            public void afterTextChanged(Editable s) {
159                validateFields();
160            }
161
162            public void beforeTextChanged(CharSequence s, int start, int count, int after) {
163            }
164
165            public void onTextChanged(CharSequence s, int start, int before, int count) {
166            }
167        };
168        mUsernameView.addTextChangedListener(validationTextWatcher);
169        mPasswordView.addTextChangedListener(validationTextWatcher);
170        mServerView.addTextChangedListener(validationTextWatcher);
171        mPortView.addTextChangedListener(validationTextWatcher);
172
173        /*
174         * Only allow digits in the port field.
175         */
176        mPortView.setKeyListener(DigitsKeyListener.getInstance("0123456789"));
177
178        mAccount = (EmailContent.Account)getIntent().getParcelableExtra(EXTRA_ACCOUNT);
179        mMakeDefault = getIntent().getBooleanExtra(EXTRA_MAKE_DEFAULT, false);
180
181        /*
182         * If we're being reloaded we override the original account with the one
183         * we saved
184         */
185        if (savedInstanceState != null && savedInstanceState.containsKey(EXTRA_ACCOUNT)) {
186            mAccount = (EmailContent.Account)savedInstanceState.getParcelable(EXTRA_ACCOUNT);
187        }
188
189        try {
190            // TODO this should be accessed directly via the HostAuth structure
191            URI uri = new URI(mAccount.getStoreUri(this));
192            String username = null;
193            String password = null;
194            if (uri.getUserInfo() != null) {
195                String[] userInfoParts = uri.getUserInfo().split(":", 2);
196                username = userInfoParts[0];
197                if (userInfoParts.length > 1) {
198                    password = userInfoParts[1];
199                }
200            }
201
202            if (username != null) {
203                mUsernameView.setText(username);
204            }
205
206            if (password != null) {
207                mPasswordView.setText(password);
208            }
209
210            if (uri.getScheme().startsWith("pop3")) {
211                serverLabelView.setText(R.string.account_setup_incoming_pop_server_label);
212                mAccountPorts = popPorts;
213                mAccountSchemes = popSchemes;
214
215                findViewById(R.id.imap_path_prefix_section).setVisibility(View.GONE);
216            } else if (uri.getScheme().startsWith("imap")) {
217                serverLabelView.setText(R.string.account_setup_incoming_imap_server_label);
218                mAccountPorts = imapPorts;
219                mAccountSchemes = imapSchemes;
220
221                findViewById(R.id.account_delete_policy_label).setVisibility(View.GONE);
222                mDeletePolicyView.setVisibility(View.GONE);
223                if (uri.getPath() != null && uri.getPath().length() > 0) {
224                    mImapPathPrefixView.setText(uri.getPath().substring(1));
225                }
226            } else {
227                throw new Error("Unknown account type: " + mAccount.getStoreUri(this));
228            }
229
230            for (int i = 0; i < mAccountSchemes.length; i++) {
231                if (mAccountSchemes[i].equals(uri.getScheme())) {
232                    SpinnerOption.setSpinnerOptionValue(mSecurityTypeView, i);
233                }
234            }
235
236            SpinnerOption.setSpinnerOptionValue(mDeletePolicyView, mAccount.getDeletePolicy());
237
238            if (uri.getHost() != null) {
239                mServerView.setText(uri.getHost());
240            }
241
242            if (uri.getPort() != -1) {
243                mPortView.setText(Integer.toString(uri.getPort()));
244            } else {
245                updatePortFromSecurityType();
246            }
247        } catch (URISyntaxException use) {
248            /*
249             * We should always be able to parse our own settings.
250             */
251            throw new Error(use);
252        }
253
254        validateFields();
255    }
256
257    @Override
258    public void onSaveInstanceState(Bundle outState) {
259        super.onSaveInstanceState(outState);
260        outState.putParcelable(EXTRA_ACCOUNT, mAccount);
261    }
262
263    /**
264     * Prepare a cached dialog with current values (e.g. account name)
265     */
266    @Override
267    public Dialog onCreateDialog(int id) {
268        switch (id) {
269            case DIALOG_DUPLICATE_ACCOUNT:
270                return new AlertDialog.Builder(this)
271                    .setIcon(android.R.drawable.ic_dialog_alert)
272                    .setTitle(R.string.account_duplicate_dlg_title)
273                    .setMessage(getString(R.string.account_duplicate_dlg_message_fmt,
274                            mDuplicateAccountName))
275                    .setPositiveButton(R.string.okay_action,
276                            new DialogInterface.OnClickListener() {
277                        public void onClick(DialogInterface dialog, int which) {
278                            dismissDialog(DIALOG_DUPLICATE_ACCOUNT);
279                        }
280                    })
281                    .create();
282        }
283        return null;
284    }
285
286    /**
287     * Update a cached dialog with current values (e.g. account name)
288     */
289    @Override
290    public void onPrepareDialog(int id, Dialog dialog) {
291        switch (id) {
292            case DIALOG_DUPLICATE_ACCOUNT:
293                if (mDuplicateAccountName != null) {
294                    AlertDialog alert = (AlertDialog) dialog;
295                    alert.setMessage(getString(R.string.account_duplicate_dlg_message_fmt,
296                            mDuplicateAccountName));
297                }
298                break;
299        }
300    }
301
302    /**
303     * Check the values in the fields and decide if it makes sense to enable the "next" button
304     * NOTE:  Does it make sense to extract & combine with similar code in AccountSetupIncoming?
305     */
306    private void validateFields() {
307        boolean enabled = Utility.requiredFieldValid(mUsernameView)
308                && Utility.requiredFieldValid(mPasswordView)
309                && Utility.requiredFieldValid(mServerView)
310                && Utility.requiredFieldValid(mPortView);
311        if (enabled) {
312            try {
313                URI uri = getUri();
314            } catch (URISyntaxException use) {
315                enabled = false;
316            }
317        }
318        mNextButton.setEnabled(enabled);
319        Utility.setCompoundDrawablesAlpha(mNextButton, enabled ? 255 : 128);
320    }
321
322    private void updatePortFromSecurityType() {
323        int securityType = (Integer)((SpinnerOption)mSecurityTypeView.getSelectedItem()).value;
324        mPortView.setText(Integer.toString(mAccountPorts[securityType]));
325    }
326
327    @Override
328    public void onActivityResult(int requestCode, int resultCode, Intent data) {
329        if (resultCode == RESULT_OK) {
330            if (Intent.ACTION_EDIT.equals(getIntent().getAction())) {
331                if (mAccount.isSaved()) {
332                    mAccount.update(this, mAccount.toContentValues());
333                    mAccount.mHostAuthRecv.update(this, mAccount.mHostAuthRecv.toContentValues());
334                } else {
335                    mAccount.save(this);
336                }
337                finish();
338            } else {
339                /*
340                 * Set the username and password for the outgoing settings to the username and
341                 * password the user just set for incoming.
342                 */
343                try {
344                    URI oldUri = new URI(mAccount.getSenderUri(this));
345                    URI uri = new URI(
346                            oldUri.getScheme(),
347                            mUsernameView.getText().toString().trim() + ":"
348                                    + mPasswordView.getText().toString().trim(),
349                            oldUri.getHost(),
350                            oldUri.getPort(),
351                            null,
352                            null,
353                            null);
354                    mAccount.setSenderUri(this, uri.toString());
355                } catch (URISyntaxException use) {
356                    /*
357                     * If we can't set up the URL we just continue. It's only for
358                     * convenience.
359                     */
360                }
361
362                AccountSetupOutgoing.actionOutgoingSettings(this, mAccount, mMakeDefault);
363                finish();
364            }
365        }
366    }
367
368    /**
369     * Attempt to create a URI from the fields provided.  Throws URISyntaxException if there's
370     * a problem with the user input.
371     * @return a URI built from the account setup fields
372     */
373    private URI getUri() throws URISyntaxException {
374        int securityType = (Integer)((SpinnerOption)mSecurityTypeView.getSelectedItem()).value;
375        String path = null;
376        if (mAccountSchemes[securityType].startsWith("imap")) {
377            path = "/" + mImapPathPrefixView.getText().toString().trim();
378        }
379        String userName = mUsernameView.getText().toString().trim();
380        mCacheLoginCredential = userName;
381        URI uri = new URI(
382                mAccountSchemes[securityType],
383                userName + ":" + mPasswordView.getText().toString().trim(),
384                mServerView.getText().toString().trim(),
385                Integer.parseInt(mPortView.getText().toString().trim()),
386                path, // path
387                null, // query
388                null);
389
390        return uri;
391    }
392
393    private void onNext() {
394        try {
395            URI uri = getUri();
396            mAccount.setStoreUri(this, uri.toString());
397
398            // Stop here if the login credentials duplicate an existing account
399            // (unless they duplicate the existing account, as they of course will)
400            mDuplicateAccountName = Utility.findDuplicateAccount(this, mAccount.mId,
401                    uri.getHost(), mCacheLoginCredential);
402            if (mDuplicateAccountName != null) {
403                this.showDialog(DIALOG_DUPLICATE_ACCOUNT);
404                return;
405            }
406        } catch (URISyntaxException use) {
407            /*
408             * It's unrecoverable if we cannot create a URI from components that
409             * we validated to be safe.
410             */
411            throw new Error(use);
412        }
413
414        mAccount.setDeletePolicy((Integer)((SpinnerOption)mDeletePolicyView.getSelectedItem()).value);
415        AccountSetupCheckSettings.actionCheckSettings(this, mAccount, true, false);
416    }
417
418    public void onClick(View v) {
419        switch (v.getId()) {
420            case R.id.next:
421                onNext();
422                break;
423        }
424    }
425}
426