• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package com.android.email.activity.setup;
18 
19 import com.android.email.Account;
20 import com.android.email.R;
21 import com.android.email.Utility;
22 import com.android.email.provider.EmailContent;
23 
24 import android.app.Activity;
25 import android.app.AlertDialog;
26 import android.app.Dialog;
27 import android.content.DialogInterface;
28 import android.content.Intent;
29 import android.os.Bundle;
30 import android.text.Editable;
31 import android.text.TextWatcher;
32 import android.text.method.DigitsKeyListener;
33 import android.view.View;
34 import android.view.View.OnClickListener;
35 import android.widget.AdapterView;
36 import android.widget.ArrayAdapter;
37 import android.widget.Button;
38 import android.widget.EditText;
39 import android.widget.Spinner;
40 import android.widget.TextView;
41 
42 import java.net.URI;
43 import java.net.URISyntaxException;
44 
45 public 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 
actionIncomingSettings(Activity fromActivity, EmailContent.Account account, boolean makeDefault)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 
actionEditIncomingSettings(Activity fromActivity, EmailContent.Account account)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
onCreate(Bundle savedInstanceState)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
onSaveInstanceState(Bundle outState)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
onCreateDialog(int id)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