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