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 { onCredentialsComplete(Bundle results)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 */ newInstance(final String email, final String protocol, final String clientCert, final boolean passwordFailed, final boolean standalone)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 onCreateView(final LayoutInflater inflater, final ViewGroup container, final Bundle savedInstanceState)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 afterTextChanged(Editable s)157 public void afterTextChanged(Editable s) { 158 validatePassword(); 159 } 160 161 @Override beforeTextChanged(CharSequence s, int start, int count, int after)162 public void beforeTextChanged(CharSequence s, int start, int count, int after) { } 163 @Override onTextChanged(CharSequence s, int start, int before, int count)164 public void onTextChanged(CharSequence s, int start, int before, int count) { } 165 } 166 167 @Override onActivityCreated(final Bundle savedInstanceState)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 onDestroy()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 setPasswordFailed(final boolean failed)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 validatePassword()246 public void validatePassword() { 247 setNextButtonEnabled(!TextUtils.isEmpty(getPassword())); 248 } 249 250 @Override onActivityResult(final int requestCode, final int resultCode, final Intent data)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 onClick(final View view)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 getPassword()315 public String getPassword() { 316 if (mOfferOAuth) { 317 return mImapPasswordText.getText().toString(); 318 } else { 319 return mRegularPasswordText.getText().toString(); 320 } 321 } 322 getCredentialResults()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 populateHostAuthWithResults(final Context context, final HostAuth hostAuth, final Bundle results)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 getClientCertificate()360 public String getClientCertificate() { 361 return mClientCertificateSelector.getCertificate(); 362 } 363 364 @Override onCertificateRequested()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