Create AuthenticationFragment

This is one fragment that holds all types of
authentication information, e.g. password,
OAuth info, and client certificates. What gets
displayed depends upon the type of account it is
dealing with.
So far this is only used in AccountSetupIncoming,
but later it can be added to other settings fragments.
There are still some issues with this, but I'd like
to check it in sooner than later to unblock other
work.

Change-Id: Iea675ad5c1727f32ca0baa270dfa793ab7109993
This commit is contained in:
Martin Hibdon 2014-01-21 16:57:06 -08:00
parent 018ae522d7
commit 9195a12024
12 changed files with 562 additions and 87 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 254 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -20,6 +20,7 @@
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical" >
<TextView
android:text="@string/account_setup_incoming_username_label"
android:layout_height="wrap_content"
@ -33,19 +34,12 @@
android:hint="@string/account_setup_incoming_username_label"
android:inputType="textEmailAddress"
android:imeOptions="actionNext" />
<TextView
android:text="@string/account_setup_incoming_password_label"
android:layout_height="wrap_content"
<FrameLayout
android:id="@+id/authentication_container"
android:layout_width="match_parent"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textColor="?android:attr/textColorPrimary" />
<EditText
android:id="@+id/account_password"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:hint="@string/account_setup_incoming_password_label"
android:inputType="textPassword"
android:imeOptions="actionNext" />
android:layout_height="wrap_content"/>
<!-- This text may be changed in code if the server is IMAP, etc. -->
<TextView
android:id="@+id/account_server_label"
@ -87,10 +81,6 @@
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:contentDescription="@string/account_setup_incoming_security_label" />
<include
android:id="@+id/client_certificate_selector"
layout="@layout/client_certificate_selector"
android:visibility="gone" />
<TextView
android:id="@+id/account_delete_policy_label"
android:text="@string/account_setup_incoming_delete_policy_label"
@ -103,6 +93,7 @@
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:contentDescription="@string/account_setup_incoming_delete_policy_label" />
<LinearLayout
android:id="@+id/imap_path_prefix_section"
android:layout_width="match_parent"
@ -122,22 +113,4 @@
android:inputType="text"
android:imeOptions="actionDone" />
</LinearLayout>
<LinearLayout
android:id="@+id/device_id_section"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:visibility="gone">
<TextView
android:text="@string/account_setup_exchange_device_id_label"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:textColor="?android:attr/textColorPrimary" />
<TextView
android:id="@+id/device_id"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textColor="#ffbebebe" />
</LinearLayout>
</LinearLayout>

View File

