Skip to content

Commit

Permalink
[VrKeyboardActivity] Add a hacky in-app keyboard that won't crash
Browse files Browse the repository at this point in the history
  • Loading branch information
amwatson committed Jan 20, 2024
1 parent 5fec9c3 commit c90d0d2
Show file tree
Hide file tree
Showing 12 changed files with 865 additions and 162 deletions.
16 changes: 16 additions & 0 deletions src/android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,22 @@
</activity>


<activity
android:name="org.citra.citra_emu.vr.VrKeyboardActivity"
android:exported="true"
android:process=":vr_process"
android:windowSoftInputMode="stateVisible|adjustResize"
android:resizeableActivity="false"
android:screenOrientation="landscape">

<!-- This intentfilter marks this Activity as the one that gets launched from Home screen. -->
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="com.oculus.intent.category.2D" />
</intent-filter>
</activity>


<service android:name="org.citra.citra_emu.utils.ForegroundService" />

<activity
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,31 +7,34 @@
import android.app.Activity;
import android.app.Dialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.text.InputFilter;
import android.text.Spanned;
import android.view.ViewGroup;
import android.widget.EditText;
import android.widget.FrameLayout;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.DialogFragment;

import com.google.android.material.dialog.MaterialAlertDialogBuilder;

import java.util.Objects;
import org.citra.citra_emu.CitraApplication;
import org.citra.citra_emu.NativeLibrary;
import org.citra.citra_emu.R;
import org.citra.citra_emu.activities.EmulationActivity;
import org.citra.citra_emu.utils.Log;
import org.citra.citra_emu.vr.VrActivity;
import org.citra.citra_emu.vr.VrKeyboardActivity;

import java.util.Objects;

