Add sign in Activity

This allows the user to choose which type of authentication
to use for existing IMAP accounts.

Change-Id: Ib44364a1059a2c4b8a7a0fa66b14cd042b28770e
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 85e9c6d..db16e02 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -151,6 +151,11 @@
         </activity>
 
         <activity
+            android:name=".activity.setup.SignInActivity"
+            android:label="@string/sign_in_title">
+        </activity>
+
+        <activity
             android:name=".activity.EventViewer"
             android:label="@string/app_name"
             >
diff --git a/emailcommon/src/com/android/emailcommon/provider/HostAuth.java b/emailcommon/src/com/android/emailcommon/provider/HostAuth.java
index 15dde4e..f4cdd1b 100644
--- a/emailcommon/src/com/android/emailcommon/provider/HostAuth.java
+++ b/emailcommon/src/com/android/emailcommon/provider/HostAuth.java
@@ -17,12 +17,16 @@
 
 package com.android.emailcommon.provider;
 
+import android.content.ContentProviderOperation;
+import android.content.ContentProviderResult;
 import android.content.ContentValues;
 import android.content.Context;
+import android.content.OperationApplicationException;
 import android.database.Cursor;
 import android.net.Uri;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.os.RemoteException;
 import android.text.TextUtils;
 
 import com.android.emailcommon.provider.EmailContent.HostAuthColumns;
@@ -31,6 +35,7 @@
 
 import java.net.URI;
 import java.net.URISyntaxException;
+import java.util.ArrayList;
 
 public class HostAuth extends EmailContent implements HostAuthColumns, Parcelable {
     public static final String TABLE_NAME = "HostAuth";
@@ -145,6 +150,14 @@
     }
 
     /**
+     * Clear the credential object.
+     */
+    public void removeCredential() {
+        mCredential = null;
+        mCredentialKey = -1;
+    }
+
+    /**
      * Builds a URI scheme name given the parameters for a {@code HostAuth}. If
      * a {@code clientAlias} is provided, this indicates that a secure
      * connection must be used.
@@ -491,5 +504,4 @@
     public String toString() {
         return "[protocol " + mProtocol + "]";
     }
-
 }
diff --git a/res/layout/sign_in_activity.xml b/res/layout/sign_in_activity.xml
new file mode 100644
index 0000000..dcd4e18
--- /dev/null
+++ b/res/layout/sign_in_activity.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 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.
+-->
+
+<FrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:paddingTop="@dimen/setup_fragment_padding_top"
+    android:paddingLeft="@dimen/setup_fragment_padding_left"
+    android:paddingRight="@dimen/setup_fragment_padding_right" >
+
+    <fragment
+        android:id="@+id/sign_in_fragment"
+        class="com.android.email.activity.setup.SignInFragment"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content" />
+
+</FrameLayout>
diff --git a/res/layout/sign_in_fragment.xml b/res/layout/sign_in_fragment.xml
new file mode 100644
index 0000000..81e0a8d
--- /dev/null
+++ b/res/layout/sign_in_fragment.xml
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 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:gravity="center"
+    android:orientation="vertical">
+
+    <View
+        android:layout_width="match_parent"
+        android:layout_height="0dip"
+        android:layout_weight="1" />
+
+    <Button
+        android:id="@+id/sign_in_with_google"
+        android:text="@string/sign_in_with_google"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"/>
+
+    <TextView
+        android:id="@+id/or_label"
+        android:text="@string/or_label"
+        android:layout_marginTop="24dip"
+        android:layout_marginBottom="24dip"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"/>
+
+    <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"/>
+
+    <View
+        android:layout_width="match_parent"
+        android:layout_height="0dip"
+        android:layout_weight="1" />
+
+    <include layout="@layout/account_setup_buttons" />
+
+</LinearLayout>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 6034ebc..5fbfeaf 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -190,8 +190,10 @@
 
     <!-- Title of screen when setting up new email account [CHAR LIMIT=45] -->
     <string name="account_setup_basics_title">Account setup</string>
-    <!-- Do Not Translate. Title of screen when trying to get oauth authentication -->
+    <!-- Title of screen when trying to get oauth authentication -->
     <string name="oauth_authentication_title" translatable="false">OAuth authentication</string>
+    <!-- Title of screen to choose with authentication to use -->
+    <string name="sign_in_title">IMAP Authentication</string>
     <!-- Headline of screen when setting up new email account (large text over divider)
         [CHAR LIMIT=none] -->
     <string name="account_setup_basics_headline">Email account</string>
@@ -200,6 +202,12 @@
         </string>
     <!-- On "Set up email" screen, hint for account email address text field -->
     <string name="account_setup_basics_email_label">Email address</string>
+
+    <!-- Label between OAuth and password authentication, indicating the user must do one or the other [CHAR LIMIT=40] -->
+    <string name="or_label">OR</string>
+    <!-- Label for signing in with Google using OAuth -->
+    <string name="sign_in_with_google">Sign in with Google</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] -->
diff --git a/src/com/android/email/activity/setup/AccountSettingsUtils.java b/src/com/android/email/activity/setup/AccountSettingsUtils.java
index 607cfb2..65afb7c 100644
--- a/src/com/android/email/activity/setup/AccountSettingsUtils.java
+++ b/src/com/android/email/activity/setup/AccountSettingsUtils.java
@@ -40,6 +40,9 @@
 import com.android.mail.utils.LogUtils;
 import com.google.common.annotations.VisibleForTesting;
 
+import java.util.ArrayList;
+import java.util.List;
+
 public class AccountSettingsUtils {
 
     /** Pattern to match any part of a domain */
