android: Add Picture in Picture mode
This commit is contained in:
parent
dc2a0b2e50
commit
fa58cbe0d2
4 changed files with 106 additions and 0 deletions
|
@ -60,6 +60,8 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
|||
android:theme="@style/Theme.Yuzu.Main"
|
||||
android:launchMode="singleTop"
|
||||
android:screenOrientation="userLandscape"
|
||||
android:supportsPictureInPicture="true"
|
||||
android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|uiMode"
|
||||
android:exported="true">
|
||||
|
||||
<intent-filter>
|
||||
|
|
|
@ -4,15 +4,22 @@
|
|||
package org.yuzu.yuzu_emu.activities
|
||||
|
||||
import android.app.Activity
|
||||
import android.app.PendingIntent
|
||||
import android.app.PictureInPictureParams
|
||||
import android.app.RemoteAction
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.content.res.Configuration
|
||||
import android.graphics.Rect
|
||||
import android.graphics.drawable.Icon
|
||||
import android.hardware.Sensor
|
||||
import android.hardware.SensorEvent
|
||||
import android.hardware.SensorEventListener
|
||||
import android.hardware.SensorManager
|
||||
import android.hardware.display.DisplayManager
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.view.Display
|
||||
import android.view.InputDevice
|
||||
|
@ -60,6 +67,10 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
|
|||
private var motionTimestamp: Long = 0
|
||||
private var flipMotionOrientation: Boolean = false
|
||||
|
||||
private val actionPause = "ACTION_EMULATOR_PAUSE"
|
||||
private val actionPlay = "ACTION_EMULATOR_PLAY"
|
||||
private lateinit var pictureInPictureParamsBuilder : PictureInPictureParams.Builder
|
||||
|
||||
private lateinit var game: Game
|
||||
|
||||
private val settingsViewModel: SettingsViewModel by viewModels()
|
||||
|
@ -88,6 +99,9 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
|
|||
// Set these options now so that the SurfaceView the game renders into is the right size.
|
||||
enableFullscreenImmersive()
|
||||
|
||||
pictureInPictureParamsBuilder = getPictureInPictureBuilder()
|
||||
setPictureInPictureParams(pictureInPictureParamsBuilder.build())
|
||||
|
||||
setContentView(R.layout.activity_emulation)
|
||||
window.decorView.setBackgroundColor(getColor(android.R.color.black))
|
||||
|
||||
|
@ -161,6 +175,12 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
|
|||
stopMotionSensorListener()
|
||||
}
|
||||
|
||||
override fun onUserLeaveHint() {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S && !isInPictureInPictureMode) {
|
||||
enterPictureInPictureMode(pictureInPictureParamsBuilder.build())
|
||||
}
|
||||
}
|
||||
|
||||
override fun onNewIntent(intent: Intent) {
|
||||
super.onNewIntent(intent)
|
||||
setIntent(intent)
|
||||
|
@ -289,6 +309,61 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
|
|||
}
|
||||
}
|
||||
|
||||
private fun getPictureInPictureBuilder() : PictureInPictureParams.Builder {
|
||||
val pictureInPictureParamsBuilder = PictureInPictureParams.Builder()
|
||||
|
||||
val pictureInPictureActions : MutableList<RemoteAction> = mutableListOf()
|
||||
val pendingFlags = PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
||||
|
||||
val pauseIcon = Icon.createWithResource(this, R.drawable.ic_pause)
|
||||
val pausePendingIntent = PendingIntent.getBroadcast(this, R.drawable.ic_pause, Intent(actionPause), pendingFlags)
|
||||
val pauseRemoteAction = RemoteAction(pauseIcon, getString(R.string.pause), getString(R.string.pause), pausePendingIntent)
|
||||
pictureInPictureActions.add(pauseRemoteAction)
|
||||
|
||||
val playIcon = Icon.createWithResource(this, R.drawable.ic_play)
|
||||
val playPendingIntent = PendingIntent.getBroadcast(this, R.drawable.ic_play, Intent(actionPlay), pendingFlags)
|
||||
val playRemoteAction = RemoteAction(playIcon, getString(R.string.play), getString(R.string.play), playPendingIntent)
|
||||
pictureInPictureActions.add(playRemoteAction)
|
||||
|
||||
pictureInPictureParamsBuilder.setActions(pictureInPictureActions)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
pictureInPictureParamsBuilder.setAutoEnterEnabled(true)
|
||||
}
|
||||
|
||||
setPictureInPictureParams(pictureInPictureParamsBuilder.build())
|
||||
|
||||
return pictureInPictureParamsBuilder
|
||||
}
|
||||
|
||||
private var pictureInPictureReceiver = object : BroadcastReceiver() {
|
||||
override fun onReceive(context : Context?, intent : Intent) {
|
||||
if (intent.action == actionPause) {
|
||||
emulationFragment?.onPictureInPicturePause()
|
||||
}
|
||||
if (intent.action == actionPlay) {
|
||||
emulationFragment?.onPictureInPicturePlay()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPictureInPictureModeChanged(isInPictureInPictureMode: Boolean, newConfig: Configuration) {
|
||||
super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig)
|
||||
if (isInPictureInPictureMode) {
|
||||
IntentFilter().apply {
|
||||
addAction(actionPause)
|
||||
addAction(actionPlay)
|
||||
}.also {
|
||||
registerReceiver(pictureInPictureReceiver, it)
|
||||
}
|
||||
emulationFragment?.onPictureInPictureEnter()
|
||||
} else {
|
||||
try {
|
||||
unregisterReceiver(pictureInPictureReceiver)
|
||||
} catch (ignored : Exception) { }
|
||||
emulationFragment?.onPictureInPictureLeave()
|
||||
}
|
||||
}
|
||||
|
||||
private fun startMotionSensorListener() {
|
||||
val sensorManager = this.getSystemService(Context.SENSOR_SERVICE) as SensorManager
|
||||
val gyroSensor = sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE)
|
||||
|
|
|
@ -23,6 +23,7 @@ import androidx.core.content.res.ResourcesCompat
|
|||
import androidx.core.graphics.Insets
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.core.view.updatePadding
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.preference.PreferenceManager
|
||||
|
@ -178,6 +179,30 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
|||
super.onDetach()
|
||||
}
|
||||
|
||||
fun onPictureInPictureEnter() {
|
||||
if (EmulationMenuSettings.showOverlay) {
|
||||
binding.surfaceInputOverlay.post { binding.surfaceInputOverlay.isVisible = false }
|
||||
}
|
||||
}
|
||||
|
||||
fun onPictureInPicturePause() {
|
||||
if (!emulationState.isPaused) {
|
||||
emulationState.pause()
|
||||
}
|
||||
}
|
||||
|
||||
fun onPictureInPicturePlay() {
|
||||
if (emulationState.isPaused) {
|
||||
emulationState.run(false)
|
||||
}
|
||||
}
|
||||
|
||||
fun onPictureInPictureLeave() {
|
||||
if (EmulationMenuSettings.showOverlay) {
|
||||
binding.surfaceInputOverlay.post { binding.surfaceInputOverlay.isVisible = true }
|
||||
}
|
||||
}
|
||||
|
||||
private fun refreshInputOverlay() {
|
||||
binding.surfaceInputOverlay.refreshControls()
|
||||
}
|
||||
|
|
|
@ -356,6 +356,10 @@
|
|||
<string name="use_black_backgrounds">Black backgrounds</string>
|
||||
<string name="use_black_backgrounds_description">When using the dark theme, apply black backgrounds.</string>
|
||||
|
||||
<!-- Picture-In-Picture -->
|
||||
<string name="pause">Pause</string>
|
||||
<string name="play">Play</string>
|
||||
|
||||
<!-- Licenses screen strings -->
|
||||
<string name="licenses">Licenses</string>
|
||||
<string name="license_fidelityfx_fsr" translatable="false">FidelityFX-FSR</string>
|
||||
|
|
Loading…
Add table
Reference in a new issue