AccountSetupExchangeFragment.java revision 3a5c1fb274a9ce72d708d88509bf2607cb018ddd
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.ExchangeUtils;
22import com.android.email.R;
23import com.android.emailcommon.Device;
24import com.android.emailcommon.Logging;
25import com.android.emailcommon.provider.EmailContent.Account;
26import com.android.emailcommon.provider.EmailContent.HostAuth;
27import com.android.emailcommon.utility.Utility;
28
29import android.app.Activity;
30import android.content.Context;
31import android.os.Bundle;
32import android.os.RemoteException;
33import android.text.Editable;
34import android.text.TextWatcher;
35import android.util.Log;
36import android.view.LayoutInflater;
37import android.view.View;
38import android.view.ViewGroup;
39import android.widget.CheckBox;
40import android.widget.CompoundButton;
41import android.widget.CompoundButton.OnCheckedChangeListener;
42import android.widget.EditText;
43import android.widget.TextView;
44
45import java.io.IOException;
46import java.net.URI;
47import java.net.URISyntaxException;
48
49/**
50 * Provides generic setup for Exchange accounts.
51 *
52 * This fragment is used by AccountSetupExchange (for creating accounts) and by AccountSettingsXL
53 * (for editing existing accounts).
54 */
55public class AccountSetupExchangeFragment extends AccountServerBaseFragment
56        implements OnCheckedChangeListener {
57
58    private final static String STATE_KEY_CREDENTIAL = "AccountSetupExchangeFragment.credential";
59    private final static String STATE_KEY_LOADED = "AccountSetupExchangeFragment.loaded";
60
61    private EditText mUsernameView;
62    private EditText mPasswordView;
63    private EditText mServerView;
64    private CheckBox mSslSecurityView;
65    private CheckBox mTrustCertificatesView;
66    private View mTrustCertificatesDivider;
67
68    // Support for lifecycle
69    private boolean mStarted;
70    /* package */ boolean mLoaded;
71    private String mCacheLoginCredential;
72
73    /**
74     * Called to do initial creation of a fragment.  This is called after
75     * {@link #onAttach(Activity)} and before {@link #onActivityCreated(Bundle)}.
76     */
77    @Override
78    public void onCreate(Bundle savedInstanceState) {
79        if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
80            Log.d(Logging.LOG_TAG, "AccountSetupExchangeFragment onCreate");
81        }
82        super.onCreate(savedInstanceState);
83
84        if (savedInstanceState != null) {
85            mCacheLoginCredential = savedInstanceState.getString(STATE_KEY_CREDENTIAL);
86            mLoaded = savedInstanceState.getBoolean(STATE_KEY_LOADED, false);
87        }
88    }
89
90    @Override
91    public View onCreateView(LayoutInflater inflater, ViewGroup container,
92            Bundle savedInstanceState) {
93        if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
94            Log.d(Logging.LOG_TAG, "AccountSetupExchangeFragment onCreateView");
95        }
96        int layoutId = mSettingsMode
97                ? R.layout.account_settings_exchange_fragment
98                : R.layout.account_setup_exchange_fragment;
99
100        View view = inflater.inflate(layoutId, container, false);
101        Context context = getActivity();
102
103        mUsernameView = (EditText) view.findViewById(R.id.account_username);
104        mPasswordView = (EditText) view.findViewById(R.id.account_password);
105        mServerView = (EditText) view.findViewById(R.id.account_server);
106        mSslSecurityView = (CheckBox) view.findViewById(R.id.account_ssl);
107        mSslSecurityView.setOnCheckedChangeListener(this);
108        mTrustCertificatesView = (CheckBox) view.findViewById(R.id.account_trust_certificates);
109        mTrustCertificatesDivider = view.findViewById(R.id.account_trust_certificates_divider);
110
111        // Calls validateFields() which enables or disables the Next button
112        // based on the fields' validity.
113        TextWatcher validationTextWatcher = new TextWatcher() {
114            public void afterTextChanged(Editable s) {
115                validateFields();
116            }
117
118            public void beforeTextChanged(CharSequence s, int start, int count, int after) { }
119            public void onTextChanged(CharSequence s, int start, int before, int count) { }
120        };
121        mUsernameView.addTextChangedListener(validationTextWatcher);
122        mPasswordView.addTextChangedListener(validationTextWatcher);
123        mServerView.addTextChangedListener(validationTextWatcher);
124
125        try {
126            String deviceId = Device.getDeviceId(context);
127            ((TextView) view.findViewById(R.id.device_id)).setText(deviceId);
128        } catch (IOException e) {
129            // Not required
130        }
131
132        // Additional setup only used while in "settings" mode
133        onCreateViewSettingsMode(view);
134
135        return view;
136    }
137
138    @Override
139    public void onActivityCreated(Bundle savedInstanceState) {
140        if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
141            Log.d(Logging.LOG_TAG, "AccountSetupExchangeFragment onActivityCreated");
142        }
143        super.onActivityCreated(savedInstanceState);
144    }
145
146    /**
147     * Called when the Fragment is visible to the user.
148     */
149    @Override
150    public void onStart() {
151        if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
152            Log.d(Logging.LOG_TAG, "AccountSetupExchangeFragment onStart");
153        }
154        super.onStart();
155        mStarted = true;
156        loadSettings(SetupData.getAccount());
157    }
158
159    /**
160     * Called when the fragment is visible to the user and actively running.
161     */
162    @Override
163    public void onResume() {
164        if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
165            Log.d(Logging.LOG_TAG, "AccountSetupExchangeFragment onResume");
166        }
167        super.onResume();
168        validateFields();
169    }
170
171    @Override
172    public void onPause() {
173        if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
174            Log.d(Logging.LOG_TAG, "AccountSetupExchangeFragment onPause");
175        }
176        super.onPause();
177    }
178
179    /**
180     * Called when the Fragment is no longer started.
181     */
182    @Override
183    public void onStop() {
184        if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
185            Log.d(Logging.LOG_TAG, "AccountSetupExchangeFragment onStop");
186        }
187        super.onStop();
188        mStarted = false;
189    }
190
191    /**
192     * Called when the fragment is no longer in use.
193     */
194    @Override
195    public void onDestroy() {
196        if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
197            Log.d(Logging.LOG_TAG, "AccountSetupExchangeFragment onDestroy");
198        }
199        super.onDestroy();
200    }
201
202    @Override
203    public void onSaveInstanceState(Bundle outState) {
204        if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
205            Log.d(Logging.LOG_TAG, "AccountSetupExchangeFragment onSaveInstanceState");
206        }
207        super.onSaveInstanceState(outState);
208
209        outState.putString(STATE_KEY_CREDENTIAL, mCacheLoginCredential);
210        outState.putBoolean(STATE_KEY_LOADED, mLoaded);
211    }
212
213    /**
214     * Activity provides callbacks here.  This also triggers loading and setting up the UX
215     */
216    @Override
217    public void setCallback(Callback callback) {
218        super.setCallback(callback);
219        if (mStarted) {
220            loadSettings(SetupData.getAccount());
221        }
222    }
223
224    /**
225     * Load the current settings into the UI
226     *
227     * @return true if the loaded values pass validation
228     */
229    /* package */ boolean loadSettings(Account account) {
230        if (mLoaded) return validateFields();
231
232        HostAuth hostAuth = account.mHostAuthRecv;
233
234        String userName = hostAuth.mLogin;
235        if (userName != null) {
236            // Add a backslash to the start of the username, but only if the username has no
237            // backslash in it.
238            if (userName.indexOf('\\') < 0) {
239                userName = "\\" + userName;
240            }
241            mUsernameView.setText(userName);
242        }
243
244        if (hostAuth.mPassword != null) {
245            mPasswordView.setText(hostAuth.mPassword);
246        }
247
248        String protocol = hostAuth.mProtocol;
249        if (protocol == null || !protocol.startsWith("eas")) {
250            throw new Error("Unknown account type: " + account.getStoreUri(mContext));
251        }
252
253        if (hostAuth.mAddress != null) {
254            mServerView.setText(hostAuth.mAddress);
255        }
256
257        boolean ssl = 0 != (hostAuth.mFlags & HostAuth.FLAG_SSL);
258        boolean trustCertificates = 0 != (hostAuth.mFlags & HostAuth.FLAG_TRUST_ALL_CERTIFICATES);
259        mSslSecurityView.setChecked(ssl);
260        mTrustCertificatesView.setChecked(trustCertificates);
261        showTrustCertificates(ssl);
262
263        try {
264            mLoadedUri = getUri();
265        } catch (URISyntaxException ignore) {
266            // ignore; should not happen
267        }
268
269        mLoaded = true;
270        return validateFields();
271    }
272
273    private boolean usernameFieldValid(EditText usernameView) {
274        return Utility.isTextViewNotEmpty(usernameView) &&
275            !usernameView.getText().toString().equals("\\");
276    }
277
278    /**
279     * Check the values in the fields and decide if it makes sense to enable the "next" button
280     * @return true if all fields are valid, false if any fields are incomplete
281     */
282    private boolean validateFields() {
283        if (!mLoaded) return false;
284        boolean enabled = usernameFieldValid(mUsernameView)
285                && Utility.isTextViewNotEmpty(mPasswordView)
286                && Utility.isTextViewNotEmpty(mServerView);
287        if (enabled) {
288            try {
289                URI uri = getUri();
290            } catch (URISyntaxException use) {
291                enabled = false;
292            }
293        }
294        enableNextButton(enabled);
295
296        // Warn (but don't prevent) if password has leading/trailing spaces
297        AccountSettingsUtils.checkPasswordSpaces(mContext, mPasswordView);
298
299        return enabled;
300    }
301
302    public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
303        if (buttonView.getId() == R.id.account_ssl) {
304            showTrustCertificates(isChecked);
305        }
306    }
307
308    public void showTrustCertificates(boolean visible) {
309        int mode = visible ? View.VISIBLE : View.GONE;
310        mTrustCertificatesView.setVisibility(mode);
311        // Divider is optional (only on XL layouts)
312        if (mTrustCertificatesDivider != null) {
313            mTrustCertificatesDivider.setVisibility(mode);
314        }
315    }
316
317    /**
318     * Entry point from Activity after editing settings and verifying them.  Must be FLOW_MODE_EDIT.
319     * Blocking - do not call from UI Thread.
320     */
321    @Override
322    public void saveSettingsAfterEdit() {
323        Account account = SetupData.getAccount();
324        account.mHostAuthRecv.update(mContext, account.mHostAuthRecv.toContentValues());
325        account.mHostAuthSend.update(mContext, account.mHostAuthSend.toContentValues());
326        // For EAS, notify ExchangeService that the password has changed
327        try {
328            ExchangeUtils.getExchangeService(mContext, null).hostChanged(account.mId);
329        } catch (RemoteException e) {
330            // Nothing to be done if this fails
331        }
332        // Update the backup (side copy) of the accounts
333        AccountBackupRestore.backupAccounts(mContext);
334    }
335
336    /**
337     * Entry point from Activity after entering new settings and verifying them.  For setup mode.
338     */
339    @Override
340    public void saveSettingsAfterSetup() {
341    }
342
343    /**
344     * Entry point from Activity after entering new settings and verifying them.  For setup mode.
345     */
346    public boolean setHostAuthFromAutodiscover(HostAuth newHostAuth) {
347        Account account = SetupData.getAccount();
348        account.mHostAuthSend = newHostAuth;
349        account.mHostAuthRecv = newHostAuth;
350        return loadSettings(account);
351    }
352
353    /**
354     * Attempt to create a URI from the fields provided.  Throws URISyntaxException if there's
355     * a problem with the user input.
356     * @return a URI built from the account setup fields
357     */
358    @Override
359    protected URI getUri() throws URISyntaxException {
360        boolean sslRequired = mSslSecurityView.isChecked();
361        boolean trustCertificates = mTrustCertificatesView.isChecked();
362        String scheme = (sslRequired)
363                        ? (trustCertificates ? "eas+ssl+trustallcerts" : "eas+ssl+")
364                        : "eas";
365        String userName = mUsernameView.getText().toString().trim();
366        // Remove a leading backslash, if there is one, since we now automatically put one at
367        // the start of the username field
368        if (userName.startsWith("\\")) {
369            userName = userName.substring(1);
370        }
371        mCacheLoginCredential = userName;
372        String userInfo = userName + ":" + mPasswordView.getText();
373        String host = mServerView.getText().toString().trim();
374        String path = null;
375
376        URI uri = new URI(
377                scheme,
378                userInfo,
379                host,
380                0,
381                path,
382                null,
383                null);
384
385        return uri;
386    }
387
388    /**
389     * Implements AccountCheckSettingsFragment.Callbacks
390     */
391    @Override
392    public void onAutoDiscoverComplete(int result, HostAuth hostAuth) {
393        AccountSetupExchange activity = (AccountSetupExchange) getActivity();
394        activity.onAutoDiscoverComplete(result, hostAuth);
395    }
396
397    /**
398     * Entry point from Activity, when "next" button is clicked
399     */
400    @Override
401    public void onNext() {
402        try {
403            URI uri = getUri();
404            Account setupAccount = SetupData.getAccount();
405            setupAccount.setStoreUri(mContext, uri.toString());
406            setupAccount.setSenderUri(mContext, uri.toString());
407
408            // Check for a duplicate account (requires async DB work) and if OK, proceed with check
409            startDuplicateTaskCheck(setupAccount.mId, uri.getHost(), mCacheLoginCredential,
410                    SetupData.CHECK_INCOMING);
411        } catch (URISyntaxException use) {
412            /*
413             * It's unrecoverable if we cannot create a URI from components that
414             * we validated to be safe.
415             */
416            throw new Error(use);
417        }
418    }
419}
420