@@ -145,6 +148,43 @@
        return findOAuthProvider(context, id, R.xml.oauth);
    }
 
+   public static List<OAuthProvider> getAllOAuthProviders(final Context context) {
+       try {
+           List<OAuthProvider> providers = new ArrayList<OAuthProvider>();
+           final XmlResourceParser xml = context.getResources().getXml(R.xml.oauth);
+           int xmlEventType;
+           OAuthProvider provider = null;
+           while ((xmlEventType = xml.next()) != XmlResourceParser.END_DOCUMENT) {
+               if (xmlEventType == XmlResourceParser.START_TAG
+                       && "provider".equals(xml.getName())) {
+                   try {
+                       provider = new OAuthProvider();
+                       provider.id = getXmlAttribute(context, xml, "id");
+                       provider.label = getXmlAttribute(context, xml, "label");
+                       provider.authEndpoint = getXmlAttribute(context, xml, "auth_endpoint");
+                       provider.tokenEndpoint = getXmlAttribute(context, xml, "token_endpoint");
+                       provider.refreshEndpoint = getXmlAttribute(context, xml,
+                               "refresh_endpoint");
+                       provider.responseType = getXmlAttribute(context, xml, "response_type");
+                       provider.redirectUri = getXmlAttribute(context, xml, "redirect_uri");
+                       provider.scope = getXmlAttribute(context, xml, "scope");
+                       provider.state = getXmlAttribute(context, xml, "state");
+                       provider.clientId = getXmlAttribute(context, xml, "client_id");
+                       provider.clientSecret = getXmlAttribute(context, xml, "client_secret");
+                       providers.add(provider);
+                   } catch (IllegalArgumentException e) {
+                       LogUtils.w(Logging.LOG_TAG, "providers line: " + xml.getLineNumber() +
+                               "; Domain contains multiple globals");
+                   }
+               }
+           }
+           return providers;
+       } catch (Exception e) {
+           LogUtils.e(Logging.LOG_TAG, "Error while trying to load provider settings.", e);
+       }
+       return null;
+   }
+
    /**
     * Search for a single resource containing known oauth provider definitions.
     *
diff --git a/src/com/android/email/activity/setup/AccountSetupBasics.java b/src/com/android/email/activity/setup/AccountSetupBasics.java
index 00af313..94c7318 100644
--- a/src/com/android/email/activity/setup/AccountSetupBasics.java
+++ b/src/com/android/email/activity/setup/AccountSetupBasics.java
@@ -110,10 +110,6 @@
     public static final int RESULT_OAUTH_USER_CANCELED = -1;
     public static final int RESULT_OAUTH_FAILURE = -2;
 
-    public static final String EXTRA_OAUTH_ACCESS_TOKEN = "accessToken";
-    public static final String EXTRA_OAUTH_REFRESH_TOKEN = "refreshToken";
-    public static final String EXTRA_OAUTH_EXPIRES_IN = "expiresIn";
-
     // Support for UI
     private Provider mProvider;
     private Button mManualButton;
@@ -144,9 +140,12 @@
     public void onActivityResult(int requestCode, int resultCode, Intent data) {
         if (requestCode == REQUEST_OAUTH) {
             if (resultCode == RESULT_OAUTH_SUCCESS) {
-                final String accessToken = data.getStringExtra(EXTRA_OAUTH_ACCESS_TOKEN);
-                final String refreshToken = data.getStringExtra(EXTRA_OAUTH_REFRESH_TOKEN);
-                final int expiresInSeconds = data.getIntExtra(EXTRA_OAUTH_EXPIRES_IN, 0);
+                final String accessToken = data.getStringExtra(
+                        OAuthAuthenticationActivity.EXTRA_OAUTH_ACCESS_TOKEN);
+                final String refreshToken = data.getStringExtra(
+                        OAuthAuthenticationActivity.EXTRA_OAUTH_REFRESH_TOKEN);
+                final int expiresInSeconds = data.getIntExtra(
+                        OAuthAuthenticationActivity.EXTRA_OAUTH_EXPIRES_IN, 0);
 
                 finishOAuthSetup(accessToken, refreshToken, expiresInSeconds);
             } else if (resultCode == RESULT_OAUTH_FAILURE
diff --git a/src/com/android/email/activity/setup/AccountSetupIncomingFragment.java b/src/com/android/email/activity/setup/AccountSetupIncomingFragment.java
index 86e9879..be9ec5f 100644
--- a/src/com/android/email/activity/setup/AccountSetupIncomingFragment.java
+++ b/src/com/android/email/activity/setup/AccountSetupIncomingFragment.java
@@ -24,6 +24,7 @@
 import android.text.Editable;
 import android.text.TextUtils;
 import android.text.TextWatcher;
+import android.text.format.DateUtils;
 import android.text.method.DigitsKeyListener;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -44,6 +45,7 @@
 import com.android.email2.ui.MailActivityEmail;
 import com.android.emailcommon.Logging;
 import com.android.emailcommon.provider.Account;
+import com.android.emailcommon.provider.Credential;
 import com.android.emailcommon.provider.HostAuth;
 import com.android.emailcommon.utility.CertificateRequestor;
 import com.android.emailcommon.utility.Utility;
@@ -61,6 +63,7 @@
         implements AuthenticationCallback {
 
     private static final int CERTIFICATE_REQUEST = 0;
+    private static final int SIGN_IN_REQUEST = 1;
 
     private final static String STATE_KEY_CREDENTIAL = "AccountSetupIncomingFragment.credential";
     private final static String STATE_KEY_LOADED = "AccountSetupIncomingFragment.loaded";
@@ -403,11 +406,12 @@
         SpinnerOption.setSpinnerOptionValue(mDeletePolicyView, mLoadedDeletePolicy);
 
         int flags = recvAuth.mFlags;
-        flags &= ~HostAuth.FLAG_AUTHENTICATE;
         if (mServiceInfo.defaultSsl) {
             flags |= HostAuth.FLAG_SSL;
         }
-        SpinnerOption.setSpinnerOptionValue(mSecurityTypeView, flags);
+        // Strip out any flags that are not related to security type.
+        int securityTypeFlags = (flags & (HostAuth.FLAG_SSL | HostAuth.FLAG_TLS | HostAuth.FLAG_NONE));
+        SpinnerOption.setSpinnerOptionValue(mSecurityTypeView, securityTypeFlags);
 
         final String hostname = recvAuth.mAddress;
         if (hostname != null) {
@@ -472,6 +476,15 @@
     public void saveSettingsAfterEdit() {
         final Account account = mSetupData.getAccount();
         account.update(mContext, account.toContentValues());
+        final Credential cred = account.mHostAuthRecv.mCredential;
+        if (cred != null) {
+            if (cred.isSaved()) {
+                cred.update(mContext, cred.toContentValues());
+            } else {
+                cred.save(mContext);
+                account.mHostAuthRecv.mCredentialKey = cred.mId;
+            }
+        }
         account.mHostAuthRecv.update(mContext, account.mHostAuthRecv.toContentValues());
         // Update the backup (side copy) of the accounts
         AccountBackupRestore.backup(mContext);
@@ -513,6 +526,10 @@
         final String userName = mUsernameView.getText().toString().trim();
         final String userPassword = mAuthenticationView.getPassword().toString();
         recvAuth.setLogin(userName, userPassword);
+        if (!TextUtils.isEmpty(mAuthenticationView.getOAuthProvider())) {
+            Credential cred = recvAuth.getOrCreateCredential(getActivity());
+            cred.mProviderId = mAuthenticationView.getOAuthProvider();
+        }
 
         final String serverAddress = mServerView.getText().toString().trim();
         int serverPort;
@@ -568,6 +585,14 @@
         validateFields();
     }
 
+    @Override
+    public void onRequestSignIn() {
+        // Launch the signin activity.
+        // TODO: at some point we should just use the sign in fragment on the main setup activity.
+        final Intent intent = new Intent(getActivity(), SignInActivity.class);
+        intent.putExtra(SignInActivity.EXTRA_EMAIL, mSetupData.getAccount().mEmailAddress);
+        startActivityForResult(intent, SIGN_IN_REQUEST);
+    }
 
     @Override
     public void onCertificateRequested() {
@@ -581,6 +606,25 @@
         if (requestCode == CERTIFICATE_REQUEST && resultCode == Activity.RESULT_OK) {
             final String certAlias = data.getStringExtra(CertificateRequestor.RESULT_ALIAS);
             mAuthenticationView.setCertificate(certAlias);
+        } else if (requestCode == SIGN_IN_REQUEST && resultCode == Activity.RESULT_OK) {
+            final Account account = mSetupData.getAccount();
+            final HostAuth recvAuth = account.getOrCreateHostAuthRecv(getActivity());
+            final String password = data.getStringExtra(SignInActivity.EXTRA_PASSWORD);
+            if (!TextUtils.isEmpty(password)) {
+                recvAuth.mPassword = password;
+                recvAuth.removeCredential();
+            } else {
+                Credential cred = recvAuth.getOrCreateCredential(getActivity());
+                cred.mProviderId = data.getStringExtra(SignInActivity.EXTRA_OAUTH_PROVIDER);
+                cred.mAccessToken = data.getStringExtra(SignInActivity.EXTRA_OAUTH_ACCESS_TOKEN);
+                cred.mRefreshToken = data.getStringExtra(SignInActivity.EXTRA_OAUTH_REFRESH_TOKEN);
+                cred.mExpiration = System.currentTimeMillis() +
+                        data.getIntExtra(SignInActivity.EXTRA_OAUTH_EXPIRES_IN_SECONDS, 0) *
+                        DateUtils.SECOND_IN_MILLIS;
+                recvAuth.mPassword = null;
+            }
+            mAuthenticationView.setAuthInfo(mServiceInfo.offerOAuth, mServiceInfo.offerCerts,
+                    recvAuth);
         }
     }
 }
diff --git a/src/com/android/email/activity/setup/AccountSetupOutgoingFragment.java b/src/com/android/email/activity/setup/AccountSetupOutgoingFragment.java
index 22a9139..fa6822f 100644
--- a/src/com/android/email/activity/setup/AccountSetupOutgoingFragment.java
+++ b/src/com/android/email/activity/setup/AccountSetupOutgoingFragment.java
@@ -43,6 +43,7 @@
 import com.android.email2.ui.MailActivityEmail;
 import com.android.emailcommon.Logging;
 import com.android.emailcommon.provider.Account;
+import com.android.emailcommon.provider.Credential;
 import com.android.emailcommon.provider.HostAuth;
 import com.android.emailcommon.utility.CertificateRequestor;
 import com.android.emailcommon.utility.Utility;
@@ -58,6 +59,7 @@
         implements OnCheckedChangeListener, AuthenticationCallback {
 
     private static final int CERTIFICATE_REQUEST = 0;
+    private static final int SIGN_IN_REQUEST = 1;
 
     private final static String STATE_KEY_LOADED = "AccountSetupOutgoingFragment.loaded";
 
@@ -348,6 +350,15 @@
     @Override
     public void saveSettingsAfterEdit() {
         final Account account = mSetupData.getAccount();
+        final Credential cred = account.mHostAuthRecv.mCredential;
+        if (cred != null) {
+            if (cred.isSaved()) {
+                cred.update(mContext, cred.toContentValues());
+            } else {
+                cred.save(mContext);
+                account.mHostAuthRecv.mCredentialKey = cred.mId;
+            }
+        }
         account.mHostAuthSend.update(mContext, account.mHostAuthSend.toContentValues());
         // Update the backup (side copy) of the accounts
         AccountBackupRestore.backup(mContext);
@@ -402,4 +413,13 @@
     public void onCertificateRequested() {
         // We don't support certificates on any outgoing protocol.
     }
+
+    @Override
+    public void onRequestSignIn() {
+        // Launch the signin activity.
+        // TODO: at some point we should just use the sign in fragment on the main setup activity.
+        final Intent intent = new Intent(getActivity(), SignInActivity.class);
+        intent.putExtra(SignInActivity.EXTRA_EMAIL, mSetupData.getAccount().mEmailAddress);
+        startActivityForResult(intent, SIGN_IN_REQUEST);
+    }
 }
diff --git a/src/com/android/email/activity/setup/AuthenticationView.java b/src/com/android/email/activity/setup/AuthenticationView.java
index 0a1bc60..3d16abd 100644
--- a/src/com/android/email/activity/setup/AuthenticationView.java
+++ b/src/com/android/email/activity/setup/AuthenticationView.java
@@ -1,9 +1,6 @@
 package com.android.email.activity.setup;
 
-import android.app.Activity;
 import android.content.Context;
-import android.content.Intent;
-import android.net.Uri;
 import android.os.Bundle;
 import android.os.Parcelable;
 import android.text.Editable;
@@ -19,14 +16,12 @@
 
 import com.android.email.R;
 import com.android.email.activity.UiUtilities;
-import com.android.email.service.EmailServiceUtils.EmailServiceInfo;
 import com.android.email.view.CertificateSelector;
 import com.android.email.view.CertificateSelector.HostCallback;
 import com.android.emailcommon.Device;
 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 java.io.IOException;
 
@@ -42,8 +37,6 @@
     private final static String SAVE_OFFER_CERTS = "save_offer_certs";
     private final static String SAVE_USE_OAUTH = "save_use_oauth";
     private final static String SAVE_OAUTH_PROVIDER = "save_oauth_provider";
-    private final static String SAVE_OAUTH_ACCESS_TOKEN = "save_oauth_access_token";
-    private final static String SAVE_OAUTH_REFRESH_TOKEN = "save_oauth_refresh_token";
 
     // Views
     private View mImapAuthenticationView;
@@ -66,8 +59,6 @@
     private boolean mOfferCerts;
     private boolean mUseOAuth;
     private String mOAuthProvider;
-    private String mOAuthAccessToken;
-    private String mOAuthRefreshToken;
 
     private boolean mAuthenticationValid;
     private AuthenticationCallback mAuthenticationCallback;
@@ -76,6 +67,8 @@
         public void onValidateStateChanged();
 
         public void onCertificateRequested();
+
+        public void onRequestSignIn();
     }
 
     public AuthenticationView(Context context) {
@@ -155,7 +148,6 @@
     }
 
     public String getOAuthProvider() {
-        // FLAG: need to handle this getting updated.
         return mOAuthProvider;
     }
 
@@ -188,8 +180,6 @@
                 // We're authenticated with OAuth.
                 mUseOAuth = true;
                 mOAuthProvider = cred.mProviderId;
-                mOAuthAccessToken = cred.mAccessToken;
-                mOAuthRefreshToken = cred.mRefreshToken;
             } else {
                 mUseOAuth = false;
             }
@@ -258,8 +248,6 @@
         bundle.putBoolean(SAVE_USE_OAUTH, mUseOAuth);
         bundle.putString(SAVE_PASSWORD, getPassword());
         bundle.putString(SAVE_OAUTH_PROVIDER, mOAuthProvider);
-        bundle.putString(SAVE_OAUTH_ACCESS_TOKEN, mOAuthAccessToken);
-        bundle.putString(SAVE_OAUTH_REFRESH_TOKEN, mOAuthRefreshToken);
         return bundle;
     }
 
@@ -272,8 +260,6 @@
             mOfferCerts = bundle.getBoolean(SAVE_OFFER_CERTS);
             mUseOAuth = bundle.getBoolean(SAVE_USE_OAUTH);
             mOAuthProvider = bundle.getString(SAVE_OAUTH_PROVIDER);
-            mOAuthAccessToken = bundle.getString(SAVE_OAUTH_ACCESS_TOKEN);
-            mOAuthRefreshToken = bundle.getString(SAVE_OAUTH_REFRESH_TOKEN);
 
             final String password = bundle.getString(SAVE_PASSWORD);
             getPasswordEditText().setText(password);
@@ -318,12 +304,14 @@
         if (view == mClearImapPasswordView) {
             getPasswordEditText().setText(null);
             updateVisibility();
+            validateFields();
         } else if (view == mClearOAuthView) {
             mUseOAuth = false;
             mOAuthProvider = null;
             updateVisibility();
+            validateFields();
         } else if (view == mAddAuthenticationView) {
-            // FLAG Launch the add authentication screen
+            mAuthenticationCallback.onRequestSignIn();
         }
     }
 }
diff --git a/src/com/android/email/activity/setup/OAuthAuthenticationActivity.java b/src/com/android/email/activity/setup/OAuthAuthenticationActivity.java
index 15f2b00..87023ec 100644
--- a/src/com/android/email/activity/setup/OAuthAuthenticationActivity.java
+++ b/src/com/android/email/activity/setup/OAuthAuthenticationActivity.java
@@ -42,6 +42,10 @@
 
     public static final int LOADER_ID_OAUTH_TOKEN = 1;
 
+    public static final String EXTRA_OAUTH_ACCESS_TOKEN = "accessToken";
+    public static final String EXTRA_OAUTH_REFRESH_TOKEN = "refreshToken";
+    public static final String EXTRA_OAUTH_EXPIRES_IN = "expiresIn";
+
     private WebView mWv;
     private OAuthProvider mProvider;
     private String mAuthenticationCode;
@@ -179,12 +183,9 @@
 
         } else {
             final Intent intent = new Intent();
-            intent.putExtra(AccountSetupBasics.EXTRA_OAUTH_ACCESS_TOKEN,
-                    data.mAccessToken);
-            intent.putExtra(AccountSetupBasics.EXTRA_OAUTH_REFRESH_TOKEN,
-                    data.mRefreshToken);
-            intent.putExtra(AccountSetupBasics.EXTRA_OAUTH_EXPIRES_IN,
-                    data.mExpiresInSeconds);
+            intent.putExtra(EXTRA_OAUTH_ACCESS_TOKEN, data.mAccessToken);
+            intent.putExtra(EXTRA_OAUTH_REFRESH_TOKEN, data.mRefreshToken);
+            intent.putExtra(EXTRA_OAUTH_EXPIRES_IN, data.mExpiresInSeconds);
             setResult(AccountSetupBasics.RESULT_OAUTH_SUCCESS, intent);
         }
         finish();
diff --git a/src/com/android/email/activity/setup/SignInActivity.java b/src/com/android/email/activity/setup/SignInActivity.java
new file mode 100644
index 0000000..fa91e7b
--- /dev/null
+++ b/src/com/android/email/activity/setup/SignInActivity.java
@@ -0,0 +1,57 @@
+package com.android.email.activity.setup;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+
+import com.android.email.R;
+
+public class SignInActivity extends Activity implements SignInFragment.SignInCallback {
+
+    public static final String EXTRA_EMAIL = "email";
+
+    public static final String EXTRA_PASSWORD = "password";
+    public static final String EXTRA_OAUTH_PROVIDER = "provider";
+    public static final String EXTRA_OAUTH_ACCESS_TOKEN = "accessToken";
+    public static final String EXTRA_OAUTH_REFRESH_TOKEN = "refreshToken";
+    public static final String EXTRA_OAUTH_EXPIRES_IN_SECONDS = "expiresInSeconds";
+
+    private SignInFragment mFragment;
+
+    @Override
+    public void onCreate(final Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.sign_in_activity);
+        mFragment = (SignInFragment)
+                getFragmentManager().findFragmentById(R.id.sign_in_fragment);
+        mFragment.setEmailAddress(getIntent().getStringExtra(EXTRA_EMAIL));
+        mFragment.setSignInCallback(this);
+    }
+
+    @Override
+    public void onOAuthSignIn(final String providerId, final String accessToken,
+            final String refreshToken, final int expiresInSeconds) {
+        final Intent intent = new Intent();
+        intent.putExtra(EXTRA_OAUTH_PROVIDER, providerId);
+        intent.putExtra(EXTRA_OAUTH_ACCESS_TOKEN, accessToken);
+        intent.putExtra(EXTRA_OAUTH_REFRESH_TOKEN, refreshToken);
+        intent.putExtra(EXTRA_OAUTH_EXPIRES_IN_SECONDS, expiresInSeconds);
+        setResult(RESULT_OK, intent);
+        finish();
+    }
+
+    @Override
+    public void onPasswordSignIn(final String password) {
+        final Intent intent = new Intent();
+        intent.putExtra(EXTRA_PASSWORD, password);
+        setResult(RESULT_OK, intent);
+        finish();
+    }
+
+    @Override
+    public void onCancel() {
+        setResult(RESULT_CANCELED);
+        finish();
+    }
+
+}
diff --git a/src/com/android/email/activity/setup/SignInFragment.java b/src/com/android/email/activity/setup/SignInFragment.java
new file mode 100644
index 0000000..83a93a1
--- /dev/null
+++ b/src/com/android/email/activity/setup/SignInFragment.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright (C) 2014 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.
+ */
+package com.android.email.activity.setup;
+
+import android.app.Fragment;
+import android.content.Context;
+import android.content.Intent;
+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.View.OnClickListener;
+import android.view.ViewGroup;
+import android.widget.EditText;
+import android.widget.Toast;
+
+import com.android.email.R;
+import com.android.email.activity.UiUtilities;
+import com.android.emailcommon.Logging;
+import com.android.emailcommon.VendorPolicyLoader.OAuthProvider;
+import com.android.emailcommon.VendorPolicyLoader.Provider;
+import com.android.mail.utils.LogUtils;
+
+import java.util.List;
+
+public class SignInFragment extends Fragment implements OnClickListener {
+
+    public static final int REQUEST_OAUTH = 1;
+
+    public static final int RESULT_OAUTH_SUCCESS = 0;
+    public static final int RESULT_OAUTH_USER_CANCELED = -1;
+    public static final int RESULT_OAUTH_FAILURE = -2;
+
+    private View mNextButton;
+    private View mPreviousButton;
+    private View mOAuthButton;
+    private EditText mPasswordText;
+    private TextWatcher mValidationTextWatcher;
+    private String mEmailAddress;
+    private String mProviderId;
+    private SignInCallback mCallback;
+    private Context mContext;
+
+    public interface SignInCallback {
+        public void onOAuthSignIn(final String providerId, final String accessToken,
+                final String refreshToken, final int expiresInSeconds);
+
+        public void onPasswordSignIn(final String password);
+
+        public void onCancel();
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+            Bundle savedInstanceState) {
+        final View view = inflater.inflate(R.layout.sign_in_fragment, container, false);
+
+        mNextButton = UiUtilities.getView(view, R.id.next);
+        mPreviousButton = UiUtilities.getView(view, R.id.previous);
+        mOAuthButton = UiUtilities.getView(view, R.id.sign_in_with_google);
+        mPasswordText = UiUtilities.getView(view, R.id.account_password);
+        mNextButton.setOnClickListener(this);
+        mPreviousButton.setOnClickListener(this);
+        mOAuthButton.setOnClickListener(this);
+
+        // After any text edits, call validateFields() which enables or disables the Next button
+        mValidationTextWatcher = new TextWatcher() {
+            @Override
+            public void afterTextChanged(Editable s) {
+                validatePassword();
+            }
+
+            @Override
+            public void beforeTextChanged(CharSequence s, int start, int count, int after) { }
+            @Override
+            public void onTextChanged(CharSequence s, int start, int before, int count) { }
+        };
+        mPasswordText.addTextChangedListener(mValidationTextWatcher);
+
+        return view;
+    }
+
+    @Override
+    public void onActivityCreated(Bundle savedInstanceState) {
+        mContext = getActivity();
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+        mPasswordText.removeTextChangedListener(mValidationTextWatcher);
+        mPasswordText = null;
+    }
+
+    public void validatePassword() {
+        enableNextButton(!TextUtils.isEmpty(mPasswordText.getText()));
+        // Warn (but don't prevent) if password has leading/trailing spaces
+        AccountSettingsUtils.checkPasswordSpaces(mContext, mPasswordText);
+    }
+
+    private void enableNextButton(final boolean enabled) {
+        mNextButton.setEnabled(enabled);
+    }
+
+    @Override
+    public void onActivityResult(final int requestCode, final int resultCode,
+            final Intent data) {
+        if (requestCode == REQUEST_OAUTH) {
+            if (resultCode == RESULT_OAUTH_SUCCESS) {
+                final String accessToken = data.getStringExtra(
+                        OAuthAuthenticationActivity.EXTRA_OAUTH_ACCESS_TOKEN);
+                final String refreshToken = data.getStringExtra(
+                        OAuthAuthenticationActivity.EXTRA_OAUTH_REFRESH_TOKEN);
+                final int expiresInSeconds = data.getIntExtra(
+                        OAuthAuthenticationActivity.EXTRA_OAUTH_EXPIRES_IN, 0);
+                mCallback.onOAuthSignIn(mProviderId, accessToken, refreshToken, expiresInSeconds);
+
+                getActivity().finish();
+            } else if (resultCode == RESULT_OAUTH_FAILURE
+                    || resultCode == RESULT_OAUTH_USER_CANCELED) {
+                LogUtils.i(Logging.LOG_TAG, "Result from oauth %d", resultCode);
+            } else {
+                LogUtils.wtf(Logging.LOG_TAG, "Unknown result code from OAUTH: %d", resultCode);
+            }
+        } else {
+            LogUtils.e(Logging.LOG_TAG, "Unknown request code for onActivityResult in"
+                    + " AccountSetupBasics: %d", requestCode);
+        }
+    }
+
+    @Override
+    public void onClick(View view) {
+        if (view == mNextButton) {
+            mCallback.onPasswordSignIn(mPasswordText.getText().toString());
+        } else if (view == mPreviousButton) {
+            mCallback.onCancel();
+        } else if (view == mOAuthButton) {
+            List<OAuthProvider> oauthProviders = AccountSettingsUtils.getAllOAuthProviders(
+                    mContext);
+            // FLAG currently the only oauth provider we support is google.
+            // If we ever have more than 1 oauth provider, then we need to implement some sort
+            // of picker UI. For now, just always take the first oauth provider.
+            if (oauthProviders.size() > 0) {
+                mProviderId = oauthProviders.get(0).id;
+                final Intent i = new Intent(mContext, OAuthAuthenticationActivity.class);
+                i.putExtra(OAuthAuthenticationActivity.EXTRA_EMAIL_ADDRESS, mEmailAddress);
+                i.putExtra(OAuthAuthenticationActivity.EXTRA_PROVIDER, mProviderId);
+                startActivityForResult(i, REQUEST_OAUTH);
+            }
+        }
+    }
+
+    public void setEmailAddress(final String emailAddress) {
+        mEmailAddress = emailAddress;
+    }
+
+    public String getEmailAddress() {
+        return mEmailAddress;
+    }
+
+    public void setSignInCallback(SignInCallback callback) {
+        mCallback = callback;
+    }
+}