1 package com.android.email.activity.setup; 2 3 import android.content.Context; 4 import android.os.Bundle; 5 import android.os.Parcelable; 6 import android.text.Editable; 7 import android.text.TextUtils; 8 import android.text.TextWatcher; 9 import android.util.AttributeSet; 10 import android.view.LayoutInflater; 11 import android.view.View; 12 import android.view.View.OnClickListener; 13 import android.widget.EditText; 14 import android.widget.LinearLayout; 15 import android.widget.TextView; 16 17 import com.android.email.R; 18 import com.android.email.activity.UiUtilities; 19 import com.android.emailcommon.Device; 20 import com.android.emailcommon.VendorPolicyLoader.OAuthProvider; 21 import com.android.emailcommon.provider.Credential; 22 import com.android.emailcommon.provider.HostAuth; 23 import com.google.common.annotations.VisibleForTesting; 24 25 import java.io.IOException; 26 27 public class AuthenticationView extends LinearLayout implements OnClickListener { 28 29 private final static String SUPER_STATE = "super_state"; 30 private final static String SAVE_PASSWORD = "save_password"; 31 private final static String SAVE_OFFER_OAUTH = "save_offer_oauth"; 32 private final static String SAVE_USE_OAUTH = "save_use_oauth"; 33 private final static String SAVE_OAUTH_PROVIDER = "save_oauth_provider"; 34 35 // Views 36 private TextView mAuthenticationHeader; 37 private View mPasswordWrapper; 38 private View mOAuthWrapper; 39 private View mNoAuthWrapper; 40 private TextView mPasswordLabel; 41 private EditText mPasswordEdit; 42 private TextView mOAuthLabel; 43 private View mClearPasswordView; 44 private View mClearOAuthView; 45 private View mAddAuthenticationView; 46 47 private TextWatcher mValidationTextWatcher; 48 49 private boolean mOfferOAuth; 50 private boolean mUseOAuth; 51 private String mOAuthProvider; 52 53 private boolean mAuthenticationValid; 54 private AuthenticationCallback mAuthenticationCallback; 55 56 public interface AuthenticationCallback { 57 public void onValidateStateChanged(); 58 59 public void onRequestSignIn(); 60 } 61 62 public AuthenticationView(Context context) { 63 this(context, null); 64 } 65 66 public AuthenticationView(Context context, AttributeSet attrs) { 67 this(context, attrs, 0); 68 } 69 70 public AuthenticationView(Context context, AttributeSet attrs, int defstyle) { 71 super(context, attrs, defstyle); 72 LayoutInflater.from(context).inflate(R.layout.authentication_view, this, true); 73 } 74 75 76 @Override 77 public void onFinishInflate() { 78 super.onFinishInflate(); 79 mPasswordWrapper = UiUtilities.getView(this, R.id.password_wrapper); 80 mOAuthWrapper = UiUtilities.getView(this, R.id.oauth_wrapper); 81 mNoAuthWrapper = UiUtilities.getView(this, R.id.no_auth_wrapper); 82 mPasswordEdit = UiUtilities.getView(this, R.id.password_edit); 83 mOAuthLabel = UiUtilities.getView(this, R.id.oauth_label); 84 mClearPasswordView = UiUtilities.getView(this, R.id.clear_password); 85 mClearOAuthView = UiUtilities.getView(this, R.id.clear_oauth); 86 mAddAuthenticationView = UiUtilities.getView(this, R.id.add_authentication); 87 // Don't use UiUtilities here, in some configurations, these view doesn't exist and 88 // UiUtilities throws an exception in this case. 89 mPasswordLabel = (TextView)findViewById(R.id.password_label); 90 mAuthenticationHeader = (TextView)findViewById(R.id.authentication_header); 91 92 mClearPasswordView.setOnClickListener(this); 93 mClearOAuthView.setOnClickListener(this); 94 mAddAuthenticationView.setOnClickListener(this); 95 96 mValidationTextWatcher = new TextWatcher() { 97 @Override 98 public void afterTextChanged(Editable s) { 99 validateFields(); 100 } 101 102 @Override 103 public void beforeTextChanged(CharSequence s, int start, int count, int after) { } 104 @Override 105 public void onTextChanged(CharSequence s, int start, int before, int count) { } 106 }; 107 mPasswordEdit.addTextChangedListener(mValidationTextWatcher); 108 } 109 110 public void setAuthenticationCallback(final AuthenticationCallback host) { 111 mAuthenticationCallback = host; 112 } 113 114 public boolean getAuthValid() { 115 if (mOfferOAuth & mUseOAuth) { 116 return mOAuthProvider != null; 117 } else { 118 return !TextUtils.isEmpty(mPasswordEdit.getText()); 119 } 120 } 121 122 @VisibleForTesting 123 public void setPassword(final String password) { 124 mPasswordEdit.setText(password); 125 } 126 127 public String getPassword() { 128 return mPasswordEdit.getText().toString(); 129 } 130 131 public String getOAuthProvider() { 132 return mOAuthProvider; 133 } 134 135 private void validateFields() { 136 boolean valid = getAuthValid(); 137 if (valid != mAuthenticationValid) { 138 mAuthenticationCallback.onValidateStateChanged(); 139 mAuthenticationValid = valid; 140 } 141 // Warn (but don't prevent) if password has leading/trailing spaces 142 AccountSettingsUtils.checkPasswordSpaces(getContext(), mPasswordEdit); 143 } 144 145 public void setAuthInfo(final boolean offerOAuth, final HostAuth hostAuth) { 146 mOfferOAuth = offerOAuth; 147 148 if (mOfferOAuth) { 149 final Credential cred = hostAuth.getCredential(getContext()); 150 if (cred != null) { 151 // We're authenticated with OAuth. 152 mUseOAuth = true; 153 mOAuthProvider = cred.mProviderId; 154 } else { 155 mUseOAuth = false; 156 } 157 } else { 158 // We're using a POP or Exchange account, which does not offer oAuth. 159 mUseOAuth = false; 160 } 161 mPasswordEdit.setText(hostAuth.mPassword); 162 163 if (mOfferOAuth && mUseOAuth) { 164 // We're authenticated with OAuth. 165 final OAuthProvider provider = AccountSettingsUtils.findOAuthProvider( 166 getContext(), mOAuthProvider); 167 mOAuthLabel.setText(getContext().getString(R.string.signed_in_with_service_label, 168 provider.label)); 169 } 170 171 updateVisibility(); 172 validateFields(); 173 } 174 175 private void updateVisibility() { 176 if (mOfferOAuth) { 177 if (mAuthenticationHeader != null) { 178 mAuthenticationHeader.setVisibility(View.VISIBLE); 179 mAuthenticationHeader.setText(R.string.authentication_label); 180 } 181 if (mUseOAuth) { 182 // We're authenticated with OAuth. 183 mOAuthWrapper.setVisibility(View.VISIBLE); 184 mPasswordWrapper.setVisibility(View.GONE); 185 mNoAuthWrapper.setVisibility(View.GONE); 186 if (mPasswordLabel != null) { 187 mPasswordLabel.setVisibility(View.VISIBLE); 188 } 189 } else if (!TextUtils.isEmpty(getPassword())) { 190 // We're authenticated with a password. 191 mOAuthWrapper.setVisibility(View.GONE); 192 mPasswordWrapper.setVisibility(View.VISIBLE); 193 mNoAuthWrapper.setVisibility(View.GONE); 194 if (TextUtils.isEmpty(mPasswordEdit.getText())) { 195 mPasswordEdit.requestFocus(); 196 } 197 mClearPasswordView.setVisibility(View.VISIBLE); 198 } else { 199 // We have no authentication, we need to allow either password or oauth. 200 mOAuthWrapper.setVisibility(View.GONE); 201 mPasswordWrapper.setVisibility(View.GONE); 202 mNoAuthWrapper.setVisibility(View.VISIBLE); 203 } 204 } else { 205 // We're using a POP or Exchange account, which does not offer oAuth. 206 if (mAuthenticationHeader != null) { 207 mAuthenticationHeader.setVisibility(View.VISIBLE); 208 mAuthenticationHeader.setText(R.string.account_setup_incoming_password_label); 209 } 210 mOAuthWrapper.setVisibility(View.GONE); 211 mPasswordWrapper.setVisibility(View.VISIBLE); 212 mNoAuthWrapper.setVisibility(View.GONE); 213 mClearPasswordView.setVisibility(View.GONE); 214 if (TextUtils.isEmpty(mPasswordEdit.getText())) { 215 mPasswordEdit.requestFocus(); 216 } 217 if (mPasswordLabel != null) { 218 mPasswordLabel.setVisibility(View.GONE); 219 } 220 } 221 } 222 223 @Override 224 public Parcelable onSaveInstanceState() { 225 Bundle bundle = new Bundle(); 226 bundle.putParcelable(SUPER_STATE, super.onSaveInstanceState()); 227 bundle.putBoolean(SAVE_OFFER_OAUTH, mOfferOAuth); 228 bundle.putBoolean(SAVE_USE_OAUTH, mUseOAuth); 229 bundle.putString(SAVE_PASSWORD, getPassword()); 230 bundle.putString(SAVE_OAUTH_PROVIDER, mOAuthProvider); 231 return bundle; 232 } 233 234 @Override 235 public void onRestoreInstanceState(Parcelable parcelable) { 236 if (parcelable instanceof Bundle) { 237 Bundle bundle = (Bundle)parcelable; 238 super.onRestoreInstanceState(bundle.getParcelable(SUPER_STATE)); 239 mOfferOAuth = bundle.getBoolean(SAVE_OFFER_OAUTH); 240 mUseOAuth = bundle.getBoolean(SAVE_USE_OAUTH); 241 mOAuthProvider = bundle.getString(SAVE_OAUTH_PROVIDER); 242 243 final String password = bundle.getString(SAVE_PASSWORD); 244 mPasswordEdit.setText(password); 245 if (!TextUtils.isEmpty(mOAuthProvider)) { 246 final OAuthProvider provider = AccountSettingsUtils.findOAuthProvider( 247 getContext(), mOAuthProvider); 248 if (provider != null) { 249 mOAuthLabel.setText(getContext().getString(R.string.signed_in_with_service_label, 250 provider.label)); 251 } 252 } 253 updateVisibility(); 254 } 255 } 256 257 @Override 258 public void onClick(View view) { 259 if (view == mClearPasswordView) { 260 mPasswordEdit.setText(null); 261 updateVisibility(); 262 validateFields(); 263 } else if (view == mClearOAuthView) { 264 mUseOAuth = false; 265 mOAuthProvider = null; 266 updateVisibility(); 267 validateFields(); 268 } else if (view == mAddAuthenticationView) { 269 mAuthenticationCallback.onRequestSignIn(); 270 } 271 } 272 } 273