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