Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
06a36b4
First stab at a login AutoForeground Service
hypest Oct 24, 2017
390ea2d
Introduce a Service state for social login
hypest Nov 15, 2017
d434308
Notify and resume for 2FA
hypest Nov 17, 2017
d0e8a35
Small code optimization
hypest Nov 17, 2017
0d78db4
Non cancellable email+password login
hypest Nov 16, 2017
458bce7
Separate class for the login notification builder
hypest Nov 17, 2017
7d1e7fa
Simplify the login progress notification message
hypest Nov 16, 2017
cccace6
Sticky EventBus message to hold login Service state
hypest Nov 17, 2017
05cdbcb
Clear sticky login state when leaving email/pass screen
hypest Nov 16, 2017
50113dc
Remove notifications when client gets bound
hypest Nov 16, 2017
4583596
Use the provided onLoginFinished(boolean)
hypest Nov 16, 2017
d375d72
Handle some more failure states in the UI
hypest Nov 17, 2017
8b39c8b
Minor cleanup
hypest Nov 17, 2017
b228183
Code cleanup to remove AS warnings
hypest Nov 17, 2017
9386143
Initialise the login state sticky event
hypest Nov 17, 2017
8ed3321
SUCCESS doesn't need a progress percentage
hypest Nov 17, 2017
fe8369b
Missing cases from isError()
hypest Nov 17, 2017
f386f95
String resources, nicer login notifications
hypest Nov 17, 2017
a7c3773
LoginWpcomService is not involved in this flow
hypest Nov 17, 2017
0efd828
clearLoginServiceState belongs in LoginWpcomService
hypest Nov 17, 2017
8db8efd
More specific name for the method
hypest Nov 17, 2017
bc0c976
No need to start the reader/notif services yet
hypest Nov 17, 2017
e102757
Contain clearLoginServiceState's usage
hypest Nov 17, 2017
ec41e9e
Big title and big content for all login notifications
hypest Nov 17, 2017
622f480
Merge branch 'develop' into issue/6700-login-progress-dialog-forever
hypest Nov 17, 2017
c8287d3
Finish the sentence
hypest Nov 17, 2017
3277502
Just clear the sticky event. Leftovers from bad merge.
hypest Nov 17, 2017
d4f65a6
Minor code optimization
hypest Nov 17, 2017
e4512f8
Don't set Large icon in login notifications
hypest Nov 18, 2017
01b1449
Set title for standard notification style
hypest Nov 18, 2017
b8243bc
Rename, better comments for getResumeIntent
hypest Nov 20, 2017
ac72de2
Rename the method for better description
hypest Nov 20, 2017
398faac
Set the standard content text along with the title
hypest Nov 20, 2017
d3124f8
Add Tracks of the service phase updates
hypest Nov 27, 2017
4e6526c
Track early, before foregroundness changes
hypest Nov 27, 2017
7959b98
Merge branch 'develop' into issue/6700-login-progress-dialog-forever
hypest Nov 27, 2017
f83dd70
Null check for mLoginPhase
hypest Nov 27, 2017
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion WordPress/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@
<activity
android:name=".ui.accounts.NewBlogActivity"
android:configChanges="orientation|keyboardHidden|screenSize"
android:theme="@style/SignInTheme"
android:theme="@style/LoginTheme"
android:windowSoftInputMode="adjustResize" />
<activity
android:name=".ui.accounts.HelpActivity"
Expand Down Expand Up @@ -535,6 +535,10 @@
android:name=".ui.notifications.services.NotificationsUpdateService"
android:exported="false"
android:label="Notifications Update Service" />
<service
android:name=".ui.accounts.login.LoginWpcomService"
android:exported="false"
android:label="Login to WPCOM Service" />

<!-- Samsung multiwindow support -->
<uses-library
Expand Down
20 changes: 20 additions & 0 deletions WordPress/src/main/java/org/wordpress/android/WordPress.java
Original file line number Diff line number Diff line change
Expand Up @@ -610,6 +610,26 @@ public static Object getBuildConfigValue(Application application, String fieldNa
}
}

