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