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