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