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

Commit

Permalink
Issue #282: Process CustomTab intents
Browse files Browse the repository at this point in the history
  • Loading branch information
csadilek committed Jun 21, 2018
1 parent 00fea5b commit 18329fd
Show file tree
Hide file tree
Showing 25 changed files with 352 additions and 75 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,11 @@ class Session(
_, _, _ -> notifyObservers ({ onCustomTabConfigChanged() })
}

/**
* Returns whether or not this session is used for a Custom Tab.
*/
fun isCustomTabSession() = customTabConfig != null

/**
* Helper method to notify observers.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,15 @@ class SessionManager(
}

/**
* Gets all sessions.
* Returns a list of active sessions and filters out sessions used for CustomTabs.
*/
val sessions: List<Session>
get() = synchronized(values) { values.filter { !it.isCustomTabSession() } }

/**
* Returns a list of all active sessions.
*/
val all: List<Session>
get() = synchronized(values) { values.toList() }

/**
Expand Down Expand Up @@ -81,7 +87,6 @@ class SessionManager(
this.engineSession = engineSession
this.engineObserver = EngineObserver(session).also { observer ->
engineSession.register(observer)
engineSession.loadUrl(session.url)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,6 @@ class DefaultSessionStorage(
val jsonSession = jsonRoot.getJSONObject(it)
val session = deserializeSession(it, jsonSession.getJSONObject(SESSION_KEY))
val engineSession = deserializeEngineSession(engine, jsonSession.getJSONObject(ENGINE_SESSION_KEY))

sessionManager.add(session, engineSession = engineSession)
}

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

sessionManager.sessions.forEach({ session ->
sessionManager.sessions.forEach { session ->
val sessionJson = JSONObject()
sessionJson.put(SESSION_KEY, serializeSession(session))
sessionJson.put(ENGINE_SESSION_KEY, serializeEngineSession(sessionManager.getEngineSession(session)))
json.put(session.id, sessionJson)
})
}

file = getFile()
outputStream = file.startWrite()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

package mozilla.components.browser.session.tab

import android.app.Service
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.os.IBinder
import android.support.customtabs.ICustomTabsCallback
import android.support.customtabs.ICustomTabsService

class CustomTabsService : Service() {
override fun onBind(intent: Intent): IBinder? {
return object : ICustomTabsService.Stub() {
override fun warmup(flags: Long): Boolean = true
override fun newSession(cb: ICustomTabsCallback) = true
override fun mayLaunchUrl(cb: ICustomTabsCallback, url: Uri, extras: Bundle, bundles: List<Bundle>) = true
override fun extraCommand(s: String, bundle: Bundle): Bundle? = null
override fun updateVisuals(cb: ICustomTabsCallback, bundle: Bundle) = false
override fun requestPostMessageChannel(cb: ICustomTabsCallback, uri: Uri): Boolean = false
override fun postMessage(cb: ICustomTabsCallback, s: String, bundle: Bundle): Int = 0
override fun validateRelationship(cb: ICustomTabsCallback, i: Int, uri: Uri, bundle: Bundle) = false
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ package mozilla.components.browser.session.storage
import android.util.AtomicFile
import mozilla.components.browser.session.Session
import mozilla.components.browser.session.SessionManager
import mozilla.components.browser.session.tab.CustomTabConfig
import mozilla.components.concept.engine.Engine
import mozilla.components.concept.engine.EngineSession
import org.json.JSONException
Expand Down Expand Up @@ -72,6 +73,35 @@ class DefaultSessionStorageTest {
verify(restoredEngineSession)!!.restoreState(engineSessionState.filter { it.key != "k3" })
}

@Test
fun testPersistIgnoresCustomTabSessions() {
val session = Session("http://mozilla.org")
session.customTabConfig = mock(CustomTabConfig::class.java)
val engineSessionState = mutableMapOf("k0" to "v0", "k1" to 1, "k2" to true, "k3" to emptyList<Any>())

val engineSession = mock(EngineSession::class.java)
`when`(engineSession.saveState()).thenReturn(engineSessionState)

val engine = mock(Engine::class.java)
`when`(engine.createSession()).thenReturn(mock(EngineSession::class.java))

val sessionManager = SessionManager(engine)
sessionManager.add(session, true, engineSession)

// Persist current sessions
val storage = DefaultSessionStorage(RuntimeEnvironment.application)
val persisted = storage.persist(sessionManager)
assertTrue(persisted)

// Restore session using a fresh SessionManager
val restoredSessionManager = SessionManager(engine)
val restored = storage.restore(engine, restoredSessionManager)
assertTrue(restored)

// Nothing to restore as CustomTab sessions should not be persisted
assertEquals(0, restoredSessionManager.sessions.size)
}

@Test
fun testRestoreReturnsFalseOnIOException() {
val engine = mock(Engine::class.java)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

package mozilla.components.browser.session.tab

import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.support.customtabs.ICustomTabsCallback
import android.support.customtabs.ICustomTabsService
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.mock
import org.robolectric.RobolectricTestRunner

@RunWith(RobolectricTestRunner::class)
class CustomTabsServiceTest {

@Test
fun testCustomTabService() {
val customTabsService = CustomTabsService()
val customTabsServiceStub = customTabsService.onBind(mock(Intent::class.java))
assertNotNull(customTabsServiceStub)

val stub = customTabsServiceStub as ICustomTabsService.Stub
assertTrue(stub.warmup(123))
assertTrue(stub.newSession(mock(ICustomTabsCallback::class.java)))
assertNull(stub.extraCommand("", mock(Bundle::class.java)))
assertFalse(stub.updateVisuals(mock(ICustomTabsCallback::class.java), mock(Bundle::class.java)))
assertFalse(stub.requestPostMessageChannel(mock(ICustomTabsCallback::class.java), mock(Uri::class.java)))
assertEquals(0, stub.postMessage(mock(ICustomTabsCallback::class.java), "", mock(Bundle::class.java)))
assertFalse(stub.validateRelationship(
mock(ICustomTabsCallback::class.java),
0,
mock(Uri::class.java),
mock(Bundle::class.java)))
assertTrue(stub.mayLaunchUrl(
mock(ICustomTabsCallback::class.java),
mock(Uri::class.java),
mock(Bundle::class.java), emptyList<Bundle>()))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,11 @@ interface Toolbar {
*/
fun addNavigationAction(action: Action)

