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 */ 16package com.android.email.activity.setup; 17 18import android.app.Activity; 19import android.content.Context; 20import android.content.Intent; 21import android.os.Bundle; 22import android.text.Editable; 23import android.text.TextUtils; 24import android.text.TextWatcher; 25import android.text.format.DateUtils; 26import android.view.LayoutInflater; 27import android.view.View; 28import android.view.View.OnClickListener; 29import android.view.ViewGroup; 30import android.widget.EditText; 31import android.widget.TextView; 32 33import com.android.email.R; 34import com.android.email.activity.UiUtilities; 35import com.android.email.service.EmailServiceUtils; 36import com.android.email.service.EmailServiceUtils.EmailServiceInfo; 37import com.android.email.view.CertificateSelector; 38import com.android.email.view.CertificateSelector.HostCallback; 39import com.android.emailcommon.Device; 40import com.android.emailcommon.VendorPolicyLoader.OAuthProvider; 41import com.android.emailcommon.provider.Credential; 42import com.android.emailcommon.provider.HostAuth; 43import com.android.emailcommon.utility.CertificateRequestor; 44import com.android.mail.utils.LogUtils; 45 46import java.io.IOException; 47import java.util.List; 48 49public 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