diff --git a/Source/Android/app/src/main/AndroidManifest.xml b/Source/Android/app/src/main/AndroidManifest.xml index 7976a1cc8d..78548b4c6c 100644 --- a/Source/Android/app/src/main/AndroidManifest.xml +++ b/Source/Android/app/src/main/AndroidManifest.xml @@ -48,20 +48,24 @@ + + @@ -69,6 +73,13 @@ + + + diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/NativeLibrary.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/NativeLibrary.java index e7d2e2b328..a47ff9c8ed 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/NativeLibrary.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/NativeLibrary.java @@ -132,9 +132,13 @@ public final class NativeLibrary public static native String GetDescription(String filename); public static native String GetGameId(String filename); - public static native String GetDate(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 boolean IsWiiTitle(String filename); + + public static native int GetPlatform(String filename); /** * Gets the Dolphin version string. diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/AddDirectoryActivity.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/AddDirectoryActivity.java index 1c536d8f91..1cbc30e7e5 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/AddDirectoryActivity.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/AddDirectoryActivity.java @@ -1,7 +1,10 @@ package org.dolphinemu.dolphinemu.activities; import android.app.Activity; +import android.content.AsyncQueryHandler; +import android.content.ContentValues; import android.content.Intent; +import android.net.Uri; import android.os.Bundle; import android.os.Environment; import android.support.v7.widget.LinearLayoutManager; @@ -14,6 +17,8 @@ import android.widget.Toolbar; import org.dolphinemu.dolphinemu.BuildConfig; import org.dolphinemu.dolphinemu.R; import org.dolphinemu.dolphinemu.adapters.FileAdapter; +import org.dolphinemu.dolphinemu.model.GameDatabase; +import org.dolphinemu.dolphinemu.model.GameProvider; /** * An Activity that shows a list of files and folders, allowing the user to tell the app which folder(s) @@ -91,17 +96,36 @@ public class AddDirectoryActivity extends Activity implements FileAdapter.FileCl } /** - * Tell the GameGridActivity that launched this Activity that the user picked a folder. + * Add a directory to the library, and if successful, end the activity. + * + * @param path The target directory's path. */ @Override - public void finishSuccessfully() + public void addDirectory() { - Intent resultData = new Intent(); + // Set up a callback for when the addition is complete + // TODO This has a nasty warning on it; find a cleaner way to do this Insert asynchronously + AsyncQueryHandler handler = new AsyncQueryHandler(getContentResolver()) + { + @Override + protected void onInsertComplete(int token, Object cookie, Uri uri) + { + Intent resultData = new Intent(); - resultData.putExtra(KEY_CURRENT_PATH, mAdapter.getPath()); - setResult(RESULT_OK, resultData); + resultData.putExtra(KEY_CURRENT_PATH, mAdapter.getPath()); + setResult(RESULT_OK, resultData); - finish(); + finish(); + } + }; + + ContentValues file = new ContentValues(); + file.put(GameDatabase.KEY_FOLDER_PATH, mAdapter.getPath()); + + handler.startInsert(0, // We don't need to identify this call to the handler + null, // We don't need to pass additional data to the handler + GameProvider.URI_FOLDER, // Tell the GameProvider we are adding a folder + file); // Tell the GameProvider what folder we are adding } @Override diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/EmulationActivity.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/EmulationActivity.java new file mode 100644 index 0000000000..de7ebb17d3 --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/EmulationActivity.java @@ -0,0 +1,292 @@ +package org.dolphinemu.dolphinemu.activities; + +import android.app.Activity; +import android.app.AlertDialog; +import android.content.DialogInterface; +import android.content.Intent; +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.view.InputDevice; +import android.view.KeyEvent; +import android.view.Menu; +import android.view.MenuItem; +import android.view.MotionEvent; +import android.view.View; + +import org.dolphinemu.dolphinemu.NativeLibrary; +import org.dolphinemu.dolphinemu.R; +import org.dolphinemu.dolphinemu.fragments.EmulationFragment; +import org.dolphinemu.dolphinemu.settings.input.InputConfigFragment; + +import java.util.List; + +public final class EmulationActivity extends Activity +{ + private View mDecorView; + + /** + * Handlers are a way to pass a message to an Activity telling it to do something + * on the UI thread. This Handler responds to any message, even blank ones, by + * hiding the system UI. + */ + private Handler mSystemUiHider = new Handler() + { + @Override + public void handleMessage(Message msg) + { + hideSystemUI(); + } + }; + + @Override + protected void onCreate(Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + + // Get a handle to the Window containing the UI. + mDecorView = getWindow().getDecorView(); + + // Set these options now so that the SurfaceView the game renders into is the right size. + mDecorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE | + View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | + View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN); + + // Set the ActionBar to follow the navigation/status bar's visibility changes. + mDecorView.setOnSystemUiVisibilityChangeListener( + new View.OnSystemUiVisibilityChangeListener() + { + @Override + public void onSystemUiVisibilityChange(int flags) + { + boolean visible = (flags & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0; + + if (visible) + { + getActionBar().show(); + hideSystemUiAfterDelay(); + } + else + { + getActionBar().hide(); + } + } + } + ); + + setContentView(R.layout.activity_emulation); + + Intent gameToEmulate = getIntent(); + String path = gameToEmulate.getStringExtra("SelectedGame"); + String title = gameToEmulate.getStringExtra("SelectedTitle"); + + setTitle(title); + + // Instantiate an EmulationFragment. + EmulationFragment emulationFragment = EmulationFragment.newInstance(path); + + // Add fragment to the activity - this triggers all its lifecycle callbacks. + getFragmentManager().beginTransaction() + .add(R.id.frame_content, emulationFragment, EmulationFragment.FRAGMENT_TAG) + .commit(); + } + + @Override + protected void onPostCreate(Bundle savedInstanceState) + { + super.onPostCreate(savedInstanceState); + + // Give the user a few seconds to see what the controls look like, then hide them. + hideSystemUiAfterDelay(); + } + + @Override + public void onWindowFocusChanged(boolean hasFocus) + { + super.onWindowFocusChanged(hasFocus); + + if (hasFocus) + { + hideSystemUiAfterDelay(); + } + else + { + // If the window loses focus (i.e. a dialog box, or a popup menu is on screen + // stop hiding the UI. + mSystemUiHider.removeMessages(0); + } + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) + { + // Inflate the menu; this adds items to the action bar if it is present. + getMenuInflater().inflate(R.menu.menu_emulation, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) + { + switch (item.getItemId()) + { + // Enable/Disable input overlay. + case R.id.enableInputOverlay: + { + EmulationFragment emulationFragment = (EmulationFragment) getFragmentManager() + .findFragmentByTag(EmulationFragment.FRAGMENT_TAG); + + emulationFragment.toggleInputOverlayVisibility(); + + return true; + } + + // Screenshot capturing + case R.id.takeScreenshot: + NativeLibrary.SaveScreenShot(); + return true; + + // Save state slots + case R.id.saveSlot1: + NativeLibrary.SaveState(0); + return true; + + case R.id.saveSlot2: + NativeLibrary.SaveState(1); + return true; + + case R.id.saveSlot3: + NativeLibrary.SaveState(2); + return true; + + case R.id.saveSlot4: + NativeLibrary.SaveState(3); + return true; + + case R.id.saveSlot5: + NativeLibrary.SaveState(4); + return true; + + // Load state slots + case R.id.loadSlot1: + NativeLibrary.LoadState(0); + return true; + + case R.id.loadSlot2: + NativeLibrary.LoadState(1); + return true; + + case R.id.loadSlot3: + NativeLibrary.LoadState(2); + return true; + + case R.id.loadSlot4: + NativeLibrary.LoadState(3); + return true; + + case R.id.loadSlot5: + NativeLibrary.LoadState(4); + return true; + + case R.id.exitEmulation: + { + // Create a confirmation method for quitting the current emulation instance. + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle(R.string.overlay_exit_emulation); + builder.setMessage(R.string.overlay_exit_emulation_confirm); + builder.setNegativeButton(R.string.no, null); + builder.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() + { + public void onClick(DialogInterface dialog, int which) + { + onDestroy(); + } + }); + builder.show(); + return true; + } + + default: + return super.onOptionsItemSelected(item); + } + } + + // Gets button presses + @Override + public boolean dispatchKeyEvent(KeyEvent event) + { + int action = 0; + + switch (event.getAction()) + { + case KeyEvent.ACTION_DOWN: + // Handling the case where the back button is pressed. + if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) + { + onBackPressed(); + return true; + } + + // Normal key events. + action = NativeLibrary.ButtonState.PRESSED; + break; + case KeyEvent.ACTION_UP: + action = NativeLibrary.ButtonState.RELEASED; + break; + default: + return false; + } + InputDevice input = event.getDevice(); + boolean handled = NativeLibrary.onGamePadEvent(InputConfigFragment.getInputDesc(input), event.getKeyCode(), action); + return handled; + } + + @Override + public boolean dispatchGenericMotionEvent(MotionEvent event) + { + if (((event.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) == 0)) + { + return super.dispatchGenericMotionEvent(event); + } + + // Don't attempt to do anything if we are disconnecting a device. + if (event.getActionMasked() == MotionEvent.ACTION_CANCEL) + return true; + + InputDevice input = event.getDevice(); + List motions = input.getMotionRanges(); + + for (InputDevice.MotionRange range : motions) + { + NativeLibrary.onGamePadMoveEvent(InputConfigFragment.getInputDesc(input), range.getAxis(), event.getAxisValue(range.getAxis())); + } + + return true; + } + + private void hideSystemUiAfterDelay() + { + // Clear any pending hide events. + mSystemUiHider.removeMessages(0); + + // Add a new hide event, to occur 3 seconds from now. + mSystemUiHider.sendEmptyMessageDelayed(0, 3000); + } + + private void hideSystemUI() + { + mDecorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE | + View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | + View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | + View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | + View.SYSTEM_UI_FLAG_FULLSCREEN | + View.SYSTEM_UI_FLAG_IMMERSIVE); + } + + private void showSystemUI() + { + mDecorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE | + View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | + View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN); + } +} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/GameGridActivity.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/GameGridActivity.java index c0ba984023..619e9c647e 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/GameGridActivity.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/GameGridActivity.java @@ -1,13 +1,15 @@ package org.dolphinemu.dolphinemu.activities; import android.app.Activity; +import android.app.LoaderManager; +import android.content.CursorLoader; import android.content.Intent; -import android.content.SharedPreferences; +import android.content.Loader; +import android.database.Cursor; import android.os.Bundle; -import android.os.Environment; -import android.preference.PreferenceManager; import android.support.v7.widget.GridLayoutManager; import android.support.v7.widget.RecyclerView; +import android.util.Log; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; @@ -15,27 +17,23 @@ import android.view.View; import android.widget.ImageButton; import android.widget.Toolbar; -import org.dolphinemu.dolphinemu.NativeLibrary; import org.dolphinemu.dolphinemu.R; import org.dolphinemu.dolphinemu.adapters.GameAdapter; -import org.dolphinemu.dolphinemu.model.Game; -import org.dolphinemu.dolphinemu.model.GcGame; +import org.dolphinemu.dolphinemu.model.GameDatabase; +import org.dolphinemu.dolphinemu.model.GameProvider; import org.dolphinemu.dolphinemu.services.AssetCopyService; -import java.io.File; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashSet; -import java.util.Set; - /** * The main Activity of the Lollipop style UI. Shows a grid of games on tablets & landscape phones, * shows a list of games on portrait phones. */ -public final class GameGridActivity extends Activity +public final class GameGridActivity extends Activity implements LoaderManager.LoaderCallbacks { private static final int REQUEST_ADD_DIRECTORY = 1; + private static final int LOADER_ID_GAMES = 1; + // TODO When each platform has its own tab, there should be a LOADER_ID for each platform. + private GameAdapter mAdapter; @Override @@ -62,7 +60,8 @@ public final class GameGridActivity extends Activity recyclerView.addItemDecoration(new GameAdapter.SpacesItemDecoration(8)); // Create an adapter that will relate the dataset to the views on-screen. - mAdapter = new GameAdapter(getGameList()); + getLoaderManager().initLoader(LOADER_ID_GAMES, null, this); + mAdapter = new GameAdapter(); recyclerView.setAdapter(mAdapter); buttonAddDirectory.setOnClickListener(new View.OnClickListener() @@ -103,20 +102,7 @@ public final class GameGridActivity extends Activity // other activities might use this callback in the future (don't forget to change Javadoc!) if (requestCode == REQUEST_ADD_DIRECTORY) { - // Get the path the user selected in AddDirectoryActivity. - String path = result.getStringExtra(AddDirectoryActivity.KEY_CURRENT_PATH); - - // Store this path as a preference. - // TODO Use SQLite instead. - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getApplicationContext()); - SharedPreferences.Editor editor = prefs.edit(); - - editor.putString(AddDirectoryActivity.KEY_CURRENT_PATH, path); - - // Using commit, not apply, in order to block so the next method has the correct data to load. - editor.commit(); - - mAdapter.setGameList(getGameList()); + getLoaderManager().restartLoader(LOADER_ID_GAMES, null, this); } } } @@ -150,55 +136,75 @@ public final class GameGridActivity extends Activity return false; } - // TODO Replace all of this with a SQLite database - private ArrayList getGameList() + + /** + * Callback that's invoked when the system has initialized the Loader and + * is ready to start the query. This usually happens when initLoader() is + * called. Here, we use it to make a DB query for games. + * + * @param id The ID value passed to the initLoader() call that triggered this. + * @param args The args bundle supplied by the caller. + * @return A new Loader instance that is ready to start loading. + */ + @Override + public Loader onCreateLoader(int id, Bundle args) { - ArrayList gameList = new ArrayList(); + Log.d("DolphinEmu", "Creating loader with id: " + id); - final String DefaultDir = Environment.getExternalStorageDirectory() + File.separator + "dolphin-emu"; - - NativeLibrary.SetUserDirectory(DefaultDir); - - // Extensions to filter by. - Set exts = new HashSet(Arrays.asList(".dff", ".dol", ".elf", ".gcm", ".gcz", ".iso", ".wad", ".wbfs")); - - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getApplicationContext()); - - String path = prefs.getString(AddDirectoryActivity.KEY_CURRENT_PATH, "/"); - - File currentDir = new File(path); - File[] dirs = currentDir.listFiles(); - try + // Take action based on the ID of the Loader that's being created. + switch (id) { - for (File entry : dirs) - { - if (!entry.isHidden() && !entry.isDirectory()) - { - String entryName = entry.getName(); + case LOADER_ID_GAMES: + // TODO Play some sort of load-starting animation; maybe fade the list out. - // Check that the file has an appropriate extension before trying to read out of it. - if (exts.contains(entryName.toLowerCase().substring(entryName.lastIndexOf('.')))) - { - GcGame game = new GcGame(NativeLibrary.GetTitle(entry.getAbsolutePath()), - NativeLibrary.GetDescription(entry.getAbsolutePath()).replace("\n", " "), - // TODO Some games might actually not be from this region, believe it or not. - "United States", - entry.getAbsolutePath(), - NativeLibrary.GetGameId(entry.getAbsolutePath()), - NativeLibrary.GetDate(entry.getAbsolutePath())); + return new CursorLoader( + this, // Parent activity context + GameProvider.URI_GAME, // URI of table to query + null, // Return all columns + null, // No selection clause + null, // No selection arguments + GameDatabase.KEY_GAME_TITLE + " asc" // Sort by game name, ascending order + ); - gameList.add(game); - } - - } - - } + default: + Log.e("DolphinEmu", "Bad ID passed in."); + return null; } - catch (Exception ignored) - { + } + /** + * Callback that's invoked when the Loader returned in onCreateLoader is finished + * with its task. In this case, the game DB query is finished, so we should put the results + * on screen. + * + * @param loader The loader that finished. + * @param data The data the Loader loaded. + */ + @Override + public void onLoadFinished(Loader loader, Cursor data) + { + int id = loader.getId(); + Log.d("DolphinEmu", "Loader finished with id: " + id); + + // TODO When each platform has its own tab, this should just call into those tabs instead. + switch (id) + { + case LOADER_ID_GAMES: + mAdapter.swapCursor(data); + // TODO Play some sort of load-finished animation; maybe fade the list in. + break; + + default: + Log.e("DolphinEmu", "Bad ID passed in."); } - return gameList; + } + + @Override + public void onLoaderReset(Loader loader) + { + Log.d("DolphinEmu", "Loader resetting."); + + // TODO ¯\_(ツ)_/¯ } } diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/FileAdapter.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/FileAdapter.java index 7ce0047b08..8d07b6c101 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/FileAdapter.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/FileAdapter.java @@ -14,7 +14,7 @@ import java.io.File; import java.util.ArrayList; import java.util.Collections; -public class FileAdapter extends RecyclerView.Adapter implements View.OnClickListener +public final class FileAdapter extends RecyclerView.Adapter implements View.OnClickListener { private ArrayList mFileList; @@ -146,7 +146,7 @@ public class FileAdapter extends RecyclerView.Adapter implements else { // Pass the activity the path of the parent directory of the clicked file. - mListener.finishSuccessfully(); + mListener.addDirectory(); } } @@ -154,7 +154,7 @@ public class FileAdapter extends RecyclerView.Adapter implements * For a given directory, return a list of Files it contains. * * @param directory A File representing the directory that should have its contents displayed. - * @return + * @return The list of files contained in the directory. */ private ArrayList generateFileList(File directory) { @@ -205,7 +205,7 @@ public class FileAdapter extends RecyclerView.Adapter implements */ public interface FileClickListener { - void finishSuccessfully(); + void addDirectory(); void updateSubtitle(String path); } diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/GameAdapter.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/GameAdapter.java index 883107a410..d296c5e023 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/GameAdapter.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/GameAdapter.java @@ -2,8 +2,11 @@ package org.dolphinemu.dolphinemu.adapters; import android.app.Activity; import android.content.Intent; +import android.database.Cursor; +import android.database.DataSetObserver; import android.graphics.Rect; import android.support.v7.widget.RecyclerView; +import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -11,27 +14,33 @@ import android.view.ViewGroup; import com.squareup.picasso.Picasso; import org.dolphinemu.dolphinemu.R; +import org.dolphinemu.dolphinemu.activities.EmulationActivity; import org.dolphinemu.dolphinemu.dialogs.GameDetailsDialog; -import org.dolphinemu.dolphinemu.emulation.EmulationActivity; -import org.dolphinemu.dolphinemu.model.Game; +import org.dolphinemu.dolphinemu.model.GameDatabase; import org.dolphinemu.dolphinemu.viewholders.GameViewHolder; -import java.util.ArrayList; - -public class GameAdapter extends RecyclerView.Adapter implements +/** + * This adapter, unlike {@link FileAdapter} which is backed by an ArrayList, gets its + * information from a database Cursor. This fact, paired with the usage of ContentProviders + * and Loaders, allows for efficient display of a limited view into a (possibly) large dataset. + */ +public final class GameAdapter extends RecyclerView.Adapter implements View.OnClickListener, View.OnLongClickListener { - private ArrayList mGameList; + private Cursor mCursor; + private GameDataSetObserver mObserver; + + private boolean mDatasetValid; /** - * Mostly just initializes the dataset to be displayed. - * - * @param gameList + * 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. */ - public GameAdapter(ArrayList gameList) + public GameAdapter() { - mGameList = gameList; + mDatasetValid = false; + mObserver = new GameDataSetObserver(); } /** @@ -52,8 +61,7 @@ public class GameAdapter extends RecyclerView.Adapter implements gameCard.setOnLongClickListener(this); // Use that view to create a ViewHolder. - GameViewHolder holder = new GameViewHolder(gameCard); - return holder; + return new GameViewHolder(gameCard); } /** @@ -67,26 +75,41 @@ public class GameAdapter extends RecyclerView.Adapter implements @Override public void onBindViewHolder(GameViewHolder holder, int position) { - // Get a reference to the item from the dataset; we'll use this to fill in the view contents. - final Game game = mGameList.get(position); - - // Fill in the view contents. - Picasso.with(holder.imageScreenshot.getContext()) - .load(game.getScreenPath()) - .fit() - .centerCrop() - .error(R.drawable.no_banner) - .into(holder.imageScreenshot); - - holder.textGameTitle.setText(game.getTitle()); - if (game.getDescription() != null) + if (mDatasetValid) { - holder.textDescription.setText(game.getDescription()); + if (mCursor.moveToPosition(position)) + { + // Fill in the view contents. + Picasso.with(holder.imageScreenshot.getContext()) + .load(mCursor.getString(GameDatabase.GAME_COLUMN_SCREENSHOT_PATH)) + .fit() + .centerCrop() + .error(R.drawable.no_banner) + .into(holder.imageScreenshot); + + 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.e("DolphinEmu", "Can't bind view; Cursor is not valid."); + } + } + else + { + Log.e("DolphinEmu", "Can't bind view; dataset is not valid."); } - holder.path = game.getPath(); - holder.screenshotPath = game.getScreenPath(); - holder.game = game; + } /** @@ -97,7 +120,85 @@ public class GameAdapter extends RecyclerView.Adapter implements @Override public int getItemCount() { - return mGameList.size(); + if (mDatasetValid && mCursor != null) + { + return mCursor.getCount(); + } + Log.e("DolphinEmu", "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.e("DolphinEmu", "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(); } /** @@ -114,6 +215,7 @@ public class GameAdapter extends RecyclerView.Adapter implements Intent intent = new Intent(view.getContext(), EmulationActivity.class); intent.putExtra("SelectedGame", holder.path); + intent.putExtra("SelectedTitle", holder.title); view.getContext().startActivity(intent); } @@ -134,7 +236,12 @@ public class GameAdapter extends RecyclerView.Adapter implements // String gameId = (String) holder.gameId; Activity activity = (Activity) view.getContext(); - GameDetailsDialog.newInstance(holder.game).show(activity.getFragmentManager(), "game_details"); + GameDetailsDialog.newInstance(holder.title, + holder.description, + holder.country, + holder.company, + holder.path, + holder.screenshotPath).show(activity.getFragmentManager(), "game_details"); return true; } @@ -158,9 +265,24 @@ public class GameAdapter extends RecyclerView.Adapter implements } } - public void setGameList(ArrayList gameList) + private final class GameDataSetObserver extends DataSetObserver { - mGameList = gameList; - notifyDataSetChanged(); + @Override + public void onChanged() + { + super.onChanged(); + + mDatasetValid = true; + notifyDataSetChanged(); + } + + @Override + public void onInvalidated() + { + super.onInvalidated(); + + mDatasetValid = false; + notifyDataSetChanged(); + } } } diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/dialogs/GameDetailsDialog.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/dialogs/GameDetailsDialog.java index e22b30ea18..04c7253cb5 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/dialogs/GameDetailsDialog.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/dialogs/GameDetailsDialog.java @@ -16,12 +16,11 @@ import com.squareup.picasso.Picasso; import org.dolphinemu.dolphinemu.BuildConfig; import org.dolphinemu.dolphinemu.R; -import org.dolphinemu.dolphinemu.emulation.EmulationActivity; -import org.dolphinemu.dolphinemu.model.Game; +import org.dolphinemu.dolphinemu.activities.EmulationActivity; import de.hdodenhof.circleimageview.CircleImageView; -public class GameDetailsDialog extends DialogFragment +public final class GameDetailsDialog extends DialogFragment { public static final String ARGUMENT_GAME_TITLE = BuildConfig.APPLICATION_ID + ".game_title"; public static final String ARGUMENT_GAME_DESCRIPTION = BuildConfig.APPLICATION_ID + ".game_description"; @@ -30,18 +29,18 @@ public class GameDetailsDialog extends DialogFragment public static final String ARGUMENT_GAME_PATH = BuildConfig.APPLICATION_ID + ".game_path"; public static final String ARGUMENT_GAME_SCREENSHOT_PATH = BuildConfig.APPLICATION_ID + ".game_screenshot_path"; - - public static GameDetailsDialog newInstance(Game game) + // TODO Add all of this to the Loader in GameActivity.java + public static GameDetailsDialog newInstance(String title, String description, int country, String company, String path, String screenshotPath) { GameDetailsDialog fragment = new GameDetailsDialog(); Bundle arguments = new Bundle(); - arguments.putString(ARGUMENT_GAME_TITLE, game.getTitle()); - arguments.putString(ARGUMENT_GAME_DESCRIPTION, game.getDescription()); - arguments.putString(ARGUMENT_GAME_COUNTRY, game.getCountry()); - arguments.putString(ARGUMENT_GAME_DATE, game.getDate()); - arguments.putString(ARGUMENT_GAME_PATH, game.getPath()); - arguments.putString(ARGUMENT_GAME_SCREENSHOT_PATH, game.getScreenPath()); + arguments.putString(ARGUMENT_GAME_TITLE, title); + arguments.putString(ARGUMENT_GAME_DESCRIPTION, description); + arguments.putInt(ARGUMENT_GAME_COUNTRY, country); + arguments.putString(ARGUMENT_GAME_DATE, company); + arguments.putString(ARGUMENT_GAME_PATH, path); + arguments.putString(ARGUMENT_GAME_SCREENSHOT_PATH, screenshotPath); fragment.setArguments(arguments); return fragment; @@ -57,16 +56,19 @@ public class GameDetailsDialog extends DialogFragment CircleImageView circleBanner = (CircleImageView) contents.findViewById(R.id.circle_banner); TextView textTitle = (TextView) contents.findViewById(R.id.text_game_title); - TextView textDescription = (TextView) contents.findViewById(R.id.text_game_description); + TextView textDescription = (TextView) contents.findViewById(R.id.text_company); TextView textCountry = (TextView) contents.findViewById(R.id.text_country); TextView textDate = (TextView) contents.findViewById(R.id.text_date); ImageButton buttonLaunch = (ImageButton) contents.findViewById(R.id.button_launch); + int countryIndex = getArguments().getInt(ARGUMENT_GAME_COUNTRY); + String country = getResources().getStringArray(R.array.country_names)[countryIndex]; + textTitle.setText(getArguments().getString(ARGUMENT_GAME_TITLE)); textDescription.setText(getArguments().getString(ARGUMENT_GAME_DESCRIPTION)); - textCountry.setText(getArguments().getString(ARGUMENT_GAME_COUNTRY)); + textCountry.setText(country); textDate.setText(getArguments().getString(ARGUMENT_GAME_DATE)); buttonLaunch.setOnClickListener(new View.OnClickListener() { @@ -77,6 +79,8 @@ public class GameDetailsDialog extends DialogFragment Intent intent = new Intent(view.getContext(), EmulationActivity.class); intent.putExtra("SelectedGame", getArguments().getString(ARGUMENT_GAME_PATH)); + intent.putExtra("SelectedTitle", getArguments().getString(ARGUMENT_GAME_TITLE)); + startActivity(intent); } }); diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/EmulationFragment.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/EmulationFragment.java new file mode 100644 index 0000000000..7c83c9d977 --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/EmulationFragment.java @@ -0,0 +1,114 @@ +package org.dolphinemu.dolphinemu.fragments; + +import android.app.Fragment; +import android.content.SharedPreferences; +import android.os.Bundle; +import android.preference.PreferenceManager; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import org.dolphinemu.dolphinemu.BuildConfig; +import org.dolphinemu.dolphinemu.NativeLibrary; +import org.dolphinemu.dolphinemu.R; +import org.dolphinemu.dolphinemu.emulation.overlay.InputOverlay; + + +public final class EmulationFragment extends Fragment +{ + public static final String FRAGMENT_TAG = BuildConfig.APPLICATION_ID + ".emulation_fragment"; + + private static final String ARGUMENT_GAME_PATH = BuildConfig.APPLICATION_ID + ".game_path"; + + private SharedPreferences mPreferences; + + private InputOverlay mInputOverlay; + + public static EmulationFragment newInstance(String path) + { + EmulationFragment fragment = new EmulationFragment(); + + Bundle arguments = new Bundle(); + arguments.putString(ARGUMENT_GAME_PATH, path); + fragment.setArguments(arguments); + + return fragment; + } + + /** + * Initialize anything that doesn't depend on the layout / views in here. + */ + @Override + public void onCreate(Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + + mPreferences = PreferenceManager.getDefaultSharedPreferences(getActivity()); + } + + /** + * Initialize the UI and start emulation in here. + */ + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) + { + String path = getArguments().getString(ARGUMENT_GAME_PATH); + + View contents = inflater.inflate(R.layout.fragment_emulation, container, false); + + mInputOverlay = (InputOverlay) contents.findViewById(R.id.surface_input_overlay); + + NativeLibrary.SetFilename(path); + + + // If the input overlay was previously disabled, then don't show it. + if (!mPreferences.getBoolean("showInputOverlay", true)) + { + mInputOverlay.setVisibility(View.GONE); + } + + return contents; + } + + @Override + public void onStart() + { + super.onStart(); + NativeLibrary.UnPauseEmulation(); + } + + @Override + public void onStop() + { + super.onStop(); + NativeLibrary.PauseEmulation(); + } + + @Override + public void onDestroyView() + { + super.onDestroyView(); + NativeLibrary.StopEmulation(); + } + + 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(); + } +} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/FileListItem.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/FileListItem.java index b9c8a571cf..e6fdacc110 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/FileListItem.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/FileListItem.java @@ -13,7 +13,8 @@ public class FileListItem implements Comparable public static final int TYPE_FOLDER = 0; public static final int TYPE_GC = 1; public static final int TYPE_WII = 2; - public static final int TYPE_OTHER = 3; + public static final int TYPE_WII_WARE = 3; + public static final int TYPE_OTHER = 4; private int mType; private String mFilename; @@ -30,8 +31,6 @@ public class FileListItem implements Comparable } else { - String fileExtension = null; - int extensionStart = mPath.lastIndexOf('.'); if (extensionStart < 1) { @@ -40,7 +39,7 @@ public class FileListItem implements Comparable } else { - fileExtension = mPath.substring(extensionStart); + String fileExtension = mPath.substring(extensionStart); // The extensions we care about. Set allowedExtensions = new HashSet(Arrays.asList(".dff", ".dol", ".elf", ".gcm", ".gcz", ".iso", ".wad", ".wbfs")); @@ -48,7 +47,8 @@ public class FileListItem implements Comparable // Check that the file has an extension we care about before trying to read out of it. if (allowedExtensions.contains(fileExtension)) { - mType = NativeLibrary.IsWiiTitle(mPath) ? TYPE_WII : TYPE_GC; + // Add 1 because 0 = TYPE_FOLDER + mType = NativeLibrary.GetPlatform(mPath) + 1; } else { diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/Game.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/Game.java index 9d3e736841..ae52261b45 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/Game.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/Game.java @@ -1,23 +1,154 @@ package org.dolphinemu.dolphinemu.model; -public interface Game +import android.content.ContentValues; +import android.database.Cursor; + +import java.io.File; + +public final class Game { public static final int PLATFORM_GC = 0; public static final int PLATFORM_WII = 1; + public static final int PLATFORM_WII_WARE = 2; - public int getPlatform(); + // Copied from IVolume::ECountry. Update these if that is ever modified. + public static final int COUNTRY_EUROPE = 0; + public static final int COUNTRY_JAPAN = 1; + public static final int COUNTRY_USA = 2; + public static final int COUNTRY_AUSTRALIA = 3; + public static final int COUNTRY_FRANCE = 4; + public static final int COUNTRY_GERMANY = 5; + public static final int COUNTRY_ITALY = 6; + public static final int COUNTRY_KOREA = 7; + public static final int COUNTRY_NETHERLANDS = 8; + public static final int COUNTRY_RUSSIA = 9; + public static final int COUNTRY_SPAIN = 10; + public static final int COUNTRY_TAIWAN = 11; + public static final int COUNTRY_WORLD = 12; + public static final int COUNTRY_UNKNOWN = 13; - public String getDate(); + private static final String PATH_SCREENSHOT_FOLDER = "file:///sdcard/dolphin-emu/ScreenShots/"; - public String getTitle(); + private String mTitle; + private String mDescription; + private String mPath; + private String mGameId; + private String mScreenshotFolderPath; + private String mCompany; - public String getDescription(); + private int mPlatform; + private int mCountry; - public String getCountry(); + public Game(int platform, String title, String description, int country, String path, String gameId, String company) + { + mPlatform = platform; + mTitle = title; + mDescription = description; + mCountry = country; + mPath = path; + mGameId = gameId; + mCompany = company; + mScreenshotFolderPath = PATH_SCREENSHOT_FOLDER + getGameId() + "/"; + } - public String getPath(); + public int getPlatform() + { + return mPlatform; + } - public String getGameId(); + public String getTitle() + { + return mTitle; + } - public String getScreenPath(); + public String getDescription() + { + return mDescription; + } + + public String getCompany() + { + return mCompany; + } + + public int getCountry() + { + return mCountry; + } + + public String getPath() + { + return mPath; + } + + public String getGameId() + { + return mGameId; + } + + public String getScreenshotFolderPath() + { + return mScreenshotFolderPath; + } + + public String getScreenPath() + { + // Count how many screenshots are available, so we can use the most recent one. + File screenshotFolder = new File(mScreenshotFolderPath.substring(mScreenshotFolderPath.indexOf('s') - 1)); + int screenCount = 0; + + if (screenshotFolder.isDirectory()) + { + screenCount = screenshotFolder.list().length; + } + + String screenPath = mScreenshotFolderPath + + getGameId() + "-" + + screenCount + ".png"; + + return screenPath; + } + + public static ContentValues asContentValues(int platform, String title, String description, int country, String path, String gameId, String company) + { + ContentValues values = new ContentValues(); + + // TODO Come up with a way of finding the most recent screenshot that doesn't involve counting files + String screenshotFolderPath = PATH_SCREENSHOT_FOLDER + gameId + "/"; + + // Count how many screenshots are available, so we can use the most recent one. + File screenshotFolder = new File(screenshotFolderPath.substring(screenshotFolderPath.indexOf('s') - 1)); + int screenCount = 0; + + if (screenshotFolder.isDirectory()) + { + screenCount = screenshotFolder.list().length; + } + + String screenPath = screenshotFolderPath + + gameId + "-" + + screenCount + ".png"; + + values.put(GameDatabase.KEY_GAME_PLATFORM, platform); + values.put(GameDatabase.KEY_GAME_TITLE, title); + values.put(GameDatabase.KEY_GAME_DESCRIPTION, description); + values.put(GameDatabase.KEY_GAME_COUNTRY, company); + 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 static Game fromCursor(Cursor cursor) + { + return new Game(cursor.getInt(GameDatabase.GAME_COLUMN_PLATFORM), + 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)); + } } diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/GameDatabase.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/GameDatabase.java new file mode 100644 index 0000000000..3d597b7ac3 --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/GameDatabase.java @@ -0,0 +1,190 @@ +package org.dolphinemu.dolphinemu.model; + +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; +import android.util.Log; + +import org.dolphinemu.dolphinemu.NativeLibrary; + +import java.io.File; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +/** + * A helper class that provides several utilities simplifying interaction with + * the SQLite database. + */ +public final class GameDatabase extends SQLiteOpenHelper +{ + private static final int DB_VERSION = 1; + + public static final int COLUMN_DB_ID = 0; + + public static final int GAME_COLUMN_PATH = 1; + public static final int GAME_COLUMN_PLATFORM = 2; + public static final int GAME_COLUMN_TITLE = 3; + public static final int GAME_COLUMN_DESCRIPTION = 4; + public static final int GAME_COLUMN_COUNTRY = 5; + public static final int GAME_COLUMN_GAME_ID = 6; + public static final int GAME_COLUMN_COMPANY = 7; + public static final int GAME_COLUMN_SCREENSHOT_PATH = 8; + + 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_PLATFORM = "platform"; + 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 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_PLATFORM + 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_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.d("DolphinEmu", "GameDatabase - Creating database..."); + + Log.v("DolphinEmu", "Executing SQL: " + SQL_CREATE_GAMES); + database.execSQL(SQL_CREATE_GAMES); + + Log.v("DolphinEmu", "Executing SQL: " + SQL_CREATE_FOLDERS); + database.execSQL(SQL_CREATE_FOLDERS); + } + + @Override + public void onUpgrade(SQLiteDatabase database, int oldVersion, int newVersion) + { + Log.i("DolphinEmu", "Upgrading database from schema version " + oldVersion + " to " + newVersion); + + Log.v("DolphinEmu", "Executing SQL: " + SQL_DELETE_GAMES); + database.execSQL(SQL_DELETE_GAMES); + + Log.v("DolphinEmu", "Executing SQL: " + SQL_CREATE_GAMES); + database.execSQL(SQL_CREATE_GAMES); + + Log.v("DolphinEmu", "Re-scanning library with new schema."); + scanLibrary(database); + } + + public void scanLibrary(SQLiteDatabase database) + { + // TODO Before scanning known folders, go through the game table and remove any entries for which the file itself is missing. + + // Get a cursor listing all the folders the user has added to the library. + Cursor cursor = database.query(TABLE_NAME_FOLDERS, + null, // Get all columns. + null, // Get all rows. + null, + null, // No grouping. + null, + null); // Order of folders is irrelevant. + + Set allowedExtensions = new HashSet(Arrays.asList(".dff", ".dol", ".elf", ".gcm", ".gcz", ".iso", ".wad", ".wbfs")); + + // Possibly overly defensive, but ensures that moveToNext() does not skip a row. + cursor.moveToPosition(-1); + + // Iterate through all results of the DB query (i.e. all folders in the library.) + while (cursor.moveToNext()) + { + + String folderPath = cursor.getString(FOLDER_COLUMN_PATH); + File folder = new File(folderPath); + + Log.i("DolphinEmu", "Reading files from library folder: " + folderPath); + + // Iterate through every file in the folder. + File[] children = folder.listFiles(); + 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)) + { + ContentValues game = Game.asContentValues(NativeLibrary.GetPlatform(filePath), + NativeLibrary.GetTitle(filePath), + NativeLibrary.GetDescription(filePath).replace("\n", " "), + NativeLibrary.GetCountry(filePath), + filePath, + NativeLibrary.GetGameId(filePath), + 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.v("DolphinEmu", "Adding game: " + game.getAsString(KEY_GAME_TITLE)); + database.insert(TABLE_NAME_GAMES, null, game); + } + else + { + Log.v("DolphinEmu", "Updated game: " + game.getAsString(KEY_GAME_TITLE)); + } + } + } + } + } + } + + cursor.close(); + database.close(); + } +} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/GameProvider.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/GameProvider.java new file mode 100644 index 0000000000..702c180309 --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/GameProvider.java @@ -0,0 +1,138 @@ +package org.dolphinemu.dolphinemu.model; + +import android.content.ContentProvider; +import android.content.ContentValues; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.net.Uri; +import android.util.Log; + +import org.dolphinemu.dolphinemu.BuildConfig; + +/** + * Provides an interface allowing Activities to interact with the SQLite database. + * CRUD methods in this class can be called by Activities using getContentResolver(). + */ +public final class GameProvider extends ContentProvider +{ + public static final String AUTHORITY = "content://" + BuildConfig.APPLICATION_ID + ".provider"; + public static final Uri URI_FOLDER = Uri.parse(AUTHORITY + "/" + GameDatabase.TABLE_NAME_FOLDERS + "/"); + public static final Uri URI_GAME = Uri.parse(AUTHORITY + "/" + GameDatabase.TABLE_NAME_GAMES + "/"); + + 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"; + + private GameDatabase mDbHelper; + + @Override + public boolean onCreate() + { + Log.i("DolphinEmu", "Creating Content Provider..."); + + mDbHelper = new GameDatabase(getContext()); + + return true; + } + + @Override + public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) + { + Log.i("DolphinEmu", "Querying URI: " + uri); + + SQLiteDatabase db = mDbHelper.getReadableDatabase(); + + String table = uri.getLastPathSegment(); + + if (table == null) + { + Log.e("DolphinEmu", "Badly formatted URI: " + uri); + return null; + } + + Cursor cursor = db.query(table, projection, selection, selectionArgs, null, null, sortOrder); + cursor.setNotificationUri(getContext().getContentResolver(), uri); + + return cursor; + } + + @Override + public String getType(Uri uri) + { + Log.v("DolphinEmu", "Getting MIME type for URI: " + uri); + String lastSegment = uri.getLastPathSegment(); + + if (lastSegment == null) + { + Log.e("DolphinEmu", "Badly formatted URI: " + uri); + 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.e("DolphinEmu", "Unknown MIME type for URI: " + uri); + return null; + } + + @Override + public Uri insert(Uri uri, ContentValues values) + { + Log.i("DolphinEmu", "Inserting row at URI: " + uri); + + SQLiteDatabase database = mDbHelper.getWritableDatabase(); + String table = uri.getLastPathSegment(); + + long id = -1; + + if (table != null) + { + 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.e("DolphinEmu", "Row already exists: " + uri + " id: " + id); + } + } + else + { + Log.e("DolphinEmu", "Badly formatted URI: " + uri); + } + + database.close(); + + return uri; + } + + @Override + public int delete(Uri uri, String selection, String[] selectionArgs) + { + Log.e("DolphinEmu", "Delete operations unsupported. URI: " + uri); + return 0; + } + + @Override + public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) + { + Log.e("DolphinEmu", "Update operations unsupported. URI: " + uri); + return 0; + } +} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/GcGame.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/GcGame.java deleted file mode 100644 index d682bee0b6..0000000000 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/GcGame.java +++ /dev/null @@ -1,96 +0,0 @@ -package org.dolphinemu.dolphinemu.model; - - -import java.io.File; - -public final class GcGame implements Game -{ - private String mTitle; - private String mDescription; - private String mCountry; - private String mPath; - private String mGameId; - - private String mScreenshotFolderPath; - - private String mDate; - private int mPlatform = PLATFORM_GC; - - private static final String PATH_SCREENSHOT_FOLDER = "file:///sdcard/dolphin-emu/ScreenShots/"; - - public GcGame(String title, String description, String country, String path, String gameId, String date) - { - mTitle = title; - mDescription = description; - mCountry = country; - mPath = path; - mGameId = gameId; - mDate = date; - mScreenshotFolderPath = PATH_SCREENSHOT_FOLDER + getGameId() + "/"; - } - - @Override - public int getPlatform() - { - return mPlatform; - } - - @Override - public String getTitle() - { - return mTitle; - } - - @Override - public String getDescription() - { - return mDescription; - } - - @Override - public String getDate() - { - return mDate; - } - - @Override - public String getCountry() - { - return mCountry; - } - - @Override - public String getPath() - { - return mPath; - } - - public String getGameId() - { - return mGameId; - } - - public String getScreenshotFolderPath() - { - return mScreenshotFolderPath; - } - - @Override - public String getScreenPath() - { - // Count how many screenshots are available, so we can use the most recent one. - File screenshotFolder = new File(mScreenshotFolderPath.substring(mScreenshotFolderPath.indexOf('s') - 1)); - int screenCount = 0; - - if (screenshotFolder.isDirectory()) - { - screenCount = screenshotFolder.list().length; - } - - String screenPath = mScreenshotFolderPath - + getGameId() + "-" - + screenCount + ".png"; - - return screenPath; - } -} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/WiiGame.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/WiiGame.java deleted file mode 100644 index c64de8d582..0000000000 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/WiiGame.java +++ /dev/null @@ -1,53 +0,0 @@ -package org.dolphinemu.dolphinemu.model; - - -public final class WiiGame implements Game -{ - @Override - public int getPlatform() - { - return 0; - } - - @Override - public String getDate() - { - return null; - } - - @Override - public String getTitle() - { - return null; - } - - @Override - public String getDescription() - { - return null; - } - - @Override - public String getCountry() - { - return null; - } - - @Override - public String getPath() - { - return null; - } - - @Override - public String getGameId() - { - return null; - } - - @Override - public String getScreenPath() - { - return null; - } -} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/viewholders/FileViewHolder.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/viewholders/FileViewHolder.java index 79acc8400b..902e5299f4 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/viewholders/FileViewHolder.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/viewholders/FileViewHolder.java @@ -7,7 +7,10 @@ import android.widget.TextView; import org.dolphinemu.dolphinemu.R; - +/** + * A simple class that stores references to views so that the FileAdapter doesn't need to + * keep calling findViewById(), which is expensive. + */ public class FileViewHolder extends RecyclerView.ViewHolder { public View itemView; diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/viewholders/GameViewHolder.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/viewholders/GameViewHolder.java index 18e271e0f5..592a2ecf32 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/viewholders/GameViewHolder.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/viewholders/GameViewHolder.java @@ -1,29 +1,31 @@ package org.dolphinemu.dolphinemu.viewholders; -import android.app.Activity; -import android.content.Intent; import android.support.v7.widget.RecyclerView; import android.view.View; -import android.widget.ImageButton; import android.widget.ImageView; import android.widget.TextView; import org.dolphinemu.dolphinemu.R; -import org.dolphinemu.dolphinemu.dialogs.GameDetailsDialog; -import org.dolphinemu.dolphinemu.emulation.EmulationActivity; -import org.dolphinemu.dolphinemu.model.Game; - +/** + * A simple class that stores references to views so that the GameAdapter doesn't need to + * keep calling findViewById(), which is expensive. + */ public class GameViewHolder extends RecyclerView.ViewHolder { public ImageView imageScreenshot; public TextView textGameTitle; - public TextView textDescription; + public TextView textCompany; - // Used to handle onClick(). Set this in onBindViewHolder(). + public String gameId; + + // TODO Not need any of this stuff. Currently only the properties dialog needs it. public String path; + public String title; + public String description; + public int country; + public String company; public String screenshotPath; - public Game game; public GameViewHolder(View itemView) { @@ -33,6 +35,6 @@ public class GameViewHolder extends RecyclerView.ViewHolder imageScreenshot = (ImageView) itemView.findViewById(R.id.image_game_screen); textGameTitle = (TextView) itemView.findViewById(R.id.text_game_title); - textDescription = (TextView) itemView.findViewById(R.id.text_game_description); + textCompany = (TextView) itemView.findViewById(R.id.text_company); } } diff --git a/Source/Android/app/src/main/res/drawable-hdpi/ic_company.png b/Source/Android/app/src/main/res/drawable-hdpi/ic_company.png new file mode 100644 index 0000000000..69dba7ac8f Binary files /dev/null and b/Source/Android/app/src/main/res/drawable-hdpi/ic_company.png differ diff --git a/Source/Android/app/src/main/res/drawable-mdpi/ic_company.png b/Source/Android/app/src/main/res/drawable-mdpi/ic_company.png new file mode 100644 index 0000000000..481de5052d Binary files /dev/null and b/Source/Android/app/src/main/res/drawable-mdpi/ic_company.png differ diff --git a/Source/Android/app/src/main/res/drawable-xhdpi/ic_company.png b/Source/Android/app/src/main/res/drawable-xhdpi/ic_company.png new file mode 100644 index 0000000000..750d7ff388 Binary files /dev/null and b/Source/Android/app/src/main/res/drawable-xhdpi/ic_company.png differ diff --git a/Source/Android/app/src/main/res/drawable-xxhdpi/ic_company.png b/Source/Android/app/src/main/res/drawable-xxhdpi/ic_company.png new file mode 100644 index 0000000000..96fa6ac8f0 Binary files /dev/null and b/Source/Android/app/src/main/res/drawable-xxhdpi/ic_company.png differ diff --git a/Source/Android/app/src/main/res/drawable-xxxhdpi/ic_company.png b/Source/Android/app/src/main/res/drawable-xxxhdpi/ic_company.png new file mode 100644 index 0000000000..967aa1fbe2 Binary files /dev/null and b/Source/Android/app/src/main/res/drawable-xxxhdpi/ic_company.png differ diff --git a/Source/Android/app/src/main/res/drawable/oval_ripple_wii.xml b/Source/Android/app/src/main/res/drawable/oval_ripple_accent.xml similarity index 73% rename from Source/Android/app/src/main/res/drawable/oval_ripple_wii.xml rename to Source/Android/app/src/main/res/drawable/oval_ripple_accent.xml index 644876eda3..fbdfec87f5 100644 --- a/Source/Android/app/src/main/res/drawable/oval_ripple_wii.xml +++ b/Source/Android/app/src/main/res/drawable/oval_ripple_accent.xml @@ -2,7 +2,7 @@ android:color="?android:colorControlHighlight"> - + \ No newline at end of file diff --git a/Source/Android/app/src/main/res/drawable/oval_ripple_gc.xml b/Source/Android/app/src/main/res/drawable/oval_ripple_gc.xml deleted file mode 100644 index 167d3ef13a..0000000000 --- a/Source/Android/app/src/main/res/drawable/oval_ripple_gc.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/Source/Android/app/src/main/res/layout/activity_emulation.xml b/Source/Android/app/src/main/res/layout/activity_emulation.xml new file mode 100644 index 0000000000..63ea99ba83 --- /dev/null +++ b/Source/Android/app/src/main/res/layout/activity_emulation.xml @@ -0,0 +1,5 @@ + + \ No newline at end of file diff --git a/Source/Android/app/src/main/res/layout/activity_game_grid.xml b/Source/Android/app/src/main/res/layout/activity_game_grid.xml index 2dc6416a94..53ac5d614e 100644 --- a/Source/Android/app/src/main/res/layout/activity_game_grid.xml +++ b/Source/Android/app/src/main/res/layout/activity_game_grid.xml @@ -9,7 +9,7 @@ android:id="@+id/toolbar_game_list" android:layout_width="match_parent" android:layout_height="wrap_content" - android:background="@color/dolphin_blue" + android:background="?android:colorPrimary" android:minHeight="?android:attr/actionBarSize" android:theme="@android:style/ThemeOverlay.Material.Dark.ActionBar" android:elevation="6dp"/> @@ -33,7 +33,7 @@ android:layout_alignBottom="@+id/image_game_screen" android:layout_alignEnd="@+id/text_game_title" android:layout_marginBottom="28dp" - android:background="@drawable/oval_ripple_gc" + android:background="@drawable/oval_ripple_accent" android:src="@drawable/ic_add" android:stateListAnimator="@anim/button_elevation" android:elevation="4dp" diff --git a/Source/Android/app/src/main/res/layout/card_game.xml b/Source/Android/app/src/main/res/layout/card_game.xml index dc8321f8f8..b18bc9e7ee 100644 --- a/Source/Android/app/src/main/res/layout/card_game.xml +++ b/Source/Android/app/src/main/res/layout/card_game.xml @@ -40,7 +40,7 @@ tools:text="The Legend of Zelda: The Wind Waker"/> + tools:text="Nintendo"/> diff --git a/Source/Android/app/src/main/res/layout/dialog_game_details.xml b/Source/Android/app/src/main/res/layout/dialog_game_details.xml index b431991baf..1d7b63468b 100644 --- a/Source/Android/app/src/main/res/layout/dialog_game_details.xml +++ b/Source/Android/app/src/main/res/layout/dialog_game_details.xml @@ -18,7 +18,7 @@ android:layout_marginLeft="16dp" android:layout_marginTop="24dp" tools:src="@drawable/placeholder_banner" - app:border_color="#ffcccccc" + app:border_color="?android:colorAccent" app:border_width="2dp" /> @@ -32,6 +32,7 @@ android:layout_alignParentStart="true" android:layout_alignParentTop="true" android:transitionName="image_game_screen" + tools:scaleType="centerCrop" tools:src="@drawable/placeholder_screenshot"/> @@ -69,7 +67,7 @@ android:layout_height="1dp" android:layout_alignParentLeft="true" android:layout_alignParentRight="true" - android:layout_below="@+id/text_game_description" + android:layout_below="@+id/text_company" android:layout_marginTop="16dp" android:background="#1F000000"/> @@ -85,14 +83,14 @@ android:src="@drawable/ic_country"/> + tools:text="Nintendo"/> diff --git a/Source/Android/app/src/main/res/layout/fragment_emulation.xml b/Source/Android/app/src/main/res/layout/fragment_emulation.xml new file mode 100644 index 0000000000..6a936dae64 --- /dev/null +++ b/Source/Android/app/src/main/res/layout/fragment_emulation.xml @@ -0,0 +1,22 @@ + + + + + + + + diff --git a/Source/Android/app/src/main/res/menu/menu_emulation.xml b/Source/Android/app/src/main/res/menu/menu_emulation.xml new file mode 100644 index 0000000000..627d1bc9e3 --- /dev/null +++ b/Source/Android/app/src/main/res/menu/menu_emulation.xml @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Source/Android/app/src/main/res/values/arrays.xml b/Source/Android/app/src/main/res/values/arrays.xml index 4ada90cb1f..9cdf07d876 100644 --- a/Source/Android/app/src/main/res/values/arrays.xml +++ b/Source/Android/app/src/main/res/values/arrays.xml @@ -190,4 +190,20 @@ 2 3 + + + Europe + Japan + USA + Australia + France + Germany + Italy + Korea + Netherlands + Russia + Spain + Taiwan + Unknown + diff --git a/Source/Android/app/src/main/res/values/strings.xml b/Source/Android/app/src/main/res/values/strings.xml index 07e5ab3690..47fefbaf20 100644 --- a/Source/Android/app/src/main/res/values/strings.xml +++ b/Source/Android/app/src/main/res/values/strings.xml @@ -236,4 +236,7 @@ CPU Settings Input Settings Video Settings + Emulation Activity + + Toggle Input Overlay diff --git a/Source/Android/app/src/main/res/values/styles.xml b/Source/Android/app/src/main/res/values/styles.xml index efc39de62c..bec7fc0035 100644 --- a/Source/Android/app/src/main/res/values/styles.xml +++ b/Source/Android/app/src/main/res/values/styles.xml @@ -22,23 +22,19 @@ @@ -46,4 +42,42 @@ @color/dolphin_accent_wiiware + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Source/Core/DolphinWX/MainAndroid.cpp b/Source/Core/DolphinWX/MainAndroid.cpp index 50aab79f45..c274deb5fb 100644 --- a/Source/Core/DolphinWX/MainAndroid.cpp +++ b/Source/Core/DolphinWX/MainAndroid.cpp @@ -21,7 +21,7 @@ #include #include #include - +#include "../DiscIO/Volume.h" #include "Android/ButtonManager.h" #include "Common/CommonPaths.h" #include "Common/CommonTypes.h" @@ -161,21 +161,52 @@ static bool LoadBanner(std::string filename, u32 *Banner) return false; } -static bool IsWiiTitle(std::string filename) +static int GetCountry(std::string filename) { std::unique_ptr pVolume(DiscIO::CreateVolumeFromFilename(filename)); if (pVolume != nullptr) { - bool is_wii_title = pVolume->IsWiiDisc() || pVolume->IsWadFile(); + DiscIO::IVolume::ECountry country = pVolume->GetCountry(); - __android_log_print(ANDROID_LOG_INFO, DOLPHIN_TAG, "Is %s a Wii Disc: %s", filename.c_str(), is_wii_title ? "Yes" : "No" ); + __android_log_print(ANDROID_LOG_INFO, DOLPHIN_TAG, "Country Code: %i", country); - return is_wii_title; + return country; } - // Technically correct. - return false; + // Return UNKNOWN + return 13; +} + +static int GetPlatform(std::string filename) +{ + std::unique_ptr pVolume(DiscIO::CreateVolumeFromFilename(filename)); + + if (pVolume != nullptr) + { + bool is_wii_disc = pVolume->IsWiiDisc(); + bool is_wii_wad = pVolume->IsWadFile(); + + if (is_wii_disc) + { + __android_log_print(ANDROID_LOG_INFO, DOLPHIN_TAG, "Volume is a Wii disc."); + + // See Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/Game.java + return 1; + } + else if (is_wii_wad) + { + __android_log_print(ANDROID_LOG_INFO, DOLPHIN_TAG, "Volume is a Wii WAD."); + return 2; + } + else + { + __android_log_print(ANDROID_LOG_INFO, DOLPHIN_TAG, "Volume is a Gamecube disc."); + return 0; + } + } + + return -1; } static std::string GetTitle(std::string filename) @@ -274,15 +305,15 @@ static std::string GetGameId(std::string filename) return std::string (""); } -static std::string GetApploaderDate(std::string filename) +static std::string GetCompany(std::string filename) { - __android_log_print(ANDROID_LOG_WARN, DOLPHIN_TAG, "Getting Date for file: %s", filename.c_str()); + __android_log_print(ANDROID_LOG_WARN, DOLPHIN_TAG, "Getting Company for file: %s", filename.c_str()); DiscIO::IVolume* pVolume = DiscIO::CreateVolumeFromFilename(filename); if (pVolume != nullptr) { - std::string date = pVolume->GetApploaderDate(); - __android_log_print(ANDROID_LOG_INFO, DOLPHIN_TAG, "Date: %s", date.c_str()); + std::string date = pVolume->GetCompany(); + __android_log_print(ANDROID_LOG_INFO, DOLPHIN_TAG, "Company: %s", date.c_str()); return date; } @@ -297,7 +328,8 @@ static u64 GetFileSize(std::string filename) if (pVolume != nullptr) { u64 size = pVolume->GetSize(); - __android_log_print(ANDROID_LOG_INFO, DOLPHIN_TAG, "Size: %lu", size); + // Causes a warning because size is u64, not 'long unsigned' + //__android_log_print(ANDROID_LOG_INFO, DOLPHIN_TAG, "Size: %lu", size); return size; } @@ -330,9 +362,10 @@ JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_onGamePadMov JNIEXPORT jintArray JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_GetBanner(JNIEnv *env, jobject obj, jstring jFile);JNIEXPORT jstring JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_GetTitle(JNIEnv *env, jobject obj, jstring jFilename); JNIEXPORT jstring JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_GetDescription(JNIEnv *env, jobject obj, jstring jFilename); JNIEXPORT jstring JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_GetGameId(JNIEnv *env, jobject obj, jstring jFilename); -JNIEXPORT jstring JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_GetDate(JNIEnv *env, jobject obj, jstring jFilename); +JNIEXPORT jint JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_GetCountry(JNIEnv *env, jobject obj, jstring jFilename); +JNIEXPORT jstring JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_GetCompany(JNIEnv *env, jobject obj, jstring jFilename); JNIEXPORT jlong JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_GetFilesize(JNIEnv *env, jobject obj, jstring jFilename); -JNIEXPORT jboolean JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_IsWiiTitle(JNIEnv *env, jobject obj, jstring jFilename); +JNIEXPORT jint JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_GetPlatform(JNIEnv *env, jobject obj, jstring jFilename); JNIEXPORT jstring JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_GetVersionString(JNIEnv *env, jobject obj); JNIEXPORT jboolean JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_SupportsNEON(JNIEnv *env, jobject obj); JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_SaveScreenShot(JNIEnv *env, jobject obj); @@ -404,11 +437,18 @@ JNIEXPORT jstring JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_GetGameId return env->NewStringUTF(id.c_str()); } -JNIEXPORT jstring JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_GetDate(JNIEnv *env, jobject obj, jstring jFilename) +JNIEXPORT jstring JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_GetCompany(JNIEnv *env, jobject obj, jstring jFilename) { std::string filename = GetJString(env, jFilename); - std::string date = GetApploaderDate(filename); - return env->NewStringUTF(date.c_str()); + std::string company = GetCompany(filename); + return env->NewStringUTF(company.c_str()); +} + +JNIEXPORT jint JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_GetCountry(JNIEnv *env, jobject obj, jstring jFilename) +{ + std::string filename = GetJString(env, jFilename); + int country = GetCountry(filename); + return country; } JNIEXPORT jlong JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_GetFilesize(JNIEnv *env, jobject obj, jstring jFilename) @@ -418,11 +458,11 @@ JNIEXPORT jlong JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_GetFilesize return size; } -JNIEXPORT jboolean JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_IsWiiTitle(JNIEnv *env, jobject obj, jstring jFilename) +JNIEXPORT jint JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_GetPlatform(JNIEnv *env, jobject obj, jstring jFilename) { std::string filename = GetJString(env, jFilename); - bool wiiDisc = IsWiiTitle(filename); - return wiiDisc; + int platform = GetPlatform(filename); + return platform; } JNIEXPORT jstring JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_GetVersionString(JNIEnv *env, jobject obj)