Home | History | Annotate | Download | only in setup
      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 
     17 package com.android.email.activity.setup;
     18 
     19 import android.app.Activity;
     20 import android.content.Context;
     21 import android.content.Intent;
     22 import android.net.Uri;
     23 import android.os.Bundle;
     24 import android.os.RemoteException;
     25 import android.text.Editable;
     26 import android.text.TextWatcher;
     27 import android.util.Log;
     28 import android.view.LayoutInflater;
     29 import android.view.View;
     30 import android.view.ViewGroup;
     31 import android.widget.CheckBox;
     32 import android.widget.CompoundButton;
     33 import android.widget.CompoundButton.OnCheckedChangeListener;
     34 import android.widget.EditText;
     35 import android.widget.TextView;
     36 
     37 import com.android.email.Email;
     38 import com.android.email.R;
     39 import com.android.email.activity.UiUtilities;
     40 import com.android.email.provider.AccountBackupRestore;
     41 import com.android.email.service.EmailServiceUtils;
     42 import com.android.email.view.CertificateSelector;
     43 import com.android.email.view.CertificateSelector.HostCallback;
     44 import com.android.emailcommon.Device;
     45 import com.android.emailcommon.Logging;
     46 import com.android.emailcommon.provider.Account;
     47 import com.android.emailcommon.provider.HostAuth;
     48 import com.android.emailcommon.utility.CertificateRequestor;
     49 import com.android.emailcommon.utility.Utility;
     50 
     51 import java.io.IOException;
     52 
     53 /**
     54  * Provides generic setup for Exchange accounts.
     55  *
     56  * This fragment is used by AccountSetupExchange (for creating accounts) and by AccountSettingsXL
     57  * (for editing existing accounts).
     58  */
     59 public class AccountSetupExchangeFragment extends AccountServerBaseFragment
     60         implements OnCheckedChangeListener, HostCallback {
     61 
     62     private static final int CERTIFICATE_REQUEST = 0;
     63     private final static String STATE_KEY_CREDENTIAL = "AccountSetupExchangeFragment.credential";
     64     private final static String STATE_KEY_LOADED = "AccountSetupExchangeFragment.loaded";
     65 
     66     private EditText mUsernameView;
     67     private EditText mPasswordView;
     68     private EditText mServerView;
     69     private CheckBox mSslSecurityView;
     70     private CheckBox mTrustCertificatesView;
     71     private CertificateSelector mClientCertificateSelector;
     72 
     73     // Support for lifecycle
     74     private boolean mStarted;
     75     /* package */ boolean mLoaded;
     76     private String mCacheLoginCredential;
     77 
     78     /**
     79      * Called to do initial creation of a fragment.  This is called after
     80      * {@link #onAttach(Activity)} and before {@link #onActivityCreated(Bundle)}.
     81      */
     82     @Override
     83     public void onCreate(Bundle savedInstanceState) {
     84         if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) {
     85             Log.d(Logging.LOG_TAG, "AccountSetupExchangeFragment onCreate");
     86         }
     87         super.onCreate(savedInstanceState);
     88 
     89         if (savedInstanceState != null) {
     90             mCacheLoginCredential = savedInstanceState.getString(STATE_KEY_CREDENTIAL);
     91             mLoaded = savedInstanceState.getBoolean(STATE_KEY_LOADED, false);
     92         }
     93         mBaseScheme = HostAuth.SCHEME_EAS;
     94     }
     95 
     96     @Override
     97     public View onCreateView(LayoutInflater inflater, ViewGroup container,
     98             Bundle savedInstanceState) {
     99         if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) {
    100             Log.d(Logging.LOG_TAG, "AccountSetupExchangeFragment onCreateView");
    101         }
    102         int layoutId = mSettingsMode
    103                 ? R.layout.account_settings_exchange_fragment
    104                 : R.layout.account_setup_exchange_fragment;
    105 
    106         View view = inflater.inflate(layoutId, container, false);
    107         final Context context = getActivity();
    108 
    109         mUsernameView = UiUtilities.getView(view, R.id.account_username);
    110         mPasswordView = UiUtilities.getView(view, R.id.account_password);
    111         mServerView = UiUtilities.getView(view, R.id.account_server);
    112         mSslSecurityView = UiUtilities.getView(view, R.id.account_ssl);
    113         mSslSecurityView.setOnCheckedChangeListener(this);
    114         mTrustCertificatesView = UiUtilities.getView(view, R.id.account_trust_certificates);
    115         mClientCertificateSelector = UiUtilities.getView(view, R.id.client_certificate_selector);
    116 
    117         // Calls validateFields() which enables or disables the Next button
    118         // based on the fields' validity.
    119         TextWatcher validationTextWatcher = new TextWatcher() {
    120             public void afterTextChanged(Editable s) {
    121                 validateFields();
    122             }
    123 
    124             public void beforeTextChanged(CharSequence s, int start, int count, int after) { }
    125             public void onTextChanged(CharSequence s, int start, int before, int count) { }
    126         };
    127         // We're editing an existing account; don't allow modification of the user name
    128         if (mSettingsMode) {
    129             makeTextViewUneditable(mUsernameView,
    130                     getString(R.string.account_setup_username_uneditable_error));
    131         }
    132         mUsernameView.addTextChangedListener(validationTextWatcher);
    133         mPasswordView.addTextChangedListener(validationTextWatcher);
    134         mServerView.addTextChangedListener(validationTextWatcher);
    135 
    136         EditText lastView = mServerView;
    137         lastView.setOnEditorActionListener(mDismissImeOnDoneListener);
    138 
    139         String deviceId = "";
    140         try {
    141             deviceId = Device.getDeviceId(context);
    142         } catch (IOException e) {
    143             // Not required
    144         }
    145         ((TextView) UiUtilities.getView(view, R.id.device_id)).setText(deviceId);
    146 
    147         // Additional setup only used while in "settings" mode
    148         onCreateViewSettingsMode(view);
    149 
    150         return view;
    151     }
    152 
    153     @Override
    154     public void onActivityCreated(Bundle savedInstanceState) {
    155         if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) {
    156             Log.d(Logging.LOG_TAG, "AccountSetupExchangeFragment onActivityCreated");
    157         }
    158         super.onActivityCreated(savedInstanceState);
    159         mClientCertificateSelector.setHostActivity(this);
    160     }
    161 
    162     /**
    163      * Called when the Fragment is visible to the user.
    164      */
    165     @Override
    166     public void onStart() {
    167         if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) {
    168             Log.d(Logging.LOG_TAG, "AccountSetupExchangeFragment onStart");
    169         }
    170         super.onStart();
    171         mStarted = true;
    172         loadSettings(SetupData.getAccount());
    173     }
    174 
    175     /**
    176      * Called when the fragment is visible to the user and actively running.
    177      */
    178     @Override
    179     public void onResume() {
    180         if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) {
    181             Log.d(Logging.LOG_TAG, "AccountSetupExchangeFragment onResume");
    182         }
    183         super.onResume();
    184         validateFields();
    185     }
    186 
    187     @Override
    188     public void onPause() {
    189         if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) {
    190             Log.d(Logging.LOG_TAG, "AccountSetupExchangeFragment onPause");
    191         }
    192         super.onPause();
    193     }
    194 
    195     /**
    196      * Called when the Fragment is no longer started.
    197      */
    198     @Override
    199     public void onStop() {
    200         if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) {
    201             Log.d(Logging.LOG_TAG, "AccountSetupExchangeFragment onStop");
    202         }
    203         super.onStop();
    204         mStarted = false;
    205     }
    206 
    207     /**
    208      * Called when the fragment is no longer in use.
    209      */
    210     @Override
    211     public void onDestroy() {
    212         if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) {
    213             Log.d(Logging.LOG_TAG, "AccountSetupExchangeFragment onDestroy");
    214         }
    215         super.onDestroy();
    216     }
    217 
    218     @Override
    219     public void onSaveInstanceState(Bundle outState) {
    220         if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) {
    221             Log.d(Logging.LOG_TAG, "AccountSetupExchangeFragment onSaveInstanceState");
    222         }
    223         super.onSaveInstanceState(outState);
    224 
    225         outState.putString(STATE_KEY_CREDENTIAL, mCacheLoginCredential);
    226         outState.putBoolean(STATE_KEY_LOADED, mLoaded);
    227     }
    228 
    229     /**
    230      * Activity provides callbacks here.  This also triggers loading and setting up the UX
    231      */
    232     @Override
    233     public void setCallback(Callback callback) {
    234         super.setCallback(callback);
    235         if (mStarted) {
    236             loadSettings(SetupData.getAccount());
    237         }
    238     }
    239 
    240     /**
    241      * Force the given account settings to be loaded using {@link #loadSettings(Account)}.
    242      *
    243      * @return true if the loaded values pass validation
    244      */
    245     private boolean forceLoadSettings(Account account) {
    246         mLoaded = false;
    247         return loadSettings(account);
    248     }
    249 
    250     /**
    251      * Load the given account settings into the UI and then ensure the settings are valid.
    252      * As an optimization, if the settings have already been loaded, the UI will not be
    253      * updated, but, the account fields will still be validated.
    254      *
    255      * @return true if the loaded values pass validation
    256      */
    257     /*package*/ boolean loadSettings(Account account) {
    258         if (mLoaded) return validateFields();
    259 
    260         HostAuth hostAuth = account.mHostAuthRecv;
    261 
    262         String userName = hostAuth.mLogin;
    263         if (userName != null) {
    264             // Add a backslash to the start of the username, but only if the username has no
    265             // backslash in it.
    266             if (userName.indexOf('\\') < 0) {
    267                 userName = "\\" + userName;
    268             }
    269             mUsernameView.setText(userName);
    270         }
    271 
    272         if (hostAuth.mPassword != null) {
    273             mPasswordView.setText(hostAuth.mPassword);
    274             // Since username is uneditable, focus on the next editable field
    275             if (mSettingsMode) {
    276                 mPasswordView.requestFocus();
    277             }
    278         }
    279 
    280         String protocol = hostAuth.mProtocol;
    281         if (protocol == null || !protocol.startsWith("eas")) {
    282             throw new Error("Unknown account type: " + protocol);
    283         }
    284 
    285         if (hostAuth.mAddress != null) {
    286             mServerView.setText(hostAuth.mAddress);
    287         }
    288 
    289         boolean ssl = 0 != (hostAuth.mFlags & HostAuth.FLAG_SSL);
    290         boolean trustCertificates = 0 != (hostAuth.mFlags & HostAuth.FLAG_TRUST_ALL);
    291         mSslSecurityView.setChecked(ssl);
    292         mTrustCertificatesView.setChecked(trustCertificates);
    293         if (hostAuth.mClientCertAlias != null) {
    294             mClientCertificateSelector.setCertificate(hostAuth.mClientCertAlias);
    295         }
    296         onUseSslChanged(ssl);
    297 
    298         mLoadedRecvAuth = hostAuth;
    299         mLoaded = true;
    300         return validateFields();
    301     }
    302 
    303     private boolean usernameFieldValid(EditText usernameView) {
    304         return Utility.isTextViewNotEmpty(usernameView) &&
    305             !usernameView.getText().toString().equals("\\");
    306     }
    307 
    308     /**
    309      * Check the values in the fields and decide if it makes sense to enable the "next" button
    310      * @return true if all fields are valid, false if any fields are incomplete
    311      */
    312     private boolean validateFields() {
    313         if (!mLoaded) return false;
    314         boolean enabled = usernameFieldValid(mUsernameView)
    315                 && Utility.isTextViewNotEmpty(mPasswordView)
    316                 && Utility.isServerNameValid(mServerView);
    317         enableNextButton(enabled);
    318 
    319         // Warn (but don't prevent) if password has leading/trailing spaces
    320         AccountSettingsUtils.checkPasswordSpaces(mContext, mPasswordView);
    321 
    322         return enabled;
    323     }
    324 
    325     @Override
    326     public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
    327         if (buttonView.getId() == R.id.account_ssl) {
    328             onUseSslChanged(isChecked);
    329         }
    330     }
    331 
    332     public void onUseSslChanged(boolean useSsl) {
    333         int mode = useSsl ? View.VISIBLE : View.GONE;
    334         mTrustCertificatesView.setVisibility(mode);
    335         UiUtilities.setVisibilitySafe(getView(), R.id.account_trust_certificates_divider, mode);
    336         mClientCertificateSelector.setVisibility(mode);
    337         UiUtilities.setVisibilitySafe(getView(), R.id.client_certificate_divider, mode);
    338     }
    339 
    340     @Override
    341     public void onCheckSettingsComplete(final int result) {
    342         if (result == AccountCheckSettingsFragment.CHECK_SETTINGS_CLIENT_CERTIFICATE_NEEDED) {
    343             mSslSecurityView.setChecked(true);
    344             onCertificateRequested();
    345             return;
    346         }
    347         super.onCheckSettingsComplete(result);
    348     }
    349 
    350 
    351     /**
    352      * Entry point from Activity after editing settings and verifying them.  Must be FLOW_MODE_EDIT.
    353      * Blocking - do not call from UI Thread.
    354      */
    355     @Override
    356     public void saveSettingsAfterEdit() {
    357         Account account = SetupData.getAccount();
    358         account.mHostAuthRecv.update(mContext, account.mHostAuthRecv.toContentValues());
    359         account.mHostAuthSend.update(mContext, account.mHostAuthSend.toContentValues());
    360         // For EAS, notify ExchangeService that the password has changed
    361         try {
    362             EmailServiceUtils.getExchangeService(mContext, null).hostChanged(account.mId);
    363         } catch (RemoteException e) {
    364             // Nothing to be done if this fails
    365         }
    366         // Update the backup (side copy) of the accounts
    367         AccountBackupRestore.backup(mContext);
    368     }
    369 
    370     /**
    371      * Entry point from Activity after entering new settings and verifying them.  For setup mode.
    372      */
    373     @Override
    374     public void saveSettingsAfterSetup() {
    375     }
    376 
    377     /**
    378      * Entry point from Activity after entering new settings and verifying them.  For setup mode.
    379      */
    380     public boolean setHostAuthFromAutodiscover(HostAuth newHostAuth) {
    381         Account account = SetupData.getAccount();
    382         account.mHostAuthSend = newHostAuth;
    383         account.mHostAuthRecv = newHostAuth;
    384         // Auto discovery may have changed the auth settings; force load them
    385         return forceLoadSettings(account);
    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         Account account = SetupData.getAccount();
    403 
    404         String userName = mUsernameView.getText().toString().trim();
    405         if (userName.startsWith("\\")) {
    406             userName = userName.substring(1);
    407         }
    408         mCacheLoginCredential = userName;
    409         String userPassword = mPasswordView.getText().toString();
    410 
    411         int flags = 0;
    412         if (mSslSecurityView.isChecked()) {
    413             flags |= HostAuth.FLAG_SSL;
    414         }
    415         if (mTrustCertificatesView.isChecked()) {
    416             flags |= HostAuth.FLAG_TRUST_ALL;
    417         }
    418         String certAlias = mClientCertificateSelector.getCertificate();
    419         String serverAddress = mServerView.getText().toString().trim();
    420 
    421         int port = mSslSecurityView.isChecked() ? 443 : 80;
    422         HostAuth sendAuth = account.getOrCreateHostAuthSend(mContext);
    423         sendAuth.setLogin(userName, userPassword);
    424         sendAuth.setConnection(mBaseScheme, serverAddress, port, flags, certAlias);
    425         sendAuth.mDomain = null;
    426 
    427         HostAuth recvAuth = account.getOrCreateHostAuthRecv(mContext);
    428         recvAuth.setLogin(userName, userPassword);
    429         recvAuth.setConnection(mBaseScheme, serverAddress, port, flags, certAlias);
    430         recvAuth.mDomain = null;
    431 
    432         // Check for a duplicate account (requires async DB work) and if OK, proceed with check
    433         startDuplicateTaskCheck(account.mId, serverAddress, mCacheLoginCredential,
    434                 SetupData.CHECK_INCOMING);
    435     }
    436 
    437     @Override
    438     public void onCertificateRequested() {
    439         Intent intent = new Intent(CertificateRequestor.ACTION_REQUEST_CERT);
    440         intent.setData(Uri.parse("eas://com.android.emailcommon/certrequest"));
    441         startActivityForResult(intent, CERTIFICATE_REQUEST);
    442     }
    443 
    444     @Override
    445     public void onActivityResult(int requestCode, int resultCode, Intent data) {
    446         if (requestCode == CERTIFICATE_REQUEST && resultCode == Activity.RESULT_OK) {
    447             String certAlias = data.getStringExtra(CertificateRequestor.RESULT_ALIAS);
    448             if (certAlias != null) {
    449                 mClientCertificateSelector.setCertificate(certAlias);
    450             }
    451         }
    452     }
    453 
    454 }
    455