diff --git a/WordPress/src/main/java/org/wordpress/android/ui/accounts/LoginActivity.java b/WordPress/src/main/java/org/wordpress/android/ui/accounts/LoginActivity.java index 6defddcab3ef..5eba82e66790 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/accounts/LoginActivity.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/accounts/LoginActivity.java @@ -19,6 +19,7 @@ import com.google.android.gms.common.api.GoogleApiClient.ConnectionCallbacks; import com.google.android.gms.common.api.GoogleApiClient.OnConnectionFailedListener; +import org.jetbrains.annotations.NotNull; import org.wordpress.android.R; import org.wordpress.android.WordPress; import org.wordpress.android.analytics.AnalyticsTracker; @@ -51,9 +52,12 @@ import org.wordpress.android.ui.accounts.login.LoginPrologueFragment; import org.wordpress.android.ui.accounts.login.LoginPrologueListener; import org.wordpress.android.ui.notifications.services.NotificationsUpdateServiceStarter; +import org.wordpress.android.ui.posts.BasicFragmentDialog; +import org.wordpress.android.ui.posts.BasicFragmentDialog.BasicDialogPositiveClickInterface; import org.wordpress.android.ui.reader.services.update.ReaderUpdateLogic; import org.wordpress.android.ui.reader.services.update.ReaderUpdateServiceStarter; import org.wordpress.android.util.AppLog; +import org.wordpress.android.util.AppLog.T; import org.wordpress.android.util.CrashlyticsUtils; import org.wordpress.android.util.LanguageUtils; import org.wordpress.android.util.LocaleManager; @@ -76,7 +80,7 @@ public class LoginActivity extends AppCompatActivity implements ConnectionCallbacks, OnConnectionFailedListener, Callback, LoginListener, GoogleListener, LoginPrologueListener, SignupSheetListener, - HasSupportFragmentInjector { + HasSupportFragmentInjector, BasicDialogPositiveClickInterface { public static final String ARG_JETPACK_CONNECT_SOURCE = "ARG_JETPACK_CONNECT_SOURCE"; public static final String MAGIC_LOGIN = "magic-login"; public static final String TOKEN_PARAMETER = "token"; @@ -86,6 +90,8 @@ public class LoginActivity extends AppCompatActivity implements ConnectionCallba private static final String FORGOT_PASSWORD_URL_SUFFIX = "wp-login.php?action=lostpassword"; + private static final String GOOGLE_ERROR_DIALOG_TAG = "google_error_dialog_tag"; + private enum SmartLockHelperState { NOT_TRIGGERED, TRIGGER_FILL_IN_ON_CONNECT, @@ -158,14 +164,6 @@ protected void onCreate(Bundle savedInstanceState) { @Override public void onSaveInstanceState(Bundle outState) { - SignupGoogleFragment signupGoogleFragment; - FragmentManager fragmentManager = getSupportFragmentManager(); - signupGoogleFragment = (SignupGoogleFragment) fragmentManager.findFragmentByTag(SignupGoogleFragment.TAG); - - if (signupGoogleFragment != null) { - fragmentManager.beginTransaction().remove(signupGoogleFragment).commit(); - } - super.onSaveInstanceState(outState); outState.putBoolean(KEY_SIGNUP_SHEET_DISPLAYED, mSignupSheetDisplayed); @@ -256,6 +254,7 @@ private void loggedInAndFinish(ArrayList oldSitesIds, boolean doLoginUp @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { + AppLog.d(T.MAIN, "LoginActivity: onActivity Result - requestCode" + requestCode); super.onActivityResult(requestCode, resultCode, data); switch (requestCode) { @@ -364,6 +363,7 @@ public void onSignupSheetEmailClicked() { @Override public void onSignupSheetGoogleClicked() { + dismissSignupSheet(); AnalyticsTracker.track(AnalyticsTracker.Stat.CREATE_ACCOUNT_INITIATED); AnalyticsTracker.track(AnalyticsTracker.Stat.SIGNUP_GOOGLE_BUTTON_TAPPED); @@ -371,12 +371,6 @@ public void onSignupSheetGoogleClicked() { SignupGoogleFragment signupGoogleFragment; FragmentManager fragmentManager = getSupportFragmentManager(); FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction(); - signupGoogleFragment = (SignupGoogleFragment) fragmentManager.findFragmentByTag(SignupGoogleFragment.TAG); - - if (signupGoogleFragment != null) { - fragmentTransaction.remove(signupGoogleFragment); - } - signupGoogleFragment = new SignupGoogleFragment(); signupGoogleFragment.setRetainInstance(true); fragmentTransaction.add(signupGoogleFragment, SignupGoogleFragment.TAG); @@ -592,16 +586,10 @@ public void helpSocialEmailScreen(String email) { } @Override - public void addGoogleLoginFragment(@NonNull Fragment parent) { + public void addGoogleLoginFragment() { LoginGoogleFragment loginGoogleFragment; - FragmentManager fragmentManager = parent.getChildFragmentManager(); + FragmentManager fragmentManager = getSupportFragmentManager(); FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction(); - loginGoogleFragment = (LoginGoogleFragment) fragmentManager.findFragmentByTag(LoginGoogleFragment.TAG); - - if (loginGoogleFragment != null) { - fragmentTransaction.remove(loginGoogleFragment); - } - loginGoogleFragment = new LoginGoogleFragment(); loginGoogleFragment.setRetainInstance(true); fragmentTransaction.add(loginGoogleFragment, LoginGoogleFragment.TAG); @@ -761,6 +749,26 @@ public void onGoogleSignupFinished(String name, String email, String photoUrl, S finish(); } + @Override + public void onGoogleSignupError(String msg) { + BasicFragmentDialog dialog = new BasicFragmentDialog(); + dialog.initialize(GOOGLE_ERROR_DIALOG_TAG, getString(R.string.error), + msg, + getString(org.wordpress.android.login.R.string.login_error_button), + null, + null); + dialog.show(this.getSupportFragmentManager(), GOOGLE_ERROR_DIALOG_TAG); + } + + @Override + public void onPositiveClicked(@NotNull String instanceTag) { + switch (instanceTag) { + case GOOGLE_ERROR_DIALOG_TAG: + // just dismiss the dialog + break; + } + } + private void dismissSignupSheet() { if (mSignupSheet != null) { mSignupSheet.dismiss(); diff --git a/WordPress/src/main/res/values/strings.xml b/WordPress/src/main/res/values/strings.xml index 0591f4d47d9b..b665ed9d2d9a 100644 --- a/WordPress/src/main/res/values/strings.xml +++ b/WordPress/src/main/res/values/strings.xml @@ -2064,8 +2064,9 @@ My sites Log in with Google. Close - The Google account \'%s\' doesn\'t match any existing account on WordPress.com. + There\'s no WordPress.com account matching this Google account. There was some trouble connecting with the Google account. + We\'ve made too many attempts to send an SMS verification code — take a break, and request a new one in a minute. Google login could not be started. \nMaybe try a different account? diff --git a/libs/login/WordPressLoginFlow/src/main/java/org/wordpress/android/login/GoogleFragment.java b/libs/login/WordPressLoginFlow/src/main/java/org/wordpress/android/login/GoogleFragment.java index 744a73c079b7..cdb255b4a018 100644 --- a/libs/login/WordPressLoginFlow/src/main/java/org/wordpress/android/login/GoogleFragment.java +++ b/libs/login/WordPressLoginFlow/src/main/java/org/wordpress/android/login/GoogleFragment.java @@ -6,9 +6,7 @@ import android.os.Bundle; import android.support.annotation.NonNull; import android.support.v4.app.Fragment; -import android.support.v7.app.AlertDialog; import android.util.Log; -import android.view.ContextThemeWrapper; import com.google.android.gms.auth.api.Auth; import com.google.android.gms.auth.api.signin.GoogleSignInOptions; @@ -21,14 +19,26 @@ import org.wordpress.android.fluxc.Dispatcher; import org.wordpress.android.fluxc.store.SiteStore; import org.wordpress.android.util.AppLog; +import org.wordpress.android.util.AppLog.T; import javax.inject.Inject; import static android.app.Activity.RESULT_OK; public class GoogleFragment extends Fragment implements ConnectionCallbacks, OnConnectionFailedListener { + private static final String STATE_SHOULD_RESOLVE_ERROR = "STATE_SHOULD_RESOLVE_ERROR"; + private static final String STATE_FINISHED = "STATE_FINISHED"; + private static final String STATE_DISPLAY_NAME = "STATE_DISPLAY_NAME"; + private static final String STATE_GOOGLE_EMAIL = "STATE_GOOGLE_EMAIL"; + private static final String STATE_GOOGLE_TOKEN_ID = "STATE_GOOGLE_TOKEN_ID"; + private static final String STATE_GOOGLE_PHOTO_URL = "STATE_GOOGLE_PHOTO_URL"; private boolean mIsResolvingError; private boolean mShouldResolveError; + /** + * This flag is used to store the information the finishFlow was called when the fragment wasn't attached to an + * activity (for example an EventBus event was received during ongoing configuration change). + */ + private boolean mFinished; private static final String STATE_RESOLVING_ERROR = "STATE_RESOLVING_ERROR"; private static final int REQUEST_CONNECT = 1000; @@ -52,14 +62,22 @@ public interface GoogleListener { void onGoogleEmailSelected(String email); void onGoogleLoginFinished(); void onGoogleSignupFinished(String name, String email, String photoUrl, String username); + void onGoogleSignupError(String msg); } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - - // Restore state of error resolving. - mIsResolvingError = savedInstanceState != null && savedInstanceState.getBoolean(STATE_RESOLVING_ERROR, false); + mDispatcher.register(this); + if (savedInstanceState != null) { + mIsResolvingError = savedInstanceState.getBoolean(STATE_RESOLVING_ERROR, false); + mShouldResolveError = savedInstanceState.getBoolean(STATE_SHOULD_RESOLVE_ERROR, false); + mFinished = savedInstanceState.getBoolean(STATE_FINISHED, false); + mDisplayName = savedInstanceState.getString(STATE_DISPLAY_NAME); + mGoogleEmail = savedInstanceState.getString(STATE_GOOGLE_EMAIL); + mIdToken = savedInstanceState.getString(STATE_GOOGLE_TOKEN_ID); + mPhotoUrl = savedInstanceState.getString(STATE_GOOGLE_PHOTO_URL); + } // Configure sign-in to request user's ID, basic profile, email address, and ID token. // ID and basic profile are included in DEFAULT_SIGN_IN. @@ -71,7 +89,7 @@ public void onCreate(Bundle savedInstanceState) { .build(); // Build Google API client with access to sign-in API and options specified above. - mGoogleApiClient = new GoogleApiClient.Builder(getActivity()) + mGoogleApiClient = new GoogleApiClient.Builder(getActivity().getApplicationContext()) .addApi(Auth.GOOGLE_SIGN_IN_API, googleSignInOptions) .addConnectionCallbacks(this) .addOnConnectionFailedListener(this) @@ -83,9 +101,15 @@ public void onCreate(Bundle savedInstanceState) { } @Override - public void onSaveInstanceState(Bundle outState) { + public void onSaveInstanceState(@NonNull Bundle outState) { super.onSaveInstanceState(outState); outState.putBoolean(STATE_RESOLVING_ERROR, mIsResolvingError); + outState.putBoolean(STATE_SHOULD_RESOLVE_ERROR, mShouldResolveError); + outState.putBoolean(STATE_FINISHED, mFinished); + outState.putString(STATE_DISPLAY_NAME, mDisplayName); + outState.putString(STATE_GOOGLE_EMAIL, mGoogleEmail); + outState.putString(STATE_GOOGLE_TOKEN_ID, mIdToken); + outState.putString(STATE_GOOGLE_PHOTO_URL, mPhotoUrl); } @Override @@ -98,29 +122,26 @@ public void onAttach(Context context) { } catch (ClassCastException exception) { throw new ClassCastException(context.toString() + " must implement GoogleListener"); } - - // Show account dialog when Google API onConnected callback returns before fragment is attached. - if (mGoogleApiClient != null && mGoogleApiClient.isConnected() && !mIsResolvingError && !mShouldResolveError) { - showAccountDialog(); + if (mFinished) { + finishFlow(); } } @Override - public void onDetach() { - super.onDetach(); + public void onDestroy() { disconnectGoogleClient(); + AppLog.d(T.MAIN, "GOOGLE SIGNUP/LOGIN: disconnecting google client"); + mDispatcher.unregister(this); + super.onDestroy(); } @Override - public void onStart() { - super.onStart(); - mDispatcher.register(this); - } - - @Override - public void onStop() { - super.onStop(); - mDispatcher.unregister(this); + public void onResume() { + super.onResume(); + // Show account dialog when Google API onConnected callback returns before fragment is attached. + if (mGoogleApiClient != null && mGoogleApiClient.isConnected() && !mIsResolvingError && !mShouldResolveError) { + startFlow(); + } } @Override @@ -130,8 +151,9 @@ public void onConnected(Bundle bundle) { if (mShouldResolveError) { mShouldResolveError = false; + // if the fragment is not attached to an activity, the process is started in the onResume if (isAdded()) { - showAccountDialog(); + startFlow(); } } } @@ -154,7 +176,7 @@ public void onConnectionFailed(@NonNull ConnectionResult connectionResult) { mIsResolvingError = false; AppLog.e(AppLog.T.NUX, GoogleApiAvailability.getInstance().getErrorString( connectionResult.getErrorCode())); - showErrorDialog(getString(R.string.login_error_generic)); + showError(getString(R.string.login_error_generic)); } } } @@ -172,7 +194,7 @@ public void connectGoogleClient() { mShouldResolveError = true; mGoogleApiClient.connect(); } else { - showAccountDialog(); + startFlow(); } } @@ -183,16 +205,24 @@ protected void disconnectGoogleClient() { } } - protected void showAccountDialog() { + protected void startFlow() { // Do nothing here. This should be overridden by inheriting class. } - protected void showErrorDialog(String message) { - AlertDialog dialog = new AlertDialog.Builder(new ContextThemeWrapper(getActivity(), R.style.LoginTheme)) - .setMessage(message) - .setPositiveButton(R.string.login_error_button, null) - .create(); - dialog.show(); + protected void finishFlow() { + /* This flag might get lost when the finishFlow is called after the fragment's + onSaveInstanceState was called - however it's a very rare case, since the fragment is retained across + config changes. */ + mFinished = true; + if (getActivity() != null) { + AppLog.d(T.MAIN, "GOOGLE SIGNUP/LOGIN: finishing signup/login"); + getActivity().getSupportFragmentManager().beginTransaction().remove(this).commitAllowingStateLoss(); + } + } + + protected void showError(String message) { + finishFlow(); + mGoogleListener.onGoogleSignupError(message); } @Override @@ -208,7 +238,7 @@ public void onActivityResult(int request, int result, Intent data) { if (!mGoogleApiClient.isConnecting() && !mGoogleApiClient.isConnected()) { mGoogleApiClient.connect(); } else { - showAccountDialog(); + startFlow(); } mIsResolvingError = false; diff --git a/libs/login/WordPressLoginFlow/src/main/java/org/wordpress/android/login/Login2FaFragment.java b/libs/login/WordPressLoginFlow/src/main/java/org/wordpress/android/login/Login2FaFragment.java index 9fcfe04f30fc..54047e998469 100644 --- a/libs/login/WordPressLoginFlow/src/main/java/org/wordpress/android/login/Login2FaFragment.java +++ b/libs/login/WordPressLoginFlow/src/main/java/org/wordpress/android/login/Login2FaFragment.java @@ -74,8 +74,8 @@ public class Login2FaFragment extends LoginBaseFormFragment imple private static final String TWO_FACTOR_TYPE_AUTHENTICATOR = "authenticator"; private static final String TWO_FACTOR_TYPE_BACKUP = "backup"; - private static final String TWO_FACTOR_TYPE_SMS = "sms"; + public static final String TWO_FACTOR_TYPE_SMS = "sms"; public static final String TAG = "login_2fa_fragment_tag"; private static final Pattern TWO_STEP_AUTH_CODE = Pattern.compile("^[0-9]{6}"); diff --git a/libs/login/WordPressLoginFlow/src/main/java/org/wordpress/android/login/LoginEmailFragment.java b/libs/login/WordPressLoginFlow/src/main/java/org/wordpress/android/login/LoginEmailFragment.java index ffc2380a89ef..1547874d00a5 100644 --- a/libs/login/WordPressLoginFlow/src/main/java/org/wordpress/android/login/LoginEmailFragment.java +++ b/libs/login/WordPressLoginFlow/src/main/java/org/wordpress/android/login/LoginEmailFragment.java @@ -149,7 +149,7 @@ public void onClick(View view) { if (isAdded()) { mOldSitesIDs = SiteUtils.getCurrentSiteIds(mSiteStore, false); mIsSocialLogin = true; - mLoginListener.addGoogleLoginFragment(LoginEmailFragment.this); + mLoginListener.addGoogleLoginFragment(); } else { AppLog.e(T.NUX, "Google login could not be started. LoginEmailFragment was not attached."); showErrorDialog(getString(R.string.login_error_generic_start)); diff --git a/libs/login/WordPressLoginFlow/src/main/java/org/wordpress/android/login/LoginGoogleFragment.java b/libs/login/WordPressLoginFlow/src/main/java/org/wordpress/android/login/LoginGoogleFragment.java index 6259b0955b0c..112bef05a8a5 100644 --- a/libs/login/WordPressLoginFlow/src/main/java/org/wordpress/android/login/LoginGoogleFragment.java +++ b/libs/login/WordPressLoginFlow/src/main/java/org/wordpress/android/login/LoginGoogleFragment.java @@ -25,6 +25,7 @@ public class LoginGoogleFragment extends GoogleFragment { private static final int REQUEST_LOGIN = 1001; + private boolean mLoginRequested = false; public static final String TAG = "login_google_fragment_tag"; @@ -34,18 +35,33 @@ public void onAttach(Context context) { super.onAttach(context); } + @Override + protected void startFlow() { + if (!mLoginRequested) { + AppLog.d(T.MAIN, "GOOGLE LOGIN: startFlow"); + mLoginRequested = true; + Intent loginIntent = Auth.GoogleSignInApi.getSignInIntent(mGoogleApiClient); + startActivityForResult(loginIntent, REQUEST_LOGIN); + } else { + AppLog.d(T.MAIN, "GOOGLE LOGIN: startFlow called, but is already in progress"); + } + } + @Override public void onActivityResult(int request, int result, Intent data) { super.onActivityResult(request, result, data); switch (request) { case REQUEST_LOGIN: + disconnectGoogleClient(); + mLoginRequested = false; if (result == RESULT_OK) { - GoogleSignInResult signInResult = Auth.GoogleSignInApi.getSignInResultFromIntent(data); + AppLog.d(T.MAIN, "GOOGLE LOGIN: Google has returned a sign in result - succcess"); + GoogleSignInResult loginResult = Auth.GoogleSignInApi.getSignInResultFromIntent(data); - if (signInResult.isSuccess()) { + if (loginResult.isSuccess()) { try { - GoogleSignInAccount account = signInResult.getSignInAccount(); + GoogleSignInAccount account = loginResult.getSignInAccount(); if (account != null) { mGoogleEmail = account.getEmail() != null ? account.getEmail() : ""; @@ -53,31 +69,35 @@ public void onActivityResult(int request, int result, Intent data) { mIdToken = account.getIdToken() != null ? account.getIdToken() : ""; } + AppLog.d(T.MAIN, + "GOOGLE LOGIN: Google has returned a sign in result - dispatching " + + "SocialLoginAction"); PushSocialPayload payload = new PushSocialPayload(mIdToken, SERVICE_TYPE_GOOGLE); mDispatcher.dispatch(AccountActionBuilder.newPushSocialLoginAction(payload)); } catch (NullPointerException exception) { - disconnectGoogleClient(); + AppLog.d(T.MAIN, "GOOGLE LOGIN: Google has returned a sign in result - NPE"); AppLog.e(T.NUX, "Cannot get ID token from Google login account.", exception); - showErrorDialog(getString(R.string.login_error_generic)); + showError(getString(R.string.login_error_generic)); } } else { + AppLog.d(T.MAIN, "GOOGLE LOGIN: Google has returned a sign in result - error"); mAnalyticsListener.trackSocialButtonFailure(); - switch (signInResult.getStatus().getStatusCode()) { + switch (loginResult.getStatus().getStatusCode()) { // Internal error. case GoogleSignInStatusCodes.INTERNAL_ERROR: AppLog.e(T.NUX, "Google Login Failed: internal error."); - showErrorDialog(getString(R.string.login_error_generic)); + showError(getString(R.string.login_error_generic)); break; // Attempted to connect with an invalid account name specified. case GoogleSignInStatusCodes.INVALID_ACCOUNT: AppLog.e(T.NUX, "Google Login Failed: invalid account name."); - showErrorDialog(getString(R.string.login_error_generic) - + getString(R.string.login_error_suffix)); + showError(getString(R.string.login_error_generic) + + getString(R.string.login_error_suffix)); break; // Network error. case GoogleSignInStatusCodes.NETWORK_ERROR: AppLog.e(T.NUX, "Google Login Failed: network error."); - showErrorDialog(getString(R.string.error_generic_network)); + showError(getString(R.string.error_generic_network)); break; // Cancelled by the user. case GoogleSignInStatusCodes.SIGN_IN_CANCELLED: @@ -86,70 +106,66 @@ public void onActivityResult(int request, int result, Intent data) { // Attempt didn't succeed with the current account. case GoogleSignInStatusCodes.SIGN_IN_FAILED: AppLog.e(T.NUX, "Google Login Failed: current account failed."); - showErrorDialog(getString(R.string.login_error_generic)); + showError(getString(R.string.login_error_generic)); break; // Attempted to connect, but the user is not signed in. case GoogleSignInStatusCodes.SIGN_IN_REQUIRED: AppLog.e(T.NUX, "Google Login Failed: user is not signed in."); - showErrorDialog(getString(R.string.login_error_generic)); + showError(getString(R.string.login_error_generic)); break; // Timeout error. case GoogleSignInStatusCodes.TIMEOUT: AppLog.e(T.NUX, "Google Login Failed: timeout error."); - showErrorDialog(getString(R.string.google_error_timeout)); + showError(getString(R.string.google_error_timeout)); break; // Unknown error. default: AppLog.e(T.NUX, "Google Login Failed: unknown error."); - showErrorDialog(getString(R.string.login_error_generic)); + showError(getString(R.string.login_error_generic)); break; } } } else if (result == RESULT_CANCELED) { + AppLog.d(T.MAIN, "GOOGLE LOGIN: Google has returned a sign in result - canceled"); mAnalyticsListener.trackSocialButtonFailure(); AppLog.e(T.NUX, "Google Login Failed: result was CANCELED."); + finishFlow(); } else { + AppLog.d(T.MAIN, "GOOGLE LOGIN: Google has returned a sign in result - unknown"); mAnalyticsListener.trackSocialButtonFailure(); AppLog.e(T.NUX, "Google Login Failed: result was not OK or CANCELED."); - showErrorDialog(getString(R.string.login_error_generic)); + showError(getString(R.string.login_error_generic)); } break; } } - @Override - protected void showAccountDialog() { - Intent signInIntent = Auth.GoogleSignInApi.getSignInIntent(mGoogleApiClient); - startActivityForResult(signInIntent, REQUEST_LOGIN); - } - @SuppressWarnings("unused") @Subscribe(threadMode = ThreadMode.MAIN) public void onAuthenticationChanged(OnAuthenticationChanged event) { - disconnectGoogleClient(); - if (event.isError()) { + AppLog.d(T.MAIN, "GOOGLE LOGIN: onAuthenticationChanged - error"); AppLog.e(T.API, "LoginGoogleFragment.onAuthenticationChanged: " + event.error.type - + " - " + event.error.message); + + " - " + event.error.message); mAnalyticsListener.trackLoginFailed(event.getClass().getSimpleName(), event.error.type.toString(), event.error.message); mAnalyticsListener.trackSocialFailure(event.getClass().getSimpleName(), event.error.type.toString(), event.error.message); - showErrorDialog(getString(R.string.login_error_generic)); + showError(getString(R.string.login_error_generic)); } else { + AppLog.d(T.MAIN, "GOOGLE LOGIN: onAuthenticationChanged - success"); AppLog.i(T.NUX, "LoginGoogleFragment.onAuthenticationChanged: " + event.toString()); mGoogleListener.onGoogleLoginFinished(); + finishFlow(); } } @SuppressWarnings("unused") @Subscribe(threadMode = ThreadMode.MAIN) public void onSocialChanged(OnSocialChanged event) { - disconnectGoogleClient(); - // Response returns error for non-existing account and existing account not connected. if (event.isError()) { AppLog.e(T.API, "LoginGoogleFragment.onSocialChanged: " + event.error.type + " - " + event.error.message); @@ -165,27 +181,38 @@ public void onSocialChanged(OnSocialChanged event) { switch (event.error.type) { // WordPress account exists with input email address, but not connected. case USER_EXISTS: + AppLog.d(T.MAIN, "GOOGLE LOGIN: onSocialChanged - wordpress acount exists but not connected"); mAnalyticsListener.trackSocialAccountsNeedConnecting(); mLoginListener.loginViaSocialAccount(mGoogleEmail, mIdToken, SERVICE_TYPE_GOOGLE, true); break; // WordPress account does not exist with input email address. case UNKNOWN_USER: + AppLog.d(T.MAIN, "GOOGLE LOGIN: onSocialChanged - wordpress acount doesn't exist"); mAnalyticsListener.trackSocialErrorUnknownUser(); - showErrorDialog(getString(R.string.login_error_email_not_found, mGoogleEmail)); + showError(getString(R.string.login_error_email_not_found_v2)); + break; + // Too many attempts on sending SMS verification code. The user has to wait before they try again + case SMS_CODE_THROTTLED: + AppLog.d(T.MAIN, "GOOGLE LOGIN: onSocialChanged - error - sms code throttled"); + showError(getString(R.string.login_error_sms_throttled)); break; // Unknown error. case GENERIC_ERROR: // Do nothing for now (included to show all error types) and just fall through to 'default' default: - showErrorDialog(getString(R.string.login_error_generic)); + AppLog.d(T.MAIN, "GOOGLE LOGIN: onSocialChanged - unknown error"); + showError(getString(R.string.login_error_generic)); break; } // Response does not return error when two-factor authentication is required. - } else if (event.requiresTwoStepAuth) { + } else if (event.requiresTwoStepAuth || Login2FaFragment.TWO_FACTOR_TYPE_SMS.equals(event.notificationSent)) { + AppLog.d(T.MAIN, "GOOGLE LOGIN: onSocialChanged - needs 2fa"); mLoginListener.needs2faSocial(mGoogleEmail, event.userId, event.nonceAuthenticator, event.nonceBackup, event.nonceSms); } else { + AppLog.d(T.MAIN, "GOOGLE LOGIN: onSocialChanged - success"); mGoogleListener.onGoogleLoginFinished(); } + finishFlow(); } } diff --git a/libs/login/WordPressLoginFlow/src/main/java/org/wordpress/android/login/LoginListener.java b/libs/login/WordPressLoginFlow/src/main/java/org/wordpress/android/login/LoginListener.java index 17bcc96b03cd..e17875f309df 100644 --- a/libs/login/WordPressLoginFlow/src/main/java/org/wordpress/android/login/LoginListener.java +++ b/libs/login/WordPressLoginFlow/src/main/java/org/wordpress/android/login/LoginListener.java @@ -3,7 +3,6 @@ import android.net.Uri; import android.support.annotation.NonNull; import android.support.annotation.Nullable; -import android.support.v4.app.Fragment; import org.wordpress.android.fluxc.network.MemorizingTrustManager; import org.wordpress.android.fluxc.store.SiteStore; @@ -25,7 +24,7 @@ interface SelfSignedSSLCallback { void loginViaWpcomUsernameInstead(); void helpEmailScreen(String email); void helpSocialEmailScreen(String email); - void addGoogleLoginFragment(@NonNull Fragment fragment); + void addGoogleLoginFragment(); // Login Request Magic Link callbacks void showMagicLinkSentScreen(String email); diff --git a/libs/login/WordPressLoginFlow/src/main/java/org/wordpress/android/login/SignupGoogleFragment.java b/libs/login/WordPressLoginFlow/src/main/java/org/wordpress/android/login/SignupGoogleFragment.java index e0173acf0be6..91ffe71d8094 100644 --- a/libs/login/WordPressLoginFlow/src/main/java/org/wordpress/android/login/SignupGoogleFragment.java +++ b/libs/login/WordPressLoginFlow/src/main/java/org/wordpress/android/login/SignupGoogleFragment.java @@ -3,6 +3,8 @@ import android.app.ProgressDialog; import android.content.Context; import android.content.Intent; +import android.os.Bundle; +import android.support.annotation.NonNull; import com.google.android.gms.auth.api.Auth; import com.google.android.gms.auth.api.signin.GoogleSignInAccount; @@ -29,8 +31,11 @@ import static android.app.Activity.RESULT_OK; public class SignupGoogleFragment extends GoogleFragment { + private static final String OLD_SITES_IDS = "old_sites_ids"; + private static final String SIGN_UP_REQUESTED = "sign_up_requested"; private ArrayList mOldSitesIds; private ProgressDialog mProgressDialog; + private boolean mSignupRequested; private static final int REQUEST_SIGNUP = 1002; @@ -44,22 +49,51 @@ public void onAttach(Context context) { super.onAttach(context); } + @Override public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + if (savedInstanceState != null) { + mOldSitesIds = savedInstanceState.getIntegerArrayList(OLD_SITES_IDS); + mSignupRequested = savedInstanceState.getBoolean(SIGN_UP_REQUESTED); + } + } + + @Override public void onSaveInstanceState(@NonNull Bundle outState) { + super.onSaveInstanceState(outState); + outState.putIntegerArrayList(OLD_SITES_IDS, mOldSitesIds); + outState.putBoolean(SIGN_UP_REQUESTED, mSignupRequested); + } + @Override public void onDetach() { dismissProgressDialog(); super.onDetach(); } + @Override + protected void startFlow() { + if (!mSignupRequested) { + AppLog.d(T.MAIN, "GOOGLE SIGNUP: startFlow"); + mSignupRequested = true; + Intent signInIntent = Auth.GoogleSignInApi.getSignInIntent(mGoogleApiClient); + startActivityForResult(signInIntent, REQUEST_SIGNUP); + } else { + AppLog.d(T.MAIN, "GOOGLE SIGNUP: startFlow called, but is already in progress"); + } + } + @Override public void onActivityResult(int request, int result, Intent data) { super.onActivityResult(request, result, data); switch (request) { case REQUEST_SIGNUP: + disconnectGoogleClient(); + mSignupRequested = false; if (result == RESULT_OK) { GoogleSignInResult signInResult = Auth.GoogleSignInApi.getSignInResultFromIntent(data); if (signInResult.isSuccess()) { + AppLog.d(T.MAIN, "GOOGLE SIGNUP: sign up result returned - succcess"); try { GoogleSignInAccount account = signInResult.getSignInAccount(); @@ -72,31 +106,33 @@ public void onActivityResult(int request, int result, Intent data) { } PushSocialPayload payload = new PushSocialPayload(mIdToken, SERVICE_TYPE_GOOGLE); + AppLog.d(T.MAIN, "GOOGLE SIGNUP: sign up result returned - dispatching SocialSignupAction"); mDispatcher.dispatch(AccountActionBuilder.newPushSocialSignupAction(payload)); mOldSitesIds = SiteUtils.getCurrentSiteIds(mSiteStore, false); } catch (NullPointerException exception) { - disconnectGoogleClient(); + AppLog.d(T.MAIN, "GOOGLE SIGNUP: sign up result returned - NPE"); AppLog.e(T.NUX, "Cannot get ID token from Google signup account.", exception); - showErrorDialog(getString(R.string.login_error_generic)); + showError(getString(R.string.login_error_generic)); } } else { + AppLog.d(T.MAIN, "GOOGLE SIGNUP: sign up result returned - error"); mAnalyticsListener.trackSignupSocialButtonFailure(); switch (signInResult.getStatus().getStatusCode()) { // Internal error. case GoogleSignInStatusCodes.INTERNAL_ERROR: AppLog.e(T.NUX, "Google Signup Failed: internal error."); - showErrorDialog(getString(R.string.login_error_generic)); + showError(getString(R.string.login_error_generic)); break; // Attempted to connect with an invalid account name specified. case GoogleSignInStatusCodes.INVALID_ACCOUNT: AppLog.e(T.NUX, "Google Signup Failed: invalid account name."); - showErrorDialog(getString(R.string.login_error_generic) - + getString(R.string.login_error_suffix)); + showError(getString(R.string.login_error_generic) + + getString(R.string.login_error_suffix)); break; // Network error. case GoogleSignInStatusCodes.NETWORK_ERROR: AppLog.e(T.NUX, "Google Signup Failed: network error."); - showErrorDialog(getString(R.string.error_generic_network)); + showError(getString(R.string.error_generic_network)); break; // Cancelled by the user. case GoogleSignInStatusCodes.SIGN_IN_CANCELLED: @@ -105,34 +141,35 @@ public void onActivityResult(int request, int result, Intent data) { // Attempt didn't succeed with the current account. case GoogleSignInStatusCodes.SIGN_IN_FAILED: AppLog.e(T.NUX, "Google Signup Failed: current account failed."); - showErrorDialog(getString(R.string.login_error_generic)); + showError(getString(R.string.login_error_generic)); break; // Attempted to connect, but the user is not signed in. case GoogleSignInStatusCodes.SIGN_IN_REQUIRED: AppLog.e(T.NUX, "Google Signup Failed: user is not signed in."); - showErrorDialog(getString(R.string.login_error_generic)); + showError(getString(R.string.login_error_generic)); break; // Timeout error. case GoogleSignInStatusCodes.TIMEOUT: AppLog.e(T.NUX, "Google Signup Failed: timeout error."); - showErrorDialog(getString(R.string.google_error_timeout)); + showError(getString(R.string.google_error_timeout)); break; // Unknown error. default: AppLog.e(T.NUX, "Google Signup Failed: unknown error."); - showErrorDialog(getString(R.string.login_error_generic)); + showError(getString(R.string.login_error_generic)); break; } } } else if (result == RESULT_CANCELED) { + AppLog.d(T.MAIN, "GOOGLE SIGNUP: sign up result returned - canceled"); mAnalyticsListener.trackSignupSocialButtonFailure(); AppLog.e(T.NUX, "Google Signup Failed: result was CANCELED."); - dismissProgressDialog(); + finishFlow(); } else { + AppLog.d(T.MAIN, "GOOGLE SIGNUP: sign up result returned - unknown"); mAnalyticsListener.trackSignupSocialButtonFailure(); AppLog.e(T.NUX, "Google Signup Failed: result was not OK or CANCELED."); - showErrorDialog(getString(R.string.login_error_generic)); - dismissProgressDialog(); + showError(getString(R.string.login_error_generic)); } break; @@ -145,12 +182,6 @@ private void dismissProgressDialog() { } } - @Override - protected void showAccountDialog() { - Intent signInIntent = Auth.GoogleSignInApi.getSignInIntent(mGoogleApiClient); - startActivityForResult(signInIntent, REQUEST_SIGNUP); - } - // Remove scale from photo URL path string. Current URL matches /s96-c, which returns a 96 x 96 // pixel image. Removing /s96-c from the string returns a 512 x 512 pixel image. Using regular // expressions may help if the photo URL scale value in the returned path changes. @@ -164,17 +195,21 @@ private String removeScaleFromGooglePhotoUrl(String photoUrl) { @Subscribe(threadMode = ThreadMode.MAIN) public void onAuthenticationChanged(OnAuthenticationChanged event) { if (event.isError()) { + AppLog.d(T.MAIN, "GOOGLE SIGNUP: onAuthenticationChanged - error"); AppLog.e(T.API, "SignupGoogleFragment.onAuthenticationChanged: " + event.error.type + " - " + event.error.message); - // Continue with signup since account was created. } else if (event.createdAccount) { + AppLog.d(T.MAIN, + "GOOGLE SIGNUP: onAuthenticationChanged - new wordpress account created"); mAnalyticsListener.trackCreatedAccount(event.userName, mGoogleEmail); mGoogleListener.onGoogleSignupFinished(mDisplayName, mGoogleEmail, mPhotoUrl, event.userName); // Continue with login since existing account was selected. } else { + AppLog.d(T.MAIN, "GOOGLE SIGNUP: onAuthenticationChanged - the email is already attached to an account"); mAnalyticsListener.trackSignupSocialToLogin(); mLoginListener.loggedInViaSocialAccount(mOldSitesIds, true); } + finishFlow(); } @SuppressWarnings("unused") @@ -186,34 +221,49 @@ public void onSocialChanged(OnSocialChanged event) { switch (event.error.type) { // WordPress account exists with input email address, and two-factor authentication is required. case TWO_STEP_ENABLED: + AppLog.d(T.MAIN, "GOOGLE SIGNUP: onSocialChanged - error - two step authentication"); mAnalyticsListener.trackSignupSocialToLogin(); mLoginListener.showSignupToLoginMessage(); // Dispatch social login action to retrieve data required for two-factor authentication. PushSocialPayload payload = new PushSocialPayload(mIdToken, SERVICE_TYPE_GOOGLE); + AppLog.d(T.MAIN, + "GOOGLE SIGNUP: onSocialChanged error - two step authentication - dispatching " + + "pushSocialLoginAction"); mDispatcher.dispatch(AccountActionBuilder.newPushSocialLoginAction(payload)); break; // WordPress account exists with input email address, but not connected. case USER_EXISTS: - mAnalyticsListener.trackSignupSocialAccountsNeedConnecting(); - mAnalyticsListener.trackSignupSocialToLogin(); - mLoginListener.showSignupToLoginMessage(); - mLoginListener.loginViaSocialAccount(mGoogleEmail, mIdToken, SERVICE_TYPE_GOOGLE, true); - // Kill connections with FluxC and this fragment since the flow is changing to login. - mDispatcher.unregister(this); - getActivity().getSupportFragmentManager().beginTransaction().remove(this).commit(); + AppLog.d(T.MAIN, "GOOGLE SIGNUP: onSocialChanged - error - user already exists"); + loginViaSocialAccount(); + break; + // Too many attempts on sending SMS verification code. The user has to wait before they try again + case SMS_CODE_THROTTLED: + AppLog.d(T.MAIN, "GOOGLE SIGNUP: onSocialChanged - error - sms code throttled"); + showError(getString(R.string.login_error_sms_throttled)); break; default: - showErrorDialog(getString(R.string.login_error_generic)); + AppLog.d(T.MAIN, "GOOGLE SIGNUP: onSocialChanged - error - unknown"); + showError(getString(R.string.login_error_generic)); break; } - // Response does not return error when two-factor authentication is required. - } else if (event.requiresTwoStepAuth) { + // Response does not return error when two-factor authentication is required. + } else if (event.requiresTwoStepAuth || Login2FaFragment.TWO_FACTOR_TYPE_SMS.equals(event.notificationSent)) { + AppLog.d(T.MAIN, "GOOGLE SIGNUP: onSocialChanged - 2fa required"); mAnalyticsListener.trackSignupSocialToLogin(); mLoginListener.needs2faSocial(mGoogleEmail, event.userId, event.nonceAuthenticator, event.nonceBackup, event.nonceSms); - // Kill connections with FluxC and this fragment since the flow is changing to login. - mDispatcher.unregister(this); - getActivity().getSupportFragmentManager().beginTransaction().remove(this).commit(); + finishFlow(); + } else { + AppLog.d(T.MAIN, "GOOGLE SIGNUP: onSocialChanged - google login success"); + loginViaSocialAccount(); } } + + private void loginViaSocialAccount() { + mAnalyticsListener.trackSignupSocialAccountsNeedConnecting(); + mAnalyticsListener.trackSignupSocialToLogin(); + mLoginListener.showSignupToLoginMessage(); + mLoginListener.loginViaSocialAccount(mGoogleEmail, mIdToken, SERVICE_TYPE_GOOGLE, true); + finishFlow(); + } } diff --git a/libs/login/WordPressLoginFlow/src/main/res/values/strings.xml b/libs/login/WordPressLoginFlow/src/main/res/values/strings.xml index e3f673efb6a4..8a6fdfa79215 100644 --- a/libs/login/WordPressLoginFlow/src/main/res/values/strings.xml +++ b/libs/login/WordPressLoginFlow/src/main/res/values/strings.xml @@ -58,8 +58,9 @@ Logged in as Log in with Google. Close - The Google account \'%s\' doesn\'t match any existing account on WordPress.com. + There\'s no WordPress.com account matching this Google account. There was some trouble connecting with the Google account. + We\'ve made too many attempts to send an SMS verification code — take a break, and request a new one in a minute. Google login could not be started. \nMaybe try a different account? There was some trouble checking the email address.