/**
* Casts this toolbar to an Android View object.
*/
fun asView(): View = this as View

/**
* Generic interface for actions to be added to the toolbar.
*/
Expand Down
1 change: 1 addition & 0 deletions components/feature/session/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ dependencies {
testImplementation "junit:junit:${rootProject.ext.dependencies['junit']}"
testImplementation "org.robolectric:robolectric:${rootProject.ext.dependencies['robolectric']}"
testImplementation "org.mockito:mockito-core:${rootProject.ext.dependencies['mockito']}"
testImplementation "com.android.support:customtabs:${rootProject.ext.dependencies['supportLibraries']}"
}

archivesBaseName = "feature-session"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,18 @@ import mozilla.components.concept.engine.EngineView
*/
class EngineViewPresenter(
private val sessionManager: SessionManager,
private val engineView: EngineView
private val engineView: EngineView,
private val sessionId: String? = null
) : SessionManager.Observer {

private var activeSession: Session? = null

/**
* Start presenter and display data in view.
*/
fun start() {
renderSession(sessionManager.selectedSession)
val session = sessionId?.let { sessionManager.findSessionById(sessionId) } ?: sessionManager.selectedSession
renderSession(session)

sessionManager.register(this)
}
Expand All @@ -37,9 +41,13 @@ class EngineViewPresenter(
*/
override fun onSessionSelected(session: Session) {
renderSession(session)
activeSession = session
}

private fun renderSession(session: Session) {
engineView.render(sessionManager.getOrCreateEngineSession(session))
sessionManager.getOrCreateEngineSession(session).apply {
engineView.render(this)
loadUrl(session.url)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,16 @@ class SessionFeature(
private val sessionManager: SessionManager,
private val sessionUseCases: SessionUseCases,
engineView: EngineView,
private val sessionStorage: SessionStorage? = null
private val sessionStorage: SessionStorage? = null,
sessionId: String? = null
) {
internal val presenter = EngineViewPresenter(sessionManager, engineView)
internal val presenter = EngineViewPresenter(sessionManager, engineView, sessionId)

/**
* Start feature: App is in the foreground.
*/
fun start() {
presenter.start()

sessionStorage?.start(sessionManager)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ package mozilla.components.feature.session

import android.content.Intent
import android.text.TextUtils
import mozilla.components.browser.session.Session
import mozilla.components.browser.session.SessionManager
import mozilla.components.browser.session.tab.CustomTabConfig
import mozilla.components.support.utils.SafeIntent

typealias IntentHandler = (Intent) -> Boolean

Expand All @@ -14,17 +18,31 @@ typealias IntentHandler = (Intent) -> Boolean
*/
class SessionIntentProcessor(
private val sessionUseCases: SessionUseCases,
private val sessionManager: SessionManager,
useDefaultHandlers: Boolean = true
) {
private val defaultActionViewHandler = { intent: Intent ->
val url = intent.dataString
if (TextUtils.isEmpty(url)) {
false
} else {
// TODO switch to loadUrlInNewTab
// https://github.com/mozilla-mobile/android-components/issues/136
sessionUseCases.loadUrl.invoke(url)
true
val safeIntent = SafeIntent(intent)
val url = safeIntent.dataString ?: ""

when {
TextUtils.isEmpty(url) -> false

CustomTabConfig.isCustomTabIntent(safeIntent) -> {
val session = Session(url).apply {
this.customTabConfig = CustomTabConfig.createFromIntent(safeIntent)
}
sessionManager.add(session)
sessionUseCases.loadUrl.invoke(url, session)
intent.putExtra(ACTIVE_SESSION_ID, session.id)
true
}

else -> {
// TODO support loadUrlInNewTab: https://github.com/mozilla-mobile/android-components/issues/136
sessionUseCases.loadUrl.invoke(url)
true
}
}
}

Expand Down Expand Up @@ -63,4 +81,8 @@ class SessionIntentProcessor(
fun unregisterHandler(action: String) {
handlers.remove(action)
}

companion object {
public const val ACTIVE_SESSION_ID = "activeSessionId"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,14 @@ class SessionUseCases(
private val sessionManager: SessionManager
) {
/**
* Loads the provided URL using the currently selected session.
* Loads the provided URL using the provided session (or the currently
* selected session if none is provided).
*
* @param url url to load.
* @param session the session for which the URL should be loaded.
*/
fun invoke(url: String) {
sessionManager.getOrCreateEngineSession().loadUrl(url)
fun invoke(url: String, session: Session = sessionManager.selectedSession) {
sessionManager.getOrCreateEngineSession(session).loadUrl(url)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,26 @@

package mozilla.components.feature.session

import mozilla.components.browser.session.Session
import mozilla.components.browser.session.SessionManager
import mozilla.components.concept.engine.EngineSession
import mozilla.components.concept.engine.EngineView
import org.junit.Test
import org.mockito.Mockito.`when`
import org.mockito.Mockito.mock
import org.mockito.Mockito.verify

class EngineViewPresenterTest {
private val sessionManager = mock(SessionManager::class.java)
private val session = mock(Session::class.java)
private val engineSession = mock(EngineSession::class.java)
private val engineView = mock(EngineView::class.java)

@Test
fun testStartRegistersObserver() {
`when`(sessionManager.selectedSession).thenReturn(session)
`when`(sessionManager.getOrCreateEngineSession(session)).thenReturn(engineSession)

val engineViewPresenter = EngineViewPresenter(sessionManager, engineView)
engineViewPresenter.start()
verify(sessionManager).register(engineViewPresenter)
Expand Down
Loading

0 comments on commit 18329fd

Please sign in to comment.