/**
* Gets a field from the project's BuildConfig using reflection. This is useful when flavors
* are used at the project level to set custom fields.
* based on: https://code.google.com/p/android/issues/detail?id=52962#c38
* @param activity Used to get the Application instance
* @param configValueName The name of the field-to-access
* @return The string value of the field, or empty string if the field is not found.
*/
public static String getBuildConfigString(Activity activity, String configValueName) {
if (!BuildConfig.DEBUG) return "";

String value = (String) WordPress.getBuildConfigValue(activity.getApplication(), configValueName);
if (!TextUtils.isEmpty(value)) {
AppLog.d(AppLog.T.NUX, "Auto-filled from build config: " + configValueName);
return value;
}

return "";
}

/**
* Detect when the app goes to the background and come back to the foreground.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import org.wordpress.android.ui.accounts.login.LoginSiteAddressFragment;
import org.wordpress.android.ui.accounts.login.LoginSiteAddressHelpDialogFragment;
import org.wordpress.android.ui.accounts.login.LoginUsernamePasswordFragment;
import org.wordpress.android.ui.accounts.login.LoginWpcomService;
import org.wordpress.android.ui.comments.CommentAdapter;
import org.wordpress.android.ui.comments.CommentDetailFragment;
import org.wordpress.android.ui.comments.CommentsActivity;
Expand Down Expand Up @@ -136,6 +137,7 @@ public interface AppComponent {
void inject(PostUploadHandler object);

void inject(LoginActivity object);
void inject(LoginWpcomService object);
void inject(LoginMagicLinkRequestFragment object);
void inject(LoginEmailFragment object);
void inject(LoginGoogleFragment object);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,6 @@ public static void viewSSLCerts(Context context, String certificateString) {

public static void newBlogForResult(Activity activity) {
Intent intent = new Intent(activity, NewBlogActivity.class);
intent.putExtra(NewBlogActivity.KEY_START_MODE, NewBlogActivity.CREATE_BLOG);
activity.startActivityForResult(intent, RequestCodes.CREATE_SITE);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -210,11 +210,15 @@ public boolean onOptionsItemSelected(MenuItem item) {
}

protected void startProgress() {
startProgress(true);
}

protected void startProgress(boolean cancellable) {
mPrimaryButton.setEnabled(false);
mSecondaryButton.setEnabled(false);

mProgressDialog =
ProgressDialog.show(getActivity(), "", getActivity().getString(getProgressBarText()), true, true,
ProgressDialog.show(getActivity(), "", getActivity().getString(getProgressBarText()), true, cancellable,
new DialogInterface.OnCancelListener() {
@Override
public void onCancel(DialogInterface dialogInterface) {
Expand Down Expand Up @@ -256,7 +260,7 @@ protected void doFinishLogin() {
protected void onLoginFinished() {
}

private void onLoginFinished(boolean success) {
protected void onLoginFinished(boolean success) {
mLoginFinished = true;

if (!success) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,13 @@
import org.wordpress.android.R;
import org.wordpress.android.WordPress;
import org.wordpress.android.analytics.AnalyticsTracker;
import org.wordpress.android.fluxc.generated.AccountActionBuilder;
import org.wordpress.android.fluxc.generated.AuthenticationActionBuilder;
import org.wordpress.android.fluxc.store.AccountStore;
import org.wordpress.android.fluxc.store.AccountStore.AuthenticatePayload;
import org.wordpress.android.fluxc.store.AccountStore.OnAuthenticationChanged;
import org.wordpress.android.fluxc.store.AccountStore.OnSocialChanged;
import org.wordpress.android.util.AnalyticsUtils;
import org.wordpress.android.ui.accounts.login.LoginWpcomService.OnCredentialsOK;
import org.wordpress.android.ui.accounts.login.LoginWpcomService.OnLoginStateUpdated;
import org.wordpress.android.util.AppLog;
import org.wordpress.android.util.AppLog.T;
import org.wordpress.android.util.AutoForeground;
import org.wordpress.android.util.NetworkUtils;
import org.wordpress.android.util.SiteUtils;
import org.wordpress.android.util.ToastUtils;
import org.wordpress.android.widgets.WPLoginInputRow;
import org.wordpress.android.widgets.WPLoginInputRow.OnEditorCommitListener;

Expand Down Expand Up @@ -62,6 +57,8 @@ public class LoginEmailPasswordFragment extends LoginBaseFormFragment<LoginListe
private String mService;
private boolean isSocialLogin;

private AutoForeground.ServiceEventConnection mServiceEventConnection;

public static LoginEmailPasswordFragment newInstance(String emailAddress, String password,
String idToken, String service,
boolean isSocialLogin) {
Expand All @@ -87,11 +84,39 @@ public void onCreate(Bundle savedInstanceState) {
mService = getArguments().getString(ARG_SOCIAL_SERVICE);
isSocialLogin = getArguments().getBoolean(ARG_SOCIAL_LOGIN);

if (savedInstanceState != null) {
if (savedInstanceState == null) {
// cleanup the service state on first appearance
LoginWpcomService.clearLoginServiceState();
} else {
mRequestedPassword = savedInstanceState.getString(KEY_REQUESTED_PASSWORD);
}
}

@Override
public void onResume() {
super.onResume();

// connect to the Service. We'll receive updates via EventBus.
mServiceEventConnection = new AutoForeground.ServiceEventConnection(getContext(), LoginWpcomService.class, this);

// install the change listener as late as possible so the UI can be setup (updated from the Service state)
// before triggering the state cleanup happening in the change listener.
mPasswordInput.addTextChangedListener(this);
}

@Override
public void onPause() {
super.onPause();

// disconnect from the Service
mServiceEventConnection.disconnect(getContext(), this);
}

@Override
protected boolean listenForLogin() {
return false;
}

@Override
protected @LayoutRes
int getContentLayout() {
Expand All @@ -113,7 +138,6 @@ protected void setupContent(ViewGroup rootView) {
((TextView) rootView.findViewById(R.id.login_email)).setText(mEmailAddress);

mPasswordInput = (WPLoginInputRow) rootView.findViewById(R.id.login_password_row);
mPasswordInput.addTextChangedListener(this);
mPasswordInput.setOnEditorCommitListener(this);
}

Expand Down Expand Up @@ -177,14 +201,13 @@ protected void next() {
return;
}

startProgress();
startProgress(false);

mRequestedPassword = mPasswordInput.getEditText().getText().toString();

LoginWpcomService.loginWithEmailAndPassword(getContext(), mEmailAddress, mRequestedPassword, mIdToken, mService,
isSocialLogin);
mOldSitesIDs = SiteUtils.getCurrentSiteIds(mSiteStore, false);

AuthenticatePayload payload = new AuthenticatePayload(mEmailAddress, mRequestedPassword);
mDispatcher.dispatch(AuthenticationActionBuilder.newAuthenticateAction(payload));
}

@Override
Expand All @@ -204,107 +227,84 @@ public void beforeTextChanged(CharSequence s, int start, int count, int after) {
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
mPasswordInput.setError(null);

LoginWpcomService.clearLoginServiceState();
}

private void showPasswordError() {
mPasswordInput.setError(getString(R.string.password_incorrect));
}

private void handleAuthError(AccountStore.AuthenticationErrorType error, String errorMessage) {

if (error != AccountStore.AuthenticationErrorType.NEEDS_2FA) {
AnalyticsTracker.track(AnalyticsTracker.Stat.LOGIN_FAILED, error.getClass().getSimpleName(), error.toString(), errorMessage);

if (isSocialLogin) {
AnalyticsTracker.track(AnalyticsTracker.Stat.LOGIN_SOCIAL_FAILURE, error.getClass().getSimpleName(), error.toString(), errorMessage);
}
}

switch (error) {
case INCORRECT_USERNAME_OR_PASSWORD:
case NOT_AUTHENTICATED: // NOT_AUTHENTICATED is the generic error from XMLRPC response on first call.
showPasswordError();
break;
case NEEDS_2FA:
// login credentials were correct anyway so, offer to save to SmartLock
saveCredentialsInSmartLock(mLoginListener.getSmartLockHelper(), mEmailAddress, mPassword);

if (isSocialLogin) {
mLoginListener.needs2faSocialConnect(mEmailAddress, mRequestedPassword, mIdToken, mService);
} else {
mLoginListener.needs2fa(mEmailAddress, mRequestedPassword);
}

break;
case INVALID_REQUEST:
// TODO: FluxC: could be specific?
default:
AppLog.e(T.NUX, "Server response: " + errorMessage);

ToastUtils.showToast(getActivity(),
errorMessage == null ? getString(R.string.error_generic) : errorMessage);
break;
}
private void showError(String error) {
mPasswordInput.setError(error);
}

// OnChanged events

@SuppressWarnings("unused")
@Subscribe(threadMode = ThreadMode.MAIN)
public void onAuthenticationChanged(OnAuthenticationChanged event) {
if (event.isError()) {
endProgress();

AppLog.e(T.API, "onAuthenticationChanged has error: " + event.error.type + " - " + event.error.message);

if (isAdded()) {
handleAuthError(event.error.type, event.error.message);
}

return;
}

AppLog.i(T.NUX, "onAuthenticationChanged: " + event.toString());

@Override
protected void onLoginFinished() {
if (isSocialLogin) {
AccountStore.PushSocialLoginPayload payload = new AccountStore.PushSocialLoginPayload(mIdToken, mService);
mDispatcher.dispatch(AccountActionBuilder.newPushSocialConnectAction(payload));
mLoginListener.loggedInViaSocialAccount(mOldSitesIDs);
} else {
saveCredentialsInSmartLock(mLoginListener.getSmartLockHelper(), mEmailAddress, mRequestedPassword);
doFinishLogin();
mLoginListener.loggedInViaPassword(mOldSitesIDs);
}
}

@SuppressWarnings("unused")
@Subscribe(threadMode = ThreadMode.MAIN)
public void onSocialChanged(OnSocialChanged event) {
if (event.isError()) {
AnalyticsTracker.track(AnalyticsTracker.Stat.LOGIN_SOCIAL_CONNECT_FAILURE);
switch (event.error.type) {
case UNABLE_CONNECT:
AppLog.e(T.API, "Unable to connect WordPress.com account to social account.");
break;
case USER_ALREADY_ASSOCIATED:
AppLog.e(T.API, "This social account is already associated with a WordPress.com account.");
break;
// Ignore other error cases. The above are the only two we have chosen to log.
}

doFinishLogin();
} else if (!event.requiresTwoStepAuth) {
AnalyticsTracker.track(AnalyticsTracker.Stat.LOGIN_SOCIAL_CONNECT_SUCCESS);
doFinishLogin();
}
public void onCredentialsOK(OnCredentialsOK event) {
saveCredentialsInSmartLock(mLoginListener.getSmartLockHelper(), mEmailAddress, mRequestedPassword);
}

@Override
protected void onLoginFinished() {
AnalyticsUtils.trackAnalyticsSignIn(mAccountStore, mSiteStore, true);
@SuppressWarnings("unused")
@Subscribe(threadMode = ThreadMode.MAIN, sticky = true)
public void onLoginStateUpdated(OnLoginStateUpdated event) {
AppLog.i(T.NUX, "Received state: " + event.state.name());

if (isSocialLogin) {
mLoginListener.loggedInViaSocialAccount(mOldSitesIDs);
} else {
mLoginListener.loggedInViaPassword(mOldSitesIDs);
switch (event.state) {
case IDLE:
// nothing special to do, we'll start the service on next()
break;
case AUTHENTICATING:
case SOCIAL_LOGIN:
case FETCHING_ACCOUNT:
case FETCHING_SETTINGS:
case FETCHING_SITES:
if (!isInProgress()) {
startProgress();
}
break;
case FAILURE_EMAIL_WRONG_PASSWORD:
onLoginFinished(false);
showPasswordError();
break;
case FAILURE_2FA:
onLoginFinished(false);
mLoginListener.needs2fa(mEmailAddress, mRequestedPassword);

// consume the state so we don't relauch the 2FA dialog if user backs up
LoginWpcomService.clearLoginServiceState();
break;
case FAILURE_SOCIAL_2FA:
onLoginFinished(false);
mLoginListener.needs2faSocialConnect(mEmailAddress, mRequestedPassword, mIdToken, mService);

// consume the state so we don't relauch the 2FA dialog if user backs up
LoginWpcomService.clearLoginServiceState();
break;
case FAILURE_FETCHING_ACCOUNT:
onLoginFinished(false);
showError(getString(R.string.error_fetch_my_profile));
break;
case FAILURE_CANNOT_ADD_DUPLICATE_SITE:
onLoginFinished(false);
showError(getString(R.string.cannot_add_duplicate_site));
break;
case FAILURE:
onLoginFinished(false);
showError(getString(R.string.error_generic));
break;
case SUCCESS:
onLoginFinished(true);
break;
}
}
}
Loading