AccountSetupExchangeFragment.java revision f5418f1f93b02e7fab9f15eb201800b65510998e
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.Email;
20import com.android.email.ExchangeUtils;
21import com.android.email.R;
22import com.android.email.activity.UiUtilities;
23import com.android.email.mail.Store;
24import com.android.email.provider.AccountBackupRestore;
25import com.android.emailcommon.Device;
26import com.android.emailcommon.Logging;
27import com.android.emailcommon.provider.Account;
28import com.android.emailcommon.provider.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;
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
67    // Support for lifecycle
68    private boolean mStarted;
69    /* package */ boolean mLoaded;
70    private String mCacheLoginCredential;
71
72    /**
73     * Called to do initial creation of a fragment.  This is called after
74     * {@link #onAttach(Activity)} and before {@link #onActivityCreated(Bundle)}.
75     */
76    @Override
77    public void onCreate(Bundle savedInstanceState) {
78        if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) {
79            Log.d(Logging.LOG_TAG, "AccountSetupExchangeFragment onCreate");
80        }
81        super.onCreate(savedInstanceState);
82
83        if (savedInstanceState != null) {
84            mCacheLoginCredential = savedInstanceState.getString(STATE_KEY_CREDENTIAL);
85            mLoaded = savedInstanceState.getBoolean(STATE_KEY_LOADED, false);
86        }
87        mBaseScheme = Store.STORE_SCHEME_EAS;
88    }
89
90    @Override
91    public View onCreateView(LayoutInflater inflater, ViewGroup container,
92            Bundle savedInstanceState) {
93        if (Logging.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) UiUtilities.getView(view, R.id.account_username);
104        mPasswordView = (EditText) UiUtilities.getView(view, R.id.account_password);
105        mServerView = (EditText) UiUtilities.getView(view, R.id.account_server);
106        mSslSecurityView = (CheckBox) UiUtilities.getView(view, R.id.account_ssl);
107        mSslSecurityView.setOnCheckedChangeListener(this);
108        mTrustCertificatesView = (CheckBox) UiUtilities.getView(view,
109                R.id.account_trust_certificates);
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        // We're editing an existing account; don't allow modification of the user name
122        if (mSettingsMode) {
123            makeTextViewUneditable(mUsernameView,
124                    getString(R.string.account_setup_username_uneditable_error));
125        }
126        mUsernameView.addTextChangedListener(validationTextWatcher);
127        mPasswordView.addTextChangedListener(validationTextWatcher);
128        mServerView.addTextChangedListener(validationTextWatcher);
129        String deviceId = "";
130        try {
131            deviceId = Device.getDeviceId(context);
132        } catch (IOException e) {
133            // Not required
134        }
135        ((TextView) UiUtilities.getView(view, R.id.device_id)).setText(deviceId);
136
137        // Additional setup only used while in "settings" mode
138        onCreateViewSettingsMode(view);
139
140        return view;
141    }
142
143    @Override
144    public void onActivityCreated(Bundle savedInstanceState) {
145        if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) {
146            Log.d(Logging.LOG_TAG, "AccountSetupExchangeFragment onActivityCreated");
147        }
148        super.onActivityCreated(savedInstanceState);
149    }
150
151    /**
152     * Called when the Fragment is visible to the user.
153     */
154    @Override
155    public void onStart() {
156        if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) {
157            Log.d(Logging.LOG_TAG, "AccountSetupExchangeFragment onStart");
158        }
159        super.onStart();
160        mStarted = true;
161        loadSettings(SetupData.getAccount());
162    }
163
164    /**
165     * Called when the fragment is visible to the user and actively running.
166     */
167    @Override
168    public void onResume() {
169        if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) {
170            Log.d(Logging.LOG_TAG, "AccountSetupExchangeFragment onResume");
171        }
172        super.onResume();
173        validateFields();
174    }
175
176    @Override
177    public void onPause() {
178        if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) {
179            Log.d(Logging.LOG_TAG, "AccountSetupExchangeFragment onPause");
180        }
181        super.onPause();
182    }
183
184    /**
185     * Called when the Fragment is no longer started.
186     */
187    @Override
188    public void onStop() {
189        if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) {
190            Log.d(Logging.LOG_TAG, "AccountSetupExchangeFragment onStop");
191        }
192        super.onStop();
193        mStarted = false;
194    }
195
196    /**
197     * Called when the fragment is no longer in use.
198     */
199    @Override
200    public void onDestroy() {
201        if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) {
202            Log.d(Logging.LOG_TAG, "AccountSetupExchangeFragment onDestroy");
203        }
204        super.onDestroy();
205    }
206
207    @Override
208    public void onSaveInstanceState(Bundle outState) {
209        if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) {
210            Log.d(Logging.LOG_TAG, "AccountSetupExchangeFragment onSaveInstanceState");
211        }
212        super.onSaveInstanceState(outState);
213
214        outState.putString(STATE_KEY_CREDENTIAL, mCacheLoginCredential);
215        outState.putBoolean(STATE_KEY_LOADED, mLoaded);
216    }
217
218    /**
219     * Activity provides callbacks here.  This also triggers loading and setting up the UX
220     */
221    @Override
222    public void setCallback(Callback callback) {
223        super.setCallback(callback);
224        if (mStarted) {
225            loadSettings(SetupData.getAccount());
226        }
227    }
228
229    /**
230     * Force the given account settings to be loaded using {@link #loadSettings(Account)}.
231     *
232     * @return true if the loaded values pass validation
233     */
234    private boolean forceLoadSettings(Account account) {
235        mLoaded = false;
236        return loadSettings(account);
237    }
238
239    /**
240     * Load the given account settings into the UI and then ensure the settings are valid.
241     * As an optimization, if the settings have already been loaded, the UI will not be
242     * updated, but, the account fields will still be validated.
243     *
244     * @return true if the loaded values pass validation
245     */
246    /*package*/ boolean loadSettings(Account account) {
247        if (mLoaded) return validateFields();
248
249        HostAuth hostAuth = account.mHostAuthRecv;
250
251        String userName = hostAuth.mLogin;
252        if (userName != null) {
253            // Add a backslash to the start of the username, but only if the username has no
254            // backslash in it.
255            if (userName.indexOf('\\') < 0) {
256                userName = "\\" + userName;
257            }
258            mUsernameView.setText(userName);
259        }
260
261        if (hostAuth.mPassword != null) {
262            mPasswordView.setText(hostAuth.mPassword);
263        }
264
265        String protocol = hostAuth.mProtocol;
266        if (protocol == null || !protocol.startsWith("eas")) {
267            throw new Error("Unknown account type: " + protocol);
268        }
269
270        if (hostAuth.mAddress != null) {
271            mServerView.setText(hostAuth.mAddress);
272        }
273
274        boolean ssl = 0 != (hostAuth.mFlags & HostAuth.FLAG_SSL);
275        boolean trustCertificates = 0 != (hostAuth.mFlags & HostAuth.FLAG_TRUST_ALL);
276        mSslSecurityView.setChecked(ssl);
277        mTrustCertificatesView.setChecked(trustCertificates);
278        showTrustCertificates(ssl);
279
280        mLoadedRecvAuth = hostAuth;
281        mLoaded = true;
282        return validateFields();
283    }
284
285    private boolean usernameFieldValid(EditText usernameView) {
286        return Utility.isTextViewNotEmpty(usernameView) &&
287            !usernameView.getText().toString().equals("\\");
288    }
289
290    /**
291     * Check the values in the fields and decide if it makes sense to enable the "next" button
292     * @return true if all fields are valid, false if any fields are incomplete
293     */
294    private boolean validateFields() {
295        if (!mLoaded) return false;
296        boolean enabled = usernameFieldValid(mUsernameView)
297                && Utility.isTextViewNotEmpty(mPasswordView)
298                && Utility.isTextViewNotEmpty(mServerView);
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        UiUtilities.setVisibilitySafe(getView(), R.id.account_trust_certificates_divider, mode);
317    }
318
319    /**
320     * Entry point from Activity after editing settings and verifying them.  Must be FLOW_MODE_EDIT.
321     * Blocking - do not call from UI Thread.
322     */
323    @Override
324    public void saveSettingsAfterEdit() {
325        Account account = SetupData.getAccount();
326        account.mHostAuthRecv.update(mContext, account.mHostAuthRecv.toContentValues());
327        account.mHostAuthSend.update(mContext, account.mHostAuthSend.toContentValues());
328        // For EAS, notify ExchangeService that the password has changed
329        try {
330            ExchangeUtils.getExchangeService(mContext, null).hostChanged(account.mId);
331        } catch (RemoteException e) {
332            // Nothing to be done if this fails
333        }
334        // Update the backup (side copy) of the accounts
335        AccountBackupRestore.backup(mContext);
336    }
337
338    /**
339     * Entry point from Activity after entering new settings and verifying them.  For setup mode.
340     */
341    @Override
342    public void saveSettingsAfterSetup() {
343    }
344
345    /**
346     * Entry point from Activity after entering new settings and verifying them.  For setup mode.
347     */
348    public boolean setHostAuthFromAutodiscover(HostAuth newHostAuth) {
349        Account account = SetupData.getAccount();
350        account.mHostAuthSend = newHostAuth;
351        account.mHostAuthRecv = newHostAuth;
352        // Auto discovery may have changed the auth settings; force load them
353        return forceLoadSettings(account);
354    }
355
356    /**
357     * Implements AccountCheckSettingsFragment.Callbacks
358     */
359    @Override
360    public void onAutoDiscoverComplete(int result, HostAuth hostAuth) {
361        AccountSetupExchange activity = (AccountSetupExchange) getActivity();
362        activity.onAutoDiscoverComplete(result, hostAuth);
363    }
364
365    /**
366     * Entry point from Activity, when "next" button is clicked
367     */
368    @Override
369    public void onNext() {
370        Account account = SetupData.getAccount();
371
372        String userName = mUsernameView.getText().toString().trim();
373        if (userName.startsWith("\\")) {
374            userName = userName.substring(1);
375        }
376        mCacheLoginCredential = userName;
377        String userPassword = mPasswordView.getText().toString();
378
379        int flags = 0;
380        if (mSslSecurityView.isChecked()) {
381            flags |= HostAuth.FLAG_SSL;
382        }
383        if (mTrustCertificatesView.isChecked()) {
384            flags |= HostAuth.FLAG_TRUST_ALL;
385        }
386        String serverAddress = mServerView.getText().toString().trim();
387
388        int port = mSslSecurityView.isChecked() ? 443 : 80;
389        HostAuth sendAuth = account.getOrCreateHostAuthSend(mContext);
390        sendAuth.setLogin(userName, userPassword);
391        sendAuth.setConnection(mBaseScheme, serverAddress, port, flags);
392        sendAuth.mDomain = null;
393
394        HostAuth recvAuth = account.getOrCreateHostAuthRecv(mContext);
395        recvAuth.setLogin(userName, userPassword);
396        recvAuth.setConnection(mBaseScheme, serverAddress, port, flags);
397        recvAuth.mDomain = null;
398
399        // Check for a duplicate account (requires async DB work) and if OK, proceed with check
400        startDuplicateTaskCheck(account.mId, serverAddress, mCacheLoginCredential,
401                SetupData.CHECK_INCOMING);
402    }
403
404}
405