Skip to content
1 change: 1 addition & 0 deletions WordPress/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ dependencies {
compile 'com.android.support:recyclerview-v7:23.1.1'
compile 'com.android.support:design:23.1.1'
compile 'com.google.android.gms:play-services-gcm:8.3.0'
compile 'com.google.android.gms:play-services-auth:8.3.0'
compile 'com.github.chrisbanes.photoview:library:1.2.4'
compile 'com.helpshift:android-helpshift-aar:4.4.0'
compile 'de.greenrobot:eventbus:2.4.0'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,7 @@ public static void viewStatsSinglePostDetails(Context context, PostModel post) {

public static void addSelfHostedSiteForResult(Activity activity) {
Intent intent = new Intent(activity, SignInActivity.class);
intent.putExtra(SignInActivity.START_FRAGMENT_KEY, SignInActivity.ADD_SELF_HOSTED_BLOG);
intent.putExtra(SignInActivity.EXTRA_START_FRAGMENT, SignInActivity.ADD_SELF_HOSTED_BLOG);
activity.startActivityForResult(intent, RequestCodes.ADD_ACCOUNT);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,25 +1,34 @@
package org.wordpress.android.ui.accounts;

import android.app.Activity;
import android.app.FragmentManager;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import android.view.Window;

import com.google.android.gms.auth.api.credentials.Credential;

import org.wordpress.android.R;
import org.wordpress.android.WordPress;
import org.wordpress.android.analytics.AnalyticsTracker;
import org.wordpress.android.analytics.AnalyticsTracker.Stat;
import org.wordpress.android.models.Blog;
import org.wordpress.android.ui.ActivityId;
import org.wordpress.android.util.AppLog;
import org.wordpress.android.util.AppLog.T;

public class SignInActivity extends Activity {
public class SignInActivity extends FragmentActivity {
public static final int SIGN_IN_REQUEST = 1;
public static final int REQUEST_CODE = 5000;
public static final int ADD_SELF_HOSTED_BLOG = 2;
public static final int SHOW_CERT_DETAILS = 4;
public static String START_FRAGMENT_KEY = "start-fragment";
public static final String ARG_JETPACK_SITE_AUTH = "ARG_JETPACK_SITE_AUTH";
public static final String ARG_JETPACK_MESSAGE_AUTH = "ARG_JETPACK_MESSAGE_AUTH";
public static final String ARG_IS_AUTH_ERROR = "ARG_IS_AUTH_ERROR";
public static final int SMART_LOCK_SAVE = 5;
public static final int SMART_LOCK_READ = 6;

public static final String EXTRA_START_FRAGMENT = "start-fragment";
public static final String EXTRA_JETPACK_SITE_AUTH = "EXTRA_JETPACK_SITE_AUTH";
public static final String EXTRA_JETPACK_MESSAGE_AUTH = "EXTRA_JETPACK_MESSAGE_AUTH";
public static final String EXTRA_IS_AUTH_ERROR = "EXTRA_IS_AUTH_ERROR";

private SignInFragment mSignInFragment;

Expand All @@ -31,22 +40,20 @@ protected void onCreate(Bundle savedInstanceState) {
FragmentManager fragmentManager = getFragmentManager();
mSignInFragment = (SignInFragment) fragmentManager.findFragmentById(R.id.sign_in_fragment);
actionMode(getIntent().getExtras());

ActivityId.trackLastActivity(ActivityId.LOGIN);
}

private void actionMode(Bundle extras) {
int actionMode = SIGN_IN_REQUEST;
if (extras != null) {
actionMode = extras.getInt(START_FRAGMENT_KEY, -1);

if (extras.containsKey(ARG_JETPACK_SITE_AUTH)) {
Blog jetpackBlog = WordPress.getBlog(extras.getInt(ARG_JETPACK_SITE_AUTH));
actionMode = extras.getInt(EXTRA_START_FRAGMENT, -1);
if (extras.containsKey(EXTRA_JETPACK_SITE_AUTH)) {
Blog jetpackBlog = WordPress.getBlog(extras.getInt(EXTRA_JETPACK_SITE_AUTH));
if (jetpackBlog != null) {
String customMessage = extras.getString(ARG_JETPACK_MESSAGE_AUTH, null);
String customMessage = extras.getString(EXTRA_JETPACK_MESSAGE_AUTH, null);
mSignInFragment.setBlogAndCustomMessageForJetpackAuth(jetpackBlog, customMessage);
}
} else if (extras.containsKey(ARG_IS_AUTH_ERROR)) {
} else if (extras.containsKey(EXTRA_IS_AUTH_ERROR)) {
mSignInFragment.showAuthErrorMessage();
}
}
Expand All @@ -57,8 +64,7 @@ private void actionMode(Bundle extras) {
default:
break;
}


mSignInFragment.smartLockAutoFill();
}

@Override
Expand All @@ -67,6 +73,21 @@ protected void onActivityResult(int requestCode, int resultCode, Intent data) {

if (requestCode == SHOW_CERT_DETAILS) {
mSignInFragment.askForSslTrust();
} else if (requestCode == SMART_LOCK_SAVE) {
if (resultCode == RESULT_OK) {
AnalyticsTracker.track(Stat.LOGIN_AUTOFILL_CREDENTIALS_UPDATED);
AppLog.d(T.NUX, "Credentials saved");
} else {
AppLog.d(T.NUX, "Credentials save cancelled");
}
} else if (requestCode == SMART_LOCK_READ) {
if (resultCode == RESULT_OK) {
AppLog.d(T.NUX, "Credentials retrieved");
Credential credential = data.getParcelableExtra(Credential.EXTRA_KEY);
mSignInFragment.onCredentialRetrieved(credential);
} else {
AppLog.e(T.NUX, "Credential read failed");
}
} else if (resultCode == RESULT_OK && data != null) {
String username = data.getStringExtra("username");
String password = data.getStringExtra("password");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentSender;
import android.content.res.Configuration;
import android.net.Uri;
import android.os.AsyncTask;
Expand All @@ -28,6 +29,17 @@
import android.widget.RelativeLayout;
import android.widget.TextView;

import com.google.android.gms.auth.api.Auth;
import com.google.android.gms.auth.api.credentials.Credential;
import com.google.android.gms.auth.api.credentials.CredentialRequest;
import com.google.android.gms.auth.api.credentials.CredentialRequestResult;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.api.CommonStatusCodes;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.api.GoogleApiClient.ConnectionCallbacks;
import com.google.android.gms.common.api.GoogleApiClient.OnConnectionFailedListener;
import com.google.android.gms.common.api.ResultCallback;
import com.google.android.gms.common.api.Status;
import com.wordpress.rest.RestRequest;

import org.json.JSONException;
Expand Down Expand Up @@ -70,7 +82,8 @@
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class SignInFragment extends AbstractFragment implements TextWatcher {
public class SignInFragment extends AbstractFragment implements TextWatcher, ConnectionCallbacks,
OnConnectionFailedListener {
private static final String DOT_COM_BASE_URL = "https://wordpress.com";
private static final String FORGOT_PASSWORD_RELATIVE_URL = "/wp-login.php?action=lostpassword";
private static final int WPCOM_ERRONEOUS_LOGIN_THRESHOLD = 3;
Expand Down Expand Up @@ -116,6 +129,8 @@ public class SignInFragment extends AbstractFragment implements TextWatcher {
private String mHttpPassword;
private Blog mJetpackBlog;

private GoogleApiClient mCredentialsClient;

public SignInFragment() {
mEmailChecker = new EmailChecker();
}
Expand Down Expand Up @@ -206,6 +221,7 @@ public void onClick(View v) {
initPasswordVisibilityButton(rootView, mPasswordEditText);
initInfoButtons(rootView);
moveBottomButtons();
initSmartLockForPasswords();

return rootView;
}
Expand Down Expand Up @@ -275,6 +291,66 @@ public void onClick(View v) {
mInfoButtonSecondary.setOnClickListener(infoButtonListener);
}

private void initSmartLockForPasswords() {
mCredentialsClient = new GoogleApiClient.Builder(getActivity())
.addConnectionCallbacks(this)
.enableAutoManage((SignInActivity) getActivity(), this)
.addApi(Auth.CREDENTIALS_API)
.build();
}

public void smartLockAutoFill() {
CredentialRequest credentialRequest = new CredentialRequest.Builder()
.setSupportsPasswordLogin(true)
.build();
Auth.CredentialsApi.request(mCredentialsClient, credentialRequest).setResultCallback(
new ResultCallback<CredentialRequestResult>() {
@Override
public void onResult(CredentialRequestResult result) {
Status status = result.getStatus();
if (status.isSuccess()) {
Credential credential = result.getCredential();
onCredentialRetrieved(credential);
} else {
if (status.getStatusCode() == CommonStatusCodes.RESOLUTION_REQUIRED) {
try {
// Prompt the user to choose a saved credential
status.startResolutionForResult(getActivity(), SignInActivity.SMART_LOCK_READ);
} catch (IntentSender.SendIntentException e) {
AppLog.d(T.NUX, "SmartLock: Failed to send resolution for credential " +
"request");
}
} else {
// The user must create an account or sign in manually.
AppLog.d(T.NUX, "SmartLock: Unsuccessful credential request.");
}
}
}
});
}

public void onCredentialRetrieved(Credential credential) {
AppLog.d(T.NUX, "Retrieved username from SmartLock: " + credential.getId());
AnalyticsTracker.track(Stat.LOGIN_AUTOFILL_CREDENTIALS_FILLED);
mUsernameEditText.setText(credential.getId());
mPasswordEditText.setText(credential.getPassword());
}

@Override
public void onConnectionFailed(ConnectionResult connectionResult) {
AppLog.d(T.NUX, "Connection result: " + connectionResult);
}

@Override
public void onConnected(Bundle bundle) {
AppLog.d(T.NUX, "Google API client connected");
}

@Override
public void onConnectionSuspended(int i) {
AppLog.d(T.NUX, "Google API client connection suspended");
}

private void setSecondaryButtonVisible(boolean visible) {
mInfoButtonSecondary.setVisibility(visible ? View.VISIBLE : View.GONE);
mInfoButton.setVisibility(visible ? View.GONE : View.VISIBLE);
Expand Down Expand Up @@ -562,12 +638,43 @@ private boolean hasHardwareKeyboard() {
return (getResources().getConfiguration().keyboard != Configuration.KEYBOARD_NOKEYS);
}

private void saveCrendentialsInSmartLock() {
Credential credential = new Credential.Builder(mUsername).setPassword(mPassword).build();
Auth.CredentialsApi.save(mCredentialsClient, credential).setResultCallback(
new ResultCallback<Status>() {
@Override
public void onResult(Status status) {
if (!status.isSuccess() && status.hasResolution()) {
try {
// This prompt the user to resolve the save request
status.startResolutionForResult(getActivity(), SignInActivity.SMART_LOCK_SAVE);
} catch (IntentSender.SendIntentException e) {
// Could not resolve the request
}
}
}
});
}

private void deleteCredentialsInSmartLock() {
Credential credential = new Credential.Builder(mUsername).setPassword(mPassword).build();
Auth.CredentialsApi.delete(mCredentialsClient, credential).setResultCallback(
new ResultCallback<Status>() {
@Override
public void onResult(Status status) {
AppLog.i(T.NUX, status.isSuccess() ? "SmartLock: credentials deleted for username: " + mUsername
: "SmartLock: Credentials not deleted for username: " + mUsername );
}
});
}

private void signInAndFetchBlogListWPCom() {
LoginWPCom login = new LoginWPCom(mUsername, mPassword, mTwoStepCode, mShouldSendTwoStepSMS, mJetpackBlog);
login.execute(new LoginAbstract.Callback() {
@Override
public void onSuccess() {
mShouldSendTwoStepSMS = false;
saveCrendentialsInSmartLock();

// Finish this activity if we've authenticated to a Jetpack site
if (isJetpackAuth() && getActivity() != null) {
Expand All @@ -584,6 +691,10 @@ public void onSuccess() {
public void onError(int errorMessageId, boolean twoStepCodeRequired, boolean httpAuthRequired, boolean erroneousSslCertificate) {
mFetchBlogListCallback.onError(errorMessageId, twoStepCodeRequired, httpAuthRequired, erroneousSslCertificate, "");
mShouldSendTwoStepSMS = false;
// Delete credentials only if login failed with an incorrect username/password error
if (errorMessageId == R.string.username_or_password_incorrect) {
deleteCredentialsInSmartLock();
}
}
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -519,9 +519,9 @@ private boolean updateTimeframeAndDateAndStartRefreshInFragment(FragmentManager
private void startWPComLoginActivity() {
mResultCode = RESULT_CANCELED;
Intent signInIntent = new Intent(this, SignInActivity.class);
signInIntent.putExtra(SignInActivity.ARG_JETPACK_SITE_AUTH, mLocalBlogID);
signInIntent.putExtra(SignInActivity.EXTRA_JETPACK_SITE_AUTH, mLocalBlogID);
signInIntent.putExtra(
SignInActivity.ARG_JETPACK_MESSAGE_AUTH,
SignInActivity.EXTRA_JETPACK_MESSAGE_AUTH,
getString(R.string.stats_sign_in_jetpack_different_com_account)
);
startActivityForResult(signInIntent, SignInActivity.REQUEST_CODE);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ public static void showAuthErrorView(Activity activity, int titleResId, int mess
// WP.com errors will show the sign in activity
if (WordPress.getCurrentBlog() == null || (WordPress.getCurrentBlog() != null && WordPress.getCurrentBlog().isDotcomFlag())) {
Intent signInIntent = new Intent(activity, SignInActivity.class);
signInIntent.putExtra(SignInActivity.ARG_IS_AUTH_ERROR, true);
signInIntent.putExtra(SignInActivity.EXTRA_IS_AUTH_ERROR, true);
signInIntent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
activity.startActivityForResult(signInIntent, SignInActivity.REQUEST_CODE);
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,8 @@ public enum Stat {
LOGIN_FAILED,
LOGIN_FAILED_TO_GUESS_XMLRPC,
LOGIN_INSERTED_INVALID_URL,
LOGIN_AUTOFILL_CREDENTIALS_FILLED,
LOGIN_AUTOFILL_CREDENTIALS_UPDATED,
PUSH_AUTHENTICATION_APPROVED,
PUSH_AUTHENTICATION_EXPIRED,
PUSH_AUTHENTICATION_FAILED,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -786,6 +786,18 @@ private AnalyticsTrackerMixpanelInstructionsForStat instructionsForStat(
instructions = AnalyticsTrackerMixpanelInstructionsForStat.
mixpanelInstructionsForEventName("Login - Failed To Guess XMLRPC");
break;
case LOGIN_INSERTED_INVALID_URL:
instructions = AnalyticsTrackerMixpanelInstructionsForStat.
mixpanelInstructionsForEventName("Login - Inserted Invalid URL");
break;
case LOGIN_AUTOFILL_CREDENTIALS_FILLED:
instructions = AnalyticsTrackerMixpanelInstructionsForStat.
mixpanelInstructionsForEventName("Login - Auto Fill Credentials Filled");
break;
case LOGIN_AUTOFILL_CREDENTIALS_UPDATED:
instructions = AnalyticsTrackerMixpanelInstructionsForStat.
mixpanelInstructionsForEventName("Login - Auto Fill Credentials Updated");
break;
case PUSH_AUTHENTICATION_APPROVED:
instructions = AnalyticsTrackerMixpanelInstructionsForStat.
mixpanelInstructionsForEventName("Push Authentication - Approved");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,12 @@ public void track(AnalyticsTracker.Stat stat, Map<String, ?> properties) {
case LOGIN_INSERTED_INVALID_URL:
eventName = "login_inserted_invalid_url";
break;
case LOGIN_AUTOFILL_CREDENTIALS_FILLED:
eventName = "login_autofill_credentials_filled";
break;
case LOGIN_AUTOFILL_CREDENTIALS_UPDATED:
eventName = "login_autofill_credentials_updated";
break;
case PUSH_AUTHENTICATION_APPROVED:
eventName = "push_authentication_approved";
break;
Expand Down