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