android: Allow opening in-emulation menu by pressing Back (#6248)

It seems like the menu isn't showing up for users on Android 13.
We're not sure what's wrong, and the approach we've been using for the
menu hasn't been entirely reliable in the past either (in particular
not on non-mobile form factors like Chromebooks and VR devices),
so let's make it possible to open the menu by pressing Back,
an action that works reliably on most kinds of Android devices.
(Not sure if there's an equivalent of Back on devices like watches,
but I think we can pretty safely ignore those for now.)
This commit is contained in:
JosJuice 2023-01-22 08:42:27 +01:00 committed by GitHub
parent 9c6035f254
commit 9b20bcea0f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 60 additions and 21 deletions

View file

@ -12,6 +12,7 @@ import android.view.InputDevice;
import android.view.KeyEvent; import android.view.KeyEvent;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.Menu; import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.MotionEvent; import android.view.MotionEvent;
import android.view.SubMenu; import android.view.SubMenu;
@ -19,11 +20,13 @@ import android.view.View;
import android.widget.CheckBox; import android.widget.CheckBox;
import android.widget.SeekBar; import android.widget.SeekBar;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.IntDef; import androidx.annotation.IntDef;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.PopupMenu;
import androidx.core.app.NotificationManagerCompat; import androidx.core.app.NotificationManagerCompat;
import androidx.fragment.app.FragmentActivity; import androidx.fragment.app.FragmentActivity;
@ -74,6 +77,7 @@ public final class EmulationActivity extends AppCompatActivity {
public static final int MENU_ACTION_JOYSTICK_REL_CENTER = 15; public static final int MENU_ACTION_JOYSTICK_REL_CENTER = 15;
public static final int MENU_ACTION_DPAD_SLIDE_ENABLE = 16; public static final int MENU_ACTION_DPAD_SLIDE_ENABLE = 16;
public static final int MENU_ACTION_OPEN_CHEATS = 17; public static final int MENU_ACTION_OPEN_CHEATS = 17;
public static final int MENU_ACTION_CLOSE_GAME = 18;
public static final int REQUEST_SELECT_AMIIBO = 2; public static final int REQUEST_SELECT_AMIIBO = 2;
private static final int EMULATION_RUNNING_NOTIFICATION = 0x1000; private static final int EMULATION_RUNNING_NOTIFICATION = 0x1000;
@ -114,6 +118,8 @@ public final class EmulationActivity extends AppCompatActivity {
EmulationActivity.MENU_ACTION_DPAD_SLIDE_ENABLE); EmulationActivity.MENU_ACTION_DPAD_SLIDE_ENABLE);
buttonsActionsMap buttonsActionsMap
.append(R.id.menu_emulation_open_cheats, EmulationActivity.MENU_ACTION_OPEN_CHEATS); .append(R.id.menu_emulation_open_cheats, EmulationActivity.MENU_ACTION_OPEN_CHEATS);
buttonsActionsMap
.append(R.id.menu_emulation_close_game, EmulationActivity.MENU_ACTION_CLOSE_GAME);
} }
private View mDecorView; private View mDecorView;
@ -223,21 +229,12 @@ public final class EmulationActivity extends AppCompatActivity {
@Override @Override
public void onBackPressed() { public void onBackPressed() {
NativeLibrary.PauseEmulation(); View anchor = findViewById(R.id.menu_anchor);
new AlertDialog.Builder(this) PopupMenu popupMenu = new PopupMenu(this, anchor);
.setTitle(R.string.emulation_close_game) onCreateOptionsMenu(popupMenu.getMenu(), popupMenu.getMenuInflater());
.setMessage(R.string.emulation_close_game_message) updateSavestateMenuOptions(popupMenu.getMenu());
.setPositiveButton(android.R.string.yes, (dialogInterface, i) -> popupMenu.setOnMenuItemClickListener(this::onOptionsItemSelected);
{ popupMenu.show();
mEmulationFragment.stopEmulation();
finish();
})
.setNegativeButton(android.R.string.cancel, (dialogInterface, i) ->
NativeLibrary.UnPauseEmulation())
.setOnCancelListener(dialogInterface ->
NativeLibrary.UnPauseEmulation())
.create()
.show();
} }
@Override @Override
@ -271,6 +268,10 @@ public final class EmulationActivity extends AppCompatActivity {
} }
} }
public void onEmulationStarted() {
Toast.makeText(this, getString(R.string.emulation_menu_help), Toast.LENGTH_LONG).show();
}
private void enableFullscreenImmersive() { private void enableFullscreenImmersive() {
// It would be nice to use IMMERSIVE_STICKY, but that doesn't show the toolbar. // It would be nice to use IMMERSIVE_STICKY, but that doesn't show the toolbar.
mDecorView.setSystemUiVisibility( mDecorView.setSystemUiVisibility(
@ -285,7 +286,12 @@ public final class EmulationActivity extends AppCompatActivity {
@Override @Override
public boolean onCreateOptionsMenu(Menu menu) { public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present. // Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_emulation, menu); onCreateOptionsMenu(menu, getMenuInflater());
return true;
}
private void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.menu_emulation, menu);
int layoutOptionMenuItem = R.id.menu_screen_layout_landscape; int layoutOptionMenuItem = R.id.menu_screen_layout_landscape;
switch (EmulationMenuSettings.getLandscapeScreenLayout()) { switch (EmulationMenuSettings.getLandscapeScreenLayout()) {
@ -306,8 +312,6 @@ public final class EmulationActivity extends AppCompatActivity {
menu.findItem(R.id.menu_emulation_show_fps).setChecked(EmulationMenuSettings.getShowFps()); menu.findItem(R.id.menu_emulation_show_fps).setChecked(EmulationMenuSettings.getShowFps());
menu.findItem(R.id.menu_emulation_swap_screens).setChecked(EmulationMenuSettings.getSwapScreens()); menu.findItem(R.id.menu_emulation_swap_screens).setChecked(EmulationMenuSettings.getSwapScreens());
menu.findItem(R.id.menu_emulation_show_overlay).setChecked(EmulationMenuSettings.getShowOverlay()); menu.findItem(R.id.menu_emulation_show_overlay).setChecked(EmulationMenuSettings.getShowOverlay());
return true;
} }
private void DisplaySavestateWarning() { private void DisplaySavestateWarning() {
@ -333,12 +337,16 @@ public final class EmulationActivity extends AppCompatActivity {
@Override @Override
public boolean onPrepareOptionsMenu(Menu menu) { public boolean onPrepareOptionsMenu(Menu menu) {
super.onPrepareOptionsMenu(menu); super.onPrepareOptionsMenu(menu);
updateSavestateMenuOptions(menu);
return true;
}
private void updateSavestateMenuOptions(Menu menu) {
final NativeLibrary.SavestateInfo[] savestates = NativeLibrary.GetSavestateInfo(); final NativeLibrary.SavestateInfo[] savestates = NativeLibrary.GetSavestateInfo();
if (savestates == null) { if (savestates == null) {
menu.findItem(R.id.menu_emulation_save_state).setVisible(false); menu.findItem(R.id.menu_emulation_save_state).setVisible(false);
menu.findItem(R.id.menu_emulation_load_state).setVisible(false); menu.findItem(R.id.menu_emulation_load_state).setVisible(false);
return true; return;
} }
menu.findItem(R.id.menu_emulation_save_state).setVisible(true); menu.findItem(R.id.menu_emulation_save_state).setVisible(true);
menu.findItem(R.id.menu_emulation_load_state).setVisible(true); menu.findItem(R.id.menu_emulation_load_state).setVisible(true);
@ -367,7 +375,6 @@ public final class EmulationActivity extends AppCompatActivity {
saveStateMenu.getItem(info.slot - 1).setTitle(text); saveStateMenu.getItem(info.slot - 1).setTitle(text);
loadStateMenu.getItem(info.slot - 1).setTitle(text).setEnabled(true); loadStateMenu.getItem(info.slot - 1).setTitle(text).setEnabled(true);
} }
return true;
} }
@SuppressWarnings("WrongConstant") @SuppressWarnings("WrongConstant")
@ -480,6 +487,24 @@ public final class EmulationActivity extends AppCompatActivity {
case MENU_ACTION_OPEN_CHEATS: case MENU_ACTION_OPEN_CHEATS:
CheatsActivity.launch(this); CheatsActivity.launch(this);
break; break;
case MENU_ACTION_CLOSE_GAME:
NativeLibrary.PauseEmulation();
new AlertDialog.Builder(this)
.setTitle(R.string.emulation_close_game)
.setMessage(R.string.emulation_close_game_message)
.setPositiveButton(android.R.string.yes, (dialogInterface, i) ->
{
mEmulationFragment.stopEmulation();
finish();
})
.setNegativeButton(android.R.string.cancel, (dialogInterface, i) ->
NativeLibrary.UnPauseEmulation())
.setOnCancelListener(dialogInterface ->
NativeLibrary.UnPauseEmulation())
.create()
.show();
break;
} }
return true; return true;

View file

@ -133,6 +133,8 @@ public class DiskShaderCacheProgress {
case Complete: case Complete:
// Workaround for when dialog is dismissed when the app is in the background // Workaround for when dialog is dismissed when the app is in the background
fragment.dismissAllowingStateLoss(); fragment.dismissAllowingStateLoss();
emulationActivity.runOnUiThread(emulationActivity::onEmulationStarted);
break; break;
} }
} }

View file

@ -14,4 +14,10 @@
android:layout_height="match_parent" android:layout_height="match_parent"
android:transitionName="image_game_icon" /> android:transitionName="image_game_icon" />
</FrameLayout> <View
android:id="@+id/menu_anchor"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_gravity="top|end" />
</FrameLayout>

View file

@ -115,4 +115,9 @@
app:showAsAction="never" app:showAsAction="never"
android:title="@string/emulation_open_settings" /> android:title="@string/emulation_open_settings" />
<item
android:id="@+id/menu_emulation_close_game"
app:showAsAction="never"
android:title="@string/emulation_close_game" />
</menu> </menu>

View file

@ -157,6 +157,7 @@
<string name="loader_error_invalid_format">Invalid ROM format</string> <string name="loader_error_invalid_format">Invalid ROM format</string>
<!-- Emulation Menu --> <!-- Emulation Menu -->
<string name="emulation_menu_help">Press Back to access the menu.</string>
<string name="emulation_save_state">Save State</string> <string name="emulation_save_state">Save State</string>
<string name="emulation_load_state">Load State</string> <string name="emulation_load_state">Load State</string>
<string name="emulation_empty_state_slot">Slot %1$d</string> <string name="emulation_empty_state_slot">Slot %1$d</string>