Home | History | Annotate | Download | only in setup
      1 /*
      2  * Copyright (C) 2014 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 package com.android.email.activity.setup;
     17 
     18 import android.app.Activity;
     19 import android.content.Context;
     20 import android.content.Intent;
     21 import android.os.Bundle;
     22 import android.text.Editable;
     23 import android.text.TextUtils;
     24 import android.text.TextWatcher;
     25 import android.text.format.DateUtils;
     26 import android.view.LayoutInflater;
     27 import android.view.View;
     28 import android.view.View.OnClickListener;
     29 import android.view.ViewGroup;
     30 import android.widget.EditText;
     31 import android.widget.TextView;
     32 
     33 import com.android.email.R;
     34 import com.android.email.activity.UiUtilities;
     35 import com.android.email.service.EmailServiceUtils;
     36 import com.android.email.service.EmailServiceUtils.EmailServiceInfo;
     37 import com.android.email.view.CertificateSelector;
     38 import com.android.email.view.CertificateSelector.HostCallback;
     39 import com.android.emailcommon.Device;
     40 import com.android.emailcommon.VendorPolicyLoader.OAuthProvider;
     41 import com.android.emailcommon.provider.Credential;
     42 import com.android.emailcommon.provider.HostAuth;
     43 import com.android.emailcommon.utility.CertificateRequestor;
     44 import com.android.mail.utils.LogUtils;
     45 
     46 import java.io.IOException;
     47 import java.util.List;
     48 
     49 public class AccountSetupCredentialsFragment extends AccountSetupFragment
     50         implements OnClickListener, HostCallback {
     51 
     52     private static final int CERTIFICATE_REQUEST = 1000;
     53 
     54     private static final String EXTRA_EMAIL = "email";
     55     private static final String EXTRA_PROTOCOL = "protocol";
     56     private static final String EXTRA_PASSWORD_FAILED = "password_failed";
     57     private static final String EXTRA_STANDALONE = "standalone";
     58 
     59     public static final String EXTRA_PASSWORD = "password";
     60     public static final String EXTRA_CLIENT_CERT = "certificate";
     61     public static final String EXTRA_OAUTH_PROVIDER = "provider";
     62     public static final String EXTRA_OAUTH_ACCESS_TOKEN = "accessToken";
     63     public static final String EXTRA_OAUTH_REFRESH_TOKEN = "refreshToken";
     64     public static final String EXTRA_OAUTH_EXPIRES_IN_SECONDS = "expiresInSeconds";
     65 
     66     private View mOAuthGroup;
     67     private View mOAuthButton;
     68     private EditText mImapPasswordText;
     69     private EditText mRegularPasswordText;
     70     private TextWatcher mValidationTextWatcher;
     71     private TextView mPasswordWarningLabel;
     72     private TextView mEmailConfirmationLabel;
     73     private TextView mEmailConfirmation;
     74     private CertificateSelector mClientCertificateSelector;
     75     private View mDeviceIdSection;
     76     private TextView mDeviceId;
     77 
     78     private String mEmailAddress;
     79     private boolean mOfferOAuth;
     80     private boolean mOfferCerts;
     81     private String mProviderId;
     82     List<OAuthProvider> mOauthProviders;
     83 
     84     private Context mAppContext;
     85 
     86     private Bundle mResults;
     87 
     88     public interface Callback extends AccountSetupFragment.Callback {
     89         void onCredentialsComplete(Bundle results);
     90     }
     91 
     92     /**
     93      * Create a new instance of this fragment with the appropriate email and protocol
     94      * @param email login address for OAuth purposes
     95      * @param protocol protocol of the service we're gathering credentials for
     96      * @param clientCert alias of existing client cert
     97      * @param passwordFailed true if the password attempt previously failed
     98      * @param standalone true if this is not being inserted in the setup flow
     99      * @return new fragment instance
    100      */
    101     public static AccountSetupCredentialsFragment newInstance(final String email,
    102             final String protocol, final String clientCert, final boolean passwordFailed,
    103             final boolean standalone) {
    104         final AccountSetupCredentialsFragment f = new AccountSetupCredentialsFragment();
    105         final Bundle b = new Bundle(5);
    106         b.putString(EXTRA_EMAIL, email);
    107         b.putString(EXTRA_PROTOCOL, protocol);
    108         b.putString(EXTRA_CLIENT_CERT, clientCert);
    109         b.putBoolean(EXTRA_PASSWORD_FAILED, passwordFailed);
    110         b.putBoolean(EXTRA_STANDALONE, standalone);
    111         f.setArguments(b);
    112         return f;
    113     }
    114 
    115     @Override
    116     public View onCreateView(final LayoutInflater inflater, final ViewGroup container,
    117             final Bundle savedInstanceState) {
    118         final boolean standalone = getArguments().getBoolean(EXTRA_STANDALONE);
    119         final View view;
    120         if (standalone) {
    121             view = inflater.inflate(R.layout.account_credentials_fragment, container, false);
    122             mNextButton = UiUtilities.getView(view, R.id.done);
    123             mNextButton.setOnClickListener(this);
    124             mPreviousButton = UiUtilities.getView(view, R.id.cancel);
    125             mPreviousButton.setOnClickListener(this);
    126         } else {
    127             // TODO: real headline string instead of sign_in_title
    128             view = inflateTemplatedView(inflater, container,
    129                     R.layout.account_setup_credentials_fragment, R.string.sign_in_title);
    130         }
    131 
    132         mImapPasswordText = UiUtilities.getView(view, R.id.imap_password);
    133         mRegularPasswordText = UiUtilities.getView(view, R.id.regular_password);
    134         mOAuthGroup = UiUtilities.getView(view, R.id.oauth_group);
    135         mOAuthButton = UiUtilities.getView(view, R.id.sign_in_with_oauth);
    136         mOAuthButton.setOnClickListener(this);
    137         mClientCertificateSelector = UiUtilities.getView(view, R.id.client_certificate_selector);
    138         mDeviceIdSection = UiUtilities.getView(view, R.id.device_id_section);
    139         mDeviceId = UiUtilities.getView(view, R.id.device_id);
    140         mPasswordWarningLabel  = UiUtilities.getView(view, R.id.wrong_password_warning_label);
    141         mEmailConfirmationLabel  = UiUtilities.getView(view, R.id.email_confirmation_label);
    142         mEmailConfirmation  = UiUtilities.getView(view, R.id.email_confirmation);
    143 
    144         mClientCertificateSelector.setHostCallback(this);
    145         mClientCertificateSelector.setCertificate(getArguments().getString(EXTRA_CLIENT_CERT));
    146 
    147         // After any text edits, call validateFields() which enables or disables the Next button
    148         mValidationTextWatcher = new TextWatcher() {
    149             @Override
    150             public void afterTextChanged(Editable s) {
    151                 validatePassword();
    152             }
    153 
    154             @Override
    155             public void beforeTextChanged(CharSequence s, int start, int count, int after) { }
    156             @Override
    157             public void onTextChanged(CharSequence s, int start, int before, int count) { }
    158         };
    159         mImapPasswordText.addTextChangedListener(mValidationTextWatcher);
    160         mRegularPasswordText.addTextChangedListener(mValidationTextWatcher);
    161 
    162         return view;
    163     }
    164 
    165     @Override
    166     public void onActivityCreated(final Bundle savedInstanceState) {
    167         super.onActivityCreated(savedInstanceState);
    168 
    169         mAppContext = getActivity().getApplicationContext();
    170         mEmailAddress = getArguments().getString(EXTRA_EMAIL);
    171         final String protocol = getArguments().getString(EXTRA_PROTOCOL);
    172         mOauthProviders = AccountSettingsUtils.getAllOAuthProviders(mAppContext);
    173         mOfferCerts = true;
    174         if (protocol != null) {
    175             final EmailServiceInfo info = EmailServiceUtils.getServiceInfo(mAppContext, protocol);
    176             if (info != null) {
    177                 if (mOauthProviders.size() > 0) {
    178                     mOfferOAuth = info.offerOAuth;
    179                 }
    180                 mOfferCerts = info.offerCerts;
    181             }
    182         } else {
    183             // For now, we might not know what protocol we're using, so just default to
    184             // offering oauth
    185             if (mOauthProviders.size() > 0) {
    186                 mOfferOAuth = true;
    187             }
    188         }
    189         // We may want to disable OAuth during the new account setup flow, but allow it elsewhere
    190         final boolean standalone = getArguments().getBoolean(EXTRA_STANDALONE);
    191         final boolean skipOAuth = !standalone &&
    192                 getActivity().getResources().getBoolean(R.bool.skip_oauth_on_setup);
    193         mOfferOAuth = mOfferOAuth && !skipOAuth;
    194 
    195         mOAuthGroup.setVisibility(mOfferOAuth ? View.VISIBLE : View.GONE);
    196         mRegularPasswordText.setVisibility(mOfferOAuth ? View.GONE : View.VISIBLE);
    197 
    198         if (mOfferCerts) {
    199             // TODO: Here we always offer certificates for any protocol that allows them (i.e.
    200             // Exchange). But they will really only be available if we are using SSL security.
    201             // Trouble is, first time through here, we haven't offered the user the choice of
    202             // which security type to use.
    203             mClientCertificateSelector.setVisibility(mOfferCerts ? View.VISIBLE : View.GONE);
    204             mDeviceIdSection.setVisibility(mOfferCerts ? View.VISIBLE : View.GONE);
    205             String deviceId = "";
    206             try {
    207                 deviceId = Device.getDeviceId(getActivity());
    208             } catch (IOException e) {
    209                 // Not required
    210             }
    211             mDeviceId.setText(deviceId);
    212         }
    213         final boolean passwordFailed = getArguments().getBoolean(EXTRA_PASSWORD_FAILED, false);
    214         setPasswordFailed(passwordFailed);
    215         validatePassword();
    216     }
    217 
    218     @Override
    219     public void onDestroy() {
    220         super.onDestroy();
    221         if (mImapPasswordText != null) {
    222             mImapPasswordText.removeTextChangedListener(mValidationTextWatcher);
    223             mImapPasswordText = null;
    224         }
    225         if (mRegularPasswordText != null) {
    226             mRegularPasswordText.removeTextChangedListener(mValidationTextWatcher);
    227             mRegularPasswordText = null;
    228         }
    229     }
    230 
    231     public void setPasswordFailed(final boolean failed) {
    232         if (failed) {
    233             mPasswordWarningLabel.setVisibility(View.VISIBLE);
    234             mEmailConfirmationLabel.setVisibility(View.VISIBLE);
    235             mEmailConfirmation.setVisibility(View.VISIBLE);
    236             mEmailConfirmation.setText(mEmailAddress);
    237         } else {
    238             mPasswordWarningLabel.setVisibility(View.GONE);
    239             mEmailConfirmationLabel.setVisibility(View.GONE);
    240             mEmailConfirmation.setVisibility(View.GONE);
    241         }
    242     }
    243 
    244     public void validatePassword() {
    245         setNextButtonEnabled(!TextUtils.isEmpty(getPassword()));
    246         // Warn (but don't prevent) if password has leading/trailing spaces
    247         AccountSettingsUtils.checkPasswordSpaces(mAppContext, mImapPasswordText);
    248         AccountSettingsUtils.checkPasswordSpaces(mAppContext, mRegularPasswordText);
    249     }
    250 
    251     @Override
    252     public void onActivityResult(final int requestCode, final int resultCode, final Intent data) {
    253         if (requestCode == CERTIFICATE_REQUEST) {
    254             if (resultCode == Activity.RESULT_OK) {
    255                 final String certAlias = data.getStringExtra(CertificateRequestor.RESULT_ALIAS);
    256                 if (certAlias != null) {
    257                     mClientCertificateSelector.setCertificate(certAlias);
    258                 }
    259             } else {
    260                 LogUtils.e(LogUtils.TAG, "Unknown result from certificate request %d",
    261                         resultCode);
    262             }
    263         } else if (requestCode == OAuthAuthenticationActivity.REQUEST_OAUTH) {
    264             if (resultCode == OAuthAuthenticationActivity.RESULT_OAUTH_SUCCESS) {
    265                 final String accessToken = data.getStringExtra(
    266                         OAuthAuthenticationActivity.EXTRA_OAUTH_ACCESS_TOKEN);
    267                 final String refreshToken = data.getStringExtra(
    268                         OAuthAuthenticationActivity.EXTRA_OAUTH_REFRESH_TOKEN);
    269                 final int expiresInSeconds = data.getIntExtra(
    270                         OAuthAuthenticationActivity.EXTRA_OAUTH_EXPIRES_IN, 0);
    271                 final Bundle results = new Bundle(4);
    272                 results.putString(EXTRA_OAUTH_PROVIDER, mProviderId);
    273                 results.putString(EXTRA_OAUTH_ACCESS_TOKEN, accessToken);
    274                 results.putString(EXTRA_OAUTH_REFRESH_TOKEN, refreshToken);
    275                 results.putInt(EXTRA_OAUTH_EXPIRES_IN_SECONDS, expiresInSeconds);
    276                 mResults = results;
    277                 final Callback callback = (Callback) getActivity();
    278                 callback.onCredentialsComplete(results);
    279             } else if (resultCode == OAuthAuthenticationActivity.RESULT_OAUTH_FAILURE
    280                     || resultCode == OAuthAuthenticationActivity.RESULT_OAUTH_USER_CANCELED) {
    281                 LogUtils.i(LogUtils.TAG, "Result from oauth %d", resultCode);
    282             } else {
    283                 LogUtils.wtf(LogUtils.TAG, "Unknown result code from OAUTH: %d", resultCode);
    284             }
    285         } else {
    286             LogUtils.e(LogUtils.TAG, "Unknown request code for onActivityResult in"
    287                     + " AccountSetupBasics: %d", requestCode);
    288         }
    289     }
    290 
    291     @Override
    292     public void onClick(final View view) {
    293         final int viewId = view.getId();
    294         if (viewId == R.id.sign_in_with_oauth) {
    295             // TODO currently the only oauth provider we support is google.
    296             // If we ever have more than 1 oauth provider, then we need to implement some sort
    297             // of picker UI. For now, just always take the first oauth provider.
    298             if (mOauthProviders.size() > 0) {
    299                 mProviderId = mOauthProviders.get(0).id;
    300                 final Intent i = new Intent(getActivity(), OAuthAuthenticationActivity.class);
    301                 i.putExtra(OAuthAuthenticationActivity.EXTRA_EMAIL_ADDRESS, mEmailAddress);
    302                 i.putExtra(OAuthAuthenticationActivity.EXTRA_PROVIDER, mProviderId);
    303                 startActivityForResult(i, OAuthAuthenticationActivity.REQUEST_OAUTH);
    304             }
    305         } else if (viewId == R.id.done) {
    306             final Callback callback = (Callback) getActivity();
    307             callback.onNextButton();
    308         } else if (viewId == R.id.cancel) {
    309             final Callback callback = (Callback) getActivity();
    310             callback.onBackPressed();
    311         } else {
    312             super.onClick(view);
    313         }
    314     }
    315 
    316     public String getPassword() {
    317         if (mOfferOAuth) {
    318             return mImapPasswordText.getText().toString();
    319         } else {
    320             return mRegularPasswordText.getText().toString();
    321         }
    322     }
    323 
    324     public Bundle getCredentialResults() {
    325         if (mResults != null) {
    326             return mResults;
    327         }
    328 
    329         final Bundle results = new Bundle(2);
    330         results.putString(EXTRA_PASSWORD, getPassword());
    331         results.putString(EXTRA_CLIENT_CERT, getClientCertificate());
    332         return results;
    333     }
    334 
    335     public static void populateHostAuthWithResults(final Context context, final HostAuth hostAuth,
    336             final Bundle results) {
    337         if (results == null) {
    338             return;
    339         }
    340         final String password = results.getString(AccountSetupCredentialsFragment.EXTRA_PASSWORD);
    341         if (!TextUtils.isEmpty(password)) {
    342             hostAuth.mPassword = password;
    343             hostAuth.removeCredential();
    344         } else {
    345             Credential cred = hostAuth.getOrCreateCredential(context);
    346             cred.mProviderId = results.getString(
    347                     AccountSetupCredentialsFragment.EXTRA_OAUTH_PROVIDER);
    348             cred.mAccessToken = results.getString(
    349                     AccountSetupCredentialsFragment.EXTRA_OAUTH_ACCESS_TOKEN);
    350             cred.mRefreshToken = results.getString(
    351                     AccountSetupCredentialsFragment.EXTRA_OAUTH_REFRESH_TOKEN);
    352             cred.mExpiration = System.currentTimeMillis()
    353                     + results.getInt(
    354                     AccountSetupCredentialsFragment.EXTRA_OAUTH_EXPIRES_IN_SECONDS, 0)
    355                     * DateUtils.SECOND_IN_MILLIS;
    356             hostAuth.mPassword = null;
    357         }
    358         hostAuth.mClientCertAlias = results.getString(EXTRA_CLIENT_CERT);
    359     }
    360 
    361     public String getClientCertificate() {
    362         return mClientCertificateSelector.getCertificate();
    363     }
    364 
    365     @Override
    366     public void onCertificateRequested() {
    367         final Intent intent = new Intent(getString(R.string.intent_exchange_cert_action));
    368         intent.setData(CertificateRequestor.CERTIFICATE_REQUEST_URI);
    369         // We don't set EXTRA_HOST or EXTRA_PORT here because we don't know the final host/port
    370         // that we're connecting to yet, and autodiscover might point us somewhere else
    371         startActivityForResult(intent, CERTIFICATE_REQUEST);
    372     }
    373 }
    374