Skip to content
This repository has been archived by the owner on Nov 1, 2022. It is now read-only.

Issue #282: Process CustomTab intent #307

Merged
merged 1 commit into from
Jun 21, 2018

Conversation

csadilek
Copy link
Contributor

This processes CustomTab intents. We currently only use the toolbarColor config. I've filed #306 for the remaining properties.

This only works with WebView right now until the issues mentioned in #158 are all resolved.

@csadilek csadilek requested a review from pocmo June 19, 2018 06:28
@csadilek csadilek force-pushed the issue-282 branch 2 times, most recently from 8c75a33 to afbb929 Compare June 19, 2018 06:29
@codecov
Copy link

codecov bot commented Jun 19, 2018

Codecov Report

Merging #307 into master will increase coverage by 0.07%.
The diff coverage is 90.32%.

Impacted file tree graph

@@             Coverage Diff              @@
##             master     #307      +/-   ##
============================================
+ Coverage     76.88%   76.95%   +0.07%     
- Complexity      686      693       +7     
============================================
  Files           100      101       +1     
  Lines          2570     2591      +21     
  Branches        374      377       +3     
============================================
+ Hits           1976     1994      +18     
- Misses          386      388       +2     
- Partials        208      209       +1
Impacted Files Coverage Δ Complexity Δ
...java/mozilla/components/concept/toolbar/Toolbar.kt 41.93% <0%> (-0.69%) 0 <0> (ø)
...s/browser/session/storage/DefaultSessionStorage.kt 89.55% <100%> (+1.31%) 19 <0> (+1) ⬆️
...zilla/components/browser/session/SessionManager.kt 89.55% <100%> (-1.36%) 32 <4> (+3)
...java/mozilla/components/browser/session/Session.kt 75.67% <100%> (+0.67%) 11 <2> (+2) ⬆️
...zilla/components/feature/session/SessionFeature.kt 72.72% <100%> (ø) 6 <1> (ø) ⬇️
...illa/components/feature/session/SessionUseCases.kt 100% <100%> (ø) 1 <0> (ø) ⬇️
...omponents/browser/session/tab/CustomTabsService.kt 100% <100%> (ø) 2 <2> (?)
.../components/feature/session/EngineViewPresenter.kt 71.42% <50%> (-11.91%) 3 <1> (ø)
...mponents/feature/session/SessionIntentProcessor.kt 90.47% <92.3%> (-1.84%) 6 <0> (ø)
... and 1 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 51a6558...7246611. Read the comment docs.

