Perform the WPCOM email+password login in the background - #6878
Conversation
The fragment attaches, detaches and spawns the email+password login Service, updating its UI to reflect the process's stage.
| // 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 before triggering the |
There was a problem hiding this comment.
the sentence in the comment is ended abruptly "before triggering the".
There was a problem hiding this comment.
🤦♂️ what a cliffhanger!
Fixed with c8287d3.
| public static void clearLoginServiceState() { | ||
| OnLoginStateUpdated onLoginStateUpdated = EventBus.getDefault().removeStickyEvent(OnLoginStateUpdated.class); | ||
| if (onLoginStateUpdated != null && onLoginStateUpdated.state.isTerminal()) { | ||
| EventBus.getDefault().removeStickyEvent(OnLoginStateUpdated.class); |
There was a problem hiding this comment.
I'm not sure why removeStickyEvent is called twice on the OnLoginStateUpdated.class here - I believe in some other part of the code there's the assumption that 2 sticky events are posted when the last sticky one is isTerminal()? but I'm only guessing here. If we need to call removeStickyEvent twice let's add a comment to clarify what's happening here (i.e. what the first removal does, and what the second one does, and when that's needed)
There was a problem hiding this comment.
Good catch! This was actually a bad merge situation where extra code was left in. Sorry for the confusion! Sticky event just needs to be removed once. Fixed with 3277502.
| mDispatcher.dispatch(AccountActionBuilder.newFetchAccountAction()); | ||
| } | ||
|
|
||
| private EventBus getEventBus() { |
There was a problem hiding this comment.
this method looks unnecessary - we can just call EventBus.getDefault()
There was a problem hiding this comment.
Right! I had grand plans for the getEventBus() method but when the plans changed I forgot to remove it. Fixed with d4f65a6.
| private static Intent getPendingIntent(Context context) { | ||
| Intent resumeIntent = context.getPackageManager().getLaunchIntentForPackage(context.getPackageName()); | ||
|
|
||
| // apparently, we need to null out the package name to make the intent resume the app |
There was a problem hiding this comment.
wow didn't know this. Interesting! Do we have a reference or explanation as to why this is like that? Something we can put in here to explain the code a bit more
There was a problem hiding this comment.
That's something I noticed as the difference between getLaunchIntentForPackage() and the simple Intent I manually constructed (like this). The Intent returned from getLaunchIntentForPackage() fails to just resume the app with the current Task in it. I don't have a reference to this behaviour.
There was a problem hiding this comment.
Can we add a comment explaining that what this getPendingIntent actually tries to do is to recover the same intent that was launched in the first place, and uses it for making the Notification tap action "resume" the original intent?
There was a problem hiding this comment.
👍 I've been considering this for a while to check if there's an alternative, "cleaner" way of resuming the app (and also possibly have better code instead of a comment clarifying why we are practically defeating the purpose of calling getLaunchIntent**ForPackage** and then nullifying the intent's pacakage immediately after). I would have liked to use explicit intents but that also means we should be able to tell which Activity the user was on, thus making the whole process more convoluted. Just wanted to clarify why I thought a longer comment was good to have here.
There was a problem hiding this comment.
Totally, I'm there with you. I think that getLaunchIntentForPackage() had just one job to do but yeah, falls short apparently.
|
|
||
| private static NotificationCompat.Builder getNotificationBuilder(Context context, @StringRes int title, | ||
| @StringRes int content) { | ||
| NotificationCompat.BigTextStyle bigTextStyle = new NotificationCompat.BigTextStyle(); |
There was a problem hiding this comment.
I think we need to set the normal notification text as well here, otherwise when the notification is collapsed a blank notification is shown:
Once you drag the bottom edge down to expand it, the bigstyle text appears:
So, we'd need to add
notificationBuilder.setContentTitle(title);
notificationBuilder.setContentText(content);
to the NotificationCompat.Builder(context) concatenation right below
There was a problem hiding this comment.
Good call. I opted (with 01b1449) to setting the standard title only so the collapsed notification can look, well, collapsed :). Let me know if this is OK or you'd prefer the standard contentText to be set as well. Thanks!
In the meantime, I removed the big icon anyway to match other notification styles in the app and this has the added benefit that the collapsed notification looks less empty.
There was a problem hiding this comment.
I think I would rather have both the title and the text on the notification so the user can know the reasons why "Login stopped" and which actions are required right at a glance. Otherwise they'd only see the title i..e "Login stopped" with no further reasons or actions to take (the "Please provide an authentication code to continue" is fully informative in this regard, you know what's about to happen after tapping, as opposed to not knowing what to expect with just the "login stopped" title).
There was a problem hiding this comment.
Hmm, I see your point @mzorz . I think that the standard content text doesn't allow for multiple lines though and the informative texts you mention actually get clipped. It depends on the screen size too of course but, that's basically the reason I employed the BigStyle notification as well.
I made the change with 398faac.
There was a problem hiding this comment.
I know, it's tricky to have all possibilities in mind. From my experience, best way to keep it with high chances of showing for everybody / giving people chance to just expand without opening to try and see what happened, is to set title and content on both normal and BigStyle, as it is now 👍
|
Nice @hypest ! This one looks good for setting the base for next steps where we have other Dialogs like this one as explained in #6700. I made a comment about the notification text that should be addressed, and then it's ok to go for me. Let's also wait for @theck13 's input and we'll be ready to roll! 👍 |
That better matches other notifications in the app.
This way, a collapsed notification can still display useful information.
|
Thanks for the the review @mzorz ! I've addressed all your feedback and this is ready for another pass.
Yes, there are dialogs like this one in several screens in the new Login and those screens too will need to be gradually ported to using the Service. This PR was limited to just one screen to keep it small and lay the groundwork. |
theck13
left a comment
There was a problem hiding this comment.
I tested the social login scenarios and they pass. It looks good to me.
|
@mzorz I've pushed a couple of commits to add some Tracks of the LoginWpcomService state to help us gain insight of the cases where the app does get backgrounded while on the email/password login screen. I've also updated the PR with latest |
|
|
||
| private void track() { | ||
| Map<String, Object> props = new HashMap<>(); | ||
| props.put("login_phase", mLoginPhase.name()); |
There was a problem hiding this comment.
can we just make sure mLoginPhase is not null here?
There was a problem hiding this comment.
Makes sense. Added a null check with f83dd70.
|
Just commented a minor nit and we'll be ready to go @hypest 👍 |
|
Addressed the feedback @mzorz , ready for another pass 👍 |
| private void track() { | ||
| Map<String, Object> props = new HashMap<>(); | ||
| props.put("login_phase", mLoginPhase.name()); | ||
| props.put("login_phase", mLoginPhase == null ? "null" : mLoginPhase.name()); |
|
LGTM |


Fixes #6700
This PR introduces a Service to handle logging in to WPCOM when the email+password flow is used. That's the flow mentioned in the ticket and this PR is on-purpose limited to that flow so the changeset is smaller and more manageable.
There is some code duplication with
LoginBaseFormFragment(mainly theonAccountChangedandonSiteChangedlisteners) which should be lifted when all login flows switch to using a Service.This PR builds on top of the Service implementation introduced with #6825. See the FAQ section below for more information.
F.A.Q
Why use a service?
A Service is used so the login procedure can continue even if the app is backgrounded. The FluxC messages that are now missed by the screen Fragments are properly received and handled in the Service without interruption.
AutoForeground class. What black magic is that??
This class monitors whether the Service has bound "clients" or not. If bound clients exist, Service's state updates are delivered via an EventBus message. If no clients are bound, the Service gets promoted to foreground and its updates are forwarded to a status bar notification. As clients get bound or unbound, this class automatically toggles the Service foreground-ness and hides or shows the notifications.
Why is the Service promoted to a foreground one?
The Service is promoted to a foreground one to make sure the system doesn't kill it, even on Android 8.0.
Why is a client (LoginEmailPasswordFragment) attaching and detaching in
onResumeandonPause?The client (the app UI essentially) attaches or detaches to denote that it wants to start or stop listening to the Service updates. That's similar to how other Fragments register/de-register from EventBus. The added functionality here is that when detached, updates are forwarded to the status bar notification.
Sticky EventBus messages? What's the deal there?
The Service is designed to start and then stop itself on success or failure. The app may be in the background when the Service stops and the sticky message makes sure the latest state of the Service is available to pick when the app resumes. The sticky message also plays the role of state retention and the UI doesn't need to save it in
onSaveInstanceStateor similar.Any downsides to using a sticky message?
Yes, someone has to cleanup or initialise the state. See usages of the
clearLoginServiceState()method.Any downsides to using a sticky message?
Yes, someone has to cleanup or initialise the state. See usages of the
clearLoginServiceState()method.Why not always
clearLoginServiceState()on the Fragment's start?We want the client (Fragment in this case) to pick up the current state of the Service. Clearing it always when starting breaks the pattern.
Why is the "logging in" notification sticky?
That notification is active when the Service is in the foreground and its work hasn't finished yet. Android API demands the notification to be sticky under those conditions.
Why are the error or success notifications dismissible?
The Service is finished by that time. Failure or success, nothing is running in the background anymore and the sticky notification doesn't make sense. User can dismiss the notification and resume the app whenever they prefer to continue.
To test:
The following flow is "touched" by this PR and should continue to work as normal:
/cc @mzorz you might want to have a look at this. It's based on the solution we reviewed last week and builds on top of it. Thanks!
/cc @theck13 there are some Social login things touched here so, I would appreciate a pass to make sure nothing broke there. Thanks!