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