1
0
Fork 0

Issue #1000 - Remove isCustomTab flag (#4656)

master
Tiger Oakes 2019-08-20 13:00:49 -04:00 committed by Jeff Boek
parent 1afc0eacd8
commit 95ef312b1e
7 changed files with 257 additions and 126 deletions

View File

@ -11,32 +11,33 @@ import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.util.AttributeSet import android.util.AttributeSet
import android.view.View import android.view.View
import androidx.annotation.CallSuper
import androidx.annotation.IdRes import androidx.annotation.IdRes
import androidx.annotation.VisibleForTesting
import androidx.annotation.VisibleForTesting.PROTECTED
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.Toolbar import androidx.appcompat.widget.Toolbar
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.navigation.NavController import androidx.navigation.NavDestination
import androidx.navigation.fragment.NavHostFragment import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.ui.AppBarConfiguration import androidx.navigation.ui.AppBarConfiguration
import androidx.navigation.ui.NavigationUI import androidx.navigation.ui.NavigationUI
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import io.sentry.Sentry
import io.sentry.event.Breadcrumb
import io.sentry.event.BreadcrumbBuilder
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import mozilla.components.browser.search.SearchEngine import mozilla.components.browser.search.SearchEngine
import mozilla.components.browser.session.Session import mozilla.components.browser.session.Session
import mozilla.components.browser.session.SessionManager import mozilla.components.browser.session.SessionManager
import mozilla.components.browser.session.intent.EXTRA_SESSION_ID
import mozilla.components.concept.engine.EngineView import mozilla.components.concept.engine.EngineView
import mozilla.components.lib.crash.Crash import mozilla.components.lib.crash.Crash
import mozilla.components.support.base.feature.BackHandler import mozilla.components.support.base.feature.BackHandler
import mozilla.components.support.ktx.kotlin.isUrl import mozilla.components.support.ktx.kotlin.isUrl
import mozilla.components.support.ktx.kotlin.toNormalizedUrl import mozilla.components.support.ktx.kotlin.toNormalizedUrl
import mozilla.components.support.utils.SafeIntent import mozilla.components.support.utils.SafeIntent
import mozilla.components.support.utils.toSafeIntent
import org.mozilla.fenix.components.FenixSnackbar import org.mozilla.fenix.components.FenixSnackbar
import org.mozilla.fenix.components.isSentryEnabled import org.mozilla.fenix.components.isSentryEnabled
import org.mozilla.fenix.components.metrics.Event import org.mozilla.fenix.components.metrics.Event
import org.mozilla.fenix.components.metrics.SentryBreadcrumbsRecorder
import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.getRootView import org.mozilla.fenix.ext.getRootView
import org.mozilla.fenix.ext.nav import org.mozilla.fenix.ext.nav
@ -45,93 +46,40 @@ import org.mozilla.fenix.utils.Settings
@SuppressWarnings("TooManyFunctions", "LargeClass") @SuppressWarnings("TooManyFunctions", "LargeClass")
open class HomeActivity : AppCompatActivity(), ShareFragment.TabsSharedCallback { open class HomeActivity : AppCompatActivity(), ShareFragment.TabsSharedCallback {
open val isCustomTab = false
private var sessionObserver: SessionManager.Observer? = null
lateinit var themeManager: ThemeManager lateinit var themeManager: ThemeManager
lateinit var browsingModeManager: BrowsingModeManager
private var sessionObserver: SessionManager.Observer? = null
private val navHost by lazy { private val navHost by lazy {
supportFragmentManager.findFragmentById(R.id.container) as NavHostFragment supportFragmentManager.findFragmentById(R.id.container) as NavHostFragment
} }
lateinit var browsingModeManager: BrowsingModeManager final override fun onCreate(savedInstanceState: Bundle?) {
private val onDestinationChangedListener =
NavController.OnDestinationChangedListener { _, dest, _ ->
val fragmentName = resources.getResourceEntryName(dest.id)
Sentry.getContext().recordBreadcrumb(
BreadcrumbBuilder()
.setCategory("DestinationChanged")
.setMessage("Changing to fragment $fragmentName, isCustomTab: $isCustomTab")
.setLevel(Breadcrumb.Level.INFO)
.build()
)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
components.publicSuffixList.prefetch() components.publicSuffixList.prefetch()
browsingModeManager = createBrowsingModeManager() setupThemeAndBrowsingMode()
themeManager = createThemeManager(browsingModeManager.mode)
setContentView(R.layout.activity_home) setContentView(R.layout.activity_home)
setupToolbarAndNavigation() setupToolbarAndNavigation()
if (Settings.getInstance(this).isTelemetryEnabled && isSentryEnabled()) { if (Settings.getInstance(this).isTelemetryEnabled) {
navHost.navController.addOnDestinationChangedListener(onDestinationChangedListener) if (isSentryEnabled()) {
} lifecycle.addObserver(SentryBreadcrumbsRecorder(navHost.navController, ::getSentryBreadcrumbMessage))
intent
?.let { SafeIntent(it) }
?.let {
when {
isCustomTab -> Event.OpenedApp.Source.CUSTOM_TAB
it.isLauncherIntent -> Event.OpenedApp.Source.APP_ICON
it.action == Intent.ACTION_VIEW -> Event.OpenedApp.Source.LINK
else -> null
}
} }
?.also { components.analytics.metrics.track(Event.OpenedApp(it)) }
handleOpenedFromExternalSourceIfNecessary(intent) intent
} ?.toSafeIntent()
?.let(::getIntentSource)
private fun setupToolbarAndNavigation() { ?.also { components.analytics.metrics.track(Event.OpenedApp(it)) }
// Add ids to this that we don't want to have a toolbar back button
val appBarConfiguration = AppBarConfiguration.Builder().build()
val navigationToolbar = findViewById<Toolbar>(R.id.navigationToolbar)
setSupportActionBar(navigationToolbar)
NavigationUI.setupWithNavController(
navigationToolbar,
navHost.navController,
appBarConfiguration
)
navigationToolbar.setNavigationOnClickListener {
onBackPressed()
} }
supportActionBar?.hide() supportActionBar?.hide()
} }
override fun onDestroy() { @CallSuper
sessionObserver?.let { components.core.sessionManager.unregister(it) }
navHost.navController.removeOnDestinationChangedListener(onDestinationChangedListener)
super.onDestroy()
}
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
intent?.let {
if (Crash.isCrashIntent(it)) {
openToCrashReporter(it)
} else {
handleOpenedFromExternalSourceIfNecessary(it)
}
}
}
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
lifecycleScope.launch { lifecycleScope.launch {
@ -147,21 +95,29 @@ open class HomeActivity : AppCompatActivity(), ShareFragment.TabsSharedCallback
} }
} }
override fun onCreateView( /**
* Handles intents received when the activity is open.
*/
final override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
handleCrashIfNecessary(intent)
handleOpenedFromExternalSourceIfNecessary(intent)
}
/**
* Overrides view inflation to inject a custom [EngineView] from [components].
*/
final override fun onCreateView(
parent: View?, parent: View?,
name: String, name: String,
context: Context, context: Context,
attrs: AttributeSet attrs: AttributeSet
): View? = ): View? = when (name) {
when (name) { EngineView::class.java.name -> components.core.engine.createView(context, attrs).asView()
EngineView::class.java.name -> components.core.engine.createView( else -> super.onCreateView(parent, name, context, attrs)
context, }
attrs
).asView()
else -> super.onCreateView(parent, name, context, attrs)
}
override fun onBackPressed() { final override fun onBackPressed() {
supportFragmentManager.primaryNavigationFragment?.childFragmentManager?.fragments?.forEach { supportFragmentManager.primaryNavigationFragment?.childFragmentManager?.fragments?.forEach {
if (it is BackHandler && it.onBackPressed()) { if (it is BackHandler && it.onBackPressed()) {
return return
@ -170,6 +126,46 @@ open class HomeActivity : AppCompatActivity(), ShareFragment.TabsSharedCallback
super.onBackPressed() super.onBackPressed()
} }
protected open fun getSentryBreadcrumbMessage(destination: NavDestination): String {
val fragmentName = resources.getResourceEntryName(destination.id)
return "Changing to fragment $fragmentName, isCustomTab: false"
}
@VisibleForTesting(otherwise = PROTECTED)
internal open fun getIntentSource(intent: SafeIntent): Event.OpenedApp.Source? {
return when {
intent.isLauncherIntent -> Event.OpenedApp.Source.APP_ICON
intent.action == Intent.ACTION_VIEW -> Event.OpenedApp.Source.LINK
else -> null
}
}
private fun setupThemeAndBrowsingMode() {
browsingModeManager = createBrowsingModeManager()
themeManager = createThemeManager()
themeManager.setActivityTheme(this)
themeManager.applyStatusBarTheme(this)
}
private fun setupToolbarAndNavigation() {
// Add ids to this that we don't want to have a toolbar back button
val appBarConfiguration = AppBarConfiguration.Builder().build()
val navigationToolbar = findViewById<Toolbar>(R.id.navigationToolbar)
setSupportActionBar(navigationToolbar)
NavigationUI.setupWithNavController(navigationToolbar, navHost.navController, appBarConfiguration)
navigationToolbar.setNavigationOnClickListener {
onBackPressed()
}
handleOpenedFromExternalSourceIfNecessary(intent)
}
private fun handleCrashIfNecessary(intent: Intent?) {
if (intent != null && Crash.isCrashIntent(intent)) {
openToCrashReporter(intent)
}
}
private fun openToCrashReporter(intent: Intent) { private fun openToCrashReporter(intent: Intent) {
val directions = NavGraphDirections.actionGlobalCrashReporter(intent) val directions = NavGraphDirections.actionGlobalCrashReporter(intent)
navHost.navController.navigate(directions) navHost.navController.navigate(directions)
@ -196,13 +192,8 @@ open class HomeActivity : AppCompatActivity(), ShareFragment.TabsSharedCallback
if (intent?.extras?.getBoolean(OPEN_TO_BROWSER) != true) return if (intent?.extras?.getBoolean(OPEN_TO_BROWSER) != true) return
this.intent.putExtra(OPEN_TO_BROWSER, false) this.intent.putExtra(OPEN_TO_BROWSER, false)
var customTabSessionId: String? = null
if (isCustomTab) { openToBrowser(BrowserDirection.FromGlobal, getIntentSessionId(intent.toSafeIntent()))
customTabSessionId = SafeIntent(intent).getStringExtra(EXTRA_SESSION_ID)
}
openToBrowser(BrowserDirection.FromGlobal, customTabSessionId)
} }
@SuppressWarnings("ComplexMethod") @SuppressWarnings("ComplexMethod")
@ -248,6 +239,8 @@ open class HomeActivity : AppCompatActivity(), ShareFragment.TabsSharedCallback
navHost.navController.navigate(directions) navHost.navController.navigate(directions)
} }
protected open fun getIntentSessionId(intent: SafeIntent): String? = null
@Suppress("LongParameterList") @Suppress("LongParameterList")
fun openToBrowserAndLoad( fun openToBrowserAndLoad(
searchTermOrURL: String, searchTermOrURL: String,
@ -312,18 +305,6 @@ open class HomeActivity : AppCompatActivity(), ShareFragment.TabsSharedCallback
} }
} }
private val singleSessionObserver = object : Session.Observer {
var urlLoading: String? = null
override fun onLoadingStateChanged(session: Session, loading: Boolean) {
if (loading) {
urlLoading = session.url
} else if (urlLoading != null && !session.private) {
components.analytics.metrics.track(Event.UriOpened)
}
}
}
fun updateThemeForSession(session: Session) { fun updateThemeForSession(session: Session) {
val sessionMode = BrowsingMode.fromBoolean(session.private) val sessionMode = BrowsingMode.fromBoolean(session.private)
if (sessionMode != browsingModeManager.mode) { if (sessionMode != browsingModeManager.mode) {
@ -331,31 +312,29 @@ open class HomeActivity : AppCompatActivity(), ShareFragment.TabsSharedCallback
} }
} }
private fun createBrowsingModeManager(): BrowsingModeManager { protected open fun createBrowsingModeManager(): BrowsingModeManager {
return if (isCustomTab) { return DefaultBrowsingModeManager(Settings.getInstance(this)) { mode ->
CustomTabBrowsingModeManager() themeManager.currentTheme = mode
} else {
DefaultBrowsingModeManager(Settings.getInstance(this)) { mode ->
themeManager.currentTheme = mode
}
} }
} }
private fun createThemeManager(currentTheme: BrowsingMode): ThemeManager { protected open fun createThemeManager(): ThemeManager {
val themeManager = if (isCustomTab) { return DefaultThemeManager(browsingModeManager.mode, this)
CustomTabThemeManager()
} else {
DefaultThemeManager(currentTheme) {
themeManager.setActivityTheme(this)
recreate()
}
}
themeManager.setActivityTheme(this)
themeManager.applyStatusBarTheme(this)
return themeManager
} }
@Suppress("ComplexMethod")
private fun subscribeToSessions(): SessionManager.Observer { private fun subscribeToSessions(): SessionManager.Observer {
val singleSessionObserver = object : Session.Observer {
var urlLoading: String? = null
override fun onLoadingStateChanged(session: Session, loading: Boolean) {
if (loading) {
urlLoading = session.url
} else if (urlLoading != null && !session.private) {
components.analytics.metrics.track(Event.UriOpened)
}
}
}
return object : SessionManager.Observer { return object : SessionManager.Observer {
override fun onAllSessionsRemoved() { override fun onAllSessionsRemoved() {
@ -381,7 +360,7 @@ open class HomeActivity : AppCompatActivity(), ShareFragment.TabsSharedCallback
} }
override fun onTabsShared(tabsSize: Int) { override fun onTabsShared(tabsSize: Int) {
this@HomeActivity.getRootView()?.let { getRootView()?.let {
FenixSnackbar.make(it, Snackbar.LENGTH_SHORT).setText( FenixSnackbar.make(it, Snackbar.LENGTH_SHORT).setText(
getString( getString(
if (tabsSize == 1) R.string.sync_sent_tab_snackbar else if (tabsSize == 1) R.string.sync_sent_tab_snackbar else

View File

@ -101,13 +101,15 @@ abstract class ThemeManager {
class DefaultThemeManager( class DefaultThemeManager(
currentTheme: BrowsingMode, currentTheme: BrowsingMode,
private val onThemeChanged: (BrowsingMode) -> Unit private val activity: Activity
) : ThemeManager() { ) : ThemeManager() {
override var currentTheme: BrowsingMode = currentTheme override var currentTheme: BrowsingMode = currentTheme
set(value) { set(value) {
if (currentTheme != value) { if (currentTheme != value) {
field = value field = value
onThemeChanged(value)
setActivityTheme(activity)
activity.recreate()
} }
} }
} }

View File

@ -0,0 +1,57 @@
/* 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 org.mozilla.fenix.components.metrics
import android.os.Bundle
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.OnLifecycleEvent
import androidx.navigation.NavController
import androidx.navigation.NavDestination
import io.sentry.Sentry
import io.sentry.event.Breadcrumb
import io.sentry.event.BreadcrumbBuilder
import org.mozilla.fenix.components.isSentryEnabled
/**
* Records breadcrumbs in Sentry when the fragment changes.
*
* Should be registered as a [LifecycleObserver] on an activity if telemetry is enabled.
* It will automatically be removed when the lifecycle owner is destroyed.
*/
class SentryBreadcrumbsRecorder(
private val navController: NavController,
private val getBreadcrumbMessage: (NavDestination) -> String
) : NavController.OnDestinationChangedListener, LifecycleObserver {
@OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
fun onCreate() {
if (isSentryEnabled()) {
navController.addOnDestinationChangedListener(this)
}
}
@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
fun onDestroy() {
navController.removeOnDestinationChangedListener(this)
}
/**
* When the destination changes, record the new destination as a breadcrumb.
*/
override fun onDestinationChanged(
controller: NavController,
destination: NavDestination,
arguments: Bundle?
) {
Sentry.getContext().recordBreadcrumb(
BreadcrumbBuilder()
.setCategory("DestinationChanged")
.setMessage(getBreadcrumbMessage(destination))
.setLevel(Breadcrumb.Level.INFO)
.build()
)
}
}

View File

@ -1,8 +1,8 @@
package org.mozilla.fenix.customtabs
/* This Source Code Form is subject to the terms of the Mozilla Public /* 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 * 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/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.fenix.customtabs
import mozilla.components.concept.sync.AccountObserver import mozilla.components.concept.sync.AccountObserver
import mozilla.components.concept.sync.OAuthAccount import mozilla.components.concept.sync.OAuthAccount

View File

@ -4,8 +4,25 @@
package org.mozilla.fenix.customtabs package org.mozilla.fenix.customtabs
import androidx.navigation.NavDestination
import mozilla.components.browser.session.intent.getSessionId
import mozilla.components.support.utils.SafeIntent
import org.mozilla.fenix.CustomTabBrowsingModeManager
import org.mozilla.fenix.CustomTabThemeManager
import org.mozilla.fenix.HomeActivity import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.components.metrics.Event
open class CustomTabActivity : HomeActivity() { open class CustomTabActivity : HomeActivity() {
override val isCustomTab = true final override fun getSentryBreadcrumbMessage(destination: NavDestination): String {
val fragmentName = resources.getResourceEntryName(destination.id)
return "Changing to fragment $fragmentName, isCustomTab: true"
}
final override fun getIntentSource(intent: SafeIntent) = Event.OpenedApp.Source.CUSTOM_TAB
final override fun getIntentSessionId(intent: SafeIntent) = intent.getSessionId()
final override fun createBrowsingModeManager() = CustomTabBrowsingModeManager()
final override fun createThemeManager() = CustomTabThemeManager()
} }

View File

@ -0,0 +1,38 @@
/* 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 org.mozilla.fenix
import android.content.Intent
import kotlinx.coroutines.ObsoleteCoroutinesApi
import mozilla.components.support.utils.toSafeIntent
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNull
import org.junit.Test
import org.junit.runner.RunWith
import org.mozilla.fenix.components.metrics.Event
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config
@ObsoleteCoroutinesApi
@RunWith(RobolectricTestRunner::class)
@Config(application = TestApplication::class)
class HomeActivityTest {
@Test
fun getIntentSource() {
val activity = HomeActivity()
val launcherIntent = Intent(Intent.ACTION_MAIN).apply {
addCategory(Intent.CATEGORY_LAUNCHER)
}.toSafeIntent()
assertEquals(Event.OpenedApp.Source.APP_ICON, activity.getIntentSource(launcherIntent))
val viewIntent = Intent(Intent.ACTION_VIEW).toSafeIntent()
assertEquals(Event.OpenedApp.Source.LINK, activity.getIntentSource(viewIntent))
val otherIntent = Intent().toSafeIntent()
assertNull(activity.getIntentSource(otherIntent))
}
}

View File

@ -0,0 +1,38 @@
/* 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 org.mozilla.fenix.customtabs
import android.content.Intent
import kotlinx.coroutines.ObsoleteCoroutinesApi
import mozilla.components.support.utils.toSafeIntent
import org.junit.Assert.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
import org.mozilla.fenix.TestApplication
import org.mozilla.fenix.components.metrics.Event
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config
@ObsoleteCoroutinesApi
@RunWith(RobolectricTestRunner::class)
@Config(application = TestApplication::class)
class CustomTabActivityTest {
@Test
fun getIntentSource() {
val activity = CustomTabActivity()
val launcherIntent = Intent(Intent.ACTION_MAIN).apply {
addCategory(Intent.CATEGORY_LAUNCHER)
}.toSafeIntent()
assertEquals(Event.OpenedApp.Source.CUSTOM_TAB, activity.getIntentSource(launcherIntent))
val viewIntent = Intent(Intent.ACTION_VIEW).toSafeIntent()
assertEquals(Event.OpenedApp.Source.CUSTOM_TAB, activity.getIntentSource(viewIntent))
val otherIntent = Intent().toSafeIntent()
assertEquals(Event.OpenedApp.Source.CUSTOM_TAB, activity.getIntentSource(otherIntent))
}
}