@ -0,0 +1,180 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2010 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical" >
<LinearLayout
android:id="@+id/imap_authentication"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical" >
<TextView
android:id="@+id/authentication_label"
android:text="@string/authenticiation_label"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textColor="?android:attr/textColorPrimary" />
<RelativeLayout
android:id="@+id/authentication_selection"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/add_authentication"
android:contentDescription="@string/clear_authentication_label"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:layout_alignParentLeft="true"
android:clickable="true"
android:src="@drawable/ic_add_authentication"
android:layout_centerVertical="true" />
<TextView
android:id="@+id/add_authentication_label"
android:text="@string/add_authentication_label"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:layout_toRightOf="@id/add_authentication"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textColor="?android:attr/textColorPrimary"
android:layout_centerVertical="true" />
</RelativeLayout>
<RelativeLayout
android:id="@+id/imap_password_selection"
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<TextView
android:id="@+id/account_setup_basics_password_label"
android:text="@string/account_setup_basics_password_label"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:layout_alignParentLeft="true"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textColor="?android:attr/textColorPrimary"
android:layout_centerVertical="true" />
<EditText
android:id="@+id/imap_account_password"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:layout_toRightOf="@id/account_setup_basics_password_label"
android:layout_toLeftOf="@+id/clear_password"
android:hint="@string/account_setup_incoming_password_label"
android:inputType="textPassword"
android:imeOptions="actionNext"
android:layout_centerVertical="true" />
<ImageView
android:id="@+id/clear_password"
android:contentDescription="@string/clear_authentication_label"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:layout_alignParentRight="true"
android:clickable="true"
android:src="@drawable/ic_clear_authentication"
android:layout_centerVertical="true" />
</RelativeLayout>
<RelativeLayout
android:id="@+id/oauth_selection"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/signed_in_with_service_label"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:layout_alignParentLeft="true"
android:layout_toLeftOf="@+id/clear_oauth"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textColor="?android:attr/textColorPrimary"
android:layout_centerVertical="true" />
<ImageView
android:id="@+id/clear_oauth"
android:contentDescription="@string/clear_authentication_label"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:layout_alignParentRight="true"
android:clickable="true"
android:src="@drawable/ic_clear_authentication"
android:layout_centerVertical="true" />
</RelativeLayout>
</LinearLayout>
<LinearLayout
android:id="@+id/standard_password_selection"
android:layout_width="match_parent"
android:layout_height="0dip"
android:layout_weight="1"
android:orientation="vertical" >
<TextView
android:text="@string/account_setup_incoming_password_label"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textColor="?android:attr/textColorPrimary" />
<EditText
android:id="@+id/account_password"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:hint="@string/account_setup_incoming_password_label"
android:inputType="textPassword"
android:imeOptions="actionNext" />
</LinearLayout>
<include
android:id="@+id/client_certificate_selector"
layout="@layout/client_certificate_selector"
android:visibility="visible" />
<LinearLayout
android:id="@+id/device_id_section"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:visibility="gone">
<TextView
android:text="@string/account_setup_exchange_device_id_label"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:textColor="?android:attr/textColorPrimary" />
<TextView
android:id="@+id/device_id"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textColor="#ffbebebe" />
</LinearLayout>
</LinearLayout>

View File

@ -0,0 +1,37 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2011 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical" >
<TextView
android:text="@string/account_setup_incoming_password_label"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textColor="?android:attr/textColorPrimary" />
<EditText
android:id="@+id/account_password"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:hint="@string/account_setup_incoming_password_label"
android:inputType="textPassword"
android:imeOptions="actionNext" />
</LinearLayout>

View File

@ -30,6 +30,7 @@
<attr name="defaultSsl" format="boolean"/>
<attr name="offerTls" format="boolean"/>
<attr name="offerCerts" format="boolean"/>
<attr name="offerOAuth" format="boolean"/>
<attr name="offerLocalDeletes" format="boolean"/>
<attr name="defaultLocalDeletes" format="integer"/>
<attr name="offerPrefix" format="boolean"/>

View File

@ -202,6 +202,14 @@
<string name="account_setup_basics_email_label">Email address</string>
<!-- On "Set up email" screen, hint for account email password text field -->
<string name="account_setup_basics_password_label">Password</string>
<!-- On the "Setup up email" screen, label indicating what service we are signed in with [CHAR LIMIT=40] -->
<string name="signed_in_with_service_label">Signed in with %s</string>
<!-- Label for the authentication section [CHAR LIMIT=20] -->
<string name="authenticiation_label">Authentication</string>
<!-- Label for the add authentication icon [CHAR LIMIT=40] -->
<string name="add_authentication_label">Add authentication</string>
<!-- Label for the add authentication icon [CHAR LIMIT=40] -->
<string name="clear_authentication_label">Clear authentication</string>
<!-- Button name on "Set up email" screen [CHAR LIMIT=20] -->
<string name="account_setup_basics_manual_setup_action">Manual setup</string>
<!-- Do not translate. Button name on "Set up email" screen [CHAR LIMIT=20] TODO: This is a temporary workaround

View File

@ -33,6 +33,7 @@
The following optional attributes default to "false":
offerTls: whether a TLS option (e.g. STARTTLS) is offered for this service
offerCerts: whether or not certificate authentication is an option for this service
offerOAuth: whether or not OAuth authentication is an option for this service
usesSmtp: whether SMTP is used as the outgoing protocol for this service
offerPrefix: whether a "prefix" is offered to the user (for IMAP)
offerLocalDeletes: whether an option to delete locally is offered
@ -83,6 +84,7 @@
email:offerPrefix="true"
email:syncChanges="true"
email:inferPrefix="imap"
email:offerOAuth="true"
email:offerLoadMore="true"
email:offerMoveTo="true"
/>

