AccountSetupOutgoingFragment.java revision 12b82d9374947c9268217f45befe8a74bd9b60d7
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
17package com.android.email.activity.setup;
18
19import com.android.email.Email;
20import com.android.email.R;
21import com.android.email.activity.UiUtilities;
22import com.android.email.provider.AccountBackupRestore;
23import com.android.emailcommon.Logging;
24import com.android.emailcommon.provider.EmailContent;
25import com.android.emailcommon.provider.EmailContent.Account;
26import com.android.emailcommon.provider.HostAuth;
27import com.android.emailcommon.utility.Utility;
28
29import android.app.Activity;
30import android.content.Context;
31import android.os.Bundle;
32import android.text.Editable;
33import android.text.TextWatcher;
34import android.text.method.DigitsKeyListener;
35import android.util.Log;
36import android.view.LayoutInflater;
37import android.view.View;
38import android.view.ViewGroup;
39import android.widget.AdapterView;
40import android.widget.ArrayAdapter;
41import android.widget.CheckBox;
42import android.widget.CompoundButton;
43import android.widget.CompoundButton.OnCheckedChangeListener;
44import android.widget.EditText;
45import android.widget.Spinner;
46
47import java.net.URI;
48import java.net.URISyntaxException;
49
50/**
51 * Provides UI for SMTP account settings (for IMAP/POP accounts).
52 *
53 * This fragment is used by AccountSetupOutgoing (for creating accounts) and by AccountSettingsXL
54 * (for editing existing accounts).
55 */
56public class AccountSetupOutgoingFragment extends AccountServerBaseFragment
57        implements OnCheckedChangeListener {
58
59    private final static String STATE_KEY_LOADED = "AccountSetupOutgoingFragment.loaded";
60
61    private static final int SMTP_PORT_NORMAL = 587;
62    private static final int SMTP_PORT_SSL    = 465;
63
64    private EditText mUsernameView;
65    private EditText mPasswordView;
66    private EditText mServerView;
67    private EditText mPortView;
68    private CheckBox mRequireLoginView;
69    private Spinner mSecurityTypeView;
70
71    // Support for lifecycle
72    private boolean mStarted;
73    private boolean mLoaded;
74
75    /**
76     * Called to do initial creation of a fragment.  This is called after
77     * {@link #onAttach(Activity)} and before {@link #onActivityCreated(Bundle)}.
78     */
79    @Override
80    public void onCreate(Bundle savedInstanceState) {
81        if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) {
82            Log.d(Logging.LOG_TAG, "AccountSetupOutgoingFragment onCreate");
83        }
84        super.onCreate(savedInstanceState);
85
86        if (savedInstanceState != null) {
87            mLoaded = savedInstanceState.getBoolean(STATE_KEY_LOADED, false);
88        }
89        mBaseScheme = "smtp";
90    }
91
92    @Override
93    public View onCreateView(LayoutInflater inflater, ViewGroup container,
94            Bundle savedInstanceState) {
95        if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) {
96            Log.d(Logging.LOG_TAG, "AccountSetupOutgoingFragment onCreateView");
97        }
98        int layoutId = mSettingsMode
99                ? R.layout.account_settings_outgoing_fragment
100                : R.layout.account_setup_outgoing_fragment;
101
102        View view = inflater.inflate(layoutId, container, false);
103        Context context = getActivity();
104
105        mUsernameView = (EditText) UiUtilities.getView(view, R.id.account_username);
106        mPasswordView = (EditText) UiUtilities.getView(view, R.id.account_password);
107        mServerView = (EditText) UiUtilities.getView(view, R.id.account_server);
108        mPortView = (EditText) UiUtilities.getView(view, R.id.account_port);
109        mRequireLoginView = (CheckBox) UiUtilities.getView(view, R.id.account_require_login);
110        mSecurityTypeView = (Spinner) UiUtilities.getView(view, R.id.account_security_type);
111        mRequireLoginView.setOnCheckedChangeListener(this);
112
113        // Note:  Strings are shared with AccountSetupIncomingFragment
114        SpinnerOption securityTypes[] = {
115            new SpinnerOption(HostAuth.FLAG_NONE, context.getString(
116                    R.string.account_setup_incoming_security_none_label)),
117            new SpinnerOption(HostAuth.FLAG_SSL, context.getString(
118                    R.string.account_setup_incoming_security_ssl_label)),
119            new SpinnerOption(HostAuth.FLAG_SSL | HostAuth.FLAG_TRUST_ALL, context.getString(
120                    R.string.account_setup_incoming_security_ssl_trust_certificates_label)),
121            new SpinnerOption(HostAuth.FLAG_TLS, context.getString(
122                    R.string.account_setup_incoming_security_tls_label)),
123            new SpinnerOption(HostAuth.FLAG_SSL | HostAuth.FLAG_TRUST_ALL, context.getString(
124                    R.string.account_setup_incoming_security_tls_trust_certificates_label)),
125        };
126
127        ArrayAdapter<SpinnerOption> securityTypesAdapter = new ArrayAdapter<SpinnerOption>(context,
128                android.R.layout.simple_spinner_item, securityTypes);
129        securityTypesAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
130        mSecurityTypeView.setAdapter(securityTypesAdapter);
131
132        // Updates the port when the user changes the security type. This allows
133        // us to show a reasonable default which the user can change.
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        // Calls validateFields() which enables or disables the Next button
143        TextWatcher validationTextWatcher = new TextWatcher() {
144            public void afterTextChanged(Editable s) {
145                validateFields();
146            }
147
148            public void beforeTextChanged(CharSequence s, int start, int count, int after) { }
149            public void onTextChanged(CharSequence s, int start, int before, int count) { }
150        };
151        mUsernameView.addTextChangedListener(validationTextWatcher);
152        mPasswordView.addTextChangedListener(validationTextWatcher);
153        mServerView.addTextChangedListener(validationTextWatcher);
154        mPortView.addTextChangedListener(validationTextWatcher);
155
156        // Only allow digits in the port field.
157        mPortView.setKeyListener(DigitsKeyListener.getInstance("0123456789"));
158
159        // Additional setup only used while in "settings" mode
160        onCreateViewSettingsMode(view);
161
162        return view;
163    }
164
165    @Override
166    public void onActivityCreated(Bundle savedInstanceState) {
167        if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) {
168            Log.d(Logging.LOG_TAG, "AccountSetupOutgoingFragment onActivityCreated");
169        }
170        super.onActivityCreated(savedInstanceState);
171    }
172
173    /**
174     * Called when the Fragment is visible to the user.
175     */
176    @Override
177    public void onStart() {
178        if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) {
179            Log.d(Logging.LOG_TAG, "AccountSetupOutgoingFragment onStart");
180        }
181        super.onStart();
182        mStarted = true;
183        loadSettings();
184    }
185
186    /**
187     * Called when the fragment is visible to the user and actively running.
188     */
189    @Override
190    public void onResume() {
191        if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) {
192            Log.d(Logging.LOG_TAG, "AccountSetupOutgoingFragment onResume");
193        }
194        super.onResume();
195        validateFields();
196    }
197
198    @Override
199    public void onPause() {
200        if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) {
201            Log.d(Logging.LOG_TAG, "AccountSetupOutgoingFragment onPause");
202        }
203        super.onPause();
204    }
205
206    /**
207     * Called when the Fragment is no longer started.
208     */
209    @Override
210    public void onStop() {
211        if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) {
212            Log.d(Logging.LOG_TAG, "AccountSetupOutgoingFragment onStop");
213        }
214        super.onStop();
215        mStarted = false;
216    }
217
218    /**
219     * Called when the fragment is no longer in use.
220     */
221    @Override
222    public void onDestroy() {
223        if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) {
224            Log.d(Logging.LOG_TAG, "AccountSetupOutgoingFragment onDestroy");
225        }
226        super.onDestroy();
227    }
228
229    @Override
230    public void onSaveInstanceState(Bundle outState) {
231        if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) {
232            Log.d(Logging.LOG_TAG, "AccountSetupOutgoingFragment onSaveInstanceState");
233        }
234        super.onSaveInstanceState(outState);
235
236        outState.putBoolean(STATE_KEY_LOADED, mLoaded);
237    }
238
239    /**
240     * Activity provides callbacks here.  This also triggers loading and setting up the UX
241     */
242    @Override
243    public void setCallback(Callback callback) {
244        super.setCallback(callback);
245        if (mStarted) {
246            loadSettings();
247        }
248    }
249
250    /**
251     * Load the current settings into the UI
252     */
253    private void loadSettings() {
254        if (mLoaded) return;
255
256        HostAuth sendAuth = SetupData.getAccount().getOrCreateHostAuthSend(mContext);
257        if ((sendAuth.mFlags & HostAuth.FLAG_AUTHENTICATE) != 0) {
258            String username = sendAuth.mLogin;
259            if (username != null) {
260                mUsernameView.setText(username);
261                mRequireLoginView.setChecked(true);
262            }
263
264            String password = sendAuth.mPassword;
265            if (password != null) {
266                mPasswordView.setText(password);
267            }
268        }
269
270        int flags = sendAuth.mFlags & ~HostAuth.FLAG_AUTHENTICATE;
271        SpinnerOption.setSpinnerOptionValue(mSecurityTypeView, flags);
272
273        String hostname = sendAuth.mAddress;
274        if (hostname != null) {
275            mServerView.setText(hostname);
276        }
277
278        int port = sendAuth.mPort;
279        if (port != -1) {
280            mPortView.setText(Integer.toString(port));
281        } else {
282            updatePortFromSecurityType();
283        }
284
285        mLoadedSendAuth = sendAuth;
286        mLoaded = true;
287        validateFields();
288    }
289
290    /**
291     * Preflight the values in the fields and decide if it makes sense to enable the "next" button
292     */
293    private void validateFields() {
294        if (!mLoaded) return;
295        boolean enabled =
296            Utility.isTextViewNotEmpty(mServerView) && Utility.isPortFieldValid(mPortView);
297
298        if (enabled && mRequireLoginView.isChecked()) {
299            enabled = (Utility.isTextViewNotEmpty(mUsernameView)
300                    && Utility.isTextViewNotEmpty(mPasswordView));
301        }
302
303        if (enabled) {
304            try {
305                URI uri = getUri();
306            } catch (URISyntaxException use) {
307                enabled = false;
308            }
309        }
310        enableNextButton(enabled);
311
312        // Warn (but don't prevent) if password has leading/trailing spaces
313        AccountSettingsUtils.checkPasswordSpaces(mContext, mPasswordView);
314   }
315
316    /**
317     * implements OnCheckedChangeListener
318     */
319    @Override
320    public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
321        final int visibility = isChecked ? View.VISIBLE : View.GONE;
322        UiUtilities.setVisibilitySafe(getView(), R.id.account_require_login_settings, visibility);
323        UiUtilities.setVisibilitySafe(getView(), R.id.account_require_login_settings_2, visibility);
324        validateFields();
325    }
326
327    private int getPortFromSecurityType() {
328        int securityType = (Integer)((SpinnerOption)mSecurityTypeView.getSelectedItem()).value;
329        int port = (securityType & HostAuth.FLAG_SSL) != 0 ? SMTP_PORT_SSL : SMTP_PORT_NORMAL;
330        return port;
331    }
332
333    private void updatePortFromSecurityType() {
334        int port = getPortFromSecurityType();
335        mPortView.setText(Integer.toString(port));
336    }
337
338    /**
339     * Entry point from Activity after editing settings and verifying them.  Must be FLOW_MODE_EDIT.
340     * Blocking - do not call from UI Thread.
341     */
342    @Override
343    public void saveSettingsAfterEdit() {
344        Account account = SetupData.getAccount();
345        account.mHostAuthSend.update(mContext, account.mHostAuthSend.toContentValues());
346        // Update the backup (side copy) of the accounts
347        AccountBackupRestore.backup(mContext);
348    }
349
350    /**
351     * Entry point from Activity after entering new settings and verifying them.  For setup mode.
352     */
353    @Override
354    public void saveSettingsAfterSetup() {
355    }
356
357    /**
358     * Attempt to create a URI from the fields provided.  Throws URISyntaxException if there's
359     * a problem with the user input.
360     * @return a URI built from the account setup fields
361     */
362    @Override
363    protected URI getUri() throws URISyntaxException {
364        int securityType = (Integer)((SpinnerOption)mSecurityTypeView.getSelectedItem()).value;
365        String userInfo = null;
366        if (mRequireLoginView.isChecked()) {
367            userInfo = mUsernameView.getText().toString().trim() + ":" + mPasswordView.getText();
368        }
369        String host = mServerView.getText().toString().trim();
370        String path = null;
371        int port = Integer.parseInt(mPortView.getText().toString().trim());
372
373        URI uri = new URI(
374                HostAuth.getSchemeString(mBaseScheme, securityType),
375                userInfo,
376                host,
377                port,
378                path,
379                null,
380                null);
381        return uri;
382    }
383
384    /**
385     * Entry point from Activity, when "next" button is clicked
386     */
387    @Override
388    public void onNext() {
389        EmailContent.Account account = SetupData.getAccount();
390        HostAuth sendAuth = account.getOrCreateHostAuthSend(mContext);
391
392        String userName = mUsernameView.getText().toString().trim();
393        String userPassword = mPasswordView.getText().toString();
394        sendAuth.setLogin(userName, userPassword);
395
396        String serverAddress = mServerView.getText().toString().trim();
397        int serverPort;
398        try {
399            serverPort = Integer.parseInt(mPortView.getText().toString().trim());
400        } catch (NumberFormatException e) {
401            serverPort = getPortFromSecurityType();
402            Log.d(Logging.LOG_TAG, "Non-integer server port; using '" + serverPort + "'");
403        }
404        int securityType = (Integer)((SpinnerOption)mSecurityTypeView.getSelectedItem()).value;
405        sendAuth.setConnection(mBaseScheme, serverAddress, serverPort, securityType);
406        sendAuth.mDomain = null;
407
408        mCallback.onProceedNext(SetupData.CHECK_OUTGOING, this);
409    }
410}
411