fix pip feature
This commit is contained in:
@@ -20,8 +20,10 @@
|
||||
package eu.weblibre.gecko
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import eu.weblibre.flutter_mozilla_components.HomePressDispatcher
|
||||
import io.flutter.embedding.android.FlutterFragmentActivity
|
||||
import io.flutter.embedding.engine.FlutterEngine
|
||||
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.plugin.common.MethodChannel
|
||||
|
||||
class MainActivity: FlutterFragmentActivity() {
|
||||
class MainActivity : FlutterFragmentActivity() {
|
||||
companion object {
|
||||
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"
|
||||
@@ -48,6 +53,45 @@ class MainActivity: FlutterFragmentActivity() {
|
||||
"cachedEngine=${engineTag(FlutterEngineCache.getInstance().get(ENGINE_ID))}")
|
||||
|
||||
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.preference.PreferenceManager
|
||||
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.ext.EventSequence
|
||||
import eu.weblibre.flutter_mozilla_components.ext.getPreferenceKey
|
||||
import eu.weblibre.flutter_mozilla_components.ext.toPigeonDownloadState
|
||||
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.ReadabilityExtractFeature
|
||||
import eu.weblibre.flutter_mozilla_components.feature.WebExtensionToolbarFeature
|
||||
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 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.findTabOrCustomTabOrSelectedTab
|
||||
import mozilla.components.browser.state.state.SessionState
|
||||
import mozilla.components.browser.state.state.WebExtensionState
|
||||
import mozilla.components.browser.thumbnails.BrowserThumbnails
|
||||
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.PictureInPictureFeature
|
||||
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.SitePermissionsRules
|
||||
import mozilla.components.feature.sitepermissions.SitePermissionsRules.AutoplayAction
|
||||
import mozilla.components.feature.tabs.WindowFeature
|
||||
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.PermissionsFeature
|
||||
import mozilla.components.support.base.feature.UserInteractionHandler
|
||||
@@ -471,6 +477,12 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Activit
|
||||
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(
|
||||
feature = MediaSessionFullscreenFeature(
|
||||
requireActivity(),
|
||||
@@ -644,8 +656,12 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Activit
|
||||
|
||||
override fun onPictureInPictureModeChanged(enabled: Boolean) {
|
||||
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()
|
||||
fullScreenChanged(false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -691,6 +707,17 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Activit
|
||||
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() {
|
||||
super.onDestroyView()
|
||||
|
||||
@@ -702,6 +729,9 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Activit
|
||||
browserHandlingScrollFeature?.stop()
|
||||
browserHandlingScrollFeature = null
|
||||
|
||||
// Holds the Activity, so it must not outlive the view it was created with.
|
||||
pictureInPictureFeature = null
|
||||
|
||||
GlobalComponents.onPullToRefreshEnabledChanged = null
|
||||
GlobalComponents.onScreenshotProtectionEnabledChanged = null
|
||||
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.ExternalAppBrowserFragment
|
||||
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.PwaSessionCreator
|
||||
import eu.weblibre.flutter_mozilla_components.R
|
||||
@@ -122,6 +123,14 @@ open class ExternalAppBrowserActivity : AppCompatActivity() {
|
||||
showFragment(sessionId)
|
||||
}
|
||||
|
||||
override fun onUserLeaveHint() {
|
||||
if (HomePressDispatcher.onUserLeaveHint(this)) {
|
||||
return
|
||||
}
|
||||
|
||||
super.onUserLeaveHint()
|
||||
}
|
||||
|
||||
private fun waitForComponents(sessionId: String) {
|
||||
coroutineScope.launch {
|
||||
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