mirror of
https://github.com/WinampDesktop/winamp.git
synced 2025-06-19 05:15:46 -04:00
Android: Multiple changes
- Fix game list display of NTSC-J region - Hook up quick load/save/reset options in emulation view. - Add speed limiter toggle to emulation view. - Add game list scanning options to main menu. - Add resume button (not yet hooked up to save states, it'll start the BIOS shell)
This commit is contained in:
@ -7,10 +7,6 @@ public class AndroidHostInterface
|
||||
{
|
||||
private long nativePointer;
|
||||
|
||||
static {
|
||||
System.loadLibrary("duckstation-native");
|
||||
}
|
||||
|
||||
static public native AndroidHostInterface create(Context context);
|
||||
|
||||
public AndroidHostInterface(long nativePointer)
|
||||
@ -28,4 +24,26 @@ public class AndroidHostInterface
|
||||
public native void setControllerType(int index, String typeName);
|
||||
public native void setControllerButtonState(int index, int buttonCode, boolean pressed);
|
||||
public static native int getControllerButtonCode(String controllerType, String buttonName);
|
||||
|
||||
public native void refreshGameList(boolean invalidateCache, boolean invalidateDatabase);
|
||||
public native GameListEntry[] getGameListEntries();
|
||||
|
||||
public native void resetSystem();
|
||||
public native void loadState(boolean global, int slot);
|
||||
public native void saveState(boolean global, int slot);
|
||||
public native void applySettings();
|
||||
|
||||
static {
|
||||
System.loadLibrary("duckstation-native");
|
||||
}
|
||||
|
||||
static private AndroidHostInterface mInstance;
|
||||
static public boolean createInstance(Context context) {
|
||||
mInstance = create(context);
|
||||
return mInstance != null;
|
||||
}
|
||||
|
||||
static public AndroidHostInterface getInstance() {
|
||||
return mInstance;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,8 @@
|
||||
package com.github.stenzek.duckstation;
|
||||
|
||||
public enum DiscRegion {
|
||||
NTSC_J,
|
||||
NTSC_U,
|
||||
PAL,
|
||||
Other
|
||||
}
|
@ -6,6 +6,7 @@ import androidx.appcompat.app.ActionBar;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.util.Log;
|
||||
@ -18,6 +19,7 @@ import android.view.MenuItem;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
import androidx.core.app.NavUtils;
|
||||
import androidx.preference.PreferenceManager;
|
||||
|
||||
/**
|
||||
* An example full-screen activity that shows and hides the system UI (i.e.
|
||||
@ -25,9 +27,17 @@ import androidx.core.app.NavUtils;
|
||||
*/
|
||||
public class EmulationActivity extends AppCompatActivity implements SurfaceHolder.Callback {
|
||||
/**
|
||||
* Interface to the native emulator core
|
||||
* Settings interfaces.
|
||||
*/
|
||||
AndroidHostInterface mHostInterface;
|
||||
SharedPreferences mPreferences;
|
||||
private boolean getBooleanSetting(String key, boolean defaultValue) {
|
||||
return mPreferences.getBoolean(key, defaultValue);
|
||||
}
|
||||
private void setBooleanSetting(String key, boolean value) {
|
||||
SharedPreferences.Editor editor = mPreferences.edit();
|
||||
editor.putBoolean(key, value);
|
||||
editor.apply();
|
||||
}
|
||||
|
||||
/**
|
||||
* Touchscreen controller overlay
|
||||
@ -96,15 +106,16 @@ public class EmulationActivity extends AppCompatActivity implements SurfaceHolde
|
||||
@Override
|
||||
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
|
||||
// Once we get a surface, we can boot.
|
||||
if (mHostInterface.isEmulationThreadRunning()) {
|
||||
mHostInterface.surfaceChanged(holder.getSurface(), format, width, height);
|
||||
if (AndroidHostInterface.getInstance().isEmulationThreadRunning()) {
|
||||
AndroidHostInterface.getInstance().surfaceChanged(holder.getSurface(), format, width, height);
|
||||
return;
|
||||
}
|
||||
|
||||
String bootPath = getIntent().getStringExtra("bootPath");
|
||||
String bootSaveStatePath = getIntent().getStringExtra("bootSaveStatePath");
|
||||
boolean resumeState = getIntent().getBooleanExtra("resumeState", false);
|
||||
|
||||
if (!mHostInterface
|
||||
if (!AndroidHostInterface.getInstance()
|
||||
.startEmulationThread(holder.getSurface(), bootPath, bootSaveStatePath)) {
|
||||
Log.e("EmulationActivity", "Failed to start emulation thread");
|
||||
finishActivity(0);
|
||||
@ -114,16 +125,17 @@ public class EmulationActivity extends AppCompatActivity implements SurfaceHolde
|
||||
|
||||
@Override
|
||||
public void surfaceDestroyed(SurfaceHolder holder) {
|
||||
if (!mHostInterface.isEmulationThreadRunning())
|
||||
if (!AndroidHostInterface.getInstance().isEmulationThreadRunning())
|
||||
return;
|
||||
|
||||
Log.i("EmulationActivity", "Stopping emulation thread");
|
||||
mHostInterface.stopEmulationThread();
|
||||
AndroidHostInterface.getInstance().stopEmulationThread();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
mPreferences = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
|
||||
setContentView(R.layout.activity_emulation);
|
||||
ActionBar actionBar = getSupportActionBar();
|
||||
@ -142,19 +154,15 @@ public class EmulationActivity extends AppCompatActivity implements SurfaceHolde
|
||||
}
|
||||
});
|
||||
|
||||
mHostInterface = AndroidHostInterface.create(this);
|
||||
if (mHostInterface == null)
|
||||
throw new InstantiationError("Failed to create host interface");
|
||||
|
||||
// Create touchscreen controller.
|
||||
FrameLayout activityLayout = findViewById(R.id.frameLayout);
|
||||
mTouchscreenController = new TouchscreenControllerView(this);
|
||||
activityLayout.addView(mTouchscreenController);
|
||||
mTouchscreenController.init(0, "DigitalController", mHostInterface);
|
||||
mTouchscreenController.init(0, "DigitalController", AndroidHostInterface.getInstance());
|
||||
setTouchscreenControllerVisibility(true);
|
||||
|
||||
// Hook up controller input.
|
||||
mContentView.initControllerKeyMapping(mHostInterface, "DigitalController");
|
||||
mContentView.initControllerKeyMapping("DigitalController");
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -172,6 +180,7 @@ public class EmulationActivity extends AppCompatActivity implements SurfaceHolde
|
||||
// Inflate the menu; this adds items to the action bar if it is present.
|
||||
getMenuInflater().inflate(R.menu.menu_emulation, menu);
|
||||
menu.findItem(R.id.show_controller).setChecked(mTouchscreenControllerVisible);
|
||||
menu.findItem(R.id.enable_speed_limiter).setChecked(getBooleanSetting("Main/SpeedLimiterEnabled", true));
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -191,6 +200,18 @@ public class EmulationActivity extends AppCompatActivity implements SurfaceHolde
|
||||
setTouchscreenControllerVisibility(!mTouchscreenControllerVisible);
|
||||
item.setChecked(mTouchscreenControllerVisible);
|
||||
return true;
|
||||
} else if (id == R.id.enable_speed_limiter) {
|
||||
boolean newSetting = !getBooleanSetting("Main/SpeedLimiterEnabled", true);
|
||||
setBooleanSetting("Main/SpeedLimiterEnabled", newSetting);
|
||||
item.setChecked(newSetting);
|
||||
AndroidHostInterface.getInstance().applySettings();
|
||||
return true;
|
||||
} else if (id == R.id.reset) {
|
||||
AndroidHostInterface.getInstance().resetSystem();
|
||||
} else if (id == R.id.quick_load) {
|
||||
AndroidHostInterface.getInstance().loadState(false, 0);
|
||||
} else if (id == R.id.quick_save) {
|
||||
AndroidHostInterface.getInstance().saveState(false, 0);
|
||||
} else if (id == R.id.quit) {
|
||||
finish();
|
||||
return true;
|
||||
|
@ -48,7 +48,6 @@ public class EmulationSurfaceView extends SurfaceView {
|
||||
return super.onKeyDown(keyCode, event);
|
||||
}
|
||||
|
||||
private AndroidHostInterface mHostInterface;
|
||||
private ArrayMap<Integer, Integer> mControllerKeyMapping;
|
||||
|
||||
private void addControllerKeyMapping(int keyCode, String controllerType, String buttonName) {
|
||||
@ -59,9 +58,7 @@ public class EmulationSurfaceView extends SurfaceView {
|
||||
mControllerKeyMapping.put(keyCode, mapping);
|
||||
}
|
||||
|
||||
public void initControllerKeyMapping(AndroidHostInterface hostInterface,
|
||||
String controllerType) {
|
||||
mHostInterface = hostInterface;
|
||||
public void initControllerKeyMapping(String controllerType) {
|
||||
mControllerKeyMapping = new ArrayMap<>();
|
||||
|
||||
// TODO: Don't hardcode...
|
||||
@ -86,7 +83,7 @@ public class EmulationSurfaceView extends SurfaceView {
|
||||
return false;
|
||||
|
||||
final int mapping = mControllerKeyMapping.get(keyCode);
|
||||
mHostInterface.setControllerButtonState(0, mapping, pressed);
|
||||
AndroidHostInterface.getInstance().setControllerButtonState(0, mapping, pressed);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -15,44 +15,21 @@ import androidx.preference.PreferenceManager;
|
||||
import java.util.Set;
|
||||
|
||||
public class GameList {
|
||||
static {
|
||||
System.loadLibrary("duckstation-native");
|
||||
}
|
||||
|
||||
private Context mContext;
|
||||
private String mCachePath;
|
||||
private String mRedumpDatPath;
|
||||
private String[] mSearchDirectories;
|
||||
private boolean mSearchRecursively;
|
||||
private GameListEntry[] mEntries;
|
||||
|
||||
static private native GameListEntry[] getEntries(String cachePath, String redumpDatPath,
|
||||
String[] searchDirectories,
|
||||
boolean searchRecursively);
|
||||
private ListViewAdapter mAdapter;
|
||||
|
||||
public GameList(Context context) {
|
||||
mContext = context;
|
||||
refresh();
|
||||
mAdapter = new ListViewAdapter();
|
||||
mEntries = new GameListEntry[0];
|
||||
}
|
||||
|
||||
public void refresh() {
|
||||
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(mContext);
|
||||
mCachePath = preferences.getString("GameList/CachePath", "");
|
||||
mRedumpDatPath = preferences.getString("GameList/RedumpDatPath", "");
|
||||
|
||||
Set<String> searchDirectories =
|
||||
preferences.getStringSet("GameList/SearchDirectories", null);
|
||||
if (searchDirectories != null) {
|
||||
mSearchDirectories = new String[searchDirectories.size()];
|
||||
searchDirectories.toArray(mSearchDirectories);
|
||||
} else {
|
||||
mSearchDirectories = new String[0];
|
||||
}
|
||||
|
||||
mSearchRecursively = preferences.getBoolean("GameList/SearchRecursively", true);
|
||||
|
||||
public void refresh(boolean invalidateCache, boolean invalidateDatabase) {
|
||||
// Search and get entries from native code
|
||||
mEntries = getEntries(mCachePath, mRedumpDatPath, mSearchDirectories, mSearchRecursively);
|
||||
AndroidHostInterface.getInstance().refreshGameList(invalidateCache, invalidateDatabase);
|
||||
mEntries = AndroidHostInterface.getInstance().getGameListEntries();
|
||||
mAdapter.notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public int getEntryCount() {
|
||||
@ -97,6 +74,6 @@ public class GameList {
|
||||
}
|
||||
|
||||
public BaseAdapter getListViewAdapter() {
|
||||
return new ListViewAdapter();
|
||||
return mAdapter;
|
||||
}
|
||||
}
|
||||
|
@ -28,7 +28,7 @@ public class GameListEntry {
|
||||
private String mTitle;
|
||||
private long mSize;
|
||||
private String mModifiedTime;
|
||||
private ConsoleRegion mRegion;
|
||||
private DiscRegion mRegion;
|
||||
private EntryType mType;
|
||||
private CompatibilityRating mCompatibilityRating;
|
||||
|
||||
@ -42,9 +42,9 @@ public class GameListEntry {
|
||||
mModifiedTime = modifiedTime;
|
||||
|
||||
try {
|
||||
mRegion = ConsoleRegion.valueOf(region);
|
||||
mRegion = DiscRegion.valueOf(region);
|
||||
} catch (IllegalArgumentException e) {
|
||||
mRegion = ConsoleRegion.NTSC_U;
|
||||
mRegion = DiscRegion.NTSC_U;
|
||||
}
|
||||
|
||||
try {
|
||||
@ -74,7 +74,7 @@ public class GameListEntry {
|
||||
|
||||
public String getModifiedTime() { return mModifiedTime; }
|
||||
|
||||
public ConsoleRegion getRegion() {
|
||||
public DiscRegion getRegion() {
|
||||
return mRegion;
|
||||
}
|
||||
|
||||
|
@ -43,22 +43,26 @@ public class MainActivity extends AppCompatActivity {
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
if (!AndroidHostInterface.createInstance(this)) {
|
||||
Log.i("MainActivity", "Failed to create host interface");
|
||||
throw new RuntimeException("Failed to create host interface");
|
||||
}
|
||||
|
||||
setContentView(R.layout.activity_main);
|
||||
Toolbar toolbar = findViewById(R.id.toolbar);
|
||||
setSupportActionBar(toolbar);
|
||||
|
||||
FloatingActionButton fab = findViewById(R.id.fab);
|
||||
fab.setOnClickListener(new View.OnClickListener() {
|
||||
findViewById(R.id.fab_add_game_directory).setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
if (!checkForExternalStoragePermissions())
|
||||
return;
|
||||
|
||||
Intent i = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
|
||||
i.addCategory(Intent.CATEGORY_DEFAULT);
|
||||
i.putExtra(Intent.EXTRA_LOCAL_ONLY, true);
|
||||
startActivityForResult(Intent.createChooser(i, "Choose directory"),
|
||||
REQUEST_ADD_DIRECTORY_TO_GAME_LIST);
|
||||
startAddGameDirectory();
|
||||
}
|
||||
});
|
||||
findViewById(R.id.fab_resume).setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
startEmulation(null, true);
|
||||
}
|
||||
});
|
||||
|
||||
@ -69,7 +73,7 @@ public class MainActivity extends AppCompatActivity {
|
||||
mGameListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||
startEmulation(mGameList.getEntry(position).getPath());
|
||||
startEmulation(mGameList.getEntry(position).getPath(), true);
|
||||
}
|
||||
});
|
||||
mGameListView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
|
||||
@ -89,6 +93,18 @@ public class MainActivity extends AppCompatActivity {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
mGameList.refresh(false, false);
|
||||
}
|
||||
|
||||
private void startAddGameDirectory() {
|
||||
if (!checkForExternalStoragePermissions())
|
||||
return;
|
||||
|
||||
Intent i = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
|
||||
i.addCategory(Intent.CATEGORY_DEFAULT);
|
||||
i.putExtra(Intent.EXTRA_LOCAL_ONLY, true);
|
||||
startActivityForResult(Intent.createChooser(i, "Choose directory"),
|
||||
REQUEST_ADD_DIRECTORY_TO_GAME_LIST);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -106,6 +122,13 @@ public class MainActivity extends AppCompatActivity {
|
||||
int id = item.getItemId();
|
||||
|
||||
//noinspection SimplifiableIfStatement
|
||||
if (id == R.id.action_add_game_directory) {
|
||||
startAddGameDirectory();
|
||||
} else if (id == R.id.action_scan_for_new_games) {
|
||||
mGameList.refresh(false, false);
|
||||
} if (id == R.id.action_rescan_all_games) {
|
||||
mGameList.refresh(true, false);
|
||||
}
|
||||
if (id == R.id.action_settings) {
|
||||
Intent intent = new Intent(this, SettingsActivity.class);
|
||||
startActivity(intent);
|
||||
@ -132,16 +155,16 @@ public class MainActivity extends AppCompatActivity {
|
||||
}
|
||||
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
Set<String> currentValues = prefs.getStringSet("GameList/SearchDirectories", null);
|
||||
Set<String> currentValues = prefs.getStringSet("GameList/RecursivePaths", null);
|
||||
if (currentValues == null)
|
||||
currentValues = new HashSet<String>();
|
||||
|
||||
currentValues.add(path);
|
||||
SharedPreferences.Editor editor = prefs.edit();
|
||||
editor.putStringSet("GameList/SearchDirectories", currentValues);
|
||||
editor.putStringSet("GameList/RecursivePaths", currentValues);
|
||||
editor.apply();
|
||||
Log.i("MainActivity", "Added path '" + path + "' to game list search directories");
|
||||
mGameList.refresh();
|
||||
mGameList.refresh(false, false);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@ -175,13 +198,14 @@ public class MainActivity extends AppCompatActivity {
|
||||
}
|
||||
}
|
||||
|
||||
private boolean startEmulation(String bootPath) {
|
||||
private boolean startEmulation(String bootPath, boolean resumeState) {
|
||||
if (!checkForExternalStoragePermissions()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Intent intent = new Intent(this, EmulationActivity.class);
|
||||
intent.putExtra("bootPath", bootPath);
|
||||
intent.putExtra("resumeState", resumeState);
|
||||
startActivity(intent);
|
||||
return true;
|
||||
}
|
||||
|
@ -0,0 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M8,5v14l11,-7z"/>
|
||||
</vector>
|
@ -24,7 +24,17 @@
|
||||
<include layout="@layout/content_main"/>
|
||||
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
android:id="@+id/fab"
|
||||
android:id="@+id/fab_resume"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom|end"
|
||||
android:layout_marginBottom="@dimen/fab_margin"
|
||||
android:layout_marginRight="96dp"
|
||||
app:backgroundTint="@android:color/background_light"
|
||||
app:srcCompat="@drawable/ic_baseline_play_arrow_24" />
|
||||
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
android:id="@+id/fab_add_game_directory"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom|end"
|
||||
|
@ -2,14 +2,22 @@
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<group android:id="@+id/quick_load_save">
|
||||
<item android:title="Quick Load" />
|
||||
<item android:title="Quick Save" />
|
||||
<group android:id="@+id/actions">
|
||||
<item android:id="@+id/reset"
|
||||
android:title="Reset" />
|
||||
<item android:id="@+id/quick_load"
|
||||
android:title="Quick Load" />
|
||||
<item android:id="@+id/quick_save"
|
||||
android:title="Quick Save" />
|
||||
</group>
|
||||
<group android:id="@+id/quick_settings">
|
||||
<item
|
||||
android:id="@+id/change_disc"
|
||||
android:title="Change Disc" />
|
||||
<item
|
||||
android:id="@+id/enable_speed_limiter"
|
||||
android:title="Enable Speed Limiter"
|
||||
android:checkable="true" />
|
||||
<item
|
||||
android:id="@+id/show_controller"
|
||||
android:checkable="true"
|
||||
|
@ -2,6 +2,14 @@
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
tools:context="com.github.stenzek.duckstation.MainActivity" >
|
||||
<group android:id="@+id/game_list">
|
||||
<item android:id="@+id/action_add_game_directory"
|
||||
android:title="Add Game Directory" />
|
||||
<item android:id="@+id/action_scan_for_new_games"
|
||||
android:title="Scan For New Games" />
|
||||
<item android:id="@+id/action_rescan_all_games"
|
||||
android:title="Rescan All Games" />
|
||||
</group>
|
||||
<item android:id="@+id/action_settings"
|
||||
android:title="@string/action_settings"
|
||||
android:orderInCategory="100"
|
||||
|
Reference in New Issue
Block a user