@@ -110,12 +110,12 @@ class DefaultSessionStorage(
json.put(VERSION_KEY, VERSION)
json.put(SELECTED_SESSION_KEY, sessionManager.selectedSession.id)

sessionManager.sessions.forEach({ session ->
sessionManager.sessions.filter { it.customTabConfig == null }.forEach { session ->
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if this should be the default for sessions (return the filtered list). A lot of things are going to need the filtered list (e.g. tabs tray) and so far nothing in Focus required all sessions. We only look up specific sessions by ID for custom tabs.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, I will update.

import android.support.customtabs.ICustomTabsCallback
import android.support.customtabs.ICustomTabsService

class CustomTabsService : Service() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we should really put this into session. Some of the implementations here will require access to more things. How about moving this to feature-session? Or should we create something like feature-customtabs where we can add more things later on?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, not sure, let's file a follow-up and refactor this later?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, let's file a follow-up :) 👍

displayToolbar.setBackgroundColor(color)
editToolbar.setBackgroundColor(color)
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This shouldn't be needed as BrowserToolbar is already a View. I had the same problem with EngineView which is just an interface but sometimes I want to use it as a View. I added this helper to it:

fun asView(): View = this as View

How about we add something like that to Toolbar? With that we do not need to mirror all View methods that we may want to access.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, looks good to me.

override fun onSessionAdded(session: Session) {
if (session.customTabConfig != null) {
renderSession(session)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we should add Session.isCustomTab(): Boolean :)

fun invoke(url: String) {
sessionManager.getOrCreateEngineSession().loadUrl(url)
fun invoke(url: String, session: Session = sessionManager.selectedSession) {
sessionManager.getOrCreateEngineSession(session).loadUrl(url)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, nice! Now this has the default parameter like the other methods here. :)

session.customTabConfig?.toolbarColor?.let {
toolbar.setBackgroundColor(it)
}
// TODO Apply remaining configurations: https://github.com/mozilla-mobile/android-components/issues/306
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if this should be done in the callback: Afaik this only works if the callback is set before the custom tab config is set, right? Otherwise the callback won't get executed, or? Especially as the config will never change I wonder if we should just do this on session selection / initializeView?

}

override fun onResume() {
super.onResume()

sessionFeature?.start()
toolbarFeature?.start()

components.sessionIntentProcessor.process(intent)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

onResume() seems to be wrong. This would be called whenever the app gets to the foreground.

onCreate() (and maybe onNewIntent()?)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@pocmo the problem I am having here is that I need the features to start before the intent is being processed. I am not able to make this work with onCreate or onNewIntent, since we have to start in onResume. Any ideas?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume because you need onCustomTabConfigChanged() to be called in ToolbarPresenter? As mentioned above: I think you want to update the toolbar from initializeView() and not onCustomTabConfigChanged().

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@pocmo no (I actually missed that comment before). The reason is that we want the ToolbarPresenter to be started, so it's an active Session observer.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to know (at least currently) when a CustomTab session was added or another session selected when we're processing the intent.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, we need to fix our presenter. The pattern for all those UI components should be: (1) Display current state, (2) update on changes. Looks like we never implemented (1) here. :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, this is addressed now. I took a step back and I don't think we should start/stop features in onPause/onResume but onStart/onStop instead. This is what we do in Fenix. Then this whole type of problem goes away. I think It's reasonable to assume that all features are started when we're processing intents. So, this is cleaner, imo, and actually works :).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This may work for now, but it will fall apart for the same reasons again: I think the assumption that all features exist, are started and subscribed at all times is wrong. It's very well possible that we are going to process intents while the toolbar is not even on the screen and we are somewhere in settings. Imagine other components like the tabs tray. There's no need to create, start and subscribe the feature if it's not visible. I think all components will need to display the current state on start and update if there are changes during their lifetime.

Copy link
Contributor

@pocmo pocmo Jun 20, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apart from that this we would still process the same Intent multiple times in onStart(). Try this:

  • Set sample browser as default browser
  • Click on a link in another app to open the browser
  • Press "home" button of device
  • Switch back to app

-> We process the Intent every time the app is shown again. Right now this means we reload the page every time. Once we start opening tabs we will open new tabs every time.

The only way to avoid that is to process the Intent in onCreate() and only if savedInstanceState is null - meaning this activity wasn't just re-created.

We can also land this and continue in a follow-up?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, I will try something. My view on the problem is that the presenters observe and act on changes only after they started. So, they might miss events. However, it should be possible to act on events that are still relevant (that we know occurred and haven't been acted on) when the presenters are started.

@csadilek csadilek force-pushed the issue-282 branch 8 times, most recently from e8c17d2 to 1cd420b Compare June 19, 2018 19:46
@csadilek csadilek changed the title Issue 282: Process CustomTab intent Issue #282: Process CustomTab intent Jun 19, 2018
*/
val sessions: List<Session>
get() = synchronized(values) { values.filter { it.customTabConfig == null }.toList() }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think toList() may not be necessary here as filter() should already return a new list? Not sure about the type though.

/**
* Returns a list of all active sessions.
*/
val allSessions: List<Session>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: For the SessionManager I would also accept just all? 🙃

@csadilek csadilek force-pushed the issue-282 branch 3 times, most recently from f3f763f to 02b72a5 Compare June 20, 2018 15:42
@@ -20,8 +20,7 @@ class EngineViewPresenter(
* Start presenter and display data in view.
*/
fun start() {
renderSession(sessionManager.selectedSession)

sessionManager.all.forEach { handleNewSession(it) }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not understand why we loop over all sessions here and then later render it if it's a custom tab or selected. Isn't this the same as the previous code that just rendered the selected session? With that we may render multiple things if there are multiple sessions (selected + multiple custom tabs) and just settle on the last one.

I think the underlying problem you are trying to solve is that we never display a custom tab right now because it is never the selected one(*) [at least if there's already one session]?

Essentially Custom Tabs and the browser will live in different activities. So I guess what we will need to do is to tell renderView to either render "whatever is selected" or a specific (custom tab) session. That's what we essentially do in Focus with BrowserFragment.

All this makes me think whether we want to delay displaying custom tabs in the sample browser until we have made the separation at the activity level.

(*) We actually should make sure that a custom tab doesn't get selected because this doesn't make any sense. But that's something for another issue.

@@ -24,6 +24,7 @@ class ToolbarPresenter(
fun start() {
session = sessionManager.selectedSession

sessionManager.all.forEach { onSessionAdded(it) }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here I also do not understand why we loop over all sessions and then display the state based on the last custom tab we saw (in onSessionAdded()) what may or may not be what we actually display.

This probably has the same problem as EngineViewPresenter: I think we actually want to display the currently selected session (main browser activity) or a specific session (custom tab activities)

@csadilek csadilek force-pushed the issue-282 branch 3 times, most recently from 3ac3eea to 18329fd Compare June 21, 2018 03:09
@csadilek
Copy link
Contributor Author

csadilek commented Jun 21, 2018

@pocmo OK, this is updated now with the discussed changes:

  • Created a separate activity to handle intents, reusing our SessionIntentProcessor and sharing code between the two activity types
  • Made sure there's only one instance of our components

This works fine now, but it uncovered one problem (which is fixed here as well):

  • It is now possible for the browser to be closed and re-opened using the same JVM (a CustomTab might still be around keeping the process alive). When the app is re-opened, it does not need to restore the sessions from storage (we have a running process with the sessions in memory, now that we have a single SessionManager). However, we have a new instance of our Presenter, which wants to render the selected session but nothing called loadUrl to make sure it's properly rendered. loadUrl is only called when the session is added/linked (e.g. when restored from storage or newly created). I fixed this by moving the loadUrl call from sessionManager.link to the Presenter. I never quite liked that this happened as a side-effect of link anyway. So, this seems cleaner to me and works well.

Curious what you think.

@csadilek csadilek force-pushed the issue-282 branch 2 times, most recently from 002993e to 2b80bdb Compare June 21, 2018 03:37
Copy link
Contributor

@pocmo pocmo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is nice! 👍


this.session = session
activeSession = session
session.register(this)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to check here whether we were started with a specific session and then ignore selection changes? Most of the time this won't be a problem as the selection won't change while the custom tab activity is in the foreground - but you may be able to have both activities visible using Android's split screen feature.

Or should we just not register an observer on the session manager in onStart() if we got a session? We only listen for a selection change right now.

override fun onCreate(savedInstanceState: Bundle?) {
components.sessionIntentProcessor.process(intent)
super.onCreate(savedInstanceState)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, interesting... the IntentReceiverActivity is a BrowserActivity instead of launching it? Won't this duplicate the browser if there are multiple non-custom-tab VIEW intents or if we launch the app normally and then send a normal intent? Anyhow, at this point I would prefer merging this and investigating this later.

@pocmo
Copy link
Contributor

pocmo commented Jun 21, 2018

It is now possible for the browser to be closed and re-opened using the same JVM (a CustomTab might still be around keeping the process alive). When the app is re-opened, it does not need to restore the sessions from storage (we have a running process with the sessions in memory, now that we have a single SessionManager). However, we have a new instance of our Presenter, which wants to render the selected session but nothing called loadUrl to make sure it's properly rendered. loadUrl is only called when the session is added/linked (e.g. when restored from storage or newly created). I fixed this by moving the loadUrl call from sessionManager.link to the Presenter. I never quite liked that this happened as a side-effect of link anyway. So, this seems cleaner to me and works well.

That's interesting. Can we file an issue for that too? Now we may create an engine session but we are not going to load the page in it unless we render it. I wonder if this may have other side effects later on.

Now that you mention that I also wonder if there are problems whenever we end up with no session (because there may be only a custom tab one when we launch the browser the first time) because a bunch of things always expect a selected session to exist. Anyhow, let's get this in first!

@csadilek csadilek force-pushed the issue-282 branch 2 times, most recently from a45eb07 to 913ac8d Compare June 21, 2018 17:01

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: This is redundant and you can just leave the class empty :)

@csadilek csadilek force-pushed the issue-282 branch 2 times, most recently from ae0b4f7 to e39db2d Compare June 21, 2018 17:47
@csadilek csadilek merged commit 343decf into mozilla-mobile:master Jun 21, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants