fix pip feature
This commit is contained in:
@@ -20,8 +20,10 @@
|
|||||||
package eu.weblibre.gecko
|
package eu.weblibre.gecko
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
import eu.weblibre.flutter_mozilla_components.HomePressDispatcher
|
||||||
import io.flutter.embedding.android.FlutterFragmentActivity
|
import io.flutter.embedding.android.FlutterFragmentActivity
|
||||||
import io.flutter.embedding.engine.FlutterEngine
|
import io.flutter.embedding.engine.FlutterEngine
|
||||||
import io.flutter.embedding.engine.FlutterEngineCache
|
import io.flutter.embedding.engine.FlutterEngineCache
|
||||||
@@ -29,9 +31,12 @@ import io.flutter.embedding.engine.FlutterJNI
|
|||||||
import io.flutter.embedding.engine.dart.DartExecutor
|
import io.flutter.embedding.engine.dart.DartExecutor
|
||||||
import io.flutter.plugin.common.MethodChannel
|
import io.flutter.plugin.common.MethodChannel
|
||||||
|
|
||||||
class MainActivity: FlutterFragmentActivity() {
|
class MainActivity : FlutterFragmentActivity() {
|
||||||
companion object {
|
companion object {
|
||||||
private const val TAG = "MainActivity"
|
private const val TAG = "MainActivity"
|
||||||
|
|
||||||
|
/** Marks the intent [checkAndExitPiP] sends to itself. */
|
||||||
|
private const val EXTRA_EXIT_PIP = "eu.weblibre.gecko.EXIT_PIP"
|
||||||
}
|
}
|
||||||
|
|
||||||
private val TRIM_MEMORY_CHANNEL = "eu.weblibre.flutter_mozilla_components/trim_memory"
|
private val TRIM_MEMORY_CHANNEL = "eu.weblibre.flutter_mozilla_components/trim_memory"
|
||||||
@@ -48,6 +53,45 @@ class MainActivity: FlutterFragmentActivity() {
|
|||||||
"cachedEngine=${engineTag(FlutterEngineCache.getInstance().get(ENGINE_ID))}")
|
"cachedEngine=${engineTag(FlutterEngineCache.getInstance().get(ENGINE_ID))}")
|
||||||
|
|
||||||
super.onCreate(null)
|
super.onCreate(null)
|
||||||
|
|
||||||
|
// Restored straight into a pinned task (e.g. the process was killed while in PiP).
|
||||||
|
checkAndExitPiP()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onNewIntent(intent: Intent) {
|
||||||
|
if (intent.hasExtra(EXTRA_EXIT_PIP)) {
|
||||||
|
// Our own intent from checkAndExitPiP, delivered back here because
|
||||||
|
// FLAG_ACTIVITY_REORDER_TO_FRONT re-delivers to a singleTask activity. It carries
|
||||||
|
// nothing for Flutter to handle, and acting on it again would recurse.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
super.onNewIntent(intent)
|
||||||
|
|
||||||
|
checkAndExitPiP()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A picture-in-picture window cannot show a newly opened page, so an intent arriving
|
||||||
|
* while we are in PiP has to pull the task back to the foreground first.
|
||||||
|
*/
|
||||||
|
private fun checkAndExitPiP() {
|
||||||
|
if (isInPictureInPictureMode) {
|
||||||
|
moveTaskToBack(false)
|
||||||
|
startActivity(
|
||||||
|
Intent(this, MainActivity::class.java)
|
||||||
|
.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT)
|
||||||
|
.putExtra(EXTRA_EXIT_PIP, true)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onUserLeaveHint() {
|
||||||
|
if (HomePressDispatcher.onUserLeaveHint(this)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
super.onUserLeaveHint()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+33
-3
@@ -25,19 +25,25 @@ import androidx.core.content.edit
|
|||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import eu.weblibre.flutter_mozilla_components.addons.WebExtensionPromptFeature
|
import eu.weblibre.flutter_mozilla_components.addons.WebExtensionPromptFeature
|
||||||
|
import eu.weblibre.flutter_mozilla_components.activities.ExternalAppBrowserActivity
|
||||||
import eu.weblibre.flutter_mozilla_components.databinding.FragmentBrowserBinding
|
import eu.weblibre.flutter_mozilla_components.databinding.FragmentBrowserBinding
|
||||||
import eu.weblibre.flutter_mozilla_components.ext.EventSequence
|
import eu.weblibre.flutter_mozilla_components.ext.EventSequence
|
||||||
import eu.weblibre.flutter_mozilla_components.ext.getPreferenceKey
|
import eu.weblibre.flutter_mozilla_components.ext.getPreferenceKey
|
||||||
import eu.weblibre.flutter_mozilla_components.ext.toPigeonDownloadState
|
import eu.weblibre.flutter_mozilla_components.ext.toPigeonDownloadState
|
||||||
import eu.weblibre.flutter_mozilla_components.feature.BrowserHandlingScrollFeature
|
import eu.weblibre.flutter_mozilla_components.feature.BrowserHandlingScrollFeature
|
||||||
|
import eu.weblibre.flutter_mozilla_components.feature.GestureAwareSwipeRefreshFeature
|
||||||
import eu.weblibre.flutter_mozilla_components.feature.KeyboardVisibilityFeature
|
import eu.weblibre.flutter_mozilla_components.feature.KeyboardVisibilityFeature
|
||||||
import eu.weblibre.flutter_mozilla_components.feature.ReadabilityExtractFeature
|
import eu.weblibre.flutter_mozilla_components.feature.ReadabilityExtractFeature
|
||||||
import eu.weblibre.flutter_mozilla_components.feature.WebExtensionToolbarFeature
|
import eu.weblibre.flutter_mozilla_components.feature.WebExtensionToolbarFeature
|
||||||
import eu.weblibre.flutter_mozilla_components.integration.ReaderViewIntegration
|
import eu.weblibre.flutter_mozilla_components.integration.ReaderViewIntegration
|
||||||
import eu.weblibre.flutter_mozilla_components.activities.ExternalAppBrowserActivity
|
|
||||||
import eu.weblibre.flutter_mozilla_components.services.DownloadService
|
import eu.weblibre.flutter_mozilla_components.services.DownloadService
|
||||||
import io.flutter.Log
|
import io.flutter.Log
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.flow.distinctUntilChangedBy
|
||||||
|
import kotlinx.coroutines.flow.mapNotNull
|
||||||
import mozilla.components.browser.state.selector.findCustomTabOrSelectedTab
|
import mozilla.components.browser.state.selector.findCustomTabOrSelectedTab
|
||||||
|
import mozilla.components.browser.state.selector.findTabOrCustomTabOrSelectedTab
|
||||||
|
import mozilla.components.browser.state.state.SessionState
|
||||||
import mozilla.components.browser.state.state.WebExtensionState
|
import mozilla.components.browser.state.state.WebExtensionState
|
||||||
import mozilla.components.browser.thumbnails.BrowserThumbnails
|
import mozilla.components.browser.thumbnails.BrowserThumbnails
|
||||||
import mozilla.components.concept.engine.EngineView
|
import mozilla.components.concept.engine.EngineView
|
||||||
@@ -55,12 +61,12 @@ import mozilla.components.feature.prompts.file.AndroidPhotoPicker
|
|||||||
import mozilla.components.feature.session.FullScreenFeature
|
import mozilla.components.feature.session.FullScreenFeature
|
||||||
import mozilla.components.feature.session.PictureInPictureFeature
|
import mozilla.components.feature.session.PictureInPictureFeature
|
||||||
import mozilla.components.feature.session.SessionFeature
|
import mozilla.components.feature.session.SessionFeature
|
||||||
import eu.weblibre.flutter_mozilla_components.feature.GestureAwareSwipeRefreshFeature
|
|
||||||
import mozilla.components.feature.sitepermissions.SitePermissionsFeature
|
import mozilla.components.feature.sitepermissions.SitePermissionsFeature
|
||||||
import mozilla.components.feature.sitepermissions.SitePermissionsRules
|
import mozilla.components.feature.sitepermissions.SitePermissionsRules
|
||||||
import mozilla.components.feature.sitepermissions.SitePermissionsRules.AutoplayAction
|
import mozilla.components.feature.sitepermissions.SitePermissionsRules.AutoplayAction
|
||||||
import mozilla.components.feature.tabs.WindowFeature
|
import mozilla.components.feature.tabs.WindowFeature
|
||||||
import mozilla.components.feature.webauthn.WebAuthnFeature
|
import mozilla.components.feature.webauthn.WebAuthnFeature
|
||||||
|
import mozilla.components.lib.state.ext.flowScoped
|
||||||
import mozilla.components.support.base.feature.ActivityResultHandler
|
import mozilla.components.support.base.feature.ActivityResultHandler
|
||||||
import mozilla.components.support.base.feature.PermissionsFeature
|
import mozilla.components.support.base.feature.PermissionsFeature
|
||||||
import mozilla.components.support.base.feature.UserInteractionHandler
|
import mozilla.components.support.base.feature.UserInteractionHandler
|
||||||
@@ -471,6 +477,12 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Activit
|
|||||||
view = binding.root,
|
view = binding.root,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
components.core.store.flowScoped(viewLifecycleOwner, Dispatchers.Main) { flow ->
|
||||||
|
flow.mapNotNull { state -> state.findTabOrCustomTabOrSelectedTab(sessionId) }
|
||||||
|
.distinctUntilChangedBy { tab -> tab.content.pictureInPictureEnabled }
|
||||||
|
.collect { tab -> pipModeChanged(tab) }
|
||||||
|
}
|
||||||
|
|
||||||
mediaSessionFullscreenFeature.set(
|
mediaSessionFullscreenFeature.set(
|
||||||
feature = MediaSessionFullscreenFeature(
|
feature = MediaSessionFullscreenFeature(
|
||||||
requireActivity(),
|
requireActivity(),
|
||||||
@@ -644,8 +656,12 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Activit
|
|||||||
|
|
||||||
override fun onPictureInPictureModeChanged(enabled: Boolean) {
|
override fun onPictureInPictureModeChanged(enabled: Boolean) {
|
||||||
pictureInPictureFeature?.onPictureInPictureModeChanged(enabled)
|
pictureInPictureFeature?.onPictureInPictureModeChanged(enabled)
|
||||||
if (lifecycle.currentState == androidx.lifecycle.Lifecycle.State.CREATED) {
|
}
|
||||||
|
|
||||||
|
private fun pipModeChanged(session: SessionState) {
|
||||||
|
if (!session.content.pictureInPictureEnabled && session.content.fullScreen && isAdded) {
|
||||||
onBackPressed()
|
onBackPressed()
|
||||||
|
fullScreenChanged(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -691,6 +707,17 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Activit
|
|||||||
keyboardVisibilityFeature?.checkKeyboardState()
|
keyboardVisibilityFeature?.checkKeyboardState()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@CallSuper
|
||||||
|
override fun onStop() {
|
||||||
|
super.onStop()
|
||||||
|
|
||||||
|
components.core.store.state.findTabOrCustomTabOrSelectedTab(sessionId)?.let { session ->
|
||||||
|
if (!session.content.pictureInPictureEnabled && fullScreenFeature.onBackPressed()) {
|
||||||
|
fullScreenChanged(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onDestroyView() {
|
override fun onDestroyView() {
|
||||||
super.onDestroyView()
|
super.onDestroyView()
|
||||||
|
|
||||||
@@ -702,6 +729,9 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Activit
|
|||||||
browserHandlingScrollFeature?.stop()
|
browserHandlingScrollFeature?.stop()
|
||||||
browserHandlingScrollFeature = null
|
browserHandlingScrollFeature = null
|
||||||
|
|
||||||
|
// Holds the Activity, so it must not outlive the view it was created with.
|
||||||
|
pictureInPictureFeature = null
|
||||||
|
|
||||||
GlobalComponents.onPullToRefreshEnabledChanged = null
|
GlobalComponents.onPullToRefreshEnabledChanged = null
|
||||||
GlobalComponents.onScreenshotProtectionEnabledChanged = null
|
GlobalComponents.onScreenshotProtectionEnabledChanged = null
|
||||||
val engineView = fragmentEngineView
|
val engineView = fragmentEngineView
|
||||||
|
|||||||
+47
@@ -0,0 +1,47 @@
|
|||||||
|
/*
|
||||||
|
* 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 https://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package eu.weblibre.flutter_mozilla_components
|
||||||
|
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.fragment.app.FragmentActivity
|
||||||
|
import mozilla.components.support.base.feature.UserInteractionHandler
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dispatches "the user is leaving the app" to the browser fragments so they can enter
|
||||||
|
* picture-in-picture.
|
||||||
|
*
|
||||||
|
* Lives in this package because Mozilla Components is an `implementation` dependency, so
|
||||||
|
* [UserInteractionHandler] is not on the app module's compile classpath.
|
||||||
|
*/
|
||||||
|
object HomePressDispatcher {
|
||||||
|
/**
|
||||||
|
* Returns true if a fragment handled the home press (i.e. entered picture-in-picture),
|
||||||
|
* in which case the caller must not run the default `onUserLeaveHint` behaviour.
|
||||||
|
*/
|
||||||
|
fun onUserLeaveHint(activity: FragmentActivity): Boolean {
|
||||||
|
// Runtime permission prompts trigger onUserLeaveHint too. That is not the user
|
||||||
|
// leaving, so it must not put a playing fullscreen video into picture-in-picture.
|
||||||
|
if (GlobalComponents.components?.notificationsDelegate?.isRequestingPermission == true) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return activity.supportFragmentManager.fragments.any(::dispatch)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun dispatch(fragment: Fragment): Boolean {
|
||||||
|
// A hidden or paused fragment must not be able to grab picture-in-picture.
|
||||||
|
if (!fragment.isResumed) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fragment is UserInteractionHandler && fragment.onHomePressed()) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return fragment.childFragmentManager.fragments.any(::dispatch)
|
||||||
|
}
|
||||||
|
}
|
||||||
+9
-8
@@ -16,6 +16,7 @@ import androidx.core.view.WindowCompat
|
|||||||
import eu.weblibre.flutter_mozilla_components.ColorSchemePreference
|
import eu.weblibre.flutter_mozilla_components.ColorSchemePreference
|
||||||
import eu.weblibre.flutter_mozilla_components.ExternalAppBrowserFragment
|
import eu.weblibre.flutter_mozilla_components.ExternalAppBrowserFragment
|
||||||
import eu.weblibre.flutter_mozilla_components.GlobalComponents
|
import eu.weblibre.flutter_mozilla_components.GlobalComponents
|
||||||
|
import eu.weblibre.flutter_mozilla_components.HomePressDispatcher
|
||||||
import eu.weblibre.flutter_mozilla_components.PwaConstants
|
import eu.weblibre.flutter_mozilla_components.PwaConstants
|
||||||
import eu.weblibre.flutter_mozilla_components.PwaSessionCreator
|
import eu.weblibre.flutter_mozilla_components.PwaSessionCreator
|
||||||
import eu.weblibre.flutter_mozilla_components.R
|
import eu.weblibre.flutter_mozilla_components.R
|
||||||
@@ -122,6 +123,14 @@ open class ExternalAppBrowserActivity : AppCompatActivity() {
|
|||||||
showFragment(sessionId)
|
showFragment(sessionId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onUserLeaveHint() {
|
||||||
|
if (HomePressDispatcher.onUserLeaveHint(this)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
super.onUserLeaveHint()
|
||||||
|
}
|
||||||
|
|
||||||
private fun waitForComponents(sessionId: String) {
|
private fun waitForComponents(sessionId: String) {
|
||||||
coroutineScope.launch {
|
coroutineScope.launch {
|
||||||
var elapsedMs = 0L
|
var elapsedMs = 0L
|
||||||
@@ -271,12 +280,4 @@ open class ExternalAppBrowserActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPictureInPictureModeChanged(isInPictureInPictureMode: Boolean) {
|
|
||||||
super.onPictureInPictureModeChanged(isInPictureInPictureMode)
|
|
||||||
val fragment = supportFragmentManager.findFragmentById(R.id.container)
|
|
||||||
if (fragment is ExternalAppBrowserFragment) {
|
|
||||||
fragment.onPictureInPictureModeChanged(isInPictureInPictureMode)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user