AccountSetupExchangeFragment.java revision 31d9acbf0623872f9d4a2b3210b5970854b654c7
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.Logging;
24import com.android.emailcommon.provider.EmailContent.Account;
25import com.android.emailcommon.provider.EmailContent.HostAuth;
26import com.android.emailcommon.utility.Utility;
27import com.android.exchange.ExchangeService;
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        //EXCHANGE-REMOVE-SECTION-START
126        // Show device ID
127        try {
128            String deviceId = ExchangeService.getDeviceId(context);
129            ((TextView) view.findViewById(R.id.device_id)).setText(deviceId);
130        } catch (IOException ignore) {
131            // There's nothing we can do here...
132        }
133        //EXCHANGE-REMOVE-SECTION-END
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_CERTIFICATES);
262        mSslSecurityView.setChecked(ssl);
263        mTrustCertificatesView.setChecked(trustCertificates);
264        showTrustCertificates(ssl);
265
266        try {
267            mLoadedUri = getUri();
268        } catch (URISyntaxException ignore) {
269            // ignore; should not happen
270        }
271
272        mLoaded = true;
273        return validateFields();
274    }
275
276    private boolean usernameFieldValid(EditText usernameView) {
277        return Utility.isTextViewNotEmpty(usernameView) &&
278            !usernameView.getText().toString().equals("\\");
279    }
280
281    /**
282     * Check the values in the fields and decide if it makes sense to enable the "next" button
283     * @return true if all fields are valid, false if any fields are incomplete
284     */
285    private boolean validateFields() {
286        if (!mLoaded) return false;
287        boolean enabled = usernameFieldValid(mUsernameView)
288                && Utility.isTextViewNotEmpty(mPasswordView)
289                && Utility.isTextViewNotEmpty(mServerView);
290        if (enabled) {
291            try {
292                URI uri = getUri();
293            } catch (URISyntaxException use) {
294                enabled = false;
295            }
296        }
297        enableNextButton(enabled);
298
299        // Warn (but don't prevent) if password has leading/trailing spaces
300        AccountSettingsUtils.checkPasswordSpaces(mContext, mPasswordView);
301
302        return enabled;
303    }
304
305    public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
306        if (buttonView.getId() == R.id.account_ssl) {
307            showTrustCertificates(isChecked);
308        }
309    }
310
311    public void showTrustCertificates(boolean visible) {
312        int mode = visible ? View.VISIBLE : View.GONE;
313        mTrustCertificatesView.setVisibility(mode);
314        // Divider is optional (only on XL layouts)
315        if (mTrustCertificatesDivider != null) {
316            mTrustCertificatesDivider.setVisibility(mode);
317        }
318    }
319
320    /**
321     * Entry point from Activity after editing settings and verifying them.  Must be FLOW_MODE_EDIT.
322     * Blocking - do not call from UI Thread.
323     */
324    @Override
325    public void saveSettingsAfterEdit() {
326        Account account = SetupData.getAccount();
327        account.mHostAuthRecv.update(mContext, account.mHostAuthRecv.toContentValues());
328        account.mHostAuthSend.update(mContext, account.mHostAuthSend.toContentValues());
329        // For EAS, notify ExchangeService that the password has changed
330        try {
331            ExchangeUtils.getExchangeService(mContext, null).hostChanged(account.mId);
332        } catch (RemoteException e) {
333            // Nothing to be done if this fails
334        }
335        // Update the backup (side copy) of the accounts
336        AccountBackupRestore.backupAccounts(mContext);
337    }
338
339    /**
340     * Entry point from Activity after entering new settings and verifying them.  For setup mode.
341     */
342    @Override
343    public void saveSettingsAfterSetup() {
344    }
345
346    /**
347     * Entry point from Activity after entering new settings and verifying them.  For setup mode.
348     */
349    public boolean setHostAuthFromAutodiscover(HostAuth newHostAuth) {
350        Account account = SetupData.getAccount();
351        account.mHostAuthSend = newHostAuth;
352        account.mHostAuthRecv = newHostAuth;
353        return loadSettings(account);
354    }
355
356    /**
357     * Attempt to create a URI from the fields provided.  Throws URISyntaxException if there's
358     * a problem with the user input.
359     * @return a URI built from the account setup fields
360     */
361    @Override
362    protected URI getUri() throws URISyntaxException {
363        boolean sslRequired = mSslSecurityView.isChecked();
364        boolean trustCertificates = mTrustCertificatesView.isChecked();
365        String scheme = (sslRequired)
366                        ? (trustCertificates ? "eas+ssl+trustallcerts" : "eas+ssl+")
367                        : "eas";
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
379        URI uri = new URI(
380                scheme,
381                userInfo,
382                host,
383                0,
384                path,
385                null,
386                null);
387
388        return uri;
389    }
390
391    /**
392     * Implements AccountCheckSettingsFragment.Callbacks
393     */
394    @Override
395    public void onAutoDiscoverComplete(int result, HostAuth hostAuth) {
396        AccountSetupExchange activity = (AccountSetupExchange) getActivity();
397        activity.onAutoDiscoverComplete(result, hostAuth);
398    }
399
400    /**
401     * Entry point from Activity, when "next" button is clicked
402     */
403    @Override
404    public void onNext() {
405        try {
406            URI uri = getUri();
407            Account setupAccount = SetupData.getAccount();
408            setupAccount.setStoreUri(mContext, uri.toString());
409            setupAccount.setSenderUri(mContext, uri.toString());
410
411            // Check for a duplicate account (requires async DB work) and if OK, proceed with check
412            startDuplicateTaskCheck(setupAccount.mId, uri.getHost(), mCacheLoginCredential,
413                    SetupData.CHECK_INCOMING);
414        } catch (URISyntaxException use) {
415            /*
416             * It's unrecoverable if we cannot create a URI from components that
417             * we validated to be safe.
418             */
419            throw new Error(use);
420        }
421    }
422}
423