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 PasswordTextWatcher();
    149         mImapPasswordText.addTextChangedListener(mValidationTextWatcher);
    150         mRegularPasswordText.addTextChangedListener(mValidationTextWatcher);
    151 
    152         return view;
    153     }
    154 
    155     private class PasswordTextWatcher implements TextWatcher {
    156         @Override
    157         public void afterTextChanged(Editable s) {
    158             validatePassword();
    159         }
    160 
    161         @Override
    162         public void beforeTextChanged(CharSequence s, int start, int count, int after) { }
    163         @Override
    164         public void onTextChanged(CharSequence s, int start, int before, int count) { }
    165     }
    166 
    167     @Override
    168     public void onActivityCreated(final Bundle savedInstanceState) {
    169         super.onActivityCreated(savedInstanceState);
    170 
    171         mAppContext = getActivity().getApplicationContext();
    172         mEmailAddress = getArguments().getString(EXTRA_EMAIL);
    173         final String protocol = getArguments().getString(EXTRA_PROTOCOL);
    174         mOauthProviders = AccountSettingsUtils.getAllOAuthProviders(mAppContext);
    175         mOfferCerts = true;
    176         if (protocol != null) {
    177             final EmailServiceInfo info = EmailServiceUtils.getServiceInfo(mAppContext, protocol);
    178             if (info != null) {
    179                 if (mOauthProviders.size() > 0) {
    180                     mOfferOAuth = info.offerOAuth;
    181                 }
    182                 mOfferCerts = info.offerCerts;
    183             }
    184         } else {
    185             // For now, we might not know what protocol we're using, so just default to
    186             // offering oauth
    187             if (mOauthProviders.size() > 0) {
    188                 mOfferOAuth = true;
    189             }
    190         }
    191         // We may want to disable OAuth during the new account setup flow, but allow it elsewhere
    192         final boolean standalone = getArguments().getBoolean(EXTRA_STANDALONE);
    193         final boolean skipOAuth = !standalone &&
    194                 getActivity().getResources().getBoolean(R.bool.skip_oauth_on_setup);
    195         mOfferOAuth = mOfferOAuth && !skipOAuth;
    196 
    197         mOAuthGroup.setVisibility(mOfferOAuth ? View.VISIBLE : View.GONE);
    198         mRegularPasswordText.setVisibility(mOfferOAuth ? View.GONE : View.VISIBLE);
    199 
    200         if (mOfferCerts) {
    201             // TODO: Here we always offer certificates for any protocol that allows them (i.e.
    202             // Exchange). But they will really only be available if we are using SSL security.
    203             // Trouble is, first time through here, we haven't offered the user the choice of
    204             // which security type to use.
    205             mClientCertificateSelector.setVisibility(mOfferCerts ? View.VISIBLE : View.GONE);
    206             mDeviceIdSection.setVisibility(mOfferCerts ? View.VISIBLE : View.GONE);
    207             String deviceId = "";
    208             try {
    209                 deviceId = Device.getDeviceId(getActivity());
    210             } catch (IOException e) {
    211                 // Not required
    212             }
    213             mDeviceId.setText(deviceId);
    214         }
    215         final boolean passwordFailed = getArguments().getBoolean(EXTRA_PASSWORD_FAILED, false);
    216         setPasswordFailed(passwordFailed);
    217         validatePassword();
    218     }
    219 
    220     @Override
    221     public void onDestroy() {
    222         super.onDestroy();
    223         if (mImapPasswordText != null) {
    224             mImapPasswordText.removeTextChangedListener(mValidationTextWatcher);
    225             mImapPasswordText = null;
    226         }
    227         if (mRegularPasswordText != null) {
    228             mRegularPasswordText.removeTextChangedListener(mValidationTextWatcher);
    229             mRegularPasswordText = null;
    230         }
    231     }
    232 
    233     public void setPasswordFailed(final boolean failed) {
    234         if (failed) {
    235             mPasswordWarningLabel.setVisibility(View.VISIBLE);
    236             mEmailConfirmationLabel.setVisibility(View.VISIBLE);
    237             mEmailConfirmation.setVisibility(View.VISIBLE);
    238             mEmailConfirmation.setText(mEmailAddress);
    239         } else {
    240             mPasswordWarningLabel.setVisibility(View.GONE);
    241             mEmailConfirmationLabel.setVisibility(View.GONE);
    242             mEmailConfirmation.setVisibility(View.GONE);
    243         }
    244     }
    245 
    246     public void validatePassword() {
    247         setNextButtonEnabled(!TextUtils.isEmpty(getPassword()));
    248     }
    249 
    250     @Override
    251     public void onActivityResult(final int requestCode, final int resultCode, final Intent data) {
    252         if (requestCode == CERTIFICATE_REQUEST) {
    253             if (resultCode == Activity.RESULT_OK) {
    254                 final String certAlias = data.getStringExtra(CertificateRequestor.RESULT_ALIAS);
    255                 if (certAlias != null) {
    256                     mClientCertificateSelector.setCertificate(certAlias);
    257                 }
    258             } else {
    259                 LogUtils.e(LogUtils.TAG, "Unknown result from certificate request %d",
    260                         resultCode);
    261             }
    262         } else if (requestCode == OAuthAuthenticationActivity.REQUEST_OAUTH) {
    263             if (resultCode == OAuthAuthenticationActivity.RESULT_OAUTH_SUCCESS) {
    264                 final String accessToken = data.getStringExtra(
    265                         OAuthAuthenticationActivity.EXTRA_OAUTH_ACCESS_TOKEN);
    266                 final String refreshToken = data.getStringExtra(
    267                         OAuthAuthenticationActivity.EXTRA_OAUTH_REFRESH_TOKEN);
    268                 final int expiresInSeconds = data.getIntExtra(
    269                         OAuthAuthenticationActivity.EXTRA_OAUTH_EXPIRES_IN, 0);
    270                 final Bundle results = new Bundle(4);
    271                 results.putString(EXTRA_OAUTH_PROVIDER, mProviderId);
    272                 results.putString(EXTRA_OAUTH_ACCESS_TOKEN, accessToken);
    273                 results.putString(EXTRA_OAUTH_REFRESH_TOKEN, refreshToken);
    274                 results.putInt(EXTRA_OAUTH_EXPIRES_IN_SECONDS, expiresInSeconds);
    275                 mResults = results;
    276                 final Callback callback = (Callback) getActivity();
    277                 callback.onCredentialsComplete(results);
    278             } else if (resultCode == OAuthAuthenticationActivity.RESULT_OAUTH_FAILURE
    279                     || resultCode == OAuthAuthenticationActivity.RESULT_OAUTH_USER_CANCELED) {
    280                 LogUtils.i(LogUtils.TAG, "Result from oauth %d", resultCode);
    281             } else {
    282                 LogUtils.wtf(LogUtils.TAG, "Unknown result code from OAUTH: %d", resultCode);
    283             }
    284         } else {
    285             LogUtils.e(LogUtils.TAG, "Unknown request code for onActivityResult in"
    286                     + " AccountSetupBasics: %d", requestCode);
    287         }
    288     }
    289 
    290     @Override
    291     public void onClick(final View view) {
    292         final int viewId = view.getId();
    293         if (viewId == R.id.sign_in_with_oauth) {
    294             // TODO currently the only oauth provider we support is google.
    295             // If we ever have more than 1 oauth provider, then we need to implement some sort
    296             // of picker UI. For now, just always take the first oauth provider.
    297             if (mOauthProviders.size() > 0) {
    298                 mProviderId = mOauthProviders.get(0).id;
    299                 final Intent i = new Intent(getActivity(), OAuthAuthenticationActivity.class);
    300                 i.putExtra(OAuthAuthenticationActivity.EXTRA_EMAIL_ADDRESS, mEmailAddress);
    301                 i.putExtra(OAuthAuthenticationActivity.EXTRA_PROVIDER, mProviderId);
    302                 startActivityForResult(i, OAuthAuthenticationActivity.REQUEST_OAUTH);
    303             }
    304         } else if (viewId == R.id.done) {
    305             final Callback callback = (Callback) getActivity();
    306             callback.onNextButton();
    307         } else if (viewId == R.id.cancel) {
    308             final Callback callback = (Callback) getActivity();
    309             callback.onBackPressed();
    310         } else {
    311             super.onClick(view);
    312         }
    313     }
    314 
    315     public String getPassword() {
    316         if (mOfferOAuth) {
    317             return mImapPasswordText.getText().toString();
    318         } else {
    319             return mRegularPasswordText.getText().toString();
    320         }
    321     }
    322 
    323     public Bundle getCredentialResults() {
    324         if (mResults != null) {
    325             return mResults;
    326         }
    327 
    328         final Bundle results = new Bundle(2);
    329         results.putString(EXTRA_PASSWORD, getPassword());
    330         results.putString(EXTRA_CLIENT_CERT, getClientCertificate());
    331         return results;
    332     }
    333 
    334     public static void populateHostAuthWithResults(final Context context, final HostAuth hostAuth,
    335             final Bundle results) {
    336         if (results == null) {
    337             return;
    338         }
    339         final String password = results.getString(AccountSetupCredentialsFragment.EXTRA_PASSWORD);
    340         if (!TextUtils.isEmpty(password)) {
    341             hostAuth.mPassword = password;
    342             hostAuth.removeCredential();
    343         } else {
    344             Credential cred = hostAuth.getOrCreateCredential(context);
    345             cred.mProviderId = results.getString(
    346                     AccountSetupCredentialsFragment.EXTRA_OAUTH_PROVIDER);
    347             cred.mAccessToken = results.getString(
    348                     AccountSetupCredentialsFragment.EXTRA_OAUTH_ACCESS_TOKEN);
    349             cred.mRefreshToken = results.getString(
    350                     AccountSetupCredentialsFragment.EXTRA_OAUTH_REFRESH_TOKEN);
    351             cred.mExpiration = System.currentTimeMillis()
    352                     + results.getInt(
    353                     AccountSetupCredentialsFragment.EXTRA_OAUTH_EXPIRES_IN_SECONDS, 0)
    354                     * DateUtils.SECOND_IN_MILLIS;
    355             hostAuth.mPassword = null;
    356         }
    357         hostAuth.mClientCertAlias = results.getString(EXTRA_CLIENT_CERT);
    358     }
    359 
    360     public String getClientCertificate() {
    361         return mClientCertificateSelector.getCertificate();
    362     }
    363 
    364     @Override
    365     public void onCertificateRequested() {
    366         final Intent intent = new Intent(getString(R.string.intent_exchange_cert_action));
    367         intent.setData(CertificateRequestor.CERTIFICATE_REQUEST_URI);
    368         // We don't set EXTRA_HOST or EXTRA_PORT here because we don't know the final host/port
    369         // that we're connecting to yet, and autodiscover might point us somewhere else
    370         startActivityForResult(intent, CERTIFICATE_REQUEST);
    371     }
    372 }
    373