summaryrefslogtreecommitdiffstats
path: root/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt
diff options
context:
space:
mode:
Diffstat (limited to 'src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt')
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt286
1 files changed, 286 insertions, 0 deletions
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt
new file mode 100644
index 000000000..bd71a3653
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt
@@ -0,0 +1,286 @@
+package org.yuzu.yuzu_emu.activities
+
+import android.app.Activity
+import android.content.DialogInterface
+import android.content.Intent
+import android.graphics.Rect
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.MotionEvent
+import android.view.View
+import android.view.WindowManager
+import android.widget.TextView
+import androidx.activity.OnBackPressedCallback
+import androidx.annotation.IntDef
+import androidx.appcompat.app.AppCompatActivity
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.FragmentActivity
+import androidx.fragment.app.FragmentManager
+import androidx.preference.PreferenceManager
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
+import com.google.android.material.slider.Slider
+import com.google.android.material.slider.Slider.OnChangeListener
+import org.yuzu.yuzu_emu.NativeLibrary
+import org.yuzu.yuzu_emu.R
+import org.yuzu.yuzu_emu.features.settings.model.Settings
+import org.yuzu.yuzu_emu.fragments.EmulationFragment
+import org.yuzu.yuzu_emu.fragments.MenuFragment
+import org.yuzu.yuzu_emu.utils.ControllerMappingHelper
+import kotlin.math.roundToInt
+
+open class EmulationActivity : AppCompatActivity() {
+ private var controllerMappingHelper: ControllerMappingHelper? = null
+
+ // TODO(bunnei): Disable notifications until we support app suspension.
+ //private Intent foregroundService;
+
+ var isActivityRecreated = false
+ private var selectedTitle: String? = null
+ private var path: String? = null
+ private var menuVisible = false
+ private var emulationFragment: EmulationFragment? = null
+
+ override fun onDestroy() {
+ // TODO(bunnei): Disable notifications until we support app suspension.
+ //stopService(foregroundService);
+ super.onDestroy()
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ if (savedInstanceState == null) {
+ // Get params we were passed
+ val gameToEmulate = intent
+ path = gameToEmulate.getStringExtra(EXTRA_SELECTED_GAME)
+ selectedTitle = gameToEmulate.getStringExtra(EXTRA_SELECTED_TITLE)
+ isActivityRecreated = false
+ } else {
+ isActivityRecreated = true
+ restoreState(savedInstanceState)
+ }
+ controllerMappingHelper = ControllerMappingHelper()
+
+ // Set these options now so that the SurfaceView the game renders into is the right size.
+ enableFullscreenImmersive()
+
+ setContentView(R.layout.activity_emulation)
+
+ // Find or create the EmulationFragment
+ var emulationFragment =
+ supportFragmentManager.findFragmentById(R.id.frame_emulation_fragment) as EmulationFragment?
+ if (emulationFragment == null) {
+ emulationFragment = EmulationFragment.newInstance(path)
+ supportFragmentManager.beginTransaction()
+ .add(R.id.frame_emulation_fragment, emulationFragment)
+ .commit()
+ }
+ title = selectedTitle
+
+ // Start a foreground service to prevent the app from getting killed in the background
+ // TODO(bunnei): Disable notifications until we support app suspension.
+ //foregroundService = new Intent(EmulationActivity.this, ForegroundService.class);
+ //startForegroundService(foregroundService);
+
+ onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) {
+ override fun handleOnBackPressed() {
+ toggleMenu()
+ }
+ })
+ }
+
+ override fun onSaveInstanceState(outState: Bundle) {
+ outState.putString(EXTRA_SELECTED_GAME, path)
+ outState.putString(EXTRA_SELECTED_TITLE, selectedTitle)
+ super.onSaveInstanceState(outState)
+ }
+
+ private fun restoreState(savedInstanceState: Bundle) {
+ path = savedInstanceState.getString(EXTRA_SELECTED_GAME)
+ selectedTitle = savedInstanceState.getString(EXTRA_SELECTED_TITLE)
+
+ // If an alert prompt was in progress when state was restored, retry displaying it
+ NativeLibrary.retryDisplayAlertPrompt()
+ }
+
+ private fun enableFullscreenImmersive() {
+ window.attributes.layoutInDisplayCutoutMode =
+ WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
+
+ // It would be nice to use IMMERSIVE_STICKY, but that doesn't show the toolbar.
+ window.decorView.systemUiVisibility = 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 or
+ View.SYSTEM_UI_FLAG_IMMERSIVE
+ }
+
+ fun handleMenuAction(action: Int) {
+ when (action) {
+ MENU_ACTION_EXIT -> {
+ emulationFragment!!.stopEmulation()
+ finish()
+ }
+ }
+ }
+
+ private fun editControlsPlacement() {
+ if (emulationFragment!!.isConfiguringControls) {
+ emulationFragment!!.stopConfiguringControls()
+ } else {
+ emulationFragment!!.startConfiguringControls()
+ }
+ }
+
+ private fun adjustScale() {
+ val inflater = LayoutInflater.from(this)
+ val view = inflater.inflate(R.layout.dialog_slider, null)
+ val slider = view.findViewById<Slider>(R.id.slider)
+ val textValue = view.findViewById<TextView>(R.id.text_value)
+ val units = view.findViewById<TextView>(R.id.text_units)
+
+ slider.valueTo = 150F
+ slider.value = PreferenceManager.getDefaultSharedPreferences(applicationContext)
+ .getInt(Settings.PREF_CONTROL_SCALE, 50).toFloat()
+ slider.addOnChangeListener(OnChangeListener { _, value, _ ->
+ textValue.text = value.toString()
+ setControlScale(value.toInt())
+ })
+ textValue.text = slider.value.toString()
+ units.text = "%"
+ MaterialAlertDialogBuilder(this)
+ .setTitle(R.string.emulation_control_scale)
+ .setView(view)
+ .setNegativeButton(android.R.string.cancel, null)
+ .setPositiveButton(android.R.string.ok) { _: DialogInterface?, _: Int ->
+ setControlScale(slider.value.toInt())
+ }
+ .setNeutralButton(R.string.slider_default) { _: DialogInterface?, _: Int ->
+ setControlScale(50)
+ }
+ .show()
+ }
+
+ private fun setControlScale(scale: Int) {
+ PreferenceManager.getDefaultSharedPreferences(applicationContext).edit()
+ .putInt(Settings.PREF_CONTROL_SCALE, scale)
+ .apply()
+ emulationFragment!!.refreshInputOverlay()
+ }
+
+ private fun resetOverlay() {
+ MaterialAlertDialogBuilder(this)
+ .setTitle(getString(R.string.emulation_touch_overlay_reset))
+ .setPositiveButton(android.R.string.ok) { _: DialogInterface?, _: Int -> emulationFragment!!.resetInputOverlay() }
+ .setNegativeButton(android.R.string.cancel, null)
+ .create()
+ .show()
+ }
+
+ override fun dispatchTouchEvent(event: MotionEvent): Boolean {
+ if (event.actionMasked == MotionEvent.ACTION_DOWN) {
+ var anyMenuClosed = false
+ var submenu = supportFragmentManager.findFragmentById(R.id.frame_submenu)
+ if (submenu != null && areCoordinatesOutside(submenu.view, event.x, event.y)) {
+ closeSubmenu()
+ submenu = null
+ anyMenuClosed = true
+ }
+ if (submenu == null) {
+ val menu = supportFragmentManager.findFragmentById(R.id.frame_menu)
+ if (menu != null && areCoordinatesOutside(menu.view, event.x, event.y)) {
+ closeMenu()
+ anyMenuClosed = true
+ }
+ }
+ if (anyMenuClosed) {
+ return true
+ }
+ }
+ return super.dispatchTouchEvent(event)
+ }
+
+ @Retention(AnnotationRetention.SOURCE)
+ @IntDef(
+ MENU_ACTION_EDIT_CONTROLS_PLACEMENT,
+ MENU_ACTION_TOGGLE_CONTROLS,
+ MENU_ACTION_ADJUST_SCALE,
+ MENU_ACTION_EXIT,
+ MENU_ACTION_SHOW_FPS,
+ MENU_ACTION_RESET_OVERLAY,
+ MENU_ACTION_SHOW_OVERLAY,
+ MENU_ACTION_OPEN_SETTINGS
+ )
+ annotation class MenuAction
+
+ private fun closeSubmenu(): Boolean {
+ return supportFragmentManager.popBackStackImmediate(
+ BACKSTACK_NAME_SUBMENU,
+ FragmentManager.POP_BACK_STACK_INCLUSIVE
+ )
+ }
+
+ private fun closeMenu(): Boolean {
+ menuVisible = false
+ return supportFragmentManager.popBackStackImmediate(
+ BACKSTACK_NAME_MENU,
+ FragmentManager.POP_BACK_STACK_INCLUSIVE
+ )
+ }
+
+ private fun toggleMenu() {
+ if (!closeMenu()) {
+ val fragment: Fragment = MenuFragment.newInstance()
+ supportFragmentManager.beginTransaction()
+ .setCustomAnimations(
+ R.animator.menu_slide_in_from_start,
+ R.animator.menu_slide_out_to_start,
+ R.animator.menu_slide_in_from_start,
+ R.animator.menu_slide_out_to_start
+ )
+ .add(R.id.frame_menu, fragment)
+ .addToBackStack(BACKSTACK_NAME_MENU)
+ .commit()
+ menuVisible = true
+ }
+ }
+
+ companion object {
+ private const val BACKSTACK_NAME_MENU = "menu"
+ private const val BACKSTACK_NAME_SUBMENU = "submenu"
+ const val EXTRA_SELECTED_GAME = "SelectedGame"
+ const val EXTRA_SELECTED_TITLE = "SelectedTitle"
+ const val MENU_ACTION_EDIT_CONTROLS_PLACEMENT = 0
+ const val MENU_ACTION_TOGGLE_CONTROLS = 1
+ const val MENU_ACTION_ADJUST_SCALE = 2
+ const val MENU_ACTION_EXIT = 3
+ const val MENU_ACTION_SHOW_FPS = 4
+ const val MENU_ACTION_RESET_OVERLAY = 6
+ const val MENU_ACTION_SHOW_OVERLAY = 7
+ const val MENU_ACTION_OPEN_SETTINGS = 8
+ private const val EMULATION_RUNNING_NOTIFICATION = 0x1000
+
+ @JvmStatic
+ fun launch(activity: FragmentActivity, path: String?, title: String?) {
+ val launcher = Intent(activity, EmulationActivity::class.java)
+ launcher.putExtra(EXTRA_SELECTED_GAME, path)
+ launcher.putExtra(EXTRA_SELECTED_TITLE, title)
+ activity.startActivity(launcher)
+ }
+
+ @JvmStatic
+ fun tryDismissRunningNotification(activity: Activity?) {
+ // TODO(bunnei): Disable notifications until we support app suspension.
+ //NotificationManagerCompat.from(activity).cancel(EMULATION_RUNNING_NOTIFICATION);
+ }
+
+ private fun areCoordinatesOutside(view: View?, x: Float, y: Float): Boolean {
+ if (view == null) {
+ return true
+ }
+ val viewBounds = Rect()
+ view.getGlobalVisibleRect(viewBounds)
+ return !viewBounds.contains(x.roundToInt(), y.roundToInt())
+ }
+ }
+}