android: Add Picture in Picture mode

This commit is contained in:
Abandoned Cart 2023-06-02 05:28:57 -04:00
parent dc2a0b2e50
commit fa58cbe0d2
4 changed files with 106 additions and 0 deletions

View file

@ -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>

View file

@ -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)

View file

@ -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()
}

View file

@ -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>