android: Convert EmulationActivity to Kotlin

This commit is contained in:
Charles Lombardo 2023-03-07 13:19:05 -05:00 committed by bunnei
parent 7cd72a7c6d
commit 39a65f8446
2 changed files with 286 additions and 347 deletions

View file

@ -1,347 +0,0 @@
package org.yuzu.yuzu_emu.activities;
import android.app.Activity;
import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.Handler;
import android.preference.PreferenceManager;
import android.view.InputDevice;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.WindowManager;
import android.widget.SeekBar;
import android.widget.TextView;
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.NotificationManagerCompat;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import androidx.fragment.app.FragmentManager;
import org.yuzu.yuzu_emu.NativeLibrary;
import org.yuzu.yuzu_emu.R;
import org.yuzu.yuzu_emu.fragments.EmulationFragment;
import org.yuzu.yuzu_emu.fragments.MenuFragment;
import org.yuzu.yuzu_emu.utils.ControllerMappingHelper;
import org.yuzu.yuzu_emu.utils.ForegroundService;
import java.lang.annotation.Retention;
import static java.lang.annotation.RetentionPolicy.SOURCE;
public final class EmulationActivity extends AppCompatActivity {
private static final String BACKSTACK_NAME_MENU = "menu";
private static final String BACKSTACK_NAME_SUBMENU = "submenu";
public static final String EXTRA_SELECTED_GAME = "SelectedGame";
public static final String EXTRA_SELECTED_TITLE = "SelectedTitle";
public static final int MENU_ACTION_EDIT_CONTROLS_PLACEMENT = 0;
public static final int MENU_ACTION_TOGGLE_CONTROLS = 1;
public static final int MENU_ACTION_ADJUST_SCALE = 2;
public static final int MENU_ACTION_EXIT = 3;
public static final int MENU_ACTION_SHOW_FPS = 4;
public static final int MENU_ACTION_RESET_OVERLAY = 6;
public static final int MENU_ACTION_SHOW_OVERLAY = 7;
public static final int MENU_ACTION_OPEN_SETTINGS = 8;
private static final int EMULATION_RUNNING_NOTIFICATION = 0x1000;
private View mDecorView;
private EmulationFragment mEmulationFragment;
private SharedPreferences mPreferences;
private ControllerMappingHelper mControllerMappingHelper;
// TODO(bunnei): Disable notifications until we support app suspension.
// private Intent foregroundService;
private boolean activityRecreated;
private String mSelectedTitle;
private String mPath;
private boolean mMenuVisible;
public static void launch(FragmentActivity activity, String path, String title) {
Intent launcher = new Intent(activity, EmulationActivity.class);
launcher.putExtra(EXTRA_SELECTED_GAME, path);
launcher.putExtra(EXTRA_SELECTED_TITLE, title);
activity.startActivity(launcher);
}
public static void tryDismissRunningNotification(Activity activity) {
// TODO(bunnei): Disable notifications until we support app suspension.
// NotificationManagerCompat.from(activity).cancel(EMULATION_RUNNING_NOTIFICATION);
}
@Override
protected void onDestroy() {
// TODO(bunnei): Disable notifications until we support app suspension.
// stopService(foregroundService);
super.onDestroy();
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState == null) {
// Get params we were passed
Intent gameToEmulate = getIntent();
mPath = gameToEmulate.getStringExtra(EXTRA_SELECTED_GAME);
mSelectedTitle = gameToEmulate.getStringExtra(EXTRA_SELECTED_TITLE);
activityRecreated = false;
} else {
activityRecreated = true;
restoreState(savedInstanceState);
}
mControllerMappingHelper = new ControllerMappingHelper();
// Get a handle to the Window containing the UI.
mDecorView = getWindow().getDecorView();
mDecorView.setOnSystemUiVisibilityChangeListener(visibility ->
{
if ((visibility & View.SYSTEM_UI_FLAG_FULLSCREEN) == 0) {
// Go back to immersive fullscreen mode in 3s
Handler handler = new Handler(getMainLooper());
handler.postDelayed(this::enableFullscreenImmersive, 3000 /* 3s */);
}
});
// Set these options now so that the SurfaceView the game renders into is the right size.
enableFullscreenImmersive();
setTheme(R.style.YuzuEmulationBase);
setContentView(R.layout.activity_emulation);
// Find or create the EmulationFragment
mEmulationFragment = (EmulationFragment) getSupportFragmentManager()
.findFragmentById(R.id.frame_emulation_fragment);
if (mEmulationFragment == null) {
mEmulationFragment = EmulationFragment.newInstance(mPath);
getSupportFragmentManager().beginTransaction()
.add(R.id.frame_emulation_fragment, mEmulationFragment)
.commit();
}
setTitle(mSelectedTitle);
mPreferences = PreferenceManager.getDefaultSharedPreferences(this);
// 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);
}
@Override
protected void onSaveInstanceState(@NonNull Bundle outState) {
outState.putString(EXTRA_SELECTED_GAME, mPath);
outState.putString(EXTRA_SELECTED_TITLE, mSelectedTitle);
super.onSaveInstanceState(outState);
}
protected void restoreState(Bundle savedInstanceState) {
mPath = savedInstanceState.getString(EXTRA_SELECTED_GAME);
mSelectedTitle = savedInstanceState.getString(EXTRA_SELECTED_TITLE);
// If an alert prompt was in progress when state was restored, retry displaying it
NativeLibrary.retryDisplayAlertPrompt();
}
@Override
public void onRestart() {
super.onRestart();
}
@Override
public void onBackPressed() {
toggleMenu();
}
private void enableFullscreenImmersive() {
getWindow().getAttributes().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.
mDecorView.setSystemUiVisibility(
View.SYSTEM_UI_FLAG_LAYOUT_STABLE |
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |
View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
View.SYSTEM_UI_FLAG_FULLSCREEN |
View.SYSTEM_UI_FLAG_IMMERSIVE);
}
public void handleMenuAction(int action) {
switch (action) {
case MENU_ACTION_EXIT:
mEmulationFragment.stopEmulation();
finish();
break;
}
}
private void editControlsPlacement() {
if (mEmulationFragment.isConfiguringControls()) {
mEmulationFragment.stopConfiguringControls();
} else {
mEmulationFragment.startConfiguringControls();
}
}
private void adjustScale() {
LayoutInflater inflater = LayoutInflater.from(this);
View view = inflater.inflate(R.layout.dialog_seekbar, null);
final SeekBar seekbar = view.findViewById(R.id.seekbar);
final TextView value = view.findViewById(R.id.text_value);
final TextView units = view.findViewById(R.id.text_units);
seekbar.setMax(150);
seekbar.setProgress(mPreferences.getInt("controlScale", 50));
seekbar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
public void onStartTrackingTouch(SeekBar seekBar) {
}
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
value.setText(String.valueOf(progress + 50));
}
public void onStopTrackingTouch(SeekBar seekBar) {
setControlScale(seekbar.getProgress());
}
});
value.setText(String.valueOf(seekbar.getProgress() + 50));
units.setText("%");
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(R.string.emulation_control_scale);
builder.setView(view);
final int previousProgress = seekbar.getProgress();
builder.setNegativeButton(android.R.string.cancel, (dialogInterface, i) -> {
setControlScale(previousProgress);
});
builder.setPositiveButton(android.R.string.ok, (dialogInterface, i) ->
{
setControlScale(seekbar.getProgress());
});
builder.setNeutralButton(R.string.slider_default, (dialogInterface, i) -> {
setControlScale(50);
});
AlertDialog alertDialog = builder.create();
alertDialog.show();
}
private void setControlScale(int scale) {
SharedPreferences.Editor editor = mPreferences.edit();
editor.putInt("controlScale", scale);
editor.apply();
mEmulationFragment.refreshInputOverlay();
}
private void resetOverlay() {
new AlertDialog.Builder(this)
.setTitle(getString(R.string.emulation_touch_overlay_reset))
.setPositiveButton(android.R.string.yes, (dialogInterface, i) -> mEmulationFragment.resetInputOverlay())
.setNegativeButton(android.R.string.cancel, (dialogInterface, i) -> {
})
.create()
.show();
}
private static boolean areCoordinatesOutside(@Nullable View view, float x, float y)
{
if (view == null)
{
return true;
}
Rect viewBounds = new Rect();
view.getGlobalVisibleRect(viewBounds);
return !viewBounds.contains(Math.round(x), Math.round(y));
}
@Override
public boolean dispatchTouchEvent(MotionEvent event)
{
if (event.getActionMasked() == MotionEvent.ACTION_DOWN)
{
boolean anyMenuClosed = false;
Fragment submenu = getSupportFragmentManager().findFragmentById(R.id.frame_submenu);
if (submenu != null && areCoordinatesOutside(submenu.getView(), event.getX(), event.getY()))
{
closeSubmenu();
submenu = null;
anyMenuClosed = true;
}
if (submenu == null)
{
Fragment menu = getSupportFragmentManager().findFragmentById(R.id.frame_menu);
if (menu != null && areCoordinatesOutside(menu.getView(), event.getX(), event.getY()))
{
closeMenu();
anyMenuClosed = true;
}
}
if (anyMenuClosed)
{
return true;
}
}
return super.dispatchTouchEvent(event);
}
public boolean isActivityRecreated() {
return activityRecreated;
}
@Retention(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})
public @interface MenuAction {
}
private boolean closeSubmenu()
{
return getSupportFragmentManager().popBackStackImmediate(BACKSTACK_NAME_SUBMENU,
FragmentManager.POP_BACK_STACK_INCLUSIVE);
}
private boolean closeMenu()
{
mMenuVisible = false;
return getSupportFragmentManager().popBackStackImmediate(BACKSTACK_NAME_MENU,
FragmentManager.POP_BACK_STACK_INCLUSIVE);
}
private void toggleMenu()
{
if (!closeMenu()) {
// Removing the menu failed, so that means it wasn't visible. Add it.
Fragment fragment = MenuFragment.newInstance();
getSupportFragmentManager().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();
mMenuVisible = true;
}
}
}

View file

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