View File

@ -388,6 +388,7 @@ public class AccountSettings extends PreferenceActivity implements FeedbackEnabl
super.onBackPressed();
}
private void launchMailboxSettings(Intent intent) {
final Folder folder = intent.getParcelableExtra(EditSettingsExtras.EXTRA_FOLDER);
@ -402,7 +403,6 @@ public class AccountSettings extends PreferenceActivity implements FeedbackEnabl
finish();
}
private void enableDebugMenu() {
mShowDebugMenu = true;
invalidateHeaders();

View File

@ -17,6 +17,9 @@
package com.android.email.activity.setup;
import android.app.Activity;
import android.app.Fragment;
import android.app.FragmentManager;
import android.app.FragmentTransaction;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
@ -37,6 +40,7 @@ import android.widget.TextView;
import com.android.email.R;
import com.android.email.activity.UiUtilities;
import com.android.email.activity.setup.AuthenticationFragment.AuthenticationCallback;
import com.android.email.provider.AccountBackupRestore;
import com.android.email.service.EmailServiceUtils;
import com.android.email.service.EmailServiceUtils.EmailServiceInfo;
@ -61,14 +65,14 @@ import java.util.ArrayList;
* (for editing existing accounts).
*/
public class AccountSetupIncomingFragment extends AccountServerBaseFragment
implements HostCallback {
implements HostCallback, AuthenticationCallback {
private static final int CERTIFICATE_REQUEST = 0;
private final static String STATE_KEY_CREDENTIAL = "AccountSetupIncomingFragment.credential";
private final static String STATE_KEY_LOADED = "AccountSetupIncomingFragment.loaded";
private EditText mUsernameView;
private EditText mPasswordView;
private AuthenticationFragment mAuthenticationFragment;
private TextView mServerLabelView;
private EditText mServerView;
private EditText mPortView;
@ -76,9 +80,7 @@ public class AccountSetupIncomingFragment extends AccountServerBaseFragment
private TextView mDeletePolicyLabelView;
private Spinner mDeletePolicyView;
private View mImapPathPrefixSectionView;
private View mDeviceIdSectionView;
private EditText mImapPathPrefixView;
private CertificateSelector mClientCertificateSelector;
// Delete policy as loaded from the device
private int mLoadedDeletePolicy;
@ -123,7 +125,6 @@ public class AccountSetupIncomingFragment extends AccountServerBaseFragment
final View view = inflater.inflate(layoutId, container, false);
mUsernameView = UiUtilities.getView(view, R.id.account_username);
mPasswordView = UiUtilities.getView(view, R.id.account_password);
mServerLabelView = UiUtilities.getView(view, R.id.account_server_label);
mServerView = UiUtilities.getView(view, R.id.account_server);
mPortView = UiUtilities.getView(view, R.id.account_port);
@ -131,9 +132,7 @@ public class AccountSetupIncomingFragment extends AccountServerBaseFragment
mDeletePolicyLabelView = UiUtilities.getView(view, R.id.account_delete_policy_label);
mDeletePolicyView = UiUtilities.getView(view, R.id.account_delete_policy);
mImapPathPrefixSectionView = UiUtilities.getView(view, R.id.imap_path_prefix_section);
mDeviceIdSectionView = UiUtilities.getView(view, R.id.device_id_section);
mImapPathPrefixView = UiUtilities.getView(view, R.id.imap_path_prefix);
mClientCertificateSelector = UiUtilities.getView(view, R.id.client_certificate_selector);
// Updates the port when the user changes the security type. This allows
// us to show a reasonable default which the user can change.
@ -165,7 +164,6 @@ public class AccountSetupIncomingFragment extends AccountServerBaseFragment
getString(R.string.account_setup_username_uneditable_error));
}
mUsernameView.addTextChangedListener(mValidationTextWatcher);
mPasswordView.addTextChangedListener(mValidationTextWatcher);
mServerView.addTextChangedListener(mValidationTextWatcher);
mPortView.addTextChangedListener(mValidationTextWatcher);
@ -175,6 +173,14 @@ public class AccountSetupIncomingFragment extends AccountServerBaseFragment
// Additional setup only used while in "settings" mode
onCreateViewSettingsMode(view);
final FragmentManager fm = getFragmentManager();
final FragmentTransaction ft = fm.beginTransaction();
mAuthenticationFragment = new AuthenticationFragment();
ft.add(R.id.authentication_container, mAuthenticationFragment, "AuthenticationFragment");
ft.commit();
mAuthenticationFragment.setAuthenticationCallback(this);
return view;
}
@ -184,14 +190,13 @@ public class AccountSetupIncomingFragment extends AccountServerBaseFragment
LogUtils.d(Logging.LOG_TAG, "AccountSetupIncomingFragment onActivityCreated");
}
super.onActivityCreated(savedInstanceState);
mClientCertificateSelector.setHostActivity(this);
final Context context = getActivity();
final SetupDataFragment.SetupDataContainer container =
(SetupDataFragment.SetupDataContainer) context;
mSetupData = container.getSetupData();
final HostAuth recvAuth = mSetupData.getAccount().mHostAuthRecv;
final Account account = mSetupData.getAccount();
final HostAuth recvAuth = account.getOrCreateHostAuthRecv(mContext);
mServiceInfo = EmailServiceUtils.getServiceInfo(mContext, recvAuth.mProtocol);
if (mServiceInfo.offerLocalDeletes) {
@ -286,14 +291,15 @@ public class AccountSetupIncomingFragment extends AccountServerBaseFragment
public void onDestroyView() {
// Make sure we don't get callbacks after the views are supposed to be destroyed
// and also don't hold onto them longer than we need
final FragmentManager fm = getFragmentManager();
final FragmentTransaction ft = fm.beginTransaction();
ft.remove(mAuthenticationFragment);
ft.commit();
if (mUsernameView != null) {
mUsernameView.removeTextChangedListener(mValidationTextWatcher);
}
mUsernameView = null;
if (mPasswordView != null) {
mPasswordView.removeTextChangedListener(mValidationTextWatcher);
}
mPasswordView = null;
mServerLabelView = null;
if (mServerView != null) {
mServerView.removeTextChangedListener(mValidationTextWatcher);
@ -310,9 +316,7 @@ public class AccountSetupIncomingFragment extends AccountServerBaseFragment
mDeletePolicyLabelView = null;
mDeletePolicyView = null;
mImapPathPrefixSectionView = null;
mDeviceIdSectionView = null;
mImapPathPrefixView = null;
mClientCertificateSelector = null;
super.onDestroyView();
}
@ -387,6 +391,8 @@ public class AccountSetupIncomingFragment extends AccountServerBaseFragment
final Account account = mSetupData.getAccount();
final HostAuth recvAuth = account.getOrCreateHostAuthRecv(mContext);
mServiceInfo = EmailServiceUtils.getServiceInfo(mContext, recvAuth.mProtocol);
mAuthenticationFragment.setAuthInfo(mServiceInfo, recvAuth);
final String username = recvAuth.mLogin;
if (username != null) {
@ -398,14 +404,6 @@ public class AccountSetupIncomingFragment extends AccountServerBaseFragment
//}
mUsernameView.setText(username);
}
final String password = recvAuth.mPassword;
if (password != null) {
mPasswordView.setText(password);
// Since username is uneditable, focus on the next editable field
if (mSettingsMode) {
mPasswordView.requestFocus();
}
}
if (mServiceInfo.offerPrefix) {
final String prefix = recvAuth.mDomain;
@ -450,14 +448,11 @@ public class AccountSetupIncomingFragment extends AccountServerBaseFragment
private void validateFields() {
if (!mLoaded) return;
enableNextButton(!TextUtils.isEmpty(mUsernameView.getText())
&& !TextUtils.isEmpty(mPasswordView.getText())
&& mAuthenticationFragment.getAuthValid()
&& Utility.isServerNameValid(mServerView)
&& Utility.isPortFieldValid(mPortView));
mCacheLoginCredential = mUsernameView.getText().toString().trim();
// Warn (but don't prevent) if password has leading/trailing spaces
AccountSettingsUtils.checkPasswordSpaces(mContext, mPasswordView);
}
private int getPortFromSecurityType(boolean useSsl) {
@ -473,20 +468,7 @@ public class AccountSetupIncomingFragment extends AccountServerBaseFragment
}
public void onUseSslChanged(boolean useSsl) {
if (mServiceInfo.offerCerts) {
final int mode = useSsl ? View.VISIBLE : View.GONE;
mClientCertificateSelector.setVisibility(mode);
String deviceId = "";
try {
deviceId = Device.getDeviceId(mContext);
} catch (IOException e) {
// Not required
}
((TextView) UiUtilities.getView(getView(), R.id.device_id)).setText(deviceId);
mDeviceIdSectionView.setVisibility(mode);
//UiUtilities.setVisibilitySafe(getView(), R.id.client_certificate_divider, mode);
}
mAuthenticationFragment.onUseSslChanged(useSsl);
}
private void updatePortFromSecurityType() {
@ -545,7 +527,7 @@ public class AccountSetupIncomingFragment extends AccountServerBaseFragment
final HostAuth recvAuth = account.getOrCreateHostAuthRecv(mContext);
final String userName = mUsernameView.getText().toString().trim();
final String userPassword = mPasswordView.getText().toString();
final String userPassword = mAuthenticationFragment.getPassword().toString();
recvAuth.setLogin(userName, userPassword);
final String serverAddress = mServerView.getText().toString().trim();
@ -565,7 +547,7 @@ public class AccountSetupIncomingFragment extends AccountServerBaseFragment
} else {
recvAuth.mDomain = null;
}
recvAuth.mClientCertAlias = mClientCertificateSelector.getCertificate();
recvAuth.mClientCertAlias = mAuthenticationFragment.getClientCertificate();
mCallback.onProceedNext(SetupDataFragment.CHECK_INCOMING, this);
clearButtonBounce();
@ -605,12 +587,7 @@ public class AccountSetupIncomingFragment extends AccountServerBaseFragment
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == CERTIFICATE_REQUEST && resultCode == Activity.RESULT_OK) {
final String certAlias = data.getStringExtra(CertificateRequestor.RESULT_ALIAS);
if (certAlias != null) {
mClientCertificateSelector.setCertificate(certAlias);
}
}
public void onValidateStateChanged() {
validateFields();
}
}

View File

@ -0,0 +1,294 @@
package com.android.email.activity.setup;
import android.app.Activity;
import android.app.Fragment;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.EditText;
import android.widget.TextView;
import com.android.email.R;
import com.android.email.activity.UiUtilities;
import com.android.email.service.EmailServiceUtils;
import com.android.email.service.EmailServiceUtils.EmailServiceInfo;
import com.android.email.view.CertificateSelector;
import com.android.email.view.CertificateSelector.HostCallback;
import com.android.email2.ui.MailActivityEmail;
import com.android.emailcommon.Device;
import com.android.emailcommon.Logging;
import com.android.emailcommon.VendorPolicyLoader.OAuthProvider;
import com.android.emailcommon.provider.Credential;
import com.android.emailcommon.provider.HostAuth;
import com.android.emailcommon.utility.CertificateRequestor;
import com.android.mail.utils.LogUtils;
import java.io.IOException;
// FLAG:
// * need to handle clicking on this to clear the auth info.
// * need to handle getting oauth tokens
// * need to handle switching from password to oauth and vice versa
public class AuthenticationFragment extends Fragment implements HostCallback {
private static final int CERTIFICATE_REQUEST = 0;
// Views
private View mImapAuthenticationView;
private View mImapPasswordContainer;
private EditText mImapPasswordView;
private View mImapOAuthContainer;
private TextView mImapOAuthView;
private View mImapAddAuthenticationView;
private View mPasswordContainer;
private EditText mPasswordView;
private CertificateSelector mClientCertificateSelector;
private View mDeviceIdSectionView;
private boolean mViewInitialized;
// Watcher
private TextWatcher mValidationTextWatcher;
private HostAuth mHostAuth;
private EmailServiceInfo mServiceInfo;
private boolean mUseOAuth;
private String mPassword;
private String mOAuthProvider;
private String mOAuthAccessToken;
private String mOAuthRefreshToken;
private boolean mAuthenticationValid;
private AuthenticationCallback mAuthenticationCallback;
public interface AuthenticationCallback {
public void onValidateStateChanged();
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mViewInitialized = false;
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
mClientCertificateSelector.setHostActivity(this);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
final View view = inflater.inflate(R.layout.authentication_fragment, container, false);
mImapAuthenticationView = UiUtilities.getView(view, R.id.imap_authentication);
mImapPasswordContainer = UiUtilities.getView(view, R.id.imap_password_selection);
mImapPasswordView = UiUtilities.getView(view, R.id.imap_account_password);
mImapOAuthContainer = UiUtilities.getView(view, R.id.oauth_selection);
mImapOAuthView = UiUtilities.getView(view, R.id.signed_in_with_service_label);
mImapAddAuthenticationView = UiUtilities.getView(view, R.id.authentication_selection);
mPasswordContainer = UiUtilities.getView(view, R.id.standard_password_selection);
mPasswordView = UiUtilities.getView(view, R.id.account_password);
mClientCertificateSelector = UiUtilities.getView(view, R.id.client_certificate_selector);
mDeviceIdSectionView = UiUtilities.getView(view, R.id.device_id_section);
mValidationTextWatcher = new TextWatcher() {
@Override
public void afterTextChanged(Editable s) {
validateFields();
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) { }
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) { }
};
mPasswordView.addTextChangedListener(mValidationTextWatcher);
mImapPasswordView.addTextChangedListener(mValidationTextWatcher);
mViewInitialized = true;
if (mHostAuth != null) {
loadInfo();
}
return view;
}
public void setAuthenticationCallback(final AuthenticationCallback host) {
mAuthenticationCallback = host;
}
public boolean getAuthValid() {
if (mServiceInfo == null || !mViewInitialized) {
// XXX this is kind of weird. We can get called in onStart() of our
// parent fragment, when we're still totally uninitialized.
// Once we get initialized, we need to call back to allow our parent
// to know what our state is.
return false;
}
if (mServiceInfo.offerOAuth) {
if (mUseOAuth) {
return mOAuthProvider != null;
} else {
return !TextUtils.isEmpty(mImapPasswordView.getText());
}
} else {
return !TextUtils.isEmpty(mPasswordView.getText());
}
}
public String getPassword() {
return getPasswordEditText().getText().toString();
}
public String getClientCertificate() {
return mClientCertificateSelector.getCertificate();
}
public String getOAuthProvider() {
// FLAG: need to handle this getting updated.
return mOAuthProvider;
}
private void validateFields() {
boolean valid = getAuthValid();
if (valid != mAuthenticationValid) {
mAuthenticationCallback.onValidateStateChanged();
mAuthenticationValid = valid;
}
// Warn (but don't prevent) if password has leading/trailing spaces
AccountSettingsUtils.checkPasswordSpaces(getActivity(), getPasswordEditText());
}
private EditText getPasswordEditText() {
if (mServiceInfo.offerOAuth) {
return mImapPasswordView;
} else {
return mPasswordView;
}
}
public void setAuthInfo(final EmailServiceInfo serviceInfo, final HostAuth hostAuth) {
mServiceInfo = serviceInfo;
mHostAuth = hostAuth;
if (mViewInitialized) {
loadInfo();
validateFields();
}
}
private void loadInfo() {
mServiceInfo = EmailServiceUtils.getServiceInfo(getActivity(), mHostAuth.mProtocol);
mClientCertificateSelector.setVisibility(
mServiceInfo.offerCerts ? View.VISIBLE : View.GONE);
mImapPasswordView.setText(null);
mPasswordView.setText(null);
if (mServiceInfo.offerOAuth) {
mPasswordContainer.setVisibility(View.GONE);
mImapAuthenticationView.setVisibility(View.VISIBLE);
final Credential cred = mHostAuth.getCredential(getActivity());
if (cred != null) {
// We're authenticated with OAuth.
mUseOAuth = true;
mOAuthProvider = cred.mProviderId;
mOAuthAccessToken = cred.mAccessToken;
mOAuthRefreshToken = cred.mRefreshToken;
final OAuthProvider provider = AccountSettingsUtils.findOAuthProvider(
getActivity(), cred.mProviderId);
mImapPasswordContainer.setVisibility(View.GONE);
mImapOAuthContainer.setVisibility(View.VISIBLE);
mImapAddAuthenticationView.setVisibility(View.GONE);
mImapOAuthView.setText(getString(R.string.signed_in_with_service_label,
provider.label));
// } else if (!TextUtils.isEmpty(hostAuth.mPassword)) {
} else {
// We're authenticated with a password.
mUseOAuth = false;
mPassword = mHostAuth.mPassword;
// XXX need to handle clicking on this to clear the password.
mImapPasswordContainer.setVisibility(View.VISIBLE);
mImapPasswordView.setText(mHostAuth.mPassword);
mImapOAuthContainer.setVisibility(View.GONE);
mImapAddAuthenticationView.setVisibility(View.GONE);
/* } else {
* XXX Allow us to choose what type of authentication when the password is unset.
// We have no authentication, we need to allow either password or oauth.
mUseOAuth = false;
mPassword = null;
// XXX need to handle clicking on this to go to the add auth fragment.
mImapPasswordContainer.setVisibility(View.GONE);
mImapOAuthContainer.setVisibility(View.GONE);
mImapAddAuthenticationView.setVisibility(View.VISIBLE); */
}
} else {
// We're using a POP or Exchange account, which does not offer oAuth.
mUseOAuth = false;
mPassword = mHostAuth.mPassword;
mImapAuthenticationView.setVisibility(View.GONE);
mPasswordContainer.setVisibility(View.VISIBLE);
mPasswordView.setVisibility(View.VISIBLE);
mPasswordView.setText(mHostAuth.mPassword);
if (TextUtils.isEmpty(mHostAuth.mPassword)) {
mPasswordView.requestFocus();
}
}
validateFields();
}
@Override
public void onSaveInstanceState(Bundle outState) {
if (Logging.DEBUG_LIFECYCLE && MailActivityEmail.DEBUG) {
LogUtils.d(Logging.LOG_TAG, "AuthenticationFragment onSaveInstanceState");
}
super.onSaveInstanceState(outState);
}
@Override
public void onCertificateRequested() {
final Intent intent = new Intent(CertificateRequestor.ACTION_REQUEST_CERT);
intent.setData(Uri.parse("eas://com.android.emailcommon/certrequest"));
startActivityForResult(intent, CERTIFICATE_REQUEST);
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == CERTIFICATE_REQUEST && resultCode == Activity.RESULT_OK) {
final String certAlias = data.getStringExtra(CertificateRequestor.RESULT_ALIAS);
if (certAlias != null) {
mClientCertificateSelector.setCertificate(certAlias);
}
}
}
public void onUseSslChanged(boolean useSsl) {
if (mServiceInfo.offerCerts) {
final int mode = useSsl ? View.VISIBLE : View.GONE;
mClientCertificateSelector.setVisibility(mode);
String deviceId = "";
try {
deviceId = Device.getDeviceId(getActivity());
} catch (IOException e) {
// Not required
}
((TextView) UiUtilities.getView(getView(), R.id.device_id)).setText(deviceId);
mDeviceIdSectionView.setVisibility(mode);
}
}
}

View File

@ -185,6 +185,7 @@ public class EmailServiceUtils {
public boolean defaultSsl;
public boolean offerTls;
public boolean offerCerts;
public boolean offerOAuth;
public boolean usesSmtp;
public boolean offerLocalDeletes;
public int defaultLocalDeletes;
@ -544,6 +545,8 @@ public class EmailServiceUtils {
info.offerTls = ta.getBoolean(R.styleable.EmailServiceInfo_offerTls, false);
info.offerCerts =
ta.getBoolean(R.styleable.EmailServiceInfo_offerCerts, false);
info.offerOAuth =
ta.getBoolean(R.styleable.EmailServiceInfo_offerOAuth, false);
info.offerLocalDeletes =
ta.getBoolean(R.styleable.EmailServiceInfo_offerLocalDeletes, false);
info.defaultLocalDeletes =