2020-03-27 20:36:02 +01:00
/ *
2020-04-19 23:04:05 +02:00
* SPDX - License - Identifier : MPL - 2.0
2020-03-27 20:36:02 +01:00
* Copyright © 2020 Skyline Team and Contributors ( https : //github.com/skyline-emu/)
* /
2019-12-02 14:39:08 +01:00
package emu.skyline
2020-04-22 19:02:27 +02:00
import android.annotation.SuppressLint
2023-02-15 05:31:09 +01:00
import android.app.PendingIntent
2023-02-14 16:08:18 +01:00
import android.app.PictureInPictureParams
2023-02-15 05:31:09 +01:00
import android.app.RemoteAction
import android.content.BroadcastReceiver
2020-09-05 01:06:07 +02:00
import android.content.Context
2020-02-11 07:34:22 +01:00
import android.content.Intent
2023-02-15 05:31:09 +01:00
import android.content.IntentFilter
2021-03-03 21:35:24 +01:00
import android.content.res.AssetManager
2023-02-14 16:08:18 +01:00
import android.content.res.Configuration
2020-10-03 12:09:35 +02:00
import android.graphics.PointF
2023-02-15 05:31:09 +01:00
import android.graphics.drawable.Icon
2022-06-07 21:44:30 +02:00
import android.hardware.display.DisplayManager
2020-09-05 01:06:07 +02:00
import android.os.*
2019-12-02 14:39:08 +01:00
import android.util.Log
2022-01-12 16:24:29 +01:00
import android.util.Rational
2020-07-06 22:47:23 +02:00
import android.view.*
2023-01-22 15:42:08 +01:00
import androidx.activity.OnBackPressedCallback
2019-12-02 14:39:08 +01:00
import androidx.appcompat.app.AppCompatActivity
2022-06-07 21:44:30 +02:00
import androidx.core.content.getSystemService
2020-10-05 12:56:38 +02:00
import androidx.core.view.isGone
2020-10-03 12:09:35 +02:00
import androidx.core.view.isInvisible
2022-03-28 10:58:10 +02:00
import androidx.core.view.updateMargins
2022-06-28 09:25:01 +02:00
import androidx.fragment.app.FragmentTransaction
import com.google.android.material.dialog.MaterialAlertDialogBuilder
2021-01-31 22:11:26 +01:00
import dagger.hilt.android.AndroidEntryPoint
2023-02-21 14:06:20 +01:00
import emu.skyline.BuildConfig
2022-06-28 09:25:01 +02:00
import emu.skyline.applet.swkbd.SoftwareKeyboardConfig
import emu.skyline.applet.swkbd.SoftwareKeyboardDialog
2023-02-23 01:02:53 +01:00
import emu.skyline.data.AppItem
2023-02-23 01:14:05 +01:00
import emu.skyline.data.AppItemTag
2021-01-31 22:11:26 +01:00
import emu.skyline.databinding.EmuActivityBinding
2020-08-15 15:51:23 +02:00
import emu.skyline.input.*
2023-02-23 01:02:53 +01:00
import emu.skyline.loader.RomFile
2020-04-03 13:47:32 +02:00
import emu.skyline.loader.getRomFormat
2023-02-22 20:31:07 +01:00
import emu.skyline.settings.AppSettings
import emu.skyline.settings.EmulationSettings
import emu.skyline.settings.NativeSettings
2022-06-28 09:25:01 +02:00
import emu.skyline.utils.ByteBufferSerializable
2022-07-30 17:29:40 +02:00
import emu.skyline.utils.GpuDriverHelper
2023-03-09 21:29:38 +01:00
import emu.skyline.utils.serializable
2022-06-28 09:25:01 +02:00
import java.nio.ByteBuffer
import java.nio.ByteOrder
import java.util.concurrent.FutureTask
2021-01-31 22:11:26 +01:00
import javax.inject.Inject
2020-04-26 16:32:24 +02:00
import kotlin.math.abs
2019-12-02 14:39:08 +01:00
2023-03-09 21:24:54 +01:00
private const val ActionPause = " ${BuildConfig.APPLICATION_ID} .ACTION_EMULATOR_PAUSE "
private const val ActionMute = " ${BuildConfig.APPLICATION_ID} .ACTION_EMULATOR_MUTE "
2021-01-31 22:11:26 +01:00
@AndroidEntryPoint
2022-06-07 21:44:30 +02:00
class EmulationActivity : AppCompatActivity ( ) , SurfaceHolder . Callback , View . OnTouchListener , DisplayManager . DisplayListener {
2020-09-14 15:53:40 +02:00
companion object {
2020-10-03 12:09:35 +02:00
private val Tag = EmulationActivity :: class . java . simpleName
2022-10-28 12:15:48 +02:00
const val ReturnToMainTag = " returnToMain "
2021-10-13 19:08:48 +02:00
/ * *
* The Kotlin thread on which emulation code executes
* /
private var emulationThread : Thread ? = null
2020-09-14 15:53:40 +02:00
}
2021-01-31 22:11:26 +01:00
private val binding by lazy { EmuActivityBinding . inflate ( layoutInflater ) }
2023-02-23 01:02:53 +01:00
/ * *
* The [ AppItem ] of the app that is being emulated
* /
lateinit var item : AppItem
2020-09-05 01:06:07 +02:00
/ * *
2021-02-07 20:42:03 +01:00
* A map of [ Vibrator ] s that correspond to [ InputManager . controllers ]
2020-09-05 01:06:07 +02:00
* /
private var vibrators = HashMap < Int , Vibrator > ( )
2020-04-03 13:47:32 +02:00
/ * *
2021-10-13 19:08:48 +02:00
* If the emulation thread should call [ returnToMain ] or not
2020-04-03 13:47:32 +02:00
* /
2020-08-15 15:51:23 +02:00
@Volatile
2020-04-24 13:39:13 +02:00
private var shouldFinish : Boolean = true
2019-12-02 14:39:08 +01:00
2020-04-03 13:47:32 +02:00
/ * *
2021-10-29 17:50:04 +02:00
* If the activity should return to [ MainActivity ] or just call [ finishAffinity ]
2020-04-03 13:47:32 +02:00
* /
2023-02-15 05:31:09 +01:00
private var returnToMain : Boolean = false
2020-04-03 13:47:32 +02:00
2022-03-07 14:47:11 +01:00
/ * *
* The desired refresh rate to present at in Hz
* /
2023-02-15 05:31:09 +01:00
private var desiredRefreshRate = 60f
2023-02-15 20:11:31 +01:00
private var isEmulatorPaused = false
2023-02-15 05:31:09 +01:00
private lateinit var pictureInPictureParamsBuilder : PictureInPictureParams . Builder
2022-03-07 14:47:11 +01:00
2021-02-07 20:42:03 +01:00
@Inject
2023-02-22 20:31:07 +01:00
lateinit var appSettings : AppSettings
lateinit var emulationSettings : EmulationSettings
2020-10-04 22:29:50 +02:00
2021-01-31 22:11:26 +01:00
@Inject
lateinit var inputManager : InputManager
2022-10-28 12:15:48 +02:00
lateinit var inputHandler : InputHandler
2023-02-15 18:24:38 +01:00
private var gameSurface : Surface ? = null
2020-04-03 13:47:32 +02:00
/ * *
* This is the entry point into the emulation code for libskyline
*
* @param romUri The URI of the ROM as a string , used to print out in the logs
* @param romType The type of the ROM as an enum value
* @param romFd The file descriptor of the ROM object
2022-07-12 12:22:10 +02:00
* @param nativeSettings The settings to be used by libskyline
2022-04-06 07:48:26 +02:00
* @param publicAppFilesPath The full path to the public app files directory
* @param privateAppFilesPath The full path to the private app files directory
2021-12-08 23:08:55 +01:00
* @param nativeLibraryPath The full path to the app native library directory
2021-03-03 21:35:24 +01:00
* @param assetManager The asset manager used for accessing app assets
2020-04-03 13:47:32 +02:00
* /
2022-07-12 12:22:10 +02:00
private external fun executeApplication ( romUri : String , romType : Int , romFd : Int , nativeSettings : NativeSettings , publicAppFilesPath : String , privateAppFilesPath : String , nativeLibraryPath : String , assetManager : AssetManager )
2020-04-03 13:47:32 +02:00
/ * *
2021-10-13 19:08:48 +02:00
* @param join If the function should only return after all the threads join or immediately
* @return If it successfully caused [ emulationThread ] to gracefully stop or do so asynchronously when not joined
2020-04-03 13:47:32 +02:00
* /
2021-10-13 19:08:48 +02:00
private external fun stopEmulation ( join : Boolean ) : Boolean
2020-04-03 13:47:32 +02:00
/ * *
* This sets the surface object in libskyline to the provided value , emulation is halted if set to null
*
* @param surface The value to set surface to
2020-11-18 20:47:09 +01:00
* @return If the value was successfully set
2020-04-03 13:47:32 +02:00
* /
2020-11-18 20:47:09 +01:00
private external fun setSurface ( surface : Surface ? ) : Boolean
2019-12-02 14:39:08 +01:00
2021-10-13 19:08:48 +02:00
/ * *
* @param play If the audio should be playing or be stopped till it is resumed by calling this again
* /
private external fun changeAudioStatus ( play : Boolean )
2020-11-18 20:47:09 +01:00
var fps : Int = 0
2021-06-27 03:26:16 +02:00
var averageFrametime : Float = 0.0f
var averageFrametimeDeviation : Float = 0.0f
2020-04-18 02:16:09 +02:00
/ * *
2021-10-13 19:08:48 +02:00
* Writes the current performance statistics into [ fps ] , [ averageFrametime ] and [ averageFrametimeDeviation ] fields
2020-04-18 02:16:09 +02:00
* /
2020-11-18 20:47:09 +01:00
private external fun updatePerformanceStatistics ( )
2020-04-18 02:16:09 +02:00
2020-04-26 16:32:24 +02:00
/ * *
2022-10-28 12:15:48 +02:00
* @see [ InputHandler . initializeControllers ]
2020-08-15 15:51:23 +02:00
* /
2020-10-03 12:09:35 +02:00
@Suppress ( " unused " )
2020-08-15 15:51:23 +02:00
private fun initializeControllers ( ) {
2022-10-28 12:15:48 +02:00
inputHandler . initializeControllers ( )
2022-11-03 16:22:49 +01:00
inputHandler . initialiseMotionSensors ( this )
2020-08-15 15:51:23 +02:00
}
2020-04-26 01:34:35 +02:00
2022-06-07 21:44:30 +02:00
/ * *
* Forces a 60 Hz refresh rate for the primary display when [ enable ] is true , otherwise selects the highest available refresh rate
* /
private fun force60HzRefreshRate ( enable : Boolean ) {
// Hack for MIUI devices since they don't support the standard Android APIs
try {
val setFpsIntent = Intent ( " com.miui.powerkeeper.SET_ACTIVITY_FPS " )
setFpsIntent . putExtra ( " package_name " , " skyline.emu " )
setFpsIntent . putExtra ( " isEnter " , enable )
sendBroadcast ( setFpsIntent )
} catch ( _ : Exception ) {
}
@Suppress ( " DEPRECATION " ) val display = if ( Build . VERSION . SDK _INT >= Build . VERSION_CODES . R ) display !! else windowManager . defaultDisplay
if ( enable )
display ?. supportedModes ?. minByOrNull { abs ( it . refreshRate - 60f ) } ?. let { window . attributes . preferredDisplayModeId = it . modeId }
else
display ?. supportedModes ?. maxByOrNull { it . refreshRate } ?. let { window . attributes . preferredDisplayModeId = it . modeId }
}
2020-04-03 13:47:32 +02:00
/ * *
2021-10-13 19:08:48 +02:00
* Return from emulation to either [ MainActivity ] or the activity on the back stack
* /
2023-02-14 16:08:18 +01:00
@SuppressWarnings ( " WeakerAccess " )
2021-10-13 19:08:48 +02:00
fun returnFromEmulation ( ) {
if ( shouldFinish ) {
runOnUiThread {
if ( shouldFinish ) {
shouldFinish = false
if ( returnToMain )
startActivity ( Intent ( applicationContext , MainActivity :: class . java ) . setFlags ( Intent . FLAG _ACTIVITY _CLEAR _TOP ) )
2022-10-21 12:41:36 +02:00
Process . killProcess ( Process . myPid ( ) )
2021-10-13 19:08:48 +02:00
}
}
}
}
/ * *
* @note Any caller has to handle the application potentially being restarted with the supplied intent
2020-04-03 13:47:32 +02:00
* /
2021-10-13 19:08:48 +02:00
private fun executeApplication ( intent : Intent ) {
if ( emulationThread ?. isAlive == true ) {
shouldFinish = false
if ( stopEmulation ( false ) )
emulationThread !! . join ( 250 )
if ( emulationThread !! . isAlive ) {
2021-10-29 17:50:04 +02:00
finishAffinity ( )
2021-10-13 19:08:48 +02:00
startActivity ( intent )
Runtime . getRuntime ( ) . exit ( 0 )
}
}
shouldFinish = true
returnToMain = intent . getBooleanExtra ( ReturnToMainTag , false )
2023-02-23 01:02:53 +01:00
val rom = item . uri
val romType = item . format . ordinal
2022-10-28 12:15:48 +02:00
@SuppressLint ( " Recycle " )
2020-09-29 14:46:17 +02:00
val romFd = contentResolver . openFileDescriptor ( rom , " r " ) !!
2020-04-03 13:47:32 +02:00
2022-07-30 17:29:40 +02:00
GpuDriverHelper . ensureFileRedirectDir ( this )
2020-04-03 13:47:32 +02:00
emulationThread = Thread {
2023-02-22 20:31:07 +01:00
executeApplication ( rom . toString ( ) , romType , romFd . detachFd ( ) , NativeSettings ( this , emulationSettings ) , applicationContext . getPublicFilesDir ( ) . canonicalPath + " / " , applicationContext . filesDir . canonicalPath + " / " , applicationInfo . nativeLibraryDir + " / " , assets )
2021-10-13 19:08:48 +02:00
returnFromEmulation ( )
2020-02-11 07:34:22 +01:00
}
2020-04-03 13:47:32 +02:00
2021-10-13 19:08:48 +02:00
emulationThread !! . start ( )
}
2023-02-23 01:02:53 +01:00
/ * *
* Populates the [ item ] member with data from the intent
* /
private fun populateAppItem ( ) {
2023-03-09 21:29:38 +01:00
val intentItem = intent . serializable ( AppItemTag ) as AppItem ?
2023-02-23 01:02:53 +01:00
if ( intentItem != null ) {
item = intentItem
return
}
// The intent did not contain an app item, fall back to the data URI
val uri = intent . data !!
val romFormat = getRomFormat ( uri , contentResolver )
val romFile = RomFile ( this , romFormat , uri , EmulationSettings . global . systemLanguage )
item = AppItem ( romFile . takeIf { it . valid } !! . appEntry )
}
2021-01-31 22:11:26 +01:00
@SuppressLint ( " SetTextI18n " , " ClickableViewAccessibility " )
2020-04-24 13:39:13 +02:00
override fun onCreate ( savedInstanceState : Bundle ? ) {
2019-12-02 14:39:08 +01:00
super . onCreate ( savedInstanceState )
2023-02-23 01:02:53 +01:00
populateAppItem ( )
emulationSettings = EmulationSettings . forEmulation ( item . titleId ?: item . key ( ) )
2023-02-22 20:31:07 +01:00
requestedOrientation = emulationSettings . orientation
2022-03-28 10:58:10 +02:00
window . attributes . layoutInDisplayCutoutMode = WindowManager . LayoutParams . LAYOUT _IN _DISPLAY _CUTOUT _MODE _SHORT _EDGES
2023-02-22 20:31:07 +01:00
inputHandler = InputHandler ( inputManager , emulationSettings )
2021-01-31 22:11:26 +01:00
setContentView ( binding . root )
2020-04-18 02:16:09 +02:00
2020-07-06 22:47:23 +02:00
if ( Build . VERSION . SDK _INT >= Build . VERSION_CODES . R ) {
2022-03-28 10:58:10 +02:00
// Android might not allow child views to overlap the system bars
// Override this behavior and force content to extend into the cutout area
window . setDecorFitsSystemWindows ( false )
2022-01-11 15:12:09 +01:00
window . insetsController ?. let {
it . systemBarsBehavior = WindowInsetsController . BEHAVIOR _SHOW _TRANSIENT _BARS _BY _SWIPE
it . hide ( WindowInsets . Type . systemBars ( ) )
}
2020-07-06 22:47:23 +02:00
}
2020-04-03 13:47:32 +02:00
2023-02-22 20:31:07 +01:00
if ( emulationSettings . respectDisplayCutout ) {
2022-03-28 10:58:10 +02:00
binding . perfStats . setOnApplyWindowInsetsListener ( insetsOrMarginHandler )
binding . onScreenControllerToggle . setOnApplyWindowInsetsListener ( insetsOrMarginHandler )
}
2023-02-23 13:57:40 +01:00
pictureInPictureParamsBuilder = getPictureInPictureBuilder ( )
2023-02-15 05:31:09 +01:00
setPictureInPictureParams ( pictureInPictureParamsBuilder . build ( ) )
2023-02-14 16:08:18 +01:00
2021-01-31 22:11:26 +01:00
binding . gameView . holder . addCallback ( this )
2020-04-03 13:47:32 +02:00
2022-01-12 16:24:29 +01:00
binding . gameView . setAspectRatio (
2023-02-22 20:31:07 +01:00
when ( emulationSettings . aspectRatio ) {
2022-01-12 16:24:29 +01:00
0 -> Rational ( 16 , 9 )
1 -> Rational ( 21 , 9 )
else -> null
}
)
2023-02-23 13:01:04 +01:00
if ( emulationSettings . perfStats ) {
2023-02-22 20:31:07 +01:00
if ( emulationSettings . disableFrameThrottling )
2022-07-03 07:47:41 +02:00
binding . perfStats . setTextColor ( getColor ( R . color . colorPerfStatsSecondary ) )
2021-01-31 22:11:26 +01:00
binding . perfStats . apply {
postDelayed ( object : Runnable {
override fun run ( ) {
updatePerformanceStatistics ( )
2021-06-27 03:26:16 +02:00
text = " $fps FPS \n ${"%.1f".format(averageFrametime)} ± ${"%.2f".format(averageFrametimeDeviation)} ms "
2021-01-31 22:11:26 +01:00
postDelayed ( this , 250 )
}
} , 250 )
}
2020-04-18 02:16:09 +02:00
}
2023-02-22 20:31:07 +01:00
force60HzRefreshRate ( ! emulationSettings . maxRefreshRate )
2022-06-07 21:44:30 +02:00
getSystemService < DisplayManager > ( ) ?. registerDisplayListener ( this , null )
2020-08-15 15:51:23 +02:00
2021-01-31 22:11:26 +01:00
binding . gameView . setOnTouchListener ( this )
2020-09-07 18:39:05 +02:00
2020-10-03 12:09:35 +02:00
// Hide on screen controls when first controller is not set
2021-01-31 22:11:26 +01:00
binding . onScreenControllerView . apply {
2022-10-28 12:15:48 +02:00
controllerType = inputHandler . getFirstControllerType ( )
2023-02-22 20:31:07 +01:00
isGone = controllerType == ControllerType . None || ! appSettings . onScreenControl
2021-01-31 22:11:26 +01:00
setOnButtonStateChangedListener ( :: onButtonStateChanged )
setOnStickStateChangedListener ( :: onStickStateChanged )
2023-02-22 20:31:07 +01:00
hapticFeedback = appSettings . onScreenControl && appSettings . onScreenControlFeedback
recenterSticks = appSettings . onScreenControlRecenterSticks
2021-01-31 22:11:26 +01:00
}
binding . onScreenControllerToggle . apply {
isGone = binding . onScreenControllerView . isGone
setOnClickListener { binding . onScreenControllerView . isInvisible = ! binding . onScreenControllerView . isInvisible }
2020-10-05 12:56:38 +02:00
}
2023-02-15 20:11:31 +01:00
binding . onScreenPauseToggle . apply {
isGone = binding . onScreenControllerView . isGone
setOnClickListener {
if ( isEmulatorPaused ) {
resumeEmulator ( )
binding . onScreenPauseToggle . setImageResource ( R . drawable . ic _pause )
} else {
pauseEmulator ( )
binding . onScreenPauseToggle . setImageResource ( R . drawable . ic _play )
}
}
}
2021-10-13 19:08:48 +02:00
executeApplication ( intent !! )
}
2023-02-15 20:11:31 +01:00
@SuppressWarnings ( " WeakerAccess " )
2023-02-15 19:18:12 +01:00
fun pauseEmulator ( ) {
2023-02-15 20:11:31 +01:00
if ( isEmulatorPaused ) return
2023-02-15 19:18:12 +01:00
setSurface ( null )
changeAudioStatus ( false )
2023-02-15 20:11:31 +01:00
isEmulatorPaused = true
2023-02-15 19:18:12 +01:00
}
2023-02-15 20:11:31 +01:00
@SuppressWarnings ( " WeakerAccess " )
2023-02-15 19:18:12 +01:00
fun resumeEmulator ( ) {
2023-02-15 20:11:31 +01:00
if ( !is EmulatorPaused ) return
2023-02-15 19:18:12 +01:00
gameSurface ?. let { setSurface ( it ) }
if ( ! emulationSettings . isAudioOutputDisabled )
changeAudioStatus ( true )
2023-02-15 20:11:31 +01:00
isEmulatorPaused = false
2023-02-15 19:18:12 +01:00
}
2021-10-13 19:08:48 +02:00
override fun onPause ( ) {
super . onPause ( )
2023-02-22 20:31:07 +01:00
if ( emulationSettings . forceMaxGpuClocks )
2022-12-24 21:38:51 +01:00
GpuDriverHelper . forceMaxGpuClocks ( false )
2023-02-15 19:18:12 +01:00
pauseEmulator ( )
2020-02-11 07:34:22 +01:00
}
2023-01-22 15:42:08 +01:00
override fun onStart ( ) {
super . onStart ( )
onBackPressedDispatcher . addCallback ( object : OnBackPressedCallback ( true ) {
override fun handleOnBackPressed ( ) {
returnFromEmulation ( )
}
} )
}
2021-02-05 17:37:05 +01:00
override fun onResume ( ) {
super . onResume ( )
2023-02-15 19:18:12 +01:00
resumeEmulator ( )
2021-10-13 19:08:48 +02:00
2022-10-31 10:16:59 +01:00
if ( Build . VERSION . SDK _INT <= Build . VERSION_CODES . R ) {
2021-02-05 17:37:05 +01:00
@Suppress ( " DEPRECATION " )
window . decorView . systemUiVisibility = ( View . SYSTEM _UI _FLAG _IMMERSIVE _STICKY
or View . SYSTEM _UI _FLAG _LAYOUT _STABLE
or View . SYSTEM _UI _FLAG _LAYOUT _HIDE _NAVIGATION
or View . SYSTEM _UI _FLAG _LAYOUT _FULLSCREEN
or View . SYSTEM _UI _FLAG _HIDE _NAVIGATION
or View . SYSTEM _UI _FLAG _FULLSCREEN )
}
}
2023-02-23 13:57:40 +01:00
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 )
2023-03-09 21:24:54 +01:00
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 )
2023-02-23 13:57:40 +01:00
pictureInPictureActions . add ( pauseRemoteAction )
2023-03-09 21:24:54 +01:00
val muteIcon = Icon . createWithResource ( this , R . drawable . ic _volume _mute )
val mutePendingIntent = PendingIntent . getBroadcast ( this , R . drawable . ic _volume _mute , Intent ( ActionMute ) , pendingFlags )
val muteRemoteAction = RemoteAction ( muteIcon , getString ( R . string . mute ) , getString ( R . string . mute ) , mutePendingIntent )
pictureInPictureActions . add ( muteRemoteAction )
2023-02-23 13:57:40 +01:00
pictureInPictureParamsBuilder . setActions ( pictureInPictureActions )
if ( Build . VERSION . SDK _INT >= Build . VERSION_CODES . S )
pictureInPictureParamsBuilder . setAutoEnterEnabled ( true )
setPictureInPictureParams ( pictureInPictureParamsBuilder . build ( ) )
return pictureInPictureParamsBuilder
}
2023-03-09 21:24:54 +01:00
private var pictureInPictureReceiver = object : BroadcastReceiver ( ) {
override fun onReceive ( context : Context ? , intent : Intent ) {
if ( intent . action == ActionPause )
pauseEmulator ( )
else if ( intent . action == ActionMute )
changeAudioStatus ( false )
}
}
2023-02-14 16:08:18 +01:00
override fun onPictureInPictureModeChanged ( isInPictureInPictureMode : Boolean , newConfig : Configuration ) {
super . onPictureInPictureModeChanged ( isInPictureInPictureMode , newConfig )
if ( isInPictureInPictureMode ) {
2023-02-15 05:31:09 +01:00
2023-02-15 18:24:38 +01:00
IntentFilter ( ) . apply {
2023-03-09 21:24:54 +01:00
addAction ( ActionPause )
addAction ( ActionMute )
2023-02-15 18:24:38 +01:00
} . also {
registerReceiver ( pictureInPictureReceiver , it )
2023-02-15 05:31:09 +01:00
}
2023-02-14 16:08:18 +01:00
binding . onScreenControllerView . isGone = true
binding . onScreenControllerToggle . isGone = true
2023-02-15 20:11:31 +01:00
binding . onScreenPauseToggle . isGone = true
2023-02-14 16:08:18 +01:00
} else {
2023-02-15 18:24:38 +01:00
try {
2023-03-09 21:24:54 +01:00
unregisterReceiver ( pictureInPictureReceiver )
} catch ( ignored : Exception ) { }
2023-02-15 18:24:38 +01:00
2023-02-15 19:18:12 +01:00
resumeEmulator ( )
2023-02-15 08:54:17 +01:00
2023-02-14 16:08:18 +01:00
binding . onScreenControllerView . apply {
controllerType = inputHandler . getFirstControllerType ( )
isGone = controllerType == ControllerType . None || ! appSettings . onScreenControl
}
binding . onScreenControllerToggle . apply {
2023-02-15 20:11:31 +01:00
isGone = binding . onScreenControllerView . isGone
}
binding . onScreenPauseToggle . apply {
2023-02-14 16:08:18 +01:00
isGone = binding . onScreenControllerView . isGone
}
}
}
2020-04-03 13:47:32 +02:00
/ * *
2021-10-13 19:08:48 +02:00
* Stop the currently executing ROM and replace it with the one specified in the new intent
2020-04-03 13:47:32 +02:00
* /
2020-04-24 13:39:13 +02:00
override fun onNewIntent ( intent : Intent ? ) {
2021-10-13 19:08:48 +02:00
super . onNewIntent ( intent !! )
2021-10-23 20:41:38 +02:00
if ( getIntent ( ) . data != intent . data ) {
setIntent ( intent )
executeApplication ( intent )
}
2019-12-02 14:39:08 +01:00
}
2023-02-14 16:08:18 +01:00
override fun onUserLeaveHint ( ) {
2023-02-15 05:31:09 +01:00
if ( Build . VERSION . SDK _INT < Build . VERSION_CODES . S && !is InPictureInPictureMode )
enterPictureInPictureMode ( pictureInPictureParamsBuilder . build ( ) )
2023-02-14 16:08:18 +01:00
}
2019-12-02 14:39:08 +01:00
override fun onDestroy ( ) {
2020-11-03 10:44:09 +01:00
super . onDestroy ( )
2020-02-11 07:34:22 +01:00
shouldFinish = false
2020-04-03 13:47:32 +02:00
2022-06-07 21:44:30 +02:00
// Stop forcing 60Hz on exit to allow the skyline UI to run at high refresh rates
getSystemService < DisplayManager > ( ) ?. unregisterDisplayListener ( this )
2022-06-28 09:25:01 +02:00
force60HzRefreshRate ( false )
2023-02-22 20:31:07 +01:00
if ( emulationSettings . forceMaxGpuClocks )
2022-12-24 21:38:51 +01:00
GpuDriverHelper . forceMaxGpuClocks ( false )
stopEmulation ( false )
vibrators . forEach { ( _ , vibrator ) -> vibrator . cancel ( ) }
vibrators . clear ( )
2019-12-02 14:39:08 +01:00
}
2020-06-15 17:37:28 +02:00
override fun surfaceCreated ( holder : SurfaceHolder ) {
2020-09-14 15:53:40 +02:00
Log . d ( Tag , " surfaceCreated Holder: $holder " )
2022-03-07 14:47:11 +01:00
if ( Build . VERSION . SDK _INT >= Build . VERSION_CODES . R )
2022-10-28 12:15:48 +02:00
// Note: We need FRAME_RATE_COMPATIBILITY_FIXED_SOURCE as there will be a degradation of user experience with FRAME_RATE_COMPATIBILITY_DEFAULT due to game speed alterations when the frame rate doesn't match the display refresh rate
2023-02-22 20:31:07 +01:00
holder . surface . setFrameRate ( desiredRefreshRate , if ( emulationSettings . maxRefreshRate ) Surface . FRAME _RATE _COMPATIBILITY _DEFAULT else Surface . FRAME _RATE _COMPATIBILITY _FIXED _SOURCE )
2022-03-07 14:47:11 +01:00
2021-10-13 19:08:48 +02:00
while ( emulationThread !! . isAlive )
2023-02-15 18:24:38 +01:00
if ( setSurface ( holder . surface ) ) {
gameSurface = holder . surface
2020-11-18 20:47:09 +01:00
return
2023-02-15 18:24:38 +01:00
}
2019-12-02 14:39:08 +01:00
}
2020-04-03 13:47:32 +02:00
/ * *
2020-04-12 22:29:19 +02:00
* This is purely used for debugging surface changes
2020-04-03 13:47:32 +02:00
* /
2020-06-15 17:37:28 +02:00
override fun surfaceChanged ( holder : SurfaceHolder , format : Int , width : Int , height : Int ) {
2020-09-14 15:53:40 +02:00
Log . d ( Tag , " surfaceChanged Holder: $holder , Format: $format , Width: $width , Height: $height " )
2022-03-07 14:47:11 +01:00
if ( Build . VERSION . SDK _INT >= Build . VERSION_CODES . R )
2023-02-22 20:31:07 +01:00
holder . surface . setFrameRate ( desiredRefreshRate , if ( emulationSettings . maxRefreshRate ) Surface . FRAME _RATE _COMPATIBILITY _DEFAULT else Surface . FRAME _RATE _COMPATIBILITY _FIXED _SOURCE )
2019-12-02 14:39:08 +01:00
}
2020-06-15 17:37:28 +02:00
override fun surfaceDestroyed ( holder : SurfaceHolder ) {
2020-09-14 15:53:40 +02:00
Log . d ( Tag , " surfaceDestroyed Holder: $holder " )
2021-10-13 19:08:48 +02:00
while ( emulationThread !! . isAlive )
2023-02-15 18:24:38 +01:00
if ( setSurface ( null ) ) {
gameSurface = null
2020-11-18 20:47:09 +01:00
return
2023-02-15 18:24:38 +01:00
}
2019-12-02 14:39:08 +01:00
}
2020-04-26 16:32:24 +02:00
override fun dispatchKeyEvent ( event : KeyEvent ) : Boolean {
2022-10-28 12:15:48 +02:00
return if ( inputHandler . handleKeyEvent ( event ) ) true else super . dispatchKeyEvent ( event )
2020-04-26 16:32:24 +02:00
}
2022-10-28 12:15:48 +02:00
override fun dispatchGenericMotionEvent ( event : MotionEvent ) : Boolean {
return if ( inputHandler . handleMotionEvent ( event ) ) true else super . dispatchGenericMotionEvent ( event )
2020-04-26 16:32:24 +02:00
}
2020-09-05 01:06:07 +02:00
2020-09-07 18:39:05 +02:00
@SuppressLint ( " ClickableViewAccessibility " )
override fun onTouch ( view : View , event : MotionEvent ) : Boolean {
2022-10-28 12:15:48 +02:00
return inputHandler . handleTouchEvent ( view , event )
2020-09-07 18:39:05 +02:00
}
2022-10-28 12:15:48 +02:00
private fun onButtonStateChanged ( buttonId : ButtonId , state : ButtonState ) = InputHandler . setButtonState ( 0 , buttonId . value ( ) , state . state )
2020-10-03 12:09:35 +02:00
2021-04-30 21:38:13 +02:00
private fun onStickStateChanged ( stickId : StickId , position : PointF ) {
2022-10-28 12:15:48 +02:00
InputHandler . setAxisValue ( 0 , stickId . xAxis . ordinal , ( position . x * Short . MAX _VALUE ) . toInt ( ) )
InputHandler . setAxisValue ( 0 , stickId . yAxis . ordinal , ( - position . y * Short . MAX _VALUE ) . toInt ( ) ) // Y is inverted, since drawing starts from top left
2020-10-03 12:09:35 +02:00
}
2020-09-05 01:06:07 +02:00
@SuppressLint ( " WrongConstant " )
2020-10-03 12:09:35 +02:00
@Suppress ( " unused " )
2020-09-05 01:06:07 +02:00
fun vibrateDevice ( index : Int , timing : LongArray , amplitude : IntArray ) {
val vibrator = if ( vibrators [ index ] != null ) {
2021-10-31 18:51:50 +01:00
vibrators [ index ]
2020-09-05 01:06:07 +02:00
} else {
2021-01-31 22:11:26 +01:00
inputManager . controllers [ index ] !! . rumbleDeviceDescriptor ?. let {
2021-10-31 18:51:50 +01:00
if ( it == Controller . BuiltinRumbleDeviceDescriptor ) {
2021-10-26 07:15:49 +02:00
val vibrator = if ( Build . VERSION . SDK _INT >= Build . VERSION_CODES . S ) {
val vibratorManager = getSystemService ( Context . VIBRATOR _MANAGER _SERVICE ) as VibratorManager
vibratorManager . defaultVibrator
} else {
@Suppress ( " DEPRECATION " )
getSystemService ( Context . VIBRATOR _SERVICE ) as Vibrator
}
2020-09-05 01:06:07 +02:00
vibrators [ index ] = vibrator
vibrator
} else {
for ( id in InputDevice . getDeviceIds ( ) ) {
val device = InputDevice . getDevice ( id )
2021-01-31 22:11:26 +01:00
if ( device . descriptor == inputManager . controllers [ index ] !! . rumbleDeviceDescriptor ) {
2021-10-26 18:24:00 +02:00
val vibrator = if ( Build . VERSION . SDK _INT >= Build . VERSION_CODES . S ) {
2021-10-26 07:15:49 +02:00
device . vibratorManager . defaultVibrator
} else {
@Suppress ( " DEPRECATION " )
2021-10-26 18:24:00 +02:00
device . vibrator !!
2021-10-26 07:15:49 +02:00
}
2021-10-26 18:24:00 +02:00
vibrators [ index ] = vibrator
return @let vibrator
2020-09-05 01:06:07 +02:00
}
}
2021-10-31 18:51:50 +01:00
return @let null
2020-09-05 01:06:07 +02:00
}
2021-10-31 18:51:50 +01:00
}
2020-09-05 01:06:07 +02:00
}
2021-10-31 18:51:50 +01:00
vibrator ?. let {
val effect = VibrationEffect . createWaveform ( timing , amplitude , 0 )
it . vibrate ( effect )
}
2020-09-05 01:06:07 +02:00
}
2020-10-03 12:09:35 +02:00
@Suppress ( " unused " )
2020-09-05 01:06:07 +02:00
fun clearVibrationDevice ( index : Int ) {
vibrators [ index ] ?. cancel ( )
}
2021-03-28 16:35:13 +02:00
2022-06-28 09:25:01 +02:00
@Suppress ( " unused " )
fun showKeyboard ( buffer : ByteBuffer , initialText : String ) : SoftwareKeyboardDialog ? {
buffer . order ( ByteOrder . LITTLE _ENDIAN )
val config = ByteBufferSerializable . createFromByteBuffer ( SoftwareKeyboardConfig :: class , buffer ) as SoftwareKeyboardConfig
val keyboardDialog = SoftwareKeyboardDialog . newInstance ( config , initialText )
runOnUiThread {
val transaction = supportFragmentManager . beginTransaction ( )
transaction . setTransition ( FragmentTransaction . TRANSIT _FRAGMENT _OPEN )
transaction
. add ( android . R . id . content , keyboardDialog )
. addToBackStack ( null )
. commit ( )
}
return keyboardDialog
}
@Suppress ( " unused " )
fun waitForSubmitOrCancel ( dialog : SoftwareKeyboardDialog ) : Array < Any ? > {
return dialog . waitForSubmitOrCancel ( ) . let { arrayOf ( if ( it . cancelled ) 1 else 0 , it . text ) }
}
@Suppress ( " unused " )
fun closeKeyboard ( dialog : SoftwareKeyboardDialog ) {
runOnUiThread { dialog . dismiss ( ) }
}
@Suppress ( " unused " )
fun showValidationResult ( dialog : SoftwareKeyboardDialog , validationResult : Int , message : String ) : Int {
val confirm = validationResult == SoftwareKeyboardDialog . validationConfirm
var accepted = false
val validatorResult = FutureTask { return @FutureTask accepted }
runOnUiThread {
val builder = MaterialAlertDialogBuilder ( dialog . requireContext ( ) )
builder . setMessage ( message )
builder . setPositiveButton ( if ( confirm ) getString ( android . R . string . ok ) else getString ( android . R . string . cancel ) ) { _ , _ -> accepted = confirm }
if ( confirm )
builder . setNegativeButton ( getString ( android . R . string . cancel ) ) { _ , _ -> }
builder . setOnDismissListener { validatorResult . run ( ) }
builder . show ( )
}
return if ( validatorResult . get ( ) ) 0 else 1
}
2021-03-28 16:35:13 +02:00
/ * *
* @return A version code in Vulkan ' s format with 14 - bit patch + 10 - bit major and minor components
* /
@ExperimentalUnsignedTypes
@Suppress ( " unused " )
fun getVersionCode ( ) : Int {
2022-09-30 14:22:09 +02:00
val ( major , minor , patch ) = BuildConfig . VERSION_NAME . split ( '-' ) [ 0 ] . split ( '.' ) . map { it . toUInt ( ) }
2021-03-28 16:35:13 +02:00
return ( ( major shl 22 ) or ( minor shl 12 ) or ( patch ) ) . toInt ( )
}
2022-03-28 10:58:10 +02:00
2023-02-14 16:08:18 +01:00
private val insetsOrMarginHandler = View . OnApplyWindowInsetsListener { view , insets ->
2022-03-28 10:58:10 +02:00
insets . displayCutout ?. let {
val defaultHorizontalMargin = view . resources . getDimensionPixelSize ( R . dimen . onScreenItemHorizontalMargin )
val left = if ( it . safeInsetLeft == 0 ) defaultHorizontalMargin else it . safeInsetLeft
val right = if ( it . safeInsetRight == 0 ) defaultHorizontalMargin else it . safeInsetRight
val params = view . layoutParams as ViewGroup . MarginLayoutParams
params . updateMargins ( left = left , right = right )
view . layoutParams = params
}
insets
}
2022-06-07 21:44:30 +02:00
override fun onDisplayChanged ( displayId : Int ) {
2022-10-28 12:15:48 +02:00
@Suppress ( " DEPRECATION " )
2022-06-07 21:44:30 +02:00
val display = if ( Build . VERSION . SDK _INT >= Build . VERSION_CODES . R ) display !! else windowManager . defaultDisplay
if ( display . displayId == displayId )
2023-02-22 20:31:07 +01:00
force60HzRefreshRate ( ! emulationSettings . maxRefreshRate )
2022-06-07 21:44:30 +02:00
}
override fun onDisplayAdded ( displayId : Int ) { }
override fun onDisplayRemoved ( displayId : Int ) { }
2019-12-02 14:39:08 +01:00
}