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