android: frontend: auto-reformat all code for consistent style.

This commit is contained in:
bunnei 2019-07-18 22:39:02 -04:00
parent e2818bf7be
commit 8f89bd53ff
81 changed files with 6629 additions and 7752 deletions

View File

@ -6,18 +6,16 @@ import org.citra.citra_android.model.GameDatabase;
import org.citra.citra_android.services.DirectoryInitializationService; import org.citra.citra_android.services.DirectoryInitializationService;
import org.citra.citra_android.utils.PermissionsHandler; import org.citra.citra_android.utils.PermissionsHandler;
public class DolphinApplication extends Application public class DolphinApplication extends Application {
{ public static GameDatabase databaseHelper;
public static GameDatabase databaseHelper;
@Override @Override
public void onCreate() public void onCreate() {
{ super.onCreate();
super.onCreate();
if (PermissionsHandler.hasWriteAccess(getApplicationContext())) if (PermissionsHandler.hasWriteAccess(getApplicationContext()))
DirectoryInitializationService.startService(getApplicationContext()); DirectoryInitializationService.startService(getApplicationContext());
databaseHelper = new GameDatabase(this); databaseHelper = new GameDatabase(this);
} }
} }

View File

@ -18,411 +18,385 @@ import java.lang.ref.WeakReference;
* Class which contains methods that interact * Class which contains methods that interact
* with the native side of the Dolphin code. * with the native side of the Dolphin code.
*/ */
public final class NativeLibrary public final class NativeLibrary {
{ /**
public static WeakReference<EmulationActivity> sEmulationActivity = new WeakReference<>(null); * Default touchscreen device
*/
public static final String TouchScreenDevice = "Touchscreen";
public static WeakReference<EmulationActivity> sEmulationActivity = new WeakReference<>(null);
private static boolean alertResult = false;
/** static {
* Button type for use in onTouchEvent try {
*/ System.loadLibrary("main");
public static final class ButtonType } catch (UnsatisfiedLinkError ex) {
{ Log.error("[NativeLibrary] " + ex.toString());
public static final int BUTTON_A = 700;
public static final int BUTTON_B = 701;
public static final int BUTTON_X = 702;
public static final int BUTTON_Y = 703;
public static final int BUTTON_START = 704;
public static final int BUTTON_SELECT = 705;
public static final int BUTTON_HOME = 706;
public static final int BUTTON_ZL = 707;
public static final int BUTTON_ZR = 708;
public static final int DPAD_UP = 709;
public static final int DPAD_DOWN = 710;
public static final int DPAD_LEFT = 711;
public static final int DPAD_RIGHT = 712;
public static final int STICK_LEFT = 713;
public static final int STICK_LEFT_UP = 714;
public static final int STICK_LEFT_DOWN = 715;
public static final int STICK_LEFT_LEFT = 716;
public static final int STICK_LEFT_RIGHT = 717;
public static final int STICK_C = 718;
public static final int STICK_C_UP = 719;
public static final int STICK_C_DOWN = 720;
public static final int STICK_C_LEFT = 771;
public static final int STICK_C_RIGHT = 772;
public static final int TRIGGER_L = 773;
public static final int TRIGGER_R = 774;
}
/**
* Button states
*/
public static final class ButtonState
{
public static final int RELEASED = 0;
public static final int PRESSED = 1;
}
private NativeLibrary()
{
// Disallows instantiation.
}
/**
* Default touchscreen device
*/
public static final String TouchScreenDevice = "Touchscreen";
/**
* Handles button press events for a gamepad.
*
* @param Device The input descriptor of the gamepad.
* @param Button Key code identifying which button was pressed.
* @param Action Mask identifying which action is happening (button pressed down, or button released).
* @return If we handled the button press.
*/
public static native boolean onGamePadEvent(String Device, int Button, int Action);
/**
* Handles gamepad movement events.
*
* @param Device The device ID of the gamepad.
* @param Axis The axis ID
* @param x_axis The value of the x-axis represented by the given ID.
* @param y_axis The value of the y-axis represented by the given ID
*/
public static native boolean onGamePadMoveEvent(String Device, int Axis, float x_axis, float y_axis);
/**
* Handles gamepad movement events.
*
* @param Device The device ID of the gamepad.
* @param Axis_id The axis ID
* @param axis_val The value of the axis represented by the given ID.
*/
public static native boolean onGamePadAxisEvent(String Device, int Axis_id, float axis_val);
/**
* Handles touch events.
*
* @param x_axis The value of the x-axis.
* @param y_axis The value of the y-axis
* @param pressed To identify if the touch held down or released.
*/
public static native void onTouchEvent(float x_axis, float y_axis, boolean pressed);
/**
* Handles touch movement.
*
* @param x_axis The value of the instantaneous x-axis.
* @param y_axis The value of the instantaneous y-axis.
*/
public static native void onTouchMoved(float x_axis, float y_axis);
public static native String GetUserSetting(String gameID, String Section, String Key);
public static native void SetUserSetting(String gameID, String Section, String Key, String Value);
public static native void InitGameIni(String gameID);
/**
* Gets a value from a key in the given ini-based config file.
*
* @param configFile The ini-based config file to get the value from.
* @param Section The section key that the actual key is in.
* @param Key The key to get the value from.
* @param Default The value to return in the event the given key doesn't exist.
* @return the value stored at the key, or a default value if it doesn't exist.
*/
public static native String GetConfig(String configFile, String Section, String Key,
String Default);
/**
* Sets a value to a key in the given ini config file.
*
* @param configFile The ini-based config file to add the value to.
* @param Section The section key for the ini key
* @param Key The actual ini key to set.
* @param Value The string to set the ini key to.
*/
public static native void SetConfig(String configFile, String Section, String Key, String Value);
/**
* Gets the embedded banner within the given ISO/ROM.
*
* @param filename the file path to the ISO/ROM.
* @return an integer array containing the color data for the banner.
*/
public static native int[] GetBanner(String filename);
/**
* Gets the embedded title of the given ISO/ROM.
*
* @param filename The file path to the ISO/ROM.
* @return the embedded title of the ISO/ROM.
*/
public static native String GetTitle(String filename);
public static native String GetDescription(String filename);
public static native String GetGameId(String filename);
public static native int GetCountry(String filename);
public static native String GetCompany(String filename);
public static native long GetFilesize(String filename);
public static native int GetPlatform(String filename);
/**
* Gets the Dolphin version string.
*
* @return the Dolphin version string.
*/
public static native String GetVersionString();
public static native String GetGitRevision();
/**
* Saves a screen capture of the game
*/
public static native void SaveScreenShot();
/**
* Saves a game state to the slot number.
*
* @param slot The slot location to save state to.
* @param wait If false, returns as early as possible.
* If true, returns once the savestate has been written to disk.
*/
public static native void SaveState(int slot, boolean wait);
/**
* Saves a game state to the specified path.
*
* @param path The path to save state to.
* @param wait If false, returns as early as possible.
* If true, returns once the savestate has been written to disk.
*/
public static native void SaveStateAs(String path, boolean wait);
/**
* Loads a game state from the slot number.
*
* @param slot The slot location to load state from.
*/
public static native void LoadState(int slot);
/**
* Loads a game state from the specified path.
*
* @param path The path to load state from.
*/
public static native void LoadStateAs(String path);
/**
* Sets the current working user directory
* If not set, it auto-detects a location
*/
public static native void SetUserDirectory(String directory);
/**
* Returns the current working user directory
*/
public static native String GetUserDirectory();
// Create the config.ini file.
public static native void CreateConfigFile();
public static native int DefaultCPUCore();
/**
* Begins emulation.
*/
public static native void Run(String path);
/**
* Begins emulation from the specified savestate.
*/
public static native void Run(String path, String savestatePath, boolean deleteSavestate);
public static native void ChangeDisc(String path);
// Surface Handling
public static native void SurfaceChanged(Surface surf);
public static native void SurfaceDestroyed();
/**
* Unpauses emulation from a paused state.
*/
public static native void UnPauseEmulation();
/**
* Pauses emulation.
*/
public static native void PauseEmulation();
/**
* Stops emulation.
*/
public static native void StopEmulation();
/**
* Returns true if emulation is running (or is paused).
*/
public static native boolean IsRunning();
/**
* Enables or disables CPU block profiling
*
* @param enable
*/
public static native void SetProfiling(boolean enable);
/**
* Writes out the block profile results
*/
public static native void WriteProfileResults();
/**
* Native EGL functions not exposed by Java bindings
**/
public static native void eglBindAPI(int api);
/**
* Provides a way to refresh the connections on Wiimotes
*/
public static native void RefreshWiimotes();
/**
* Returns the performance stats for the current game
**/
public static native double[] GetPerfStats();
/**
* The methods C++ uses to find references to Java classes and methods
* are really expensive. Rather than calling them every time we want to
* run them, do it once when we load the native library.
*/
private static native void CacheClassesAndMethods();
/**
* Switches the screen layout.
*/
public static native void SwitchScreenLayout();
/**
* Swaps the top and bottom screens.
*/
public static native void SwapScreens();
static
{
try
{
System.loadLibrary("main");
}
catch (UnsatisfiedLinkError ex)
{
Log.error("[NativeLibrary] " + ex.toString());
}
CacheClassesAndMethods();
}
private static boolean alertResult = false;
public static boolean displayAlertMsg(final String caption, final String text,
final boolean yesNo)
{
Log.error("[NativeLibrary] Alert: " + text);
final EmulationActivity emulationActivity = sEmulationActivity.get();
boolean result = false;
if (emulationActivity == null)
{
Log.warning("[NativeLibrary] EmulationActivity is null, can't do panic alert.");
}
else
{
// Create object used for waiting.
final Object lock = new Object();
AlertDialog.Builder builder = new AlertDialog.Builder(emulationActivity)
.setTitle(caption)
.setMessage(text);
// If not yes/no dialog just have one button that dismisses modal,
// otherwise have a yes and no button that sets alertResult accordingly.
if (!yesNo)
{
builder
.setCancelable(false)
.setPositiveButton("OK", (dialog, whichButton) ->
{
dialog.dismiss();
synchronized (lock)
{
lock.notify();
}
});
}
else
{
alertResult = false;
builder
.setPositiveButton("Yes", (dialog, whichButton) ->
{
alertResult = true;
dialog.dismiss();
synchronized (lock)
{
lock.notify();
}
})
.setNegativeButton("No", (dialog, whichButton) ->
{
alertResult = false;
dialog.dismiss();
synchronized (lock)
{
lock.notify();
}
});
}
// Show the AlertDialog on the main thread.
emulationActivity.runOnUiThread(() -> builder.show());
// Wait for the lock to notify that it is complete.
synchronized (lock)
{
try
{
lock.wait();
} }
catch (Exception e)
{
}
}
if (yesNo) CacheClassesAndMethods();
result = alertResult;
} }
return result;
}
public static void setEmulationActivity(EmulationActivity emulationActivity) private NativeLibrary() {
{ // Disallows instantiation.
Log.verbose("[NativeLibrary] Registering EmulationActivity."); }
sEmulationActivity = new WeakReference<>(emulationActivity);
}
public static void clearEmulationActivity() /**
{ * Handles button press events for a gamepad.
Log.verbose("[NativeLibrary] Unregistering EmulationActivity."); *
* @param Device The input descriptor of the gamepad.
* @param Button Key code identifying which button was pressed.
* @param Action Mask identifying which action is happening (button pressed down, or button released).
* @return If we handled the button press.
*/
public static native boolean onGamePadEvent(String Device, int Button, int Action);
sEmulationActivity.clear(); /**
} * Handles gamepad movement events.
*
* @param Device The device ID of the gamepad.
* @param Axis The axis ID
* @param x_axis The value of the x-axis represented by the given ID.
* @param y_axis The value of the y-axis represented by the given ID
*/
public static native boolean onGamePadMoveEvent(String Device, int Axis, float x_axis, float y_axis);
/**
* Handles gamepad movement events.
*
* @param Device The device ID of the gamepad.
* @param Axis_id The axis ID
* @param axis_val The value of the axis represented by the given ID.
*/
public static native boolean onGamePadAxisEvent(String Device, int Axis_id, float axis_val);
/**
* Handles touch events.
*
* @param x_axis The value of the x-axis.
* @param y_axis The value of the y-axis
* @param pressed To identify if the touch held down or released.
*/
public static native void onTouchEvent(float x_axis, float y_axis, boolean pressed);
/**
* Handles touch movement.
*
* @param x_axis The value of the instantaneous x-axis.
* @param y_axis The value of the instantaneous y-axis.
*/
public static native void onTouchMoved(float x_axis, float y_axis);
public static native String GetUserSetting(String gameID, String Section, String Key);
public static native void SetUserSetting(String gameID, String Section, String Key, String Value);
public static native void InitGameIni(String gameID);
/**
* Gets a value from a key in the given ini-based config file.
*
* @param configFile The ini-based config file to get the value from.
* @param Section The section key that the actual key is in.
* @param Key The key to get the value from.
* @param Default The value to return in the event the given key doesn't exist.
* @return the value stored at the key, or a default value if it doesn't exist.
*/
public static native String GetConfig(String configFile, String Section, String Key,
String Default);
/**
* Sets a value to a key in the given ini config file.
*
* @param configFile The ini-based config file to add the value to.
* @param Section The section key for the ini key
* @param Key The actual ini key to set.
* @param Value The string to set the ini key to.
*/
public static native void SetConfig(String configFile, String Section, String Key, String Value);
/**
* Gets the embedded banner within the given ISO/ROM.
*
* @param filename the file path to the ISO/ROM.
* @return an integer array containing the color data for the banner.
*/
public static native int[] GetBanner(String filename);
/**
* Gets the embedded title of the given ISO/ROM.
*
* @param filename The file path to the ISO/ROM.
* @return the embedded title of the ISO/ROM.
*/
public static native String GetTitle(String filename);
public static native String GetDescription(String filename);
public static native String GetGameId(String filename);
public static native int GetCountry(String filename);
public static native String GetCompany(String filename);
public static native long GetFilesize(String filename);
public static native int GetPlatform(String filename);
/**
* Gets the Dolphin version string.
*
* @return the Dolphin version string.
*/
public static native String GetVersionString();
public static native String GetGitRevision();
/**
* Saves a screen capture of the game
*/
public static native void SaveScreenShot();
/**
* Saves a game state to the slot number.
*
* @param slot The slot location to save state to.
* @param wait If false, returns as early as possible.
* If true, returns once the savestate has been written to disk.
*/
public static native void SaveState(int slot, boolean wait);
/**
* Saves a game state to the specified path.
*
* @param path The path to save state to.
* @param wait If false, returns as early as possible.
* If true, returns once the savestate has been written to disk.
*/
public static native void SaveStateAs(String path, boolean wait);
/**
* Loads a game state from the slot number.
*
* @param slot The slot location to load state from.
*/
public static native void LoadState(int slot);
/**
* Loads a game state from the specified path.
*
* @param path The path to load state from.
*/
public static native void LoadStateAs(String path);
/**
* Sets the current working user directory
* If not set, it auto-detects a location
*/
public static native void SetUserDirectory(String directory);
/**
* Returns the current working user directory
*/
public static native String GetUserDirectory();
// Create the config.ini file.
public static native void CreateConfigFile();
public static native int DefaultCPUCore();
/**
* Begins emulation.
*/
public static native void Run(String path);
/**
* Begins emulation from the specified savestate.
*/
public static native void Run(String path, String savestatePath, boolean deleteSavestate);
public static native void ChangeDisc(String path);
// Surface Handling
public static native void SurfaceChanged(Surface surf);
public static native void SurfaceDestroyed();
/**
* Unpauses emulation from a paused state.
*/
public static native void UnPauseEmulation();
/**
* Pauses emulation.
*/
public static native void PauseEmulation();
/**
* Stops emulation.
*/
public static native void StopEmulation();
/**
* Returns true if emulation is running (or is paused).
*/
public static native boolean IsRunning();
/**
* Enables or disables CPU block profiling
*
* @param enable
*/
public static native void SetProfiling(boolean enable);
/**
* Writes out the block profile results
*/
public static native void WriteProfileResults();
/**
* Native EGL functions not exposed by Java bindings
**/
public static native void eglBindAPI(int api);
/**
* Provides a way to refresh the connections on Wiimotes
*/
public static native void RefreshWiimotes();
/**
* Returns the performance stats for the current game
**/
public static native double[] GetPerfStats();
/**
* The methods C++ uses to find references to Java classes and methods
* are really expensive. Rather than calling them every time we want to
* run them, do it once when we load the native library.
*/
private static native void CacheClassesAndMethods();
/**
* Switches the screen layout.
*/
public static native void SwitchScreenLayout();
/**
* Swaps the top and bottom screens.
*/
public static native void SwapScreens();
public static boolean displayAlertMsg(final String caption, final String text,
final boolean yesNo) {
Log.error("[NativeLibrary] Alert: " + text);
final EmulationActivity emulationActivity = sEmulationActivity.get();
boolean result = false;
if (emulationActivity == null) {
Log.warning("[NativeLibrary] EmulationActivity is null, can't do panic alert.");
} else {
// Create object used for waiting.
final Object lock = new Object();
AlertDialog.Builder builder = new AlertDialog.Builder(emulationActivity)
.setTitle(caption)
.setMessage(text);
// If not yes/no dialog just have one button that dismisses modal,
// otherwise have a yes and no button that sets alertResult accordingly.
if (!yesNo) {
builder
.setCancelable(false)
.setPositiveButton("OK", (dialog, whichButton) ->
{
dialog.dismiss();
synchronized (lock) {
lock.notify();
}
});
} else {
alertResult = false;
builder
.setPositiveButton("Yes", (dialog, whichButton) ->
{
alertResult = true;
dialog.dismiss();
synchronized (lock) {
lock.notify();
}
})
.setNegativeButton("No", (dialog, whichButton) ->
{
alertResult = false;
dialog.dismiss();
synchronized (lock) {
lock.notify();
}
});
}
// Show the AlertDialog on the main thread.
emulationActivity.runOnUiThread(() -> builder.show());
// Wait for the lock to notify that it is complete.
synchronized (lock) {
try {
lock.wait();
} catch (Exception e) {
}
}
if (yesNo)
result = alertResult;
}
return result;
}
public static void setEmulationActivity(EmulationActivity emulationActivity) {
Log.verbose("[NativeLibrary] Registering EmulationActivity.");
sEmulationActivity = new WeakReference<>(emulationActivity);
}
public static void clearEmulationActivity() {
Log.verbose("[NativeLibrary] Unregistering EmulationActivity.");
sEmulationActivity.clear();
}
/**
* Button type for use in onTouchEvent
*/
public static final class ButtonType {
public static final int BUTTON_A = 700;
public static final int BUTTON_B = 701;
public static final int BUTTON_X = 702;
public static final int BUTTON_Y = 703;
public static final int BUTTON_START = 704;
public static final int BUTTON_SELECT = 705;
public static final int BUTTON_HOME = 706;
public static final int BUTTON_ZL = 707;
public static final int BUTTON_ZR = 708;
public static final int DPAD_UP = 709;
public static final int DPAD_DOWN = 710;
public static final int DPAD_LEFT = 711;
public static final int DPAD_RIGHT = 712;
public static final int STICK_LEFT = 713;
public static final int STICK_LEFT_UP = 714;
public static final int STICK_LEFT_DOWN = 715;
public static final int STICK_LEFT_LEFT = 716;
public static final int STICK_LEFT_RIGHT = 717;
public static final int STICK_C = 718;
public static final int STICK_C_UP = 719;
public static final int STICK_C_DOWN = 720;
public static final int STICK_C_LEFT = 771;
public static final int STICK_C_RIGHT = 772;
public static final int TRIGGER_L = 773;
public static final int TRIGGER_R = 774;
}
/**
* Button states
*/
public static final class ButtonState {
public static final int RELEASED = 0;
public static final int PRESSED = 1;
}
} }

View File

@ -10,20 +10,17 @@ import org.citra.citra_android.fragments.CustomFilePickerFragment;
import java.io.File; import java.io.File;
public class CustomFilePickerActivity extends FilePickerActivity public class CustomFilePickerActivity extends FilePickerActivity {
@Override
{ protected AbstractFilePickerFragment<File> getFragment(
@Override @Nullable final String startPath, final int mode, final boolean allowMultiple,
protected AbstractFilePickerFragment<File> getFragment( final boolean allowCreateDir, final boolean allowExistingFile,
@Nullable final String startPath, final int mode, final boolean allowMultiple, final boolean singleClick) {
final boolean allowCreateDir, final boolean allowExistingFile, AbstractFilePickerFragment<File> fragment = new CustomFilePickerFragment();
final boolean singleClick) // startPath is allowed to be null. In that case, default folder should be SD-card and not "/"
{ fragment.setArgs(
AbstractFilePickerFragment<File> fragment = new CustomFilePickerFragment(); startPath != null ? startPath : Environment.getExternalStorageDirectory().getPath(),
// startPath is allowed to be null. In that case, default folder should be SD-card and not "/" mode, allowMultiple, allowCreateDir, allowExistingFile, singleClick);
fragment.setArgs( return fragment;
startPath != null ? startPath : Environment.getExternalStorageDirectory().getPath(), }
mode, allowMultiple, allowCreateDir, allowExistingFile, singleClick);
return fragment;
}
} }

View File

@ -22,230 +22,200 @@ import org.citra.citra_android.viewholders.GameViewHolder;
* large dataset. * large dataset.
*/ */
public final class GameAdapter extends RecyclerView.Adapter<GameViewHolder> implements public final class GameAdapter extends RecyclerView.Adapter<GameViewHolder> implements
View.OnClickListener View.OnClickListener {
{ private Cursor mCursor;
private Cursor mCursor; private GameDataSetObserver mObserver;
private GameDataSetObserver mObserver;
private boolean mDatasetValid; private boolean mDatasetValid;
/** /**
* Initializes the adapter's observer, which watches for changes to the dataset. The adapter will * Initializes the adapter's observer, which watches for changes to the dataset. The adapter will
* display no data until a Cursor is supplied by a CursorLoader. * display no data until a Cursor is supplied by a CursorLoader.
*/ */
public GameAdapter() public GameAdapter() {
{ mDatasetValid = false;
mDatasetValid = false; mObserver = new GameDataSetObserver();
mObserver = new GameDataSetObserver();
}
/**
* Called by the LayoutManager when it is necessary to create a new view.
*
* @param parent The RecyclerView (I think?) the created view will be thrown into.
* @param viewType Not used here, but useful when more than one type of child will be used in the RecyclerView.
* @return The created ViewHolder with references to all the child view's members.
*/
@Override
public GameViewHolder onCreateViewHolder(ViewGroup parent, int viewType)
{
// Create a new view.
View gameCard = LayoutInflater.from(parent.getContext())
.inflate(R.layout.card_game, parent, false);
gameCard.setOnClickListener(this);
// Use that view to create a ViewHolder.
return new GameViewHolder(gameCard);
}
/**
* Called by the LayoutManager when a new view is not necessary because we can recycle
* an existing one (for example, if a view just scrolled onto the screen from the bottom, we
* can use the view that just scrolled off the top instead of inflating a new one.)
*
* @param holder A ViewHolder representing the view we're recycling.
* @param position The position of the 'new' view in the dataset.
*/
@Override
public void onBindViewHolder(GameViewHolder holder, int position)
{
if (mDatasetValid)
{
if (mCursor.moveToPosition(position))
{
String screenPath = mCursor.getString(GameDatabase.GAME_COLUMN_SCREENSHOT_PATH);
PicassoUtils.loadGameBanner(holder.imageScreenshot, screenPath,
mCursor.getString(GameDatabase.GAME_COLUMN_PATH));
holder.textGameTitle.setText(mCursor.getString(GameDatabase.GAME_COLUMN_TITLE));
holder.textCompany.setText(mCursor.getString(GameDatabase.GAME_COLUMN_COMPANY));
// TODO These shouldn't be necessary once the move to a DB-based model is complete.
holder.gameId = mCursor.getString(GameDatabase.GAME_COLUMN_GAME_ID);
holder.path = mCursor.getString(GameDatabase.GAME_COLUMN_PATH);
holder.title = mCursor.getString(GameDatabase.GAME_COLUMN_TITLE);
holder.description = mCursor.getString(GameDatabase.GAME_COLUMN_DESCRIPTION);
holder.country = mCursor.getInt(GameDatabase.GAME_COLUMN_COUNTRY);
holder.company = mCursor.getString(GameDatabase.GAME_COLUMN_COMPANY);
holder.screenshotPath = mCursor.getString(GameDatabase.GAME_COLUMN_SCREENSHOT_PATH);
}
else
{
Log.error("[GameAdapter] Can't bind view; Cursor is not valid.");
}
}
else
{
Log.error("[GameAdapter] Can't bind view; dataset is not valid.");
}
}
/**
* Called by the LayoutManager to find out how much data we have.
*
* @return Size of the dataset.
*/
@Override
public int getItemCount()
{
if (mDatasetValid && mCursor != null)
{
return mCursor.getCount();
}
Log.error("[GameAdapter] Dataset is not valid.");
return 0;
}
/**
* Return the contents of the _id column for a given row.
*
* @param position The row for which Android wants an ID.
* @return A valid ID from the database, or 0 if not available.
*/
@Override
public long getItemId(int position)
{
if (mDatasetValid && mCursor != null)
{
if (mCursor.moveToPosition(position))
{
return mCursor.getLong(GameDatabase.COLUMN_DB_ID);
}
}
Log.error("[GameAdapter] Dataset is not valid.");
return 0;
}
/**
* Tell Android whether or not each item in the dataset has a stable identifier.
* Which it does, because it's a database, so always tell Android 'true'.
*
* @param hasStableIds ignored.
*/
@Override
public void setHasStableIds(boolean hasStableIds)
{
super.setHasStableIds(true);
}
/**
* When a load is finished, call this to replace the existing data with the newly-loaded
* data.
*
* @param cursor The newly-loaded Cursor.
*/
public void swapCursor(Cursor cursor)
{
// Sanity check.
if (cursor == mCursor)
{
return;
}
// Before getting rid of the old cursor, disassociate it from the Observer.
final Cursor oldCursor = mCursor;
if (oldCursor != null && mObserver != null)
{
oldCursor.unregisterDataSetObserver(mObserver);
}
mCursor = cursor;
if (mCursor != null)
{
// Attempt to associate the new Cursor with the Observer.
if (mObserver != null)
{
mCursor.registerDataSetObserver(mObserver);
}
mDatasetValid = true;
}
else
{
mDatasetValid = false;
}
notifyDataSetChanged();
}
/**
* Launches the game that was clicked on.
*
* @param view The card representing the game the user wants to play.
*/
@Override
public void onClick(View view)
{
GameViewHolder holder = (GameViewHolder) view.getTag();
EmulationActivity.launch((FragmentActivity) view.getContext(),
holder.path,
holder.title,
holder.screenshotPath,
holder.getAdapterPosition(),
holder.imageScreenshot);
}
public static class SpacesItemDecoration extends RecyclerView.ItemDecoration
{
private int space;
public SpacesItemDecoration(int space)
{
this.space = space;
} }
/**
* Called by the LayoutManager when it is necessary to create a new view.
*
* @param parent The RecyclerView (I think?) the created view will be thrown into.
* @param viewType Not used here, but useful when more than one type of child will be used in the RecyclerView.
* @return The created ViewHolder with references to all the child view's members.
*/
@Override @Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, public GameViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
RecyclerView.State state) // Create a new view.
{ View gameCard = LayoutInflater.from(parent.getContext())
outRect.left = space; .inflate(R.layout.card_game, parent, false);
outRect.right = space;
outRect.bottom = space;
outRect.top = space;
}
}
private final class GameDataSetObserver extends DataSetObserver gameCard.setOnClickListener(this);
{
// Use that view to create a ViewHolder.
return new GameViewHolder(gameCard);
}
/**
* Called by the LayoutManager when a new view is not necessary because we can recycle
* an existing one (for example, if a view just scrolled onto the screen from the bottom, we
* can use the view that just scrolled off the top instead of inflating a new one.)
*
* @param holder A ViewHolder representing the view we're recycling.
* @param position The position of the 'new' view in the dataset.
*/
@Override @Override
public void onChanged() public void onBindViewHolder(GameViewHolder holder, int position) {
{ if (mDatasetValid) {
super.onChanged(); if (mCursor.moveToPosition(position)) {
String screenPath = mCursor.getString(GameDatabase.GAME_COLUMN_SCREENSHOT_PATH);
PicassoUtils.loadGameBanner(holder.imageScreenshot, screenPath,
mCursor.getString(GameDatabase.GAME_COLUMN_PATH));
mDatasetValid = true; holder.textGameTitle.setText(mCursor.getString(GameDatabase.GAME_COLUMN_TITLE));
notifyDataSetChanged(); holder.textCompany.setText(mCursor.getString(GameDatabase.GAME_COLUMN_COMPANY));
// TODO These shouldn't be necessary once the move to a DB-based model is complete.
holder.gameId = mCursor.getString(GameDatabase.GAME_COLUMN_GAME_ID);
holder.path = mCursor.getString(GameDatabase.GAME_COLUMN_PATH);
holder.title = mCursor.getString(GameDatabase.GAME_COLUMN_TITLE);
holder.description = mCursor.getString(GameDatabase.GAME_COLUMN_DESCRIPTION);
holder.country = mCursor.getInt(GameDatabase.GAME_COLUMN_COUNTRY);
holder.company = mCursor.getString(GameDatabase.GAME_COLUMN_COMPANY);
holder.screenshotPath = mCursor.getString(GameDatabase.GAME_COLUMN_SCREENSHOT_PATH);
} else {
Log.error("[GameAdapter] Can't bind view; Cursor is not valid.");
}
} else {
Log.error("[GameAdapter] Can't bind view; dataset is not valid.");
}
} }
/**
* Called by the LayoutManager to find out how much data we have.
*
* @return Size of the dataset.
*/
@Override @Override
public void onInvalidated() public int getItemCount() {
{ if (mDatasetValid && mCursor != null) {
super.onInvalidated(); return mCursor.getCount();
}
mDatasetValid = false; Log.error("[GameAdapter] Dataset is not valid.");
notifyDataSetChanged(); return 0;
}
/**
* Return the contents of the _id column for a given row.
*
* @param position The row for which Android wants an ID.
* @return A valid ID from the database, or 0 if not available.
*/
@Override
public long getItemId(int position) {
if (mDatasetValid && mCursor != null) {
if (mCursor.moveToPosition(position)) {
return mCursor.getLong(GameDatabase.COLUMN_DB_ID);
}
}
Log.error("[GameAdapter] Dataset is not valid.");
return 0;
}
/**
* Tell Android whether or not each item in the dataset has a stable identifier.
* Which it does, because it's a database, so always tell Android 'true'.
*
* @param hasStableIds ignored.
*/
@Override
public void setHasStableIds(boolean hasStableIds) {
super.setHasStableIds(true);
}
/**
* When a load is finished, call this to replace the existing data with the newly-loaded
* data.
*
* @param cursor The newly-loaded Cursor.
*/
public void swapCursor(Cursor cursor) {
// Sanity check.
if (cursor == mCursor) {
return;
}
// Before getting rid of the old cursor, disassociate it from the Observer.
final Cursor oldCursor = mCursor;
if (oldCursor != null && mObserver != null) {
oldCursor.unregisterDataSetObserver(mObserver);
}
mCursor = cursor;
if (mCursor != null) {
// Attempt to associate the new Cursor with the Observer.
if (mObserver != null) {
mCursor.registerDataSetObserver(mObserver);
}
mDatasetValid = true;
} else {
mDatasetValid = false;
}
notifyDataSetChanged();
}
/**
* Launches the game that was clicked on.
*
* @param view The card representing the game the user wants to play.
*/
@Override
public void onClick(View view) {
GameViewHolder holder = (GameViewHolder) view.getTag();
EmulationActivity.launch((FragmentActivity) view.getContext(),
holder.path,
holder.title,
holder.screenshotPath,
holder.getAdapterPosition(),
holder.imageScreenshot);
}
public static class SpacesItemDecoration extends RecyclerView.ItemDecoration {
private int space;
public SpacesItemDecoration(int space) {
this.space = space;
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
RecyclerView.State state) {
outRect.left = space;
outRect.right = space;
outRect.bottom = space;
outRect.top = space;
}
}
private final class GameDataSetObserver extends DataSetObserver {
@Override
public void onChanged() {
super.onChanged();
mDatasetValid = true;
notifyDataSetChanged();
}
@Override
public void onInvalidated() {
super.onInvalidated();
mDatasetValid = false;
notifyDataSetChanged();
}
} }
}
} }

View File

@ -17,57 +17,53 @@ import org.citra.citra_android.viewholders.TvGameViewHolder;
* The Leanback library / docs call this a Presenter, but it works very * The Leanback library / docs call this a Presenter, but it works very
* similarly to a RecyclerView.Adapter. * similarly to a RecyclerView.Adapter.
*/ */
public final class GameRowPresenter extends Presenter public final class GameRowPresenter extends Presenter {
{ @Override
@Override public ViewHolder onCreateViewHolder(ViewGroup parent) {
public ViewHolder onCreateViewHolder(ViewGroup parent) // Create a new view.
{ ImageCardView gameCard = new ImageCardView(parent.getContext());
// Create a new view.
ImageCardView gameCard = new ImageCardView(parent.getContext());
gameCard.setMainImageAdjustViewBounds(true); gameCard.setMainImageAdjustViewBounds(true);
gameCard.setMainImageDimensions(48, 48); gameCard.setMainImageDimensions(48, 48);
gameCard.setMainImageScaleType(ImageView.ScaleType.CENTER_CROP); gameCard.setMainImageScaleType(ImageView.ScaleType.CENTER_CROP);
gameCard.setFocusable(true); gameCard.setFocusable(true);
gameCard.setFocusableInTouchMode(true); gameCard.setFocusableInTouchMode(true);
// Use that view to create a ViewHolder. // Use that view to create a ViewHolder.
return new TvGameViewHolder(gameCard); return new TvGameViewHolder(gameCard);
} }
@Override @Override
public void onBindViewHolder(ViewHolder viewHolder, Object item) public void onBindViewHolder(ViewHolder viewHolder, Object item) {
{ TvGameViewHolder holder = (TvGameViewHolder) viewHolder;
TvGameViewHolder holder = (TvGameViewHolder) viewHolder; Game game = (Game) item;
Game game = (Game) item;
String screenPath = game.getScreenshotPath(); String screenPath = game.getScreenshotPath();
holder.imageScreenshot.setImageDrawable(null); holder.imageScreenshot.setImageDrawable(null);
PicassoUtils.loadGameBanner(holder.imageScreenshot, screenPath, game.getPath()); PicassoUtils.loadGameBanner(holder.imageScreenshot, screenPath, game.getPath());
holder.cardParent.setTitleText(game.getTitle()); holder.cardParent.setTitleText(game.getTitle());
holder.cardParent.setContentText(game.getCompany()); holder.cardParent.setContentText(game.getCompany());
// TODO These shouldn't be necessary once the move to a DB-based model is complete. // TODO These shouldn't be necessary once the move to a DB-based model is complete.
holder.gameId = game.getGameId(); holder.gameId = game.getGameId();
holder.path = game.getPath(); holder.path = game.getPath();
holder.title = game.getTitle(); holder.title = game.getTitle();
holder.description = game.getDescription(); holder.description = game.getDescription();
holder.country = game.getCountry(); holder.country = game.getCountry();
holder.company = game.getCompany(); holder.company = game.getCompany();
holder.screenshotPath = game.getScreenshotPath(); holder.screenshotPath = game.getScreenshotPath();
// Set the platform-dependent background color of the card // Set the platform-dependent background color of the card
Context context = holder.cardParent.getContext(); Context context = holder.cardParent.getContext();
Drawable background = ContextCompat.getDrawable(context, R.drawable.tv_card_background_gamecube); Drawable background = ContextCompat.getDrawable(context, R.drawable.tv_card_background_gamecube);
holder.cardParent.setInfoAreaBackground(background); holder.cardParent.setInfoAreaBackground(background);
} }
@Override @Override
public void onUnbindViewHolder(ViewHolder viewHolder) public void onUnbindViewHolder(ViewHolder viewHolder) {
{ // no op
// no op }
}
} }

View File

@ -8,39 +8,35 @@ import android.view.ViewGroup;
import org.citra.citra_android.model.TvSettingsItem; import org.citra.citra_android.model.TvSettingsItem;
import org.citra.citra_android.viewholders.TvSettingsViewHolder; import org.citra.citra_android.viewholders.TvSettingsViewHolder;
public final class SettingsRowPresenter extends Presenter public final class SettingsRowPresenter extends Presenter {
{ public Presenter.ViewHolder onCreateViewHolder(ViewGroup parent) {
public Presenter.ViewHolder onCreateViewHolder(ViewGroup parent) // Create a new view.
{ ImageCardView settingsCard = new ImageCardView(parent.getContext());
// Create a new view.
ImageCardView settingsCard = new ImageCardView(parent.getContext());
settingsCard.setMainImageAdjustViewBounds(true); settingsCard.setMainImageAdjustViewBounds(true);
settingsCard.setMainImageDimensions(192, 160); settingsCard.setMainImageDimensions(192, 160);
settingsCard.setFocusable(true); settingsCard.setFocusable(true);
settingsCard.setFocusableInTouchMode(true); settingsCard.setFocusableInTouchMode(true);
// Use that view to create a ViewHolder. // Use that view to create a ViewHolder.
return new TvSettingsViewHolder(settingsCard); return new TvSettingsViewHolder(settingsCard);
} }
public void onBindViewHolder(Presenter.ViewHolder viewHolder, Object item) public void onBindViewHolder(Presenter.ViewHolder viewHolder, Object item) {
{ TvSettingsViewHolder holder = (TvSettingsViewHolder) viewHolder;
TvSettingsViewHolder holder = (TvSettingsViewHolder) viewHolder; TvSettingsItem settingsItem = (TvSettingsItem) item;
TvSettingsItem settingsItem = (TvSettingsItem) item;
Resources resources = holder.cardParent.getResources(); Resources resources = holder.cardParent.getResources();
holder.itemId = settingsItem.getItemId(); holder.itemId = settingsItem.getItemId();
holder.cardParent.setTitleText(resources.getString(settingsItem.getLabelId())); holder.cardParent.setTitleText(resources.getString(settingsItem.getLabelId()));
holder.cardParent.setMainImage(resources.getDrawable(settingsItem.getIconId(), null)); holder.cardParent.setMainImage(resources.getDrawable(settingsItem.getIconId(), null));
} }
public void onUnbindViewHolder(Presenter.ViewHolder viewHolder) public void onUnbindViewHolder(Presenter.ViewHolder viewHolder) {
{ // no op
// no op }
}
} }

View File

@ -16,82 +16,79 @@ import org.citra.citra_android.activities.EmulationActivity;
import de.hdodenhof.circleimageview.CircleImageView; import de.hdodenhof.circleimageview.CircleImageView;
public final class GameDetailsDialog extends DialogFragment public final class GameDetailsDialog extends DialogFragment {
{ private static final String ARG_GAME_TITLE = "game_title";
private static final String ARG_GAME_TITLE = "game_title"; private static final String ARG_GAME_DESCRIPTION = "game_description";
private static final String ARG_GAME_DESCRIPTION = "game_description"; private static final String ARG_GAME_COUNTRY = "game_country";
private static final String ARG_GAME_COUNTRY = "game_country"; private static final String ARG_GAME_DATE = "game_date";
private static final String ARG_GAME_DATE = "game_date"; private static final String ARG_GAME_PATH = "game_path";
private static final String ARG_GAME_PATH = "game_path"; private static final String ARG_GAME_SCREENSHOT_PATH = "game_screenshot_path";
private static final String ARG_GAME_SCREENSHOT_PATH = "game_screenshot_path";
// TODO Add all of this to the Loader in GameActivity.java // TODO Add all of this to the Loader in GameActivity.java
public static GameDetailsDialog newInstance(String title, String description, int country, public static GameDetailsDialog newInstance(String title, String description, int country,
String company, String path, String screenshotPath) String company, String path, String screenshotPath) {
{ GameDetailsDialog fragment = new GameDetailsDialog();
GameDetailsDialog fragment = new GameDetailsDialog();
Bundle arguments = new Bundle(); Bundle arguments = new Bundle();
arguments.putString(ARG_GAME_TITLE, title); arguments.putString(ARG_GAME_TITLE, title);
arguments.putString(ARG_GAME_DESCRIPTION, description); arguments.putString(ARG_GAME_DESCRIPTION, description);
arguments.putInt(ARG_GAME_COUNTRY, country); arguments.putInt(ARG_GAME_COUNTRY, country);
arguments.putString(ARG_GAME_DATE, company); arguments.putString(ARG_GAME_DATE, company);
arguments.putString(ARG_GAME_PATH, path); arguments.putString(ARG_GAME_PATH, path);
arguments.putString(ARG_GAME_SCREENSHOT_PATH, screenshotPath); arguments.putString(ARG_GAME_SCREENSHOT_PATH, screenshotPath);
fragment.setArguments(arguments); fragment.setArguments(arguments);
return fragment; return fragment;
} }
@Override @Override
public Dialog onCreateDialog(Bundle savedInstanceState) public Dialog onCreateDialog(Bundle savedInstanceState) {
{ AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); ViewGroup contents = (ViewGroup) getActivity().getLayoutInflater()
ViewGroup contents = (ViewGroup) getActivity().getLayoutInflater() .inflate(R.layout.dialog_game_details, null);
.inflate(R.layout.dialog_game_details, null);
final ImageView imageGameScreen = contents.findViewById(R.id.image_game_screen); final ImageView imageGameScreen = contents.findViewById(R.id.image_game_screen);
CircleImageView circleBanner = contents.findViewById(R.id.circle_banner); CircleImageView circleBanner = contents.findViewById(R.id.circle_banner);
TextView textTitle = contents.findViewById(R.id.text_game_title); TextView textTitle = contents.findViewById(R.id.text_game_title);
TextView textDescription = contents.findViewById(R.id.text_company); TextView textDescription = contents.findViewById(R.id.text_company);
TextView textCountry = contents.findViewById(R.id.text_country); TextView textCountry = contents.findViewById(R.id.text_country);
TextView textDate = contents.findViewById(R.id.text_date); TextView textDate = contents.findViewById(R.id.text_date);
FloatingActionButton buttonLaunch = contents.findViewById(R.id.button_launch); FloatingActionButton buttonLaunch = contents.findViewById(R.id.button_launch);
int countryIndex = getArguments().getInt(ARG_GAME_COUNTRY); int countryIndex = getArguments().getInt(ARG_GAME_COUNTRY);
String country = getResources().getStringArray(R.array.countryNames)[countryIndex]; String country = getResources().getStringArray(R.array.countryNames)[countryIndex];
textTitle.setText(getArguments().getString(ARG_GAME_TITLE)); textTitle.setText(getArguments().getString(ARG_GAME_TITLE));
textDescription.setText(getArguments().getString(ARG_GAME_DESCRIPTION)); textDescription.setText(getArguments().getString(ARG_GAME_DESCRIPTION));
textCountry.setText(country); textCountry.setText(country);
textDate.setText(getArguments().getString(ARG_GAME_DATE)); textDate.setText(getArguments().getString(ARG_GAME_DATE));
buttonLaunch.setOnClickListener(view -> buttonLaunch.setOnClickListener(view ->
{ {
// Start the emulation activity and send the path of the clicked ROM to it. // Start the emulation activity and send the path of the clicked ROM to it.
EmulationActivity.launch(getActivity(), EmulationActivity.launch(getActivity(),
getArguments().getString(ARG_GAME_PATH), getArguments().getString(ARG_GAME_PATH),
getArguments().getString(ARG_GAME_TITLE), getArguments().getString(ARG_GAME_TITLE),
getArguments().getString(ARG_GAME_SCREENSHOT_PATH), getArguments().getString(ARG_GAME_SCREENSHOT_PATH),
-1, -1,
imageGameScreen); imageGameScreen);
}); });
// Fill in the view contents. // Fill in the view contents.
Picasso.with(imageGameScreen.getContext()) Picasso.with(imageGameScreen.getContext())
.load(getArguments().getString(ARG_GAME_SCREENSHOT_PATH)) .load(getArguments().getString(ARG_GAME_SCREENSHOT_PATH))
.fit() .fit()
.centerCrop() .centerCrop()
.noFade() .noFade()
.noPlaceholder() .noPlaceholder()
.into(imageGameScreen); .into(imageGameScreen);
circleBanner.setImageResource(R.drawable.no_banner); circleBanner.setImageResource(R.drawable.no_banner);
builder.setView(contents); builder.setView(contents);
return builder.create(); return builder.create();
} }
} }

View File

@ -18,157 +18,141 @@ import java.util.List;
* {@link AlertDialog} derivative that listens for * {@link AlertDialog} derivative that listens for
* motion events from controllers and joysticks. * motion events from controllers and joysticks.
*/ */
public final class MotionAlertDialog extends AlertDialog public final class MotionAlertDialog extends AlertDialog {
{ // The selected input preference
// The selected input preference private final InputBindingSetting setting;
private final InputBindingSetting setting; private final ControllerMappingHelper mControllerMappingHelper;
private final ControllerMappingHelper mControllerMappingHelper; private boolean mWaitingForEvent = true;
private boolean mWaitingForEvent = true;
/** /**
* Constructor * Constructor
* *
* @param context The current {@link Context}. * @param context The current {@link Context}.
* @param setting The Preference to show this dialog for. * @param setting The Preference to show this dialog for.
*/ */
public MotionAlertDialog(Context context, InputBindingSetting setting) public MotionAlertDialog(Context context, InputBindingSetting setting) {
{ super(context);
super(context);
this.setting = setting; this.setting = setting;
this.mControllerMappingHelper = new ControllerMappingHelper(); this.mControllerMappingHelper = new ControllerMappingHelper();
} }
public boolean onKeyEvent(int keyCode, KeyEvent event) public boolean onKeyEvent(int keyCode, KeyEvent event) {
{ Log.debug("[MotionAlertDialog] Received key event: " + event.getAction());
Log.debug("[MotionAlertDialog] Received key event: " + event.getAction()); switch (event.getAction()) {
switch (event.getAction()) case KeyEvent.ACTION_DOWN:
{ if (!mControllerMappingHelper.shouldKeyBeIgnored(event.getDevice(), keyCode)) {
case KeyEvent.ACTION_DOWN: saveKeyInput(event);
if (!mControllerMappingHelper.shouldKeyBeIgnored(event.getDevice(), keyCode)) }
{ // Even if we ignore the key, we still consume it. Thus return true regardless.
saveKeyInput(event); return true;
default:
return false;
} }
// Even if we ignore the key, we still consume it. Thus return true regardless. }
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
// Handle this key if we care about it, otherwise pass it down the framework
return onKeyEvent(event.getKeyCode(), event) || super.dispatchKeyEvent(event);
}
@Override
public boolean dispatchGenericMotionEvent(MotionEvent event) {
// Handle this event if we care about it, otherwise pass it down the framework
return onMotionEvent(event) || super.dispatchGenericMotionEvent(event);
}
private boolean onMotionEvent(MotionEvent event) {
if ((event.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) == 0)
return false;
if (event.getAction() != MotionEvent.ACTION_MOVE)
return false;
InputDevice input = event.getDevice();
List<InputDevice.MotionRange> motionRanges = input.getMotionRanges();
int numMovedAxis = 0;
float axisMoveValue = 0.0f;
InputDevice.MotionRange lastMovedRange = null;
char lastMovedDir = '?';
if (mWaitingForEvent) {
// Get only the axis that seem to have moved (more than .5)
for (InputDevice.MotionRange range : motionRanges) {
int axis = range.getAxis();
float origValue = event.getAxisValue(axis);
float value = mControllerMappingHelper.scaleAxis(input, axis, origValue);
if (Math.abs(value) > 0.5f) {
// It is common to have multiple axis with the same physical input. For example,
// shoulder butters are provided as both AXIS_LTRIGGER and AXIS_BRAKE.
// To handle this, we ignore an axis motion that's the exact same as a motion
// we already saw. This way, we ignore axis with two names, but catch the case
// where a joystick is moved in two directions.
// ref: bottom of https://developer.android.com/training/game-controllers/controller-input.html
if (value != axisMoveValue) {
axisMoveValue = value;
numMovedAxis++;
lastMovedRange = range;
lastMovedDir = value < 0.0f ? '-' : '+';
}
}
}
// If only one axis moved, that's the winner.
if (numMovedAxis == 1) {
mWaitingForEvent = false;
saveMotionInput(input, lastMovedRange, lastMovedDir);
}
}
return true; return true;
default:
return false;
}
}
@Override
public boolean dispatchKeyEvent(KeyEvent event)
{
// Handle this key if we care about it, otherwise pass it down the framework
return onKeyEvent(event.getKeyCode(), event) || super.dispatchKeyEvent(event);
}
@Override
public boolean dispatchGenericMotionEvent(MotionEvent event)
{
// Handle this event if we care about it, otherwise pass it down the framework
return onMotionEvent(event) || super.dispatchGenericMotionEvent(event);
}
private boolean onMotionEvent(MotionEvent event)
{
if ((event.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) == 0)
return false;
if (event.getAction() != MotionEvent.ACTION_MOVE)
return false;
InputDevice input = event.getDevice();
List<InputDevice.MotionRange> motionRanges = input.getMotionRanges();
int numMovedAxis = 0;
float axisMoveValue = 0.0f;
InputDevice.MotionRange lastMovedRange = null;
char lastMovedDir = '?';
if (mWaitingForEvent)
{
// Get only the axis that seem to have moved (more than .5)
for (InputDevice.MotionRange range : motionRanges)
{
int axis = range.getAxis();
float origValue = event.getAxisValue(axis);
float value = mControllerMappingHelper.scaleAxis(input, axis, origValue);
if (Math.abs(value) > 0.5f)
{
// It is common to have multiple axis with the same physical input. For example,
// shoulder butters are provided as both AXIS_LTRIGGER and AXIS_BRAKE.
// To handle this, we ignore an axis motion that's the exact same as a motion
// we already saw. This way, we ignore axis with two names, but catch the case
// where a joystick is moved in two directions.
// ref: bottom of https://developer.android.com/training/game-controllers/controller-input.html
if (value != axisMoveValue)
{
axisMoveValue = value;
numMovedAxis++;
lastMovedRange = range;
lastMovedDir = value < 0.0f ? '-' : '+';
}
}
}
// If only one axis moved, that's the winner.
if (numMovedAxis == 1)
{
mWaitingForEvent = false;
saveMotionInput(input, lastMovedRange, lastMovedDir);
}
} }
return true; /**
} * Saves the provided key input setting both to the INI file (so native code can use it) and as
* an Android preference (so it persists correctly and is human-readable.)
*
* @param keyEvent KeyEvent of this key press.
*/
private void saveKeyInput(KeyEvent keyEvent) {
InputDevice device = keyEvent.getDevice();
String bindStr = "Device '" + device.getDescriptor() + "'-Button " + keyEvent.getKeyCode();
String uiString = device.getName() + ": Button " + keyEvent.getKeyCode();
/** saveInput(bindStr, uiString);
* Saves the provided key input setting both to the INI file (so native code can use it) and as }
* an Android preference (so it persists correctly and is human-readable.)
*
* @param keyEvent KeyEvent of this key press.
*/
private void saveKeyInput(KeyEvent keyEvent)
{
InputDevice device = keyEvent.getDevice();
String bindStr = "Device '" + device.getDescriptor() + "'-Button " + keyEvent.getKeyCode();
String uiString = device.getName() + ": Button " + keyEvent.getKeyCode();
saveInput(bindStr, uiString); /**
} * Saves the provided motion input setting both to the INI file (so native code can use it) and as
* an Android preference (so it persists correctly and is human-readable.)
*
* @param device InputDevice from which the input event originated.
* @param motionRange MotionRange of the movement
* @param axisDir Either '-' or '+'
*/
private void saveMotionInput(InputDevice device, InputDevice.MotionRange motionRange,
char axisDir) {
String bindStr =
"Device '" + device.getDescriptor() + "'-Axis " + motionRange.getAxis() + axisDir;
String uiString = device.getName() + ": Axis " + motionRange.getAxis() + axisDir;
/** saveInput(bindStr, uiString);
* Saves the provided motion input setting both to the INI file (so native code can use it) and as }
* an Android preference (so it persists correctly and is human-readable.)
*
* @param device InputDevice from which the input event originated.
* @param motionRange MotionRange of the movement
* @param axisDir Either '-' or '+'
*/
private void saveMotionInput(InputDevice device, InputDevice.MotionRange motionRange,
char axisDir)
{
String bindStr =
"Device '" + device.getDescriptor() + "'-Axis " + motionRange.getAxis() + axisDir;
String uiString = device.getName() + ": Axis " + motionRange.getAxis() + axisDir;
saveInput(bindStr, uiString); /**
} * Save the input string to settings and SharedPreferences, then dismiss this Dialog.
*/
private void saveInput(String bind, String ui) {
setting.setValue(bind);
/** SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(getContext());
* Save the input string to settings and SharedPreferences, then dismiss this Dialog. SharedPreferences.Editor editor = preferences.edit();
*/
private void saveInput(String bind, String ui)
{
setting.setValue(bind);
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(getContext()); editor.putString(setting.getKey(), ui);
SharedPreferences.Editor editor = preferences.edit(); editor.apply();
editor.putString(setting.getKey(), ui); dismiss();
editor.apply(); }
dismiss();
}
} }

View File

@ -8,15 +8,13 @@ import com.nononsenseapps.filepicker.FilePickerFragment;
import java.io.File; import java.io.File;
public class CustomFilePickerFragment extends FilePickerFragment public class CustomFilePickerFragment extends FilePickerFragment {
{ @NonNull
@NonNull @Override
@Override public Uri toUri(@NonNull final File file) {
public Uri toUri(@NonNull final File file) return FileProvider
{ .getUriForFile(getContext(),
return FileProvider getContext().getApplicationContext().getPackageName() + ".filesprovider",
.getUriForFile(getContext(), file);
getContext().getApplicationContext().getPackageName() + ".filesprovider", }
file);
}
} }

View File

@ -27,465 +27,378 @@ import org.citra.citra_android.services.DirectoryInitializationService.Directory
import org.citra.citra_android.utils.DirectoryStateReceiver; import org.citra.citra_android.utils.DirectoryStateReceiver;
import org.citra.citra_android.utils.Log; import org.citra.citra_android.utils.Log;
public final class EmulationFragment extends Fragment implements SurfaceHolder.Callback public final class EmulationFragment extends Fragment implements SurfaceHolder.Callback {
{ private static final String KEY_GAMEPATH = "gamepath";
private static final String KEY_GAMEPATH = "gamepath";
private static final Handler perfStatsUpdateHandler = new Handler(); private static final Handler perfStatsUpdateHandler = new Handler();
private SharedPreferences mPreferences; private SharedPreferences mPreferences;
private InputOverlay mInputOverlay; private InputOverlay mInputOverlay;
private EmulationState mEmulationState; private EmulationState mEmulationState;
private DirectoryStateReceiver directoryStateReceiver; private DirectoryStateReceiver directoryStateReceiver;
private EmulationActivity activity; private EmulationActivity activity;
private TextView mPerfStats; private TextView mPerfStats;
private Runnable perfStatsUpdater; private Runnable perfStatsUpdater;
public static EmulationFragment newInstance(String gamePath) public static EmulationFragment newInstance(String gamePath) {
{
Bundle args = new Bundle(); Bundle args = new Bundle();
args.putString(KEY_GAMEPATH, gamePath); args.putString(KEY_GAMEPATH, gamePath);
EmulationFragment fragment = new EmulationFragment(); EmulationFragment fragment = new EmulationFragment();
fragment.setArguments(args); fragment.setArguments(args);
return fragment; return fragment;
}
@Override
public void onAttach(Context context)
{
super.onAttach(context);
if (context instanceof EmulationActivity)
{
activity = (EmulationActivity) context;
NativeLibrary.setEmulationActivity((EmulationActivity) context);
}
else
{
throw new IllegalStateException("EmulationFragment must have EmulationActivity parent");
}
}
/**
* Initialize anything that doesn't depend on the layout / views in here.
*/
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
// So this fragment doesn't restart on configuration changes; i.e. rotation.
setRetainInstance(true);
mPreferences = PreferenceManager.getDefaultSharedPreferences(getActivity());
String gamePath = getArguments().getString(KEY_GAMEPATH);
mEmulationState = new EmulationState(gamePath);
}
/**
* Initialize the UI and start emulation in here.
*/
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
View contents = inflater.inflate(R.layout.fragment_emulation, container, false);
SurfaceView surfaceView = contents.findViewById(R.id.surface_emulation);
surfaceView.getHolder().addCallback(this);
mInputOverlay = contents.findViewById(R.id.surface_input_overlay);
if (mInputOverlay != null)
{
// If the input overlay was previously disabled, then don't show it.
if (!mPreferences.getBoolean("showInputOverlay", true))
{
mInputOverlay.setVisibility(View.GONE);
}
} }
Button doneButton = contents.findViewById(R.id.done_control_config); @Override
if (doneButton != null) public void onAttach(Context context) {
{ super.onAttach(context);
doneButton.setOnClickListener(v -> stopConfiguringControls());
}
mPerfStats = contents.findViewById(R.id.perf_stats_text); if (context instanceof EmulationActivity) {
if (mPerfStats != null) activity = (EmulationActivity) context;
{ NativeLibrary.setEmulationActivity((EmulationActivity) context);
// If the overlay was previously disabled, then don't show it. } else {
if (!mPreferences.getBoolean("showPerfStats", true)) throw new IllegalStateException("EmulationFragment must have EmulationActivity parent");
{
mPerfStats.setVisibility(View.GONE);
}
else
{
updatePerfStats();
}
}
// The new Surface created here will get passed to the native code via onSurfaceChanged.
return contents;
}
@Override
public void onResume()
{
super.onResume();
if (DirectoryInitializationService.areDolphinDirectoriesReady())
{
mEmulationState.run(activity.isActivityRecreated());
}
else
{
setupDolphinDirectoriesThenStartEmulation();
}
}
@Override
public void onPause()
{
if (directoryStateReceiver != null)
{
LocalBroadcastManager.getInstance(getActivity()).unregisterReceiver(directoryStateReceiver);
directoryStateReceiver = null;
}
mEmulationState.pause();
super.onPause();
}
@Override
public void onDetach()
{
NativeLibrary.clearEmulationActivity();
super.onDetach();
}
private void setupDolphinDirectoriesThenStartEmulation()
{
IntentFilter statusIntentFilter = new IntentFilter(
DirectoryInitializationService.BROADCAST_ACTION);
directoryStateReceiver =
new DirectoryStateReceiver(directoryInitializationState ->
{
if (directoryInitializationState ==
DirectoryInitializationState.DOLPHIN_DIRECTORIES_INITIALIZED)
{
mEmulationState.run(activity.isActivityRecreated());
}
else if (directoryInitializationState ==
DirectoryInitializationState.EXTERNAL_STORAGE_PERMISSION_NEEDED)
{
Toast.makeText(getContext(), R.string.write_permission_needed, Toast.LENGTH_SHORT)
.show();
}
else if (directoryInitializationState ==
DirectoryInitializationState.CANT_FIND_EXTERNAL_STORAGE)
{
Toast.makeText(getContext(), R.string.external_storage_not_mounted,
Toast.LENGTH_SHORT)
.show();
}
});
// Registers the DirectoryStateReceiver and its intent filters
LocalBroadcastManager.getInstance(getActivity()).registerReceiver(
directoryStateReceiver,
statusIntentFilter);
DirectoryInitializationService.startService(getActivity());
}
public void toggleInputOverlayVisibility()
{
SharedPreferences.Editor editor = mPreferences.edit();
// If the overlay is currently set to INVISIBLE
if (!mPreferences.getBoolean("showInputOverlay", false))
{
// Set it to VISIBLE
mInputOverlay.setVisibility(View.VISIBLE);
editor.putBoolean("showInputOverlay", true);
}
else
{
// Set it to INVISIBLE
mInputOverlay.setVisibility(View.GONE);
editor.putBoolean("showInputOverlay", false);
}
editor.apply();
}
public void refreshInputOverlay()
{
mInputOverlay.refreshControls();
}
public void resetInputOverlay()
{
mInputOverlay.resetButtonPlacement();
}
public void togglePerfStatsVisibility()
{
SharedPreferences.Editor editor = mPreferences.edit();
// If the overlay is currently set to INVISIBLE
if (!mPreferences.getBoolean("showPerfStats", false))
{
updatePerfStats();
// Set it to VISIBLE
mPerfStats.setVisibility(View.VISIBLE);
editor.putBoolean("showPerfStats", true);
}
else
{
stopPerfStatsUpdates();
// Set it to INVISIBLE
mPerfStats.setVisibility(View.GONE);
editor.putBoolean("showPerfStats", false);
}
editor.apply();
}
private void updatePerfStats()
{
final int SYSTEM_FPS = 0;
final int FPS = 1;
final int FRAMETIME = 2;
final int SPEED = 3;
perfStatsUpdater = () ->
{
double perfStats[] = NativeLibrary.GetPerfStats();
mPerfStats
.setText(String.format("FPS: %.5s\nFrametime: %.7sms\nSpeed: %.4s%%", perfStats[FPS],
perfStats[FRAMETIME] * 1000.0, perfStats[SPEED] * 100.0));
perfStatsUpdateHandler.postDelayed(perfStatsUpdater, 3000 /* 3s */);
};
perfStatsUpdateHandler.post(perfStatsUpdater);
}
private void stopPerfStatsUpdates()
{
if (perfStatsUpdater != null)
{
perfStatsUpdateHandler.removeCallbacks(perfStatsUpdater);
}
}
@Override
public void surfaceCreated(SurfaceHolder holder)
{
// We purposely don't do anything here.
// All work is done in surfaceChanged, which we are guaranteed to get even for surface creation.
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height)
{
Log.debug("[EmulationFragment] Surface changed. Resolution: " + width + "x" + height);
mEmulationState.newSurface(holder.getSurface());
}
@Override
public void surfaceDestroyed(SurfaceHolder holder)
{
mEmulationState.clearSurface();
}
public void stopEmulation()
{
mEmulationState.stop();
}
public void startConfiguringControls()
{
getView().findViewById(R.id.done_control_config).setVisibility(View.VISIBLE);
mInputOverlay.setIsInEditMode(true);
}
public void stopConfiguringControls()
{
getView().findViewById(R.id.done_control_config).setVisibility(View.GONE);
mInputOverlay.setIsInEditMode(false);
}
public boolean isConfiguringControls()
{
return mInputOverlay.isInEditMode();
}
private static class EmulationState
{
private enum State
{
STOPPED, RUNNING, PAUSED
}
private final String mGamePath;
private Thread mEmulationThread;
private State state;
private Surface mSurface;
private boolean mRunWhenSurfaceIsValid;
EmulationState(String gamePath)
{
mGamePath = gamePath;
// Starting state is stopped.
state = State.STOPPED;
}
// Getters for the current state
public synchronized boolean isStopped()
{
return state == State.STOPPED;
}
public synchronized boolean isPaused()
{
return state == State.PAUSED;
}
public synchronized boolean isRunning()
{
return state == State.RUNNING;
}
// State changing methods
public synchronized void stop()
{
if (state != State.STOPPED)
{
Log.debug("[EmulationFragment] Stopping emulation.");
state = State.STOPPED;
NativeLibrary.StopEmulation();
}
else
{
Log.warning("[EmulationFragment] Stop called while already stopped.");
}
}
public synchronized void pause()
{
if (state != State.PAUSED)
{
state = State.PAUSED;
Log.debug("[EmulationFragment] Pausing emulation.");
// Release the surface before pausing, since emulation has to be running for that.
NativeLibrary.SurfaceDestroyed();
NativeLibrary.PauseEmulation();
}
else
{
Log.warning("[EmulationFragment] Pause called while already paused.");
}
}
public synchronized void run(boolean isActivityRecreated)
{
if (isActivityRecreated)
{
if (NativeLibrary.IsRunning())
{
state = State.PAUSED;
} }
}
else
{
Log.debug("[EmulationFragment] activity resumed or fresh start");
}
// If the surface is set, run now. Otherwise, wait for it to get set.
if (mSurface != null)
{
runWithValidSurface();
}
else
{
mRunWhenSurfaceIsValid = true;
}
} }
// Surface callbacks /**
public synchronized void newSurface(Surface surface) * Initialize anything that doesn't depend on the layout / views in here.
{ */
mSurface = surface; @Override
if (mRunWhenSurfaceIsValid) public void onCreate(Bundle savedInstanceState) {
{ super.onCreate(savedInstanceState);
runWithValidSurface();
} // So this fragment doesn't restart on configuration changes; i.e. rotation.
setRetainInstance(true);
mPreferences = PreferenceManager.getDefaultSharedPreferences(getActivity());
String gamePath = getArguments().getString(KEY_GAMEPATH);
mEmulationState = new EmulationState(gamePath);
} }
public synchronized void clearSurface() /**
{ * Initialize the UI and start emulation in here.
if (mSurface == null) */
{ @Override
Log.warning("[EmulationFragment] clearSurface called, but surface already null."); public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
} View contents = inflater.inflate(R.layout.fragment_emulation, container, false);
else
{
mSurface = null;
Log.debug("[EmulationFragment] Surface destroyed.");
if (state == State.RUNNING) SurfaceView surfaceView = contents.findViewById(R.id.surface_emulation);
{ surfaceView.getHolder().addCallback(this);
NativeLibrary.SurfaceDestroyed();
state = State.PAUSED; mInputOverlay = contents.findViewById(R.id.surface_input_overlay);
if (mInputOverlay != null) {
// If the input overlay was previously disabled, then don't show it.
if (!mPreferences.getBoolean("showInputOverlay", true)) {
mInputOverlay.setVisibility(View.GONE);
}
} }
else if (state == State.PAUSED)
{ Button doneButton = contents.findViewById(R.id.done_control_config);
Log.warning("[EmulationFragment] Surface cleared while emulation paused."); if (doneButton != null) {
doneButton.setOnClickListener(v -> stopConfiguringControls());
} }
else
{ mPerfStats = contents.findViewById(R.id.perf_stats_text);
Log.warning("[EmulationFragment] Surface cleared while emulation stopped."); if (mPerfStats != null) {
// If the overlay was previously disabled, then don't show it.
if (!mPreferences.getBoolean("showPerfStats", true)) {
mPerfStats.setVisibility(View.GONE);
} else {
updatePerfStats();
}
} }
}
// The new Surface created here will get passed to the native code via onSurfaceChanged.
return contents;
} }
private void runWithValidSurface() @Override
{ public void onResume() {
mRunWhenSurfaceIsValid = false; super.onResume();
if (state == State.STOPPED) if (DirectoryInitializationService.areDolphinDirectoriesReady()) {
{ mEmulationState.run(activity.isActivityRecreated());
mEmulationThread = new Thread(() -> } else {
{ setupDolphinDirectoriesThenStartEmulation();
NativeLibrary.SurfaceChanged(mSurface); }
Log.debug("[EmulationFragment] Starting emulation thread."); }
NativeLibrary.Run(mGamePath);
}, "NativeEmulation"); @Override
mEmulationThread.start(); public void onPause() {
if (directoryStateReceiver != null) {
} LocalBroadcastManager.getInstance(getActivity()).unregisterReceiver(directoryStateReceiver);
else if (state == State.PAUSED) directoryStateReceiver = null;
{ }
Log.debug("[EmulationFragment] Resuming emulation.");
NativeLibrary.SurfaceChanged(mSurface); mEmulationState.pause();
NativeLibrary.UnPauseEmulation(); super.onPause();
} }
else
{ @Override
Log.debug("[EmulationFragment] Bug, run called while already running."); public void onDetach() {
} NativeLibrary.clearEmulationActivity();
state = State.RUNNING; super.onDetach();
}
private void setupDolphinDirectoriesThenStartEmulation() {
IntentFilter statusIntentFilter = new IntentFilter(
DirectoryInitializationService.BROADCAST_ACTION);
directoryStateReceiver =
new DirectoryStateReceiver(directoryInitializationState ->
{
if (directoryInitializationState ==
DirectoryInitializationState.DOLPHIN_DIRECTORIES_INITIALIZED) {
mEmulationState.run(activity.isActivityRecreated());
} else if (directoryInitializationState ==
DirectoryInitializationState.EXTERNAL_STORAGE_PERMISSION_NEEDED) {
Toast.makeText(getContext(), R.string.write_permission_needed, Toast.LENGTH_SHORT)
.show();
} else if (directoryInitializationState ==
DirectoryInitializationState.CANT_FIND_EXTERNAL_STORAGE) {
Toast.makeText(getContext(), R.string.external_storage_not_mounted,
Toast.LENGTH_SHORT)
.show();
}
});
// Registers the DirectoryStateReceiver and its intent filters
LocalBroadcastManager.getInstance(getActivity()).registerReceiver(
directoryStateReceiver,
statusIntentFilter);
DirectoryInitializationService.startService(getActivity());
}
public void toggleInputOverlayVisibility() {
SharedPreferences.Editor editor = mPreferences.edit();
// If the overlay is currently set to INVISIBLE
if (!mPreferences.getBoolean("showInputOverlay", false)) {
// Set it to VISIBLE
mInputOverlay.setVisibility(View.VISIBLE);
editor.putBoolean("showInputOverlay", true);
} else {
// Set it to INVISIBLE
mInputOverlay.setVisibility(View.GONE);
editor.putBoolean("showInputOverlay", false);
}
editor.apply();
}
public void refreshInputOverlay() {
mInputOverlay.refreshControls();
}
public void resetInputOverlay() {
mInputOverlay.resetButtonPlacement();
}
public void togglePerfStatsVisibility() {
SharedPreferences.Editor editor = mPreferences.edit();
// If the overlay is currently set to INVISIBLE
if (!mPreferences.getBoolean("showPerfStats", false)) {
updatePerfStats();
// Set it to VISIBLE
mPerfStats.setVisibility(View.VISIBLE);
editor.putBoolean("showPerfStats", true);
} else {
stopPerfStatsUpdates();
// Set it to INVISIBLE
mPerfStats.setVisibility(View.GONE);
editor.putBoolean("showPerfStats", false);
}
editor.apply();
}
private void updatePerfStats() {
final int SYSTEM_FPS = 0;
final int FPS = 1;
final int FRAMETIME = 2;
final int SPEED = 3;
perfStatsUpdater = () ->
{
double[] perfStats = NativeLibrary.GetPerfStats();
mPerfStats
.setText(String.format("FPS: %.5s\nFrametime: %.7sms\nSpeed: %.4s%%", perfStats[FPS],
perfStats[FRAMETIME] * 1000.0, perfStats[SPEED] * 100.0));
perfStatsUpdateHandler.postDelayed(perfStatsUpdater, 3000 /* 3s */);
};
perfStatsUpdateHandler.post(perfStatsUpdater);
}
private void stopPerfStatsUpdates() {
if (perfStatsUpdater != null) {
perfStatsUpdateHandler.removeCallbacks(perfStatsUpdater);
}
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
// We purposely don't do anything here.
// All work is done in surfaceChanged, which we are guaranteed to get even for surface creation.
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
Log.debug("[EmulationFragment] Surface changed. Resolution: " + width + "x" + height);
mEmulationState.newSurface(holder.getSurface());
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
mEmulationState.clearSurface();
}
public void stopEmulation() {
mEmulationState.stop();
}
public void startConfiguringControls() {
getView().findViewById(R.id.done_control_config).setVisibility(View.VISIBLE);
mInputOverlay.setIsInEditMode(true);
}
public void stopConfiguringControls() {
getView().findViewById(R.id.done_control_config).setVisibility(View.GONE);
mInputOverlay.setIsInEditMode(false);
}
public boolean isConfiguringControls() {
return mInputOverlay.isInEditMode();
}
private static class EmulationState {
private final String mGamePath;
private Thread mEmulationThread;
private State state;
private Surface mSurface;
private boolean mRunWhenSurfaceIsValid;
EmulationState(String gamePath) {
mGamePath = gamePath;
// Starting state is stopped.
state = State.STOPPED;
}
public synchronized boolean isStopped() {
return state == State.STOPPED;
}
// Getters for the current state
public synchronized boolean isPaused() {
return state == State.PAUSED;
}
public synchronized boolean isRunning() {
return state == State.RUNNING;
}
public synchronized void stop() {
if (state != State.STOPPED) {
Log.debug("[EmulationFragment] Stopping emulation.");
state = State.STOPPED;
NativeLibrary.StopEmulation();
} else {
Log.warning("[EmulationFragment] Stop called while already stopped.");
}
}
// State changing methods
public synchronized void pause() {
if (state != State.PAUSED) {
state = State.PAUSED;
Log.debug("[EmulationFragment] Pausing emulation.");
// Release the surface before pausing, since emulation has to be running for that.
NativeLibrary.SurfaceDestroyed();
NativeLibrary.PauseEmulation();
} else {
Log.warning("[EmulationFragment] Pause called while already paused.");
}
}
public synchronized void run(boolean isActivityRecreated) {
if (isActivityRecreated) {
if (NativeLibrary.IsRunning()) {
state = State.PAUSED;
}
} else {
Log.debug("[EmulationFragment] activity resumed or fresh start");
}
// If the surface is set, run now. Otherwise, wait for it to get set.
if (mSurface != null) {
runWithValidSurface();
} else {
mRunWhenSurfaceIsValid = true;
}
}
// Surface callbacks
public synchronized void newSurface(Surface surface) {
mSurface = surface;
if (mRunWhenSurfaceIsValid) {
runWithValidSurface();
}
}
public synchronized void clearSurface() {
if (mSurface == null) {
Log.warning("[EmulationFragment] clearSurface called, but surface already null.");
} else {
mSurface = null;
Log.debug("[EmulationFragment] Surface destroyed.");
if (state == State.RUNNING) {
NativeLibrary.SurfaceDestroyed();
state = State.PAUSED;
} else if (state == State.PAUSED) {
Log.warning("[EmulationFragment] Surface cleared while emulation paused.");
} else {
Log.warning("[EmulationFragment] Surface cleared while emulation stopped.");
}
}
}
private void runWithValidSurface() {
mRunWhenSurfaceIsValid = false;
if (state == State.STOPPED) {
mEmulationThread = new Thread(() ->
{
NativeLibrary.SurfaceChanged(mSurface);
Log.debug("[EmulationFragment] Starting emulation thread.");
NativeLibrary.Run(mGamePath);
}, "NativeEmulation");
mEmulationThread.start();
} else if (state == State.PAUSED) {
Log.debug("[EmulationFragment] Resuming emulation.");
NativeLibrary.SurfaceChanged(mSurface);
NativeLibrary.UnPauseEmulation();
} else {
Log.debug("[EmulationFragment] Bug, run called while already running.");
}
state = State.RUNNING;
}
private enum State {
STOPPED, RUNNING, PAUSED
}
} }
}
} }

View File

@ -14,65 +14,57 @@ import android.widget.TextView;
import org.citra.citra_android.R; import org.citra.citra_android.R;
import org.citra.citra_android.activities.EmulationActivity; import org.citra.citra_android.activities.EmulationActivity;
public final class MenuFragment extends Fragment implements View.OnClickListener public final class MenuFragment extends Fragment implements View.OnClickListener {
{ private static final String KEY_TITLE = "title";
private static final String KEY_TITLE = "title"; private static SparseIntArray buttonsActionsMap = new SparseIntArray();
private static SparseIntArray buttonsActionsMap = new SparseIntArray();
static static {
{ buttonsActionsMap.append(R.id.menu_emulation_toggle_perf_stats,
buttonsActionsMap.append(R.id.menu_emulation_toggle_perf_stats, EmulationActivity.MENU_ACTION_TOGGLE_PREF_STATS);
EmulationActivity.MENU_ACTION_TOGGLE_PREF_STATS); buttonsActionsMap.append(R.id.menu_exit, EmulationActivity.MENU_ACTION_EXIT);
buttonsActionsMap.append(R.id.menu_exit, EmulationActivity.MENU_ACTION_EXIT); buttonsActionsMap.append(R.id.menu_emulation_switch_screen_layout,
buttonsActionsMap.append(R.id.menu_emulation_switch_screen_layout, EmulationActivity.MENU_ACTION_SWITCH_SCREEN_LAYOUT);
EmulationActivity.MENU_ACTION_SWITCH_SCREEN_LAYOUT); buttonsActionsMap.append(R.id.menu_emulation_swap_screens,
buttonsActionsMap.append(R.id.menu_emulation_swap_screens, EmulationActivity.MENU_ACTION_SWAP_SCREENS);
EmulationActivity.MENU_ACTION_SWAP_SCREENS);
}
public static MenuFragment newInstance(String title)
{
MenuFragment fragment = new MenuFragment();
Bundle arguments = new Bundle();
arguments.putSerializable(KEY_TITLE, title);
fragment.setArguments(arguments);
return fragment;
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
View rootView = inflater.inflate(R.layout.fragment_ingame_menu, container, false);
LinearLayout options = rootView.findViewById(R.id.layout_options);
for (int childIndex = 0; childIndex < options.getChildCount(); childIndex++)
{
Button button = (Button) options.getChildAt(childIndex);
button.setOnClickListener(this);
} }
TextView titleText = rootView.findViewById(R.id.text_game_title); public static MenuFragment newInstance(String title) {
String title = getArguments().getString(KEY_TITLE); MenuFragment fragment = new MenuFragment();
if (title != null)
{ Bundle arguments = new Bundle();
titleText.setText(title); arguments.putSerializable(KEY_TITLE, title);
fragment.setArguments(arguments);
return fragment;
} }
return rootView; @Nullable
} @Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_ingame_menu, container, false);
@SuppressWarnings("WrongConstant") LinearLayout options = rootView.findViewById(R.id.layout_options);
@Override for (int childIndex = 0; childIndex < options.getChildCount(); childIndex++) {
public void onClick(View button) Button button = (Button) options.getChildAt(childIndex);
{
int action = buttonsActionsMap.get(button.getId()); button.setOnClickListener(this);
if (action >= 0) }
{
((EmulationActivity) getActivity()).handleMenuAction(action); TextView titleText = rootView.findViewById(R.id.text_game_title);
String title = getArguments().getString(KEY_TITLE);
if (title != null) {
titleText.setText(title);
}
return rootView;
}
@SuppressWarnings("WrongConstant")
@Override
public void onClick(View button) {
int action = buttonsActionsMap.get(button.getId());
if (action >= 0) {
((EmulationActivity) getActivity()).handleMenuAction(action);
}
} }
}
} }

View File

@ -4,93 +4,82 @@ import android.content.ContentValues;
import android.database.Cursor; import android.database.Cursor;
import android.os.Environment; import android.os.Environment;
public final class Game public final class Game {
{ private static final String PATH_SCREENSHOT_FOLDER =
private static final String PATH_SCREENSHOT_FOLDER = "file://" + Environment.getExternalStorageDirectory().getPath() + "/citra-emu/ScreenShots/";
"file://" + Environment.getExternalStorageDirectory().getPath() + "/citra-emu/ScreenShots/";
private String mTitle; private String mTitle;
private String mDescription; private String mDescription;
private String mPath; private String mPath;
private String mGameId; private String mGameId;
private String mScreenshotPath; private String mScreenshotPath;
private String mCompany; private String mCompany;
private int mCountry; private int mCountry;
public Game(String title, String description, int country, String path, public Game(String title, String description, int country, String path,
String gameId, String company, String screenshotPath) String gameId, String company, String screenshotPath) {
{ mTitle = title;
mTitle = title; mDescription = description;
mDescription = description; mCountry = country;
mCountry = country; mPath = path;
mPath = path; mGameId = gameId;
mGameId = gameId; mCompany = company;
mCompany = company; mScreenshotPath = screenshotPath;
mScreenshotPath = screenshotPath; }
}
public String getTitle() public static ContentValues asContentValues(String title, String description,
{ int country, String path, String gameId, String company) {
return mTitle; ContentValues values = new ContentValues();
}
public String getDescription() String screenPath = PATH_SCREENSHOT_FOLDER + gameId + "/" + gameId + "-1.png";
{
return mDescription;
}
public String getCompany() values.put(GameDatabase.KEY_GAME_TITLE, title);
{ values.put(GameDatabase.KEY_GAME_DESCRIPTION, description);
return mCompany; values.put(GameDatabase.KEY_GAME_COUNTRY, country);
} values.put(GameDatabase.KEY_GAME_PATH, path);
values.put(GameDatabase.KEY_GAME_ID, gameId);
values.put(GameDatabase.KEY_GAME_COMPANY, company);
values.put(GameDatabase.KEY_GAME_SCREENSHOT_PATH, screenPath);
public int getCountry() return values;
{ }
return mCountry;
}
public String getPath() public static Game fromCursor(Cursor cursor) {
{ return new Game(cursor.getString(GameDatabase.GAME_COLUMN_TITLE),
return mPath; cursor.getString(GameDatabase.GAME_COLUMN_DESCRIPTION),
} cursor.getInt(GameDatabase.GAME_COLUMN_COUNTRY),
cursor.getString(GameDatabase.GAME_COLUMN_PATH),
cursor.getString(GameDatabase.GAME_COLUMN_GAME_ID),
cursor.getString(GameDatabase.GAME_COLUMN_COMPANY),
cursor.getString(GameDatabase.GAME_COLUMN_SCREENSHOT_PATH));
}
public String getGameId() public String getTitle() {
{ return mTitle;
return mGameId; }
}
public String getScreenshotPath() public String getDescription() {
{ return mDescription;
return mScreenshotPath; }
}
public static ContentValues asContentValues(String title, String description, public String getCompany() {
int country, String path, String gameId, String company) return mCompany;
{ }
ContentValues values = new ContentValues();
String screenPath = PATH_SCREENSHOT_FOLDER + gameId + "/" + gameId + "-1.png"; public int getCountry() {
return mCountry;
}
values.put(GameDatabase.KEY_GAME_TITLE, title); public String getPath() {
values.put(GameDatabase.KEY_GAME_DESCRIPTION, description); return mPath;
values.put(GameDatabase.KEY_GAME_COUNTRY, country); }
values.put(GameDatabase.KEY_GAME_PATH, path);
values.put(GameDatabase.KEY_GAME_ID, gameId);
values.put(GameDatabase.KEY_GAME_COMPANY, company);
values.put(GameDatabase.KEY_GAME_SCREENSHOT_PATH, screenPath);
return values; public String getGameId() {
} return mGameId;
}
public static Game fromCursor(Cursor cursor) public String getScreenshotPath() {
{ return mScreenshotPath;
return new Game(cursor.getString(GameDatabase.GAME_COLUMN_TITLE), }
cursor.getString(GameDatabase.GAME_COLUMN_DESCRIPTION),
cursor.getInt(GameDatabase.GAME_COLUMN_COUNTRY),
cursor.getString(GameDatabase.GAME_COLUMN_PATH),
cursor.getString(GameDatabase.GAME_COLUMN_GAME_ID),
cursor.getString(GameDatabase.GAME_COLUMN_COMPANY),
cursor.getString(GameDatabase.GAME_COLUMN_SCREENSHOT_PATH));
}
} }

View File

@ -20,273 +20,241 @@ import rx.Observable;
* A helper class that provides several utilities simplifying interaction with * A helper class that provides several utilities simplifying interaction with
* the SQLite database. * the SQLite database.
*/ */
public final class GameDatabase extends SQLiteOpenHelper public final class GameDatabase extends SQLiteOpenHelper {
{ public static final int COLUMN_DB_ID = 0;
private static final int DB_VERSION = 1; public static final int GAME_COLUMN_PATH = 1;
public static final int GAME_COLUMN_TITLE = 2;
public static final int GAME_COLUMN_DESCRIPTION = 3;
public static final int GAME_COLUMN_COUNTRY = 4;
public static final int GAME_COLUMN_GAME_ID = 5;
public static final int GAME_COLUMN_COMPANY = 6;
public static final int GAME_COLUMN_SCREENSHOT_PATH = 7;
public static final int FOLDER_COLUMN_PATH = 1;
public static final String KEY_DB_ID = "_id";
public static final String KEY_GAME_PATH = "path";
public static final String KEY_GAME_TITLE = "title";
public static final String KEY_GAME_DESCRIPTION = "description";
public static final String KEY_GAME_COUNTRY = "country";
public static final String KEY_GAME_ID = "game_id";
public static final String KEY_GAME_COMPANY = "company";
public static final String KEY_GAME_SCREENSHOT_PATH = "screenshot_path";
public static final String KEY_FOLDER_PATH = "path";
public static final String TABLE_NAME_FOLDERS = "folders";
public static final String TABLE_NAME_GAMES = "games";
private static final int DB_VERSION = 1;
private static final String TYPE_PRIMARY = " INTEGER PRIMARY KEY";
private static final String TYPE_INTEGER = " INTEGER";
private static final String TYPE_STRING = " TEXT";
public static final int COLUMN_DB_ID = 0; private static final String CONSTRAINT_UNIQUE = " UNIQUE";
public static final int GAME_COLUMN_PATH = 1; private static final String SEPARATOR = ", ";
public static final int GAME_COLUMN_TITLE = 2;
public static final int GAME_COLUMN_DESCRIPTION = 3;
public static final int GAME_COLUMN_COUNTRY = 4;
public static final int GAME_COLUMN_GAME_ID = 5;
public static final int GAME_COLUMN_COMPANY = 6;
public static final int GAME_COLUMN_SCREENSHOT_PATH = 7;
public static final int FOLDER_COLUMN_PATH = 1; private static final String SQL_CREATE_GAMES = "CREATE TABLE " + TABLE_NAME_GAMES + "("
+ KEY_DB_ID + TYPE_PRIMARY + SEPARATOR
+ KEY_GAME_PATH + TYPE_STRING + SEPARATOR
+ KEY_GAME_TITLE + TYPE_STRING + SEPARATOR
+ KEY_GAME_DESCRIPTION + TYPE_STRING + SEPARATOR
+ KEY_GAME_COUNTRY + TYPE_INTEGER + SEPARATOR
+ KEY_GAME_ID + TYPE_STRING + SEPARATOR
+ KEY_GAME_COMPANY + TYPE_STRING + SEPARATOR
+ KEY_GAME_SCREENSHOT_PATH + TYPE_STRING + ")";
public static final String KEY_DB_ID = "_id"; private static final String SQL_CREATE_FOLDERS = "CREATE TABLE " + TABLE_NAME_FOLDERS + "("
+ KEY_DB_ID + TYPE_PRIMARY + SEPARATOR
+ KEY_FOLDER_PATH + TYPE_STRING + CONSTRAINT_UNIQUE + ")";
public static final String KEY_GAME_PATH = "path"; private static final String SQL_DELETE_FOLDERS = "DROP TABLE IF EXISTS " + TABLE_NAME_FOLDERS;
public static final String KEY_GAME_TITLE = "title"; private static final String SQL_DELETE_GAMES = "DROP TABLE IF EXISTS " + TABLE_NAME_GAMES;
public static final String KEY_GAME_DESCRIPTION = "description";
public static final String KEY_GAME_COUNTRY = "country";
public static final String KEY_GAME_ID = "game_id";
public static final String KEY_GAME_COMPANY = "company";
public static final String KEY_GAME_SCREENSHOT_PATH = "screenshot_path";
public static final String KEY_FOLDER_PATH = "path"; public GameDatabase(Context context) {
// Superclass constructor builds a database or uses an existing one.
public static final String TABLE_NAME_FOLDERS = "folders"; super(context, "games.db", null, DB_VERSION);
public static final String TABLE_NAME_GAMES = "games";
private static final String TYPE_PRIMARY = " INTEGER PRIMARY KEY";
private static final String TYPE_INTEGER = " INTEGER";
private static final String TYPE_STRING = " TEXT";
private static final String CONSTRAINT_UNIQUE = " UNIQUE";
private static final String SEPARATOR = ", ";
private static final String SQL_CREATE_GAMES = "CREATE TABLE " + TABLE_NAME_GAMES + "("
+ KEY_DB_ID + TYPE_PRIMARY + SEPARATOR
+ KEY_GAME_PATH + TYPE_STRING + SEPARATOR
+ KEY_GAME_TITLE + TYPE_STRING + SEPARATOR
+ KEY_GAME_DESCRIPTION + TYPE_STRING + SEPARATOR
+ KEY_GAME_COUNTRY + TYPE_INTEGER + SEPARATOR
+ KEY_GAME_ID + TYPE_STRING + SEPARATOR
+ KEY_GAME_COMPANY + TYPE_STRING + SEPARATOR
+ KEY_GAME_SCREENSHOT_PATH + TYPE_STRING + ")";
private static final String SQL_CREATE_FOLDERS = "CREATE TABLE " + TABLE_NAME_FOLDERS + "("
+ KEY_DB_ID + TYPE_PRIMARY + SEPARATOR
+ KEY_FOLDER_PATH + TYPE_STRING + CONSTRAINT_UNIQUE + ")";
private static final String SQL_DELETE_FOLDERS = "DROP TABLE IF EXISTS " + TABLE_NAME_FOLDERS;
private static final String SQL_DELETE_GAMES = "DROP TABLE IF EXISTS " + TABLE_NAME_GAMES;
public GameDatabase(Context context)
{
// Superclass constructor builds a database or uses an existing one.
super(context, "games.db", null, DB_VERSION);
}
@Override
public void onCreate(SQLiteDatabase database)
{
Log.debug("[GameDatabase] GameDatabase - Creating database...");
execSqlAndLog(database, SQL_CREATE_GAMES);
execSqlAndLog(database, SQL_CREATE_FOLDERS);
}
@Override
public void onDowngrade(SQLiteDatabase database, int oldVersion, int newVersion)
{
Log.verbose("[GameDatabase] Downgrades not supporting, clearing databases..");
execSqlAndLog(database, SQL_DELETE_FOLDERS);
execSqlAndLog(database, SQL_CREATE_FOLDERS);
execSqlAndLog(database, SQL_DELETE_GAMES);
execSqlAndLog(database, SQL_CREATE_GAMES);
}
@Override
public void onUpgrade(SQLiteDatabase database, int oldVersion, int newVersion)
{
Log.info("[GameDatabase] Upgrading database from schema version " + oldVersion + " to " +
newVersion);
// Delete all the games
execSqlAndLog(database, SQL_DELETE_GAMES);
execSqlAndLog(database, SQL_CREATE_GAMES);
Log.verbose("[GameDatabase] Re-scanning library with new schema.");
scanLibrary(database);
}
public void scanLibrary(SQLiteDatabase database)
{
// Before scanning known folders, go through the game table and remove any entries for which the file itself is missing.
Cursor fileCursor = database.query(TABLE_NAME_GAMES,
null, // Get all columns.
null, // Get all rows.
null,
null, // No grouping.
null,
null); // Order of games is irrelevant.
// Possibly overly defensive, but ensures that moveToNext() does not skip a row.
fileCursor.moveToPosition(-1);
while (fileCursor.moveToNext())
{
String gamePath = fileCursor.getString(GAME_COLUMN_PATH);
File game = new File(gamePath);
if (!game.exists())
{
Log.error("[GameDatabase] Game file no longer exists. Removing from the library: " +
gamePath);
database.delete(TABLE_NAME_GAMES,
KEY_DB_ID + " = ?",
new String[]{Long.toString(fileCursor.getLong(COLUMN_DB_ID))});
}
} }
@Override
public void onCreate(SQLiteDatabase database) {
Log.debug("[GameDatabase] GameDatabase - Creating database...");
// Get a cursor listing all the folders the user has added to the library. execSqlAndLog(database, SQL_CREATE_GAMES);
Cursor folderCursor = database.query(TABLE_NAME_FOLDERS, execSqlAndLog(database, SQL_CREATE_FOLDERS);
null, // Get all columns. }
null, // Get all rows.
null,
null, // No grouping.
null,
null); // Order of folders is irrelevant.
Set<String> allowedExtensions = new HashSet<String>(Arrays.asList( @Override
".3ds", ".3ds", ".3dsx", ".elf", ".axf", ".cci", ".cxi", ".app")); public void onDowngrade(SQLiteDatabase database, int oldVersion, int newVersion) {
Log.verbose("[GameDatabase] Downgrades not supporting, clearing databases..");
execSqlAndLog(database, SQL_DELETE_FOLDERS);
execSqlAndLog(database, SQL_CREATE_FOLDERS);
// Possibly overly defensive, but ensures that moveToNext() does not skip a row. execSqlAndLog(database, SQL_DELETE_GAMES);
folderCursor.moveToPosition(-1); execSqlAndLog(database, SQL_CREATE_GAMES);
}
// Iterate through all results of the DB query (i.e. all folders in the library.) @Override
while (folderCursor.moveToNext()) public void onUpgrade(SQLiteDatabase database, int oldVersion, int newVersion) {
{ Log.info("[GameDatabase] Upgrading database from schema version " + oldVersion + " to " +
newVersion);
String folderPath = folderCursor.getString(FOLDER_COLUMN_PATH); // Delete all the games
File folder = new File(folderPath); execSqlAndLog(database, SQL_DELETE_GAMES);
execSqlAndLog(database, SQL_CREATE_GAMES);
Log.info("[GameDatabase] Reading files from library folder: " + folderPath); Log.verbose("[GameDatabase] Re-scanning library with new schema.");
scanLibrary(database);
}
// Iterate through every file in the folder. public void scanLibrary(SQLiteDatabase database) {
File[] children = folder.listFiles(); // Before scanning known folders, go through the game table and remove any entries for which the file itself is missing.
Cursor fileCursor = database.query(TABLE_NAME_GAMES,
null, // Get all columns.
null, // Get all rows.
null,
null, // No grouping.
null,
null); // Order of games is irrelevant.
if (children != null) // Possibly overly defensive, but ensures that moveToNext() does not skip a row.
{ fileCursor.moveToPosition(-1);
for (File file : children)
{
if (!file.isHidden() && !file.isDirectory())
{
String filePath = file.getPath();
int extensionStart = filePath.lastIndexOf('.'); while (fileCursor.moveToNext()) {
if (extensionStart > 0) String gamePath = fileCursor.getString(GAME_COLUMN_PATH);
{ File game = new File(gamePath);
String fileExtension = filePath.substring(extensionStart);
// Check that the file has an extension we care about before trying to read out of it. if (!game.exists()) {
if (allowedExtensions.contains(fileExtension.toLowerCase())) Log.error("[GameDatabase] Game file no longer exists. Removing from the library: " +
{ gamePath);
String name = NativeLibrary.GetTitle(filePath); database.delete(TABLE_NAME_GAMES,
KEY_DB_ID + " = ?",
// If the game's title field is empty, use the filename. new String[]{Long.toString(fileCursor.getLong(COLUMN_DB_ID))});
if (name.isEmpty())
{
name = filePath.substring(filePath.lastIndexOf("/") + 1);
}
String gameId = NativeLibrary.GetGameId(filePath);
// If the game's ID field is empty, use the filename without extension.
if (gameId.isEmpty())
{
gameId = filePath.substring(filePath.lastIndexOf("/") + 1,
filePath.lastIndexOf("."));
}
ContentValues game = Game.asContentValues(name,
NativeLibrary.GetDescription(filePath).replace("\n", " "),
NativeLibrary.GetCountry(filePath),
filePath,
gameId,
NativeLibrary.GetCompany(filePath));
// Try to update an existing game first.
int rowsMatched = database.update(TABLE_NAME_GAMES, // Which table to update.
game,
// The values to fill the row with.
KEY_GAME_ID + " = ?",
// The WHERE clause used to find the right row.
new String[]{game.getAsString(
KEY_GAME_ID)}); // The ? in WHERE clause is replaced with this,
// which is provided as an array because there
// could potentially be more than one argument.
// If update fails, insert a new game instead.
if (rowsMatched == 0)
{
Log.verbose("[GameDatabase] Adding game: " + game.getAsString(KEY_GAME_TITLE));
database.insert(TABLE_NAME_GAMES, null, game);
}
else
{
Log.verbose("[GameDatabase] Updated game: " + game.getAsString(KEY_GAME_TITLE));
}
}
} }
}
} }
}
// If the folder is empty because it no longer exists, remove it from the library.
else if (!folder.exists()) // Get a cursor listing all the folders the user has added to the library.
{ Cursor folderCursor = database.query(TABLE_NAME_FOLDERS,
Log.error( null, // Get all columns.
"[GameDatabase] Folder no longer exists. Removing from the library: " + folderPath); null, // Get all rows.
database.delete(TABLE_NAME_FOLDERS, null,
KEY_DB_ID + " = ?", null, // No grouping.
new String[]{Long.toString(folderCursor.getLong(COLUMN_DB_ID))}); null,
} null); // Order of folders is irrelevant.
else
{ Set<String> allowedExtensions = new HashSet<String>(Arrays.asList(
Log.error("[GameDatabase] Folder contains no games: " + folderPath); ".3ds", ".3ds", ".3dsx", ".elf", ".axf", ".cci", ".cxi", ".app"));
}
// Possibly overly defensive, but ensures that moveToNext() does not skip a row.
folderCursor.moveToPosition(-1);
// Iterate through all results of the DB query (i.e. all folders in the library.)
while (folderCursor.moveToNext()) {
String folderPath = folderCursor.getString(FOLDER_COLUMN_PATH);
File folder = new File(folderPath);
Log.info("[GameDatabase] Reading files from library folder: " + folderPath);
// Iterate through every file in the folder.
File[] children = folder.listFiles();
if (children != null) {
for (File file : children) {
if (!file.isHidden() && !file.isDirectory()) {
String filePath = file.getPath();
int extensionStart = filePath.lastIndexOf('.');
if (extensionStart > 0) {
String fileExtension = filePath.substring(extensionStart);
// Check that the file has an extension we care about before trying to read out of it.
if (allowedExtensions.contains(fileExtension.toLowerCase())) {
String name = NativeLibrary.GetTitle(filePath);
// If the game's title field is empty, use the filename.
if (name.isEmpty()) {
name = filePath.substring(filePath.lastIndexOf("/") + 1);
}
String gameId = NativeLibrary.GetGameId(filePath);
// If the game's ID field is empty, use the filename without extension.
if (gameId.isEmpty()) {
gameId = filePath.substring(filePath.lastIndexOf("/") + 1,
filePath.lastIndexOf("."));
}
ContentValues game = Game.asContentValues(name,
NativeLibrary.GetDescription(filePath).replace("\n", " "),
NativeLibrary.GetCountry(filePath),
filePath,
gameId,
NativeLibrary.GetCompany(filePath));
// Try to update an existing game first.
int rowsMatched = database.update(TABLE_NAME_GAMES, // Which table to update.
game,
// The values to fill the row with.
KEY_GAME_ID + " = ?",
// The WHERE clause used to find the right row.
new String[]{game.getAsString(
KEY_GAME_ID)}); // The ? in WHERE clause is replaced with this,
// which is provided as an array because there
// could potentially be more than one argument.
// If update fails, insert a new game instead.
if (rowsMatched == 0) {
Log.verbose("[GameDatabase] Adding game: " + game.getAsString(KEY_GAME_TITLE));
database.insert(TABLE_NAME_GAMES, null, game);
} else {
Log.verbose("[GameDatabase] Updated game: " + game.getAsString(KEY_GAME_TITLE));
}
}
}
}
}
}
// If the folder is empty because it no longer exists, remove it from the library.
else if (!folder.exists()) {
Log.error(
"[GameDatabase] Folder no longer exists. Removing from the library: " + folderPath);
database.delete(TABLE_NAME_FOLDERS,
KEY_DB_ID + " = ?",
new String[]{Long.toString(folderCursor.getLong(COLUMN_DB_ID))});
} else {
Log.error("[GameDatabase] Folder contains no games: " + folderPath);
}
}
fileCursor.close();
folderCursor.close();
database.close();
} }
fileCursor.close(); public Observable<Cursor> getGames() {
folderCursor.close(); return Observable.create(subscriber ->
database.close(); {
} Log.info("[GameDatabase] Reading games list...");
public Observable<Cursor> getGames() SQLiteDatabase database = getReadableDatabase();
{ Cursor resultCursor = database.query(
return Observable.create(subscriber -> TABLE_NAME_GAMES,
{ null,
Log.info("[GameDatabase] Reading games list..."); null,
null,
null,
null,
KEY_GAME_TITLE + " ASC"
);
SQLiteDatabase database = getReadableDatabase(); // Pass the result cursor to the consumer.
Cursor resultCursor = database.query( subscriber.onNext(resultCursor);
TABLE_NAME_GAMES,
null,
null,
null,
null,
null,
KEY_GAME_TITLE + " ASC"
);
// Pass the result cursor to the consumer. // Tell the consumer we're done; it will unsubscribe implicitly.
subscriber.onNext(resultCursor); subscriber.onCompleted();
});
}
// Tell the consumer we're done; it will unsubscribe implicitly. private void execSqlAndLog(SQLiteDatabase database, String sql) {
subscriber.onCompleted(); Log.verbose("[GameDatabase] Executing SQL: " + sql);
}); database.execSQL(sql);
} }
private void execSqlAndLog(SQLiteDatabase database, String sql)
{
Log.verbose("[GameDatabase] Executing SQL: " + sql);
database.execSQL(sql);
}
} }

View File

@ -14,142 +14,122 @@ import org.citra.citra_android.utils.Log;
* Provides an interface allowing Activities to interact with the SQLite database. * Provides an interface allowing Activities to interact with the SQLite database.
* CRUD methods in this class can be called by Activities using getContentResolver(). * CRUD methods in this class can be called by Activities using getContentResolver().
*/ */
public final class GameProvider extends ContentProvider public final class GameProvider extends ContentProvider {
{ public static final String REFRESH_LIBRARY = "refresh";
public static final String REFRESH_LIBRARY = "refresh";
public static final String AUTHORITY = "content://" + BuildConfig.APPLICATION_ID + ".provider"; public static final String AUTHORITY = "content://" + BuildConfig.APPLICATION_ID + ".provider";
public static final Uri URI_FOLDER = public static final Uri URI_FOLDER =
Uri.parse(AUTHORITY + "/" + GameDatabase.TABLE_NAME_FOLDERS + "/"); Uri.parse(AUTHORITY + "/" + GameDatabase.TABLE_NAME_FOLDERS + "/");
public static final Uri URI_GAME = public static final Uri URI_GAME =
Uri.parse(AUTHORITY + "/" + GameDatabase.TABLE_NAME_GAMES + "/"); Uri.parse(AUTHORITY + "/" + GameDatabase.TABLE_NAME_GAMES + "/");
public static final Uri URI_REFRESH = Uri.parse(AUTHORITY + "/" + REFRESH_LIBRARY + "/"); public static final Uri URI_REFRESH = Uri.parse(AUTHORITY + "/" + REFRESH_LIBRARY + "/");
public static final String MIME_TYPE_FOLDER = "vnd.android.cursor.item/vnd.dolphin.folder"; public static final String MIME_TYPE_FOLDER = "vnd.android.cursor.item/vnd.dolphin.folder";
public static final String MIME_TYPE_GAME = "vnd.android.cursor.item/vnd.dolphin.game"; public static final String MIME_TYPE_GAME = "vnd.android.cursor.item/vnd.dolphin.game";
private GameDatabase mDbHelper; private GameDatabase mDbHelper;
@Override @Override
public boolean onCreate() public boolean onCreate() {
{ Log.info("[GameProvider] Creating Content Provider...");
Log.info("[GameProvider] Creating Content Provider...");
mDbHelper = new GameDatabase(getContext()); mDbHelper = new GameDatabase(getContext());
return true; return true;
}
@Override
public Cursor query(@NonNull Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder)
{
Log.info("[GameProvider] Querying URI: " + uri);
SQLiteDatabase db = mDbHelper.getReadableDatabase();
String table = uri.getLastPathSegment();
if (table == null)
{
Log.error("[GameProvider] Badly formatted URI: " + uri);
return null;
} }
Cursor cursor = db.query(table, projection, selection, selectionArgs, null, null, sortOrder); @Override
cursor.setNotificationUri(getContext().getContentResolver(), uri); public Cursor query(@NonNull Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
Log.info("[GameProvider] Querying URI: " + uri);
return cursor; SQLiteDatabase db = mDbHelper.getReadableDatabase();
}
@Override String table = uri.getLastPathSegment();
public String getType(@NonNull Uri uri)
{
Log.verbose("[GameProvider] Getting MIME type for URI: " + uri);
String lastSegment = uri.getLastPathSegment();
if (lastSegment == null) if (table == null) {
{ Log.error("[GameProvider] Badly formatted URI: " + uri);
Log.error("[GameProvider] Badly formatted URI: " + uri); return null;
return null;
}
if (lastSegment.equals(GameDatabase.TABLE_NAME_FOLDERS))
{
return MIME_TYPE_FOLDER;
}
else if (lastSegment.equals(GameDatabase.TABLE_NAME_GAMES))
{
return MIME_TYPE_GAME;
}
Log.error("[GameProvider] Unknown MIME type for URI: " + uri);
return null;
}
@Override
public Uri insert(@NonNull Uri uri, ContentValues values)
{
Log.info("[GameProvider] Inserting row at URI: " + uri);
SQLiteDatabase database = mDbHelper.getWritableDatabase();
String table = uri.getLastPathSegment();
long id = -1;
if (table != null)
{
if (table.equals(REFRESH_LIBRARY))
{
Log.info(
"[GameProvider] URI specified table REFRESH_LIBRARY. No insertion necessary; refreshing library contents...");
mDbHelper.scanLibrary(database);
return uri;
}
id = database.insertWithOnConflict(table, null, values, SQLiteDatabase.CONFLICT_IGNORE);
// If insertion was successful...
if (id > 0)
{
// If we just added a folder, add its contents to the game list.
if (table.equals(GameDatabase.TABLE_NAME_FOLDERS))
{
mDbHelper.scanLibrary(database);
} }
// Notify the UI that its contents should be refreshed. Cursor cursor = db.query(table, projection, selection, selectionArgs, null, null, sortOrder);
getContext().getContentResolver().notifyChange(uri, null); cursor.setNotificationUri(getContext().getContentResolver(), uri);
uri = Uri.withAppendedPath(uri, Long.toString(id));
} return cursor;
else
{
Log.error("[GameProvider] Row already exists: " + uri + " id: " + id);
}
}
else
{
Log.error("[GameProvider] Badly formatted URI: " + uri);
} }
database.close(); @Override
public String getType(@NonNull Uri uri) {
Log.verbose("[GameProvider] Getting MIME type for URI: " + uri);
String lastSegment = uri.getLastPathSegment();
return uri; if (lastSegment == null) {
} Log.error("[GameProvider] Badly formatted URI: " + uri);
return null;
}
@Override if (lastSegment.equals(GameDatabase.TABLE_NAME_FOLDERS)) {
public int delete(@NonNull Uri uri, String selection, String[] selectionArgs) return MIME_TYPE_FOLDER;
{ } else if (lastSegment.equals(GameDatabase.TABLE_NAME_GAMES)) {
Log.error("[GameProvider] Delete operations unsupported. URI: " + uri); return MIME_TYPE_GAME;
return 0; }
}
@Override Log.error("[GameProvider] Unknown MIME type for URI: " + uri);
public int update(@NonNull Uri uri, ContentValues values, String selection, return null;
String[] selectionArgs) }
{
Log.error("[GameProvider] Update operations unsupported. URI: " + uri); @Override
return 0; public Uri insert(@NonNull Uri uri, ContentValues values) {
} Log.info("[GameProvider] Inserting row at URI: " + uri);
SQLiteDatabase database = mDbHelper.getWritableDatabase();
String table = uri.getLastPathSegment();
long id = -1;
if (table != null) {
if (table.equals(REFRESH_LIBRARY)) {
Log.info(
"[GameProvider] URI specified table REFRESH_LIBRARY. No insertion necessary; refreshing library contents...");
mDbHelper.scanLibrary(database);
return uri;
}
id = database.insertWithOnConflict(table, null, values, SQLiteDatabase.CONFLICT_IGNORE);
// If insertion was successful...
if (id > 0) {
// If we just added a folder, add its contents to the game list.
if (table.equals(GameDatabase.TABLE_NAME_FOLDERS)) {
mDbHelper.scanLibrary(database);
}
// Notify the UI that its contents should be refreshed.
getContext().getContentResolver().notifyChange(uri, null);
uri = Uri.withAppendedPath(uri, Long.toString(id));
} else {
Log.error("[GameProvider] Row already exists: " + uri + " id: " + id);
}
} else {
Log.error("[GameProvider] Badly formatted URI: " + uri);
}
database.close();
return uri;
}
@Override
public int delete(@NonNull Uri uri, String selection, String[] selectionArgs) {
Log.error("[GameProvider] Delete operations unsupported. URI: " + uri);
return 0;
}
@Override
public int update(@NonNull Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
Log.error("[GameProvider] Update operations unsupported. URI: " + uri);
return 0;
}
} }

View File

@ -1,30 +1,25 @@
package org.citra.citra_android.model; package org.citra.citra_android.model;
public final class TvSettingsItem public final class TvSettingsItem {
{ private final int mItemId;
private final int mItemId; private final int mIconId;
private final int mIconId; private final int mLabelId;
private final int mLabelId;
public TvSettingsItem(int itemId, int iconId, int labelId) public TvSettingsItem(int itemId, int iconId, int labelId) {
{ mItemId = itemId;
mItemId = itemId; mIconId = iconId;
mIconId = iconId; mLabelId = labelId;
mLabelId = labelId; }
}
public int getItemId() public int getItemId() {
{ return mItemId;
return mItemId; }
}
public int getIconId() public int getIconId() {
{ return mIconId;
return mIconId; }
}
public int getLabelId() public int getLabelId() {
{ return mLabelId;
return mLabelId; }
}
} }

View File

@ -0,0 +1,23 @@
package org.citra.citra_android.model.settings;
public final class BooleanSetting extends Setting {
private boolean mValue;
public BooleanSetting(String key, String section, int file, boolean value) {
super(key, section, file);
mValue = value;
}
public boolean getValue() {
return mValue;
}
public void setValue(boolean value) {
mValue = value;
}
@Override
public String getValueAsString() {
return mValue ? "True" : "False";
}
}

View File

@ -1,28 +1,23 @@
package org.citra.citra_android.model.settings; package org.citra.citra_android.model.settings;
public final class FloatSetting extends Setting public final class FloatSetting extends Setting {
{ private float mValue;
private float mValue;
public FloatSetting(String key, String section, int file, float value) public FloatSetting(String key, String section, int file, float value) {
{ super(key, section, file);
super(key, section, file); mValue = value;
mValue = value; }
}
public float getValue() public float getValue() {
{ return mValue;
return mValue; }
}
public void setValue(float value) public void setValue(float value) {
{ mValue = value;
mValue = value; }
}
@Override @Override
public String getValueAsString() public String getValueAsString() {
{ return Float.toString(mValue);
return Float.toString(mValue); }
}
} }

View File

@ -1,28 +1,23 @@
package org.citra.citra_android.model.settings; package org.citra.citra_android.model.settings;
public final class IntSetting extends Setting public final class IntSetting extends Setting {
{ private int mValue;
private int mValue;
public IntSetting(String key, String section, int file, int value) public IntSetting(String key, String section, int file, int value) {
{ super(key, section, file);
super(key, section, file); mValue = value;
mValue = value; }
}
public int getValue() public int getValue() {
{ return mValue;
return mValue; }
}
public void setValue(int value) public void setValue(int value) {
{ mValue = value;
mValue = value; }
}
@Override @Override
public String getValueAsString() public String getValueAsString() {
{ return Integer.toString(mValue);
return Integer.toString(mValue); }
}
} }

View File

@ -6,52 +6,47 @@ package org.citra.citra_android.model.settings;
* must be inferred at read-time. The type of value determines which child of this class is used * must be inferred at read-time. The type of value determines which child of this class is used
* to represent the Setting. * to represent the Setting.
*/ */
public abstract class Setting public abstract class Setting {
{ private String mKey;
private String mKey; private String mSection;
private String mSection; private int mFile;
private int mFile;
/** /**
* Base constructor. * Base constructor.
* *
* @param key Everything to the left of the = in a line from the ini file. * @param key Everything to the left of the = in a line from the ini file.
* @param section The corresponding recent section header; e.g. [Core] or [Enhancements] without the brackets. * @param section The corresponding recent section header; e.g. [Core] or [Enhancements] without the brackets.
* @param file The ini file the Setting is stored in. * @param file The ini file the Setting is stored in.
*/ */
public Setting(String key, String section, int file) public Setting(String key, String section, int file) {
{ mKey = key;
mKey = key; mSection = section;
mSection = section; mFile = file;
mFile = file; }
}
/** /**
* @return The identifier used to write this setting to the ini file. * @return The identifier used to write this setting to the ini file.
*/ */
public String getKey() public String getKey() {
{ return mKey;
return mKey; }
}
/** /**
* @return The name of the header under which this Setting should be written in the ini file. * @return The name of the header under which this Setting should be written in the ini file.
*/ */
public String getSection() public String getSection() {
{ return mSection;
return mSection; }
}
/** /**
* @return The ini file the Setting is stored in. * @return The ini file the Setting is stored in.
*/ */
public int getFile() public int getFile() {
{ return mFile;
return mFile; }
}
/** /**
* @return A representation of this Setting's backing value converted to a String (e.g. for serialization). * @return A representation of this Setting's backing value converted to a String (e.g. for serialization).
*/ */
public abstract String getValueAsString(); public abstract String getValueAsString();
} }

View File

@ -6,50 +6,44 @@ import java.util.HashMap;
* A semantically-related group of Settings objects. These Settings are * A semantically-related group of Settings objects. These Settings are
* internally stored as a HashMap. * internally stored as a HashMap.
*/ */
public final class SettingSection public final class SettingSection {
{ private String mName;
private String mName;
private HashMap<String, Setting> mSettings = new HashMap<>(); private HashMap<String, Setting> mSettings = new HashMap<>();
/** /**
* Create a new SettingSection with no Settings in it. * Create a new SettingSection with no Settings in it.
* *
* @param name The header of this section; e.g. [Core] or [Enhancements] without the brackets. * @param name The header of this section; e.g. [Core] or [Enhancements] without the brackets.
*/ */
public SettingSection(String name) public SettingSection(String name) {
{ mName = name;
mName = name; }
}
public String getName() public String getName() {
{ return mName;
return mName; }
}
/** /**
* Convenience method; inserts a value directly into the backing HashMap. * Convenience method; inserts a value directly into the backing HashMap.
* *
* @param setting The Setting to be inserted. * @param setting The Setting to be inserted.
*/ */
public void putSetting(Setting setting) public void putSetting(Setting setting) {
{ mSettings.put(setting.getKey(), setting);
mSettings.put(setting.getKey(), setting); }
}
/** /**
* Convenience method; gets a value directly from the backing HashMap. * Convenience method; gets a value directly from the backing HashMap.
* *
* @param key Used to retrieve the Setting. * @param key Used to retrieve the Setting.
* @return A Setting object (you should probably cast this before using) * @return A Setting object (you should probably cast this before using)
*/ */
public Setting getSetting(String key) public Setting getSetting(String key) {
{ return mSettings.get(key);
return mSettings.get(key); }
}
public HashMap<String, Setting> getSettings() public HashMap<String, Setting> getSettings() {
{ return mSettings;
return mSettings; }
}
} }

View File

@ -1,28 +1,23 @@
package org.citra.citra_android.model.settings; package org.citra.citra_android.model.settings;
public final class StringSetting extends Setting public final class StringSetting extends Setting {
{ private String mValue;
private String mValue;
public StringSetting(String key, String section, int file, String value) public StringSetting(String key, String section, int file, String value) {
{ super(key, section, file);
super(key, section, file); mValue = value;
mValue = value; }
}
public String getValue() public String getValue() {
{ return mValue;
return mValue; }
}
public void setValue(String value) public void setValue(String value) {
{ mValue = value;
mValue = value; }
}
@Override @Override
public String getValueAsString() public String getValueAsString() {
{ return mValue;
return mValue; }
}
} }

View File

@ -3,54 +3,45 @@ package org.citra.citra_android.model.settings.view;
import org.citra.citra_android.model.settings.IntSetting; import org.citra.citra_android.model.settings.IntSetting;
import org.citra.citra_android.model.settings.Setting; import org.citra.citra_android.model.settings.Setting;
public final class CheckBoxSetting extends SettingsItem public final class CheckBoxSetting extends SettingsItem {
{ private boolean mDefaultValue;
private boolean mDefaultValue;
public CheckBoxSetting(String key, String section, int file, int titleId, int descriptionId, public CheckBoxSetting(String key, String section, int file, int titleId, int descriptionId,
boolean defaultValue, Setting setting) boolean defaultValue, Setting setting) {
{ super(key, section, file, setting, titleId, descriptionId);
super(key, section, file, setting, titleId, descriptionId); mDefaultValue = defaultValue;
mDefaultValue = defaultValue;
}
public boolean isChecked()
{
if (getSetting() == null)
{
return mDefaultValue;
} }
IntSetting setting = (IntSetting) getSetting(); public boolean isChecked() {
return setting.getValue() == 1; if (getSetting() == null) {
} return mDefaultValue;
}
/** IntSetting setting = (IntSetting) getSetting();
* Write a value to the backing boolean. If that boolean was previously null, return setting.getValue() == 1;
* initializes a new one and returns it, so it can be added to the Hashmap.
*
* @param checked Pretty self explanatory.
* @return null if overwritten successfully; otherwise, a newly created BooleanSetting.
*/
public IntSetting setChecked(boolean checked)
{
if (getSetting() == null)
{
IntSetting setting = new IntSetting(getKey(), getSection(), getFile(), checked ? 1 : 0);
setSetting(setting);
return setting;
} }
else
{
IntSetting setting = (IntSetting) getSetting();
setting.setValue(checked ? 1 : 0);
return null;
}
}
@Override /**
public int getType() * Write a value to the backing boolean. If that boolean was previously null,
{ * initializes a new one and returns it, so it can be added to the Hashmap.
return TYPE_CHECKBOX; *
} * @param checked Pretty self explanatory.
* @return null if overwritten successfully; otherwise, a newly created BooleanSetting.
*/
public IntSetting setChecked(boolean checked) {
if (getSetting() == null) {
IntSetting setting = new IntSetting(getKey(), getSection(), getFile(), checked ? 1 : 0);
setSetting(setting);
return setting;
} else {
IntSetting setting = (IntSetting) getSetting();
setting.setValue(checked ? 1 : 0);
return null;
}
}
@Override
public int getType() {
return TYPE_CHECKBOX;
}
} }

View File

@ -3,39 +3,32 @@ package org.citra.citra_android.model.settings.view;
import org.citra.citra_android.model.settings.Setting; import org.citra.citra_android.model.settings.Setting;
import org.citra.citra_android.model.settings.StringSetting; import org.citra.citra_android.model.settings.StringSetting;
public final class DateTimeSetting extends SettingsItem public final class DateTimeSetting extends SettingsItem {
{
private String mDefaultValue; private String mDefaultValue;
public DateTimeSetting(String key, String section, int file, int titleId, int descriptionId, public DateTimeSetting(String key, String section, int file, int titleId, int descriptionId,
String defaultValue, Setting setting) String defaultValue, Setting setting) {
{
super(key, section, file, setting, titleId, descriptionId); super(key, section, file, setting, titleId, descriptionId);
mDefaultValue = defaultValue; mDefaultValue = defaultValue;
} }
public String getValue() public String getValue() {
{ if (getSetting() != null) {
if (getSetting() != null)
{
StringSetting setting = (StringSetting) getSetting(); StringSetting setting = (StringSetting) getSetting();
return setting.getValue(); return setting.getValue();
} } else {
else
{
return mDefaultValue; return mDefaultValue;
} }
} }
public StringSetting setSelectedValue(String datetime)
{ public StringSetting setSelectedValue(String datetime) {
StringSetting setting = new StringSetting(getKey(), getSection(), getFile(), datetime); StringSetting setting = new StringSetting(getKey(), getSection(), getFile(), datetime);
setSetting(setting); setSetting(setting);
return setting; return setting;
} }
@Override @Override
public int getType() public int getType() {
{
return TYPE_DATETIME_SETTING; return TYPE_DATETIME_SETTING;
} }
} }

View File

@ -2,16 +2,13 @@ package org.citra.citra_android.model.settings.view;
import org.citra.citra_android.model.settings.Setting; import org.citra.citra_android.model.settings.Setting;
public final class HeaderSetting extends SettingsItem public final class HeaderSetting extends SettingsItem {
{ public HeaderSetting(String key, Setting setting, int titleId, int descriptionId) {
public HeaderSetting(String key, Setting setting, int titleId, int descriptionId) super(key, null, 0, setting, titleId, descriptionId);
{ }
super(key, null, 0, setting, titleId, descriptionId);
}
@Override @Override
public int getType() public int getType() {
{ return SettingsItem.TYPE_HEADER;
return SettingsItem.TYPE_HEADER; }
}
} }

View File

@ -3,50 +3,41 @@ package org.citra.citra_android.model.settings.view;
import org.citra.citra_android.model.settings.Setting; import org.citra.citra_android.model.settings.Setting;
import org.citra.citra_android.model.settings.StringSetting; import org.citra.citra_android.model.settings.StringSetting;
public final class InputBindingSetting extends SettingsItem public final class InputBindingSetting extends SettingsItem {
{ public InputBindingSetting(String key, String section, int file, int titleId, Setting setting) {
public InputBindingSetting(String key, String section, int file, int titleId, Setting setting) super(key, section, file, setting, titleId, 0);
{
super(key, section, file, setting, titleId, 0);
}
public String getValue()
{
if (getSetting() == null)
{
return "";
} }
StringSetting setting = (StringSetting) getSetting(); public String getValue() {
return setting.getValue(); if (getSetting() == null) {
} return "";
}
/** StringSetting setting = (StringSetting) getSetting();
* Write a value to the backing string. If that string was previously null, return setting.getValue();
* initializes a new one and returns it, so it can be added to the Hashmap.
*
* @param bind The input that will be bound
* @return null if overwritten successfully; otherwise, a newly created StringSetting.
*/
public StringSetting setValue(String bind)
{
if (getSetting() == null)
{
StringSetting setting = new StringSetting(getKey(), getSection(), getFile(), bind);
setSetting(setting);
return setting;
} }
else
{
StringSetting setting = (StringSetting) getSetting();
setting.setValue(bind);
return null;
}
}
@Override /**
public int getType() * Write a value to the backing string. If that string was previously null,
{ * initializes a new one and returns it, so it can be added to the Hashmap.
return TYPE_INPUT_BINDING; *
} * @param bind The input that will be bound
* @return null if overwritten successfully; otherwise, a newly created StringSetting.
*/
public StringSetting setValue(String bind) {
if (getSetting() == null) {
StringSetting setting = new StringSetting(getKey(), getSection(), getFile(), bind);
setSetting(setting);
return setting;
} else {
StringSetting setting = (StringSetting) getSetting();
setting.setValue(bind);
return null;
}
}
@Override
public int getType() {
return TYPE_INPUT_BINDING;
}
} }

View File

@ -9,107 +9,98 @@ import org.citra.citra_android.model.settings.Setting;
* and a few with none (Headers, for example, do not correspond to anything in the ini * and a few with none (Headers, for example, do not correspond to anything in the ini
* file.) * file.)
*/ */
public abstract class SettingsItem public abstract class SettingsItem {
{ public static final int TYPE_HEADER = 0;
public static final int TYPE_HEADER = 0; public static final int TYPE_CHECKBOX = 1;
public static final int TYPE_CHECKBOX = 1; public static final int TYPE_SINGLE_CHOICE = 2;
public static final int TYPE_SINGLE_CHOICE = 2; public static final int TYPE_SLIDER = 3;
public static final int TYPE_SLIDER = 3; public static final int TYPE_SUBMENU = 4;
public static final int TYPE_SUBMENU = 4; public static final int TYPE_INPUT_BINDING = 5;
public static final int TYPE_INPUT_BINDING = 5; public static final int TYPE_DATETIME_SETTING = 6;
public static final int TYPE_DATETIME_SETTING = 6;
private String mKey; private String mKey;
private String mSection; private String mSection;
private int mFile; private int mFile;
private Setting mSetting; private Setting mSetting;
private int mNameId; private int mNameId;
private int mDescriptionId; private int mDescriptionId;
/** /**
* Base constructor. Takes a key / section name in case the third parameter, the Setting, * Base constructor. Takes a key / section name in case the third parameter, the Setting,
* is null; in which case, one can be constructed and saved using the key / section. * is null; in which case, one can be constructed and saved using the key / section.
* *
* @param key Identifier for the Setting represented by this Item. * @param key Identifier for the Setting represented by this Item.
* @param section Section to which the Setting belongs. * @param section Section to which the Setting belongs.
* @param setting A possibly-null backing Setting, to be modified on UI events. * @param setting A possibly-null backing Setting, to be modified on UI events.
* @param nameId Resource ID for a text string to be displayed as this setting's name. * @param nameId Resource ID for a text string to be displayed as this setting's name.
* @param descriptionId Resource ID for a text string to be displayed as this setting's description. * @param descriptionId Resource ID for a text string to be displayed as this setting's description.
*/ */
public SettingsItem(String key, String section, int file, Setting setting, int nameId, public SettingsItem(String key, String section, int file, Setting setting, int nameId,
int descriptionId) int descriptionId) {
{ mKey = key;
mKey = key; mSection = section;
mSection = section; mFile = file;
mFile = file; mSetting = setting;
mSetting = setting; mNameId = nameId;
mNameId = nameId; mDescriptionId = descriptionId;
mDescriptionId = descriptionId; }
}
/** /**
* @return The identifier for the backing Setting. * @return The identifier for the backing Setting.
*/ */
public String getKey() public String getKey() {
{ return mKey;
return mKey; }
}
/** /**
* @return The header under which the backing Setting belongs. * @return The header under which the backing Setting belongs.
*/ */
public String getSection() public String getSection() {
{ return mSection;
return mSection; }
}
/** /**
* @return The file the backing Setting is saved to. * @return The file the backing Setting is saved to.
*/ */
public int getFile() public int getFile() {
{ return mFile;
return mFile; }
}
/** /**
* @return The backing Setting, possibly null. * @return The backing Setting, possibly null.
*/ */
public Setting getSetting() public Setting getSetting() {
{ return mSetting;
return mSetting; }
}
/** /**
* Replace the backing setting with a new one. Generally used in cases where * Replace the backing setting with a new one. Generally used in cases where
* the backing setting is null. * the backing setting is null.
* *
* @param setting A non-null Setting. * @param setting A non-null Setting.
*/ */
public void setSetting(Setting setting) public void setSetting(Setting setting) {
{ mSetting = setting;
mSetting = setting; }
}
/** /**
* @return A resource ID for a text string representing this Setting's name. * @return A resource ID for a text string representing this Setting's name.
*/ */
public int getNameId() public int getNameId() {
{ return mNameId;
return mNameId; }
}
public int getDescriptionId() public int getDescriptionId() {
{ return mDescriptionId;
return mDescriptionId; }
}
/** /**
* Used by {@link org.citra.citra_android.ui.settings.SettingsAdapter}'s onCreateViewHolder() * Used by {@link org.citra.citra_android.ui.settings.SettingsAdapter}'s onCreateViewHolder()
* method to determine which type of ViewHolder should be created. * method to determine which type of ViewHolder should be created.
* *
* @return An integer (ideally, one of the constants defined in this file) * @return An integer (ideally, one of the constants defined in this file)
*/ */
public abstract int getType(); public abstract int getType();
} }

View File

@ -3,71 +3,58 @@ package org.citra.citra_android.model.settings.view;
import org.citra.citra_android.model.settings.IntSetting; import org.citra.citra_android.model.settings.IntSetting;
import org.citra.citra_android.model.settings.Setting; import org.citra.citra_android.model.settings.Setting;
public final class SingleChoiceSetting extends SettingsItem public final class SingleChoiceSetting extends SettingsItem {
{ private int mDefaultValue;
private int mDefaultValue;
private int mChoicesId; private int mChoicesId;
private int mValuesId; private int mValuesId;
public SingleChoiceSetting(String key, String section, int file, int titleId, int descriptionId, public SingleChoiceSetting(String key, String section, int file, int titleId, int descriptionId,
int choicesId, int valuesId, int defaultValue, Setting setting) int choicesId, int valuesId, int defaultValue, Setting setting) {
{ super(key, section, file, setting, titleId, descriptionId);
super(key, section, file, setting, titleId, descriptionId); mValuesId = valuesId;
mValuesId = valuesId; mChoicesId = choicesId;
mChoicesId = choicesId; mDefaultValue = defaultValue;
mDefaultValue = defaultValue;
}
public int getChoicesId()
{
return mChoicesId;
}
public int getValuesId()
{
return mValuesId;
}
public int getSelectedValue()
{
if (getSetting() != null)
{
IntSetting setting = (IntSetting) getSetting();
return setting.getValue();
} }
else
{
return mDefaultValue;
}
}
/** public int getChoicesId() {
* Write a value to the backing int. If that int was previously null, return mChoicesId;
* initializes a new one and returns it, so it can be added to the Hashmap.
*
* @param selection New value of the int.
* @return null if overwritten successfully otherwise; a newly created IntSetting.
*/
public IntSetting setSelectedValue(int selection)
{
if (getSetting() == null)
{
IntSetting setting = new IntSetting(getKey(), getSection(), getFile(), selection);
setSetting(setting);
return setting;
} }
else
{
IntSetting setting = (IntSetting) getSetting();
setting.setValue(selection);
return null;
}
}
@Override public int getValuesId() {
public int getType() return mValuesId;
{ }
return TYPE_SINGLE_CHOICE;
} public int getSelectedValue() {
if (getSetting() != null) {
IntSetting setting = (IntSetting) getSetting();
return setting.getValue();
} else {
return mDefaultValue;
}
}
/**
* Write a value to the backing int. If that int was previously null,
* initializes a new one and returns it, so it can be added to the Hashmap.
*
* @param selection New value of the int.
* @return null if overwritten successfully otherwise; a newly created IntSetting.
*/
public IntSetting setSelectedValue(int selection) {
if (getSetting() == null) {
IntSetting setting = new IntSetting(getKey(), getSection(), getFile(), selection);
setSetting(setting);
return setting;
} else {
IntSetting setting = (IntSetting) getSetting();
setting.setValue(selection);
return null;
}
}
@Override
public int getType() {
return TYPE_SINGLE_CHOICE;
}
} }

View File

@ -6,114 +6,91 @@ import org.citra.citra_android.model.settings.Setting;
import org.citra.citra_android.utils.Log; import org.citra.citra_android.utils.Log;
import org.citra.citra_android.utils.SettingsFile; import org.citra.citra_android.utils.SettingsFile;
public final class SliderSetting extends SettingsItem public final class SliderSetting extends SettingsItem {
{ private int mMax;
private int mMax; private int mDefaultValue;
private int mDefaultValue;
private String mUnits; private String mUnits;
public SliderSetting(String key, String section, int file, int titleId, int descriptionId, public SliderSetting(String key, String section, int file, int titleId, int descriptionId,
int max, String units, int defaultValue, Setting setting) int max, String units, int defaultValue, Setting setting) {
{ super(key, section, file, setting, titleId, descriptionId);
super(key, section, file, setting, titleId, descriptionId); mMax = max;
mMax = max; mUnits = units;
mUnits = units; mDefaultValue = defaultValue;
mDefaultValue = defaultValue;
}
public int getMax()
{
return mMax;
}
public int getSelectedValue()
{
Setting setting = getSetting();
if (setting == null)
{
return mDefaultValue;
} }
if (setting instanceof IntSetting) public int getMax() {
{ return mMax;
IntSetting intSetting = (IntSetting) setting;
return intSetting.getValue();
} }
else if (setting instanceof FloatSetting)
{
FloatSetting floatSetting = (FloatSetting) setting;
if (floatSetting.getKey().equals(SettingsFile.KEY_FRAME_LIMIT))
{
return Math.round(floatSetting.getValue() * 100);
}
else
{
return Math.round(floatSetting.getValue());
}
}
else
{
Log.error("[SliderSetting] Error casting setting type.");
return -1;
}
}
/** public int getSelectedValue() {
* Write a value to the backing int. If that int was previously null, Setting setting = getSetting();
* initializes a new one and returns it, so it can be added to the Hashmap.
*
* @param selection New value of the int.
* @return null if overwritten successfully otherwise; a newly created IntSetting.
*/
public IntSetting setSelectedValue(int selection)
{
if (getSetting() == null)
{
IntSetting setting = new IntSetting(getKey(), getSection(), getFile(), selection);
setSetting(setting);
return setting;
}
else
{
IntSetting setting = (IntSetting) getSetting();
setting.setValue(selection);
return null;
}
}
/** if (setting == null) {
* Write a value to the backing float. If that float was previously null, return mDefaultValue;
* initializes a new one and returns it, so it can be added to the Hashmap. }
*
* @param selection New value of the float.
* @return null if overwritten successfully otherwise; a newly created FloatSetting.
*/
public FloatSetting setSelectedValue(float selection)
{
if (getSetting() == null)
{
FloatSetting setting = new FloatSetting(getKey(), getSection(), getFile(), selection);
setSetting(setting);
return setting;
}
else
{
FloatSetting setting = (FloatSetting) getSetting();
setting.setValue(selection);
return null;
}
}
public String getUnits() if (setting instanceof IntSetting) {
{ IntSetting intSetting = (IntSetting) setting;
return mUnits; return intSetting.getValue();
} } else if (setting instanceof FloatSetting) {
FloatSetting floatSetting = (FloatSetting) setting;
if (floatSetting.getKey().equals(SettingsFile.KEY_FRAME_LIMIT)) {
return Math.round(floatSetting.getValue() * 100);
} else {
return Math.round(floatSetting.getValue());
}
} else {
Log.error("[SliderSetting] Error casting setting type.");
return -1;
}
}
@Override /**
public int getType() * Write a value to the backing int. If that int was previously null,
{ * initializes a new one and returns it, so it can be added to the Hashmap.
return TYPE_SLIDER; *
} * @param selection New value of the int.
* @return null if overwritten successfully otherwise; a newly created IntSetting.
*/
public IntSetting setSelectedValue(int selection) {
if (getSetting() == null) {
IntSetting setting = new IntSetting(getKey(), getSection(), getFile(), selection);
setSetting(setting);
return setting;
} else {
IntSetting setting = (IntSetting) getSetting();
setting.setValue(selection);
return null;
}
}
/**
* Write a value to the backing float. If that float was previously null,
* initializes a new one and returns it, so it can be added to the Hashmap.
*
* @param selection New value of the float.
* @return null if overwritten successfully otherwise; a newly created FloatSetting.
*/
public FloatSetting setSelectedValue(float selection) {
if (getSetting() == null) {
FloatSetting setting = new FloatSetting(getKey(), getSection(), getFile(), selection);
setSetting(setting);
return setting;
} else {
FloatSetting setting = (FloatSetting) getSetting();
setting.setValue(selection);
return null;
}
}
public String getUnits() {
return mUnits;
}
@Override
public int getType() {
return TYPE_SLIDER;
}
} }

View File

@ -2,24 +2,20 @@ package org.citra.citra_android.model.settings.view;
import org.citra.citra_android.model.settings.Setting; import org.citra.citra_android.model.settings.Setting;
public final class SubmenuSetting extends SettingsItem public final class SubmenuSetting extends SettingsItem {
{ private String mMenuKey;
private String mMenuKey;
public SubmenuSetting(String key, Setting setting, int titleId, int descriptionId, String menuKey) public SubmenuSetting(String key, Setting setting, int titleId, int descriptionId, String menuKey) {
{ super(key, null, 0, setting, titleId, descriptionId);
super(key, null, 0, setting, titleId, descriptionId); mMenuKey = menuKey;
mMenuKey = menuKey; }
}
public String getMenuKey() public String getMenuKey() {
{ return mMenuKey;
return mMenuKey; }
}
@Override @Override
public int getType() public int getType() {
{ return TYPE_SUBMENU;
return TYPE_SUBMENU; }
}
} }

View File

@ -17,121 +17,106 @@ import android.view.MotionEvent;
* Custom {@link BitmapDrawable} that is capable * Custom {@link BitmapDrawable} that is capable
* of storing it's own ID. * of storing it's own ID.
*/ */
public final class InputOverlayDrawableButton public final class InputOverlayDrawableButton {
{ // The ID identifying what type of button this Drawable represents.
// The ID identifying what type of button this Drawable represents. private int mButtonType;
private int mButtonType; private int mTrackId;
private int mTrackId; private int mPreviousTouchX, mPreviousTouchY;
private int mPreviousTouchX, mPreviousTouchY; private int mControlPositionX, mControlPositionY;
private int mControlPositionX, mControlPositionY; private int mWidth;
private int mWidth; private int mHeight;
private int mHeight; private BitmapDrawable mDefaultStateBitmap;
private BitmapDrawable mDefaultStateBitmap; private BitmapDrawable mPressedStateBitmap;
private BitmapDrawable mPressedStateBitmap; private boolean mPressedState = false;
private boolean mPressedState = false;
/** /**
* Constructor * Constructor
* *
* @param res {@link Resources} instance. * @param res {@link Resources} instance.
* @param defaultStateBitmap {@link Bitmap} to use with the default state Drawable. * @param defaultStateBitmap {@link Bitmap} to use with the default state Drawable.
* @param pressedStateBitmap {@link Bitmap} to use with the pressed state Drawable. * @param pressedStateBitmap {@link Bitmap} to use with the pressed state Drawable.
* @param buttonType Identifier for this type of button. * @param buttonType Identifier for this type of button.
*/ */
public InputOverlayDrawableButton(Resources res, Bitmap defaultStateBitmap, public InputOverlayDrawableButton(Resources res, Bitmap defaultStateBitmap,
Bitmap pressedStateBitmap, int buttonType) Bitmap pressedStateBitmap, int buttonType) {
{ mDefaultStateBitmap = new BitmapDrawable(res, defaultStateBitmap);
mDefaultStateBitmap = new BitmapDrawable(res, defaultStateBitmap); mPressedStateBitmap = new BitmapDrawable(res, pressedStateBitmap);
mPressedStateBitmap = new BitmapDrawable(res, pressedStateBitmap); mButtonType = buttonType;
mButtonType = buttonType;
mWidth = mDefaultStateBitmap.getIntrinsicWidth();
mHeight = mDefaultStateBitmap.getIntrinsicHeight();
}
/**
* Gets this InputOverlayDrawableButton's button ID.
*
* @return this InputOverlayDrawableButton's button ID.
*/
public int getId()
{
return mButtonType;
}
public void setTrackId(int trackId)
{
mTrackId = trackId;
}
public int getTrackId()
{
return mTrackId;
}
public boolean onConfigureTouch(MotionEvent event)
{
int pointerIndex = event.getActionIndex();
int fingerPositionX = (int) event.getX(pointerIndex);
int fingerPositionY = (int) event.getY(pointerIndex);
switch (event.getAction())
{
case MotionEvent.ACTION_DOWN:
mPreviousTouchX = fingerPositionX;
mPreviousTouchY = fingerPositionY;
break;
case MotionEvent.ACTION_MOVE:
mControlPositionX += fingerPositionX - mPreviousTouchX;
mControlPositionY += fingerPositionY - mPreviousTouchY;
setBounds(mControlPositionX, mControlPositionY, getWidth() + mControlPositionX,
getHeight() + mControlPositionY);
mPreviousTouchX = fingerPositionX;
mPreviousTouchY = fingerPositionY;
break;
mWidth = mDefaultStateBitmap.getIntrinsicWidth();
mHeight = mDefaultStateBitmap.getIntrinsicHeight();
} }
return true;
}
public void setPosition(int x, int y) /**
{ * Gets this InputOverlayDrawableButton's button ID.
mControlPositionX = x; *
mControlPositionY = y; * @return this InputOverlayDrawableButton's button ID.
} */
public int getId() {
return mButtonType;
}
public void draw(Canvas canvas) public int getTrackId() {
{ return mTrackId;
getCurrentStateBitmapDrawable().draw(canvas); }
}
private BitmapDrawable getCurrentStateBitmapDrawable() public void setTrackId(int trackId) {
{ mTrackId = trackId;
return mPressedState ? mPressedStateBitmap : mDefaultStateBitmap; }
}
public void setBounds(int left, int top, int right, int bottom) public boolean onConfigureTouch(MotionEvent event) {
{ int pointerIndex = event.getActionIndex();
mDefaultStateBitmap.setBounds(left, top, right, bottom); int fingerPositionX = (int) event.getX(pointerIndex);
mPressedStateBitmap.setBounds(left, top, right, bottom); int fingerPositionY = (int) event.getY(pointerIndex);
} switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mPreviousTouchX = fingerPositionX;
mPreviousTouchY = fingerPositionY;
break;
case MotionEvent.ACTION_MOVE:
mControlPositionX += fingerPositionX - mPreviousTouchX;
mControlPositionY += fingerPositionY - mPreviousTouchY;
setBounds(mControlPositionX, mControlPositionY, getWidth() + mControlPositionX,
getHeight() + mControlPositionY);
mPreviousTouchX = fingerPositionX;
mPreviousTouchY = fingerPositionY;
break;
public Rect getBounds() }
{ return true;
return mDefaultStateBitmap.getBounds(); }
}
public int getWidth() public void setPosition(int x, int y) {
{ mControlPositionX = x;
return mWidth; mControlPositionY = y;
} }
public int getHeight() public void draw(Canvas canvas) {
{ getCurrentStateBitmapDrawable().draw(canvas);
return mHeight; }
}
public void setPressedState(boolean isPressed) private BitmapDrawable getCurrentStateBitmapDrawable() {
{ return mPressedState ? mPressedStateBitmap : mDefaultStateBitmap;
mPressedState = isPressed; }
}
public void setBounds(int left, int top, int right, int bottom) {
mDefaultStateBitmap.setBounds(left, top, right, bottom);
mPressedStateBitmap.setBounds(left, top, right, bottom);
}
public Rect getBounds() {
return mDefaultStateBitmap.getBounds();
}
public int getWidth() {
return mWidth;
}
public int getHeight() {
return mHeight;
}
public void setPressedState(boolean isPressed) {
mPressedState = isPressed;
}
} }

View File

@ -17,190 +17,174 @@ import android.view.MotionEvent;
* Custom {@link BitmapDrawable} that is capable * Custom {@link BitmapDrawable} that is capable
* of storing it's own ID. * of storing it's own ID.
*/ */
public final class InputOverlayDrawableDpad public final class InputOverlayDrawableDpad {
{ public static final int STATE_DEFAULT = 0;
// The ID identifying what type of button this Drawable represents. public static final int STATE_PRESSED_UP = 1;
private int[] mButtonType = new int[4]; public static final int STATE_PRESSED_DOWN = 2;
private int mTrackId; public static final int STATE_PRESSED_LEFT = 3;
private int mPreviousTouchX, mPreviousTouchY; public static final int STATE_PRESSED_RIGHT = 4;
private int mControlPositionX, mControlPositionY; public static final int STATE_PRESSED_UP_LEFT = 5;
private int mWidth; public static final int STATE_PRESSED_UP_RIGHT = 6;
private int mHeight; public static final int STATE_PRESSED_DOWN_LEFT = 7;
private BitmapDrawable mDefaultStateBitmap; public static final int STATE_PRESSED_DOWN_RIGHT = 8;
private BitmapDrawable mPressedOneDirectionStateBitmap; // The ID identifying what type of button this Drawable represents.
private BitmapDrawable mPressedTwoDirectionsStateBitmap; private int[] mButtonType = new int[4];
private int mPressState = STATE_DEFAULT; private int mTrackId;
private int mPreviousTouchX, mPreviousTouchY;
private int mControlPositionX, mControlPositionY;
private int mWidth;
private int mHeight;
private BitmapDrawable mDefaultStateBitmap;
private BitmapDrawable mPressedOneDirectionStateBitmap;
private BitmapDrawable mPressedTwoDirectionsStateBitmap;
private int mPressState = STATE_DEFAULT;
public static final int STATE_DEFAULT = 0; /**
public static final int STATE_PRESSED_UP = 1; * Constructor
public static final int STATE_PRESSED_DOWN = 2; *
public static final int STATE_PRESSED_LEFT = 3; * @param res {@link Resources} instance.
public static final int STATE_PRESSED_RIGHT = 4; * @param defaultStateBitmap {@link Bitmap} of the default state.
public static final int STATE_PRESSED_UP_LEFT = 5; * @param pressedOneDirectionStateBitmap {@link Bitmap} of the pressed state in one direction.
public static final int STATE_PRESSED_UP_RIGHT = 6; * @param pressedTwoDirectionsStateBitmap {@link Bitmap} of the pressed state in two direction.
public static final int STATE_PRESSED_DOWN_LEFT = 7; * @param buttonUp Identifier for the up button.
public static final int STATE_PRESSED_DOWN_RIGHT = 8; * @param buttonDown Identifier for the down button.
* @param buttonLeft Identifier for the left button.
* @param buttonRight Identifier for the right button.
*/
public InputOverlayDrawableDpad(Resources res,
Bitmap defaultStateBitmap,
Bitmap pressedOneDirectionStateBitmap,
Bitmap pressedTwoDirectionsStateBitmap,
int buttonUp, int buttonDown,
int buttonLeft, int buttonRight) {
mDefaultStateBitmap = new BitmapDrawable(res, defaultStateBitmap);
mPressedOneDirectionStateBitmap = new BitmapDrawable(res, pressedOneDirectionStateBitmap);
mPressedTwoDirectionsStateBitmap = new BitmapDrawable(res, pressedTwoDirectionsStateBitmap);
/** mWidth = mDefaultStateBitmap.getIntrinsicWidth();
* Constructor mHeight = mDefaultStateBitmap.getIntrinsicHeight();
*
* @param res {@link Resources} instance.
* @param defaultStateBitmap {@link Bitmap} of the default state.
* @param pressedOneDirectionStateBitmap {@link Bitmap} of the pressed state in one direction.
* @param pressedTwoDirectionsStateBitmap {@link Bitmap} of the pressed state in two direction.
* @param buttonUp Identifier for the up button.
* @param buttonDown Identifier for the down button.
* @param buttonLeft Identifier for the left button.
* @param buttonRight Identifier for the right button.
*/
public InputOverlayDrawableDpad(Resources res,
Bitmap defaultStateBitmap,
Bitmap pressedOneDirectionStateBitmap,
Bitmap pressedTwoDirectionsStateBitmap,
int buttonUp, int buttonDown,
int buttonLeft, int buttonRight)
{
mDefaultStateBitmap = new BitmapDrawable(res, defaultStateBitmap);
mPressedOneDirectionStateBitmap = new BitmapDrawable(res, pressedOneDirectionStateBitmap);
mPressedTwoDirectionsStateBitmap = new BitmapDrawable(res, pressedTwoDirectionsStateBitmap);
mWidth = mDefaultStateBitmap.getIntrinsicWidth(); mButtonType[0] = buttonUp;
mHeight = mDefaultStateBitmap.getIntrinsicHeight(); mButtonType[1] = buttonDown;
mButtonType[2] = buttonLeft;
mButtonType[0] = buttonUp; mButtonType[3] = buttonRight;
mButtonType[1] = buttonDown;
mButtonType[2] = buttonLeft;
mButtonType[3] = buttonRight;
}
public void draw(Canvas canvas)
{
int px = mControlPositionX + (getWidth() / 2);
int py = mControlPositionY + (getHeight() / 2);
switch (mPressState)
{
case STATE_DEFAULT:
mDefaultStateBitmap.draw(canvas);
break;
case STATE_PRESSED_UP:
mPressedOneDirectionStateBitmap.draw(canvas);
break;
case STATE_PRESSED_RIGHT:
canvas.save();
canvas.rotate(90, px, py);
mPressedOneDirectionStateBitmap.draw(canvas);
canvas.restore();
break;
case STATE_PRESSED_DOWN:
canvas.save();
canvas.rotate(180, px, py);
mPressedOneDirectionStateBitmap.draw(canvas);
canvas.restore();
break;
case STATE_PRESSED_LEFT:
canvas.save();
canvas.rotate(270, px, py);
mPressedOneDirectionStateBitmap.draw(canvas);
canvas.restore();
break;
case STATE_PRESSED_UP_LEFT:
mPressedTwoDirectionsStateBitmap.draw(canvas);
break;
case STATE_PRESSED_UP_RIGHT:
canvas.save();
canvas.rotate(90, px, py);
mPressedTwoDirectionsStateBitmap.draw(canvas);
canvas.restore();
break;
case STATE_PRESSED_DOWN_RIGHT:
canvas.save();
canvas.rotate(180, px, py);
mPressedTwoDirectionsStateBitmap.draw(canvas);
canvas.restore();
break;
case STATE_PRESSED_DOWN_LEFT:
canvas.save();
canvas.rotate(270, px, py);
mPressedTwoDirectionsStateBitmap.draw(canvas);
canvas.restore();
break;
} }
}
/**
* Gets one of the InputOverlayDrawableDpad's button IDs.
*
* @return the requested InputOverlayDrawableDpad's button ID.
*/
public int getId(int direction)
{
return mButtonType[direction];
}
public void setTrackId(int trackId)
{
mTrackId = trackId;
}
public int getTrackId()
{
return mTrackId;
}
public boolean onConfigureTouch(MotionEvent event)
{
int pointerIndex = event.getActionIndex();
int fingerPositionX = (int) event.getX(pointerIndex);
int fingerPositionY = (int) event.getY(pointerIndex);
switch (event.getAction())
{
case MotionEvent.ACTION_DOWN:
mPreviousTouchX = fingerPositionX;
mPreviousTouchY = fingerPositionY;
break;
case MotionEvent.ACTION_MOVE:
mControlPositionX += fingerPositionX - mPreviousTouchX;
mControlPositionY += fingerPositionY - mPreviousTouchY;
setBounds(mControlPositionX, mControlPositionY, getWidth() + mControlPositionX,
getHeight() + mControlPositionY);
mPreviousTouchX = fingerPositionX;
mPreviousTouchY = fingerPositionY;
break;
public void draw(Canvas canvas) {
int px = mControlPositionX + (getWidth() / 2);
int py = mControlPositionY + (getHeight() / 2);
switch (mPressState) {
case STATE_DEFAULT:
mDefaultStateBitmap.draw(canvas);
break;
case STATE_PRESSED_UP:
mPressedOneDirectionStateBitmap.draw(canvas);
break;
case STATE_PRESSED_RIGHT:
canvas.save();
canvas.rotate(90, px, py);
mPressedOneDirectionStateBitmap.draw(canvas);
canvas.restore();
break;
case STATE_PRESSED_DOWN:
canvas.save();
canvas.rotate(180, px, py);
mPressedOneDirectionStateBitmap.draw(canvas);
canvas.restore();
break;
case STATE_PRESSED_LEFT:
canvas.save();
canvas.rotate(270, px, py);
mPressedOneDirectionStateBitmap.draw(canvas);
canvas.restore();
break;
case STATE_PRESSED_UP_LEFT:
mPressedTwoDirectionsStateBitmap.draw(canvas);
break;
case STATE_PRESSED_UP_RIGHT:
canvas.save();
canvas.rotate(90, px, py);
mPressedTwoDirectionsStateBitmap.draw(canvas);
canvas.restore();
break;
case STATE_PRESSED_DOWN_RIGHT:
canvas.save();
canvas.rotate(180, px, py);
mPressedTwoDirectionsStateBitmap.draw(canvas);
canvas.restore();
break;
case STATE_PRESSED_DOWN_LEFT:
canvas.save();
canvas.rotate(270, px, py);
mPressedTwoDirectionsStateBitmap.draw(canvas);
canvas.restore();
break;
}
} }
return true;
}
public void setPosition(int x, int y) /**
{ * Gets one of the InputOverlayDrawableDpad's button IDs.
mControlPositionX = x; *
mControlPositionY = y; * @return the requested InputOverlayDrawableDpad's button ID.
} */
public int getId(int direction) {
return mButtonType[direction];
}
public void setBounds(int left, int top, int right, int bottom) public int getTrackId() {
{ return mTrackId;
mDefaultStateBitmap.setBounds(left, top, right, bottom); }
mPressedOneDirectionStateBitmap.setBounds(left, top, right, bottom);
mPressedTwoDirectionsStateBitmap.setBounds(left, top, right, bottom);
}
public Rect getBounds() public void setTrackId(int trackId) {
{ mTrackId = trackId;
return mDefaultStateBitmap.getBounds(); }
}
public int getWidth() public boolean onConfigureTouch(MotionEvent event) {
{ int pointerIndex = event.getActionIndex();
return mWidth; int fingerPositionX = (int) event.getX(pointerIndex);
} int fingerPositionY = (int) event.getY(pointerIndex);
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mPreviousTouchX = fingerPositionX;
mPreviousTouchY = fingerPositionY;
break;
case MotionEvent.ACTION_MOVE:
mControlPositionX += fingerPositionX - mPreviousTouchX;
mControlPositionY += fingerPositionY - mPreviousTouchY;
setBounds(mControlPositionX, mControlPositionY, getWidth() + mControlPositionX,
getHeight() + mControlPositionY);
mPreviousTouchX = fingerPositionX;
mPreviousTouchY = fingerPositionY;
break;
public int getHeight() }
{ return true;
return mHeight; }
}
public void setState(int pressState) public void setPosition(int x, int y) {
{ mControlPositionX = x;
mPressState = pressState; mControlPositionY = y;
} }
public void setBounds(int left, int top, int right, int bottom) {
mDefaultStateBitmap.setBounds(left, top, right, bottom);
mPressedOneDirectionStateBitmap.setBounds(left, top, right, bottom);
mPressedTwoDirectionsStateBitmap.setBounds(left, top, right, bottom);
}
public Rect getBounds() {
return mDefaultStateBitmap.getBounds();
}
public int getWidth() {
return mWidth;
}
public int getHeight() {
return mHeight;
}
public void setState(int pressState) {
mPressState = pressState;
}
} }

View File

@ -17,248 +17,224 @@ import android.view.MotionEvent;
* Custom {@link BitmapDrawable} that is capable * Custom {@link BitmapDrawable} that is capable
* of storing it's own ID. * of storing it's own ID.
*/ */
public final class InputOverlayDrawableJoystick public final class InputOverlayDrawableJoystick {
{ private final int[] axisIDs = {0, 0, 0, 0};
private final int[] axisIDs = {0, 0, 0, 0}; private final float[] axises = {0f, 0f};
private final float[] axises = {0f, 0f}; private int trackId = -1;
private int trackId = -1; private int mJoystickType;
private int mJoystickType; private int mControlPositionX, mControlPositionY;
private int mControlPositionX, mControlPositionY; private int mPreviousTouchX, mPreviousTouchY;
private int mPreviousTouchX, mPreviousTouchY; private int mWidth;
private int mWidth; private int mHeight;
private int mHeight; private Rect mVirtBounds;
private Rect mVirtBounds; private Rect mOrigBounds;
private Rect mOrigBounds; private BitmapDrawable mOuterBitmap;
private BitmapDrawable mOuterBitmap; private BitmapDrawable mDefaultStateInnerBitmap;
private BitmapDrawable mDefaultStateInnerBitmap; private BitmapDrawable mPressedStateInnerBitmap;
private BitmapDrawable mPressedStateInnerBitmap; private BitmapDrawable mBoundsBoxBitmap;
private BitmapDrawable mBoundsBoxBitmap; private boolean mPressedState = false;
private boolean mPressedState = false;
/** /**
* Constructor * Constructor
* *
* @param res {@link Resources} instance. * @param res {@link Resources} instance.
* @param bitmapOuter {@link Bitmap} which represents the outer non-movable part of the joystick. * @param bitmapOuter {@link Bitmap} which represents the outer non-movable part of the joystick.
* @param bitmapInnerDefault {@link Bitmap} which represents the default inner movable part of the joystick. * @param bitmapInnerDefault {@link Bitmap} which represents the default inner movable part of the joystick.
* @param bitmapInnerPressed {@link Bitmap} which represents the pressed inner movable part of the joystick. * @param bitmapInnerPressed {@link Bitmap} which represents the pressed inner movable part of the joystick.
* @param rectOuter {@link Rect} which represents the outer joystick bounds. * @param rectOuter {@link Rect} which represents the outer joystick bounds.
* @param rectInner {@link Rect} which represents the inner joystick bounds. * @param rectInner {@link Rect} which represents the inner joystick bounds.
* @param joystick Identifier for which joystick this is. * @param joystick Identifier for which joystick this is.
*/ */
public InputOverlayDrawableJoystick(Resources res, Bitmap bitmapOuter, public InputOverlayDrawableJoystick(Resources res, Bitmap bitmapOuter,
Bitmap bitmapInnerDefault, Bitmap bitmapInnerPressed, Bitmap bitmapInnerDefault, Bitmap bitmapInnerPressed,
Rect rectOuter, Rect rectInner, int joystick) Rect rectOuter, Rect rectInner, int joystick) {
{ axisIDs[0] = joystick + 1; // Up
axisIDs[0] = joystick + 1; // Up axisIDs[1] = joystick + 2; // Down
axisIDs[1] = joystick + 2; // Down axisIDs[2] = joystick + 3; // Left
axisIDs[2] = joystick + 3; // Left axisIDs[3] = joystick + 4; // Right
axisIDs[3] = joystick + 4; // Right mJoystickType = joystick;
mJoystickType = joystick;
mOuterBitmap = new BitmapDrawable(res, bitmapOuter); mOuterBitmap = new BitmapDrawable(res, bitmapOuter);
mDefaultStateInnerBitmap = new BitmapDrawable(res, bitmapInnerDefault); mDefaultStateInnerBitmap = new BitmapDrawable(res, bitmapInnerDefault);
mPressedStateInnerBitmap = new BitmapDrawable(res, bitmapInnerPressed); mPressedStateInnerBitmap = new BitmapDrawable(res, bitmapInnerPressed);
mBoundsBoxBitmap = new BitmapDrawable(res, bitmapOuter); mBoundsBoxBitmap = new BitmapDrawable(res, bitmapOuter);
mWidth = bitmapOuter.getWidth(); mWidth = bitmapOuter.getWidth();
mHeight = bitmapOuter.getHeight(); mHeight = bitmapOuter.getHeight();
setBounds(rectOuter);
mDefaultStateInnerBitmap.setBounds(rectInner);
mPressedStateInnerBitmap.setBounds(rectInner);
mVirtBounds = getBounds();
mOrigBounds = mOuterBitmap.copyBounds();
mBoundsBoxBitmap.setAlpha(0);
mBoundsBoxBitmap.setBounds(getVirtBounds());
SetInnerBounds();
}
/**
* Gets this InputOverlayDrawableJoystick's button ID.
*
* @return this InputOverlayDrawableJoystick's button ID.
*/
public int getId()
{
return mJoystickType;
}
public void draw(Canvas canvas)
{
mOuterBitmap.draw(canvas);
getCurrentStateBitmapDrawable().draw(canvas);
mBoundsBoxBitmap.draw(canvas);
}
public void TrackEvent(MotionEvent event)
{
int pointerIndex = event.getActionIndex();
switch (event.getAction() & MotionEvent.ACTION_MASK)
{
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_POINTER_DOWN:
if (getBounds().contains((int) event.getX(pointerIndex), (int) event.getY(pointerIndex)))
{
mPressedState = true;
mOuterBitmap.setAlpha(0);
mBoundsBoxBitmap.setAlpha(255);
getVirtBounds().offset((int) event.getX(pointerIndex) - getVirtBounds().centerX(),
(int) event.getY(pointerIndex) - getVirtBounds().centerY());
mBoundsBoxBitmap.setBounds(getVirtBounds());
trackId = event.getPointerId(pointerIndex);
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_POINTER_UP:
if (trackId == event.getPointerId(pointerIndex))
{
mPressedState = false;
axises[0] = axises[1] = 0.0f;
mOuterBitmap.setAlpha(255);
mBoundsBoxBitmap.setAlpha(0);
setVirtBounds(new Rect(mOrigBounds.left, mOrigBounds.top, mOrigBounds.right,
mOrigBounds.bottom));
setBounds(new Rect(mOrigBounds.left, mOrigBounds.top, mOrigBounds.right,
mOrigBounds.bottom));
SetInnerBounds();
trackId = -1;
}
break;
}
if (trackId == -1)
return;
for (int i = 0; i < event.getPointerCount(); i++)
{
if (trackId == event.getPointerId(i))
{
float touchX = event.getX(i);
float touchY = event.getY(i);
float maxY = getVirtBounds().bottom;
float maxX = getVirtBounds().right;
touchX -= getVirtBounds().centerX();
maxX -= getVirtBounds().centerX();
touchY -= getVirtBounds().centerY();
maxY -= getVirtBounds().centerY();
final float AxisX = touchX / maxX;
final float AxisY = touchY / maxY;
axises[0] = AxisX;
axises[1] = AxisY;
setBounds(rectOuter);
mDefaultStateInnerBitmap.setBounds(rectInner);
mPressedStateInnerBitmap.setBounds(rectInner);
mVirtBounds = getBounds();
mOrigBounds = mOuterBitmap.copyBounds();
mBoundsBoxBitmap.setAlpha(0);
mBoundsBoxBitmap.setBounds(getVirtBounds());
SetInnerBounds(); SetInnerBounds();
}
} }
}
public boolean onConfigureTouch(MotionEvent event) /**
{ * Gets this InputOverlayDrawableJoystick's button ID.
int pointerIndex = event.getActionIndex(); *
int fingerPositionX = (int) event.getX(pointerIndex); * @return this InputOverlayDrawableJoystick's button ID.
int fingerPositionY = (int) event.getY(pointerIndex); */
switch (event.getAction()) public int getId() {
{ return mJoystickType;
case MotionEvent.ACTION_DOWN: }
mPreviousTouchX = fingerPositionX;
mPreviousTouchY = fingerPositionY; public void draw(Canvas canvas) {
break; mOuterBitmap.draw(canvas);
case MotionEvent.ACTION_MOVE: getCurrentStateBitmapDrawable().draw(canvas);
int deltaX = fingerPositionX - mPreviousTouchX; mBoundsBoxBitmap.draw(canvas);
int deltaY = fingerPositionY - mPreviousTouchY; }
mControlPositionX += deltaX;
mControlPositionY += deltaY; public void TrackEvent(MotionEvent event) {
setBounds(new Rect(mControlPositionX, mControlPositionY, int pointerIndex = event.getActionIndex();
mOuterBitmap.getIntrinsicWidth() + mControlPositionX,
mOuterBitmap.getIntrinsicHeight() + mControlPositionY)); switch (event.getAction() & MotionEvent.ACTION_MASK) {
setVirtBounds(new Rect(mControlPositionX, mControlPositionY, case MotionEvent.ACTION_DOWN:
mOuterBitmap.getIntrinsicWidth() + mControlPositionX, case MotionEvent.ACTION_POINTER_DOWN:
mOuterBitmap.getIntrinsicHeight() + mControlPositionY)); if (getBounds().contains((int) event.getX(pointerIndex), (int) event.getY(pointerIndex))) {
SetInnerBounds(); mPressedState = true;
setOrigBounds(new Rect(new Rect(mControlPositionX, mControlPositionY, mOuterBitmap.setAlpha(0);
mOuterBitmap.getIntrinsicWidth() + mControlPositionX, mBoundsBoxBitmap.setAlpha(255);
mOuterBitmap.getIntrinsicHeight() + mControlPositionY))); getVirtBounds().offset((int) event.getX(pointerIndex) - getVirtBounds().centerX(),
mPreviousTouchX = fingerPositionX; (int) event.getY(pointerIndex) - getVirtBounds().centerY());
mPreviousTouchY = fingerPositionY; mBoundsBoxBitmap.setBounds(getVirtBounds());
break; trackId = event.getPointerId(pointerIndex);
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_POINTER_UP:
if (trackId == event.getPointerId(pointerIndex)) {
mPressedState = false;
axises[0] = axises[1] = 0.0f;
mOuterBitmap.setAlpha(255);
mBoundsBoxBitmap.setAlpha(0);
setVirtBounds(new Rect(mOrigBounds.left, mOrigBounds.top, mOrigBounds.right,
mOrigBounds.bottom));
setBounds(new Rect(mOrigBounds.left, mOrigBounds.top, mOrigBounds.right,
mOrigBounds.bottom));
SetInnerBounds();
trackId = -1;
}
break;
}
if (trackId == -1)
return;
for (int i = 0; i < event.getPointerCount(); i++) {
if (trackId == event.getPointerId(i)) {
float touchX = event.getX(i);
float touchY = event.getY(i);
float maxY = getVirtBounds().bottom;
float maxX = getVirtBounds().right;
touchX -= getVirtBounds().centerX();
maxX -= getVirtBounds().centerX();
touchY -= getVirtBounds().centerY();
maxY -= getVirtBounds().centerY();
final float AxisX = touchX / maxX;
final float AxisY = touchY / maxY;
axises[0] = AxisX;
axises[1] = AxisY;
SetInnerBounds();
}
}
}
public boolean onConfigureTouch(MotionEvent event) {
int pointerIndex = event.getActionIndex();
int fingerPositionX = (int) event.getX(pointerIndex);
int fingerPositionY = (int) event.getY(pointerIndex);
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mPreviousTouchX = fingerPositionX;
mPreviousTouchY = fingerPositionY;
break;
case MotionEvent.ACTION_MOVE:
int deltaX = fingerPositionX - mPreviousTouchX;
int deltaY = fingerPositionY - mPreviousTouchY;
mControlPositionX += deltaX;
mControlPositionY += deltaY;
setBounds(new Rect(mControlPositionX, mControlPositionY,
mOuterBitmap.getIntrinsicWidth() + mControlPositionX,
mOuterBitmap.getIntrinsicHeight() + mControlPositionY));
setVirtBounds(new Rect(mControlPositionX, mControlPositionY,
mOuterBitmap.getIntrinsicWidth() + mControlPositionX,
mOuterBitmap.getIntrinsicHeight() + mControlPositionY));
SetInnerBounds();
setOrigBounds(new Rect(new Rect(mControlPositionX, mControlPositionY,
mOuterBitmap.getIntrinsicWidth() + mControlPositionX,
mOuterBitmap.getIntrinsicHeight() + mControlPositionY)));
mPreviousTouchX = fingerPositionX;
mPreviousTouchY = fingerPositionY;
break;
}
return true;
} }
return true;
}
public float[] getAxisValues() public float[] getAxisValues() {
{ return axises;
return axises; }
}
public int[] getAxisIDs() public int[] getAxisIDs() {
{ return axisIDs;
return axisIDs; }
}
private void SetInnerBounds() private void SetInnerBounds() {
{ int X = getVirtBounds().centerX() + (int) ((axises[0]) * (getVirtBounds().width() / 2));
int X = getVirtBounds().centerX() + (int) ((axises[0]) * (getVirtBounds().width() / 2)); int Y = getVirtBounds().centerY() + (int) ((axises[1]) * (getVirtBounds().height() / 2));
int Y = getVirtBounds().centerY() + (int) ((axises[1]) * (getVirtBounds().height() / 2));
if (X > getVirtBounds().centerX() + (getVirtBounds().width() / 2)) if (X > getVirtBounds().centerX() + (getVirtBounds().width() / 2))
X = getVirtBounds().centerX() + (getVirtBounds().width() / 2); X = getVirtBounds().centerX() + (getVirtBounds().width() / 2);
if (X < getVirtBounds().centerX() - (getVirtBounds().width() / 2)) if (X < getVirtBounds().centerX() - (getVirtBounds().width() / 2))
X = getVirtBounds().centerX() - (getVirtBounds().width() / 2); X = getVirtBounds().centerX() - (getVirtBounds().width() / 2);
if (Y > getVirtBounds().centerY() + (getVirtBounds().height() / 2)) if (Y > getVirtBounds().centerY() + (getVirtBounds().height() / 2))
Y = getVirtBounds().centerY() + (getVirtBounds().height() / 2); Y = getVirtBounds().centerY() + (getVirtBounds().height() / 2);
if (Y < getVirtBounds().centerY() - (getVirtBounds().height() / 2)) if (Y < getVirtBounds().centerY() - (getVirtBounds().height() / 2))
Y = getVirtBounds().centerY() - (getVirtBounds().height() / 2); Y = getVirtBounds().centerY() - (getVirtBounds().height() / 2);
int width = mPressedStateInnerBitmap.getBounds().width() / 2; int width = mPressedStateInnerBitmap.getBounds().width() / 2;
int height = mPressedStateInnerBitmap.getBounds().height() / 2; int height = mPressedStateInnerBitmap.getBounds().height() / 2;
mDefaultStateInnerBitmap.setBounds(X - width, Y - height, X + width, Y + height); mDefaultStateInnerBitmap.setBounds(X - width, Y - height, X + width, Y + height);
mPressedStateInnerBitmap.setBounds(mDefaultStateInnerBitmap.getBounds()); mPressedStateInnerBitmap.setBounds(mDefaultStateInnerBitmap.getBounds());
} }
public void setPosition(int x, int y) public void setPosition(int x, int y) {
{ mControlPositionX = x;
mControlPositionX = x; mControlPositionY = y;
mControlPositionY = y; }
}
private BitmapDrawable getCurrentStateBitmapDrawable() private BitmapDrawable getCurrentStateBitmapDrawable() {
{ return mPressedState ? mPressedStateInnerBitmap : mDefaultStateInnerBitmap;
return mPressedState ? mPressedStateInnerBitmap : mDefaultStateInnerBitmap; }
}
public void setBounds(Rect bounds) public Rect getBounds() {
{ return mOuterBitmap.getBounds();
mOuterBitmap.setBounds(bounds); }
}
public Rect getBounds() public void setBounds(Rect bounds) {
{ mOuterBitmap.setBounds(bounds);
return mOuterBitmap.getBounds(); }
}
private void setVirtBounds(Rect bounds) private void setOrigBounds(Rect bounds) {
{ mOrigBounds = bounds;
mVirtBounds = bounds; }
}
private void setOrigBounds(Rect bounds) private Rect getVirtBounds() {
{ return mVirtBounds;
mOrigBounds = bounds; }
}
private Rect getVirtBounds() private void setVirtBounds(Rect bounds) {
{ mVirtBounds = bounds;
return mVirtBounds; }
}
public int getWidth() public int getWidth() {
{ return mWidth;
return mWidth; }
}
public int getHeight() public int getHeight() {
{ return mHeight;
return mHeight; }
}
} }

View File

@ -30,204 +30,166 @@ import java.util.concurrent.atomic.AtomicBoolean;
* A service that spawns its own thread in order to copy several binary and shader files * A service that spawns its own thread in order to copy several binary and shader files
* from the Dolphin APK to the external file system. * from the Dolphin APK to the external file system.
*/ */
public final class DirectoryInitializationService extends IntentService public final class DirectoryInitializationService extends IntentService {
{ public static final String BROADCAST_ACTION = "org.citra.citra_android.BROADCAST";
public static final String BROADCAST_ACTION = "org.citra.citra_android.BROADCAST";
public static final String EXTRA_STATE = "directoryState"; public static final String EXTRA_STATE = "directoryState";
private static volatile DirectoryInitializationState directoryState = null; private static volatile DirectoryInitializationState directoryState = null;
private static String userPath; private static String userPath;
private static AtomicBoolean isDolphinDirectoryInitializationRunning = new AtomicBoolean(false); private static AtomicBoolean isDolphinDirectoryInitializationRunning = new AtomicBoolean(false);
public enum DirectoryInitializationState public DirectoryInitializationService() {
{ // Superclass constructor is called to name the thread on which this service executes.
DOLPHIN_DIRECTORIES_INITIALIZED, super("DirectoryInitializationService");
EXTERNAL_STORAGE_PERMISSION_NEEDED, }
CANT_FIND_EXTERNAL_STORAGE
}
public DirectoryInitializationService() public static void startService(Context context) {
{ Intent intent = new Intent(context, DirectoryInitializationService.class);
// Superclass constructor is called to name the thread on which this service executes. context.startService(intent);
super("DirectoryInitializationService"); }
}
public static void startService(Context context) private static void deleteDirectoryRecursively(File file) {
{ if (file.isDirectory()) {
Intent intent = new Intent(context, DirectoryInitializationService.class); for (File child : file.listFiles())
context.startService(intent); deleteDirectoryRecursively(child);
}
@Override
protected void onHandleIntent(Intent intent)
{
isDolphinDirectoryInitializationRunning.set(true);
if (directoryState != DirectoryInitializationState.DOLPHIN_DIRECTORIES_INITIALIZED)
{
if (PermissionsHandler.hasWriteAccess(this))
{
if (setDolphinUserDirectory())
{
initializeInternalStorage();
CreateUserDirectories();
NativeLibrary.CreateConfigFile();
directoryState = DirectoryInitializationState.DOLPHIN_DIRECTORIES_INITIALIZED;
} }
else file.delete();
{ }
directoryState = DirectoryInitializationState.CANT_FIND_EXTERNAL_STORAGE;
public static boolean areDolphinDirectoriesReady() {
return directoryState == DirectoryInitializationState.DOLPHIN_DIRECTORIES_INITIALIZED;
}
public static String getUserDirectory() {
if (directoryState == null) {
throw new IllegalStateException("DirectoryInitializationService has to run at least once!");
} else if (isDolphinDirectoryInitializationRunning.get()) {
throw new IllegalStateException(
"DirectoryInitializationService has to finish running first!");
} }
} return userPath;
else
{
directoryState = DirectoryInitializationState.EXTERNAL_STORAGE_PERMISSION_NEEDED;
}
}
isDolphinDirectoryInitializationRunning.set(false);
sendBroadcastState(directoryState);
}
private boolean setDolphinUserDirectory()
{
if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()))
{
File externalPath = Environment.getExternalStorageDirectory();
if (externalPath != null)
{
userPath = externalPath.getAbsolutePath() + "/citra-emu";
Log.debug("[DirectoryInitializationService] User Dir: " + userPath);
// NativeLibrary.SetUserDirectory(userPath);
return true;
}
} }
return false; private static native void CreateUserDirectories();
}
private void initializeInternalStorage() private static native void SetSysDirectory(String path);
{
File sysDirectory = new File(getFilesDir(), "Sys");
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this); @Override
String revision = NativeLibrary.GetGitRevision(); protected void onHandleIntent(Intent intent) {
if (!preferences.getString("sysDirectoryVersion", "").equals(revision)) isDolphinDirectoryInitializationRunning.set(true);
{
// There is no extracted Sys directory, or there is a Sys directory from another
// version of Dolphin that might contain outdated files. Let's (re-)extract Sys.
deleteDirectoryRecursively(sysDirectory);
copyAssetFolder("Sys", sysDirectory, true);
SharedPreferences.Editor editor = preferences.edit(); if (directoryState != DirectoryInitializationState.DOLPHIN_DIRECTORIES_INITIALIZED) {
editor.putString("sysDirectoryVersion", revision); if (PermissionsHandler.hasWriteAccess(this)) {
editor.apply(); if (setDolphinUserDirectory()) {
} initializeInternalStorage();
CreateUserDirectories();
// Let the native code know where the Sys directory is. NativeLibrary.CreateConfigFile();
SetSysDirectory(sysDirectory.getPath()); directoryState = DirectoryInitializationState.DOLPHIN_DIRECTORIES_INITIALIZED;
} } else {
directoryState = DirectoryInitializationState.CANT_FIND_EXTERNAL_STORAGE;
private static void deleteDirectoryRecursively(File file) }
{ } else {
if (file.isDirectory()) directoryState = DirectoryInitializationState.EXTERNAL_STORAGE_PERMISSION_NEEDED;
{ }
for (File child : file.listFiles())
deleteDirectoryRecursively(child);
}
file.delete();
}
public static boolean areDolphinDirectoriesReady()
{
return directoryState == DirectoryInitializationState.DOLPHIN_DIRECTORIES_INITIALIZED;
}
public static String getUserDirectory()
{
if (directoryState == null)
{
throw new IllegalStateException("DirectoryInitializationService has to run at least once!");
}
else if (isDolphinDirectoryInitializationRunning.get())
{
throw new IllegalStateException(
"DirectoryInitializationService has to finish running first!");
}
return userPath;
}
private void sendBroadcastState(DirectoryInitializationState state)
{
Intent localIntent =
new Intent(BROADCAST_ACTION)
.putExtra(EXTRA_STATE, state);
LocalBroadcastManager.getInstance(this).sendBroadcast(localIntent);
}
private void copyAsset(String asset, File output, Boolean overwrite)
{
Log.verbose("[DirectoryInitializationService] Copying File " + asset + " to " + output);
try
{
if (!output.exists() || overwrite)
{
InputStream in = getAssets().open(asset);
OutputStream out = new FileOutputStream(output);
copyFile(in, out);
in.close();
out.close();
}
}
catch (IOException e)
{
Log.error("[DirectoryInitializationService] Failed to copy asset file: " + asset +
e.getMessage());
}
}
private void copyAssetFolder(String assetFolder, File outputFolder, Boolean overwrite)
{
Log.verbose("[DirectoryInitializationService] Copying Folder " + assetFolder + " to " +
outputFolder);
try
{
boolean createdFolder = false;
for (String file : getAssets().list(assetFolder))
{
if (!createdFolder)
{
outputFolder.mkdir();
createdFolder = true;
} }
copyAssetFolder(assetFolder + File.separator + file, new File(outputFolder, file),
overwrite); isDolphinDirectoryInitializationRunning.set(false);
copyAsset(assetFolder + File.separator + file, new File(outputFolder, file), overwrite); sendBroadcastState(directoryState);
}
} }
catch (IOException e)
{ private boolean setDolphinUserDirectory() {
Log.error("[DirectoryInitializationService] Failed to copy asset folder: " + assetFolder + if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
e.getMessage()); File externalPath = Environment.getExternalStorageDirectory();
if (externalPath != null) {
userPath = externalPath.getAbsolutePath() + "/citra-emu";
Log.debug("[DirectoryInitializationService] User Dir: " + userPath);
// NativeLibrary.SetUserDirectory(userPath);
return true;
}
}
return false;
} }
}
private void copyFile(InputStream in, OutputStream out) throws IOException private void initializeInternalStorage() {
{ File sysDirectory = new File(getFilesDir(), "Sys");
byte[] buffer = new byte[1024];
int read;
while ((read = in.read(buffer)) != -1) SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
{ String revision = NativeLibrary.GetGitRevision();
out.write(buffer, 0, read); if (!preferences.getString("sysDirectoryVersion", "").equals(revision)) {
// There is no extracted Sys directory, or there is a Sys directory from another
// version of Dolphin that might contain outdated files. Let's (re-)extract Sys.
deleteDirectoryRecursively(sysDirectory);
copyAssetFolder("Sys", sysDirectory, true);
SharedPreferences.Editor editor = preferences.edit();
editor.putString("sysDirectoryVersion", revision);
editor.apply();
}
// Let the native code know where the Sys directory is.
SetSysDirectory(sysDirectory.getPath());
} }
}
private static native void CreateUserDirectories(); private void sendBroadcastState(DirectoryInitializationState state) {
Intent localIntent =
new Intent(BROADCAST_ACTION)
.putExtra(EXTRA_STATE, state);
LocalBroadcastManager.getInstance(this).sendBroadcast(localIntent);
}
private static native void SetSysDirectory(String path); private void copyAsset(String asset, File output, Boolean overwrite) {
Log.verbose("[DirectoryInitializationService] Copying File " + asset + " to " + output);
try {
if (!output.exists() || overwrite) {
InputStream in = getAssets().open(asset);
OutputStream out = new FileOutputStream(output);
copyFile(in, out);
in.close();
out.close();
}
} catch (IOException e) {
Log.error("[DirectoryInitializationService] Failed to copy asset file: " + asset +
e.getMessage());
}
}
private void copyAssetFolder(String assetFolder, File outputFolder, Boolean overwrite) {
Log.verbose("[DirectoryInitializationService] Copying Folder " + assetFolder + " to " +
outputFolder);
try {
boolean createdFolder = false;
for (String file : getAssets().list(assetFolder)) {
if (!createdFolder) {
outputFolder.mkdir();
createdFolder = true;
}
copyAssetFolder(assetFolder + File.separator + file, new File(outputFolder, file),
overwrite);
copyAsset(assetFolder + File.separator + file, new File(outputFolder, file), overwrite);
}
} catch (IOException e) {
Log.error("[DirectoryInitializationService] Failed to copy asset folder: " + assetFolder +
e.getMessage());
}
}
private void copyFile(InputStream in, OutputStream out) throws IOException {
byte[] buffer = new byte[1024];
int read;
while ((read = in.read(buffer)) != -1) {
out.write(buffer, 0, read);
}
}
public enum DirectoryInitializationState {
DOLPHIN_DIRECTORIES_INITIALIZED,
EXTERNAL_STORAGE_PERMISSION_NEEDED,
CANT_FIND_EXTERNAL_STORAGE
}
} }

View File

@ -3,17 +3,14 @@ package org.citra.citra_android.services;
import android.app.IntentService; import android.app.IntentService;
import android.content.Intent; import android.content.Intent;
public final class USBPermService extends IntentService public final class USBPermService extends IntentService {
{ public USBPermService() {
public USBPermService() super("USBPermService");
{ }
super("USBPermService");
}
// Needed when extending IntentService. // Needed when extending IntentService.
// We don't care about the results of the intent handler for this. // We don't care about the results of the intent handler for this.
@Override @Override
protected void onHandleIntent(Intent intent) protected void onHandleIntent(Intent intent) {
{ }
}
} }

View File

@ -14,144 +14,116 @@ import android.view.View;
* Implementation from: * Implementation from:
* https://gist.github.com/lapastillaroja/858caf1a82791b6c1a36 * https://gist.github.com/lapastillaroja/858caf1a82791b6c1a36
*/ */
public final class DividerItemDecoration extends RecyclerView.ItemDecoration public final class DividerItemDecoration extends RecyclerView.ItemDecoration {
{
private Drawable mDivider; private Drawable mDivider;
private boolean mShowFirstDivider = false; private boolean mShowFirstDivider = false;
private boolean mShowLastDivider = false; private boolean mShowLastDivider = false;
public DividerItemDecoration(Context context, AttributeSet attrs) public DividerItemDecoration(Context context, AttributeSet attrs) {
{ final TypedArray a = context
final TypedArray a = context .obtainStyledAttributes(attrs, new int[]{android.R.attr.listDivider});
.obtainStyledAttributes(attrs, new int[]{android.R.attr.listDivider}); mDivider = a.getDrawable(0);
mDivider = a.getDrawable(0); a.recycle();
a.recycle();
}
public DividerItemDecoration(Context context, AttributeSet attrs, boolean showFirstDivider,
boolean showLastDivider)
{
this(context, attrs);
mShowFirstDivider = showFirstDivider;
mShowLastDivider = showLastDivider;
}
public DividerItemDecoration(Drawable divider)
{
mDivider = divider;
}
public DividerItemDecoration(Drawable divider, boolean showFirstDivider,
boolean showLastDivider)
{
this(divider);
mShowFirstDivider = showFirstDivider;
mShowLastDivider = showLastDivider;
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
RecyclerView.State state)
{
super.getItemOffsets(outRect, view, parent, state);
if (mDivider == null)
{
return;
}
if (parent.getChildPosition(view) < 1)
{
return;
} }
if (getOrientation(parent) == LinearLayoutManager.VERTICAL) public DividerItemDecoration(Context context, AttributeSet attrs, boolean showFirstDivider,
{ boolean showLastDivider) {
outRect.top = mDivider.getIntrinsicHeight(); this(context, attrs);
} mShowFirstDivider = showFirstDivider;
else mShowLastDivider = showLastDivider;
{
outRect.left = mDivider.getIntrinsicWidth();
}
}
@Override
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state)
{
if (mDivider == null)
{
super.onDrawOver(c, parent, state);
return;
} }
// Initialization needed to avoid compiler warning public DividerItemDecoration(Drawable divider) {
int left = 0, right = 0, top = 0, bottom = 0, size; mDivider = divider;
int orientation = getOrientation(parent);
int childCount = parent.getChildCount();
if (orientation == LinearLayoutManager.VERTICAL)
{
size = mDivider.getIntrinsicHeight();
left = parent.getPaddingLeft();
right = parent.getWidth() - parent.getPaddingRight();
}
else
{ //horizontal
size = mDivider.getIntrinsicWidth();
top = parent.getPaddingTop();
bottom = parent.getHeight() - parent.getPaddingBottom();
} }
for (int i = mShowFirstDivider ? 0 : 1; i < childCount; i++) public DividerItemDecoration(Drawable divider, boolean showFirstDivider,
{ boolean showLastDivider) {
View child = parent.getChildAt(i); this(divider);
RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams(); mShowFirstDivider = showFirstDivider;
mShowLastDivider = showLastDivider;
if (orientation == LinearLayoutManager.VERTICAL)
{
top = child.getTop() - params.topMargin;
bottom = top + size;
}
else
{ //horizontal
left = child.getLeft() - params.leftMargin;
right = left + size;
}
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(c);
} }
// show last divider @Override
if (mShowLastDivider && childCount > 0) public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
{ RecyclerView.State state) {
View child = parent.getChildAt(childCount - 1); super.getItemOffsets(outRect, view, parent, state);
RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams(); if (mDivider == null) {
if (orientation == LinearLayoutManager.VERTICAL) return;
{ }
top = child.getBottom() + params.bottomMargin; if (parent.getChildPosition(view) < 1) {
bottom = top + size; return;
} }
else
{ // horizontal
left = child.getRight() + params.rightMargin;
right = left + size;
}
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(c);
}
}
private int getOrientation(RecyclerView parent) if (getOrientation(parent) == LinearLayoutManager.VERTICAL) {
{ outRect.top = mDivider.getIntrinsicHeight();
if (parent.getLayoutManager() instanceof LinearLayoutManager) } else {
{ outRect.left = mDivider.getIntrinsicWidth();
LinearLayoutManager layoutManager = (LinearLayoutManager) parent.getLayoutManager(); }
return layoutManager.getOrientation();
} }
else
{ @Override
throw new IllegalStateException( public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
"DividerItemDecoration can only be used with a LinearLayoutManager."); if (mDivider == null) {
super.onDrawOver(c, parent, state);
return;
}
// Initialization needed to avoid compiler warning
int left = 0, right = 0, top = 0, bottom = 0, size;
int orientation = getOrientation(parent);
int childCount = parent.getChildCount();
if (orientation == LinearLayoutManager.VERTICAL) {
size = mDivider.getIntrinsicHeight();
left = parent.getPaddingLeft();
right = parent.getWidth() - parent.getPaddingRight();
} else { //horizontal
size = mDivider.getIntrinsicWidth();
top = parent.getPaddingTop();
bottom = parent.getHeight() - parent.getPaddingBottom();
}
for (int i = mShowFirstDivider ? 0 : 1; i < childCount; i++) {
View child = parent.getChildAt(i);
RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
if (orientation == LinearLayoutManager.VERTICAL) {
top = child.getTop() - params.topMargin;
bottom = top + size;
} else { //horizontal
left = child.getLeft() - params.leftMargin;
right = left + size;
}
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(c);
}
// show last divider
if (mShowLastDivider && childCount > 0) {
View child = parent.getChildAt(childCount - 1);
RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
if (orientation == LinearLayoutManager.VERTICAL) {
top = child.getBottom() + params.bottomMargin;
bottom = top + size;
} else { // horizontal
left = child.getRight() + params.rightMargin;
right = left + size;
}
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(c);
}
}
private int getOrientation(RecyclerView parent) {
if (parent.getLayoutManager() instanceof LinearLayoutManager) {
LinearLayoutManager layoutManager = (LinearLayoutManager) parent.getLayoutManager();
return layoutManager.getOrientation();
} else {
throw new IllegalStateException(
"DividerItemDecoration can only be used with a LinearLayoutManager.");
}
} }
}
} }

View File

@ -13,13 +13,11 @@ import android.view.View;
/** /**
* Work around a bug with the nVidia Shield. * Work around a bug with the nVidia Shield.
*/ */
public final class NVidiaShieldWorkaroundView extends View public final class NVidiaShieldWorkaroundView extends View {
{ public NVidiaShieldWorkaroundView(Context context, AttributeSet attrs) {
public NVidiaShieldWorkaroundView(Context context, AttributeSet attrs) super(context, attrs);
{
super(context, attrs);
// Setting this seems to workaround the bug // Setting this seems to workaround the bug
setWillNotDraw(false); setWillNotDraw(false);
} }
} }

View File

@ -1,6 +1,5 @@
package org.citra.citra_android.ui.input.gamecube; package org.citra.citra_android.ui.input.gamecube;
public class ControllerFragment public class ControllerFragment {
{
} }

View File

@ -1,6 +1,5 @@
package org.citra.citra_android.ui.input.gamecube; package org.citra.citra_android.ui.input.gamecube;
public class ControllerFragmentPresenter public class ControllerFragmentPresenter {
{
} }

View File

@ -1,6 +1,5 @@
package org.citra.citra_android.ui.input.gamecube; package org.citra.citra_android.ui.input.gamecube;
public interface ControllerFragmentView public interface ControllerFragmentView {
{
} }

View File

@ -11,72 +11,59 @@ import android.widget.TextView;
import org.citra.citra_android.R; import org.citra.citra_android.R;
public class CustomTitleView extends LinearLayout implements TitleViewAdapter.Provider public class CustomTitleView extends LinearLayout implements TitleViewAdapter.Provider {
{ private final TextView mTitleView;
private final TextView mTitleView; private final View mBadgeView;
private final View mBadgeView;
private final TitleViewAdapter mTitleViewAdapter = new TitleViewAdapter() private final TitleViewAdapter mTitleViewAdapter = new TitleViewAdapter() {
{ @Override
@Override public View getSearchAffordanceView() {
public View getSearchAffordanceView() return null;
{ }
return null;
@Override
public void setTitle(CharSequence titleText) {
CustomTitleView.this.setTitle(titleText);
}
@Override
public void setBadgeDrawable(Drawable drawable) {
CustomTitleView.this.setBadgeDrawable(drawable);
}
};
public CustomTitleView(Context context) {
this(context, null);
}
public CustomTitleView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public CustomTitleView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
View root = LayoutInflater.from(context).inflate(R.layout.tv_title, this);
mTitleView = root.findViewById(R.id.title);
mBadgeView = root.findViewById(R.id.badge);
}
public void setTitle(CharSequence title) {
if (title != null) {
mTitleView.setText(title);
mTitleView.setVisibility(View.VISIBLE);
mBadgeView.setVisibility(View.VISIBLE);
}
}
public void setBadgeDrawable(Drawable drawable) {
if (drawable != null) {
mTitleView.setVisibility(View.GONE);
mBadgeView.setVisibility(View.VISIBLE);
}
} }
@Override @Override
public void setTitle(CharSequence titleText) public TitleViewAdapter getTitleViewAdapter() {
{ return mTitleViewAdapter;
CustomTitleView.this.setTitle(titleText);
} }
@Override
public void setBadgeDrawable(Drawable drawable)
{
CustomTitleView.this.setBadgeDrawable(drawable);
}
};
public CustomTitleView(Context context)
{
this(context, null);
}
public CustomTitleView(Context context, AttributeSet attrs)
{
this(context, attrs, 0);
}
public CustomTitleView(Context context, AttributeSet attrs, int defStyle)
{
super(context, attrs, defStyle);
View root = LayoutInflater.from(context).inflate(R.layout.tv_title, this);
mTitleView = root.findViewById(R.id.title);
mBadgeView = root.findViewById(R.id.badge);
}
public void setTitle(CharSequence title)
{
if (title != null)
{
mTitleView.setText(title);
mTitleView.setVisibility(View.VISIBLE);
mBadgeView.setVisibility(View.VISIBLE);
}
}
public void setBadgeDrawable(Drawable drawable)
{
if (drawable != null)
{
mTitleView.setVisibility(View.GONE);
mBadgeView.setVisibility(View.VISIBLE);
}
}
@Override
public TitleViewAdapter getTitleViewAdapter()
{
return mTitleViewAdapter;
}
} }

View File

@ -28,186 +28,161 @@ import org.citra.citra_android.utils.StartupHandler;
* The main Activity of the Lollipop style UI. Manages several PlatformGamesFragments, which * The main Activity of the Lollipop style UI. Manages several PlatformGamesFragments, which
* individually display a grid of available games for each Fragment, in a tabbed layout. * individually display a grid of available games for each Fragment, in a tabbed layout.
*/ */
public final class MainActivity extends AppCompatActivity implements MainView public final class MainActivity extends AppCompatActivity implements MainView {
{ private Toolbar mToolbar;
private Toolbar mToolbar; private int mFrameLayoutId;
private int mFrameLayoutId; private PlatformGamesFragment mPlatformGamesFragment;
private PlatformGamesFragment mPlatformGamesFragment; private FloatingActionButton mFab;
private FloatingActionButton mFab;
private MainPresenter mPresenter = new MainPresenter(this); private MainPresenter mPresenter = new MainPresenter(this);
@Override @Override
protected void onCreate(Bundle savedInstanceState) protected void onCreate(Bundle savedInstanceState) {
{ super.onCreate(savedInstanceState);
super.onCreate(savedInstanceState); setContentView(R.layout.activity_main);
setContentView(R.layout.activity_main);
findViews(); findViews();
setSupportActionBar(mToolbar); setSupportActionBar(mToolbar);
mFrameLayoutId = R.id.games_platform_frame; mFrameLayoutId = R.id.games_platform_frame;
// Set up the FAB. // Set up the FAB.
mFab.setOnClickListener(view -> mPresenter.onFabClick()); mFab.setOnClickListener(view -> mPresenter.onFabClick());
mPresenter.onCreate(); mPresenter.onCreate();
// Stuff in this block only happens when this activity is newly created (i.e. not a rotation) // Stuff in this block only happens when this activity is newly created (i.e. not a rotation)
if (savedInstanceState == null) if (savedInstanceState == null)
StartupHandler.HandleInit(this); StartupHandler.HandleInit(this);
if (PermissionsHandler.hasWriteAccess(this)) if (PermissionsHandler.hasWriteAccess(this)) {
{ mPlatformGamesFragment = new PlatformGamesFragment();
mPlatformGamesFragment = new PlatformGamesFragment(); getSupportFragmentManager().beginTransaction().add(mFrameLayoutId, mPlatformGamesFragment)
getSupportFragmentManager().beginTransaction().add(mFrameLayoutId, mPlatformGamesFragment) .commit();
.commit();
}
}
@Override
protected void onResume()
{
super.onResume();
mPresenter.addDirIfNeeded(new AddDirectoryHelper(this));
}
// TODO: Replace with a ButterKnife injection.
private void findViews()
{
mToolbar = findViewById(R.id.toolbar_main);
mFab = findViewById(R.id.button_add_directory);
}
@Override
public boolean onCreateOptionsMenu(Menu menu)
{
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.menu_game_grid, menu);
return true;
}
/**
* MainView
*/
@Override
public void setVersionString(String version)
{
mToolbar.setSubtitle(version);
}
@Override
public void refresh()
{
getContentResolver().insert(GameProvider.URI_REFRESH, null);
refreshFragment();
}
@Override
public void refreshFragmentScreenshot(int fragmentPosition)
{
// Invalidate Picasso image so that the new screenshot is animated in.
PlatformGamesView fragment = getPlatformGamesView();
if (fragment != null)
{
fragment.refreshScreenshotAtPosition(fragmentPosition);
}
}
@Override
public void launchSettingsActivity(String menuTag)
{
SettingsActivity.launch(this, menuTag, "");
}
@Override
public void launchFileListActivity()
{
FileBrowserHelper.openDirectoryPicker(this);
}
@Override
public void showGames(Cursor games)
{
// no-op. Handled by PlatformGamesFragment.
}
/**
* @param requestCode An int describing whether the Activity that is returning did so successfully.
* @param resultCode An int describing what Activity is giving us this callback.
* @param result The information the returning Activity is providing us.
*/
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent result)
{
switch (requestCode)
{
case MainPresenter.REQUEST_ADD_DIRECTORY:
// If the user picked a file, as opposed to just backing out.
if (resultCode == MainActivity.RESULT_OK)
{
mPresenter.onDirectorySelected(FileBrowserHelper.getSelectedDirectory(result));
} }
break;
case MainPresenter.REQUEST_EMULATE_GAME:
mPresenter.refreshFragmentScreenshot(resultCode);
break;
} }
}
@Override @Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) protected void onResume() {
{ super.onResume();
switch (requestCode) mPresenter.addDirIfNeeded(new AddDirectoryHelper(this));
{ }
case PermissionsHandler.REQUEST_CODE_WRITE_PERMISSION:
if (grantResults[0] == PackageManager.PERMISSION_GRANTED)
{
DirectoryInitializationService.startService(this);
mPlatformGamesFragment = new PlatformGamesFragment(); // TODO: Replace with a ButterKnife injection.
getSupportFragmentManager().beginTransaction().add(mFrameLayoutId, mPlatformGamesFragment) private void findViews() {
.commit(); mToolbar = findViewById(R.id.toolbar_main);
mFab = findViewById(R.id.button_add_directory);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.menu_game_grid, menu);
return true;
}
/**
* MainView
*/
@Override
public void setVersionString(String version) {
mToolbar.setSubtitle(version);
}
@Override
public void refresh() {
getContentResolver().insert(GameProvider.URI_REFRESH, null);
refreshFragment();
}
@Override
public void refreshFragmentScreenshot(int fragmentPosition) {
// Invalidate Picasso image so that the new screenshot is animated in.
PlatformGamesView fragment = getPlatformGamesView();
if (fragment != null) {
fragment.refreshScreenshotAtPosition(fragmentPosition);
} }
else }
{
Toast.makeText(this, R.string.write_permission_needed, Toast.LENGTH_SHORT) @Override
.show(); public void launchSettingsActivity(String menuTag) {
SettingsActivity.launch(this, menuTag, "");
}
@Override
public void launchFileListActivity() {
FileBrowserHelper.openDirectoryPicker(this);
}
@Override
public void showGames(Cursor games) {
// no-op. Handled by PlatformGamesFragment.
}
/**
* @param requestCode An int describing whether the Activity that is returning did so successfully.
* @param resultCode An int describing what Activity is giving us this callback.
* @param result The information the returning Activity is providing us.
*/
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent result) {
switch (requestCode) {
case MainPresenter.REQUEST_ADD_DIRECTORY:
// If the user picked a file, as opposed to just backing out.
if (resultCode == MainActivity.RESULT_OK) {
mPresenter.onDirectorySelected(FileBrowserHelper.getSelectedDirectory(result));
}
break;
case MainPresenter.REQUEST_EMULATE_GAME:
mPresenter.refreshFragmentScreenshot(resultCode);
break;
} }
break;
default:
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
break;
} }
}
/** @Override
* Called by the framework whenever any actionbar/toolbar icon is clicked. public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
* switch (requestCode) {
* @param item The icon that was clicked on. case PermissionsHandler.REQUEST_CODE_WRITE_PERMISSION:
* @return True if the event was handled, false to bubble it up to the OS. if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
*/ DirectoryInitializationService.startService(this);
@Override
public boolean onOptionsItemSelected(MenuItem item)
{
return mPresenter.handleOptionSelection(item.getItemId());
}
private void refreshFragment() mPlatformGamesFragment = new PlatformGamesFragment();
{ getSupportFragmentManager().beginTransaction().add(mFrameLayoutId, mPlatformGamesFragment)
if (mPlatformGamesFragment != null) .commit();
{ } else {
mPlatformGamesFragment.refresh(); Toast.makeText(this, R.string.write_permission_needed, Toast.LENGTH_SHORT)
.show();
}
break;
default:
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
break;
}
} }
}
@Nullable /**
private PlatformGamesView getPlatformGamesView() * Called by the framework whenever any actionbar/toolbar icon is clicked.
{ *
return (PlatformGamesView) getSupportFragmentManager().findFragmentById(mFrameLayoutId); * @param item The icon that was clicked on.
} * @return True if the event was handled, false to bubble it up to the OS.
*/
@Override
public boolean onOptionsItemSelected(MenuItem item) {
return mPresenter.handleOptionSelection(item.getItemId());
}
private void refreshFragment() {
if (mPlatformGamesFragment != null) {
mPlatformGamesFragment.refresh();
}
}
@Nullable
private PlatformGamesView getPlatformGamesView() {
return (PlatformGamesView) getSupportFragmentManager().findFragmentById(mFrameLayoutId);
}
} }

View File

@ -11,80 +11,69 @@ import org.citra.citra_android.utils.SettingsFile;
import rx.android.schedulers.AndroidSchedulers; import rx.android.schedulers.AndroidSchedulers;
import rx.schedulers.Schedulers; import rx.schedulers.Schedulers;
public final class MainPresenter public final class MainPresenter {
{ public static final int REQUEST_ADD_DIRECTORY = 1;
public static final int REQUEST_ADD_DIRECTORY = 1; public static final int REQUEST_EMULATE_GAME = 2;
public static final int REQUEST_EMULATE_GAME = 2;
private final MainView mView; private final MainView mView;
private String mDirToAdd; private String mDirToAdd;
public MainPresenter(MainView view) public MainPresenter(MainView view) {
{ mView = view;
mView = view; }
}
public void onCreate() public void onCreate() {
{ String versionName = BuildConfig.VERSION_NAME;
String versionName = BuildConfig.VERSION_NAME; mView.setVersionString(versionName);
mView.setVersionString(versionName); }
}
public void onFabClick() public void onFabClick() {
{
mView.launchFileListActivity();
}
public boolean handleOptionSelection(int itemId)
{
switch (itemId)
{
case R.id.menu_settings_core:
mView.launchSettingsActivity(SettingsFile.FILE_NAME_CONFIG);
return true;
case R.id.menu_refresh:
GameDatabase databaseHelper = DolphinApplication.databaseHelper;
databaseHelper.scanLibrary(databaseHelper.getWritableDatabase());
mView.refresh();
return true;
case R.id.button_add_directory:
mView.launchFileListActivity(); mView.launchFileListActivity();
return true;
} }
return false; public boolean handleOptionSelection(int itemId) {
} switch (itemId) {
case R.id.menu_settings_core:
mView.launchSettingsActivity(SettingsFile.FILE_NAME_CONFIG);
return true;
public void addDirIfNeeded(AddDirectoryHelper helper) case R.id.menu_refresh:
{ GameDatabase databaseHelper = DolphinApplication.databaseHelper;
if (mDirToAdd != null) databaseHelper.scanLibrary(databaseHelper.getWritableDatabase());
{ mView.refresh();
helper.addDirectory(mDirToAdd, mView::refresh); return true;
mDirToAdd = null; case R.id.button_add_directory:
mView.launchFileListActivity();
return true;
}
return false;
} }
}
public void onDirectorySelected(String dir) public void addDirIfNeeded(AddDirectoryHelper helper) {
{ if (mDirToAdd != null) {
mDirToAdd = dir; helper.addDirectory(mDirToAdd, mView::refresh);
}
public void refreshFragmentScreenshot(int resultCode) mDirToAdd = null;
{ }
mView.refreshFragmentScreenshot(resultCode); }
}
public void onDirectorySelected(String dir) {
mDirToAdd = dir;
}
public void refreshFragmentScreenshot(int resultCode) {
mView.refreshFragmentScreenshot(resultCode);
}
public void loadGames() public void loadGames() {
{ GameDatabase databaseHelper = DolphinApplication.databaseHelper;
GameDatabase databaseHelper = DolphinApplication.databaseHelper;
databaseHelper.getGames() databaseHelper.getGames()
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe(games -> mView.showGames(games)); .subscribe(games -> mView.showGames(games));
} }
} }

View File

@ -7,39 +7,38 @@ import android.database.Cursor;
* Implementations will differ primarily to target touch-screen * Implementations will differ primarily to target touch-screen
* or non-touch screen devices. * or non-touch screen devices.
*/ */
public interface MainView public interface MainView {
{ /**
/** * Pass the view the native library's version string. Displaying
* Pass the view the native library's version string. Displaying * it is optional.
* it is optional. *
* * @param version A string pulled from native code.
* @param version A string pulled from native code. */
*/ void setVersionString(String version);
void setVersionString(String version);
/** /**
* Tell the view to refresh its contents. * Tell the view to refresh its contents.
*/ */
void refresh(); void refresh();
/** /**
* Tell the view to tell the currently displayed {@link android.support.v4.app.Fragment} * Tell the view to tell the currently displayed {@link android.support.v4.app.Fragment}
* to refresh the screenshot at the given position in its list of games. * to refresh the screenshot at the given position in its list of games.
* *
* @param fragmentPosition An index corresponding to the list or grid of games. * @param fragmentPosition An index corresponding to the list or grid of games.
*/ */
void refreshFragmentScreenshot(int fragmentPosition); void refreshFragmentScreenshot(int fragmentPosition);
void launchSettingsActivity(String menuTag); void launchSettingsActivity(String menuTag);
void launchFileListActivity(); void launchFileListActivity();
/** /**
* To be called when an asynchronous database read completes. Passes the * To be called when an asynchronous database read completes. Passes the
* result, in this case a {@link Cursor} to the view. * result, in this case a {@link Cursor} to the view.
* *
* @param games A Cursor containing the games read from the database. * @param games A Cursor containing the games read from the database.
*/ */
void showGames(Cursor games); void showGames(Cursor games);
} }

View File

@ -31,239 +31,208 @@ import org.citra.citra_android.utils.PermissionsHandler;
import org.citra.citra_android.utils.StartupHandler; import org.citra.citra_android.utils.StartupHandler;
import org.citra.citra_android.viewholders.TvGameViewHolder; import org.citra.citra_android.viewholders.TvGameViewHolder;
public final class TvMainActivity extends FragmentActivity implements MainView public final class TvMainActivity extends FragmentActivity implements MainView {
{ private MainPresenter mPresenter = new MainPresenter(this);
private MainPresenter mPresenter = new MainPresenter(this);
private BrowseSupportFragment mBrowseFragment; private BrowseSupportFragment mBrowseFragment;
private ArrayObjectAdapter mRowsAdapter; private ArrayObjectAdapter mRowsAdapter;
@Override @Override
protected void onCreate(Bundle savedInstanceState) protected void onCreate(Bundle savedInstanceState) {
{ super.onCreate(savedInstanceState);
super.onCreate(savedInstanceState); setContentView(R.layout.activity_tv_main);
setContentView(R.layout.activity_tv_main);
setupUI(); setupUI();
mPresenter.onCreate(); mPresenter.onCreate();
// Stuff in this block only happens when this activity is newly created (i.e. not a rotation) // Stuff in this block only happens when this activity is newly created (i.e. not a rotation)
if (savedInstanceState == null) if (savedInstanceState == null)
StartupHandler.HandleInit(this); StartupHandler.HandleInit(this);
}
@Override
protected void onResume()
{
super.onResume();
mPresenter.addDirIfNeeded(new AddDirectoryHelper(this));
}
void setupUI()
{
final FragmentManager fragmentManager = getSupportFragmentManager();
mBrowseFragment = new BrowseSupportFragment();
fragmentManager
.beginTransaction()
.add(R.id.content, mBrowseFragment, "BrowseFragment")
.commit();
// Set display parameters for the BrowseFragment
mBrowseFragment.setHeadersState(BrowseFragment.HEADERS_ENABLED);
mBrowseFragment.setBrandColor(ContextCompat.getColor(this, R.color.citra_orange_dark));
buildRowsAdapter();
mBrowseFragment.setOnItemViewClickedListener(
(itemViewHolder, item, rowViewHolder, row) ->
{
// Special case: user clicked on a settings row item.
if (item instanceof TvSettingsItem)
{
TvSettingsItem settingsItem = (TvSettingsItem) item;
mPresenter.handleOptionSelection(settingsItem.getItemId());
}
else
{
TvGameViewHolder holder = (TvGameViewHolder) itemViewHolder;
// Start the emulation activity and send the path of the clicked ISO to it.
EmulationActivity.launch(TvMainActivity.this,
holder.path,
holder.title,
holder.screenshotPath,
-1,
holder.imageScreenshot);
}
});
}
/**
* MainView
*/
@Override
public void setVersionString(String version)
{
mBrowseFragment.setTitle(version);
}
@Override
public void refresh()
{
recreate();
}
@Override
public void refreshFragmentScreenshot(int fragmentPosition)
{
mRowsAdapter.notifyArrayItemRangeChanged(0, mRowsAdapter.size());
}
@Override
public void launchSettingsActivity(String menuTag)
{
SettingsActivity.launch(this, menuTag, "");
}
@Override
public void launchFileListActivity()
{
FileBrowserHelper.openDirectoryPicker(this);
}
@Override
public void showGames(Cursor games)
{
ListRow row = buildGamesRow(games);
// Add row to the adapter only if it is not empty.
if (row != null)
{
mRowsAdapter.add(games);
} }
}
/** @Override
* Callback from AddDirectoryActivity. Applies any changes necessary to the GameGridActivity. protected void onResume() {
* super.onResume();
* @param requestCode An int describing whether the Activity that is returning did so successfully. mPresenter.addDirIfNeeded(new AddDirectoryHelper(this));
* @param resultCode An int describing what Activity is giving us this callback. }
* @param result The information the returning Activity is providing us.
*/ void setupUI() {
@Override final FragmentManager fragmentManager = getSupportFragmentManager();
protected void onActivityResult(int requestCode, int resultCode, Intent result) mBrowseFragment = new BrowseSupportFragment();
{ fragmentManager
switch (requestCode) .beginTransaction()
{ .add(R.id.content, mBrowseFragment, "BrowseFragment")
case MainPresenter.REQUEST_ADD_DIRECTORY: .commit();
// If the user picked a file, as opposed to just backing out.
if (resultCode == MainActivity.RESULT_OK) // Set display parameters for the BrowseFragment
{ mBrowseFragment.setHeadersState(BrowseFragment.HEADERS_ENABLED);
mPresenter.onDirectorySelected(FileBrowserHelper.getSelectedDirectory(result)); mBrowseFragment.setBrandColor(ContextCompat.getColor(this, R.color.citra_orange_dark));
buildRowsAdapter();
mBrowseFragment.setOnItemViewClickedListener(
(itemViewHolder, item, rowViewHolder, row) ->
{
// Special case: user clicked on a settings row item.
if (item instanceof TvSettingsItem) {
TvSettingsItem settingsItem = (TvSettingsItem) item;
mPresenter.handleOptionSelection(settingsItem.getItemId());
} else {
TvGameViewHolder holder = (TvGameViewHolder) itemViewHolder;
// Start the emulation activity and send the path of the clicked ISO to it.
EmulationActivity.launch(TvMainActivity.this,
holder.path,
holder.title,
holder.screenshotPath,
-1,
holder.imageScreenshot);
}
});
}
/**
* MainView
*/
@Override
public void setVersionString(String version) {
mBrowseFragment.setTitle(version);
}
@Override
public void refresh() {
recreate();
}
@Override
public void refreshFragmentScreenshot(int fragmentPosition) {
mRowsAdapter.notifyArrayItemRangeChanged(0, mRowsAdapter.size());
}
@Override
public void launchSettingsActivity(String menuTag) {
SettingsActivity.launch(this, menuTag, "");
}
@Override
public void launchFileListActivity() {
FileBrowserHelper.openDirectoryPicker(this);
}
@Override
public void showGames(Cursor games) {
ListRow row = buildGamesRow(games);
// Add row to the adapter only if it is not empty.
if (row != null) {
mRowsAdapter.add(games);
} }
break;
case MainPresenter.REQUEST_EMULATE_GAME:
mPresenter.refreshFragmentScreenshot(resultCode);
break;
} }
}
@Override /**
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) * Callback from AddDirectoryActivity. Applies any changes necessary to the GameGridActivity.
{ *
switch (requestCode) * @param requestCode An int describing whether the Activity that is returning did so successfully.
{ * @param resultCode An int describing what Activity is giving us this callback.
case PermissionsHandler.REQUEST_CODE_WRITE_PERMISSION: * @param result The information the returning Activity is providing us.
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) */
{ @Override
DirectoryInitializationService.startService(this); protected void onActivityResult(int requestCode, int resultCode, Intent result) {
loadGames(); switch (requestCode) {
case MainPresenter.REQUEST_ADD_DIRECTORY:
// If the user picked a file, as opposed to just backing out.
if (resultCode == MainActivity.RESULT_OK) {
mPresenter.onDirectorySelected(FileBrowserHelper.getSelectedDirectory(result));
}
break;
case MainPresenter.REQUEST_EMULATE_GAME:
mPresenter.refreshFragmentScreenshot(resultCode);
break;
} }
else }
{
Toast.makeText(this, R.string.write_permission_needed, Toast.LENGTH_SHORT) @Override
.show(); public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
switch (requestCode) {
case PermissionsHandler.REQUEST_CODE_WRITE_PERMISSION:
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
DirectoryInitializationService.startService(this);
loadGames();
} else {
Toast.makeText(this, R.string.write_permission_needed, Toast.LENGTH_SHORT)
.show();
}
break;
default:
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
break;
} }
break;
default:
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
break;
}
}
private void buildRowsAdapter()
{
mRowsAdapter = new ArrayObjectAdapter(new ListRowPresenter());
if (PermissionsHandler.hasWriteAccess(this))
{
loadGames();
} }
mRowsAdapter.add(buildSettingsRow()); private void buildRowsAdapter() {
mRowsAdapter = new ArrayObjectAdapter(new ListRowPresenter());
mBrowseFragment.setAdapter(mRowsAdapter); if (PermissionsHandler.hasWriteAccess(this)) {
} loadGames();
}
private void loadGames() mRowsAdapter.add(buildSettingsRow());
{
mPresenter.loadGames();
}
private ListRow buildGamesRow(Cursor games) mBrowseFragment.setAdapter(mRowsAdapter);
{
// Create an adapter for this row.
CursorObjectAdapter row = new CursorObjectAdapter(new GameRowPresenter());
// If cursor is empty, don't return a Row.
if (!games.moveToFirst())
{
return null;
} }
row.changeCursor(games); private void loadGames() {
row.setMapper(new CursorMapper() mPresenter.loadGames();
{ }
@Override
protected void bindColumns(Cursor cursor)
{
// No-op? Not sure what this does.
}
@Override private ListRow buildGamesRow(Cursor games) {
protected Object bind(Cursor cursor) // Create an adapter for this row.
{ CursorObjectAdapter row = new CursorObjectAdapter(new GameRowPresenter());
return Game.fromCursor(cursor);
}
});
// Create the row, passing it the filled adapter and the header, and give it to the master adapter. // If cursor is empty, don't return a Row.
return new ListRow(null, row); if (!games.moveToFirst()) {
} return null;
}
private ListRow buildSettingsRow() row.changeCursor(games);
{ row.setMapper(new CursorMapper() {
ArrayObjectAdapter rowItems = new ArrayObjectAdapter(new SettingsRowPresenter()); @Override
protected void bindColumns(Cursor cursor) {
// No-op? Not sure what this does.
}
rowItems.add(new TvSettingsItem(R.id.menu_settings_core, @Override
R.drawable.ic_settings_core_tv, protected Object bind(Cursor cursor) {
R.string.grid_menu_core_settings)); return Game.fromCursor(cursor);
}
});
rowItems.add(new TvSettingsItem(R.id.button_add_directory, // Create the row, passing it the filled adapter and the header, and give it to the master adapter.
R.drawable.ic_add_tv, return new ListRow(null, row);
R.string.add_directory_title)); }
rowItems.add(new TvSettingsItem(R.id.menu_refresh, private ListRow buildSettingsRow() {
R.drawable.ic_refresh_tv, ArrayObjectAdapter rowItems = new ArrayObjectAdapter(new SettingsRowPresenter());
R.string.grid_menu_refresh));
// Create a header for this row. rowItems.add(new TvSettingsItem(R.id.menu_settings_core,
HeaderItem header = R.drawable.ic_settings_core_tv,
new HeaderItem(R.string.preferences_settings, getString(R.string.preferences_settings)); R.string.grid_menu_core_settings));
return new ListRow(header, rowItems); rowItems.add(new TvSettingsItem(R.id.button_add_directory,
} R.drawable.ic_add_tv,
R.string.add_directory_title));
rowItems.add(new TvSettingsItem(R.id.menu_refresh,
R.drawable.ic_refresh_tv,
R.string.grid_menu_refresh));
// Create a header for this row.
HeaderItem header =
new HeaderItem(R.string.preferences_settings, getString(R.string.preferences_settings));
return new ListRow(header, rowItems);
}
} }

View File

@ -13,76 +13,66 @@ import android.view.ViewGroup;
import org.citra.citra_android.R; import org.citra.citra_android.R;
import org.citra.citra_android.adapters.GameAdapter; import org.citra.citra_android.adapters.GameAdapter;
public final class PlatformGamesFragment extends Fragment implements PlatformGamesView public final class PlatformGamesFragment extends Fragment implements PlatformGamesView {
{ private static final String ARG_PLATFORM = "platform";
private static final String ARG_PLATFORM = "platform";
private PlatformGamesPresenter mPresenter = new PlatformGamesPresenter(this); private PlatformGamesPresenter mPresenter = new PlatformGamesPresenter(this);
private GameAdapter mAdapter; private GameAdapter mAdapter;
private RecyclerView mRecyclerView; private RecyclerView mRecyclerView;
@Override @Override
public void onCreate(Bundle savedInstanceState) public void onCreate(Bundle savedInstanceState) {
{ super.onCreate(savedInstanceState);
super.onCreate(savedInstanceState);
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
View rootView = inflater.inflate(R.layout.fragment_grid, container, false);
findViews(rootView);
mPresenter.onCreateView();
return rootView;
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState)
{
int columns = getResources().getInteger(R.integer.game_grid_columns);
RecyclerView.LayoutManager layoutManager = new GridLayoutManager(getActivity(), columns);
mAdapter = new GameAdapter();
mRecyclerView.setLayoutManager(layoutManager);
mRecyclerView.setAdapter(mAdapter);
mRecyclerView.addItemDecoration(new GameAdapter.SpacesItemDecoration(8));
}
@Override
public void refreshScreenshotAtPosition(int position)
{
mAdapter.notifyItemChanged(position);
}
@Override
public void refresh()
{
mPresenter.refresh();
}
@Override
public void onItemClick(String gameId)
{
// No-op for now
}
@Override
public void showGames(Cursor games)
{
if (mAdapter != null)
{
mAdapter.swapCursor(games);
} }
}
private void findViews(View root) @Nullable
{ @Override
mRecyclerView = root.findViewById(R.id.grid_games); public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
} View rootView = inflater.inflate(R.layout.fragment_grid, container, false);
findViews(rootView);
mPresenter.onCreateView();
return rootView;
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
int columns = getResources().getInteger(R.integer.game_grid_columns);
RecyclerView.LayoutManager layoutManager = new GridLayoutManager(getActivity(), columns);
mAdapter = new GameAdapter();
mRecyclerView.setLayoutManager(layoutManager);
mRecyclerView.setAdapter(mAdapter);
mRecyclerView.addItemDecoration(new GameAdapter.SpacesItemDecoration(8));
}
@Override
public void refreshScreenshotAtPosition(int position) {
mAdapter.notifyItemChanged(position);
}
@Override
public void refresh() {
mPresenter.refresh();
}
@Override
public void onItemClick(String gameId) {
// No-op for now
}
@Override
public void showGames(Cursor games) {
if (mAdapter != null) {
mAdapter.swapCursor(games);
}
}
private void findViews(View root) {
mRecyclerView = root.findViewById(R.id.grid_games);
}
} }

View File

@ -8,40 +8,35 @@ import org.citra.citra_android.utils.Log;
import rx.android.schedulers.AndroidSchedulers; import rx.android.schedulers.AndroidSchedulers;
import rx.schedulers.Schedulers; import rx.schedulers.Schedulers;
public final class PlatformGamesPresenter public final class PlatformGamesPresenter {
{ private final PlatformGamesView mView;
private final PlatformGamesView mView;
public PlatformGamesPresenter(PlatformGamesView view) public PlatformGamesPresenter(PlatformGamesView view) {
{ mView = view;
mView = view; }
}
public void onCreateView() public void onCreateView() {
{ loadGames();
loadGames(); }
}
public void refresh() public void refresh() {
{ Log.debug("[PlatformGamesPresenter] : Refreshing...");
Log.debug("[PlatformGamesPresenter] : Refreshing..."); loadGames();
loadGames(); }
}
private void loadGames() private void loadGames() {
{ Log.debug("[PlatformGamesPresenter] : Loading games...");
Log.debug("[PlatformGamesPresenter] : Loading games...");
GameDatabase databaseHelper = DolphinApplication.databaseHelper; GameDatabase databaseHelper = DolphinApplication.databaseHelper;
databaseHelper.getGames() databaseHelper.getGames()
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe(games -> .subscribe(games ->
{ {
Log.debug("[PlatformGamesPresenter] : Load finished, swapping cursor..."); Log.debug("[PlatformGamesPresenter] : Load finished, swapping cursor...");
mView.showGames(games); mView.showGames(games);
}); });
} }
} }

View File

@ -5,34 +5,33 @@ import android.database.Cursor;
/** /**
* Abstraction for a screen representing a single platform's games. * Abstraction for a screen representing a single platform's games.
*/ */
public interface PlatformGamesView public interface PlatformGamesView {
{ /**
/** * Tell the view to refresh its contents.
* Tell the view to refresh its contents. */
*/ void refresh();
void refresh();
/** /**
* Tell the view that a certain game's screenshot has been updated, * Tell the view that a certain game's screenshot has been updated,
* and should be redrawn on-screen. * and should be redrawn on-screen.
* *
* @param position The index of the game that should be redrawn. * @param position The index of the game that should be redrawn.
*/ */
void refreshScreenshotAtPosition(int position); void refreshScreenshotAtPosition(int position);
/** /**
* Pass a click event to the view's Presenter. Typically called from the * Pass a click event to the view's Presenter. Typically called from the
* view's list adapter. * view's list adapter.
* *
* @param gameId The ID of the game that was clicked. * @param gameId The ID of the game that was clicked.
*/ */
void onItemClick(String gameId); void onItemClick(String gameId);
/** /**
* To be called when an asynchronous database read completes. Passes the * To be called when an asynchronous database read completes. Passes the
* result, in this case a {@link Cursor}, to the view. * result, in this case a {@link Cursor}, to the view.
* *
* @param games A Cursor containing the games read from the database. * @param games A Cursor containing the games read from the database.
*/ */
void showGames(Cursor games); void showGames(Cursor games);
} }

View File

@ -18,249 +18,215 @@ import org.citra.citra_android.R;
import org.citra.citra_android.model.settings.SettingSection; import org.citra.citra_android.model.settings.SettingSection;
import org.citra.citra_android.services.DirectoryInitializationService; import org.citra.citra_android.services.DirectoryInitializationService;
import org.citra.citra_android.utils.DirectoryStateReceiver; import org.citra.citra_android.utils.DirectoryStateReceiver;
import org.citra.citra_android.utils.Log;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
public final class SettingsActivity extends AppCompatActivity implements SettingsActivityView public final class SettingsActivity extends AppCompatActivity implements SettingsActivityView {
{ private static final String ARG_FILE_NAME = "file_name";
private static final String ARG_FILE_NAME = "file_name"; private static final String ARG_GAME_ID = "game_id";
private static final String ARG_GAME_ID = "game_id";
private static final String FRAGMENT_TAG = "settings"; private static final String FRAGMENT_TAG = "settings";
private SettingsActivityPresenter mPresenter = new SettingsActivityPresenter(this); private SettingsActivityPresenter mPresenter = new SettingsActivityPresenter(this);
private ProgressDialog dialog; private ProgressDialog dialog;
public static void launch(Context context, String menuTag, String gameId) public static void launch(Context context, String menuTag, String gameId) {
{ Intent settings = new Intent(context, SettingsActivity.class);
Intent settings = new Intent(context, SettingsActivity.class); settings.putExtra(ARG_FILE_NAME, menuTag);
settings.putExtra(ARG_FILE_NAME, menuTag); settings.putExtra(ARG_GAME_ID, gameId);
settings.putExtra(ARG_GAME_ID, gameId);
context.startActivity(settings); context.startActivity(settings);
} }
@Override @Override
protected void onCreate(Bundle savedInstanceState) protected void onCreate(Bundle savedInstanceState) {
{ super.onCreate(savedInstanceState);
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_settings); setContentView(R.layout.activity_settings);
Intent launcher = getIntent(); Intent launcher = getIntent();
String filename = launcher.getStringExtra(ARG_FILE_NAME); String filename = launcher.getStringExtra(ARG_FILE_NAME);
String gameID = launcher.getStringExtra(ARG_GAME_ID); String gameID = launcher.getStringExtra(ARG_GAME_ID);
mPresenter.onCreate(savedInstanceState, filename, gameID); mPresenter.onCreate(savedInstanceState, filename, gameID);
} }
@Override @Override
public boolean onCreateOptionsMenu(Menu menu) public boolean onCreateOptionsMenu(Menu menu) {
{ MenuInflater inflater = getMenuInflater();
MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.menu_settings, menu);
inflater.inflate(R.menu.menu_settings, menu);
return true; return true;
} }
@Override @Override
public boolean onOptionsItemSelected(MenuItem item) public boolean onOptionsItemSelected(MenuItem item) {
{ return mPresenter.handleOptionsItem(item.getItemId());
return mPresenter.handleOptionsItem(item.getItemId()); }
}
@Override @Override
protected void onSaveInstanceState(Bundle outState) protected void onSaveInstanceState(Bundle outState) {
{ // Critical: If super method is not called, rotations will be busted.
// Critical: If super method is not called, rotations will be busted. super.onSaveInstanceState(outState);
super.onSaveInstanceState(outState); mPresenter.saveState(outState);
mPresenter.saveState(outState); }
}
@Override @Override
protected void onStart() protected void onStart() {
{ super.onStart();
super.onStart(); mPresenter.onStart();
mPresenter.onStart(); }
}
/** /**
* If this is called, the user has left the settings screen (potentially through the * If this is called, the user has left the settings screen (potentially through the
* home button) and will expect their changes to be persisted. So we kick off an * home button) and will expect their changes to be persisted. So we kick off an
* IntentService which will do so on a background thread. * IntentService which will do so on a background thread.
*/ */
@Override @Override
protected void onStop() protected void onStop() {
{ super.onStop();
super.onStop();
mPresenter.onStop(isFinishing()); mPresenter.onStop(isFinishing());
} }
@Override @Override
public void onBackPressed() public void onBackPressed() {
{ mPresenter.onBackPressed();
mPresenter.onBackPressed(); }
}
@Override @Override
public void showSettingsFragment(String menuTag, boolean addToStack, String gameID) public void showSettingsFragment(String menuTag, boolean addToStack, String gameID) {
{ FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
if (addToStack) if (addToStack) {
{ if (areSystemAnimationsEnabled()) {
if (areSystemAnimationsEnabled()) transaction.setCustomAnimations(
{ R.animator.settings_enter,
transaction.setCustomAnimations( R.animator.settings_exit,
R.animator.settings_enter, R.animator.settings_pop_enter,
R.animator.settings_exit, R.animator.setttings_pop_exit);
R.animator.settings_pop_enter, }
R.animator.setttings_pop_exit);
}
transaction.addToBackStack(null); transaction.addToBackStack(null);
mPresenter.addToStack(); mPresenter.addToStack();
} }
transaction.replace(R.id.frame_content, SettingsFragment.newInstance(menuTag, gameID), FRAGMENT_TAG); transaction.replace(R.id.frame_content, SettingsFragment.newInstance(menuTag, gameID), FRAGMENT_TAG);
transaction.commit(); transaction.commit();
} }
private boolean areSystemAnimationsEnabled() private boolean areSystemAnimationsEnabled() {
{ float duration = Settings.Global.getFloat(
float duration = Settings.Global.getFloat( getContentResolver(),
getContentResolver(), Settings.Global.ANIMATOR_DURATION_SCALE, 1);
Settings.Global.ANIMATOR_DURATION_SCALE, 1); float transition = Settings.Global.getFloat(
float transition = Settings.Global.getFloat( getContentResolver(),
getContentResolver(), Settings.Global.TRANSITION_ANIMATION_SCALE, 1);
Settings.Global.TRANSITION_ANIMATION_SCALE, 1); return duration != 0 && transition != 0;
return duration != 0 && transition != 0; }
}
@Override @Override
public void startDirectoryInitializationService(DirectoryStateReceiver receiver, IntentFilter filter) public void startDirectoryInitializationService(DirectoryStateReceiver receiver, IntentFilter filter) {
{ LocalBroadcastManager.getInstance(this).registerReceiver(
LocalBroadcastManager.getInstance(this).registerReceiver( receiver,
receiver, filter);
filter); DirectoryInitializationService.startService(this);
DirectoryInitializationService.startService(this); }
}
@Override @Override
public void stopListeningToDirectoryInitializationService(DirectoryStateReceiver receiver) public void stopListeningToDirectoryInitializationService(DirectoryStateReceiver receiver) {
{ LocalBroadcastManager.getInstance(this).unregisterReceiver(receiver);
LocalBroadcastManager.getInstance(this).unregisterReceiver(receiver); }
}
@Override @Override
public void showLoading() public void showLoading() {
{ if (dialog == null) {
if (dialog == null) dialog = new ProgressDialog(this);
{ dialog.setMessage(getString(R.string.load_settings));
dialog = new ProgressDialog(this); dialog.setIndeterminate(true);
dialog.setMessage(getString(R.string.load_settings)); }
dialog.setIndeterminate(true);
}
dialog.show(); dialog.show();
} }
@Override @Override
public void hideLoading() public void hideLoading() {
{ dialog.dismiss();
dialog.dismiss(); }
}
@Override @Override
public void showPermissionNeededHint() public void showPermissionNeededHint() {
{ Toast.makeText(this, R.string.write_permission_needed, Toast.LENGTH_SHORT)
Toast.makeText(this, R.string.write_permission_needed, Toast.LENGTH_SHORT) .show();
.show(); }
}
@Override @Override
public void showExternalStorageNotMountedHint() public void showExternalStorageNotMountedHint() {
{ Toast.makeText(this, R.string.external_storage_not_mounted, Toast.LENGTH_SHORT)
Toast.makeText(this, R.string.external_storage_not_mounted, Toast.LENGTH_SHORT) .show();
.show(); }
}
@Override @Override
public HashMap<String, SettingSection> getSettings(int file) public HashMap<String, SettingSection> getSettings(int file) {
{ return mPresenter.getSettings(file);
return mPresenter.getSettings(file); }
}
@Override @Override
public void setSettings(ArrayList<HashMap<String, SettingSection>> settings) public void setSettings(ArrayList<HashMap<String, SettingSection>> settings) {
{ mPresenter.setSettings(settings);
mPresenter.setSettings(settings); }
}
@Override @Override
public void onSettingsFileLoaded(ArrayList<HashMap<String, SettingSection>> settings) public void onSettingsFileLoaded(ArrayList<HashMap<String, SettingSection>> settings) {
{ SettingsFragmentView fragment = getFragment();
SettingsFragmentView fragment = getFragment();
if (fragment != null) if (fragment != null) {
{ fragment.onSettingsFileLoaded(settings);
fragment.onSettingsFileLoaded(settings); }
} }
}
@Override @Override
public void onSettingsFileNotFound() public void onSettingsFileNotFound() {
{ SettingsFragmentView fragment = getFragment();
SettingsFragmentView fragment = getFragment();
if (fragment != null) if (fragment != null) {
{ fragment.loadDefaultSettings();
fragment.loadDefaultSettings(); }
} }
}
@Override @Override
public void showToastMessage(String message) public void showToastMessage(String message) {
{ Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
Toast.makeText(this, message, Toast.LENGTH_SHORT).show(); }
}
@Override @Override
public void popBackStack() public void popBackStack() {
{ getSupportFragmentManager().popBackStackImmediate();
getSupportFragmentManager().popBackStackImmediate(); }
}
@Override @Override
public void onSettingChanged() public void onSettingChanged() {
{ mPresenter.onSettingChanged();
mPresenter.onSettingChanged(); }
}
@Override @Override
public void onGcPadSettingChanged(String key, int value) public void onGcPadSettingChanged(String key, int value) {
{ mPresenter.onGcPadSettingChanged(key, value);
mPresenter.onGcPadSettingChanged(key, value); }
}
@Override @Override
public void onWiimoteSettingChanged(String section, int value) public void onWiimoteSettingChanged(String section, int value) {
{ mPresenter.onWiimoteSettingChanged(section, value);
mPresenter.onWiimoteSettingChanged(section, value); }
}
@Override @Override
public void onExtensionSettingChanged(String key, int value) public void onExtensionSettingChanged(String key, int value) {
{ mPresenter.onExtensionSettingChanged(key, value);
mPresenter.onExtensionSettingChanged(key, value); }
}
private SettingsFragment getFragment() private SettingsFragment getFragment() {
{ return (SettingsFragment) getSupportFragmentManager().findFragmentByTag(FRAGMENT_TAG);
return (SettingsFragment) getSupportFragmentManager().findFragmentByTag(FRAGMENT_TAG); }
}
} }

View File

@ -4,7 +4,6 @@ import android.content.IntentFilter;
import android.os.Bundle; import android.os.Bundle;
import android.text.TextUtils; import android.text.TextUtils;
import org.citra.citra_android.NativeLibrary;
import org.citra.citra_android.R; import org.citra.citra_android.R;
import org.citra.citra_android.model.settings.SettingSection; import org.citra.citra_android.model.settings.SettingSection;
import org.citra.citra_android.services.DirectoryInitializationService; import org.citra.citra_android.services.DirectoryInitializationService;
@ -17,204 +16,167 @@ import java.io.File;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
public final class SettingsActivityPresenter public final class SettingsActivityPresenter {
{ private static final String KEY_SHOULD_SAVE = "should_save";
private static final String KEY_SHOULD_SAVE = "should_save";
private SettingsActivityView mView; private SettingsActivityView mView;
private ArrayList<HashMap<String, SettingSection>> mSettings = new ArrayList<>(); private ArrayList<HashMap<String, SettingSection>> mSettings = new ArrayList<>();
private int mStackCount; private int mStackCount;
private boolean mShouldSave; private boolean mShouldSave;
private DirectoryStateReceiver directoryStateReceiver; private DirectoryStateReceiver directoryStateReceiver;
private String menuTag; private String menuTag;
private String gameId; private String gameId;
public SettingsActivityPresenter(SettingsActivityView view) public SettingsActivityPresenter(SettingsActivityView view) {
{ mView = view;
mView = view; }
}
public void onCreate(Bundle savedInstanceState, String menuTag, String gameId) public void onCreate(Bundle savedInstanceState, String menuTag, String gameId) {
{ if (savedInstanceState == null) {
if (savedInstanceState == null) this.menuTag = menuTag;
{ this.gameId = gameId;
this.menuTag = menuTag; } else {
this.gameId = gameId; mShouldSave = savedInstanceState.getBoolean(KEY_SHOULD_SAVE);
} }
else }
{
mShouldSave = savedInstanceState.getBoolean(KEY_SHOULD_SAVE);
}
}
public void onStart() public void onStart() {
{ prepareDolphinDirectoriesIfNeeded();
prepareDolphinDirectoriesIfNeeded(); }
}
void loadSettingsUI() void loadSettingsUI() {
{ if (mSettings.isEmpty()) {
if (mSettings.isEmpty()) if (!TextUtils.isEmpty(gameId)) {
{ mSettings.add(SettingsFile.SETTINGS_DOLPHIN, SettingsFile.readFile("../GameSettings/" + gameId, mView));
if (!TextUtils.isEmpty(gameId)) } else {
{ mSettings.add(SettingsFile.SETTINGS_DOLPHIN, SettingsFile.readFile(SettingsFile.FILE_NAME_CONFIG, mView));
mSettings.add(SettingsFile.SETTINGS_DOLPHIN, SettingsFile.readFile("../GameSettings/" + gameId, mView)); }
} }
else
{
mSettings.add(SettingsFile.SETTINGS_DOLPHIN, SettingsFile.readFile(SettingsFile.FILE_NAME_CONFIG, mView));
}
}
mView.showSettingsFragment(menuTag, false, gameId); mView.showSettingsFragment(menuTag, false, gameId);
mView.onSettingsFileLoaded(mSettings); mView.onSettingsFileLoaded(mSettings);
} }
private void prepareDolphinDirectoriesIfNeeded() private void prepareDolphinDirectoriesIfNeeded() {
{ File configFile = new File(DirectoryInitializationService.getUserDirectory() + "/config/" + SettingsFile.FILE_NAME_CONFIG + ".ini");
File configFile = new File(DirectoryInitializationService.getUserDirectory() + "/config/"+SettingsFile.FILE_NAME_CONFIG + ".ini"); if (!configFile.exists()) {
if(!configFile.exists()) {
} }
if (DirectoryInitializationService.areDolphinDirectoriesReady()) { if (DirectoryInitializationService.areDolphinDirectoriesReady()) {
loadSettingsUI(); loadSettingsUI();
} else { } else {
mView.showLoading(); mView.showLoading();
IntentFilter statusIntentFilter = new IntentFilter( IntentFilter statusIntentFilter = new IntentFilter(
DirectoryInitializationService.BROADCAST_ACTION); DirectoryInitializationService.BROADCAST_ACTION);
directoryStateReceiver = directoryStateReceiver =
new DirectoryStateReceiver(directoryInitializationState -> new DirectoryStateReceiver(directoryInitializationState ->
{ {
if (directoryInitializationState == DirectoryInitializationState.DOLPHIN_DIRECTORIES_INITIALIZED) if (directoryInitializationState == DirectoryInitializationState.DOLPHIN_DIRECTORIES_INITIALIZED) {
{ mView.hideLoading();
mView.hideLoading(); loadSettingsUI();
loadSettingsUI(); } else if (directoryInitializationState == DirectoryInitializationState.EXTERNAL_STORAGE_PERMISSION_NEEDED) {
} mView.showPermissionNeededHint();
else if (directoryInitializationState == DirectoryInitializationState.EXTERNAL_STORAGE_PERMISSION_NEEDED) mView.hideLoading();
{ } else if (directoryInitializationState == DirectoryInitializationState.CANT_FIND_EXTERNAL_STORAGE) {
mView.showPermissionNeededHint(); mView.showExternalStorageNotMountedHint();
mView.hideLoading(); mView.hideLoading();
} }
else if (directoryInitializationState == DirectoryInitializationState.CANT_FIND_EXTERNAL_STORAGE) });
{
mView.showExternalStorageNotMountedHint();
mView.hideLoading();
}
});
mView.startDirectoryInitializationService(directoryStateReceiver, statusIntentFilter); mView.startDirectoryInitializationService(directoryStateReceiver, statusIntentFilter);
} }
} }
public void setSettings(ArrayList<HashMap<String, SettingSection>> settings) public void setSettings(ArrayList<HashMap<String, SettingSection>> settings) {
{ mSettings = settings;
mSettings = settings; }
}
public HashMap<String, SettingSection> getSettings(int file) public HashMap<String, SettingSection> getSettings(int file) {
{ return mSettings.get(file);
return mSettings.get(file); }
}
public void onStop(boolean finishing) public void onStop(boolean finishing) {
{ if (directoryStateReceiver != null) {
if (directoryStateReceiver != null) mView.stopListeningToDirectoryInitializationService(directoryStateReceiver);
{ directoryStateReceiver = null;
mView.stopListeningToDirectoryInitializationService(directoryStateReceiver); }
directoryStateReceiver = null;
}
if (mSettings != null && finishing && mShouldSave) if (mSettings != null && finishing && mShouldSave) {
{ if (!TextUtils.isEmpty(gameId)) {
if (!TextUtils.isEmpty(gameId)) { Log.debug("[SettingsActivity] Settings activity stopping. Saving settings to INI...");
Log.debug("[SettingsActivity] Settings activity stopping. Saving settings to INI..."); // Needed workaround for now due to an odd bug in how it handles saving two different settings sections to the same file. It won't save GFX settings if it follows the normal saving pattern
// Needed workaround for now due to an odd bug in how it handles saving two different settings sections to the same file. It won't save GFX settings if it follows the normal saving pattern if (menuTag.equals("Dolphin")) {
if (menuTag.equals("Dolphin")) SettingsFile.saveFile("../GameSettings/" + gameId, mSettings.get(SettingsFile.SETTINGS_DOLPHIN), mView);
{ }
SettingsFile.saveFile("../GameSettings/" + gameId, mSettings.get(SettingsFile.SETTINGS_DOLPHIN), mView); mView.showToastMessage("Saved settings for " + gameId);
} } else {
mView.showToastMessage("Saved settings for " + gameId); Log.debug("[SettingsActivity] Settings activity stopping. Saving settings to INI...");
} else { SettingsFile.saveFile(SettingsFile.FILE_NAME_CONFIG, mSettings.get(SettingsFile.SETTINGS_DOLPHIN), mView);
Log.debug("[SettingsActivity] Settings activity stopping. Saving settings to INI..."); mView.showToastMessage("Saved settings to INI files");
SettingsFile.saveFile(SettingsFile.FILE_NAME_CONFIG, mSettings.get(SettingsFile.SETTINGS_DOLPHIN), mView); }
mView.showToastMessage("Saved settings to INI files"); }
} }
}
}
public void addToStack() public void addToStack() {
{ mStackCount++;
mStackCount++; }
}
public void onBackPressed() public void onBackPressed() {
{ if (mStackCount > 0) {
if (mStackCount > 0) mView.popBackStack();
{ mStackCount--;
mView.popBackStack(); } else {
mStackCount--; mView.finish();
} }
else }
{
mView.finish();
}
}
public boolean handleOptionsItem(int itemId) public boolean handleOptionsItem(int itemId) {
{ switch (itemId) {
switch (itemId) case R.id.menu_save_exit:
{ mView.finish();
case R.id.menu_save_exit: return true;
mView.finish(); }
return true;
}
return false; return false;
} }
public void onSettingChanged() public void onSettingChanged() {
{ mShouldSave = true;
mShouldSave = true; }
}
public void saveState(Bundle outState) public void saveState(Bundle outState) {
{ outState.putBoolean(KEY_SHOULD_SAVE, mShouldSave);
outState.putBoolean(KEY_SHOULD_SAVE, mShouldSave); }
}
public void onGcPadSettingChanged(String key, int value) public void onGcPadSettingChanged(String key, int value) {
{ if (value != 0) // Not disabled
if (value != 0) // Not disabled {
{ mView.showSettingsFragment(key + (value / 6), true, gameId);
mView.showSettingsFragment(key + (value / 6), true, gameId); }
} }
}
public void onWiimoteSettingChanged(String section, int value) public void onWiimoteSettingChanged(String section, int value) {
{ switch (value) {
switch (value) case 1:
{ mView.showSettingsFragment(section, true, gameId);
case 1: break;
mView.showSettingsFragment(section, true, gameId);
break;
case 2: case 2:
mView.showToastMessage("Please make sure Continuous Scanning is enabled in Core Settings."); mView.showToastMessage("Please make sure Continuous Scanning is enabled in Core Settings.");
break; break;
} }
} }
public void onExtensionSettingChanged(String key, int value) public void onExtensionSettingChanged(String key, int value) {
{ if (value != 0) // None
if (value != 0) // None {
{ mView.showSettingsFragment(key + value, true, gameId);
mView.showSettingsFragment(key + value, true, gameId); }
} }
}
} }

View File

@ -11,130 +11,129 @@ import java.util.HashMap;
/** /**
* Abstraction for the Activity that manages SettingsFragments. * Abstraction for the Activity that manages SettingsFragments.
*/ */
public interface SettingsActivityView public interface SettingsActivityView {
{ /**
/** * Show a new SettingsFragment.
* Show a new SettingsFragment. *
* * @param menuTag Identifier for the settings group that should be displayed.
* @param menuTag Identifier for the settings group that should be displayed. * @param addToStack Whether or not this fragment should replace a previous one.
* @param addToStack Whether or not this fragment should replace a previous one. */
*/ void showSettingsFragment(String menuTag, boolean addToStack, String gameId);
void showSettingsFragment(String menuTag, boolean addToStack, String gameId);
/** /**
* Called by a contained Fragment to get access to the Setting HashMap * Called by a contained Fragment to get access to the Setting HashMap
* loaded from disk, so that each Fragment doesn't need to perform its own * loaded from disk, so that each Fragment doesn't need to perform its own
* read operation. * read operation.
* *
* @param file The settings file to load. * @param file The settings file to load.
* @return A possibly null HashMap of Settings. * @return A possibly null HashMap of Settings.
*/ */
HashMap<String, SettingSection> getSettings(int file); HashMap<String, SettingSection> getSettings(int file);
/** /**
* Used to provide the Activity with Settings HashMaps if a Fragment already * Used to provide the Activity with Settings HashMaps if a Fragment already
* has one; for example, if a rotation occurs, the Fragment will not be killed, * has one; for example, if a rotation occurs, the Fragment will not be killed,
* but the Activity will, so the Activity needs to have its HashMaps resupplied. * but the Activity will, so the Activity needs to have its HashMaps resupplied.
* *
* @param settings The ArrayList of all the Settings HashMaps. * @param settings The ArrayList of all the Settings HashMaps.
*/ */
void setSettings(ArrayList<HashMap<String, SettingSection>> settings); void setSettings(ArrayList<HashMap<String, SettingSection>> settings);
/** /**
* Called when an asynchronous load operation completes. * Called when an asynchronous load operation completes.
* *
* @param settings The (possibly null) result of the ini load operation. * @param settings The (possibly null) result of the ini load operation.
*/ */
void onSettingsFileLoaded(ArrayList<HashMap<String, SettingSection>> settings); void onSettingsFileLoaded(ArrayList<HashMap<String, SettingSection>> settings);
/** /**
* Called when an asynchronous load operation fails. * Called when an asynchronous load operation fails.
*/ */
void onSettingsFileNotFound(); void onSettingsFileNotFound();
/** /**
* Display a popup text message on screen. * Display a popup text message on screen.
* *
* @param message The contents of the onscreen message. * @param message The contents of the onscreen message.
*/ */
void showToastMessage(String message); void showToastMessage(String message);
/** /**
* Show the previous fragment. * Show the previous fragment.
*/ */
void popBackStack(); void popBackStack();
/** /**
* End the activity. * End the activity.
*/ */
void finish(); void finish();
/** /**
* Called by a containing Fragment to tell the Activity that a setting was changed; * Called by a containing Fragment to tell the Activity that a setting was changed;
* unless this has been called, the Activity will not save to disk. * unless this has been called, the Activity will not save to disk.
*/ */
void onSettingChanged(); void onSettingChanged();
/** /**
* Called by a containing Fragment to tell the containing Activity that a GCPad's setting * Called by a containing Fragment to tell the containing Activity that a GCPad's setting
* was modified. * was modified.
* *
* @param key Identifier for the GCPad that was modified. * @param key Identifier for the GCPad that was modified.
* @param value New setting for the GCPad. * @param value New setting for the GCPad.
*/ */
void onGcPadSettingChanged(String key, int value); void onGcPadSettingChanged(String key, int value);
/** /**
* Called by a containing Fragment to tell the containing Activity that a Wiimote's setting * Called by a containing Fragment to tell the containing Activity that a Wiimote's setting
* was modified. * was modified.
* *
* @param section Identifier for Wiimote that was modified; Wiimotes are identified by their section, * @param section Identifier for Wiimote that was modified; Wiimotes are identified by their section,
* not their key. * not their key.
* @param value New setting for the Wiimote. * @param value New setting for the Wiimote.
*/ */
void onWiimoteSettingChanged(String section, int value); void onWiimoteSettingChanged(String section, int value);
/** /**
* Called by a containing Fragment to tell the containing Activity that an extension setting * Called by a containing Fragment to tell the containing Activity that an extension setting
* was modified. * was modified.
* *
* @param key Identifier for the extension that was modified. * @param key Identifier for the extension that was modified.
* @param value New setting for the extension. * @param value New setting for the extension.
*/ */
void onExtensionSettingChanged(String key, int value); void onExtensionSettingChanged(String key, int value);
/** /**
* Show loading dialog while loading the settings * Show loading dialog while loading the settings
*/ */
void showLoading(); void showLoading();
/** /**
* Hide the loading the dialog * Hide the loading the dialog
*/ */
void hideLoading(); void hideLoading();
/** /**
* Show a hint to the user that the app needs write to external storage access * Show a hint to the user that the app needs write to external storage access
*/ */
void showPermissionNeededHint(); void showPermissionNeededHint();
/** /**
* Show a hint to the user that the app needs the external storage to be mounted * Show a hint to the user that the app needs the external storage to be mounted
*/ */
void showExternalStorageNotMountedHint(); void showExternalStorageNotMountedHint();
/** /**
* Start the DirectoryInitializationService and listen for the result. * Start the DirectoryInitializationService and listen for the result.
* *
* @param receiver the broadcast receiver for the DirectoryInitializationService * @param receiver the broadcast receiver for the DirectoryInitializationService
* @param filter the Intent broadcasts to be received. * @param filter the Intent broadcasts to be received.
*/ */
void startDirectoryInitializationService(DirectoryStateReceiver receiver, IntentFilter filter); void startDirectoryInitializationService(DirectoryStateReceiver receiver, IntentFilter filter);
/** /**
* Stop listening to the DirectoryInitializationService. * Stop listening to the DirectoryInitializationService.
* *
* @param receiver The broadcast receiver to unregister. * @param receiver The broadcast receiver to unregister.
*/ */
void stopListeningToDirectoryInitializationService(DirectoryStateReceiver receiver); void stopListeningToDirectoryInitializationService(DirectoryStateReceiver receiver);
} }

View File

@ -40,140 +40,125 @@ import org.citra.citra_android.utils.SettingsFile;
import java.util.ArrayList; import java.util.ArrayList;
public final class SettingsAdapter extends RecyclerView.Adapter<SettingViewHolder> public final class SettingsAdapter extends RecyclerView.Adapter<SettingViewHolder>
implements DialogInterface.OnClickListener, SeekBar.OnSeekBarChangeListener implements DialogInterface.OnClickListener, SeekBar.OnSeekBarChangeListener {
{ private SettingsFragmentView mView;
private SettingsFragmentView mView; private Context mContext;
private Context mContext; private ArrayList<SettingsItem> mSettings;
private ArrayList<SettingsItem> mSettings;
private SettingsItem mClickedItem; private SettingsItem mClickedItem;
private int mSeekbarProgress; private int mSeekbarProgress;
private AlertDialog mDialog; private AlertDialog mDialog;
private TextView mTextSliderValue; private TextView mTextSliderValue;
public SettingsAdapter(SettingsFragmentView view, Context context) public SettingsAdapter(SettingsFragmentView view, Context context) {
{ mView = view;
mView = view; mContext = context;
mContext = context; }
}
@Override @Override
public SettingViewHolder onCreateViewHolder(ViewGroup parent, int viewType) public SettingViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
{ View view;
View view; LayoutInflater inflater = LayoutInflater.from(parent.getContext());
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
switch (viewType) switch (viewType) {
{ case SettingsItem.TYPE_HEADER:
case SettingsItem.TYPE_HEADER: view = inflater.inflate(R.layout.list_item_settings_header, parent, false);
view = inflater.inflate(R.layout.list_item_settings_header, parent, false); return new HeaderViewHolder(view, this);
return new HeaderViewHolder(view, this);
case SettingsItem.TYPE_CHECKBOX: case SettingsItem.TYPE_CHECKBOX:
view = inflater.inflate(R.layout.list_item_setting_checkbox, parent, false); view = inflater.inflate(R.layout.list_item_setting_checkbox, parent, false);
return new CheckBoxSettingViewHolder(view, this); return new CheckBoxSettingViewHolder(view, this);
case SettingsItem.TYPE_SINGLE_CHOICE: case SettingsItem.TYPE_SINGLE_CHOICE:
view = inflater.inflate(R.layout.list_item_setting, parent, false); view = inflater.inflate(R.layout.list_item_setting, parent, false);
return new SingleChoiceViewHolder(view, this); return new SingleChoiceViewHolder(view, this);
case SettingsItem.TYPE_SLIDER: case SettingsItem.TYPE_SLIDER:
view = inflater.inflate(R.layout.list_item_setting, parent, false); view = inflater.inflate(R.layout.list_item_setting, parent, false);
return new SliderViewHolder(view, this); return new SliderViewHolder(view, this);
case SettingsItem.TYPE_SUBMENU: case SettingsItem.TYPE_SUBMENU:
view = inflater.inflate(R.layout.list_item_setting, parent, false); view = inflater.inflate(R.layout.list_item_setting, parent, false);
return new SubmenuViewHolder(view, this); return new SubmenuViewHolder(view, this);
case SettingsItem.TYPE_INPUT_BINDING: case SettingsItem.TYPE_INPUT_BINDING:
view = inflater.inflate(R.layout.list_item_setting, parent, false); view = inflater.inflate(R.layout.list_item_setting, parent, false);
return new InputBindingSettingViewHolder(view, this, mContext); return new InputBindingSettingViewHolder(view, this, mContext);
case SettingsItem.TYPE_DATETIME_SETTING: case SettingsItem.TYPE_DATETIME_SETTING:
view = inflater.inflate(R.layout.list_item_setting, parent, false); view = inflater.inflate(R.layout.list_item_setting, parent, false);
return new DateTimeViewHolder(view, this); return new DateTimeViewHolder(view, this);
default: default:
Log.error("[SettingsAdapter] Invalid view type: " + viewType); Log.error("[SettingsAdapter] Invalid view type: " + viewType);
return null; return null;
} }
} }
@Override @Override
public void onBindViewHolder(SettingViewHolder holder, int position) public void onBindViewHolder(SettingViewHolder holder, int position) {
{ holder.bind(getItem(position));
holder.bind(getItem(position)); }
}
private SettingsItem getItem(int position) private SettingsItem getItem(int position) {
{ return mSettings.get(position);
return mSettings.get(position); }
}
@Override @Override
public int getItemCount() public int getItemCount() {
{ if (mSettings != null) {
if (mSettings != null) return mSettings.size();
{ } else {
return mSettings.size(); return 0;
} }
else }
{
return 0;
}
}
@Override @Override
public int getItemViewType(int position) public int getItemViewType(int position) {
{ return getItem(position).getType();
return getItem(position).getType(); }
}
public void setSettings(ArrayList<SettingsItem> settings) public void setSettings(ArrayList<SettingsItem> settings) {
{ mSettings = settings;
mSettings = settings; notifyDataSetChanged();
notifyDataSetChanged(); }
}
public void onBooleanClick(CheckBoxSetting item, int position, boolean checked) public void onBooleanClick(CheckBoxSetting item, int position, boolean checked) {
{ IntSetting setting = item.setChecked(checked);
IntSetting setting = item.setChecked(checked); notifyItemChanged(position);
notifyItemChanged(position);
if (setting != null) if (setting != null) {
{ mView.putSetting(setting);
mView.putSetting(setting); }
}
mView.onSettingChanged(); mView.onSettingChanged();
} }
public void onSingleChoiceClick(SingleChoiceSetting item) public void onSingleChoiceClick(SingleChoiceSetting item) {
{ mClickedItem = item;
mClickedItem = item;
int value = getSelectionForSingleChoiceValue(item); int value = getSelectionForSingleChoiceValue(item);
AlertDialog.Builder builder = new AlertDialog.Builder(mView.getActivity()); AlertDialog.Builder builder = new AlertDialog.Builder(mView.getActivity());
builder.setTitle(item.getNameId()); builder.setTitle(item.getNameId());
builder.setSingleChoiceItems(item.getChoicesId(), value, this); builder.setSingleChoiceItems(item.getChoicesId(), value, this);
mDialog = builder.show(); mDialog = builder.show();
} }
public void onDateTimeClick(DateTimeSetting item){ public void onDateTimeClick(DateTimeSetting item) {
mClickedItem = item; mClickedItem = item;
AlertDialog.Builder builder = new AlertDialog.Builder(mView.getActivity()); AlertDialog.Builder builder = new AlertDialog.Builder(mView.getActivity());
LayoutInflater inflater = LayoutInflater.from(mView.getActivity()); LayoutInflater inflater = LayoutInflater.from(mView.getActivity());
View view = inflater.inflate(R.layout.sysclock_datetime_picker, null); View view = inflater.inflate(R.layout.sysclock_datetime_picker, null);
DatePicker dp = (DatePicker) view.findViewById(R.id.date_picker); DatePicker dp = view.findViewById(R.id.date_picker);
TimePicker tp = (TimePicker) view.findViewById(R.id.time_picker); TimePicker tp = view.findViewById(R.id.time_picker);
//set date and time to substrings of settingValue; format = 2018-12-24 04:20:69 (alright maybe not that 69) //set date and time to substrings of settingValue; format = 2018-12-24 04:20:69 (alright maybe not that 69)
String settingValue = item.getValue(); String settingValue = item.getValue();
@ -188,7 +173,7 @@ public final class SettingsAdapter extends RecyclerView.Adapter<SettingViewHolde
public void onClick(DialogInterface dialog, int which) { public void onClick(DialogInterface dialog, int which) {
//set it //set it
int year = dp.getYear(); int year = dp.getYear();
if (year < 2000){ if (year < 2000) {
year = 2000; year = 2000;
} }
String month = ("00" + (dp.getMonth() + 1)).substring(String.valueOf(dp.getMonth() + 1).length()); String month = ("00" + (dp.getMonth() + 1)).substring(String.valueOf(dp.getMonth() + 1).length());
@ -203,200 +188,169 @@ public final class SettingsAdapter extends RecyclerView.Adapter<SettingViewHolde
closeDialog(); closeDialog();
} }
}; };
DialogInterface.OnClickListener cancel = new DialogInterface.OnClickListener() { DialogInterface.OnClickListener cancel = new DialogInterface.OnClickListener() {
@Override @Override
public void onClick(DialogInterface dialog, int which) { public void onClick(DialogInterface dialog, int which) {
closeDialog(); closeDialog();
} }
}; };
builder.setView(view); builder.setView(view);
builder.setPositiveButton("Set", ok); builder.setPositiveButton("Set", ok);
builder.setNegativeButton("Cancel", cancel); builder.setNegativeButton("Cancel", cancel);
mDialog = builder.show(); mDialog = builder.show();
} }
public void onSliderClick(SliderSetting item)
{
mClickedItem = item;
mSeekbarProgress = item.getSelectedValue();
AlertDialog.Builder builder = new AlertDialog.Builder(mView.getActivity());
LayoutInflater inflater = LayoutInflater.from(mView.getActivity()); public void onSliderClick(SliderSetting item) {
View view = inflater.inflate(R.layout.dialog_seekbar, null); mClickedItem = item;
mSeekbarProgress = item.getSelectedValue();
AlertDialog.Builder builder = new AlertDialog.Builder(mView.getActivity());
builder.setTitle(item.getNameId()); LayoutInflater inflater = LayoutInflater.from(mView.getActivity());
builder.setView(view); View view = inflater.inflate(R.layout.dialog_seekbar, null);
builder.setPositiveButton(R.string.ok, this);
builder.setNegativeButton(R.string.cancel, this);
mDialog = builder.show();
mTextSliderValue = view.findViewById(R.id.text_value); builder.setTitle(item.getNameId());
mTextSliderValue.setText(String.valueOf(mSeekbarProgress)); builder.setView(view);
builder.setPositiveButton(R.string.ok, this);
builder.setNegativeButton(R.string.cancel, this);
mDialog = builder.show();
TextView units = view.findViewById(R.id.text_units); mTextSliderValue = view.findViewById(R.id.text_value);
units.setText(item.getUnits()); mTextSliderValue.setText(String.valueOf(mSeekbarProgress));
SeekBar seekbar = view.findViewById(R.id.seekbar); TextView units = view.findViewById(R.id.text_units);
units.setText(item.getUnits());
seekbar.setMax(item.getMax()); SeekBar seekbar = view.findViewById(R.id.seekbar);
seekbar.setProgress(mSeekbarProgress);
seekbar.setOnSeekBarChangeListener(this); seekbar.setMax(item.getMax());
} seekbar.setProgress(mSeekbarProgress);
public void onSubmenuClick(SubmenuSetting item) seekbar.setOnSeekBarChangeListener(this);
{ }
mView.loadSubMenu(item.getMenuKey());
}
public void onInputBindingClick(final InputBindingSetting item, final int position) public void onSubmenuClick(SubmenuSetting item) {
{ mView.loadSubMenu(item.getMenuKey());
final MotionAlertDialog dialog = new MotionAlertDialog(mContext, item); }
dialog.setTitle(R.string.input_binding);
dialog.setMessage(String.format(mContext.getString(R.string.input_binding_descrip), mContext.getString(item.getNameId())));
dialog.setButton(AlertDialog.BUTTON_NEGATIVE, mContext.getString(R.string.cancel), this);
dialog.setButton(AlertDialog.BUTTON_NEUTRAL, mContext.getString(R.string.clear), (dialogInterface, i) ->
{
item.setValue("");
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(mContext); public void onInputBindingClick(final InputBindingSetting item, final int position) {
SharedPreferences.Editor editor = sharedPreferences.edit(); final MotionAlertDialog dialog = new MotionAlertDialog(mContext, item);
editor.remove(item.getKey()); dialog.setTitle(R.string.input_binding);
editor.apply(); dialog.setMessage(String.format(mContext.getString(R.string.input_binding_descrip), mContext.getString(item.getNameId())));
}); dialog.setButton(AlertDialog.BUTTON_NEGATIVE, mContext.getString(R.string.cancel), this);
dialog.setOnDismissListener(dialog1 -> dialog.setButton(AlertDialog.BUTTON_NEUTRAL, mContext.getString(R.string.clear), (dialogInterface, i) ->
{ {
StringSetting setting = new StringSetting(item.getKey(), item.getSection(), item.getFile(), item.getValue()); item.setValue("");
notifyItemChanged(position);
if (setting != null) SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(mContext);
{ SharedPreferences.Editor editor = sharedPreferences.edit();
mView.putSetting(setting); editor.remove(item.getKey());
} editor.apply();
});
dialog.setOnDismissListener(dialog1 ->
{
StringSetting setting = new StringSetting(item.getKey(), item.getSection(), item.getFile(), item.getValue());
notifyItemChanged(position);
mView.onSettingChanged(); if (setting != null) {
}); mView.putSetting(setting);
dialog.setCanceledOnTouchOutside(false); }
dialog.show();
}
@Override mView.onSettingChanged();
public void onClick(DialogInterface dialog, int which) });
{ dialog.setCanceledOnTouchOutside(false);
if (mClickedItem instanceof SingleChoiceSetting) dialog.show();
{ }
SingleChoiceSetting scSetting = (SingleChoiceSetting) mClickedItem;
int value = getValueForSingleChoiceSelection(scSetting, which); @Override
public void onClick(DialogInterface dialog, int which) {
if (mClickedItem instanceof SingleChoiceSetting) {
SingleChoiceSetting scSetting = (SingleChoiceSetting) mClickedItem;
// Get the backing Setting, which may be null (if for example it was missing from the file) int value = getValueForSingleChoiceSelection(scSetting, which);
IntSetting setting = scSetting.setSelectedValue(value);
if (setting != null)
{
mView.putSetting(setting);
}
closeDialog(); // Get the backing Setting, which may be null (if for example it was missing from the file)
} IntSetting setting = scSetting.setSelectedValue(value);
else if (mClickedItem instanceof SliderSetting) if (setting != null) {
{ mView.putSetting(setting);
SliderSetting sliderSetting = (SliderSetting) mClickedItem; }
if (sliderSetting.getSetting() instanceof FloatSetting)
{
float value;
if (sliderSetting.getKey().equals(SettingsFile.KEY_FRAME_LIMIT)) closeDialog();
{ } else if (mClickedItem instanceof SliderSetting) {
value = mSeekbarProgress / 100.0f; SliderSetting sliderSetting = (SliderSetting) mClickedItem;
} if (sliderSetting.getSetting() instanceof FloatSetting) {
else float value;
{
value = (float) mSeekbarProgress;
}
FloatSetting setting = sliderSetting.setSelectedValue(value); if (sliderSetting.getKey().equals(SettingsFile.KEY_FRAME_LIMIT)) {
if (setting != null) value = mSeekbarProgress / 100.0f;
{ } else {
mView.putSetting(setting); value = (float) mSeekbarProgress;
} }
}
else
{
IntSetting setting = sliderSetting.setSelectedValue(mSeekbarProgress);
if (setting != null)
{
mView.putSetting(setting);
}
}
}
mView.onSettingChanged(); FloatSetting setting = sliderSetting.setSelectedValue(value);
mClickedItem = null; if (setting != null) {
mSeekbarProgress = -1; mView.putSetting(setting);
} }
} else {
IntSetting setting = sliderSetting.setSelectedValue(mSeekbarProgress);
if (setting != null) {
mView.putSetting(setting);
}
}
}
public void closeDialog() mView.onSettingChanged();
{ mClickedItem = null;
if (mDialog != null) mSeekbarProgress = -1;
{ }
mDialog.dismiss();
mDialog = null;
}
}
@Override public void closeDialog() {
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) if (mDialog != null) {
{ mDialog.dismiss();
mSeekbarProgress = progress; mDialog = null;
mTextSliderValue.setText(String.valueOf(mSeekbarProgress)); }
} }
@Override @Override
public void onStartTrackingTouch(SeekBar seekBar) public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
{ mSeekbarProgress = progress;
} mTextSliderValue.setText(String.valueOf(mSeekbarProgress));
}
@Override @Override
public void onStopTrackingTouch(SeekBar seekBar) public void onStartTrackingTouch(SeekBar seekBar) {
{ }
}
private int getValueForSingleChoiceSelection(SingleChoiceSetting item, int which) @Override
{ public void onStopTrackingTouch(SeekBar seekBar) {
int valuesId = item.getValuesId(); }
if (valuesId > 0) private int getValueForSingleChoiceSelection(SingleChoiceSetting item, int which) {
{ int valuesId = item.getValuesId();
int[] valuesArray = mContext.getResources().getIntArray(valuesId);
return valuesArray[which];
}
else
{
return which;
}
}
private int getSelectionForSingleChoiceValue(SingleChoiceSetting item) if (valuesId > 0) {
{ int[] valuesArray = mContext.getResources().getIntArray(valuesId);
int value = item.getSelectedValue(); return valuesArray[which];
int valuesId = item.getValuesId(); } else {
return which;
}
}
if (valuesId > 0) private int getSelectionForSingleChoiceValue(SingleChoiceSetting item) {
{ int value = item.getSelectedValue();
int[] valuesArray = mContext.getResources().getIntArray(valuesId); int valuesId = item.getValuesId();
for (int index = 0; index < valuesArray.length; index++)
{
int current = valuesArray[index];
if (current == value)
{
return index;
}
}
}
else
{
return value;
}
return -1; if (valuesId > 0) {
} int[] valuesArray = mContext.getResources().getIntArray(valuesId);
for (int index = 0; index < valuesArray.length; index++) {
int current = valuesArray[index];
if (current == value) {
return index;
}
}
} else {
return value;
}
return -1;
}
} }

View File

@ -21,169 +21,148 @@ import org.citra.citra_android.utils.SettingsFile;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
public final class SettingsFragment extends Fragment implements SettingsFragmentView public final class SettingsFragment extends Fragment implements SettingsFragmentView {
{ private static final String ARGUMENT_MENU_TAG = "menu_tag";
private static final String ARGUMENT_MENU_TAG = "menu_tag"; private static final String ARGUMENT_GAME_ID = "game_id";
private static final String ARGUMENT_GAME_ID = "game_id";
private SettingsFragmentPresenter mPresenter = new SettingsFragmentPresenter(this); private SettingsFragmentPresenter mPresenter = new SettingsFragmentPresenter(this);
private SettingsActivityView mActivity; private SettingsActivityView mActivity;
private SettingsAdapter mAdapter; private SettingsAdapter mAdapter;
public static Fragment newInstance(String menuTag, String gameId) public static Fragment newInstance(String menuTag, String gameId) {
{ SettingsFragment fragment = new SettingsFragment();
SettingsFragment fragment = new SettingsFragment();
Bundle arguments = new Bundle(); Bundle arguments = new Bundle();
arguments.putString(ARGUMENT_MENU_TAG, menuTag); arguments.putString(ARGUMENT_MENU_TAG, menuTag);
arguments.putString(ARGUMENT_GAME_ID, gameId); arguments.putString(ARGUMENT_GAME_ID, gameId);
fragment.setArguments(arguments); fragment.setArguments(arguments);
return fragment; return fragment;
} }
@Override @Override
public void onAttach(Context context) public void onAttach(Context context) {
{ super.onAttach(context);
super.onAttach(context);
mActivity = (SettingsActivityView) context; mActivity = (SettingsActivityView) context;
mPresenter.onAttach(); mPresenter.onAttach();
} }
/** /**
* This version of onAttach is needed for versions below Marshmallow. * This version of onAttach is needed for versions below Marshmallow.
* *
* @param activity * @param activity
*/ */
@Override @Override
public void onAttach(Activity activity) public void onAttach(Activity activity) {
{ super.onAttach(activity);
super.onAttach(activity);
mActivity = (SettingsActivityView) activity; mActivity = (SettingsActivityView) activity;
mPresenter.onAttach(); mPresenter.onAttach();
} }
@Override @Override
public void onCreate(Bundle savedInstanceState) public void onCreate(Bundle savedInstanceState) {
{ super.onCreate(savedInstanceState);
super.onCreate(savedInstanceState);
setRetainInstance(true); setRetainInstance(true);
String menuTag = getArguments().getString(ARGUMENT_MENU_TAG); String menuTag = getArguments().getString(ARGUMENT_MENU_TAG);
String gameId = getArguments().getString(ARGUMENT_GAME_ID); String gameId = getArguments().getString(ARGUMENT_GAME_ID);
mAdapter = new SettingsAdapter(this, getActivity()); mAdapter = new SettingsAdapter(this, getActivity());
mPresenter.onCreate(menuTag, gameId); mPresenter.onCreate(menuTag, gameId);
} }
@Nullable @Nullable
@Override @Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
{ return inflater.inflate(R.layout.fragment_settings, container, false);
return inflater.inflate(R.layout.fragment_settings, container, false); }
}
@Override @Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
{ LinearLayoutManager manager = new LinearLayoutManager(getActivity());
LinearLayoutManager manager = new LinearLayoutManager(getActivity());
RecyclerView recyclerView = view.findViewById(R.id.list_settings); RecyclerView recyclerView = view.findViewById(R.id.list_settings);
recyclerView.setAdapter(mAdapter); recyclerView.setAdapter(mAdapter);
recyclerView.setLayoutManager(manager); recyclerView.setLayoutManager(manager);
recyclerView.addItemDecoration(new DividerItemDecoration(getActivity(), null)); recyclerView.addItemDecoration(new DividerItemDecoration(getActivity(), null));
SettingsActivityView activity = (SettingsActivityView) getActivity(); SettingsActivityView activity = (SettingsActivityView) getActivity();
ArrayList<HashMap<String, SettingSection>> settings = new ArrayList<>(); ArrayList<HashMap<String, SettingSection>> settings = new ArrayList<>();
settings.add(SettingsFile.SETTINGS_DOLPHIN, activity.getSettings(SettingsFile.SETTINGS_DOLPHIN)); settings.add(SettingsFile.SETTINGS_DOLPHIN, activity.getSettings(SettingsFile.SETTINGS_DOLPHIN));
mPresenter.onViewCreated(settings); mPresenter.onViewCreated(settings);
} }
@Override @Override
public void onDetach() public void onDetach() {
{ super.onDetach();
super.onDetach(); mActivity = null;
mActivity = null;
if (mAdapter != null) if (mAdapter != null) {
{ mAdapter.closeDialog();
mAdapter.closeDialog(); }
} }
}
@Override @Override
public void onSettingsFileLoaded(ArrayList<HashMap<String, SettingSection>> settings) public void onSettingsFileLoaded(ArrayList<HashMap<String, SettingSection>> settings) {
{ mPresenter.setSettings(settings);
mPresenter.setSettings(settings); }
}
@Override @Override
public void passSettingsToActivity(ArrayList<HashMap<String, SettingSection>> settings) public void passSettingsToActivity(ArrayList<HashMap<String, SettingSection>> settings) {
{ if (mActivity != null) {
if (mActivity != null) mActivity.setSettings(settings);
{ }
mActivity.setSettings(settings); }
}
}
@Override @Override
public void showSettingsList(ArrayList<SettingsItem> settingsList) public void showSettingsList(ArrayList<SettingsItem> settingsList) {
{ mAdapter.setSettings(settingsList);
mAdapter.setSettings(settingsList); }
}
@Override @Override
public void loadDefaultSettings() public void loadDefaultSettings() {
{ mPresenter.loadDefaultSettings();
mPresenter.loadDefaultSettings(); }
}
@Override @Override
public void loadSubMenu(String menuKey) public void loadSubMenu(String menuKey) {
{ mActivity.showSettingsFragment(menuKey, true, getArguments().getString(ARGUMENT_GAME_ID));
mActivity.showSettingsFragment(menuKey, true, getArguments().getString(ARGUMENT_GAME_ID)); }
}
@Override @Override
public void showToastMessage(String message) public void showToastMessage(String message) {
{ mActivity.showToastMessage(message);
mActivity.showToastMessage(message); }
}
@Override @Override
public void putSetting(Setting setting) public void putSetting(Setting setting) {
{ mPresenter.putSetting(setting);
mPresenter.putSetting(setting); }
}
@Override @Override
public void onSettingChanged() public void onSettingChanged() {
{ mActivity.onSettingChanged();
mActivity.onSettingChanged(); }
}
@Override @Override
public void onGcPadSettingChanged(String key, int value) public void onGcPadSettingChanged(String key, int value) {
{ mActivity.onGcPadSettingChanged(key, value);
mActivity.onGcPadSettingChanged(key, value); }
}
@Override @Override
public void onWiimoteSettingChanged(String section, int value) public void onWiimoteSettingChanged(String section, int value) {
{ mActivity.onWiimoteSettingChanged(section, value);
mActivity.onWiimoteSettingChanged(section, value); }
}
@Override @Override
public void onExtensionSettingChanged(String key, int value) public void onExtensionSettingChanged(String key, int value) {
{ mActivity.onExtensionSettingChanged(key, value);
mActivity.onExtensionSettingChanged(key, value); }
}
} }

View File

@ -18,205 +18,178 @@ import org.citra.citra_android.utils.SettingsFile;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
public final class SettingsFragmentPresenter public final class SettingsFragmentPresenter {
{ private SettingsFragmentView mView;
private SettingsFragmentView mView;
private String mMenuTag; private String mMenuTag;
private String mGameID; private String mGameID;
private ArrayList<HashMap<String, SettingSection>> mSettings; private ArrayList<HashMap<String, SettingSection>> mSettings;
private ArrayList<SettingsItem> mSettingsList; private ArrayList<SettingsItem> mSettingsList;
private int mControllerNumber; private int mControllerNumber;
private int mControllerType; private int mControllerType;
public SettingsFragmentPresenter(SettingsFragmentView view) public SettingsFragmentPresenter(SettingsFragmentView view) {
{ mView = view;
mView = view; }
}
public void onCreate(String menuTag, String gameId) public void onCreate(String menuTag, String gameId) {
{ mGameID = gameId;
mGameID = gameId; mMenuTag = menuTag;
mMenuTag = menuTag;
} }
public void onViewCreated(ArrayList<HashMap<String, SettingSection>> settings) public void onViewCreated(ArrayList<HashMap<String, SettingSection>> settings) {
{ setSettings(settings);
setSettings(settings); }
}
/** /**
* If the screen is rotated, the Activity will forget the settings map. This fragment * If the screen is rotated, the Activity will forget the settings map. This fragment
* won't, though; so rather than have the Activity reload from disk, have the fragment pass * won't, though; so rather than have the Activity reload from disk, have the fragment pass
* the settings map back to the Activity. * the settings map back to the Activity.
*/ */
public void onAttach() public void onAttach() {
{ if (mSettings != null) {
if (mSettings != null) mView.passSettingsToActivity(mSettings);
{ }
mView.passSettingsToActivity(mSettings); }
}
}
public void putSetting(Setting setting) public void putSetting(Setting setting) {
{ mSettings.get(setting.getFile()).get(setting.getSection()).putSetting(setting);
mSettings.get(setting.getFile()).get(setting.getSection()).putSetting(setting); }
}
public void loadDefaultSettings() public void loadDefaultSettings() {
{ loadSettingsList();
loadSettingsList(); }
}
public void setSettings(ArrayList<HashMap<String, SettingSection>> settings) public void setSettings(ArrayList<HashMap<String, SettingSection>> settings) {
{ if (mSettingsList == null && settings != null) {
if (mSettingsList == null && settings != null) mSettings = settings;
{
mSettings = settings;
loadSettingsList(); loadSettingsList();
} } else {
else mView.showSettingsList(mSettingsList);
{ }
mView.showSettingsList(mSettingsList); }
}
}
private void loadSettingsList() private void loadSettingsList() {
{ if (!TextUtils.isEmpty(mGameID)) {
if (!TextUtils.isEmpty(mGameID)) mView.getActivity().setTitle("Game Settings: " + mGameID);
{ }
mView.getActivity().setTitle("Game Settings: " + mGameID); ArrayList<SettingsItem> sl = new ArrayList<>();
}
ArrayList<SettingsItem> sl = new ArrayList<>();
switch (mMenuTag) switch (mMenuTag) {
{ case SettingsFile.FILE_NAME_CONFIG:
case SettingsFile.FILE_NAME_CONFIG: addCoreSettings(sl);
addCoreSettings(sl); sl.add(new HeaderSetting(null, null, R.string.video_backend, 0));
sl.add(new HeaderSetting(null, null, R.string.video_backend, 0)); addGraphicsSettings(sl);
addGraphicsSettings(sl); break;
break; default:
default: mView.showToastMessage("Unimplemented menu");
mView.showToastMessage("Unimplemented menu"); return;
return; }
}
mSettingsList = sl; mSettingsList = sl;
mView.showSettingsList(mSettingsList); mView.showSettingsList(mSettingsList);
} }
private void addCoreSettings(ArrayList<SettingsItem> sl) private void addCoreSettings(ArrayList<SettingsItem> sl) {
{ Setting useCpuJit = null;
Setting useCpuJit = null; Setting audioStretch = null;
Setting audioStretch = null; Setting region = null;
Setting region = null; Setting systemClock = null;
Setting systemClock = null; Setting dateTime = null;
Setting dateTime = null;
if (!mSettings.get(SettingsFile.SETTINGS_DOLPHIN).isEmpty()) if (!mSettings.get(SettingsFile.SETTINGS_DOLPHIN).isEmpty()) {
{ useCpuJit = mSettings.get(SettingsFile.SETTINGS_DOLPHIN).get(SettingsFile.SECTION_CORE).getSetting(SettingsFile.KEY_CPU_JIT);
useCpuJit = mSettings.get(SettingsFile.SETTINGS_DOLPHIN).get(SettingsFile.SECTION_CORE).getSetting(SettingsFile.KEY_CPU_JIT); audioStretch = mSettings.get(SettingsFile.SETTINGS_DOLPHIN).get(SettingsFile.SECTION_AUDIO).getSetting(SettingsFile.KEY_ENABLE_AUDIO_STRETCHING);
audioStretch = mSettings.get(SettingsFile.SETTINGS_DOLPHIN).get(SettingsFile.SECTION_AUDIO).getSetting(SettingsFile.KEY_ENABLE_AUDIO_STRETCHING); region = mSettings.get(SettingsFile.SETTINGS_DOLPHIN).get(SettingsFile.SECTION_SYSTEM).getSetting(SettingsFile.KEY_REGION_VALUE);
region = mSettings.get(SettingsFile.SETTINGS_DOLPHIN).get(SettingsFile.SECTION_SYSTEM).getSetting(SettingsFile.KEY_REGION_VALUE); systemClock = mSettings.get(SettingsFile.SETTINGS_DOLPHIN).get(SettingsFile.SECTION_SYSTEM).getSetting(SettingsFile.KEY_INIT_CLOCK);
systemClock = mSettings.get(SettingsFile.SETTINGS_DOLPHIN).get(SettingsFile.SECTION_SYSTEM).getSetting(SettingsFile.KEY_INIT_CLOCK); dateTime = mSettings.get(SettingsFile.SETTINGS_DOLPHIN).get(SettingsFile.SECTION_SYSTEM).getSetting(SettingsFile.KEY_INIT_TIME);
dateTime = mSettings.get(SettingsFile.SETTINGS_DOLPHIN).get(SettingsFile.SECTION_SYSTEM).getSetting(SettingsFile.KEY_INIT_TIME); } else {
} mView.passSettingsToActivity(mSettings);
else }
{
mView.passSettingsToActivity(mSettings);
}
String defaultCpuCore = System.getProperty("os.arch"); String defaultCpuCore = System.getProperty("os.arch");
switch (defaultCpuCore) switch (defaultCpuCore) {
{ case "x86_64":
case "x86_64": sl.add(new CheckBoxSetting(SettingsFile.KEY_CPU_JIT, SettingsFile.SECTION_CORE,
sl.add(new CheckBoxSetting(SettingsFile.KEY_CPU_JIT, SettingsFile.SECTION_CORE, SettingsFile.SETTINGS_DOLPHIN, R.string.cpu_jit, 0, true, useCpuJit));
SettingsFile.SETTINGS_DOLPHIN, R.string.cpu_jit, 0, true, useCpuJit)); break;
break; case "aarch64":
case "aarch64": default:
default: break;
break; }
} sl.add(new SingleChoiceSetting(SettingsFile.KEY_REGION_VALUE, SettingsFile.SECTION_SYSTEM, SettingsFile.SETTINGS_DOLPHIN, R.string.region, 0, R.array.regionNames, R.array.regionValues, -1, region));
sl.add(new SingleChoiceSetting(SettingsFile.KEY_REGION_VALUE, SettingsFile.SECTION_SYSTEM,SettingsFile.SETTINGS_DOLPHIN, R.string.region, 0, R.array.regionNames, R.array.regionValues, -1, region)); sl.add(new CheckBoxSetting(SettingsFile.KEY_ENABLE_AUDIO_STRETCHING, SettingsFile.SECTION_AUDIO, SettingsFile.SETTINGS_DOLPHIN, R.string.audio_stretch, R.string.audio_stretch_description, false, audioStretch));
sl.add(new CheckBoxSetting(SettingsFile.KEY_ENABLE_AUDIO_STRETCHING, SettingsFile.SECTION_AUDIO, SettingsFile.SETTINGS_DOLPHIN, R.string.audio_stretch, R.string.audio_stretch_description, false, audioStretch)); sl.add(new SingleChoiceSetting(SettingsFile.KEY_INIT_CLOCK, SettingsFile.SECTION_SYSTEM, SettingsFile.SETTINGS_DOLPHIN, R.string.init_clock, R.string.init_clock_descrip, R.array.systemClockNames, R.array.systemClockValues, 0, systemClock));
sl.add(new SingleChoiceSetting(SettingsFile.KEY_INIT_CLOCK, SettingsFile.SECTION_SYSTEM, SettingsFile.SETTINGS_DOLPHIN, R.string.init_clock, R.string.init_clock_descrip, R.array.systemClockNames, R.array.systemClockValues, 0, systemClock)); sl.add(new DateTimeSetting(SettingsFile.KEY_INIT_TIME, SettingsFile.SECTION_SYSTEM, SettingsFile.SETTINGS_DOLPHIN, R.string.init_time, R.string.init_time_descrip, "2000-01-01 00:00:01", dateTime));
sl.add(new DateTimeSetting(SettingsFile.KEY_INIT_TIME, SettingsFile.SECTION_SYSTEM, SettingsFile.SETTINGS_DOLPHIN, R.string.init_time, R.string.init_time_descrip, "2000-01-01 00:00:01", dateTime)); }
}
private void addGraphicsSettings(ArrayList<SettingsItem> sl) private void addGraphicsSettings(ArrayList<SettingsItem> sl) {
{ Setting hardwareRenderer = null;
Setting hardwareRenderer = null; Setting hardwareShader = null;
Setting hardwareShader = null; Setting shadersAccurateMul = null;
Setting shadersAccurateMul = null; Setting shadersAccurateGs = null;
Setting shadersAccurateGs = null; Setting shaderJitEnable = null;
Setting shaderJitEnable = null; Setting resolutionFactor = null;
Setting resolutionFactor = null; Setting vsyncEnable = null;
Setting vsyncEnable = null; Setting frameLimitEnable = null;
Setting frameLimitEnable = null; Setting frameLimitValue = null;
Setting frameLimitValue = null; Setting stereoscopyEnable = null;
Setting stereoscopyEnable = null; Setting stereoscopyDepth = null;
Setting stereoscopyDepth = null;
if (!mSettings.get(SettingsFile.SETTINGS_DOLPHIN).isEmpty()) if (!mSettings.get(SettingsFile.SETTINGS_DOLPHIN).isEmpty()) {
{ hardwareRenderer = mSettings.get(SettingsFile.SETTINGS_DOLPHIN).get(SettingsFile.SECTION_RENDERER).getSetting(SettingsFile.KEY_HW_RENDERER);
hardwareRenderer = mSettings.get(SettingsFile.SETTINGS_DOLPHIN).get(SettingsFile.SECTION_RENDERER).getSetting(SettingsFile.KEY_HW_RENDERER); hardwareShader = mSettings.get(SettingsFile.SETTINGS_DOLPHIN).get(SettingsFile.SECTION_RENDERER).getSetting(SettingsFile.KEY_HW_SHADER);
hardwareShader = mSettings.get(SettingsFile.SETTINGS_DOLPHIN).get(SettingsFile.SECTION_RENDERER).getSetting(SettingsFile.KEY_HW_SHADER); shadersAccurateMul = mSettings.get(SettingsFile.SETTINGS_DOLPHIN).get(SettingsFile.SECTION_RENDERER).getSetting(SettingsFile.KEY_SHADERS_ACCURATE_MUL);
shadersAccurateMul = mSettings.get(SettingsFile.SETTINGS_DOLPHIN).get(SettingsFile.SECTION_RENDERER).getSetting(SettingsFile.KEY_SHADERS_ACCURATE_MUL); shadersAccurateGs = mSettings.get(SettingsFile.SETTINGS_DOLPHIN).get(SettingsFile.SECTION_RENDERER).getSetting(SettingsFile.KEY_SHADERS_ACCURATE_GS);
shadersAccurateGs = mSettings.get(SettingsFile.SETTINGS_DOLPHIN).get(SettingsFile.SECTION_RENDERER).getSetting(SettingsFile.KEY_SHADERS_ACCURATE_GS); shaderJitEnable = mSettings.get(SettingsFile.SETTINGS_DOLPHIN).get(SettingsFile.SECTION_RENDERER).getSetting(SettingsFile.KEY_USE_SHADER_JIT);
shaderJitEnable = mSettings.get(SettingsFile.SETTINGS_DOLPHIN).get(SettingsFile.SECTION_RENDERER).getSetting(SettingsFile.KEY_USE_SHADER_JIT); resolutionFactor = mSettings.get(SettingsFile.SETTINGS_DOLPHIN).get(SettingsFile.SECTION_RENDERER).getSetting(SettingsFile.KEY_RESOLUTION_FACTOR);
resolutionFactor = mSettings.get(SettingsFile.SETTINGS_DOLPHIN).get(SettingsFile.SECTION_RENDERER).getSetting(SettingsFile.KEY_RESOLUTION_FACTOR); vsyncEnable = mSettings.get(SettingsFile.SETTINGS_DOLPHIN).get(SettingsFile.SECTION_RENDERER).getSetting(SettingsFile.KEY_USE_VSYNC);
vsyncEnable = mSettings.get(SettingsFile.SETTINGS_DOLPHIN).get(SettingsFile.SECTION_RENDERER).getSetting(SettingsFile.KEY_USE_VSYNC); frameLimitEnable = mSettings.get(SettingsFile.SETTINGS_DOLPHIN).get(SettingsFile.SECTION_RENDERER).getSetting(SettingsFile.KEY_FRAME_LIMIT_ENABLED);
frameLimitEnable = mSettings.get(SettingsFile.SETTINGS_DOLPHIN).get(SettingsFile.SECTION_RENDERER).getSetting(SettingsFile.KEY_FRAME_LIMIT_ENABLED); frameLimitValue = mSettings.get(SettingsFile.SETTINGS_DOLPHIN).get(SettingsFile.SECTION_RENDERER).getSetting(SettingsFile.KEY_FRAME_LIMIT);
frameLimitValue = mSettings.get(SettingsFile.SETTINGS_DOLPHIN).get(SettingsFile.SECTION_RENDERER).getSetting(SettingsFile.KEY_FRAME_LIMIT); stereoscopyEnable = mSettings.get(SettingsFile.SETTINGS_DOLPHIN).get(SettingsFile.SECTION_RENDERER).getSetting(SettingsFile.KEY_STEREOSCOPY);
stereoscopyEnable = mSettings.get(SettingsFile.SETTINGS_DOLPHIN).get(SettingsFile.SECTION_RENDERER).getSetting(SettingsFile.KEY_STEREOSCOPY); stereoscopyDepth = mSettings.get(SettingsFile.SETTINGS_DOLPHIN).get(SettingsFile.SECTION_RENDERER).getSetting(SettingsFile.KEY_FACTOR_3D);
stereoscopyDepth = mSettings.get(SettingsFile.SETTINGS_DOLPHIN).get(SettingsFile.SECTION_RENDERER).getSetting(SettingsFile.KEY_FACTOR_3D); } else {
} mView.passSettingsToActivity(mSettings);
else }
{
mView.passSettingsToActivity(mSettings);
}
if (mSettings.get(SettingsFile.SETTINGS_DOLPHIN).isEmpty()) if (mSettings.get(SettingsFile.SETTINGS_DOLPHIN).isEmpty()) {
{ mView.passSettingsToActivity(mSettings);
mView.passSettingsToActivity(mSettings); }
}
sl.add(new CheckBoxSetting(SettingsFile.KEY_HW_RENDERER, SettingsFile.SECTION_RENDERER, SettingsFile.SETTINGS_DOLPHIN, R.string.hw_renderer, 0, true, hardwareRenderer)); sl.add(new CheckBoxSetting(SettingsFile.KEY_HW_RENDERER, SettingsFile.SECTION_RENDERER, SettingsFile.SETTINGS_DOLPHIN, R.string.hw_renderer, 0, true, hardwareRenderer));
sl.add(new CheckBoxSetting(SettingsFile.KEY_HW_SHADER, SettingsFile.SECTION_RENDERER, SettingsFile.SETTINGS_DOLPHIN, R.string.hw_shaders, R.string.hw_shaders_descrip, true, hardwareShader)); sl.add(new CheckBoxSetting(SettingsFile.KEY_HW_SHADER, SettingsFile.SECTION_RENDERER, SettingsFile.SETTINGS_DOLPHIN, R.string.hw_shaders, R.string.hw_shaders_descrip, true, hardwareShader));
sl.add(new CheckBoxSetting(SettingsFile.KEY_SHADERS_ACCURATE_MUL, SettingsFile.SECTION_RENDERER, SettingsFile.SETTINGS_DOLPHIN, R.string.shaders_accurate_mul, 0, false, shadersAccurateMul)); sl.add(new CheckBoxSetting(SettingsFile.KEY_SHADERS_ACCURATE_MUL, SettingsFile.SECTION_RENDERER, SettingsFile.SETTINGS_DOLPHIN, R.string.shaders_accurate_mul, 0, false, shadersAccurateMul));
sl.add(new CheckBoxSetting(SettingsFile.KEY_SHADERS_ACCURATE_GS, SettingsFile.SECTION_RENDERER, SettingsFile.SETTINGS_DOLPHIN, R.string.shaders_accurate_gs , 0, false, shadersAccurateGs)); sl.add(new CheckBoxSetting(SettingsFile.KEY_SHADERS_ACCURATE_GS, SettingsFile.SECTION_RENDERER, SettingsFile.SETTINGS_DOLPHIN, R.string.shaders_accurate_gs, 0, false, shadersAccurateGs));
sl.add(new CheckBoxSetting(SettingsFile.KEY_USE_SHADER_JIT, SettingsFile.SECTION_RENDERER, SettingsFile.SETTINGS_DOLPHIN, R.string.use_shader_jit, 0, true, shaderJitEnable)); sl.add(new CheckBoxSetting(SettingsFile.KEY_USE_SHADER_JIT, SettingsFile.SECTION_RENDERER, SettingsFile.SETTINGS_DOLPHIN, R.string.use_shader_jit, 0, true, shaderJitEnable));
sl.add(new SliderSetting(SettingsFile.KEY_RESOLUTION_FACTOR, SettingsFile.SECTION_RENDERER, SettingsFile.SETTINGS_DOLPHIN, R.string.internal_resolution, R.string.internal_resolution_descrip, 10, "x", 0, resolutionFactor)); sl.add(new SliderSetting(SettingsFile.KEY_RESOLUTION_FACTOR, SettingsFile.SECTION_RENDERER, SettingsFile.SETTINGS_DOLPHIN, R.string.internal_resolution, R.string.internal_resolution_descrip, 10, "x", 0, resolutionFactor));
sl.add(new CheckBoxSetting(SettingsFile.KEY_USE_VSYNC, SettingsFile.SECTION_RENDERER, SettingsFile.SETTINGS_DOLPHIN, R.string.vsync , 0, false, vsyncEnable)); sl.add(new CheckBoxSetting(SettingsFile.KEY_USE_VSYNC, SettingsFile.SECTION_RENDERER, SettingsFile.SETTINGS_DOLPHIN, R.string.vsync, 0, false, vsyncEnable));
sl.add(new CheckBoxSetting(SettingsFile.KEY_FRAME_LIMIT_ENABLED, SettingsFile.SECTION_RENDERER, SettingsFile.SETTINGS_DOLPHIN, R.string.overclock_enable, 0, false, frameLimitEnable)); sl.add(new CheckBoxSetting(SettingsFile.KEY_FRAME_LIMIT_ENABLED, SettingsFile.SECTION_RENDERER, SettingsFile.SETTINGS_DOLPHIN, R.string.overclock_enable, 0, false, frameLimitEnable));
sl.add(new SliderSetting(SettingsFile.KEY_FRAME_LIMIT, SettingsFile.SECTION_RENDERER, SettingsFile.SETTINGS_DOLPHIN, R.string.overclock_title, R.string.overclock_enable_description, 500, "%", 100, frameLimitValue)); sl.add(new SliderSetting(SettingsFile.KEY_FRAME_LIMIT, SettingsFile.SECTION_RENDERER, SettingsFile.SETTINGS_DOLPHIN, R.string.overclock_title, R.string.overclock_enable_description, 500, "%", 100, frameLimitValue));
// Todo: Implement ColorPickerSetting // Todo: Implement ColorPickerSetting
// sl.add(new ColorPickerSetting(SettingsFile.KEY_BACKGROUND_RED,SettingsFile.KEY_BACKGROUND_GREEN,SettingsFile.KEY_BACKGROUND_BLUE, SettingsFile.SECTION_RENDERER, SettingsFile.SETTINGS_DOLPHIN, R.string., R.string., Color.BLACK, backgroundColor)); // sl.add(new ColorPickerSetting(SettingsFile.KEY_BACKGROUND_RED,SettingsFile.KEY_BACKGROUND_GREEN,SettingsFile.KEY_BACKGROUND_BLUE, SettingsFile.SECTION_RENDERER, SettingsFile.SETTINGS_DOLPHIN, R.string., R.string., Color.BLACK, backgroundColor));
/* /*
Check if we support stereo Check if we support stereo
If we support desktop GL then we must support at least OpenGL 3.2 If we support desktop GL then we must support at least OpenGL 3.2
If we only support OpenGLES then we need both OpenGLES 3.1 and AEP If we only support OpenGLES then we need both OpenGLES 3.1 and AEP
*/ */
EGLHelper helper = new EGLHelper(EGLHelper.EGL_OPENGL_ES2_BIT); EGLHelper helper = new EGLHelper(EGLHelper.EGL_OPENGL_ES2_BIT);
if ((helper.supportsOpenGL() && helper.GetVersion() >= 320) || if ((helper.supportsOpenGL() && helper.GetVersion() >= 320) ||
(helper.supportsGLES3() && helper.GetVersion() >= 310 && helper.SupportsExtension("GL_ANDROID_extension_pack_es31a"))) (helper.supportsGLES3() && helper.GetVersion() >= 310 && helper.SupportsExtension("GL_ANDROID_extension_pack_es31a"))) {
{ sl.add(new CheckBoxSetting(SettingsFile.KEY_STEREOSCOPY, SettingsFile.SECTION_RENDERER, SettingsFile.SETTINGS_DOLPHIN, R.string.stereoscopy, R.string.stereoscopy_descrip, false, stereoscopyEnable));
sl.add(new CheckBoxSetting(SettingsFile.KEY_STEREOSCOPY, SettingsFile.SECTION_RENDERER, SettingsFile.SETTINGS_DOLPHIN, R.string.stereoscopy, R.string.stereoscopy_descrip, false, stereoscopyEnable)); sl.add(new SliderSetting(SettingsFile.KEY_FACTOR_3D, SettingsFile.SECTION_RENDERER, SettingsFile.SETTINGS_DOLPHIN, R.string.sterescopy_depth, R.string.sterescopy_depth_descrip, 100, "%", 0, stereoscopyDepth));
sl.add(new SliderSetting(SettingsFile.KEY_FACTOR_3D, SettingsFile.SECTION_RENDERER, SettingsFile.SETTINGS_DOLPHIN, R.string.sterescopy_depth,R.string.sterescopy_depth_descrip,100,"%",0, stereoscopyDepth)); }
}
} }
private void addGcPadSubSettings(ArrayList<SettingsItem> sl, int gcPadNumber, int gcPadType) private void addGcPadSubSettings(ArrayList<SettingsItem> sl, int gcPadNumber, int gcPadType) {
{
/* /*
if (gcPadType == 1) // Emulated if (gcPadType == 1) // Emulated
{ {
@ -280,18 +253,14 @@ public final class SettingsFragmentPresenter
sl.add(new CheckBoxSetting(SettingsFile.KEY_GCADAPTER_BONGOS + gcPadNumber, SettingsFile.SECTION_CORE, SettingsFile.SETTINGS_DOLPHIN, R.string.gc_adapter_bongos, R.string.gc_adapter_bongos_description, false, bongos)); sl.add(new CheckBoxSetting(SettingsFile.KEY_GCADAPTER_BONGOS + gcPadNumber, SettingsFile.SECTION_CORE, SettingsFile.SETTINGS_DOLPHIN, R.string.gc_adapter_bongos, R.string.gc_adapter_bongos_description, false, bongos));
} }
*/ */
} }
private boolean getInvertedBooleanValue(int file, String section, String key, boolean defaultValue) private boolean getInvertedBooleanValue(int file, String section, String key, boolean defaultValue) {
{ try {
try return ((IntSetting) mSettings.get(file).get(section).getSetting(key)).getValue() != 1;
{ } catch (NullPointerException ex) {
return ((IntSetting) mSettings.get(file).get(section).getSetting(key)).getValue() != 1; return defaultValue;
} }
catch (NullPointerException ex) }
{
return defaultValue;
}
}
} }

View File

@ -13,92 +13,91 @@ import java.util.HashMap;
* Abstraction for a screen showing a list of settings. Instances of * Abstraction for a screen showing a list of settings. Instances of
* this type of view will each display a layer of the setting hierarchy. * this type of view will each display a layer of the setting hierarchy.
*/ */
public interface SettingsFragmentView public interface SettingsFragmentView {
{ /**
/** * Called by the containing Activity to notify the Fragment that an
* Called by the containing Activity to notify the Fragment that an * asynchronous load operation completed.
* asynchronous load operation completed. *
* * @param settings The (possibly null) result of the ini load operation.
* @param settings The (possibly null) result of the ini load operation. */
*/ void onSettingsFileLoaded(ArrayList<HashMap<String, SettingSection>> settings);
void onSettingsFileLoaded(ArrayList<HashMap<String, SettingSection>> settings);
/** /**
* Pass a settings HashMap to the containing activity, so that it can * Pass a settings HashMap to the containing activity, so that it can
* share the HashMap with other SettingsFragments; useful so that rotations * share the HashMap with other SettingsFragments; useful so that rotations
* do not require an additional load operation. * do not require an additional load operation.
* *
* @param settings An ArrayList containing all the settings HashMaps. * @param settings An ArrayList containing all the settings HashMaps.
*/ */
void passSettingsToActivity(ArrayList<HashMap<String, SettingSection>> settings); void passSettingsToActivity(ArrayList<HashMap<String, SettingSection>> settings);
/** /**
* Pass an ArrayList to the View so that it can be displayed on screen. * Pass an ArrayList to the View so that it can be displayed on screen.
* *
* @param settingsList The result of converting the HashMap to an ArrayList * @param settingsList The result of converting the HashMap to an ArrayList
*/ */
void showSettingsList(ArrayList<SettingsItem> settingsList); void showSettingsList(ArrayList<SettingsItem> settingsList);
/** /**
* Called by the containing Activity when an asynchronous load operation fails. * Called by the containing Activity when an asynchronous load operation fails.
* Instructs the Fragment to load the settings screen with defaults selected. * Instructs the Fragment to load the settings screen with defaults selected.
*/ */
void loadDefaultSettings(); void loadDefaultSettings();
/** /**
* @return The Fragment's containing activity. * @return The Fragment's containing activity.
*/ */
FragmentActivity getActivity(); FragmentActivity getActivity();
/** /**
* Tell the Fragment to tell the containing Activity to show a new * Tell the Fragment to tell the containing Activity to show a new
* Fragment containing a submenu of settings. * Fragment containing a submenu of settings.
* *
* @param menuKey Identifier for the settings group that should be shown. * @param menuKey Identifier for the settings group that should be shown.
*/ */
void loadSubMenu(String menuKey); void loadSubMenu(String menuKey);
/** /**
* Tell the Fragment to tell the containing activity to display a toast message. * Tell the Fragment to tell the containing activity to display a toast message.
* *
* @param message Text to be shown in the Toast * @param message Text to be shown in the Toast
*/ */
void showToastMessage(String message); void showToastMessage(String message);
/** /**
* Have the fragment add a setting to the HashMap. * Have the fragment add a setting to the HashMap.
* *
* @param setting The (possibly previously missing) new setting. * @param setting The (possibly previously missing) new setting.
*/ */
void putSetting(Setting setting); void putSetting(Setting setting);
/** /**
* Have the fragment tell the containing Activity that a setting was modified. * Have the fragment tell the containing Activity that a setting was modified.
*/ */
void onSettingChanged(); void onSettingChanged();
/** /**
* Have the fragment tell the containing Activity that a GCPad's setting was modified. * Have the fragment tell the containing Activity that a GCPad's setting was modified.
* *
* @param key Identifier for the GCPad that was modified. * @param key Identifier for the GCPad that was modified.
* @param value New setting for the GCPad. * @param value New setting for the GCPad.
*/ */
void onGcPadSettingChanged(String key, int value); void onGcPadSettingChanged(String key, int value);
/** /**
* Have the fragment tell the containing Activity that a Wiimote's setting was modified. * Have the fragment tell the containing Activity that a Wiimote's setting was modified.
* *
* @param section Identifier for Wiimote that was modified; Wiimotes are identified by their section, * @param section Identifier for Wiimote that was modified; Wiimotes are identified by their section,
* not their key. * not their key.
* @param value New setting for the Wiimote. * @param value New setting for the Wiimote.
*/ */
void onWiimoteSettingChanged(String section, int value); void onWiimoteSettingChanged(String section, int value);
/** /**
* Have the fragment tell the containing Activity that an extension setting was modified. * Have the fragment tell the containing Activity that an extension setting was modified.
* *
* @param key Identifier for the extension that was modified. * @param key Identifier for the extension that was modified.
* @param value New setting for the extension. * @param value New setting for the extension.
*/ */
void onExtensionSettingChanged(String key, int value); void onExtensionSettingChanged(String key, int value);
} }

View File

@ -7,50 +7,41 @@ import android.widget.FrameLayout;
/** /**
* FrameLayout subclass with few Properties added to simplify animations. * FrameLayout subclass with few Properties added to simplify animations.
*/ */
public final class SettingsFrameLayout extends FrameLayout public final class SettingsFrameLayout extends FrameLayout {
{ private float mVisibleness = 1.0f;
private float mVisibleness = 1.0f;
public SettingsFrameLayout(Context context) public SettingsFrameLayout(Context context) {
{ super(context);
super(context); }
}
public SettingsFrameLayout(Context context, AttributeSet attrs) public SettingsFrameLayout(Context context, AttributeSet attrs) {
{ super(context, attrs);
super(context, attrs); }
}
public SettingsFrameLayout(Context context, AttributeSet attrs, int defStyleAttr) public SettingsFrameLayout(Context context, AttributeSet attrs, int defStyleAttr) {
{ super(context, attrs, defStyleAttr);
super(context, attrs, defStyleAttr); }
}
public SettingsFrameLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) public SettingsFrameLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
{ super(context, attrs, defStyleAttr, defStyleRes);
super(context, attrs, defStyleAttr, defStyleRes); }
}
public float getYFraction() public float getYFraction() {
{ return getY() / getHeight();
return getY() / getHeight(); }
}
public void setYFraction(float yFraction) public void setYFraction(float yFraction) {
{ final int height = getHeight();
final int height = getHeight(); setY((height > 0) ? (yFraction * height) : -9999);
setY((height > 0) ? (yFraction * height) : -9999); }
}
public float getVisibleness() public float getVisibleness() {
{ return mVisibleness;
return mVisibleness; }
}
public void setVisibleness(float visibleness) public void setVisibleness(float visibleness) {
{ setScaleX(visibleness);
setScaleX(visibleness); setScaleY(visibleness);
setScaleY(visibleness); setAlpha(visibleness);
setAlpha(visibleness); }
}
} }

View File

@ -9,48 +9,42 @@ import org.citra.citra_android.model.settings.view.CheckBoxSetting;
import org.citra.citra_android.model.settings.view.SettingsItem; import org.citra.citra_android.model.settings.view.SettingsItem;
import org.citra.citra_android.ui.settings.SettingsAdapter; import org.citra.citra_android.ui.settings.SettingsAdapter;
public final class CheckBoxSettingViewHolder extends SettingViewHolder public final class CheckBoxSettingViewHolder extends SettingViewHolder {
{ private CheckBoxSetting mItem;
private CheckBoxSetting mItem;
private TextView mTextSettingName; private TextView mTextSettingName;
private TextView mTextSettingDescription; private TextView mTextSettingDescription;
private CheckBox mCheckbox; private CheckBox mCheckbox;
public CheckBoxSettingViewHolder(View itemView, SettingsAdapter adapter) public CheckBoxSettingViewHolder(View itemView, SettingsAdapter adapter) {
{ super(itemView, adapter);
super(itemView, adapter); }
}
@Override @Override
protected void findViews(View root) protected void findViews(View root) {
{ mTextSettingName = root.findViewById(R.id.text_setting_name);
mTextSettingName = root.findViewById(R.id.text_setting_name); mTextSettingDescription = root.findViewById(R.id.text_setting_description);
mTextSettingDescription = root.findViewById(R.id.text_setting_description); mCheckbox = root.findViewById(R.id.checkbox);
mCheckbox = root.findViewById(R.id.checkbox); }
}
@Override @Override
public void bind(SettingsItem item) public void bind(SettingsItem item) {
{ mItem = (CheckBoxSetting) item;
mItem = (CheckBoxSetting) item;
mTextSettingName.setText(item.getNameId()); mTextSettingName.setText(item.getNameId());
if (item.getDescriptionId() > 0) if (item.getDescriptionId() > 0) {
{ mTextSettingDescription.setText(item.getDescriptionId());
mTextSettingDescription.setText(item.getDescriptionId()); }
}
mCheckbox.setChecked(mItem.isChecked()); mCheckbox.setChecked(mItem.isChecked());
} }
@Override @Override
public void onClick(View clicked) public void onClick(View clicked) {
{ mCheckbox.toggle();
mCheckbox.toggle();
getAdapter().onBooleanClick(mItem, getAdapterPosition(), mCheckbox.isChecked()); getAdapter().onBooleanClick(mItem, getAdapterPosition(), mCheckbox.isChecked());
} }
} }

View File

@ -9,21 +9,18 @@ import org.citra.citra_android.model.settings.view.SettingsItem;
import org.citra.citra_android.ui.settings.SettingsAdapter; import org.citra.citra_android.ui.settings.SettingsAdapter;
import org.citra.citra_android.utils.Log; import org.citra.citra_android.utils.Log;
public final class DateTimeViewHolder extends SettingViewHolder public final class DateTimeViewHolder extends SettingViewHolder {
{
private DateTimeSetting mItem; private DateTimeSetting mItem;
private TextView mTextSettingName; private TextView mTextSettingName;
private TextView mTextSettingDescription; private TextView mTextSettingDescription;
public DateTimeViewHolder(View itemView, SettingsAdapter adapter) public DateTimeViewHolder(View itemView, SettingsAdapter adapter) {
{
super(itemView, adapter); super(itemView, adapter);
} }
@Override @Override
protected void findViews(View root) protected void findViews(View root) {
{
mTextSettingName = root.findViewById(R.id.text_setting_name); mTextSettingName = root.findViewById(R.id.text_setting_name);
Log.error("test " + mTextSettingName); Log.error("test " + mTextSettingName);
mTextSettingDescription = root.findViewById(R.id.text_setting_description); mTextSettingDescription = root.findViewById(R.id.text_setting_description);
@ -31,19 +28,16 @@ public final class DateTimeViewHolder extends SettingViewHolder
} }
@Override @Override
public void bind(SettingsItem item) public void bind(SettingsItem item) {
{
mItem = (DateTimeSetting) item; mItem = (DateTimeSetting) item;
mTextSettingName.setText(item.getNameId()); mTextSettingName.setText(item.getNameId());
if (item.getDescriptionId() > 0) if (item.getDescriptionId() > 0) {
{
mTextSettingDescription.setText(item.getDescriptionId()); mTextSettingDescription.setText(item.getDescriptionId());
} }
} }
@Override @Override
public void onClick(View clicked) public void onClick(View clicked) {
{
getAdapter().onDateTimeClick(mItem); getAdapter().onDateTimeClick(mItem);
} }
} }

View File

@ -7,31 +7,26 @@ import org.citra.citra_android.R;
import org.citra.citra_android.model.settings.view.SettingsItem; import org.citra.citra_android.model.settings.view.SettingsItem;
import org.citra.citra_android.ui.settings.SettingsAdapter; import org.citra.citra_android.ui.settings.SettingsAdapter;
public final class HeaderViewHolder extends SettingViewHolder public final class HeaderViewHolder extends SettingViewHolder {
{ private TextView mHeaderName;
private TextView mHeaderName;
public HeaderViewHolder(View itemView, SettingsAdapter adapter) public HeaderViewHolder(View itemView, SettingsAdapter adapter) {
{ super(itemView, adapter);
super(itemView, adapter); itemView.setOnClickListener(null);
itemView.setOnClickListener(null); }
}
@Override @Override
protected void findViews(View root) protected void findViews(View root) {
{ mHeaderName = root.findViewById(R.id.text_header_name);
mHeaderName = root.findViewById(R.id.text_header_name); }
}
@Override @Override
public void bind(SettingsItem item) public void bind(SettingsItem item) {
{ mHeaderName.setText(item.getNameId());
mHeaderName.setText(item.getNameId()); }
}
@Override @Override
public void onClick(View clicked) public void onClick(View clicked) {
{ // no-op
// no-op }
}
} }

View File

@ -11,43 +11,38 @@ import org.citra.citra_android.model.settings.view.InputBindingSetting;
import org.citra.citra_android.model.settings.view.SettingsItem; import org.citra.citra_android.model.settings.view.SettingsItem;
import org.citra.citra_android.ui.settings.SettingsAdapter; import org.citra.citra_android.ui.settings.SettingsAdapter;
public final class InputBindingSettingViewHolder extends SettingViewHolder public final class InputBindingSettingViewHolder extends SettingViewHolder {
{ private InputBindingSetting mItem;
private InputBindingSetting mItem;
private TextView mTextSettingName; private TextView mTextSettingName;
private TextView mTextSettingDescription; private TextView mTextSettingDescription;
private Context mContext; private Context mContext;
public InputBindingSettingViewHolder(View itemView, SettingsAdapter adapter, Context context) public InputBindingSettingViewHolder(View itemView, SettingsAdapter adapter, Context context) {
{ super(itemView, adapter);
super(itemView, adapter);
mContext = context; mContext = context;
} }
@Override @Override
protected void findViews(View root) protected void findViews(View root) {
{ mTextSettingName = root.findViewById(R.id.text_setting_name);
mTextSettingName = root.findViewById(R.id.text_setting_name); mTextSettingDescription = root.findViewById(R.id.text_setting_description);
mTextSettingDescription = root.findViewById(R.id.text_setting_description); }
}
@Override @Override
public void bind(SettingsItem item) public void bind(SettingsItem item) {
{ SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(mContext);
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(mContext);
mItem = (InputBindingSetting) item; mItem = (InputBindingSetting) item;
mTextSettingName.setText(item.getNameId()); mTextSettingName.setText(item.getNameId());
mTextSettingDescription.setText(sharedPreferences.getString(mItem.getKey(), "")); mTextSettingDescription.setText(sharedPreferences.getString(mItem.getKey(), ""));
} }
@Override @Override
public void onClick(View clicked) public void onClick(View clicked) {
{ getAdapter().onInputBindingClick(mItem, getAdapterPosition());
getAdapter().onInputBindingClick(mItem, getAdapterPosition()); }
}
} }

View File

@ -6,46 +6,43 @@ import android.view.View;
import org.citra.citra_android.model.settings.view.SettingsItem; import org.citra.citra_android.model.settings.view.SettingsItem;
import org.citra.citra_android.ui.settings.SettingsAdapter; import org.citra.citra_android.ui.settings.SettingsAdapter;
public abstract class SettingViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener public abstract class SettingViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
{ private SettingsAdapter mAdapter;
private SettingsAdapter mAdapter;
public SettingViewHolder(View itemView, SettingsAdapter adapter) public SettingViewHolder(View itemView, SettingsAdapter adapter) {
{ super(itemView);
super(itemView);
mAdapter = adapter; mAdapter = adapter;
itemView.setOnClickListener(this); itemView.setOnClickListener(this);
findViews(itemView); findViews(itemView);
} }
protected SettingsAdapter getAdapter() protected SettingsAdapter getAdapter() {
{ return mAdapter;
return mAdapter; }
}
/** /**
* Gets handles to all this ViewHolder's child views using their XML-defined identifiers. * Gets handles to all this ViewHolder's child views using their XML-defined identifiers.
* *
* @param root The newly inflated top-level view. * @param root The newly inflated top-level view.
*/ */
protected abstract void findViews(View root); protected abstract void findViews(View root);
/** /**
* Called by the adapter to set this ViewHolder's child views to display the list item * Called by the adapter to set this ViewHolder's child views to display the list item
* it must now represent. * it must now represent.
* *
* @param item The list item that should be represented by this ViewHolder. * @param item The list item that should be represented by this ViewHolder.
*/ */
public abstract void bind(SettingsItem item); public abstract void bind(SettingsItem item);
/** /**
* Called when this ViewHolder's view is clicked on. Implementations should usually pass * Called when this ViewHolder's view is clicked on. Implementations should usually pass
* this event up to the adapter. * this event up to the adapter.
* *
* @param clicked The view that was clicked on. * @param clicked The view that was clicked on.
*/ */
public abstract void onClick(View clicked); public abstract void onClick(View clicked);
} }

View File

@ -8,41 +8,35 @@ import org.citra.citra_android.model.settings.view.SettingsItem;
import org.citra.citra_android.model.settings.view.SingleChoiceSetting; import org.citra.citra_android.model.settings.view.SingleChoiceSetting;
import org.citra.citra_android.ui.settings.SettingsAdapter; import org.citra.citra_android.ui.settings.SettingsAdapter;
public final class SingleChoiceViewHolder extends SettingViewHolder public final class SingleChoiceViewHolder extends SettingViewHolder {
{ private SingleChoiceSetting mItem;
private SingleChoiceSetting mItem;
private TextView mTextSettingName; private TextView mTextSettingName;
private TextView mTextSettingDescription; private TextView mTextSettingDescription;
public SingleChoiceViewHolder(View itemView, SettingsAdapter adapter) public SingleChoiceViewHolder(View itemView, SettingsAdapter adapter) {
{ super(itemView, adapter);
super(itemView, adapter); }
}
@Override @Override
protected void findViews(View root) protected void findViews(View root) {
{ mTextSettingName = root.findViewById(R.id.text_setting_name);
mTextSettingName = root.findViewById(R.id.text_setting_name); mTextSettingDescription = root.findViewById(R.id.text_setting_description);
mTextSettingDescription = root.findViewById(R.id.text_setting_description); }
}
@Override @Override
public void bind(SettingsItem item) public void bind(SettingsItem item) {
{ mItem = (SingleChoiceSetting) item;
mItem = (SingleChoiceSetting) item;
mTextSettingName.setText(item.getNameId()); mTextSettingName.setText(item.getNameId());
if (item.getDescriptionId() > 0) if (item.getDescriptionId() > 0) {
{ mTextSettingDescription.setText(item.getDescriptionId());
mTextSettingDescription.setText(item.getDescriptionId()); }
} }
}
@Override @Override
public void onClick(View clicked) public void onClick(View clicked) {
{ getAdapter().onSingleChoiceClick(mItem);
getAdapter().onSingleChoiceClick(mItem); }
}
} }

View File

@ -8,42 +8,36 @@ import org.citra.citra_android.model.settings.view.SettingsItem;
import org.citra.citra_android.model.settings.view.SliderSetting; import org.citra.citra_android.model.settings.view.SliderSetting;
import org.citra.citra_android.ui.settings.SettingsAdapter; import org.citra.citra_android.ui.settings.SettingsAdapter;
public final class SliderViewHolder extends SettingViewHolder public final class SliderViewHolder extends SettingViewHolder {
{ private SliderSetting mItem;
private SliderSetting mItem;
private TextView mTextSettingName; private TextView mTextSettingName;
private TextView mTextSettingDescription; private TextView mTextSettingDescription;
public SliderViewHolder(View itemView, SettingsAdapter adapter) public SliderViewHolder(View itemView, SettingsAdapter adapter) {
{ super(itemView, adapter);
super(itemView, adapter); }
}
@Override @Override
protected void findViews(View root) protected void findViews(View root) {
{ mTextSettingName = root.findViewById(R.id.text_setting_name);
mTextSettingName = root.findViewById(R.id.text_setting_name); mTextSettingDescription = root.findViewById(R.id.text_setting_description);
mTextSettingDescription = root.findViewById(R.id.text_setting_description); }
}
@Override @Override
public void bind(SettingsItem item) public void bind(SettingsItem item) {
{ mItem = (SliderSetting) item;
mItem = (SliderSetting) item;
mTextSettingName.setText(item.getNameId()); mTextSettingName.setText(item.getNameId());
if (item.getDescriptionId() > 0) if (item.getDescriptionId() > 0) {
{ mTextSettingDescription.setText(item.getDescriptionId());
mTextSettingDescription.setText(item.getDescriptionId()); }
} }
}
@Override @Override
public void onClick(View clicked) public void onClick(View clicked) {
{ getAdapter().onSliderClick(mItem);
getAdapter().onSliderClick(mItem); }
}
} }

View File

@ -8,41 +8,35 @@ import org.citra.citra_android.model.settings.view.SettingsItem;
import org.citra.citra_android.model.settings.view.SubmenuSetting; import org.citra.citra_android.model.settings.view.SubmenuSetting;
import org.citra.citra_android.ui.settings.SettingsAdapter; import org.citra.citra_android.ui.settings.SettingsAdapter;
public final class SubmenuViewHolder extends SettingViewHolder public final class SubmenuViewHolder extends SettingViewHolder {
{ private SubmenuSetting mItem;
private SubmenuSetting mItem;
private TextView mTextSettingName; private TextView mTextSettingName;
private TextView mTextSettingDescription; private TextView mTextSettingDescription;
public SubmenuViewHolder(View itemView, SettingsAdapter adapter) public SubmenuViewHolder(View itemView, SettingsAdapter adapter) {
{ super(itemView, adapter);
super(itemView, adapter); }
}
@Override @Override
protected void findViews(View root) protected void findViews(View root) {
{ mTextSettingName = root.findViewById(R.id.text_setting_name);
mTextSettingName = root.findViewById(R.id.text_setting_name); mTextSettingDescription = root.findViewById(R.id.text_setting_description);
mTextSettingDescription = root.findViewById(R.id.text_setting_description); }
}
@Override @Override
public void bind(SettingsItem item) public void bind(SettingsItem item) {
{ mItem = (SubmenuSetting) item;
mItem = (SubmenuSetting) item;
mTextSettingName.setText(item.getNameId()); mTextSettingName.setText(item.getNameId());
if (item.getDescriptionId() > 0) if (item.getDescriptionId() > 0) {
{ mTextSettingDescription.setText(item.getDescriptionId());
mTextSettingDescription.setText(item.getDescriptionId()); }
} }
}
@Override @Override
public void onClick(View clicked) public void onClick(View clicked) {
{ getAdapter().onSubmenuClick(mItem);
getAdapter().onSubmenuClick(mItem); }
}
} }

View File

@ -8,37 +8,31 @@ import android.net.Uri;
import org.citra.citra_android.model.GameDatabase; import org.citra.citra_android.model.GameDatabase;
import org.citra.citra_android.model.GameProvider; import org.citra.citra_android.model.GameProvider;
public class AddDirectoryHelper public class AddDirectoryHelper {
{ private Context mContext;
private Context mContext;
public interface AddDirectoryListener public AddDirectoryHelper(Context context) {
{ this.mContext = context;
void onDirectoryAdded(); }
}
public AddDirectoryHelper(Context context) public void addDirectory(String dir, AddDirectoryListener addDirectoryListener) {
{ AsyncQueryHandler handler = new AsyncQueryHandler(mContext.getContentResolver()) {
this.mContext = context; @Override
} protected void onInsertComplete(int token, Object cookie, Uri uri) {
addDirectoryListener.onDirectoryAdded();
}
};
public void addDirectory(String dir, AddDirectoryListener addDirectoryListener) ContentValues file = new ContentValues();
{ file.put(GameDatabase.KEY_FOLDER_PATH, dir);
AsyncQueryHandler handler = new AsyncQueryHandler(mContext.getContentResolver())
{
@Override
protected void onInsertComplete(int token, Object cookie, Uri uri)
{
addDirectoryListener.onDirectoryAdded();
}
};
ContentValues file = new ContentValues(); handler.startInsert(0, // We don't need to identify this call to the handler
file.put(GameDatabase.KEY_FOLDER_PATH, dir); null, // We don't need to pass additional data to the handler
GameProvider.URI_FOLDER, // Tell the GameProvider we are adding a folder
file);
}
handler.startInsert(0, // We don't need to identify this call to the handler public interface AddDirectoryListener {
null, // We don't need to pass additional data to the handler void onDirectoryAdded();
GameProvider.URI_FOLDER, // Tell the GameProvider we are adding a folder }
file);
}
} }

View File

@ -3,27 +3,23 @@ package org.citra.citra_android.utils;
import android.view.View; import android.view.View;
import android.view.ViewPropertyAnimator; import android.view.ViewPropertyAnimator;
public final class Animations public final class Animations {
{ private Animations() {
private Animations() }
{
}
public static ViewPropertyAnimator fadeViewIn(View view) public static ViewPropertyAnimator fadeViewIn(View view) {
{ view.setVisibility(View.VISIBLE);
view.setVisibility(View.VISIBLE);
return view.animate() return view.animate()
.withLayer() .withLayer()
.setDuration(100) .setDuration(100)
.alpha(1.0f); .alpha(1.0f);
} }
public static ViewPropertyAnimator fadeViewOut(View view) public static ViewPropertyAnimator fadeViewOut(View view) {
{ return view.animate()
return view.animate() .withLayer()
.withLayer() .setDuration(300)
.setDuration(300) .alpha(0.0f);
.alpha(0.0f); }
}
} }

View File

@ -7,77 +7,61 @@ import android.view.MotionEvent;
/** /**
* Some controllers have incorrect mappings. This class has special-case fixes for them. * Some controllers have incorrect mappings. This class has special-case fixes for them.
*/ */
public class ControllerMappingHelper public class ControllerMappingHelper {
{ /**
/** * Some controllers report extra button presses that can be ignored.
* Some controllers report extra button presses that can be ignored. */
*/ public boolean shouldKeyBeIgnored(InputDevice inputDevice, int keyCode) {
public boolean shouldKeyBeIgnored(InputDevice inputDevice, int keyCode) if (isDualShock4(inputDevice)) {
{ // The two analog triggers generate analog motion events as well as a keycode.
if (isDualShock4(inputDevice)) // We always prefer to use the analog values, so throw away the button press
{ // Even though the triggers are L/R2, without mappings they generate L/R1 events.
// The two analog triggers generate analog motion events as well as a keycode. return keyCode == KeyEvent.KEYCODE_BUTTON_L1 || keyCode == KeyEvent.KEYCODE_BUTTON_R1;
// We always prefer to use the analog values, so throw away the button press }
// Even though the triggers are L/R2, without mappings they generate L/R1 events. return false;
return keyCode == KeyEvent.KEYCODE_BUTTON_L1 || keyCode == KeyEvent.KEYCODE_BUTTON_R1;
} }
return false;
}
/** /**
* Scale an axis to be zero-centered with a proper range. * Scale an axis to be zero-centered with a proper range.
*/ */
public float scaleAxis(InputDevice inputDevice, int axis, float value) public float scaleAxis(InputDevice inputDevice, int axis, float value) {
{ if (isDualShock4(inputDevice)) {
if (isDualShock4(inputDevice)) // Android doesn't have correct mappings for this controller's triggers. It reports them
{ // as RX & RY, centered at -1.0, and with a range of [-1.0, 1.0]
// Android doesn't have correct mappings for this controller's triggers. It reports them // Scale them to properly zero-centered with a range of [0.0, 1.0].
// as RX & RY, centered at -1.0, and with a range of [-1.0, 1.0] if (axis == MotionEvent.AXIS_RX || axis == MotionEvent.AXIS_RY) {
// Scale them to properly zero-centered with a range of [0.0, 1.0]. return (value + 1) / 2.0f;
if (axis == MotionEvent.AXIS_RX || axis == MotionEvent.AXIS_RY) }
{ } else if (isXboxOneWireless(inputDevice)) {
return (value + 1) / 2.0f; // Same as the DualShock 4, the mappings are missing.
} if (axis == MotionEvent.AXIS_Z || axis == MotionEvent.AXIS_RZ) {
return (value + 1) / 2.0f;
}
if (axis == MotionEvent.AXIS_GENERIC_1) {
// This axis is stuck at ~.5. Ignore it.
return 0.0f;
}
} else if (isMogaPro2Hid(inputDevice)) {
// This controller has a broken axis that reports a constant value. Ignore it.
if (axis == MotionEvent.AXIS_GENERIC_1) {
return 0.0f;
}
}
return value;
} }
else if (isXboxOneWireless(inputDevice))
{ private boolean isDualShock4(InputDevice inputDevice) {
// Same as the DualShock 4, the mappings are missing. // Sony DualShock 4 controller
if (axis == MotionEvent.AXIS_Z || axis == MotionEvent.AXIS_RZ) return inputDevice.getVendorId() == 0x54c && inputDevice.getProductId() == 0x9cc;
{
return (value + 1) / 2.0f;
}
if (axis == MotionEvent.AXIS_GENERIC_1)
{
// This axis is stuck at ~.5. Ignore it.
return 0.0f;
}
} }
else if (isMogaPro2Hid(inputDevice))
{ private boolean isXboxOneWireless(InputDevice inputDevice) {
// This controller has a broken axis that reports a constant value. Ignore it. // Microsoft Xbox One controller
if (axis == MotionEvent.AXIS_GENERIC_1) return inputDevice.getVendorId() == 0x45e && inputDevice.getProductId() == 0x2e0;
{
return 0.0f;
}
} }
return value;
}
private boolean isDualShock4(InputDevice inputDevice) private boolean isMogaPro2Hid(InputDevice inputDevice) {
{ // Moga Pro 2 HID
// Sony DualShock 4 controller return inputDevice.getVendorId() == 0x20d6 && inputDevice.getProductId() == 0x6271;
return inputDevice.getVendorId() == 0x54c && inputDevice.getProductId() == 0x9cc; }
}
private boolean isXboxOneWireless(InputDevice inputDevice)
{
// Microsoft Xbox One controller
return inputDevice.getVendorId() == 0x45e && inputDevice.getProductId() == 0x2e0;
}
private boolean isMogaPro2Hid(InputDevice inputDevice)
{
// Moga Pro 2 HID
return inputDevice.getVendorId() == 0x20d6 && inputDevice.getProductId() == 0x6271;
}
} }

View File

@ -9,20 +9,17 @@ import org.citra.citra_android.services.DirectoryInitializationService.Directory
import rx.functions.Action1; import rx.functions.Action1;
public class DirectoryStateReceiver extends BroadcastReceiver public class DirectoryStateReceiver extends BroadcastReceiver {
{ Action1<DirectoryInitializationState> callback;
Action1<DirectoryInitializationState> callback;
public DirectoryStateReceiver(Action1<DirectoryInitializationState> callback) public DirectoryStateReceiver(Action1<DirectoryInitializationState> callback) {
{ this.callback = callback;
this.callback = callback; }
}
@Override @Override
public void onReceive(Context context, Intent intent) public void onReceive(Context context, Intent intent) {
{ DirectoryInitializationState state = (DirectoryInitializationState) intent
DirectoryInitializationState state = (DirectoryInitializationState) intent .getSerializableExtra(DirectoryInitializationService.EXTRA_STATE);
.getSerializableExtra(DirectoryInitializationService.EXTRA_STATE); callback.call(state);
callback.call(state); }
}
} }

View File

@ -22,368 +22,332 @@ import javax.microedition.khronos.opengles.GL10;
* EGL initialization out of the way if all that is * EGL initialization out of the way if all that is
* wanted is to query the underlying GL API for information. * wanted is to query the underlying GL API for information.
*/ */
public final class EGLHelper public final class EGLHelper {
{ // Renderable type bitmasks
private final EGL10 mEGL; public static final int EGL_OPENGL_ES_BIT = 0x0001;
private final EGLDisplay mDisplay; public static final int EGL_OPENGL_ES2_BIT = 0x0004;
private EGLConfig[] mEGLConfigs; public static final int EGL_OPENGL_BIT = 0x0008;
private EGLContext mEGLContext; public static final int EGL_OPENGL_ES3_BIT_KHR = 0x0040;
private EGLSurface mEGLSurface; // API types
private GL10 mGL; public static final int EGL_OPENGL_ES_API = 0x30A0;
public static final int EGL_OPENGL_API = 0x30A2;
private final EGL10 mEGL;
private final EGLDisplay mDisplay;
private EGLConfig[] mEGLConfigs;
private EGLContext mEGLContext;
private EGLSurface mEGLSurface;
private GL10 mGL;
// GL support flags
private boolean supportGL;
private boolean supportGLES2;
private boolean supportGLES3;
// GL support flags /**
private boolean supportGL; * Constructor
private boolean supportGLES2; * <p>
private boolean supportGLES3; * Initializes the underlying {@link EGLSurface} with a width and height of 1.
* This is useful if all you need to use this class for is to query information
// Renderable type bitmasks * from specific API contexts.
public static final int EGL_OPENGL_ES_BIT = 0x0001; *
public static final int EGL_OPENGL_ES2_BIT = 0x0004; * @param renderableType Bitmask indicating which types of client API contexts
public static final int EGL_OPENGL_BIT = 0x0008; * the framebuffer config must support.
public static final int EGL_OPENGL_ES3_BIT_KHR = 0x0040; */
public EGLHelper(int renderableType) {
// API types this(1, 1, renderableType);
public static final int EGL_OPENGL_ES_API = 0x30A0;
public static final int EGL_OPENGL_API = 0x30A2;
/**
* Constructor
* <p>
* Initializes the underlying {@link EGLSurface} with a width and height of 1.
* This is useful if all you need to use this class for is to query information
* from specific API contexts.
*
* @param renderableType Bitmask indicating which types of client API contexts
* the framebuffer config must support.
*/
public EGLHelper(int renderableType)
{
this(1, 1, renderableType);
}
/**
* Constructor
*
* @param width Width of the underlying {@link EGLSurface}.
* @param height Height of the underlying {@link EGLSurface}.
* @param renderableType Bitmask indicating which types of client API contexts
* the framebuffer config must support.
*/
public EGLHelper(int width, int height, int renderableType)
{
// Initialize handle to an EGL display.
mEGL = (EGL10) EGLContext.getEGL();
mDisplay = mEGL.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
// If a display is present, initialize EGL.
if (mDisplay != EGL10.EGL_NO_DISPLAY)
{
int[] version = new int[2];
if (mEGL.eglInitialize(mDisplay, version))
{
// Detect supported GL APIs, initialize configs, etc.
detect();
// Create context and surface
create(width, height, renderableType);
}
else
{
Log.error("[EGLHelper] Error initializing EGL.");
}
}
else
{
Log.error("[EGLHelper] Error initializing EGL display.");
}
}
/**
* Releases all resources associated with this helper.
* <p>
* This should be called whenever this helper is no longer needed.
*/
public void closeHelper()
{
mEGL.eglTerminate(mDisplay);
}
/**
* Gets information through EGL.<br/>
* <p>
* Index 0: Vendor <br/>
* Index 1: Version <br/>
* Index 2: Renderer <br/>
* Index 3: Extensions <br/>
*
* @return information retrieved through EGL.
*/
public String[] getEGLInfo()
{
return new String[]{
mGL.glGetString(GL10.GL_VENDOR),
mGL.glGetString(GL10.GL_VERSION),
mGL.glGetString(GL10.GL_RENDERER),
mGL.glGetString(GL10.GL_EXTENSIONS),
};
}
/**
* Whether or not this device supports OpenGL.
*
* @return true if this device supports OpenGL; false otherwise.
*/
public boolean supportsOpenGL()
{
return supportGL;
}
/**
* Whether or not this device supports OpenGL ES 2.
* <br/>
* Note that if this returns true, then OpenGL ES 1 is also supported.
*
* @return true if this device supports OpenGL ES 2; false otherwise.
*/
public boolean supportsGLES2()
{
return supportGLES2;
}
/**
* Whether or not this device supports OpenGL ES 3.
* <br/>
* Note that if this returns true, then OpenGL ES 1 and 2 are also supported.
*
* @return true if this device supports OpenGL ES 3; false otherwise.
*/
public boolean supportsGLES3()
{
return supportGLES3;
}
/**
* Gets the underlying {@link EGL10} instance.
*
* @return the underlying {@link EGL10} instance.
*/
public EGL10 getEGL()
{
return mEGL;
}
/**
* Gets the underlying {@link GL10} instance.
*
* @return the underlying {@link GL10} instance.
*/
public GL10 getGL()
{
return mGL;
}
/**
* Gets the underlying {@link EGLDisplay}.
*
* @return the underlying {@link EGLDisplay}
*/
public EGLDisplay getDisplay()
{
return mDisplay;
}
/**
* Gets all supported framebuffer configurations for this device.
*
* @return all supported framebuffer configurations for this device.
*/
public EGLConfig[] getConfigs()
{
return mEGLConfigs;
}
/**
* Gets the underlying {@link EGLContext}.
*
* @return the underlying {@link EGLContext}.
*/
public EGLContext getContext()
{
return mEGLContext;
}
/**
* Gets the underlying {@link EGLSurface}.
*
* @return the underlying {@link EGLSurface}.
*/
public EGLSurface getSurface()
{
return mEGLSurface;
}
// Detects the specific kind of GL modes that are supported
private boolean detect()
{
// Get total number of configs available.
int[] numConfigs = new int[1];
if (!mEGL.eglGetConfigs(mDisplay, null, 0, numConfigs))
{
Log.error("[EGLHelper] Error retrieving number of EGL configs available.");
return false;
} }
// Now get all the configurations /**
mEGLConfigs = new EGLConfig[numConfigs[0]]; * Constructor
if (!mEGL.eglGetConfigs(mDisplay, mEGLConfigs, mEGLConfigs.length, numConfigs)) *
{ * @param width Width of the underlying {@link EGLSurface}.
Log.error("[EGLHelper] Error retrieving all EGL configs."); * @param height Height of the underlying {@link EGLSurface}.
return false; * @param renderableType Bitmask indicating which types of client API contexts
* the framebuffer config must support.
*/
public EGLHelper(int width, int height, int renderableType) {
// Initialize handle to an EGL display.
mEGL = (EGL10) EGLContext.getEGL();
mDisplay = mEGL.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
// If a display is present, initialize EGL.
if (mDisplay != EGL10.EGL_NO_DISPLAY) {
int[] version = new int[2];
if (mEGL.eglInitialize(mDisplay, version)) {
// Detect supported GL APIs, initialize configs, etc.
detect();
// Create context and surface
create(width, height, renderableType);
} else {
Log.error("[EGLHelper] Error initializing EGL.");
}
} else {
Log.error("[EGLHelper] Error initializing EGL display.");
}
} }
for (EGLConfig mEGLConfig : mEGLConfigs) /**
{ * Releases all resources associated with this helper.
int[] attribVal = new int[1]; * <p>
boolean ret = * This should be called whenever this helper is no longer needed.
mEGL.eglGetConfigAttrib(mDisplay, mEGLConfig, EGL10.EGL_RENDERABLE_TYPE, attribVal); */
if (ret) public void closeHelper() {
{ mEGL.eglTerminate(mDisplay);
if ((attribVal[0] & EGL_OPENGL_BIT) != 0)
supportGL = true;
if ((attribVal[0] & EGL_OPENGL_ES2_BIT) != 0)
supportGLES2 = true;
if ((attribVal[0] & EGL_OPENGL_ES3_BIT_KHR) != 0)
supportGLES3 = true;
}
} }
return true; /**
} * Gets information through EGL.<br/>
* <p>
// Creates the context and surface. * Index 0: Vendor <br/>
private void create(int width, int height, int renderableType) * Index 1: Version <br/>
{ * Index 2: Renderer <br/>
int[] attribs = { * Index 3: Extensions <br/>
EGL10.EGL_WIDTH, width, *
EGL10.EGL_HEIGHT, height, * @return information retrieved through EGL.
EGL10.EGL_NONE */
}; public String[] getEGLInfo() {
return new String[]{
// Initially we just assume GLES2 will be the default context. mGL.glGetString(GL10.GL_VENDOR),
int EGL_CONTEXT_CLIENT_VERSION = 0x3098; mGL.glGetString(GL10.GL_VERSION),
int[] ctx_attribs = { mGL.glGetString(GL10.GL_RENDERER),
EGL_CONTEXT_CLIENT_VERSION, 2, mGL.glGetString(GL10.GL_EXTENSIONS),
EGL10.EGL_NONE };
};
// Determine the type of context that will be created
// and change the attribute arrays accordingly.
switch (renderableType)
{
case EGL_OPENGL_ES_BIT:
ctx_attribs[1] = 1;
break;
case EGL_OPENGL_BIT:
ctx_attribs[0] = EGL10.EGL_NONE;
break;
case EGL_OPENGL_ES3_BIT_KHR:
ctx_attribs[1] = 3;
break;
case EGL_OPENGL_ES2_BIT:
default: // Fall-back to GLES 2.
ctx_attribs[1] = 2;
break;
} }
if (renderableType == EGL_OPENGL_BIT)
NativeLibrary.eglBindAPI(EGL_OPENGL_API);
else
NativeLibrary.eglBindAPI(EGL_OPENGL_ES_API);
mEGLContext = /**
mEGL.eglCreateContext(mDisplay, mEGLConfigs[0], EGL10.EGL_NO_CONTEXT, ctx_attribs); * Whether or not this device supports OpenGL.
mEGLSurface = mEGL.eglCreatePbufferSurface(mDisplay, mEGLConfigs[0], attribs); *
mEGL.eglMakeCurrent(mDisplay, mEGLSurface, mEGLSurface, mEGLContext); * @return true if this device supports OpenGL; false otherwise.
mGL = (GL10) mEGLContext.getGL(); */
} public boolean supportsOpenGL() {
return supportGL;
}
/** /**
* Simplified call to {@link GL10#glGetString(int)} * Whether or not this device supports OpenGL ES 2.
* <p> * <br/>
* Accepts the following constants: * Note that if this returns true, then OpenGL ES 1 is also supported.
* <ul> *
* <li>GL_VENDOR - Company responsible for the GL implementation.</li> * @return true if this device supports OpenGL ES 2; false otherwise.
* <li>GL_VERSION - Version or release number.</li> */
* <li>GL_RENDERER - Name of the renderer</li> public boolean supportsGLES2() {
* <li>GL_SHADING_LANGUAGE_VERSION - Version or release number of the shading language </li> return supportGLES2;
* </ul> }
*
* @param glEnum A symbolic constant within {@link GL10}.
* @return the string information represented by {@code glEnum}.
*/
public String glGetString(int glEnum)
{
return mGL.glGetString(glEnum);
}
/** /**
* Simplified call to {@link GLES30#glGetStringi(int, int)} * Whether or not this device supports OpenGL ES 3.
* <p> * <br/>
* Accepts the following constants: * Note that if this returns true, then OpenGL ES 1 and 2 are also supported.
* <ul> *
* <li>GL_VENDOR - Company responsible for the GL implementation.</li> * @return true if this device supports OpenGL ES 3; false otherwise.
* <li>GL_VERSION - Version or release number.</li> */
* <li>GL_RENDERER - Name of the renderer</li> public boolean supportsGLES3() {
* <li>GL_SHADING_LANGUAGE_VERSION - Version or release number of the shading language </li> return supportGLES3;
* <li>GL_EXTENSIONS - Extension string supported by the implementation at {@code index}.</li> }
* </ul>
*
* @param glEnum A symbolic GL constant
* @param index The index of the string to return.
* @return the string information represented by {@code glEnum} and {@code index}.
*/
public String glGetStringi(int glEnum, int index)
{
return GLES30.glGetStringi(glEnum, index);
}
public boolean SupportsExtension(String extension) /**
{ * Gets the underlying {@link EGL10} instance.
int[] num_ext = new int[1]; *
GLES30.glGetIntegerv(GLES30.GL_NUM_EXTENSIONS, num_ext, 0); * @return the underlying {@link EGL10} instance.
*/
public EGL10 getEGL() {
return mEGL;
}
/**
* Gets the underlying {@link GL10} instance.
*
* @return the underlying {@link GL10} instance.
*/
public GL10 getGL() {
return mGL;
}
/**
* Gets the underlying {@link EGLDisplay}.
*
* @return the underlying {@link EGLDisplay}
*/
public EGLDisplay getDisplay() {
return mDisplay;
}
/**
* Gets all supported framebuffer configurations for this device.
*
* @return all supported framebuffer configurations for this device.
*/
public EGLConfig[] getConfigs() {
return mEGLConfigs;
}
/**
* Gets the underlying {@link EGLContext}.
*
* @return the underlying {@link EGLContext}.
*/
public EGLContext getContext() {
return mEGLContext;
}
/**
* Gets the underlying {@link EGLSurface}.
*
* @return the underlying {@link EGLSurface}.
*/
public EGLSurface getSurface() {
return mEGLSurface;
}
// Detects the specific kind of GL modes that are supported
private boolean detect() {
// Get total number of configs available.
int[] numConfigs = new int[1];
if (!mEGL.eglGetConfigs(mDisplay, null, 0, numConfigs)) {
Log.error("[EGLHelper] Error retrieving number of EGL configs available.");
return false;
}
// Now get all the configurations
mEGLConfigs = new EGLConfig[numConfigs[0]];
if (!mEGL.eglGetConfigs(mDisplay, mEGLConfigs, mEGLConfigs.length, numConfigs)) {
Log.error("[EGLHelper] Error retrieving all EGL configs.");
return false;
}
for (EGLConfig mEGLConfig : mEGLConfigs) {
int[] attribVal = new int[1];
boolean ret =
mEGL.eglGetConfigAttrib(mDisplay, mEGLConfig, EGL10.EGL_RENDERABLE_TYPE, attribVal);
if (ret) {
if ((attribVal[0] & EGL_OPENGL_BIT) != 0)
supportGL = true;
if ((attribVal[0] & EGL_OPENGL_ES2_BIT) != 0)
supportGLES2 = true;
if ((attribVal[0] & EGL_OPENGL_ES3_BIT_KHR) != 0)
supportGLES3 = true;
}
}
for (int i = 0; i < num_ext[0]; ++i)
{
String ext = GLES30.glGetStringi(GLES30.GL_EXTENSIONS, i);
if (ext.equals(extension))
return true; return true;
} }
return false;
}
public int GetVersion() // Creates the context and surface.
{ private void create(int width, int height, int renderableType) {
int[] major = new int[1]; int[] attribs = {
int[] minor = new int[1]; EGL10.EGL_WIDTH, width,
GLES30.glGetIntegerv(GLES30.GL_MAJOR_VERSION, major, 0); EGL10.EGL_HEIGHT, height,
GLES30.glGetIntegerv(GLES30.GL_MINOR_VERSION, minor, 0); EGL10.EGL_NONE
return major[0] * 100 + minor[0] * 10; };
}
/** // Initially we just assume GLES2 will be the default context.
* Simplified call to {@link GL10#glGetIntegerv(int, int[], int) int EGL_CONTEXT_CLIENT_VERSION = 0x3098;
* int[] ctx_attribs = {
* @param glEnum A symbolic GL constant. EGL_CONTEXT_CLIENT_VERSION, 2,
* @return the integer information represented by {@code glEnum}. EGL10.EGL_NONE
*/ };
public int glGetInteger(int glEnum)
{ // Determine the type of context that will be created
int[] val = new int[1]; // and change the attribute arrays accordingly.
mGL.glGetIntegerv(glEnum, val, 0); switch (renderableType) {
return val[0]; case EGL_OPENGL_ES_BIT:
} ctx_attribs[1] = 1;
break;
case EGL_OPENGL_BIT:
ctx_attribs[0] = EGL10.EGL_NONE;
break;
case EGL_OPENGL_ES3_BIT_KHR:
ctx_attribs[1] = 3;
break;
case EGL_OPENGL_ES2_BIT:
default: // Fall-back to GLES 2.
ctx_attribs[1] = 2;
break;
}
if (renderableType == EGL_OPENGL_BIT)
NativeLibrary.eglBindAPI(EGL_OPENGL_API);
else
NativeLibrary.eglBindAPI(EGL_OPENGL_ES_API);
mEGLContext =
mEGL.eglCreateContext(mDisplay, mEGLConfigs[0], EGL10.EGL_NO_CONTEXT, ctx_attribs);
mEGLSurface = mEGL.eglCreatePbufferSurface(mDisplay, mEGLConfigs[0], attribs);
mEGL.eglMakeCurrent(mDisplay, mEGLSurface, mEGLSurface, mEGLContext);
mGL = (GL10) mEGLContext.getGL();
}
/**
* Simplified call to {@link GL10#glGetString(int)}
* <p>
* Accepts the following constants:
* <ul>
* <li>GL_VENDOR - Company responsible for the GL implementation.</li>
* <li>GL_VERSION - Version or release number.</li>
* <li>GL_RENDERER - Name of the renderer</li>
* <li>GL_SHADING_LANGUAGE_VERSION - Version or release number of the shading language </li>
* </ul>
*
* @param glEnum A symbolic constant within {@link GL10}.
* @return the string information represented by {@code glEnum}.
*/
public String glGetString(int glEnum) {
return mGL.glGetString(glEnum);
}
/**
* Simplified call to {@link GLES30#glGetStringi(int, int)}
* <p>
* Accepts the following constants:
* <ul>
* <li>GL_VENDOR - Company responsible for the GL implementation.</li>
* <li>GL_VERSION - Version or release number.</li>
* <li>GL_RENDERER - Name of the renderer</li>
* <li>GL_SHADING_LANGUAGE_VERSION - Version or release number of the shading language </li>
* <li>GL_EXTENSIONS - Extension string supported by the implementation at {@code index}.</li>
* </ul>
*
* @param glEnum A symbolic GL constant
* @param index The index of the string to return.
* @return the string information represented by {@code glEnum} and {@code index}.
*/
public String glGetStringi(int glEnum, int index) {
return GLES30.glGetStringi(glEnum, index);
}
public boolean SupportsExtension(String extension) {
int[] num_ext = new int[1];
GLES30.glGetIntegerv(GLES30.GL_NUM_EXTENSIONS, num_ext, 0);
for (int i = 0; i < num_ext[0]; ++i) {
String ext = GLES30.glGetStringi(GLES30.GL_EXTENSIONS, i);
if (ext.equals(extension))
return true;
}
return false;
}
public int GetVersion() {
int[] major = new int[1];
int[] minor = new int[1];
GLES30.glGetIntegerv(GLES30.GL_MAJOR_VERSION, major, 0);
GLES30.glGetIntegerv(GLES30.GL_MINOR_VERSION, minor, 0);
return major[0] * 100 + minor[0] * 10;
}
/**
* Simplified call to {@link GL10#glGetIntegerv(int, int[], int)
*
* @param glEnum A symbolic GL constant.
* @return the integer information represented by {@code glEnum}.
*/
public int glGetInteger(int glEnum) {
int[] val = new int[1];
mGL.glGetIntegerv(glEnum, val, 0);
return val[0];
}
} }

View File

@ -15,45 +15,40 @@ import org.citra.citra_android.ui.main.MainPresenter;
import java.io.File; import java.io.File;
import java.util.List; import java.util.List;
public final class FileBrowserHelper public final class FileBrowserHelper {
{ public static void openDirectoryPicker(FragmentActivity activity) {
public static void openDirectoryPicker(FragmentActivity activity) Intent i = new Intent(activity, CustomFilePickerActivity.class);
{
Intent i = new Intent(activity, CustomFilePickerActivity.class);
i.putExtra(FilePickerActivity.EXTRA_ALLOW_MULTIPLE, false); i.putExtra(FilePickerActivity.EXTRA_ALLOW_MULTIPLE, false);
i.putExtra(FilePickerActivity.EXTRA_ALLOW_CREATE_DIR, false); i.putExtra(FilePickerActivity.EXTRA_ALLOW_CREATE_DIR, false);
i.putExtra(FilePickerActivity.EXTRA_MODE, FilePickerActivity.MODE_DIR); i.putExtra(FilePickerActivity.EXTRA_MODE, FilePickerActivity.MODE_DIR);
i.putExtra(FilePickerActivity.EXTRA_START_PATH, i.putExtra(FilePickerActivity.EXTRA_START_PATH,
Environment.getExternalStorageDirectory().getPath()); Environment.getExternalStorageDirectory().getPath());
activity.startActivityForResult(i, MainPresenter.REQUEST_ADD_DIRECTORY); activity.startActivityForResult(i, MainPresenter.REQUEST_ADD_DIRECTORY);
}
public static void openFilePicker(FragmentActivity activity, int requestCode)
{
Intent i = new Intent(activity, CustomFilePickerActivity.class);
i.putExtra(FilePickerActivity.EXTRA_ALLOW_MULTIPLE, false);
i.putExtra(FilePickerActivity.EXTRA_ALLOW_CREATE_DIR, false);
i.putExtra(FilePickerActivity.EXTRA_MODE, FilePickerActivity.MODE_FILE);
i.putExtra(FilePickerActivity.EXTRA_START_PATH,
Environment.getExternalStorageDirectory().getPath());
activity.startActivityForResult(i, requestCode);
}
@Nullable
public static String getSelectedDirectory(Intent result)
{
// Use the provided utility method to parse the result
List<Uri> files = Utils.getSelectedFilesFromResult(result);
if (!files.isEmpty())
{
File file = Utils.getFileForUri(files.get(0));
return file.getAbsolutePath();
} }
return null; public static void openFilePicker(FragmentActivity activity, int requestCode) {
} Intent i = new Intent(activity, CustomFilePickerActivity.class);
i.putExtra(FilePickerActivity.EXTRA_ALLOW_MULTIPLE, false);
i.putExtra(FilePickerActivity.EXTRA_ALLOW_CREATE_DIR, false);
i.putExtra(FilePickerActivity.EXTRA_MODE, FilePickerActivity.MODE_FILE);
i.putExtra(FilePickerActivity.EXTRA_START_PATH,
Environment.getExternalStorageDirectory().getPath());
activity.startActivityForResult(i, requestCode);
}
@Nullable
public static String getSelectedDirectory(Intent result) {
// Use the provided utility method to parse the result
List<Uri> files = Utils.getSelectedFilesFromResult(result);
if (!files.isEmpty()) {
File file = Utils.getFileForUri(files.get(0));
return file.getAbsolutePath();
}
return null;
}
} }

View File

@ -10,21 +10,18 @@ import org.citra.citra_android.NativeLibrary;
import java.nio.IntBuffer; import java.nio.IntBuffer;
public class GameBannerRequestHandler extends RequestHandler public class GameBannerRequestHandler extends RequestHandler {
{ @Override
@Override public boolean canHandleRequest(Request data) {
public boolean canHandleRequest(Request data) return "iso".equals(data.uri.getScheme());
{ }
return "iso".equals(data.uri.getScheme());
}
@Override @Override
public Result load(Request request, int networkPolicy) public Result load(Request request, int networkPolicy) {
{ String url = request.uri.getHost() + request.uri.getPath();
String url = request.uri.getHost() + request.uri.getPath(); int[] vector = NativeLibrary.GetBanner(url);
int[] vector = NativeLibrary.GetBanner(url); Bitmap bitmap = Bitmap.createBitmap(48, 48, Bitmap.Config.RGB_565);
Bitmap bitmap = Bitmap.createBitmap(48, 48, Bitmap.Config.RGB_565); bitmap.copyPixelsFromBuffer(IntBuffer.wrap(vector));
bitmap.copyPixelsFromBuffer(IntBuffer.wrap(vector)); return new Result(bitmap, Picasso.LoadedFrom.DISK);
return new Result(bitmap, Picasso.LoadedFrom.DISK); }
}
} }

View File

@ -19,143 +19,116 @@ import org.citra.citra_android.services.USBPermService;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
public class Java_GCAdapter public class Java_GCAdapter {
{ public static UsbManager manager;
public static UsbManager manager; static byte[] controller_payload = new byte[37];
static byte[] controller_payload = new byte[37];
static UsbDeviceConnection usb_con; static UsbDeviceConnection usb_con;
static UsbInterface usb_intf; static UsbInterface usb_intf;
static UsbEndpoint usb_in; static UsbEndpoint usb_in;
static UsbEndpoint usb_out; static UsbEndpoint usb_out;
private static void RequestPermission() private static void RequestPermission() {
{ Context context = NativeLibrary.sEmulationActivity.get();
Context context = NativeLibrary.sEmulationActivity.get(); if (context != null) {
if (context != null) HashMap<String, UsbDevice> devices = manager.getDeviceList();
{ for (Map.Entry<String, UsbDevice> pair : devices.entrySet()) {
HashMap<String, UsbDevice> devices = manager.getDeviceList(); UsbDevice dev = pair.getValue();
for (Map.Entry<String, UsbDevice> pair : devices.entrySet()) if (dev.getProductId() == 0x0337 && dev.getVendorId() == 0x057e) {
{ if (!manager.hasPermission(dev)) {
UsbDevice dev = pair.getValue(); Intent intent = new Intent();
if (dev.getProductId() == 0x0337 && dev.getVendorId() == 0x057e) PendingIntent pend_intent;
{ intent.setClass(context, USBPermService.class);
if (!manager.hasPermission(dev)) pend_intent = PendingIntent.getService(context, 0, intent, 0);
{ manager.requestPermission(dev, pend_intent);
Intent intent = new Intent(); }
PendingIntent pend_intent; }
intent.setClass(context, USBPermService.class); }
pend_intent = PendingIntent.getService(context, 0, intent, 0); } else {
manager.requestPermission(dev, pend_intent); Log.warning("Cannot request GameCube Adapter permission as EmulationActivity is null.");
}
} }
}
}
else
{
Log.warning("Cannot request GameCube Adapter permission as EmulationActivity is null.");
} }
} public static void Shutdown() {
usb_con.close();
public static void Shutdown()
{
usb_con.close();
}
public static int GetFD()
{
return usb_con.getFileDescriptor();
}
public static boolean QueryAdapter()
{
HashMap<String, UsbDevice> devices = manager.getDeviceList();
for (Map.Entry<String, UsbDevice> pair : devices.entrySet())
{
UsbDevice dev = pair.getValue();
if (dev.getProductId() == 0x0337 && dev.getVendorId() == 0x057e)
{
if (manager.hasPermission(dev))
return true;
else
RequestPermission();
}
} }
return false;
}
public static void InitAdapter() public static int GetFD() {
{ return usb_con.getFileDescriptor();
byte[] init = {0x13}; }
usb_con.bulkTransfer(usb_in, init, init.length, 0);
}
public static int Input() public static boolean QueryAdapter() {
{ HashMap<String, UsbDevice> devices = manager.getDeviceList();
return usb_con.bulkTransfer(usb_in, controller_payload, controller_payload.length, 16); for (Map.Entry<String, UsbDevice> pair : devices.entrySet()) {
} UsbDevice dev = pair.getValue();
if (dev.getProductId() == 0x0337 && dev.getVendorId() == 0x057e) {
public static int Output(byte[] rumble) if (manager.hasPermission(dev))
{ return true;
return usb_con.bulkTransfer(usb_out, rumble, 5, 16);
}
public static boolean OpenAdapter()
{
HashMap<String, UsbDevice> devices = manager.getDeviceList();
for (Map.Entry<String, UsbDevice> pair : devices.entrySet())
{
UsbDevice dev = pair.getValue();
if (dev.getProductId() == 0x0337 && dev.getVendorId() == 0x057e)
{
if (manager.hasPermission(dev))
{
usb_con = manager.openDevice(dev);
Log.info("GCAdapter: Number of configurations: " + dev.getConfigurationCount());
Log.info("GCAdapter: Number of interfaces: " + dev.getInterfaceCount());
if (dev.getConfigurationCount() > 0 && dev.getInterfaceCount() > 0)
{
UsbConfiguration conf = dev.getConfiguration(0);
usb_intf = conf.getInterface(0);
usb_con.claimInterface(usb_intf, true);
Log.info("GCAdapter: Number of endpoints: " + usb_intf.getEndpointCount());
if (usb_intf.getEndpointCount() == 2)
{
for (int i = 0; i < usb_intf.getEndpointCount(); ++i)
if (usb_intf.getEndpoint(i).getDirection() == UsbConstants.USB_DIR_IN)
usb_in = usb_intf.getEndpoint(i);
else else
usb_out = usb_intf.getEndpoint(i); RequestPermission();
InitAdapter();
return true;
} }
else
{
usb_con.releaseInterface(usb_intf);
}
}
final Activity emulationActivity = NativeLibrary.sEmulationActivity.get();
if (emulationActivity != null)
{
emulationActivity.runOnUiThread(() -> Toast.makeText(emulationActivity,
"GameCube Adapter couldn't be opened. Please re-plug the device.",
Toast.LENGTH_LONG).show());
}
else
{
Log.warning("Cannot show toast for GameCube Adapter failure.");
}
usb_con.close();
} }
} return false;
}
public static void InitAdapter() {
byte[] init = {0x13};
usb_con.bulkTransfer(usb_in, init, init.length, 0);
}
public static int Input() {
return usb_con.bulkTransfer(usb_in, controller_payload, controller_payload.length, 16);
}
public static int Output(byte[] rumble) {
return usb_con.bulkTransfer(usb_out, rumble, 5, 16);
}
public static boolean OpenAdapter() {
HashMap<String, UsbDevice> devices = manager.getDeviceList();
for (Map.Entry<String, UsbDevice> pair : devices.entrySet()) {
UsbDevice dev = pair.getValue();
if (dev.getProductId() == 0x0337 && dev.getVendorId() == 0x057e) {
if (manager.hasPermission(dev)) {
usb_con = manager.openDevice(dev);
Log.info("GCAdapter: Number of configurations: " + dev.getConfigurationCount());
Log.info("GCAdapter: Number of interfaces: " + dev.getInterfaceCount());
if (dev.getConfigurationCount() > 0 && dev.getInterfaceCount() > 0) {
UsbConfiguration conf = dev.getConfiguration(0);
usb_intf = conf.getInterface(0);
usb_con.claimInterface(usb_intf, true);
Log.info("GCAdapter: Number of endpoints: " + usb_intf.getEndpointCount());
if (usb_intf.getEndpointCount() == 2) {
for (int i = 0; i < usb_intf.getEndpointCount(); ++i)
if (usb_intf.getEndpoint(i).getDirection() == UsbConstants.USB_DIR_IN)
usb_in = usb_intf.getEndpoint(i);
else
usb_out = usb_intf.getEndpoint(i);
InitAdapter();
return true;
} else {
usb_con.releaseInterface(usb_intf);
}
}
final Activity emulationActivity = NativeLibrary.sEmulationActivity.get();
if (emulationActivity != null) {
emulationActivity.runOnUiThread(() -> Toast.makeText(emulationActivity,
"GameCube Adapter couldn't be opened. Please re-plug the device.",
Toast.LENGTH_LONG).show());
} else {
Log.warning("Cannot show toast for GameCube Adapter failure.");
}
usb_con.close();
}
}
}
return false;
} }
return false;
}
} }

View File

@ -7,47 +7,37 @@ import org.citra.citra_android.BuildConfig;
* with the same TAG automatically provided. Also no-ops VERBOSE and DEBUG log * with the same TAG automatically provided. Also no-ops VERBOSE and DEBUG log
* levels in release builds. * levels in release builds.
*/ */
public final class Log public final class Log {
{ private static final String TAG = "Citra Frontend";
private static final String TAG = "Citra Frontend";
private Log() private Log() {
{
}
public static void verbose(String message)
{
if (BuildConfig.DEBUG)
{
android.util.Log.v(TAG, message);
} }
}
public static void debug(String message) public static void verbose(String message) {
{ if (BuildConfig.DEBUG) {
if (BuildConfig.DEBUG) android.util.Log.v(TAG, message);
{ }
android.util.Log.d(TAG, message);
} }
}
public static void info(String message) public static void debug(String message) {
{ if (BuildConfig.DEBUG) {
android.util.Log.i(TAG, message); android.util.Log.d(TAG, message);
} }
}
public static void warning(String message) public static void info(String message) {
{ android.util.Log.i(TAG, message);
android.util.Log.w(TAG, message); }
}
public static void error(String message) public static void warning(String message) {
{ android.util.Log.w(TAG, message);
android.util.Log.e(TAG, message); }
}
public static void wtf(String message) public static void error(String message) {
{ android.util.Log.e(TAG, message);
android.util.Log.wtf(TAG, message); }
}
public static void wtf(String message) {
android.util.Log.wtf(TAG, message);
}
} }

View File

@ -14,59 +14,51 @@ import org.citra.citra_android.R;
import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE; import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE;
public class PermissionsHandler public class PermissionsHandler {
{ public static final int REQUEST_CODE_WRITE_PERMISSION = 500;
public static final int REQUEST_CODE_WRITE_PERMISSION = 500;
@TargetApi(Build.VERSION_CODES.M) @TargetApi(Build.VERSION_CODES.M)
public static boolean checkWritePermission(final FragmentActivity activity) public static boolean checkWritePermission(final FragmentActivity activity) {
{ if (android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
if (android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.M) return true;
{ }
return true;
int hasWritePermission = ContextCompat.checkSelfPermission(activity, WRITE_EXTERNAL_STORAGE);
if (hasWritePermission != PackageManager.PERMISSION_GRANTED) {
if (activity.shouldShowRequestPermissionRationale(WRITE_EXTERNAL_STORAGE)) {
showMessageOKCancel(activity, activity.getString(R.string.write_permission_needed),
(dialog, which) -> activity.requestPermissions(new String[]{WRITE_EXTERNAL_STORAGE},
REQUEST_CODE_WRITE_PERMISSION));
return false;
}
activity.requestPermissions(new String[]{WRITE_EXTERNAL_STORAGE},
REQUEST_CODE_WRITE_PERMISSION);
return false;
}
return true;
} }
int hasWritePermission = ContextCompat.checkSelfPermission(activity, WRITE_EXTERNAL_STORAGE); public static boolean hasWriteAccess(Context context) {
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
int hasWritePermission = ContextCompat.checkSelfPermission(context, WRITE_EXTERNAL_STORAGE);
return hasWritePermission == PackageManager.PERMISSION_GRANTED;
}
if (hasWritePermission != PackageManager.PERMISSION_GRANTED) return true;
{
if (activity.shouldShowRequestPermissionRationale(WRITE_EXTERNAL_STORAGE))
{
showMessageOKCancel(activity, activity.getString(R.string.write_permission_needed),
(dialog, which) -> activity.requestPermissions(new String[]{WRITE_EXTERNAL_STORAGE},
REQUEST_CODE_WRITE_PERMISSION));
return false;
}
activity.requestPermissions(new String[]{WRITE_EXTERNAL_STORAGE},
REQUEST_CODE_WRITE_PERMISSION);
return false;
} }
return true; private static void showMessageOKCancel(final FragmentActivity activity, String message,
} DialogInterface.OnClickListener okListener) {
new AlertDialog.Builder(activity)
public static boolean hasWriteAccess(Context context) .setMessage(message)
{ .setPositiveButton(android.R.string.ok, okListener)
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) .setNegativeButton(android.R.string.cancel, (dialogInterface, i) ->
{ Toast.makeText(activity, R.string.write_permission_needed, Toast.LENGTH_SHORT)
int hasWritePermission = ContextCompat.checkSelfPermission(context, WRITE_EXTERNAL_STORAGE); .show())
return hasWritePermission == PackageManager.PERMISSION_GRANTED; .create()
.show();
} }
return true;
}
private static void showMessageOKCancel(final FragmentActivity activity, String message,
DialogInterface.OnClickListener okListener)
{
new AlertDialog.Builder(activity)
.setMessage(message)
.setPositiveButton(android.R.string.ok, okListener)
.setNegativeButton(android.R.string.cancel, (dialogInterface, i) ->
Toast.makeText(activity, R.string.write_permission_needed, Toast.LENGTH_SHORT)
.show())
.create()
.show();
}
} }

View File

@ -11,40 +11,35 @@ import org.citra.citra_android.R;
import java.io.File; import java.io.File;
import java.net.URI; import java.net.URI;
public class PicassoUtils public class PicassoUtils {
{ public static void loadGameBanner(ImageView imageView, String screenshotPath, String gamePath) {
public static void loadGameBanner(ImageView imageView, String screenshotPath, String gamePath) File file = new File(URI.create(screenshotPath.replaceAll(" ", "%20")));
{ if (file.exists()) {
File file = new File(URI.create(screenshotPath.replaceAll(" ", "%20"))); // Fill in the view contents.
if (file.exists()) Picasso.with(imageView.getContext())
{ .load(screenshotPath)
// Fill in the view contents. .fit()
Picasso.with(imageView.getContext()) .centerCrop()
.load(screenshotPath) .noFade()
.fit() .noPlaceholder()
.centerCrop() .config(Bitmap.Config.RGB_565)
.noFade() .error(R.drawable.no_banner)
.noPlaceholder() .into(imageView);
.config(Bitmap.Config.RGB_565) } else {
.error(R.drawable.no_banner) Picasso picassoInstance = new Picasso.Builder(imageView.getContext())
.into(imageView); .addRequestHandler(new GameBannerRequestHandler())
.build();
picassoInstance
.load(Uri.parse("iso:/" + gamePath))
.fit()
.noFade()
.noPlaceholder()
.config(Bitmap.Config.RGB_565)
.error(R.drawable.no_banner)
.into(imageView);
}
} }
else
{
Picasso picassoInstance = new Picasso.Builder(imageView.getContext())
.addRequestHandler(new GameBannerRequestHandler())
.build();
picassoInstance
.load(Uri.parse("iso:/" + gamePath))
.fit()
.noFade()
.noPlaceholder()
.config(Bitmap.Config.RGB_565)
.error(R.drawable.no_banner)
.into(imageView);
}
}
} }

View File

@ -23,279 +23,239 @@ import java.util.Set;
* A HashMap<String, SettingSection> that constructs a new SettingSection instead of returning null * A HashMap<String, SettingSection> that constructs a new SettingSection instead of returning null
* when getting a key not already in the map * when getting a key not already in the map
*/ */
final class SettingsSectionMap extends HashMap<String, SettingSection> final class SettingsSectionMap extends HashMap<String, SettingSection> {
{ @Override
@Override public SettingSection get(Object key) {
public SettingSection get(Object key) if (!(key instanceof String)) {
{ return null;
if (!(key instanceof String)) }
{
return null;
}
String stringKey = (String) key; String stringKey = (String) key;
if (!super.containsKey(stringKey)) if (!super.containsKey(stringKey)) {
{ SettingSection section = new SettingSection(stringKey);
SettingSection section = new SettingSection(stringKey); super.put(stringKey, section);
super.put(stringKey, section); return section;
return section; }
return super.get(key);
} }
return super.get(key);
}
} }
/** /**
* Contains static methods for interacting with .ini files in which settings are stored. * Contains static methods for interacting with .ini files in which settings are stored.
*/ */
public final class SettingsFile public final class SettingsFile {
{ public static final int SETTINGS_DOLPHIN = 0;
public static final int SETTINGS_DOLPHIN = 0;
public static final String FILE_NAME_CONFIG = "config"; public static final String FILE_NAME_CONFIG = "config";
public static final String SECTION_CONTROLS = "Controls"; public static final String SECTION_CONTROLS = "Controls";
public static final String SECTION_CORE = "Core"; public static final String SECTION_CORE = "Core";
public static final String SECTION_RENDERER = "Renderer"; public static final String SECTION_RENDERER = "Renderer";
public static final String SECTION_LAYOUT = "Layout"; public static final String SECTION_LAYOUT = "Layout";
public static final String SECTION_AUDIO = "Audio"; public static final String SECTION_AUDIO = "Audio";
public static final String SECTION_SYSTEM = "System"; public static final String SECTION_SYSTEM = "System";
public static final String SECTION_CAMERA = "Camera"; public static final String SECTION_CAMERA = "Camera";
public static final String SECTION_MISC = "Miscellaneous"; public static final String SECTION_MISC = "Miscellaneous";
public static final String SECTION_DEBUGGING = "Debugging"; public static final String SECTION_DEBUGGING = "Debugging";
public static final String SECTION_WEBSERVICE = "WebService"; public static final String SECTION_WEBSERVICE = "WebService";
public static final String KEY_CPU_JIT = "use_cpu_jit"; public static final String KEY_CPU_JIT = "use_cpu_jit";
public static final String KEY_HW_RENDERER = "use_hw_renderer"; public static final String KEY_HW_RENDERER = "use_hw_renderer";
public static final String KEY_HW_SHADER = "use_hw_shader"; public static final String KEY_HW_SHADER = "use_hw_shader";
public static final String KEY_SHADERS_ACCURATE_MUL = "shaders_accurate_mul"; public static final String KEY_SHADERS_ACCURATE_MUL = "shaders_accurate_mul";
public static final String KEY_SHADERS_ACCURATE_GS = "shaders_accurate_gs"; public static final String KEY_SHADERS_ACCURATE_GS = "shaders_accurate_gs";
public static final String KEY_USE_SHADER_JIT = "use_shader_jit"; public static final String KEY_USE_SHADER_JIT = "use_shader_jit";
public static final String KEY_USE_VSYNC = "use_vsync"; public static final String KEY_USE_VSYNC = "use_vsync";
public static final String KEY_RESOLUTION_FACTOR = "resolution_factor"; public static final String KEY_RESOLUTION_FACTOR = "resolution_factor";
public static final String KEY_FRAME_LIMIT_ENABLED = "use_frame_limit"; public static final String KEY_FRAME_LIMIT_ENABLED = "use_frame_limit";
public static final String KEY_FRAME_LIMIT = "frame_limit"; public static final String KEY_FRAME_LIMIT = "frame_limit";
public static final String KEY_BACKGROUND_RED = "bg_red"; public static final String KEY_BACKGROUND_RED = "bg_red";
public static final String KEY_BACKGROUND_BLUE = "bg_blue"; public static final String KEY_BACKGROUND_BLUE = "bg_blue";
public static final String KEY_BACKGROUND_GREEN = "bg_green"; public static final String KEY_BACKGROUND_GREEN = "bg_green";
public static final String KEY_STEREOSCOPY = "toggle_3d"; public static final String KEY_STEREOSCOPY = "toggle_3d";
public static final String KEY_FACTOR_3D = "factor_3d"; public static final String KEY_FACTOR_3D = "factor_3d";
public static final String KEY_LAYOUT_OPTION = "layout_option"; public static final String KEY_LAYOUT_OPTION = "layout_option";
public static final String KEY_SWAP_SCREEN = "swap_screen"; public static final String KEY_SWAP_SCREEN = "swap_screen";
public static final String KEY_AUDIO_OUTPUT_ENGINE = "output_engine"; public static final String KEY_AUDIO_OUTPUT_ENGINE = "output_engine";
public static final String KEY_ENABLE_AUDIO_STRETCHING = "enable_audio_stretching"; public static final String KEY_ENABLE_AUDIO_STRETCHING = "enable_audio_stretching";
public static final String KEY_VOLUME = "volume"; public static final String KEY_VOLUME = "volume";
public static final String KEY_USE_VIRTUAL_SD = "use_virtual_sd"; public static final String KEY_USE_VIRTUAL_SD = "use_virtual_sd";
public static final String KEY_IS_NEW_3DS = "is_new_3ds"; public static final String KEY_IS_NEW_3DS = "is_new_3ds";
public static final String KEY_REGION_VALUE = "region_value"; public static final String KEY_REGION_VALUE = "region_value";
public static final String KEY_INIT_CLOCK = "init_clock"; public static final String KEY_INIT_CLOCK = "init_clock";
public static final String KEY_INIT_TIME = "init_time"; public static final String KEY_INIT_TIME = "init_time";
public static final String KEY_CAMERA_OUTER_RIGHT_NAME = "camera_outer_right_name"; public static final String KEY_CAMERA_OUTER_RIGHT_NAME = "camera_outer_right_name";
public static final String KEY_CAMERA_OUTER_RIGHT_CONFIG = "camera_outer_right_config"; public static final String KEY_CAMERA_OUTER_RIGHT_CONFIG = "camera_outer_right_config";
public static final String KEY_CAMERA_OUTER_RIGHT_FLIP = "camera_outer_right_flip"; public static final String KEY_CAMERA_OUTER_RIGHT_FLIP = "camera_outer_right_flip";
public static final String KEY_CAMERA_OUTER_LEFT_FLIP = "camera_outer_left_flip"; public static final String KEY_CAMERA_OUTER_LEFT_FLIP = "camera_outer_left_flip";
public static final String KEY_CAMERA_INNER_NAME = "camera_inner_name"; public static final String KEY_CAMERA_INNER_NAME = "camera_inner_name";
public static final String KEY_CAMERA_INNER_CONFIG = "camera_inner_config"; public static final String KEY_CAMERA_INNER_CONFIG = "camera_inner_config";
public static final String KEY_CAMERA_INNER_FLIP = "camera_inner_flip"; public static final String KEY_CAMERA_INNER_FLIP = "camera_inner_flip";
public static final String KEY_LOG_FILTER = "log_filter"; public static final String KEY_LOG_FILTER = "log_filter";
private SettingsFile() private SettingsFile() {
{ }
}
/** /**
* Reads a given .ini file from disk and returns it as a HashMap of SettingSections, themselves * Reads a given .ini file from disk and returns it as a HashMap of SettingSections, themselves
* effectively a HashMap of key/value settings. If unsuccessful, outputs an error telling why it * effectively a HashMap of key/value settings. If unsuccessful, outputs an error telling why it
* failed. * failed.
* *
* @param fileName The name of the settings file without a path or extension. * @param fileName The name of the settings file without a path or extension.
* @param view The current view. * @param view The current view.
* @return An Observable that emits a HashMap of the file's contents, then completes. * @return An Observable that emits a HashMap of the file's contents, then completes.
*/ */
public static HashMap<String, SettingSection> readFile(final String fileName, public static HashMap<String, SettingSection> readFile(final String fileName,
SettingsActivityView view) SettingsActivityView view) {
{ HashMap<String, SettingSection> sections = new SettingsSectionMap();
HashMap<String, SettingSection> sections = new SettingsSectionMap();
File ini = getSettingsFile(fileName); File ini = getSettingsFile(fileName);
BufferedReader reader = null; BufferedReader reader = null;
try try {
{ reader = new BufferedReader(new FileReader(ini));
reader = new BufferedReader(new FileReader(ini));
SettingSection current = null; SettingSection current = null;
for (String line; (line = reader.readLine()) != null; ) for (String line; (line = reader.readLine()) != null; ) {
{ if (line.startsWith("[") && line.endsWith("]")) {
if (line.startsWith("[") && line.endsWith("]")) current = sectionFromLine(line);
{ sections.put(current.getName(), current);
current = sectionFromLine(line); } else if ((current != null)) {
sections.put(current.getName(), current); Setting setting = settingFromLine(current, line, fileName);
if (setting != null) {
current.putSetting(setting);
}
}
}
} catch (FileNotFoundException e) {
Log.error("[SettingsFile] File not found: " + fileName + ".ini: " + e.getMessage());
view.onSettingsFileNotFound();
} catch (IOException e) {
Log.error("[SettingsFile] Error reading from: " + fileName + ".ini: " + e.getMessage());
view.onSettingsFileNotFound();
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
Log.error("[SettingsFile] Error closing: " + fileName + ".ini: " + e.getMessage());
}
}
} }
else if ((current != null))
{ return sections;
Setting setting = settingFromLine(current, line, fileName); }
if (setting != null)
{ /**
current.putSetting(setting); * Saves a Settings HashMap to a given .ini file on disk. If unsuccessful, outputs an error
} * telling why it failed.
*
* @param fileName The target filename without a path or extension.
* @param sections The HashMap containing the Settings we want to serialize.
* @param view The current view.
* @return An Observable representing the operation.
*/
public static void saveFile(final String fileName, final HashMap<String, SettingSection> sections,
SettingsActivityView view) {
File ini = getSettingsFile(fileName);
Wini writer = null;
try {
writer = new Wini(ini);
Set<String> keySet = sections.keySet();
for (String key : keySet) {
SettingSection section = sections.get(key);
writeSection(writer, section);
}
writer.store();
} catch (IOException e) {
Log.error("[SettingsFile] File not found: " + fileName + ".ini: " + e.getMessage());
view.showToastMessage("Error saving " + fileName + ".ini: " + e.getMessage());
} }
}
} }
catch (FileNotFoundException e)
{ @NonNull
Log.error("[SettingsFile] File not found: " + fileName + ".ini: " + e.getMessage()); private static File getSettingsFile(String fileName) {
view.onSettingsFileNotFound(); return new File(
DirectoryInitializationService.getUserDirectory() + "/config/" + fileName + ".ini");
} }
catch (IOException e)
{ private static SettingSection sectionFromLine(String line) {
Log.error("[SettingsFile] Error reading from: " + fileName + ".ini: " + e.getMessage()); String sectionName = line.substring(1, line.length() - 1);
view.onSettingsFileNotFound(); return new SettingSection(sectionName);
} }
finally
{ /**
if (reader != null) * For a line of text, determines what type of data is being represented, and returns
{ * a Setting object containing this data.
try *
{ * @param current The section currently being parsed by the consuming method.
reader.close(); * @param line The line of text being parsed.
* @param fileName The name of the ini file the setting is in.
* @return A typed Setting containing the key/value contained in the line.
*/
private static Setting settingFromLine(SettingSection current, String line, String fileName) {
String[] splitLine = line.split("=");
if (splitLine.length != 2) {
Log.warning("Skipping invalid config line \"" + line + "\"");
return null;
} }
catch (IOException e)
{ String key = splitLine[0].trim();
Log.error("[SettingsFile] Error closing: " + fileName + ".ini: " + e.getMessage()); String value = splitLine[1].trim();
if (value.isEmpty()) {
Log.warning("Skipping null value in config line \"" + line + "\"");
return null;
} }
}
int file = SETTINGS_DOLPHIN;
try {
int valueAsInt = Integer.valueOf(value);
return new IntSetting(key, current.getName(), file, valueAsInt);
} catch (NumberFormatException ex) {
}
try {
float valueAsFloat = Float.valueOf(value);
return new FloatSetting(key, current.getName(), file, valueAsFloat);
} catch (NumberFormatException ex) {
}
return new StringSetting(key, current.getName(), file, value);
} }
return sections; /**
} * Writes the contents of a Section HashMap to disk.
*
* @param parser A Wini pointed at a file on disk.
* @param section A section containing settings to be written to the file.
*/
private static void writeSection(Wini parser, SettingSection section) {
// Write the section header.
String header = section.getName();
/** // Write this section's values.
* Saves a Settings HashMap to a given .ini file on disk. If unsuccessful, outputs an error HashMap<String, Setting> settings = section.getSettings();
* telling why it failed. Set<String> keySet = settings.keySet();
*
* @param fileName The target filename without a path or extension.
* @param sections The HashMap containing the Settings we want to serialize.
* @param view The current view.
* @return An Observable representing the operation.
*/
public static void saveFile(final String fileName, final HashMap<String, SettingSection> sections,
SettingsActivityView view)
{
File ini = getSettingsFile(fileName);
Wini writer = null; for (String key : keySet) {
try Setting setting = settings.get(key);
{ parser.put(header, setting.getKey(), setting.getValueAsString());
writer = new Wini(ini); }
Set<String> keySet = sections.keySet();
for (String key : keySet)
{
SettingSection section = sections.get(key);
writeSection(writer, section);
}
writer.store();
} }
catch (IOException e)
{
Log.error("[SettingsFile] File not found: " + fileName + ".ini: " + e.getMessage());
view.showToastMessage("Error saving " + fileName + ".ini: " + e.getMessage());
}
}
@NonNull
private static File getSettingsFile(String fileName)
{
return new File(
DirectoryInitializationService.getUserDirectory() + "/config/" + fileName + ".ini");
}
private static SettingSection sectionFromLine(String line)
{
String sectionName = line.substring(1, line.length() - 1);
return new SettingSection(sectionName);
}
/**
* For a line of text, determines what type of data is being represented, and returns
* a Setting object containing this data.
*
* @param current The section currently being parsed by the consuming method.
* @param line The line of text being parsed.
* @param fileName The name of the ini file the setting is in.
* @return A typed Setting containing the key/value contained in the line.
*/
private static Setting settingFromLine(SettingSection current, String line, String fileName)
{
String[] splitLine = line.split("=");
if (splitLine.length != 2)
{
Log.warning("Skipping invalid config line \"" + line + "\"");
return null;
}
String key = splitLine[0].trim();
String value = splitLine[1].trim();
if(value.isEmpty()){
Log.warning("Skipping null value in config line \"" + line + "\"");
return null;
}
int file = SETTINGS_DOLPHIN;
try
{
int valueAsInt = Integer.valueOf(value);
return new IntSetting(key, current.getName(), file, valueAsInt);
}
catch (NumberFormatException ex)
{
}
try
{
float valueAsFloat = Float.valueOf(value);
return new FloatSetting(key, current.getName(), file, valueAsFloat);
}
catch (NumberFormatException ex)
{
}
return new StringSetting(key, current.getName(), file, value);
}
/**
* Writes the contents of a Section HashMap to disk.
*
* @param parser A Wini pointed at a file on disk.
* @param section A section containing settings to be written to the file.
*/
private static void writeSection(Wini parser, SettingSection section)
{
// Write the section header.
String header = section.getName();
// Write this section's values.
HashMap<String, Setting> settings = section.getSettings();
Set<String> keySet = settings.keySet();
for (String key : keySet)
{
Setting setting = settings.get(key);
parser.put(header, setting.getKey(), setting.getValueAsString());
}
}
} }

View File

@ -7,25 +7,22 @@ import android.text.TextUtils;
import org.citra.citra_android.activities.EmulationActivity; import org.citra.citra_android.activities.EmulationActivity;
public final class StartupHandler public final class StartupHandler {
{ public static void HandleInit(FragmentActivity parent) {
public static void HandleInit(FragmentActivity parent) // Ask the user to grant write permission if it's not already granted
{ PermissionsHandler.checkWritePermission(parent);
// Ask the user to grant write permission if it's not already granted
PermissionsHandler.checkWritePermission(parent);
String start_file = ""; String start_file = "";
Bundle extras = parent.getIntent().getExtras(); Bundle extras = parent.getIntent().getExtras();
if (extras != null) if (extras != null)
start_file = extras.getString("AutoStartFile"); start_file = extras.getString("AutoStartFile");
if (!TextUtils.isEmpty(start_file)) if (!TextUtils.isEmpty(start_file)) {
{ // Start the emulation activity, send the ISO passed in and finish the main activity
// Start the emulation activity, send the ISO passed in and finish the main activity Intent emulation_intent = new Intent(parent, EmulationActivity.class);
Intent emulation_intent = new Intent(parent, EmulationActivity.class); emulation_intent.putExtra("SelectedGame", start_file);
emulation_intent.putExtra("SelectedGame", start_file); parent.startActivity(emulation_intent);
parent.startActivity(emulation_intent); parent.finish();
parent.finish(); }
} }
}
} }

View File

@ -11,30 +11,28 @@ import org.citra.citra_android.R;
* A simple class that stores references to views so that the GameAdapter doesn't need to * A simple class that stores references to views so that the GameAdapter doesn't need to
* keep calling findViewById(), which is expensive. * keep calling findViewById(), which is expensive.
*/ */
public class GameViewHolder extends RecyclerView.ViewHolder public class GameViewHolder extends RecyclerView.ViewHolder {
{ public ImageView imageScreenshot;
public ImageView imageScreenshot; public TextView textGameTitle;
public TextView textGameTitle; public TextView textCompany;
public TextView textCompany;
public String gameId; public String gameId;
// TODO Not need any of this stuff. Currently only the properties dialog needs it. // TODO Not need any of this stuff. Currently only the properties dialog needs it.
public String path; public String path;
public String title; public String title;
public String description; public String description;
public int country; public int country;
public String company; public String company;
public String screenshotPath; public String screenshotPath;
public GameViewHolder(View itemView) public GameViewHolder(View itemView) {
{ super(itemView);
super(itemView);
itemView.setTag(this); itemView.setTag(this);
imageScreenshot = itemView.findViewById(R.id.image_game_screen); imageScreenshot = itemView.findViewById(R.id.image_game_screen);
textGameTitle = itemView.findViewById(R.id.text_game_title); textGameTitle = itemView.findViewById(R.id.text_game_title);
textCompany = itemView.findViewById(R.id.text_company); textCompany = itemView.findViewById(R.id.text_company);
} }
} }

View File

@ -9,29 +9,27 @@ import android.widget.ImageView;
* A simple class that stores references to views so that the GameAdapter doesn't need to * A simple class that stores references to views so that the GameAdapter doesn't need to
* keep calling findViewById(), which is expensive. * keep calling findViewById(), which is expensive.
*/ */
public final class TvGameViewHolder extends Presenter.ViewHolder public final class TvGameViewHolder extends Presenter.ViewHolder {
{ public ImageCardView cardParent;
public ImageCardView cardParent;
public ImageView imageScreenshot; public ImageView imageScreenshot;
public String gameId; public String gameId;
// TODO Not need any of this stuff. Currently only the properties dialog needs it. // TODO Not need any of this stuff. Currently only the properties dialog needs it.
public String path; public String path;
public String title; public String title;
public String description; public String description;
public int country; public int country;
public String company; public String company;
public String screenshotPath; public String screenshotPath;
public TvGameViewHolder(View itemView) public TvGameViewHolder(View itemView) {
{ super(itemView);
super(itemView);
itemView.setTag(this); itemView.setTag(this);
cardParent = (ImageCardView) itemView; cardParent = (ImageCardView) itemView;
imageScreenshot = cardParent.getMainImageView(); imageScreenshot = cardParent.getMainImageView();
} }
} }

View File

@ -4,19 +4,17 @@ import android.support.v17.leanback.widget.ImageCardView;
import android.support.v17.leanback.widget.Presenter; import android.support.v17.leanback.widget.Presenter;
import android.view.View; import android.view.View;
public final class TvSettingsViewHolder extends Presenter.ViewHolder public final class TvSettingsViewHolder extends Presenter.ViewHolder {
{ public ImageCardView cardParent;
public ImageCardView cardParent;
// Determines what action to take when this item is clicked. // Determines what action to take when this item is clicked.
public int itemId; public int itemId;
public TvSettingsViewHolder(View itemView) public TvSettingsViewHolder(View itemView) {
{ super(itemView);
super(itemView);
itemView.setTag(this); itemView.setTag(this);
cardParent = (ImageCardView) itemView; cardParent = (ImageCardView) itemView;
} }
} }