// Warning (amwatson): I had to tear through this pretty quickly because I didn't realize
// there was a system keyboard. This is a pretty hack solution that will not
// merge well.
public final class SoftwareKeyboard {
/// Corresponds to Frontend::ButtonConfig
private interface ButtonConfig {
public interface ButtonConfig {
int Single = 0; /// Ok button
int Dual = 1; /// Cancel | Ok buttons
int Triple = 2; /// Cancel | I Forgot | Ok buttons
Expand Down Expand Up @@ -62,8 +65,7 @@ public static class KeyboardConfig implements java.io.Serializable {
public int max_text_length;
public boolean multiline_mode; /// True if the keyboard accepts multiple lines of input
public String hint_text; /// Displayed in the field as a hint before
@Nullable
public String[] button_text; /// Contains the button text that the caller provides
@Nullable public String[] button_text; /// Contains the button text that the caller provides
}

/// Corresponds to Frontend::KeyboardData
Expand All @@ -77,13 +79,13 @@ private KeyboardData(int button, String text) {
}
}

private static class Filter implements InputFilter {
public static class Filter implements InputFilter {
@Override
public CharSequence filter(CharSequence source, int start, int end, Spanned dest,
int dstart, int dend) {
String text = new StringBuilder(dest)
.replace(dstart, dend, source.subSequence(start, end).toString())
.toString();
.replace(dstart, dend, source.subSequence(start, end).toString())
.toString();
if (ValidateFilters(text) == ValidationError.None) {
return null; // Accept replacement
}
Expand All @@ -107,52 +109,52 @@ public Dialog onCreateDialog(Bundle savedInstanceState) {
assert emulationActivity != null;

FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
params.leftMargin = params.rightMargin =
CitraApplication.getAppContext().getResources().getDimensionPixelSize(
R.dimen.dialog_margin);
CitraApplication.getAppContext().getResources().getDimensionPixelSize(
R.dimen.dialog_margin);

KeyboardConfig config = Objects.requireNonNull(
(KeyboardConfig) Objects.requireNonNull(getArguments()).getSerializable("config"));
(KeyboardConfig)Objects.requireNonNull(getArguments()).getSerializable("config"));

// Set up the input
EditText editText = new EditText(CitraApplication.getAppContext());
editText.setHint(config.hint_text);
editText.setSingleLine(!config.multiline_mode);
editText.setLayoutParams(params);
editText.setFilters(new InputFilter[]{
new Filter(), new InputFilter.LengthFilter(config.max_text_length)});
editText.setFilters(new InputFilter[] {
new Filter(), new InputFilter.LengthFilter(config.max_text_length)});

FrameLayout container = new FrameLayout(emulationActivity);
container.addView(editText);

MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(emulationActivity)
.setTitle(R.string.software_keyboard)
.setView(container);
.setTitle(R.string.software_keyboard)
.setView(container);
setCancelable(false);

switch (config.button_config) {
case ButtonConfig.Triple: {
final String text = config.button_text[1].isEmpty()
? emulationActivity.getString(R.string.i_forgot)
: config.button_text[1];
builder.setNeutralButton(text, null);
}
// fallthrough
case ButtonConfig.Dual: {
final String text = config.button_text[0].isEmpty()
? emulationActivity.getString(android.R.string.cancel)
: config.button_text[0];
builder.setNegativeButton(text, null);
}
// fallthrough
case ButtonConfig.Single: {
final String text = config.button_text[2].isEmpty()
? emulationActivity.getString(android.R.string.ok)
: config.button_text[2];
builder.setPositiveButton(text, null);
break;
}
case ButtonConfig.Triple: {
final String text = config.button_text[1].isEmpty()
? emulationActivity.getString(R.string.i_forgot)
: config.button_text[1];
builder.setNeutralButton(text, null);
}
// fallthrough
case ButtonConfig.Dual: {
final String text = config.button_text[0].isEmpty()
? emulationActivity.getString(android.R.string.cancel)
: config.button_text[0];
builder.setNegativeButton(text, null);
}
// fallthrough
case ButtonConfig.Single: {
final String text = config.button_text[2].isEmpty()
? emulationActivity.getString(android.R.string.ok)
: config.button_text[2];
builder.setPositiveButton(text, null);
break;
}
}

final AlertDialog dialog = builder.create();
Expand Down Expand Up @@ -209,31 +211,65 @@ private static void ExecuteImpl(KeyboardConfig config) {
fragment.show(emulationActivity.getSupportFragmentManager(), "keyboard");
}

private static void HandleValidationError(KeyboardConfig config, ValidationError error) {
public static void HandleValidationError(KeyboardConfig config, ValidationError error) {
final EmulationActivity emulationActivity = NativeLibrary.sEmulationActivity.get();
String message = "";
switch (error) {
case FixedLengthRequired:
message =
emulationActivity.getString(R.string.fixed_length_required, config.max_text_length);
break;
case MaxLengthExceeded:
message =
emulationActivity.getString(R.string.max_length_exceeded, config.max_text_length);
break;
case BlankInputNotAllowed:
message = emulationActivity.getString(R.string.blank_input_not_allowed);
break;
case EmptyInputNotAllowed:
message = emulationActivity.getString(R.string.empty_input_not_allowed);
break;
case FixedLengthRequired:
message =
emulationActivity.getString(R.string.fixed_length_required, config.max_text_length);
break;
case MaxLengthExceeded:
message =
emulationActivity.getString(R.string.max_length_exceeded, config.max_text_length);
break;
case BlankInputNotAllowed:
message = emulationActivity.getString(R.string.blank_input_not_allowed);
break;
case EmptyInputNotAllowed:
message = emulationActivity.getString(R.string.empty_input_not_allowed);
break;
}

new MaterialAlertDialogBuilder(emulationActivity)
.setTitle(R.string.software_keyboard)
.setMessage(message)
.setPositiveButton(android.R.string.ok, null)
.show();
// TODO show error dialog
Log.warning("Keyboard error: " + message);
/* new MaterialAlertDialogBuilder(emulationActivity)
.setTitle(R.string.software_keyboard)
.setMessage(message)
.setPositiveButton(android.R.string.ok, null)
.show();*/
}

public static void onFinishVrKeyboardPositive(final String text, final KeyboardConfig config) {
data = new KeyboardData(0, "");
data.button = config.button_config;
data.text = text;
final ValidationError error = ValidateInput(data.text);
if (error != ValidationError.None) {
HandleValidationError(config, error);
final EmulationActivity emulationActivity = NativeLibrary.sEmulationActivity.get();
onFinishVrKeyboardNegative();
return;
}
synchronized (finishLock) {
finishLock.notifyAll();
}
}

public static void onFinishVrKeyboardNeutral() {
data = new KeyboardData(0, "");
data.button = 1;
synchronized (finishLock) {
finishLock.notifyAll();
}
}

public static void onFinishVrKeyboardNegative() {
data = new KeyboardData(0, "");
data.button = 0;
synchronized (finishLock) {
finishLock.notifyAll();
}
}

public static KeyboardData Execute(KeyboardConfig config) {
Expand All @@ -242,7 +278,12 @@ public static KeyboardData Execute(KeyboardConfig config) {
return new KeyboardData(0, "");
}

NativeLibrary.sEmulationActivity.get().runOnUiThread(() -> ExecuteImpl(config));
final EmulationActivity emulationActivity = NativeLibrary.sEmulationActivity.get();
if (emulationActivity instanceof VrActivity) {
((VrActivity)emulationActivity).mVrKeyboardLauncher.launch(config);
} else {
NativeLibrary.sEmulationActivity.get().runOnUiThread(() -> ExecuteImpl(config));
}

synchronized (finishLock) {
try {
Expand All @@ -256,11 +297,11 @@ public static KeyboardData Execute(KeyboardConfig config) {

public static void ShowError(String error) {
NativeLibrary.displayAlertMsg(
CitraApplication.getAppContext().getResources().getString(R.string.software_keyboard),
error, false);
CitraApplication.getAppContext().getResources().getString(R.string.software_keyboard),
error, false);
}

private static native ValidationError ValidateFilters(String text);
public static native ValidationError ValidateFilters(String text);

private static native ValidationError ValidateInput(String text);
public static native ValidationError ValidateInput(String text);
}
Original file line number Diff line number Diff line change
@@ -1,19 +1,12 @@
package org.citra.citra_emu.vr;

public class ErrorMessageLayer
{
public class ErrorMessageLayer {

public static ErrorMessageLayer instance = null;

public static void showErrorWindow(final String titleStr,
final String mainMessageStr)
{
}
public static void showErrorWindow(final String titleStr, final String mainMessageStr) {}

public void _showErrorWindow(final String titleStr,
final String mainMessageStr)
{
}
public void _showErrorWindow(final String titleStr, final String mainMessageStr) {}

public void hideErrorWindow() {}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,11 @@
* Note: this is set up to require the min number of changes possible to
*existing Citra code, in case an upstream merge is desired.
**/
public class GameSurfaceLayer
{
public static void setSurface(VrActivity activity, Surface surface)
{
public class GameSurfaceLayer {
public static void setSurface(VrActivity activity, Surface surface) {
assert activity != null;
((EmulationFragment)activity.getSupportFragmentManager()
.findFragmentById(R.id.frame_emulation_fragment))
((EmulationFragment)activity.getSupportFragmentManager().findFragmentById(
R.id.frame_emulation_fragment))
.surfaceCreated(surface);
}
}
Loading

0 comments on commit c90d0d2

Please sign in to comment.