mirror of
https://github.com/izzy2lost/PSX2.git
synced 2026-07-05 15:18:36 -07:00
cleaned up a bunch of warnings more ui stuff
This commit is contained in:
@@ -5,6 +5,7 @@ import android.view.InputDevice;
|
||||
import android.util.Log;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Controller configuration and detection utility
|
||||
@@ -43,7 +44,7 @@ public class ControllerConfig {
|
||||
* Get controller type description
|
||||
*/
|
||||
private static String getControllerType(InputDevice device) {
|
||||
String name = device.getName().toLowerCase();
|
||||
String name = device.getName().toLowerCase(Locale.ROOT);
|
||||
|
||||
if (name.contains("xbox")) {
|
||||
return "Xbox Controller";
|
||||
|
||||
@@ -5,6 +5,7 @@ import android.view.KeyEvent;
|
||||
import android.view.MotionEvent;
|
||||
import android.util.Log;
|
||||
import android.util.SparseArray;
|
||||
import android.util.SparseIntArray;
|
||||
|
||||
/**
|
||||
* Controller input handler based on AetherSX2's PAD implementation
|
||||
@@ -50,10 +51,10 @@ public class ControllerInputHandler {
|
||||
private static final long COMBO_TIMEOUT_MS = 500; // 500ms window for combo
|
||||
|
||||
// Key mapping from Android KeyEvent to PS2 buttons
|
||||
private static final SparseArray<Integer> sKeyMapping = new SparseArray<>();
|
||||
private static final SparseIntArray sKeyMapping = new SparseIntArray();
|
||||
|
||||
// Motion axis mapping from Android MotionEvent to PS2 analog inputs
|
||||
private static final SparseArray<Integer> sAxisMapping = new SparseArray<>();
|
||||
private static final SparseIntArray sAxisMapping = new SparseIntArray();
|
||||
|
||||
static {
|
||||
// Initialize key mappings (Android KeyEvent -> PS2 button)
|
||||
@@ -128,9 +129,9 @@ public class ControllerInputHandler {
|
||||
}
|
||||
|
||||
int keyCode = event.getKeyCode();
|
||||
Integer ps2Button = sKeyMapping.get(keyCode);
|
||||
int ps2Button = sKeyMapping.get(keyCode, Integer.MIN_VALUE);
|
||||
|
||||
if (ps2Button != null) {
|
||||
if (ps2Button != Integer.MIN_VALUE) {
|
||||
int controllerId = event.getDeviceId();
|
||||
boolean pressed = (event.getAction() == KeyEvent.ACTION_DOWN);
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Dialog for testing controller input
|
||||
@@ -29,7 +30,7 @@ public class ControllerTestDialogFragment extends DialogFragment implements Cont
|
||||
@NonNull
|
||||
@Override
|
||||
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
|
||||
View view = LayoutInflater.from(getContext()).inflate(R.layout.dialog_controller_test, null);
|
||||
View view = getLayoutInflater().inflate(R.layout.dialog_controller_test, null);
|
||||
|
||||
mControllerListText = view.findViewById(R.id.tv_controller_list);
|
||||
mInputLogText = view.findViewById(R.id.tv_input_log);
|
||||
@@ -66,7 +67,7 @@ public class ControllerTestDialogFragment extends DialogFragment implements Cont
|
||||
public void onControllerButtonPressed(int controllerId, int button, boolean pressed) {
|
||||
String buttonName = getButtonName(button);
|
||||
String action = pressed ? "PRESSED" : "RELEASED";
|
||||
String logEntry = String.format("Controller %d: %s %s\n", controllerId, buttonName, action);
|
||||
String logEntry = String.format(Locale.ROOT, "Controller %d: %s %s\n", controllerId, buttonName, action);
|
||||
|
||||
mInputLog.append(logEntry);
|
||||
|
||||
@@ -88,7 +89,7 @@ public class ControllerTestDialogFragment extends DialogFragment implements Cont
|
||||
public void onControllerAnalogInput(int controllerId, int axis, float value) {
|
||||
if (Math.abs(value) > 0.1f) { // Only log significant analog input
|
||||
String axisName = getAxisName(axis);
|
||||
String logEntry = String.format("Controller %d: %s %.2f\n", controllerId, axisName, value);
|
||||
String logEntry = String.format(Locale.ROOT, "Controller %d: %s %.2f\n", controllerId, axisName, value);
|
||||
|
||||
mInputLog.append(logEntry);
|
||||
|
||||
@@ -145,7 +146,7 @@ public class ControllerTestDialogFragment extends DialogFragment implements Cont
|
||||
|
||||
@Override
|
||||
public void onControllerCombo(int controllerId, String comboName) {
|
||||
String logEntry = String.format("Controller %d: COMBO %s\n", controllerId, comboName);
|
||||
String logEntry = String.format(Locale.ROOT, "Controller %d: COMBO %s\n", controllerId, comboName);
|
||||
|
||||
mInputLog.append(logEntry);
|
||||
|
||||
|
||||
@@ -53,22 +53,25 @@ public class CoversAdapter extends RecyclerView.Adapter<CoversAdapter.VH> {
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull VH holder, int position) {
|
||||
holder.title.setText(titles[position]);
|
||||
String url = coverUrls[position];
|
||||
String local = (localPaths != null && position < localPaths.length) ? localPaths[position] : null;
|
||||
Object source = null;
|
||||
File localFile = null;
|
||||
if (local != null) {
|
||||
File f = new File(local);
|
||||
if (f.exists() && f.length() > 0) source = f;
|
||||
if (f.exists() && f.length() > 0) localFile = f;
|
||||
}
|
||||
if (source == null) source = url;
|
||||
|
||||
Glide.with(context)
|
||||
.load(source)
|
||||
.diskCacheStrategy(DiskCacheStrategy.AUTOMATIC)
|
||||
.fitCenter()
|
||||
.placeholder(android.R.color.transparent)
|
||||
.error(android.R.color.transparent)
|
||||
.into(holder.cover);
|
||||
if (localFile != null) {
|
||||
Glide.with(context)
|
||||
.load(localFile)
|
||||
.diskCacheStrategy(DiskCacheStrategy.AUTOMATIC)
|
||||
.fitCenter()
|
||||
.placeholder(android.R.color.transparent)
|
||||
.error(android.R.color.transparent)
|
||||
.into(holder.cover);
|
||||
} else {
|
||||
// Do not load from network automatically; wait for explicit download
|
||||
holder.cover.setImageDrawable(null);
|
||||
}
|
||||
holder.itemView.setOnClickListener(v -> {
|
||||
if (onItemClick != null) onItemClick.onClick(position);
|
||||
});
|
||||
|
||||
@@ -9,7 +9,7 @@ import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.Switch;
|
||||
import com.google.android.material.materialswitch.MaterialSwitch;
|
||||
import android.widget.TextView;
|
||||
import android.net.Uri;
|
||||
|
||||
@@ -41,7 +41,7 @@ public class GameSettingsDialogFragment extends DialogFragment {
|
||||
@Override
|
||||
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
|
||||
Context ctx = requireContext();
|
||||
View view = LayoutInflater.from(ctx).inflate(R.layout.dialog_game_settings, null, false);
|
||||
View view = getLayoutInflater().inflate(R.layout.dialog_game_settings, null, false);
|
||||
|
||||
Bundle args = getArguments();
|
||||
String gameTitle = args != null ? args.getString(ARG_GAME_TITLE, "Unknown Game") : "Unknown Game";
|
||||
@@ -85,10 +85,10 @@ public class GameSettingsDialogFragment extends DialogFragment {
|
||||
spResolution.setAdapter(resolutionAdapter);
|
||||
|
||||
// Switches
|
||||
Switch swWidescreenPatches = view.findViewById(R.id.sw_widescreen_patches);
|
||||
Switch swNoInterlacingPatches = view.findViewById(R.id.sw_no_interlacing_patches);
|
||||
Switch swEnablePatchCodes = view.findViewById(R.id.sw_enable_patch_codes);
|
||||
Switch swEnableCheats = view.findViewById(R.id.sw_enable_cheats);
|
||||
MaterialSwitch swWidescreenPatches = view.findViewById(R.id.sw_widescreen_patches);
|
||||
MaterialSwitch swNoInterlacingPatches = view.findViewById(R.id.sw_no_interlacing_patches);
|
||||
MaterialSwitch swEnablePatchCodes = view.findViewById(R.id.sw_enable_patch_codes);
|
||||
MaterialSwitch swEnableCheats = view.findViewById(R.id.sw_enable_cheats);
|
||||
|
||||
// Load existing per-game settings from INI and prefill widgets; if missing, use global
|
||||
try {
|
||||
|
||||
@@ -17,14 +17,18 @@ import android.view.WindowInsetsController;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
// Import MaterialAlertDialogBuilder for Material 3 styling
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
import android.app.Dialog;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
import androidx.recyclerview.widget.GridLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import java.util.Locale;
|
||||
|
||||
public class GamesCoverDialogFragment extends DialogFragment {
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setStyle(DialogFragment.STYLE_NO_FRAME, R.style.AppTheme);
|
||||
}
|
||||
private CoversAdapter adapter;
|
||||
private String[] titles;
|
||||
private String[] uris;
|
||||
@@ -75,14 +79,19 @@ public class GamesCoverDialogFragment extends DialogFragment {
|
||||
@NonNull
|
||||
@Override
|
||||
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
|
||||
LayoutInflater inflater = LayoutInflater.from(requireContext());
|
||||
View root = inflater.inflate(R.layout.dialog_covers_grid, null, false);
|
||||
// Return a styled dialog; content is provided by onCreateView
|
||||
return new Dialog(requireContext(), R.style.PSX2_FullScreenDialog);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
View root = inflater.inflate(R.layout.dialog_covers_grid, container, false);
|
||||
|
||||
rv = root.findViewById(R.id.recycler_covers);
|
||||
rv.setHasFixedSize(true);
|
||||
glm = new GridLayoutManager(requireContext(), 3);
|
||||
rv.setLayoutManager(glm);
|
||||
// spacing decoration (8dp) using half on each side so the gap between items is exactly spacingPx
|
||||
final int spacingPx = (int) (8 * getResources().getDisplayMetrics().density);
|
||||
final int half = Math.max(1, spacingPx / 2);
|
||||
rv.addItemDecoration(new RecyclerView.ItemDecoration() {
|
||||
@@ -91,13 +100,11 @@ public class GamesCoverDialogFragment extends DialogFragment {
|
||||
outRect.set(half, half, half, half);
|
||||
}
|
||||
});
|
||||
// Hard lock spans based on orientation only
|
||||
rv.addOnLayoutChangeListener((v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
|
||||
int currentOrientation = getResources().getConfiguration().orientation;
|
||||
int fixedSpan = (currentOrientation == android.content.res.Configuration.ORIENTATION_LANDSCAPE) ? 4 : 2;
|
||||
if (fixedSpan != glm.getSpanCount()) { glm.setSpanCount(fixedSpan); }
|
||||
});
|
||||
// Set initial fixed span as soon as possible
|
||||
root.post(() -> {
|
||||
int currentOrientation = getResources().getConfiguration().orientation;
|
||||
int fixedSpan = (currentOrientation == android.content.res.Configuration.ORIENTATION_LANDSCAPE) ? 4 : 2;
|
||||
@@ -113,7 +120,6 @@ public class GamesCoverDialogFragment extends DialogFragment {
|
||||
String saved = prefs.getString("serial:" + uris[i], null);
|
||||
String serial = saved;
|
||||
if (serial == null || serial.isEmpty()) {
|
||||
// Ask native core for the real serial (supports ISO/CHD and content://)
|
||||
try {
|
||||
String nativeSerial = NativeApp.getGameSerial(uris[i]);
|
||||
if (nativeSerial != null && !nativeSerial.isEmpty()) {
|
||||
@@ -123,63 +129,58 @@ public class GamesCoverDialogFragment extends DialogFragment {
|
||||
} catch (Throwable ignored) {}
|
||||
}
|
||||
if (serial == null || serial.isEmpty()) {
|
||||
// Heuristic fallback from filename
|
||||
serial = buildSerialFromUri(uris[i]);
|
||||
}
|
||||
coverUrls[i] = buildCoverUrlFromSerial(serial);
|
||||
localPaths[i] = new java.io.File(getCoversDir(), serial + ".png").getAbsolutePath();
|
||||
}
|
||||
|
||||
adapter = new CoversAdapter(requireContext(), titles, coverUrls, localPaths,
|
||||
position -> {
|
||||
// Regular click - start game
|
||||
if (listener != null && position >= 0 && position < uris.length) {
|
||||
listener.onGameSelected(uris[position]);
|
||||
dismissAllowingStateLoss();
|
||||
}
|
||||
},
|
||||
position -> {
|
||||
// Long click - show game settings
|
||||
if (position >= 0 && position < uris.length) {
|
||||
showGameSettings(titles[position], uris[position]);
|
||||
}
|
||||
});
|
||||
adapter = new CoversAdapter(requireContext(), titles, coverUrls, localPaths,
|
||||
position -> {
|
||||
if (listener != null && position >= 0 && position < uris.length) {
|
||||
listener.onGameSelected(uris[position]);
|
||||
dismissAllowingStateLoss();
|
||||
}
|
||||
},
|
||||
position -> {
|
||||
if (position >= 0 && position < uris.length) {
|
||||
showGameSettings(titles[position], uris[position]);
|
||||
}
|
||||
});
|
||||
rv.setAdapter(adapter);
|
||||
|
||||
// Toolbar buttons
|
||||
View btnHome = root.findViewById(R.id.btn_home);
|
||||
if (btnHome != null) btnHome.setOnClickListener(v -> dismissAllowingStateLoss());
|
||||
View btnDownload = root.findViewById(R.id.btn_download);
|
||||
if (btnDownload != null) btnDownload.setOnClickListener(v -> startDownloadCovers());
|
||||
|
||||
AlertDialog dialog = new MaterialAlertDialogBuilder(requireContext(),
|
||||
com.google.android.material.R.style.ThemeOverlay_Material3_MaterialAlertDialog)
|
||||
.setView(root)
|
||||
.create();
|
||||
return dialog;
|
||||
return root;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
Dialog d = getDialog();
|
||||
if (d != null) {
|
||||
Window w = d.getWindow();
|
||||
if (w != null) {
|
||||
w.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
|
||||
w.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
|
||||
// Hide status bar for true full-screen dialog
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
w.setDecorFitsSystemWindows(false);
|
||||
WindowInsetsController controller = w.getInsetsController();
|
||||
if (controller != null) {
|
||||
controller.hide(WindowInsets.Type.statusBars());
|
||||
controller.setSystemBarsBehavior(WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE);
|
||||
}
|
||||
} else {
|
||||
w.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
|
||||
}
|
||||
if (d == null) return;
|
||||
Window w = d.getWindow();
|
||||
if (w == null) return;
|
||||
w.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
w.setDecorFitsSystemWindows(false);
|
||||
WindowInsetsController controller = w.getInsetsController();
|
||||
if (controller != null) {
|
||||
controller.hide(WindowInsets.Type.systemBars());
|
||||
controller.setSystemBarsBehavior(WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE);
|
||||
}
|
||||
} else {
|
||||
w.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
|
||||
View decor = w.getDecorView();
|
||||
int flags = 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_STICKY;
|
||||
decor.setSystemUiVisibility(flags);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -298,7 +299,7 @@ public class GamesCoverDialogFragment extends DialogFragment {
|
||||
if (dot > 0) last = last.substring(0, dot);
|
||||
String serial = null;
|
||||
// Very simple heuristic: find token like XXXX-XXXXX
|
||||
String upper = last.toUpperCase();
|
||||
String upper = last.toUpperCase(Locale.ROOT);
|
||||
java.util.regex.Matcher m = java.util.regex.Pattern.compile("([A-Z]{4,5}-[0-9]{3,5})").matcher(upper);
|
||||
if (m.find()) {
|
||||
serial = m.group(1);
|
||||
@@ -353,7 +354,7 @@ public class GamesCoverDialogFragment extends DialogFragment {
|
||||
|
||||
private static String normalizeSerial(String serial) {
|
||||
if (serial == null) return null;
|
||||
String s = serial.toUpperCase().replace('_', '-');
|
||||
String s = serial.toUpperCase(Locale.ROOT).replace('_', '-');
|
||||
// If form like XXXX-123.45 -> XXXX-12345
|
||||
s = s.replaceAll("([A-Z]{4,5})-([0-9]{3})\\.([0-9]{2})", "$1-$2$3");
|
||||
return s;
|
||||
|
||||
@@ -9,11 +9,13 @@ import android.bluetooth.BluetoothGattService;
|
||||
import android.bluetooth.BluetoothManager;
|
||||
import android.bluetooth.BluetoothProfile;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.hardware.usb.UsbDevice;
|
||||
import android.os.Build;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.util.Log;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedList;
|
||||
@@ -80,23 +82,33 @@ class HIDDeviceBLESteamController extends BluetoothGattCallback implements HIDDe
|
||||
case CHR_READ:
|
||||
chr = getCharacteristic(mUuid);
|
||||
//Log.v(TAG, "Reading characteristic " + chr.getUuid());
|
||||
if (!mGatt.readCharacteristic(chr)) {
|
||||
Log.e(TAG, "Unable to read characteristic " + mUuid.toString());
|
||||
try {
|
||||
if (!mGatt.readCharacteristic(chr)) {
|
||||
Log.e(TAG, "Unable to read characteristic " + mUuid.toString());
|
||||
mResult = false;
|
||||
break;
|
||||
}
|
||||
mResult = true;
|
||||
} catch (SecurityException e) {
|
||||
Log.e(TAG, "Permission denied for reading characteristic: " + e.getMessage());
|
||||
mResult = false;
|
||||
break;
|
||||
}
|
||||
mResult = true;
|
||||
break;
|
||||
case CHR_WRITE:
|
||||
chr = getCharacteristic(mUuid);
|
||||
//Log.v(TAG, "Writing characteristic " + chr.getUuid() + " value=" + HexDump.toHexString(value));
|
||||
chr.setValue(mValue);
|
||||
if (!mGatt.writeCharacteristic(chr)) {
|
||||
Log.e(TAG, "Unable to write characteristic " + mUuid.toString());
|
||||
try {
|
||||
if (!mGatt.writeCharacteristic(chr)) {
|
||||
Log.e(TAG, "Unable to write characteristic " + mUuid.toString());
|
||||
mResult = false;
|
||||
break;
|
||||
}
|
||||
mResult = true;
|
||||
} catch (SecurityException e) {
|
||||
Log.e(TAG, "Permission denied for writing characteristic: " + e.getMessage());
|
||||
mResult = false;
|
||||
break;
|
||||
}
|
||||
mResult = true;
|
||||
break;
|
||||
case ENABLE_NOTIFICATION:
|
||||
chr = getCharacteristic(mUuid);
|
||||
@@ -116,14 +128,19 @@ class HIDDeviceBLESteamController extends BluetoothGattCallback implements HIDDe
|
||||
return;
|
||||
}
|
||||
|
||||
mGatt.setCharacteristicNotification(chr, true);
|
||||
cccd.setValue(value);
|
||||
if (!mGatt.writeDescriptor(cccd)) {
|
||||
Log.e(TAG, "Unable to write descriptor " + mUuid.toString());
|
||||
try {
|
||||
mGatt.setCharacteristicNotification(chr, true);
|
||||
cccd.setValue(value);
|
||||
if (!mGatt.writeDescriptor(cccd)) {
|
||||
Log.e(TAG, "Unable to write descriptor " + mUuid.toString());
|
||||
mResult = false;
|
||||
return;
|
||||
}
|
||||
mResult = true;
|
||||
} catch (SecurityException e) {
|
||||
Log.e(TAG, "Permission denied for notification setup: " + e.getMessage());
|
||||
mResult = false;
|
||||
return;
|
||||
}
|
||||
mResult = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -176,6 +193,14 @@ class HIDDeviceBLESteamController extends BluetoothGattCallback implements HIDDe
|
||||
return String.format("SteamController.%s", mDevice.getAddress());
|
||||
}
|
||||
|
||||
private boolean hasBluetoothPermission() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
return ContextCompat.checkSelfPermission(mManager.getContext(),
|
||||
android.Manifest.permission.BLUETOOTH_CONNECT) == PackageManager.PERMISSION_GRANTED;
|
||||
}
|
||||
return true; // Pre-Android 12 doesn't need runtime permission
|
||||
}
|
||||
|
||||
BluetoothGatt getGatt() {
|
||||
return mGatt;
|
||||
}
|
||||
@@ -183,14 +208,19 @@ class HIDDeviceBLESteamController extends BluetoothGattCallback implements HIDDe
|
||||
// Because on Chromebooks we show up as a dual-mode device, it will attempt to connect TRANSPORT_AUTO, which will use TRANSPORT_BREDR instead
|
||||
// of TRANSPORT_LE. Let's force ourselves to connect low energy.
|
||||
private BluetoothGatt connectGatt(boolean managed) {
|
||||
if (Build.VERSION.SDK_INT >= 23 /* Android 6.0 (M) */) {
|
||||
try {
|
||||
return mDevice.connectGatt(mManager.getContext(), managed, this, TRANSPORT_LE);
|
||||
} catch (Exception e) {
|
||||
try {
|
||||
if (Build.VERSION.SDK_INT >= 23 /* Android 6.0 (M) */) {
|
||||
try {
|
||||
return mDevice.connectGatt(mManager.getContext(), managed, this, TRANSPORT_LE);
|
||||
} catch (Exception e) {
|
||||
return mDevice.connectGatt(mManager.getContext(), managed, this);
|
||||
}
|
||||
} else {
|
||||
return mDevice.connectGatt(mManager.getContext(), managed, this);
|
||||
}
|
||||
} else {
|
||||
return mDevice.connectGatt(mManager.getContext(), managed, this);
|
||||
} catch (SecurityException e) {
|
||||
Log.e(TAG, "Permission denied for GATT connection: " + e.getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -213,13 +243,22 @@ class HIDDeviceBLESteamController extends BluetoothGattCallback implements HIDDe
|
||||
return BluetoothProfile.STATE_DISCONNECTED;
|
||||
}
|
||||
|
||||
return btManager.getConnectionState(mDevice, BluetoothProfile.GATT);
|
||||
try {
|
||||
return btManager.getConnectionState(mDevice, BluetoothProfile.GATT);
|
||||
} catch (SecurityException e) {
|
||||
Log.e(TAG, "Permission denied for getting connection state: " + e.getMessage());
|
||||
return BluetoothProfile.STATE_DISCONNECTED;
|
||||
}
|
||||
}
|
||||
|
||||
void reconnect() {
|
||||
|
||||
if (getConnectionState() != BluetoothProfile.STATE_CONNECTED) {
|
||||
mGatt.disconnect();
|
||||
try {
|
||||
mGatt.disconnect();
|
||||
} catch (SecurityException e) {
|
||||
Log.e(TAG, "Permission denied for disconnect: " + e.getMessage());
|
||||
}
|
||||
mGatt = connectGatt();
|
||||
}
|
||||
|
||||
@@ -241,7 +280,11 @@ class HIDDeviceBLESteamController extends BluetoothGattCallback implements HIDDe
|
||||
// to try to recover.
|
||||
Log.v(TAG, "Chromebook: We are in a very bad state; the controller shows as connected in the underlying Bluetooth layer, but we never received a callback. Forcing a reconnect.");
|
||||
mIsReconnecting = true;
|
||||
mGatt.disconnect();
|
||||
try {
|
||||
mGatt.disconnect();
|
||||
} catch (SecurityException e) {
|
||||
Log.e(TAG, "Permission denied for disconnect: " + e.getMessage());
|
||||
}
|
||||
mGatt = connectGatt(false);
|
||||
break;
|
||||
}
|
||||
@@ -253,7 +296,11 @@ class HIDDeviceBLESteamController extends BluetoothGattCallback implements HIDDe
|
||||
else {
|
||||
Log.v(TAG, "Chromebook: We are connected to a controller, but never discovered services. Trying to recover.");
|
||||
mIsReconnecting = true;
|
||||
mGatt.disconnect();
|
||||
try {
|
||||
mGatt.disconnect();
|
||||
} catch (SecurityException e) {
|
||||
Log.e(TAG, "Permission denied for disconnect: " + e.getMessage());
|
||||
}
|
||||
mGatt = connectGatt(false);
|
||||
break;
|
||||
}
|
||||
@@ -268,7 +315,11 @@ class HIDDeviceBLESteamController extends BluetoothGattCallback implements HIDDe
|
||||
Log.v(TAG, "Chromebook: We have either been disconnected, or the Chromebook BtGatt.ContextMap bug has bitten us. Attempting a disconnect/reconnect, but we may not be able to recover.");
|
||||
|
||||
mIsReconnecting = true;
|
||||
mGatt.disconnect();
|
||||
try {
|
||||
mGatt.disconnect();
|
||||
} catch (SecurityException e) {
|
||||
Log.e(TAG, "Permission denied for disconnect: " + e.getMessage());
|
||||
}
|
||||
mGatt = connectGatt(false);
|
||||
break;
|
||||
|
||||
@@ -328,7 +379,11 @@ class HIDDeviceBLESteamController extends BluetoothGattCallback implements HIDDe
|
||||
Log.e(TAG, "Chromebook: Discovered services were empty; this almost certainly means the BtGatt.ContextMap bug has bitten us.");
|
||||
mIsConnected = false;
|
||||
mIsReconnecting = true;
|
||||
mGatt.disconnect();
|
||||
try {
|
||||
mGatt.disconnect();
|
||||
} catch (SecurityException e) {
|
||||
Log.e(TAG, "Permission denied for disconnect: " + e.getMessage());
|
||||
}
|
||||
mGatt = connectGatt(false);
|
||||
}
|
||||
|
||||
@@ -423,7 +478,11 @@ class HIDDeviceBLESteamController extends BluetoothGattCallback implements HIDDe
|
||||
mHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mGatt.discoverServices();
|
||||
try {
|
||||
mGatt.discoverServices();
|
||||
} catch (SecurityException e) {
|
||||
Log.e(TAG, "Permission denied for service discovery: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -443,7 +502,11 @@ class HIDDeviceBLESteamController extends BluetoothGattCallback implements HIDDe
|
||||
Log.v(TAG, "onServicesDiscovered returned zero services; something has gone horribly wrong down in Android's Bluetooth stack.");
|
||||
mIsReconnecting = true;
|
||||
mIsConnected = false;
|
||||
gatt.disconnect();
|
||||
try {
|
||||
gatt.disconnect();
|
||||
} catch (SecurityException e) {
|
||||
Log.e(TAG, "Permission denied for disconnect: " + e.getMessage());
|
||||
}
|
||||
mGatt = connectGatt(false);
|
||||
}
|
||||
else {
|
||||
@@ -505,7 +568,11 @@ class HIDDeviceBLESteamController extends BluetoothGattCallback implements HIDDe
|
||||
if (reportChr != null) {
|
||||
Log.v(TAG, "Writing report characteristic to enter valve mode");
|
||||
reportChr.setValue(enterValveMode);
|
||||
gatt.writeCharacteristic(reportChr);
|
||||
try {
|
||||
gatt.writeCharacteristic(reportChr);
|
||||
} catch (SecurityException e) {
|
||||
Log.e(TAG, "Permission denied for writing report characteristic: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -638,8 +705,12 @@ class HIDDeviceBLESteamController extends BluetoothGattCallback implements HIDDe
|
||||
|
||||
BluetoothGatt g = mGatt;
|
||||
if (g != null) {
|
||||
g.disconnect();
|
||||
g.close();
|
||||
try {
|
||||
g.disconnect();
|
||||
g.close();
|
||||
} catch (SecurityException e) {
|
||||
Log.e(TAG, "Permission denied for shutdown operations: " + e.getMessage());
|
||||
}
|
||||
mGatt = null;
|
||||
}
|
||||
mManager = null;
|
||||
|
||||
@@ -12,6 +12,7 @@ import android.content.IntentFilter;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.hardware.usb.UsbConstants;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import android.hardware.usb.UsbDevice;
|
||||
import android.hardware.usb.UsbInterface;
|
||||
import android.hardware.usb.UsbManager;
|
||||
@@ -134,7 +135,7 @@ public class HIDDeviceManager {
|
||||
}
|
||||
|
||||
spedit.putInt(identifier, result);
|
||||
spedit.commit();
|
||||
spedit.apply();
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -192,11 +193,7 @@ public class HIDDeviceManager {
|
||||
filter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);
|
||||
filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED);
|
||||
filter.addAction(HIDDeviceManager.ACTION_USB_PERMISSION);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
mContext.registerReceiver(mUsbBroadcast, filter, Context.RECEIVER_EXPORTED);
|
||||
} else {
|
||||
mContext.registerReceiver(mUsbBroadcast, filter);
|
||||
}
|
||||
ContextCompat.registerReceiver(mContext, mUsbBroadcast, filter, ContextCompat.RECEIVER_NOT_EXPORTED);
|
||||
|
||||
for (UsbDevice usbDevice : mUsbManager.getDeviceList().values()) {
|
||||
handleUsbDeviceAttached(usbDevice);
|
||||
@@ -446,7 +443,13 @@ public class HIDDeviceManager {
|
||||
ArrayList<BluetoothDevice> disconnected = new ArrayList<BluetoothDevice>();
|
||||
ArrayList<BluetoothDevice> connected = new ArrayList<BluetoothDevice>();
|
||||
|
||||
List<BluetoothDevice> currentConnected = mBluetoothManager.getConnectedDevices(BluetoothProfile.GATT);
|
||||
List<BluetoothDevice> currentConnected;
|
||||
try {
|
||||
currentConnected = mBluetoothManager.getConnectedDevices(BluetoothProfile.GATT);
|
||||
} catch (SecurityException e) {
|
||||
Log.e(TAG, "Permission denied for getting connected devices: " + e.getMessage());
|
||||
return;
|
||||
}
|
||||
|
||||
for (BluetoothDevice bluetoothDevice : currentConnected) {
|
||||
if (!mLastBluetoothDevices.contains(bluetoothDevice)) {
|
||||
@@ -519,11 +522,21 @@ public class HIDDeviceManager {
|
||||
}
|
||||
|
||||
// If the device has no local name, we really don't want to try an equality check against it.
|
||||
if (bluetoothDevice.getName() == null) {
|
||||
String deviceName;
|
||||
int deviceType;
|
||||
try {
|
||||
deviceName = bluetoothDevice.getName();
|
||||
deviceType = bluetoothDevice.getType();
|
||||
} catch (SecurityException e) {
|
||||
Log.e(TAG, "Permission denied for getting device info: " + e.getMessage());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (deviceName == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return bluetoothDevice.getName().equals("SteamController") && ((bluetoothDevice.getType() & BluetoothDevice.DEVICE_TYPE_LE) != 0);
|
||||
return deviceName.equals("SteamController") && ((deviceType & BluetoothDevice.DEVICE_TYPE_LE) != 0);
|
||||
}
|
||||
|
||||
private void close() {
|
||||
|
||||
@@ -9,6 +9,7 @@ import android.os.Build;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Locale;
|
||||
|
||||
class HIDDeviceUSB implements HIDDevice {
|
||||
|
||||
@@ -36,7 +37,7 @@ class HIDDeviceUSB implements HIDDevice {
|
||||
}
|
||||
|
||||
String getIdentifier() {
|
||||
return String.format("%s/%x/%x/%d", mDevice.getDeviceName(), mDevice.getVendorId(), mDevice.getProductId(), mInterfaceIndex);
|
||||
return String.format(Locale.ROOT, "%s/%x/%x/%d", mDevice.getDeviceName(), mDevice.getVendorId(), mDevice.getProductId(), mInterfaceIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -51,6 +51,7 @@ import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
public class MainActivity extends AppCompatActivity implements GamesCoverDialogFragment.OnGameSelectedListener, ControllerInputHandler.ControllerInputListener {
|
||||
private String m_szGamefile = "";
|
||||
@@ -306,7 +307,7 @@ public class MainActivity extends AppCompatActivity implements GamesCoverDialogF
|
||||
|
||||
private static boolean hasGameExt(String name) {
|
||||
if (TextUtils.isEmpty(name)) return false;
|
||||
String lower = name.toLowerCase();
|
||||
String lower = name.toLowerCase(Locale.ROOT);
|
||||
for (String ext : GAME_EXTS) {
|
||||
if (lower.endsWith(ext)) return true;
|
||||
}
|
||||
@@ -528,7 +529,7 @@ public class MainActivity extends AppCompatActivity implements GamesCoverDialogF
|
||||
if(btn_ogl != null) {
|
||||
btn_ogl.setOnClickListener(v -> {
|
||||
// Save setting first with commit() to ensure immediate write
|
||||
getSharedPreferences("app_prefs", MODE_PRIVATE).edit().putInt("renderer", 12).commit();
|
||||
getSharedPreferences("app_prefs", MODE_PRIVATE).edit().putInt("renderer", 12).apply();
|
||||
|
||||
// Small delay to ensure setting is persisted
|
||||
try {
|
||||
@@ -546,7 +547,7 @@ public class MainActivity extends AppCompatActivity implements GamesCoverDialogF
|
||||
if(btn_vulkan != null) {
|
||||
btn_vulkan.setOnClickListener(v -> {
|
||||
// Save setting first with commit() to ensure immediate write
|
||||
getSharedPreferences("app_prefs", MODE_PRIVATE).edit().putInt("renderer", 14).commit();
|
||||
getSharedPreferences("app_prefs", MODE_PRIVATE).edit().putInt("renderer", 14).apply();
|
||||
|
||||
// Small delay to ensure setting is persisted
|
||||
try {
|
||||
@@ -566,7 +567,7 @@ public class MainActivity extends AppCompatActivity implements GamesCoverDialogF
|
||||
android.util.Log.d("MainActivity", "SW button clicked - saving renderer 13");
|
||||
|
||||
// Save setting first with commit() to ensure immediate write
|
||||
getSharedPreferences("app_prefs", MODE_PRIVATE).edit().putInt("renderer", 13).commit();
|
||||
getSharedPreferences("app_prefs", MODE_PRIVATE).edit().putInt("renderer", 13).apply();
|
||||
android.util.Log.d("MainActivity", "SW renderer setting saved");
|
||||
|
||||
// Small delay to ensure setting is persisted
|
||||
@@ -908,7 +909,7 @@ public class MainActivity extends AppCompatActivity implements GamesCoverDialogF
|
||||
// Filter by name for common virtual/built-in inputs
|
||||
String name = device.getName();
|
||||
if (name != null) {
|
||||
String lower = name.toLowerCase();
|
||||
String lower = name.toLowerCase(Locale.ROOT);
|
||||
if (lower.contains("virtual") || lower.contains("uinput") || lower.contains("touch")
|
||||
|| lower.contains("keyboard") || lower.contains("keypad") || lower.contains("gpio")) {
|
||||
return false;
|
||||
@@ -986,7 +987,7 @@ public class MainActivity extends AppCompatActivity implements GamesCoverDialogF
|
||||
|
||||
private void setRendererAndSave(int renderer) {
|
||||
SharedPreferences prefs = getSharedPreferences("app_prefs", MODE_PRIVATE);
|
||||
prefs.edit().putInt("renderer", renderer).commit();
|
||||
prefs.edit().putInt("renderer", renderer).apply();
|
||||
// Apply live if possible; this path has proven stable via top bar buttons
|
||||
try {
|
||||
NativeApp.renderGpu(renderer);
|
||||
@@ -1054,7 +1055,7 @@ public class MainActivity extends AppCompatActivity implements GamesCoverDialogF
|
||||
String name = f.getName();
|
||||
// Common PCSX2 BIOS components: SCPH*.bin (main), and component ROMs ROM0/ROM1/ROM2/EROM
|
||||
if (name != null) {
|
||||
String lower = name.toLowerCase();
|
||||
String lower = name.toLowerCase(Locale.ROOT);
|
||||
boolean isMainBios = lower.startsWith("scph") && (lower.endsWith(".bin") || lower.endsWith(".rom"));
|
||||
boolean isComponentSuffix = lower.endsWith(".rom0") || lower.endsWith(".rom1") || lower.endsWith(".rom2") || lower.endsWith(".erom");
|
||||
boolean isBareComponent = lower.equals("rom0") || lower.equals("rom1") || lower.equals("rom2") || lower.equals("erom");
|
||||
@@ -1247,7 +1248,12 @@ public class MainActivity extends AppCompatActivity implements GamesCoverDialogF
|
||||
if (treeUri != null) {
|
||||
// Persist read permission for future imports, optional
|
||||
final int takeFlags = (data.getFlags() & (Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION));
|
||||
getContentResolver().takePersistableUriPermission(treeUri, takeFlags);
|
||||
if ((takeFlags & Intent.FLAG_GRANT_READ_URI_PERMISSION) != 0) {
|
||||
getContentResolver().takePersistableUriPermission(treeUri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||
}
|
||||
if ((takeFlags & Intent.FLAG_GRANT_WRITE_URI_PERMISSION) != 0) {
|
||||
getContentResolver().takePersistableUriPermission(treeUri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
|
||||
}
|
||||
|
||||
DocumentFile pickedDir = DocumentFile.fromTreeUri(this, treeUri);
|
||||
if (pickedDir != null && pickedDir.isDirectory()) {
|
||||
@@ -1273,9 +1279,16 @@ public class MainActivity extends AppCompatActivity implements GamesCoverDialogF
|
||||
Uri treeUri = data.getData();
|
||||
if (treeUri != null) {
|
||||
final int takeFlags = (data.getFlags() & (Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION));
|
||||
try {
|
||||
getContentResolver().takePersistableUriPermission(treeUri, takeFlags);
|
||||
} catch (SecurityException ignored) {}
|
||||
if ((takeFlags & Intent.FLAG_GRANT_READ_URI_PERMISSION) != 0) {
|
||||
try {
|
||||
getContentResolver().takePersistableUriPermission(treeUri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||
} catch (SecurityException ignored) {}
|
||||
}
|
||||
if ((takeFlags & Intent.FLAG_GRANT_WRITE_URI_PERMISSION) != 0) {
|
||||
try {
|
||||
getContentResolver().takePersistableUriPermission(treeUri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
|
||||
} catch (SecurityException ignored) {}
|
||||
}
|
||||
// Save folder and show games
|
||||
getSharedPreferences("app_prefs", MODE_PRIVATE)
|
||||
.edit()
|
||||
@@ -1423,13 +1436,8 @@ public class MainActivity extends AppCompatActivity implements GamesCoverDialogF
|
||||
// Initialize HID device manager for USB and Bluetooth controllers
|
||||
mHIDDeviceManager.initialize(true, true);
|
||||
|
||||
// Apply renderer setting again after a delay to override any automatic detection
|
||||
new android.os.Handler(android.os.Looper.getMainLooper()).postDelayed(() -> {
|
||||
SharedPreferences prefs = getSharedPreferences("app_prefs", MODE_PRIVATE);
|
||||
int renderer = prefs.getInt("renderer", 14); // Default to Vulkan if no setting
|
||||
android.util.Log.d("MainActivity", "Re-applying renderer setting after delay: " + renderer);
|
||||
NativeApp.renderGpu(renderer);
|
||||
}, 1000); // 1 second delay
|
||||
// Avoid forcing renderer init when no game/surface is active.
|
||||
// Renderer is applied on game start (restartEmuThread) and from settings when appropriate.
|
||||
}
|
||||
|
||||
private void setSurfaceView(Object p_value) {
|
||||
@@ -1622,6 +1630,7 @@ public class MainActivity extends AppCompatActivity implements GamesCoverDialogF
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
// Fallback for older Android versions
|
||||
super.onBackPressed();
|
||||
showExitDialog();
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ public class QuickActionsDialogFragment extends DialogFragment {
|
||||
@NonNull
|
||||
@Override
|
||||
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
|
||||
View view = LayoutInflater.from(requireContext()).inflate(R.layout.dialog_quick_actions, null, false);
|
||||
View view = getLayoutInflater().inflate(R.layout.dialog_quick_actions, null, false);
|
||||
|
||||
MaterialButton btnOpenGames = view.findViewById(R.id.btn_open_games);
|
||||
MaterialButton btnExitToMenu = view.findViewById(R.id.btn_exit_to_menu);
|
||||
|
||||
@@ -504,14 +504,23 @@ class SDLHapticHandler_API31 extends SDLHapticHandler {
|
||||
return;
|
||||
}
|
||||
|
||||
VibratorManager manager = device.getVibratorManager();
|
||||
int[] vibrators = manager.getVibratorIds();
|
||||
if (vibrators.length >= 2) {
|
||||
vibrate(manager.getVibrator(vibrators[0]), low_frequency_intensity, length);
|
||||
vibrate(manager.getVibrator(vibrators[1]), high_frequency_intensity, length);
|
||||
} else if (vibrators.length == 1) {
|
||||
float intensity = (low_frequency_intensity * 0.6f) + (high_frequency_intensity * 0.4f);
|
||||
vibrate(manager.getVibrator(vibrators[0]), intensity, length);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
VibratorManager manager = device.getVibratorManager();
|
||||
int[] vibrators = manager.getVibratorIds();
|
||||
if (vibrators.length >= 2) {
|
||||
vibrate(manager.getVibrator(vibrators[0]), low_frequency_intensity, length);
|
||||
vibrate(manager.getVibrator(vibrators[1]), high_frequency_intensity, length);
|
||||
} else if (vibrators.length == 1) {
|
||||
float intensity = (low_frequency_intensity * 0.6f) + (high_frequency_intensity * 0.4f);
|
||||
vibrate(manager.getVibrator(vibrators[0]), intensity, length);
|
||||
}
|
||||
} else {
|
||||
// Fallback for older Android versions
|
||||
Vibrator vibrator = device.getVibrator();
|
||||
if (vibrator != null) {
|
||||
float intensity = (low_frequency_intensity * 0.6f) + (high_frequency_intensity * 0.4f);
|
||||
vibrate(vibrator, intensity, length);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -149,7 +149,7 @@ public class SavesDialogFragment extends DialogFragment {
|
||||
@Override
|
||||
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
|
||||
Context ctx = requireContext();
|
||||
View view = LayoutInflater.from(ctx).inflate(R.layout.dialog_saves, null, false);
|
||||
View view = getLayoutInflater().inflate(R.layout.dialog_saves, null, false);
|
||||
|
||||
RecyclerView recyclerView = view.findViewById(R.id.rv_save_slots);
|
||||
recyclerView.setLayoutManager(new LinearLayoutManager(ctx));
|
||||
|
||||
@@ -10,7 +10,7 @@ import android.widget.ArrayAdapter;
|
||||
import android.widget.RadioButton;
|
||||
import android.widget.RadioGroup;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.Switch;
|
||||
import com.google.android.material.materialswitch.MaterialSwitch;
|
||||
import android.content.res.ColorStateList;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
@@ -70,7 +70,7 @@ public class SettingsDialogFragment extends DialogFragment {
|
||||
@Override
|
||||
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
|
||||
Context ctx = requireContext();
|
||||
View view = LayoutInflater.from(ctx).inflate(R.layout.dialog_settings, null, false);
|
||||
View view = getLayoutInflater().inflate(R.layout.dialog_settings, null, false);
|
||||
|
||||
RadioGroup rgRenderer = view.findViewById(R.id.rg_renderer);
|
||||
RadioButton rbGl = view.findViewById(R.id.rb_renderer_gl);
|
||||
@@ -79,11 +79,11 @@ public class SettingsDialogFragment extends DialogFragment {
|
||||
Spinner spScale = view.findViewById(R.id.sp_scale);
|
||||
Spinner spBlending = view.findViewById(R.id.sp_blending_accuracy);
|
||||
Spinner spAspectRatio = view.findViewById(R.id.sp_aspect_ratio);
|
||||
Switch swWidescreen = view.findViewById(R.id.sw_widescreen);
|
||||
Switch swNoInterlacing = view.findViewById(R.id.sw_no_interlacing);
|
||||
Switch swLoadTextures = view.findViewById(R.id.sw_load_textures);
|
||||
Switch swAsyncTextureLoading = view.findViewById(R.id.sw_async_texture_loading);
|
||||
Switch swDevHud = view.findViewById(R.id.sw_dev_hud);
|
||||
MaterialSwitch swWidescreen = view.findViewById(R.id.sw_widescreen);
|
||||
MaterialSwitch swNoInterlacing = view.findViewById(R.id.sw_no_interlacing);
|
||||
MaterialSwitch swLoadTextures = view.findViewById(R.id.sw_load_textures);
|
||||
MaterialSwitch swAsyncTextureLoading = view.findViewById(R.id.sw_async_texture_loading);
|
||||
MaterialSwitch swDevHud = view.findViewById(R.id.sw_dev_hud);
|
||||
View btnPower = view.findViewById(R.id.btn_power);
|
||||
View btnReboot = view.findViewById(R.id.btn_reboot);
|
||||
View btnTestController = view.findViewById(R.id.btn_test_controller);
|
||||
@@ -106,37 +106,7 @@ public class SettingsDialogFragment extends DialogFragment {
|
||||
if (rbVk != null) CompoundButtonCompat.setButtonTintList(rbVk, brandChecked);
|
||||
if (rbSw != null) CompoundButtonCompat.setButtonTintList(rbSw, brandChecked);
|
||||
|
||||
// Switches (thumb = brand when checked; track = subtle brand when checked)
|
||||
ColorStateList thumbTint = new ColorStateList(
|
||||
new int[][]{new int[]{android.R.attr.state_checked}, new int[]{}},
|
||||
new int[]{brand, outline}
|
||||
);
|
||||
int brandTrack = ColorStateList.valueOf(brand).withAlpha(100).getDefaultColor();
|
||||
int outlineTrack = ColorStateList.valueOf(outline).withAlpha(80).getDefaultColor();
|
||||
ColorStateList trackTint = new ColorStateList(
|
||||
new int[][]{new int[]{android.R.attr.state_checked}, new int[]{}},
|
||||
new int[]{brandTrack, outlineTrack}
|
||||
);
|
||||
if (swWidescreen != null) {
|
||||
swWidescreen.setThumbTintList(thumbTint);
|
||||
swWidescreen.setTrackTintList(trackTint);
|
||||
}
|
||||
if (swNoInterlacing != null) {
|
||||
swNoInterlacing.setThumbTintList(thumbTint);
|
||||
swNoInterlacing.setTrackTintList(trackTint);
|
||||
}
|
||||
if (swLoadTextures != null) {
|
||||
swLoadTextures.setThumbTintList(thumbTint);
|
||||
swLoadTextures.setTrackTintList(trackTint);
|
||||
}
|
||||
if (swAsyncTextureLoading != null) {
|
||||
swAsyncTextureLoading.setThumbTintList(thumbTint);
|
||||
swAsyncTextureLoading.setTrackTintList(trackTint);
|
||||
}
|
||||
if (swDevHud != null) {
|
||||
swDevHud.setThumbTintList(thumbTint);
|
||||
swDevHud.setTrackTintList(trackTint);
|
||||
}
|
||||
// MaterialSwitch: rely on default Material3 theme styling from XML style/overlay
|
||||
|
||||
if (btnPower != null) {
|
||||
btnPower.setOnClickListener(v -> {
|
||||
@@ -264,7 +234,7 @@ public class SettingsDialogFragment extends DialogFragment {
|
||||
" (12=OpenGL, 13=Software, 14=Vulkan)");
|
||||
|
||||
// Save renderer setting first with commit() to ensure immediate write
|
||||
prefs.edit().putInt("renderer", renderer).commit();
|
||||
prefs.edit().putInt("renderer", renderer).apply();
|
||||
android.util.Log.d("SettingsDialog", "Renderer setting saved successfully");
|
||||
|
||||
// Small delay to ensure setting is persisted
|
||||
|
||||
Reference in New Issue
Block a user