1package com.android.email.activity.setup;
2
3import android.content.Context;
4import android.os.Bundle;
5import android.os.Parcelable;
6import android.text.Editable;
7import android.text.TextUtils;
8import android.text.TextWatcher;
9import android.util.AttributeSet;
10import android.view.LayoutInflater;
11import android.view.View;
12import android.view.View.OnClickListener;
13import android.widget.EditText;
14import android.widget.LinearLayout;
15import android.widget.TextView;
16
17import com.android.email.R;
18import com.android.email.activity.UiUtilities;
19import com.android.emailcommon.Device;
20import com.android.emailcommon.VendorPolicyLoader.OAuthProvider;
21import com.android.emailcommon.provider.Credential;
22import com.android.emailcommon.provider.HostAuth;
23import com.google.common.annotations.VisibleForTesting;
24
25import java.io.IOException;
26
27public 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