• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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  */
16 package com.android.email.activity.setup;
17 
18 import android.app.Activity;
19 import android.content.Context;
20 import android.content.Intent;
21 import android.os.Bundle;
22 import android.text.Editable;
23 import android.text.TextUtils;
24 import android.text.TextWatcher;
25 import android.text.format.DateUtils;
26 import android.view.LayoutInflater;
27 import android.view.View;
28 import android.view.View.OnClickListener;
29 import android.view.ViewGroup;
30 import android.widget.EditText;
31 import android.widget.TextView;
32 
33 import com.android.email.R;
34 import com.android.email.activity.UiUtilities;
35 import com.android.email.service.EmailServiceUtils;
36 import com.android.email.service.EmailServiceUtils.EmailServiceInfo;
37 import com.android.email.view.CertificateSelector;
38 import com.android.email.view.CertificateSelector.HostCallback;
39 import com.android.emailcommon.Device;
40 import com.android.emailcommon.VendorPolicyLoader.OAuthProvider;
41 import com.android.emailcommon.provider.Credential;
42 import com.android.emailcommon.provider.HostAuth;
43 import com.android.emailcommon.utility.CertificateRequestor;
44 import com.android.mail.utils.LogUtils;
45 
46 import java.io.IOException;
47 import java.util.List;
48 
49 public 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 {
onCredentialsComplete(Bundle results)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      */
newInstance(final String email, final String protocol, final String clientCert, final boolean passwordFailed, final boolean standalone)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
onCreateView(final LayoutInflater inflater, final ViewGroup container, final Bundle savedInstanceState)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
onActivityCreated(final Bundle savedInstanceState)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
onDestroy()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 
setPasswordFailed(final boolean failed)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 
validatePassword()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
onActivityResult(final int requestCode, final int resultCode, final Intent data)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
onClick(final View view)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 
getPassword()316     public String getPassword() {
317         if (mOfferOAuth) {
318             return mImapPasswordText.getText().toString();
319         } else {
320             return mRegularPasswordText.getText().toString();
321         }
322     }
323 
getCredentialResults()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 
populateHostAuthWithResults(final Context context, final HostAuth hostAuth, final Bundle results)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 
getClientCertificate()361     public String getClientCertificate() {
362         return mClientCertificateSelector.getCertificate();
363     }
364 
365     @Override
onCertificateRequested()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