• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2010 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 android.app.Activity;
20 import android.content.Context;
21 import android.os.Bundle;
22 import android.text.Editable;
23 import android.text.TextUtils;
24 import android.text.TextWatcher;
25 import android.text.method.DigitsKeyListener;
26 import android.util.Log;
27 import android.view.LayoutInflater;
28 import android.view.View;
29 import android.view.ViewGroup;
30 import android.view.inputmethod.EditorInfo;
31 import android.widget.AdapterView;
32 import android.widget.ArrayAdapter;
33 import android.widget.EditText;
34 import android.widget.Spinner;
35 import android.widget.TextView;
36 
37 import com.android.email.Email;
38 import com.android.email.R;
39 import com.android.email.activity.UiUtilities;
40 import com.android.email.provider.AccountBackupRestore;
41 import com.android.emailcommon.Logging;
42 import com.android.emailcommon.provider.Account;
43 import com.android.emailcommon.provider.HostAuth;
44 import com.android.emailcommon.utility.Utility;
45 
46 /**
47  * Provides UI for IMAP/POP account settings.
48  *
49  * This fragment is used by AccountSetupIncoming (for creating accounts) and by AccountSettingsXL
50  * (for editing existing accounts).
51  */
52 public class AccountSetupIncomingFragment extends AccountServerBaseFragment {
53 
54     private final static String STATE_KEY_CREDENTIAL = "AccountSetupIncomingFragment.credential";
55     private final static String STATE_KEY_LOADED = "AccountSetupIncomingFragment.loaded";
56 
57     private static final int POP3_PORT_NORMAL = 110;
58     private static final int POP3_PORT_SSL = 995;
59 
60     private static final int IMAP_PORT_NORMAL = 143;
61     private static final int IMAP_PORT_SSL = 993;
62 
63     private EditText mUsernameView;
64     private EditText mPasswordView;
65     private TextView mServerLabelView;
66     private EditText mServerView;
67     private EditText mPortView;
68     private Spinner mSecurityTypeView;
69     private TextView mDeletePolicyLabelView;
70     private Spinner mDeletePolicyView;
71     private View mImapPathPrefixSectionView;
72     private EditText mImapPathPrefixView;
73     // Delete policy as loaded from the device
74     private int mLoadedDeletePolicy;
75 
76     // Support for lifecycle
77     private boolean mStarted;
78     private boolean mConfigured;
79     private boolean mLoaded;
80     private String mCacheLoginCredential;
81 
82     /**
83      * Called to do initial creation of a fragment.  This is called after
84      * {@link #onAttach(Activity)} and before {@link #onActivityCreated(Bundle)}.
85      */
86     @Override
onCreate(Bundle savedInstanceState)87     public void onCreate(Bundle savedInstanceState) {
88         if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) {
89             Log.d(Logging.LOG_TAG, "AccountSetupIncomingFragment onCreate");
90         }
91         super.onCreate(savedInstanceState);
92 
93         if (savedInstanceState != null) {
94             mCacheLoginCredential = savedInstanceState.getString(STATE_KEY_CREDENTIAL);
95             mLoaded = savedInstanceState.getBoolean(STATE_KEY_LOADED, false);
96         }
97     }
98 
99     @Override
onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)100     public View onCreateView(LayoutInflater inflater, ViewGroup container,
101             Bundle savedInstanceState) {
102         if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) {
103             Log.d(Logging.LOG_TAG, "AccountSetupIncomingFragment onCreateView");
104         }
105         int layoutId = mSettingsMode
106                 ? R.layout.account_settings_incoming_fragment
107                 : R.layout.account_setup_incoming_fragment;
108 
109         View view = inflater.inflate(layoutId, container, false);
110         Context context = getActivity();
111 
112         mUsernameView = (EditText) UiUtilities.getView(view, R.id.account_username);
113         mPasswordView = (EditText) UiUtilities.getView(view, R.id.account_password);
114         mServerLabelView = (TextView) UiUtilities.getView(view, R.id.account_server_label);
115         mServerView = (EditText) UiUtilities.getView(view, R.id.account_server);
116         mPortView = (EditText) UiUtilities.getView(view, R.id.account_port);
117         mSecurityTypeView = (Spinner) UiUtilities.getView(view, R.id.account_security_type);
118         mDeletePolicyLabelView = (TextView) UiUtilities.getView(view,
119                 R.id.account_delete_policy_label);
120         mDeletePolicyView = (Spinner) UiUtilities.getView(view, R.id.account_delete_policy);
121         mImapPathPrefixSectionView = UiUtilities.getView(view, R.id.imap_path_prefix_section);
122         mImapPathPrefixView = (EditText) UiUtilities.getView(view, R.id.imap_path_prefix);
123 
124         // Set up spinners
125         SpinnerOption securityTypes[] = {
126             new SpinnerOption(HostAuth.FLAG_NONE, context.getString(
127                     R.string.account_setup_incoming_security_none_label)),
128             new SpinnerOption(HostAuth.FLAG_SSL, context.getString(
129                     R.string.account_setup_incoming_security_ssl_label)),
130             new SpinnerOption(HostAuth.FLAG_SSL | HostAuth.FLAG_TRUST_ALL, context.getString(
131                     R.string.account_setup_incoming_security_ssl_trust_certificates_label)),
132             new SpinnerOption(HostAuth.FLAG_TLS, context.getString(
133                     R.string.account_setup_incoming_security_tls_label)),
134             new SpinnerOption(HostAuth.FLAG_TLS | HostAuth.FLAG_TRUST_ALL, context.getString(
135                     R.string.account_setup_incoming_security_tls_trust_certificates_label)),
136         };
137 
138         SpinnerOption deletePolicies[] = {
139             new SpinnerOption(Account.DELETE_POLICY_NEVER,
140                     context.getString(R.string.account_setup_incoming_delete_policy_never_label)),
141             new SpinnerOption(Account.DELETE_POLICY_ON_DELETE,
142                     context.getString(R.string.account_setup_incoming_delete_policy_delete_label)),
143         };
144 
145         ArrayAdapter<SpinnerOption> securityTypesAdapter = new ArrayAdapter<SpinnerOption>(context,
146                 android.R.layout.simple_spinner_item, securityTypes);
147         securityTypesAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
148         mSecurityTypeView.setAdapter(securityTypesAdapter);
149 
150         ArrayAdapter<SpinnerOption> deletePoliciesAdapter = new ArrayAdapter<SpinnerOption>(context,
151                 android.R.layout.simple_spinner_item, deletePolicies);
152         deletePoliciesAdapter.setDropDownViewResource(
153                 android.R.layout.simple_spinner_dropdown_item);
154         mDeletePolicyView.setAdapter(deletePoliciesAdapter);
155 
156         // Updates the port when the user changes the security type. This allows
157         // us to show a reasonable default which the user can change.
158         mSecurityTypeView.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
159             @Override
160             public void onItemSelected(AdapterView<?> arg0, View arg1, int arg2, long arg3) {
161                 updatePortFromSecurityType();
162             }
163 
164             @Override
165             public void onNothingSelected(AdapterView<?> arg0) { }
166         });
167 
168         // After any text edits, call validateFields() which enables or disables the Next button
169         TextWatcher validationTextWatcher = new TextWatcher() {
170             @Override
171             public void afterTextChanged(Editable s) {
172                 validateFields();
173             }
174 
175             @Override
176             public void beforeTextChanged(CharSequence s, int start, int count, int after) { }
177             @Override
178             public void onTextChanged(CharSequence s, int start, int before, int count) { }
179         };
180         // We're editing an existing account; don't allow modification of the user name
181         if (mSettingsMode) {
182             makeTextViewUneditable(mUsernameView,
183                     getString(R.string.account_setup_username_uneditable_error));
184         }
185         mUsernameView.addTextChangedListener(validationTextWatcher);
186         mPasswordView.addTextChangedListener(validationTextWatcher);
187         mServerView.addTextChangedListener(validationTextWatcher);
188         mPortView.addTextChangedListener(validationTextWatcher);
189 
190         // Only allow digits in the port field.
191         mPortView.setKeyListener(DigitsKeyListener.getInstance("0123456789"));
192 
193         // Additional setup only used while in "settings" mode
194         onCreateViewSettingsMode(view);
195 
196         return view;
197     }
198 
199     @Override
onActivityCreated(Bundle savedInstanceState)200     public void onActivityCreated(Bundle savedInstanceState) {
201         if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) {
202             Log.d(Logging.LOG_TAG, "AccountSetupIncomingFragment onActivityCreated");
203         }
204         super.onActivityCreated(savedInstanceState);
205     }
206 
207     /**
208      * Called when the Fragment is visible to the user.
209      */
210     @Override
onStart()211     public void onStart() {
212         if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) {
213             Log.d(Logging.LOG_TAG, "AccountSetupIncomingFragment onStart");
214         }
215         super.onStart();
216         mStarted = true;
217         configureEditor();
218         loadSettings();
219     }
220 
221     /**
222      * Called when the fragment is visible to the user and actively running.
223      */
224     @Override
onResume()225     public void onResume() {
226         if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) {
227             Log.d(Logging.LOG_TAG, "AccountSetupIncomingFragment onResume");
228         }
229         super.onResume();
230         validateFields();
231     }
232 
233     @Override
onPause()234     public void onPause() {
235         if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) {
236             Log.d(Logging.LOG_TAG, "AccountSetupIncomingFragment onPause");
237         }
238         super.onPause();
239     }
240 
241     /**
242      * Called when the Fragment is no longer started.
243      */
244     @Override
onStop()245     public void onStop() {
246         if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) {
247             Log.d(Logging.LOG_TAG, "AccountSetupIncomingFragment onStop");
248         }
249         super.onStop();
250         mStarted = false;
251     }
252 
253     /**
254      * Called when the fragment is no longer in use.
255      */
256     @Override
onDestroy()257     public void onDestroy() {
258         if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) {
259             Log.d(Logging.LOG_TAG, "AccountSetupIncomingFragment onDestroy");
260         }
261         super.onDestroy();
262     }
263 
264     @Override
onSaveInstanceState(Bundle outState)265     public void onSaveInstanceState(Bundle outState) {
266         if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) {
267             Log.d(Logging.LOG_TAG, "AccountSetupIncomingFragment onSaveInstanceState");
268         }
269         super.onSaveInstanceState(outState);
270 
271         outState.putString(STATE_KEY_CREDENTIAL, mCacheLoginCredential);
272         outState.putBoolean(STATE_KEY_LOADED, mLoaded);
273     }
274 
275     /**
276      * Activity provides callbacks here.  This also triggers loading and setting up the UX
277      */
278     @Override
setCallback(Callback callback)279     public void setCallback(Callback callback) {
280         super.setCallback(callback);
281         if (mStarted) {
282             configureEditor();
283             loadSettings();
284         }
285     }
286 
287     /**
288      * Configure the editor for the account type
289      */
configureEditor()290     private void configureEditor() {
291         if (mConfigured) return;
292         Account account = SetupData.getAccount();
293         if (account == null || account.mHostAuthRecv == null) {
294             return;
295         }
296         TextView lastView = mImapPathPrefixView;
297         mBaseScheme = account.mHostAuthRecv.mProtocol;
298         if (HostAuth.SCHEME_POP3.equals(mBaseScheme)) {
299             mServerLabelView.setText(R.string.account_setup_incoming_pop_server_label);
300             mServerView.setContentDescription(
301                     getResources().getString(R.string.account_setup_incoming_pop_server_label));
302             mImapPathPrefixSectionView.setVisibility(View.GONE);
303             lastView = mPortView;
304         } else if (HostAuth.SCHEME_IMAP.equals(mBaseScheme)) {
305             mServerLabelView.setText(R.string.account_setup_incoming_imap_server_label);
306             mServerView.setContentDescription(
307                     getResources().getString(R.string.account_setup_incoming_imap_server_label));
308             mDeletePolicyLabelView.setVisibility(View.GONE);
309             mDeletePolicyView.setVisibility(View.GONE);
310             mPortView.setImeOptions(EditorInfo.IME_ACTION_NEXT);
311         } else {
312             throw new Error("Unknown account type: " + account);
313         }
314         lastView.setOnEditorActionListener(mDismissImeOnDoneListener);
315         mConfigured = true;
316     }
317 
318     /**
319      * Load the current settings into the UI
320      */
loadSettings()321     private void loadSettings() {
322         if (mLoaded) return;
323 
324         Account account = SetupData.getAccount();
325         HostAuth recvAuth = account.getOrCreateHostAuthRecv(mContext);
326 
327         String username = recvAuth.mLogin;
328         if (username != null) {
329             mUsernameView.setText(username);
330         }
331         String password = recvAuth.mPassword;
332         if (password != null) {
333             mPasswordView.setText(password);
334             // Since username is uneditable, focus on the next editable field
335             if (mSettingsMode) {
336                 mPasswordView.requestFocus();
337             }
338         }
339 
340         if (HostAuth.SCHEME_IMAP.equals(recvAuth.mProtocol)) {
341             String prefix = recvAuth.mDomain;
342             if (prefix != null && prefix.length() > 0) {
343                 mImapPathPrefixView.setText(prefix.substring(1));
344             }
345         } else if (!HostAuth.SCHEME_POP3.equals(recvAuth.mProtocol)) {
346             // Account must either be IMAP or POP3
347             throw new Error("Unknown account type: " + recvAuth.mProtocol);
348         }
349 
350         // The delete policy is set for all legacy accounts. For POP3 accounts, the user sets
351         // the policy explicitly. For IMAP accounts, the policy is set when the Account object
352         // is created. @see AccountSetupBasics#populateSetupData
353         mLoadedDeletePolicy = account.getDeletePolicy();
354         SpinnerOption.setSpinnerOptionValue(mDeletePolicyView, mLoadedDeletePolicy);
355 
356         int flags = recvAuth.mFlags;
357         flags &= ~HostAuth.FLAG_AUTHENTICATE;
358         SpinnerOption.setSpinnerOptionValue(mSecurityTypeView, flags);
359 
360         String hostname = recvAuth.mAddress;
361         if (hostname != null) {
362             mServerView.setText(hostname);
363         }
364 
365         int port = recvAuth.mPort;
366         if (port != HostAuth.PORT_UNKNOWN) {
367             mPortView.setText(Integer.toString(port));
368         } else {
369             updatePortFromSecurityType();
370         }
371 
372         mLoadedRecvAuth = recvAuth;
373         mLoaded = true;
374         validateFields();
375     }
376 
377     /**
378      * Check the values in the fields and decide if it makes sense to enable the "next" button
379      */
validateFields()380     private void validateFields() {
381         if (!mConfigured || !mLoaded) return;
382         boolean enabled = Utility.isTextViewNotEmpty(mUsernameView)
383                 && Utility.isTextViewNotEmpty(mPasswordView)
384                 && Utility.isServerNameValid(mServerView)
385                 && Utility.isPortFieldValid(mPortView);
386         enableNextButton(enabled);
387 
388         String userName = mUsernameView.getText().toString().trim();
389         mCacheLoginCredential = userName;
390 
391         // Warn (but don't prevent) if password has leading/trailing spaces
392         AccountSettingsUtils.checkPasswordSpaces(mContext, mPasswordView);
393     }
394 
getPortFromSecurityType()395     private int getPortFromSecurityType() {
396         int securityType = (Integer)((SpinnerOption)mSecurityTypeView.getSelectedItem()).value;
397         boolean useSsl = ((securityType & HostAuth.FLAG_SSL) != 0);
398         int port = useSsl ? IMAP_PORT_SSL : IMAP_PORT_NORMAL;     // default to IMAP
399         if (HostAuth.SCHEME_POP3.equals(mBaseScheme)) {
400             port = useSsl ? POP3_PORT_SSL : POP3_PORT_NORMAL;
401         }
402         return port;
403     }
404 
updatePortFromSecurityType()405     private void updatePortFromSecurityType() {
406         int port = getPortFromSecurityType();
407         mPortView.setText(Integer.toString(port));
408     }
409 
410     /**
411      * Entry point from Activity after editing settings and verifying them.  Must be FLOW_MODE_EDIT.
412      * Note, we update account here (as well as the account.mHostAuthRecv) because we edit
413      * account's delete policy here.
414      * Blocking - do not call from UI Thread.
415      */
416     @Override
saveSettingsAfterEdit()417     public void saveSettingsAfterEdit() {
418         Account account = SetupData.getAccount();
419         account.update(mContext, account.toContentValues());
420         account.mHostAuthRecv.update(mContext, account.mHostAuthRecv.toContentValues());
421         // Update the backup (side copy) of the accounts
422         AccountBackupRestore.backup(mContext);
423     }
424 
425     /**
426      * Entry point from Activity after entering new settings and verifying them.  For setup mode.
427      */
428     @Override
saveSettingsAfterSetup()429     public void saveSettingsAfterSetup() {
430         Account account = SetupData.getAccount();
431         HostAuth recvAuth = account.getOrCreateHostAuthRecv(mContext);
432         HostAuth sendAuth = account.getOrCreateHostAuthSend(mContext);
433 
434         // Set the username and password for the outgoing settings to the username and
435         // password the user just set for incoming.  Use the verified host address to try and
436         // pick a smarter outgoing address.
437         String hostName = AccountSettingsUtils.inferServerName(recvAuth.mAddress, null, "smtp");
438         sendAuth.setLogin(recvAuth.mLogin, recvAuth.mPassword);
439         sendAuth.setConnection(sendAuth.mProtocol, hostName, sendAuth.mPort, sendAuth.mFlags);
440     }
441 
442     /**
443      * Entry point from Activity, when "next" button is clicked
444      */
445     @Override
onNext()446     public void onNext() {
447         Account account = SetupData.getAccount();
448 
449         // Make sure delete policy is an valid option before using it; otherwise, the results are
450         // indeterminate, I suspect...
451         if (mDeletePolicyView.getVisibility() == View.VISIBLE) {
452             account.setDeletePolicy(
453                     (Integer) ((SpinnerOption) mDeletePolicyView.getSelectedItem()).value);
454         }
455 
456         HostAuth recvAuth = account.getOrCreateHostAuthRecv(mContext);
457         String userName = mUsernameView.getText().toString().trim();
458         String userPassword = mPasswordView.getText().toString();
459         recvAuth.setLogin(userName, userPassword);
460 
461         String serverAddress = mServerView.getText().toString().trim();
462         int serverPort;
463         try {
464             serverPort = Integer.parseInt(mPortView.getText().toString().trim());
465         } catch (NumberFormatException e) {
466             serverPort = getPortFromSecurityType();
467             Log.d(Logging.LOG_TAG, "Non-integer server port; using '" + serverPort + "'");
468         }
469         int securityType = (Integer) ((SpinnerOption) mSecurityTypeView.getSelectedItem()).value;
470         recvAuth.setConnection(mBaseScheme, serverAddress, serverPort, securityType);
471         if (HostAuth.SCHEME_IMAP.equals(recvAuth.mProtocol)) {
472             String prefix = mImapPathPrefixView.getText().toString().trim();
473             recvAuth.mDomain = TextUtils.isEmpty(prefix) ? null : ("/" + prefix);
474         } else {
475             recvAuth.mDomain = null;
476         }
477 
478         // Check for a duplicate account (requires async DB work) and if OK,
479         // proceed with check
480         startDuplicateTaskCheck(
481                 account.mId, serverAddress, mCacheLoginCredential, SetupData.CHECK_INCOMING);
482     }
483 
484     @Override
haveSettingsChanged()485     public boolean haveSettingsChanged() {
486         boolean deletePolicyChanged = false;
487 
488         // Only verify the delete policy if the control is visible (i.e. is a pop3 account)
489         if (mDeletePolicyView.getVisibility() == View.VISIBLE) {
490             int newDeletePolicy =
491                 (Integer)((SpinnerOption)mDeletePolicyView.getSelectedItem()).value;
492             deletePolicyChanged = mLoadedDeletePolicy != newDeletePolicy;
493         }
494 
495         return deletePolicyChanged || super.haveSettingsChanged();
496     }
497 }
498