diff --git a/Source/Android/app/src/main/AndroidManifest.xml b/Source/Android/app/src/main/AndroidManifest.xml
index 5d817d220a..aa0b121c5c 100644
--- a/Source/Android/app/src/main/AndroidManifest.xml
+++ b/Source/Android/app/src/main/AndroidManifest.xml
@@ -71,13 +71,7 @@
-
-
-
+
implements
View.OnClickListener,
View.OnLongClickListener
{
- private Cursor mCursor;
- private GameDataSetObserver mObserver;
-
- private boolean mDatasetValid;
+ private List mGameFiles;
/**
* 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 swapDataSet is called.
*/
public GameAdapter()
{
- mDatasetValid = false;
- mObserver = new GameDataSetObserver();
+ mGameFiles = new ArrayList<>();
}
/**
@@ -80,34 +70,13 @@ public final class GameAdapter extends RecyclerView.Adapter impl
@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));
+ GameFile gameFile = mGameFiles.get(position);
+ PicassoUtils.loadGameBanner(holder.imageScreenshot, gameFile);
- holder.textGameTitle.setText(mCursor.getString(GameDatabase.GAME_COLUMN_TITLE));
- holder.textCompany.setText(mCursor.getString(GameDatabase.GAME_COLUMN_COMPANY));
+ holder.textGameTitle.setText(gameFile.getTitle());
+ holder.textCompany.setText(gameFile.getCompany());
- // 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.");
- }
+ holder.gameFile = gameFile;
}
/**
@@ -118,84 +87,27 @@ public final class GameAdapter extends RecyclerView.Adapter impl
@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;
+ return mGameFiles.size();
}
/**
* 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);
+ super.setHasStableIds(false);
}
/**
- * When a load is finished, call this to replace the existing data with the newly-loaded
- * data.
- *
- * @param cursor The newly-loaded Cursor.
+ * When a load is finished, call this to replace the existing data
+ * with the newly-loaded data.
*/
- public void swapCursor(Cursor cursor)
+ public void swapDataSet(List gameFiles)
{
- // 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;
- }
-
+ mGameFiles = gameFiles;
notifyDataSetChanged();
}
@@ -210,9 +122,7 @@ public final class GameAdapter extends RecyclerView.Adapter impl
GameViewHolder holder = (GameViewHolder) view.getTag();
EmulationActivity.launch((FragmentActivity) view.getContext(),
- holder.path,
- holder.title,
- holder.screenshotPath,
+ holder.gameFile,
holder.getAdapterPosition(),
holder.imageScreenshot);
}
@@ -227,13 +137,16 @@ public final class GameAdapter extends RecyclerView.Adapter impl
public boolean onLongClick(View view)
{
GameViewHolder holder = (GameViewHolder) view.getTag();
+ String gameId = holder.gameFile.getGameId();
- // Get the ID of the game we want to look at.
- String gameId = (String) holder.gameId;
+ if (gameId.isEmpty())
+ {
+ // We can't make a game-specific INI file if there is no game ID
+ return true;
+ }
FragmentActivity activity = (FragmentActivity) view.getContext();
-
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
builder.setTitle("Game Settings")
.setItems(R.array.gameSettingsMenus, new DialogInterface.OnClickListener() {
@@ -290,25 +203,4 @@ public final class GameAdapter extends RecyclerView.Adapter impl
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();
- }
- }
}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/GameRowPresenter.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/GameRowPresenter.java
index 81234904da..d12ce579c1 100644
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/GameRowPresenter.java
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/GameRowPresenter.java
@@ -14,8 +14,9 @@ import android.widget.ImageView;
import android.widget.Toast;
import org.dolphinemu.dolphinemu.R;
-import org.dolphinemu.dolphinemu.model.Game;
+import org.dolphinemu.dolphinemu.model.GameFile;
import org.dolphinemu.dolphinemu.services.DirectoryInitializationService;
+import org.dolphinemu.dolphinemu.ui.platform.Platform;
import org.dolphinemu.dolphinemu.ui.settings.SettingsActivity;
import org.dolphinemu.dolphinemu.utils.PicassoUtils;
import org.dolphinemu.dolphinemu.utils.SettingsFile;
@@ -50,28 +51,19 @@ public final class GameRowPresenter extends Presenter
public void onBindViewHolder(ViewHolder viewHolder, Object item)
{
TvGameViewHolder holder = (TvGameViewHolder) viewHolder;
- Game game = (Game) item;
-
- String screenPath = game.getScreenshotPath();
+ GameFile gameFile = (GameFile) item;
holder.imageScreenshot.setImageDrawable(null);
- PicassoUtils.loadGameBanner(holder.imageScreenshot, screenPath, game.getPath());
+ PicassoUtils.loadGameBanner(holder.imageScreenshot, gameFile);
- holder.cardParent.setTitleText(game.getTitle());
- holder.cardParent.setContentText(game.getCompany());
+ holder.cardParent.setTitleText(gameFile.getTitle());
+ holder.cardParent.setContentText(gameFile.getCompany());
- // TODO These shouldn't be necessary once the move to a DB-based model is complete.
- holder.gameId = game.getGameId();
- holder.path = game.getPath();
- holder.title = game.getTitle();
- holder.description = game.getDescription();
- holder.country = game.getCountry();
- holder.company = game.getCompany();
- holder.screenshotPath = game.getScreenshotPath();
+ holder.gameFile = gameFile;
// Set the platform-dependent background color of the card
int backgroundId;
- switch (game.getPlatform())
+ switch (Platform.fromNativeInt(gameFile.getPlatform()))
{
case GAMECUBE:
backgroundId = R.drawable.tv_card_background_gamecube;
@@ -93,7 +85,13 @@ public final class GameRowPresenter extends Presenter
public boolean onLongClick(View view)
{
FragmentActivity activity = (FragmentActivity) view.getContext();
+ String gameId = gameFile.getGameId();
+ if (gameId.isEmpty())
+ {
+ // We can't make a game-specific INI file if there is no game ID
+ return true;
+ }
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
builder.setTitle("Game Settings")
@@ -101,23 +99,23 @@ public final class GameRowPresenter extends Presenter
public void onClick(DialogInterface dialog, int which) {
switch (which) {
case 0:
- SettingsActivity.launch(activity, SettingsFile.FILE_NAME_DOLPHIN, game.getGameId());
+ SettingsActivity.launch(activity, SettingsFile.FILE_NAME_DOLPHIN, gameId);
break;
case 1:
- SettingsActivity.launch(activity, SettingsFile.FILE_NAME_GFX, game.getGameId());
+ SettingsActivity.launch(activity, SettingsFile.FILE_NAME_GFX, gameId);
break;
case 2:
- String path = DirectoryInitializationService.getUserDirectory() + "/GameSettings/" + game.getGameId() + ".ini";
+ String path = DirectoryInitializationService.getUserDirectory() + "/GameSettings/" + gameId + ".ini";
File gameSettingsFile = new File(path);
if (gameSettingsFile.exists())
{
if (gameSettingsFile.delete())
{
- Toast.makeText(view.getContext(), "Cleared settings for " + game.getGameId(), Toast.LENGTH_SHORT).show();
+ Toast.makeText(view.getContext(), "Cleared settings for " + gameId, Toast.LENGTH_SHORT).show();
}
else
{
- Toast.makeText(view.getContext(), "Unable to clear settings for " + game.getGameId(), Toast.LENGTH_SHORT).show();
+ Toast.makeText(view.getContext(), "Unable to clear settings for " + gameId, Toast.LENGTH_SHORT).show();
}
}
else
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 b3dc13de22..718c03152d 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
@@ -12,32 +12,24 @@ import android.widget.TextView;
import com.squareup.picasso.Picasso;
+import org.dolphinemu.dolphinemu.DolphinApplication;
import org.dolphinemu.dolphinemu.R;
import org.dolphinemu.dolphinemu.activities.EmulationActivity;
+import org.dolphinemu.dolphinemu.model.GameFile;
+import org.dolphinemu.dolphinemu.services.GameFileCacheService;
import de.hdodenhof.circleimageview.CircleImageView;
public final class GameDetailsDialog extends DialogFragment
{
- private static final String ARG_GAME_TITLE = "game_title";
- private static final String ARG_GAME_DESCRIPTION = "game_description";
- private static final String ARG_GAME_COUNTRY = "game_country";
- private static final String ARG_GAME_DATE = "game_date";
private static final String ARG_GAME_PATH = "game_path";
- private static final String ARG_GAME_SCREENSHOT_PATH = "game_screenshot_path";
- // 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)
+ public static GameDetailsDialog newInstance(String gamePath)
{
GameDetailsDialog fragment = new GameDetailsDialog();
Bundle arguments = new Bundle();
- arguments.putString(ARG_GAME_TITLE, title);
- arguments.putString(ARG_GAME_DESCRIPTION, description);
- arguments.putInt(ARG_GAME_COUNTRY, country);
- arguments.putString(ARG_GAME_DATE, company);
- arguments.putString(ARG_GAME_PATH, path);
- arguments.putString(ARG_GAME_SCREENSHOT_PATH, screenshotPath);
+ arguments.putString(ARG_GAME_PATH, gamePath);
fragment.setArguments(arguments);
return fragment;
@@ -46,42 +38,38 @@ public final class GameDetailsDialog extends DialogFragment
@Override
public Dialog onCreateDialog(Bundle savedInstanceState)
{
+ GameFile gameFile = GameFileCacheService.addOrGet(getArguments().getString(ARG_GAME_PATH));
+
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
ViewGroup contents = (ViewGroup) getActivity().getLayoutInflater().inflate(R.layout.dialog_game_details, null);
- final ImageView imageGameScreen = (ImageView) contents.findViewById(R.id.image_game_screen);
- CircleImageView circleBanner = (CircleImageView) contents.findViewById(R.id.circle_banner);
+ final ImageView imageGameScreen = contents.findViewById(R.id.image_game_screen);
+ CircleImageView circleBanner = contents.findViewById(R.id.circle_banner);
- TextView textTitle = (TextView) contents.findViewById(R.id.text_game_title);
- TextView textDescription = (TextView) contents.findViewById(R.id.text_company);
+ TextView textTitle = contents.findViewById(R.id.text_game_title);
+ TextView textDescription = contents.findViewById(R.id.text_description);
- TextView textCountry = (TextView) contents.findViewById(R.id.text_country);
- TextView textDate = (TextView) contents.findViewById(R.id.text_date);
+ TextView textCountry = contents.findViewById(R.id.text_country);
+ TextView textCompany = contents.findViewById(R.id.text_company);
- FloatingActionButton buttonLaunch = (FloatingActionButton) contents.findViewById(R.id.button_launch);
+ FloatingActionButton buttonLaunch = contents.findViewById(R.id.button_launch);
- int countryIndex = getArguments().getInt(ARG_GAME_COUNTRY);
- String country = getResources().getStringArray(R.array.countryNames)[countryIndex];
+ String country = getResources().getStringArray(R.array.countryNames)[gameFile.getCountry()];
- textTitle.setText(getArguments().getString(ARG_GAME_TITLE));
- textDescription.setText(getArguments().getString(ARG_GAME_DESCRIPTION));
+ textTitle.setText(gameFile.getTitle());
+ textDescription.setText(gameFile.getDescription());
textCountry.setText(country);
- textDate.setText(getArguments().getString(ARG_GAME_DATE));
+ textCompany.setText(gameFile.getCompany());
buttonLaunch.setOnClickListener(view ->
{
// Start the emulation activity and send the path of the clicked ROM to it.
- EmulationActivity.launch(getActivity(),
- getArguments().getString(ARG_GAME_PATH),
- getArguments().getString(ARG_GAME_TITLE),
- getArguments().getString(ARG_GAME_SCREENSHOT_PATH),
- -1,
- imageGameScreen);
+ EmulationActivity.launch(getActivity(), gameFile, -1, imageGameScreen);
});
// Fill in the view contents.
Picasso.with(imageGameScreen.getContext())
- .load(getArguments().getString(ARG_GAME_SCREENSHOT_PATH))
+ .load(getArguments().getString(gameFile.getScreenshotPath()))
.fit()
.centerCrop()
.noFade()
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
deleted file mode 100644
index 4460a1e155..0000000000
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/FileListItem.java
+++ /dev/null
@@ -1,95 +0,0 @@
-package org.dolphinemu.dolphinemu.model;
-
-import org.dolphinemu.dolphinemu.NativeLibrary;
-
-import java.io.File;
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.Set;
-
-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_WII_WARE = 3;
- public static final int TYPE_OTHER = 4;
-
- private int mType;
- private String mFilename;
- private String mPath;
-
- public FileListItem(File file)
- {
- mPath = file.getAbsolutePath();
- mFilename = file.getName();
-
- if (file.isDirectory())
- {
- mType = TYPE_FOLDER;
- }
- else
- {
- int extensionStart = mPath.lastIndexOf('.');
- if (extensionStart < 1)
- {
- // Ignore hidden files & files without extensions.
- mType = TYPE_OTHER;
- }
- else
- {
- String fileExtension = mPath.substring(extensionStart);
-
- // The extensions we care about.
- Set allowedExtensions = new HashSet(Arrays.asList(
- ".ciso", ".dff", ".dol", ".elf", ".gcm", ".gcz", ".iso", ".tgc", ".wad", ".wbfs"));
-
- // Check that the file has an extension we care about before trying to read out of it.
- if (allowedExtensions.contains(fileExtension.toLowerCase()))
- {
- // Add 1 because 0 = TYPE_FOLDER
- mType = NativeLibrary.GetPlatform(mPath) + 1;
- }
- else
- {
- mType = TYPE_OTHER;
- }
- }
- }
- }
-
- public int getType()
- {
- return mType;
- }
-
- public String getFilename()
- {
- return mFilename;
- }
-
- public String getPath()
- {
- return mPath;
- }
-
- @Override
- public int compareTo(FileListItem theOther)
- {
- if (theOther.getType() == getType())
- {
- return getFilename().toLowerCase().compareTo(theOther.getFilename().toLowerCase());
- }
- else
- {
- if (getType() > theOther.getType())
- {
- return 1;
- }
- else
- {
- return -1;
- }
- }
- }
-}
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
deleted file mode 100644
index c38bcef2e1..0000000000
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/Game.java
+++ /dev/null
@@ -1,120 +0,0 @@
-package org.dolphinemu.dolphinemu.model;
-
-import android.content.ContentValues;
-import android.database.Cursor;
-import android.os.Environment;
-
-import org.dolphinemu.dolphinemu.ui.platform.Platform;
-
-public final class Game
-{
- // 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;
-
- private static final String PATH_SCREENSHOT_FOLDER = "file://" + Environment.getExternalStorageDirectory().getPath() + "/dolphin-emu/ScreenShots/";
-
- private String mTitle;
- private String mDescription;
- private String mPath;
- private String mGameId;
- private String mScreenshotPath;
- private String mCompany;
-
- private Platform mPlatform;
- private int mCountry;
-
- public Game(Platform platform, String title, String description, int country, String path, String gameId, String company, String screenshotPath)
- {
- mPlatform = platform;
- mTitle = title;
- mDescription = description;
- mCountry = country;
- mPath = path;
- mGameId = gameId;
- mCompany = company;
- mScreenshotPath = screenshotPath;
- }
-
- public Platform getPlatform()
- {
- return mPlatform;
- }
-
- public String getTitle()
- {
- return mTitle;
- }
-
- 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 getScreenshotPath()
- {
- return mScreenshotPath;
- }
-
- public static ContentValues asContentValues(Platform platform, String title, String description, int country, String path, String gameId, String company)
- {
- ContentValues values = new ContentValues();
-
- String screenPath = PATH_SCREENSHOT_FOLDER + gameId + "/" + gameId + "-1.png";
-
- values.put(GameDatabase.KEY_GAME_PLATFORM, platform.toInt());
- 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(Platform.fromInt(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),
- cursor.getString(GameDatabase.GAME_COLUMN_SCREENSHOT_PATH));
- }
-}
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
deleted file mode 100644
index 41181f4183..0000000000
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/GameDatabase.java
+++ /dev/null
@@ -1,295 +0,0 @@
-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 org.dolphinemu.dolphinemu.NativeLibrary;
-import org.dolphinemu.dolphinemu.ui.platform.Platform;
-import org.dolphinemu.dolphinemu.utils.Log;
-
-import java.io.File;
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.Set;
-
-import rx.Observable;
-import rx.Subscriber;
-
-/**
- * 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_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))});
- }
- }
-
-
- // Get a cursor listing all the folders the user has added to the library.
- Cursor folderCursor = 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(
- ".ciso", ".dff", ".dol", ".elf", ".gcm", ".gcz", ".iso", ".tgc", ".wad", ".wbfs"));
-
- // 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("."));
- }
-
- Platform platform = Platform.fromNativeInt(NativeLibrary.GetPlatform(filePath));
-
- ContentValues game = Game.asContentValues(platform,
- 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();
- }
-
- public Observable getGamesForPlatform(final Platform platform)
- {
- return Observable.create(subscriber ->
- {
- Log.info("[GameDatabase] Reading games list...");
-
- String[] whereArgs = new String[]{Integer.toString(platform.toInt())};
-
- SQLiteDatabase database = getReadableDatabase();
- Cursor resultCursor = database.query(
- TABLE_NAME_GAMES,
- null,
- KEY_GAME_PLATFORM + " = ?",
- whereArgs,
- null,
- null,
- KEY_GAME_TITLE + " ASC"
- );
-
- // Pass the result cursor to the consumer.
- subscriber.onNext(resultCursor);
-
- // Tell the consumer we're done; it will unsubscribe implicitly.
- subscriber.onCompleted();
- });
- }
-
- private void execSqlAndLog(SQLiteDatabase database, String sql)
- {
- Log.verbose("[GameDatabase] Executing SQL: " + sql);
- database.execSQL(sql);
- }
-}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/GameFile.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/GameFile.java
new file mode 100644
index 0000000000..7e8628d4d2
--- /dev/null
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/GameFile.java
@@ -0,0 +1,34 @@
+package org.dolphinemu.dolphinemu.model;
+
+import android.os.Environment;
+
+public class GameFile
+{
+ private long mPointer; // Do not rename or move without editing the native code
+
+ private GameFile(long pointer)
+ {
+ mPointer = pointer;
+ }
+
+ @Override
+ public native void finalize();
+
+ public native int getPlatform();
+ public native String getTitle();
+ public native String getDescription();
+ public native String getCompany();
+ public native int getCountry();
+ public native String getPath();
+ public native String getGameId();
+ public native int[] getBanner();
+ public native int getBannerWidth();
+ public native int getBannerHeight();
+
+ public String getScreenshotPath()
+ {
+ String gameId = getGameId();
+ return "file://" + Environment.getExternalStorageDirectory().getPath() +
+ "/dolphin-emu/ScreenShots/" + gameId + "/" + gameId + "-1.png";
+ }
+}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/GameFileCache.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/GameFileCache.java
new file mode 100644
index 0000000000..6d8cdaa718
--- /dev/null
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/GameFileCache.java
@@ -0,0 +1,89 @@
+package org.dolphinemu.dolphinemu.model;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.preference.PreferenceManager;
+
+import java.io.File;
+import java.util.HashSet;
+import java.util.Set;
+
+public class GameFileCache
+{
+ private static final String GAME_FOLDER_PATHS_PREFERENCE = "gameFolderPaths";
+ private static final Set EMPTY_SET = new HashSet<>();
+
+ private long mPointer; // Do not rename or move without editing the native code
+
+ public GameFileCache(String path)
+ {
+ mPointer = newGameFileCache(path);
+ }
+
+ private static native long newGameFileCache(String path);
+
+ @Override
+ public native void finalize();
+
+ public static void addGameFolder(String path, Context context)
+ {
+ SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
+ Set folderPaths = preferences.getStringSet(GAME_FOLDER_PATHS_PREFERENCE, EMPTY_SET);
+ Set newFolderPaths = new HashSet<>(folderPaths);
+ newFolderPaths.add(path);
+ SharedPreferences.Editor editor = preferences.edit();
+ editor.putStringSet(GAME_FOLDER_PATHS_PREFERENCE, newFolderPaths);
+ editor.apply();
+ }
+
+ private void removeNonExistentGameFolders(Context context)
+ {
+ SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
+ Set folderPaths = preferences.getStringSet(GAME_FOLDER_PATHS_PREFERENCE, EMPTY_SET);
+ Set newFolderPaths = new HashSet<>();
+ for (String folderPath : folderPaths)
+ {
+ File folder = new File(folderPath);
+ if (folder.exists())
+ {
+ newFolderPaths.add(folderPath);
+ }
+ }
+
+ if (folderPaths.size() != newFolderPaths.size())
+ {
+ // One or more folders are being deleted
+ SharedPreferences.Editor editor = preferences.edit();
+ editor.putStringSet(GAME_FOLDER_PATHS_PREFERENCE, newFolderPaths);
+ editor.apply();
+ }
+ }
+
+ /**
+ * Scans through the file system and updates the cache to match.
+ * @return true if the cache was modified
+ */
+ public boolean scanLibrary(Context context)
+ {
+ removeNonExistentGameFolders(context);
+
+ SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
+ Set folderPathsSet = preferences.getStringSet(GAME_FOLDER_PATHS_PREFERENCE, EMPTY_SET);
+ String[] folderPaths = folderPathsSet.toArray(new String[folderPathsSet.size()]);
+
+ boolean cacheChanged = update(folderPaths);
+ cacheChanged |= updateAdditionalMetadata();
+ if (cacheChanged)
+ {
+ save();
+ }
+ return cacheChanged;
+ }
+
+ public native GameFile[] getAllGames();
+ public native GameFile addOrGet(String gamePath);
+ private native boolean update(String[] folderPaths);
+ private native boolean updateAdditionalMetadata();
+ public native boolean load();
+ private native boolean save();
+}
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
deleted file mode 100644
index 61abb0ab00..0000000000
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/GameProvider.java
+++ /dev/null
@@ -1,150 +0,0 @@
-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.support.annotation.NonNull;
-
-import org.dolphinemu.dolphinemu.BuildConfig;
-import org.dolphinemu.dolphinemu.utils.Log;
-
-/**
- * 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 REFRESH_LIBRARY = "refresh";
-
- 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 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_GAME = "vnd.android.cursor.item/vnd.dolphin.game";
-
-
- private GameDatabase mDbHelper;
-
- @Override
- public boolean onCreate()
- {
- Log.info("[GameProvider] Creating Content Provider...");
-
- mDbHelper = new GameDatabase(getContext());
-
- 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);
- cursor.setNotificationUri(getContext().getContentResolver(), uri);
-
- return cursor;
- }
-
- @Override
- public String getType(@NonNull Uri uri)
- {
- Log.verbose("[GameProvider] Getting MIME type for URI: " + uri);
- String lastSegment = uri.getLastPathSegment();
-
- if (lastSegment == null)
- {
- Log.error("[GameProvider] 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.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.
- 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;
- }
-}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/services/DirectoryInitializationService.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/services/DirectoryInitializationService.java
index d23b478065..9dde847d35 100644
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/services/DirectoryInitializationService.java
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/services/DirectoryInitializationService.java
@@ -32,7 +32,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
*/
public final class DirectoryInitializationService extends IntentService
{
- public static final String BROADCAST_ACTION = "org.dolphinemu.dolphinemu.BROADCAST";
+ public static final String BROADCAST_ACTION = "org.dolphinemu.dolphinemu.DIRECTORY_INITIALIZATION";
public static final String EXTRA_STATE = "directoryState";
private static volatile DirectoryInitializationState directoryState = null;
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/services/GameFileCacheService.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/services/GameFileCacheService.java
new file mode 100644
index 0000000000..c4aa8e5715
--- /dev/null
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/services/GameFileCacheService.java
@@ -0,0 +1,125 @@
+package org.dolphinemu.dolphinemu.services;
+
+import android.app.IntentService;
+import android.content.Context;
+import android.content.Intent;
+import android.support.v4.content.LocalBroadcastManager;
+
+import org.dolphinemu.dolphinemu.model.GameFile;
+import org.dolphinemu.dolphinemu.model.GameFileCache;
+import org.dolphinemu.dolphinemu.ui.platform.Platform;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * A service that loads game list data on a separate thread.
+ */
+public final class GameFileCacheService extends IntentService
+{
+ public static final String BROADCAST_ACTION = "org.dolphinemu.dolphinemu.GAME_FILE_CACHE_UPDATED";
+
+ private static final String ACTION_LOAD = "org.dolphinemu.dolphinemu.LOAD_GAME_FILE_CACHE";
+ private static final String ACTION_RESCAN = "org.dolphinemu.dolphinemu.RESCAN_GAME_FILE_CACHE";
+
+ private static GameFileCache gameFileCache = null;
+ private static AtomicReference gameFiles = new AtomicReference<>(new GameFile[]{});
+
+ public GameFileCacheService()
+ {
+ // Superclass constructor is called to name the thread on which this service executes.
+ super("GameFileCacheService");
+ }
+
+ public static List getGameFilesForPlatform(Platform platform)
+ {
+ GameFile[] allGames = gameFiles.get();
+ ArrayList platformGames = new ArrayList<>();
+ for (GameFile game : allGames)
+ {
+ if (Platform.fromNativeInt(game.getPlatform()) == platform)
+ {
+ platformGames.add(game);
+ }
+ }
+ return platformGames;
+ }
+
+ private static void startService(Context context, String action)
+ {
+ Intent intent = new Intent(context, GameFileCacheService.class);
+ intent.setAction(action);
+ context.startService(intent);
+ }
+
+ /**
+ * Asynchronously loads the game file cache from disk without checking
+ * which games are present on the file system.
+ */
+ public static void startLoad(Context context)
+ {
+ startService(context, ACTION_LOAD);
+ }
+
+ /**
+ * Asynchronously scans for games in the user's configured folders,
+ * updating the game file cache with the results.
+ * If startLoad hasn't been called before this, this has no effect.
+ */
+ public static void startRescan(Context context)
+ {
+ startService(context, ACTION_RESCAN);
+ }
+
+ public static GameFile addOrGet(String gamePath)
+ {
+ // The existence of this one function, which is called from one
+ // single place, forces us to use synchronization in onHandleIntent...
+ // A bit annoying, but should be good enough for now
+ synchronized (gameFileCache)
+ {
+ return gameFileCache.addOrGet(gamePath);
+ }
+ }
+
+ @Override
+ protected void onHandleIntent(Intent intent)
+ {
+ // Load the game list cache if it isn't already loaded, otherwise do nothing
+ if (ACTION_LOAD.equals(intent.getAction()) && gameFileCache == null)
+ {
+ GameFileCache temp = new GameFileCache(getCacheDir() + File.separator + "gamelist.cache");
+ synchronized (temp)
+ {
+ gameFileCache = temp;
+ gameFileCache.load();
+ updateGameFileArray();
+ }
+ }
+
+ // Rescan the file system and update the game list cache with the results
+ if (ACTION_RESCAN.equals(intent.getAction()) && gameFileCache != null)
+ {
+ synchronized (gameFileCache)
+ {
+ if (gameFileCache.scanLibrary(this))
+ {
+ updateGameFileArray();
+ }
+ }
+ }
+ }
+
+ private void updateGameFileArray()
+ {
+ GameFile[] gameFilesTemp = gameFileCache.getAllGames();
+ Arrays.sort(gameFilesTemp, (lhs, rhs) -> lhs.getTitle().compareTo(rhs.getTitle()));
+ gameFiles.set(gameFilesTemp);
+ LocalBroadcastManager.getInstance(this).sendBroadcast(new Intent(BROADCAST_ACTION));
+ }
+}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/MainActivity.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/MainActivity.java
index 97ad22bd7a..57b9814a75 100644
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/MainActivity.java
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/MainActivity.java
@@ -2,7 +2,6 @@ package org.dolphinemu.dolphinemu.ui.main;
import android.content.Intent;
import android.content.pm.PackageManager;
-import android.database.Cursor;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.design.widget.FloatingActionButton;
@@ -18,12 +17,11 @@ import android.widget.Toast;
import org.dolphinemu.dolphinemu.R;
import org.dolphinemu.dolphinemu.adapters.PlatformPagerAdapter;
-import org.dolphinemu.dolphinemu.model.GameProvider;
import org.dolphinemu.dolphinemu.services.DirectoryInitializationService;
+import org.dolphinemu.dolphinemu.services.GameFileCacheService;
import org.dolphinemu.dolphinemu.ui.platform.Platform;
import org.dolphinemu.dolphinemu.ui.platform.PlatformGamesView;
import org.dolphinemu.dolphinemu.ui.settings.SettingsActivity;
-import org.dolphinemu.dolphinemu.utils.AddDirectoryHelper;
import org.dolphinemu.dolphinemu.utils.FileBrowserHelper;
import org.dolphinemu.dolphinemu.utils.PermissionsHandler;
import org.dolphinemu.dolphinemu.utils.StartupHandler;
@@ -39,7 +37,7 @@ public final class MainActivity extends AppCompatActivity implements MainView
private TabLayout mTabLayout;
private FloatingActionButton mFab;
- private MainPresenter mPresenter = new MainPresenter(this);
+ private MainPresenter mPresenter = new MainPresenter(this, this);
@Override
protected void onCreate(Bundle savedInstanceState)
@@ -67,7 +65,11 @@ public final class MainActivity extends AppCompatActivity implements MainView
PlatformPagerAdapter platformPagerAdapter = new PlatformPagerAdapter(
getSupportFragmentManager(), this);
mViewPager.setAdapter(platformPagerAdapter);
- } else {
+ showGames();
+ GameFileCacheService.startLoad(this);
+ }
+ else
+ {
mViewPager.setVisibility(View.INVISIBLE);
}
}
@@ -76,7 +78,14 @@ public final class MainActivity extends AppCompatActivity implements MainView
protected void onResume()
{
super.onResume();
- mPresenter.addDirIfNeeded(new AddDirectoryHelper(this));
+ mPresenter.addDirIfNeeded(this);
+ }
+
+ @Override
+ protected void onDestroy()
+ {
+ super.onDestroy();
+ mPresenter.onDestroy();
}
// TODO: Replace with a ButterKnife injection.
@@ -106,13 +115,6 @@ public final class MainActivity extends AppCompatActivity implements MainView
mToolbar.setSubtitle(version);
}
- @Override
- public void refresh()
- {
- getContentResolver().insert(GameProvider.URI_REFRESH, null);
- refreshAllFragments();
- }
-
@Override
public void refreshFragmentScreenshot(int fragmentPosition)
{
@@ -138,12 +140,6 @@ public final class MainActivity extends AppCompatActivity implements MainView
FileBrowserHelper.openDirectoryPicker(this);
}
- @Override
- public void showGames(Platform platform, 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.
@@ -174,12 +170,12 @@ public final class MainActivity extends AppCompatActivity implements MainView
case PermissionsHandler.REQUEST_CODE_WRITE_PERMISSION:
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
DirectoryInitializationService.startService(this);
-
PlatformPagerAdapter platformPagerAdapter = new PlatformPagerAdapter(
getSupportFragmentManager(), this);
mViewPager.setAdapter(platformPagerAdapter);
mTabLayout.setupWithViewPager(mViewPager);
mViewPager.setVisibility(View.VISIBLE);
+ GameFileCacheService.startLoad(this);
} else {
Toast.makeText(this, R.string.write_permission_needed, Toast.LENGTH_SHORT)
.show();
@@ -200,17 +196,17 @@ public final class MainActivity extends AppCompatActivity implements MainView
@Override
public boolean onOptionsItemSelected(MenuItem item)
{
- return mPresenter.handleOptionSelection(item.getItemId());
+ return mPresenter.handleOptionSelection(item.getItemId(), this);
}
- private void refreshAllFragments()
+ public void showGames()
{
for (Platform platform : Platform.values())
{
PlatformGamesView fragment = getPlatformGamesView(platform);
if (fragment != null)
{
- fragment.refresh();
+ fragment.showGames();
}
}
}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/MainPresenter.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/MainPresenter.java
index 4ed97b6ecb..9ae48acb0f 100644
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/MainPresenter.java
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/MainPresenter.java
@@ -1,34 +1,57 @@
package org.dolphinemu.dolphinemu.ui.main;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.support.v4.content.LocalBroadcastManager;
import org.dolphinemu.dolphinemu.BuildConfig;
-import org.dolphinemu.dolphinemu.DolphinApplication;
import org.dolphinemu.dolphinemu.R;
-import org.dolphinemu.dolphinemu.model.GameDatabase;
-import org.dolphinemu.dolphinemu.ui.platform.Platform;
-import org.dolphinemu.dolphinemu.utils.AddDirectoryHelper;
+import org.dolphinemu.dolphinemu.model.GameFileCache;
+import org.dolphinemu.dolphinemu.services.GameFileCacheService;
import org.dolphinemu.dolphinemu.utils.SettingsFile;
-import rx.android.schedulers.AndroidSchedulers;
-import rx.schedulers.Schedulers;
-
public final class MainPresenter
{
public static final int REQUEST_ADD_DIRECTORY = 1;
public static final int REQUEST_EMULATE_GAME = 2;
private final MainView mView;
+ private final Context mContext;
+ private BroadcastReceiver mBroadcastReceiver = null;
private String mDirToAdd;
- public MainPresenter(MainView view)
+ public MainPresenter(MainView view, Context context)
{
mView = view;
+ mContext = context;
}
public void onCreate()
{
String versionName = BuildConfig.VERSION_NAME;
mView.setVersionString(versionName);
+
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(GameFileCacheService.BROADCAST_ACTION);
+ mBroadcastReceiver = new BroadcastReceiver()
+ {
+ @Override
+ public void onReceive(Context context, Intent intent)
+ {
+ mView.showGames();
+ }
+ };
+ LocalBroadcastManager.getInstance(mContext).registerReceiver(mBroadcastReceiver, filter);
+ }
+
+ public void onDestroy()
+ {
+ if (mBroadcastReceiver != null)
+ {
+ LocalBroadcastManager.getInstance(mContext).unregisterReceiver(mBroadcastReceiver);
+ }
}
public void onFabClick()
@@ -36,7 +59,7 @@ public final class MainPresenter
mView.launchFileListActivity();
}
- public boolean handleOptionSelection(int itemId)
+ public boolean handleOptionSelection(int itemId, Context context)
{
switch (itemId)
{
@@ -57,9 +80,7 @@ public final class MainPresenter
return true;
case R.id.menu_refresh:
- GameDatabase databaseHelper = DolphinApplication.databaseHelper;
- databaseHelper.scanLibrary(databaseHelper.getWritableDatabase());
- mView.refresh();
+ GameFileCacheService.startRescan(context);
return true;
case R.id.button_add_directory:
@@ -70,13 +91,13 @@ public final class MainPresenter
return false;
}
- public void addDirIfNeeded(AddDirectoryHelper helper)
+ public void addDirIfNeeded(Context context)
{
if (mDirToAdd != null)
{
- helper.addDirectory(mDirToAdd, mView::refresh);
-
+ GameFileCache.addGameFolder(mDirToAdd, context);
mDirToAdd = null;
+ GameFileCacheService.startRescan(context);
}
}
@@ -89,15 +110,4 @@ public final class MainPresenter
{
mView.refreshFragmentScreenshot(resultCode);
}
-
-
- public void loadGames(final Platform platform)
- {
- GameDatabase databaseHelper = DolphinApplication.databaseHelper;
-
- databaseHelper.getGamesForPlatform(platform)
- .subscribeOn(Schedulers.io())
- .observeOn(AndroidSchedulers.mainThread())
- .subscribe(games -> mView.showGames(platform, games));
- }
}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/MainView.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/MainView.java
index fdaef5eefa..cb21b7b084 100644
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/MainView.java
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/MainView.java
@@ -2,6 +2,7 @@ package org.dolphinemu.dolphinemu.ui.main;
import android.database.Cursor;
+import org.dolphinemu.dolphinemu.model.GameFile;
import org.dolphinemu.dolphinemu.ui.platform.Platform;
/**
@@ -19,11 +20,6 @@ public interface MainView
*/
void setVersionString(String version);
- /**
- * Tell the view to refresh its contents.
- */
- void refresh();
-
/**
* 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.
@@ -38,11 +34,7 @@ public interface MainView
void launchFileListActivity();
/**
- * To be called when an asynchronous database read completes. Passes the
- * result, in this case a {@link Cursor} to the view.
- *
- * @param platform Which platform to show games for.
- * @param games A Cursor containing the games read from the database.
+ * To be called when the game file cache is updated.
*/
- void showGames(Platform platform, Cursor games);
+ void showGames();
}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/TvMainActivity.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/TvMainActivity.java
index 659ee97d37..10187df402 100644
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/TvMainActivity.java
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/TvMainActivity.java
@@ -2,13 +2,10 @@ package org.dolphinemu.dolphinemu.ui.main;
import android.content.Intent;
import android.content.pm.PackageManager;
-import android.database.Cursor;
import android.os.Bundle;
import android.support.v17.leanback.app.BrowseFragment;
import android.support.v17.leanback.app.BrowseSupportFragment;
-import android.support.v17.leanback.database.CursorMapper;
import android.support.v17.leanback.widget.ArrayObjectAdapter;
-import android.support.v17.leanback.widget.CursorObjectAdapter;
import android.support.v17.leanback.widget.HeaderItem;
import android.support.v17.leanback.widget.ListRow;
import android.support.v17.leanback.widget.ListRowPresenter;
@@ -21,20 +18,22 @@ import org.dolphinemu.dolphinemu.R;
import org.dolphinemu.dolphinemu.activities.EmulationActivity;
import org.dolphinemu.dolphinemu.adapters.GameRowPresenter;
import org.dolphinemu.dolphinemu.adapters.SettingsRowPresenter;
-import org.dolphinemu.dolphinemu.model.Game;
+import org.dolphinemu.dolphinemu.model.GameFile;
import org.dolphinemu.dolphinemu.model.TvSettingsItem;
import org.dolphinemu.dolphinemu.services.DirectoryInitializationService;
+import org.dolphinemu.dolphinemu.services.GameFileCacheService;
import org.dolphinemu.dolphinemu.ui.platform.Platform;
import org.dolphinemu.dolphinemu.ui.settings.SettingsActivity;
-import org.dolphinemu.dolphinemu.utils.AddDirectoryHelper;
import org.dolphinemu.dolphinemu.utils.FileBrowserHelper;
import org.dolphinemu.dolphinemu.utils.PermissionsHandler;
import org.dolphinemu.dolphinemu.utils.StartupHandler;
import org.dolphinemu.dolphinemu.viewholders.TvGameViewHolder;
+import java.util.Collection;
+
public final class TvMainActivity extends FragmentActivity implements MainView
{
- private MainPresenter mPresenter = new MainPresenter(this);
+ private MainPresenter mPresenter = new MainPresenter(this, this);
private BrowseSupportFragment mBrowseFragment;
@@ -59,7 +58,14 @@ public final class TvMainActivity extends FragmentActivity implements MainView
protected void onResume()
{
super.onResume();
- mPresenter.addDirIfNeeded(new AddDirectoryHelper(this));
+ mPresenter.addDirIfNeeded(this);
+ }
+
+ @Override
+ protected void onDestroy()
+ {
+ super.onDestroy();
+ mPresenter.onDestroy();
}
void setupUI() {
@@ -82,7 +88,7 @@ public final class TvMainActivity extends FragmentActivity implements MainView
if (item instanceof TvSettingsItem)
{
TvSettingsItem settingsItem = (TvSettingsItem) item;
- mPresenter.handleOptionSelection(settingsItem.getItemId());
+ mPresenter.handleOptionSelection(settingsItem.getItemId(), this);
}
else
{
@@ -90,9 +96,7 @@ public final class TvMainActivity extends FragmentActivity implements MainView
// Start the emulation activity and send the path of the clicked ISO to it.
EmulationActivity.launch(TvMainActivity.this,
- holder.path,
- holder.title,
- holder.screenshotPath,
+ holder.gameFile,
-1,
holder.imageScreenshot);
}
@@ -108,12 +112,6 @@ public final class TvMainActivity extends FragmentActivity implements MainView
mBrowseFragment.setTitle(version);
}
- @Override
- public void refresh()
- {
- recreate();
- }
-
@Override
public void refreshFragmentScreenshot(int fragmentPosition)
{
@@ -133,15 +131,9 @@ public final class TvMainActivity extends FragmentActivity implements MainView
}
@Override
- public void showGames(Platform platform, Cursor games)
+ public void showGames()
{
- ListRow row = buildGamesRow(platform, games);
-
- // Add row to the adapter only if it is not empty.
- if (row != null)
- {
- mRowsAdapter.add(row);
- }
+ recreate();
}
/**
@@ -176,7 +168,7 @@ public final class TvMainActivity extends FragmentActivity implements MainView
case PermissionsHandler.REQUEST_CODE_WRITE_PERMISSION:
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
DirectoryInitializationService.startService(this);
- loadGames();
+ GameFileCacheService.startLoad(this);
} else {
Toast.makeText(this, R.string.write_permission_needed, Toast.LENGTH_SHORT)
.show();
@@ -194,48 +186,36 @@ public final class TvMainActivity extends FragmentActivity implements MainView
if (PermissionsHandler.hasWriteAccess(this))
{
- loadGames();
+ GameFileCacheService.startLoad(this);
}
mRowsAdapter.add(buildSettingsRow());
+ for (Platform platform : Platform.values())
+ {
+ ListRow row = buildGamesRow(platform, GameFileCacheService.getGameFilesForPlatform(platform));
+
+ // Add row to the adapter only if it is not empty.
+ if (row != null)
+ {
+ mRowsAdapter.add(row);
+ }
+ }
+
mBrowseFragment.setAdapter(mRowsAdapter);
}
- private void loadGames() {
- for (Platform platform : Platform.values()) {
- mPresenter.loadGames(platform);
- }
- }
-
- private ListRow buildGamesRow(Platform platform, Cursor games)
+ private ListRow buildGamesRow(Platform platform, Collection gameFiles)
{
- // Create an adapter for this row.
- CursorObjectAdapter row = new CursorObjectAdapter(new GameRowPresenter());
-
- // If cursor is empty, don't return a Row.
- if (!games.moveToFirst())
+ // If there are no games, don't return a Row.
+ if (gameFiles.size() == 0)
{
return null;
}
- row.changeCursor(games);
- row.setMapper(new CursorMapper()
- {
- @Override
- protected void bindColumns(Cursor cursor)
- {
- // No-op? Not sure what this does.
- }
-
- @Override
- protected Object bind(Cursor cursor)
- {
- return Game.fromCursor(cursor);
- }
- });
-
- String headerName = platform.getHeaderName();
+ // Create an adapter for this row.
+ ArrayObjectAdapter row = new ArrayObjectAdapter(new GameRowPresenter());
+ row.addAll(0, gameFiles);
// Create a header for this row.
HeaderItem header = new HeaderItem(platform.toInt(), platform.getHeaderName());
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/platform/Platform.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/platform/Platform.java
index b161a1cde4..64f1cc71cd 100644
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/platform/Platform.java
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/platform/Platform.java
@@ -23,11 +23,9 @@ public enum Platform
public static Platform fromNativeInt(int i)
{
- // If the game's platform field is empty, file under Wiiware. // TODO Something less dum
- if (i == -1) {
- return Platform.WIIWARE;
- }
- return values()[i];
+ // TODO: Proper support for DOL and ELF files
+ boolean in_range = i >= 0 && i < values().length;
+ return values()[in_range ? i : WIIWARE.value];
}
public static Platform fromPosition(int position)
@@ -44,4 +42,4 @@ public enum Platform
{
return headerName;
}
-}
\ No newline at end of file
+}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/platform/PlatformGamesFragment.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/platform/PlatformGamesFragment.java
index 3325a1ca23..7d15609055 100644
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/platform/PlatformGamesFragment.java
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/platform/PlatformGamesFragment.java
@@ -1,6 +1,5 @@
package org.dolphinemu.dolphinemu.ui.platform;
-import android.database.Cursor;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
@@ -12,13 +11,15 @@ import android.view.ViewGroup;
import org.dolphinemu.dolphinemu.R;
import org.dolphinemu.dolphinemu.adapters.GameAdapter;
+import org.dolphinemu.dolphinemu.model.GameFile;
+import org.dolphinemu.dolphinemu.services.GameFileCacheService;
+
+import java.util.List;
public final class PlatformGamesFragment extends Fragment implements PlatformGamesView
{
private static final String ARG_PLATFORM = "platform";
- private PlatformGamesPresenter mPresenter = new PlatformGamesPresenter(this);
-
private GameAdapter mAdapter;
private RecyclerView mRecyclerView;
@@ -37,8 +38,6 @@ public final class PlatformGamesFragment extends Fragment implements PlatformGam
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
-
- mPresenter.onCreate((Platform) getArguments().getSerializable(ARG_PLATFORM));
}
@Nullable
@@ -49,8 +48,6 @@ public final class PlatformGamesFragment extends Fragment implements PlatformGam
findViews(rootView);
- mPresenter.onCreateView();
-
return rootView;
}
@@ -65,6 +62,8 @@ public final class PlatformGamesFragment extends Fragment implements PlatformGam
mRecyclerView.setAdapter(mAdapter);
mRecyclerView.addItemDecoration(new GameAdapter.SpacesItemDecoration(8));
+
+ showGames();
}
@Override
@@ -73,12 +72,6 @@ public final class PlatformGamesFragment extends Fragment implements PlatformGam
mAdapter.notifyItemChanged(position);
}
- @Override
- public void refresh()
- {
- mPresenter.refresh();
- }
-
@Override
public void onItemClick(String gameId)
{
@@ -86,11 +79,12 @@ public final class PlatformGamesFragment extends Fragment implements PlatformGam
}
@Override
- public void showGames(Cursor games)
+ public void showGames()
{
if (mAdapter != null)
{
- mAdapter.swapCursor(games);
+ Platform platform = (Platform) getArguments().getSerializable(ARG_PLATFORM);
+ mAdapter.swapDataSet(GameFileCacheService.getGameFilesForPlatform(platform));
}
}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/platform/PlatformGamesPresenter.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/platform/PlatformGamesPresenter.java
deleted file mode 100644
index 4c6782dd5f..0000000000
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/platform/PlatformGamesPresenter.java
+++ /dev/null
@@ -1,57 +0,0 @@
-package org.dolphinemu.dolphinemu.ui.platform;
-
-
-import android.database.Cursor;
-
-import org.dolphinemu.dolphinemu.DolphinApplication;
-import org.dolphinemu.dolphinemu.model.GameDatabase;
-import org.dolphinemu.dolphinemu.utils.Log;
-
-import rx.android.schedulers.AndroidSchedulers;
-import rx.functions.Action1;
-import rx.schedulers.Schedulers;
-
-public final class PlatformGamesPresenter
-{
- private final PlatformGamesView mView;
-
- private Platform mPlatform;
-
- public PlatformGamesPresenter(PlatformGamesView view)
- {
- mView = view;
- }
-
- public void onCreate(Platform platform)
- {
- mPlatform = platform;
- }
-
- public void onCreateView()
- {
- loadGames();
- }
-
- public void refresh()
- {
- Log.debug("[PlatformGamesPresenter] " + mPlatform + ": Refreshing...");
- loadGames();
- }
-
- private void loadGames()
- {
- Log.debug("[PlatformGamesPresenter] " + mPlatform + ": Loading games...");
-
- GameDatabase databaseHelper = DolphinApplication.databaseHelper;
-
- databaseHelper.getGamesForPlatform(mPlatform)
- .subscribeOn(Schedulers.io())
- .observeOn(AndroidSchedulers.mainThread())
- .subscribe(games ->
- {
- Log.debug("[PlatformGamesPresenter] " + mPlatform + ": Load finished, swapping cursor...");
-
- mView.showGames(games);
- });
- }
-}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/platform/PlatformGamesView.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/platform/PlatformGamesView.java
index 361d16dfbc..b584b8090f 100644
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/platform/PlatformGamesView.java
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/platform/PlatformGamesView.java
@@ -1,17 +1,14 @@
package org.dolphinemu.dolphinemu.ui.platform;
-import android.database.Cursor;
+import org.dolphinemu.dolphinemu.model.GameFile;
+
+import java.util.List;
/**
* Abstraction for a screen representing a single platform's games.
*/
public interface PlatformGamesView
{
- /**
- * Tell the view to refresh its contents.
- */
- void refresh();
-
/**
* Tell the view that a certain game's screenshot has been updated,
* and should be redrawn on-screen.
@@ -29,10 +26,7 @@ public interface PlatformGamesView
void onItemClick(String gameId);
/**
- * To be called when an asynchronous database read completes. Passes the
- * result, in this case a {@link Cursor}, to the view.
- *
- * @param games A Cursor containing the games read from the database.
+ * To be called when the game file cache is updated.
*/
- void showGames(Cursor games);
+ void showGames();
}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/AddDirectoryHelper.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/AddDirectoryHelper.java
deleted file mode 100644
index 55feddb957..0000000000
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/AddDirectoryHelper.java
+++ /dev/null
@@ -1,44 +0,0 @@
-package org.dolphinemu.dolphinemu.utils;
-
-import android.content.AsyncQueryHandler;
-import android.content.ContentValues;
-import android.content.Context;
-import android.net.Uri;
-
-import org.dolphinemu.dolphinemu.model.GameDatabase;
-import org.dolphinemu.dolphinemu.model.GameProvider;
-
-public class AddDirectoryHelper
-{
- private Context mContext;
-
- public interface AddDirectoryListener
- {
- void onDirectoryAdded();
- }
-
- public AddDirectoryHelper(Context context)
- {
- this.mContext = context;
- }
-
- public void addDirectory(String dir, AddDirectoryListener addDirectoryListener)
- {
- AsyncQueryHandler handler = new AsyncQueryHandler(mContext.getContentResolver())
- {
- @Override
- protected void onInsertComplete(int token, Object cookie, Uri uri)
- {
- addDirectoryListener.onDirectoryAdded();
- }
- };
-
- ContentValues file = new ContentValues();
- file.put(GameDatabase.KEY_FOLDER_PATH, dir);
-
- 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);
- }
-}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/GameBannerRequestHandler.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/GameBannerRequestHandler.java
index ef95cd7712..b8afcf6a52 100644
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/GameBannerRequestHandler.java
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/GameBannerRequestHandler.java
@@ -7,22 +7,28 @@ import com.squareup.picasso.Request;
import com.squareup.picasso.RequestHandler;
import org.dolphinemu.dolphinemu.NativeLibrary;
+import org.dolphinemu.dolphinemu.model.GameFile;
import java.io.IOException;
import java.nio.IntBuffer;
public class GameBannerRequestHandler extends RequestHandler {
+ GameFile mGameFile;
+
+ public GameBannerRequestHandler(GameFile gameFile)
+ {
+ mGameFile = gameFile;
+ }
@Override
public boolean canHandleRequest(Request data) {
- return "iso".equals(data.uri.getScheme());
+ return true;
}
@Override
- public Result load(Request request, int networkPolicy) throws IOException {
- String url = request.uri.getHost() + request.uri.getPath();
- int[] vector = NativeLibrary.GetBanner(url);
- int width = 96;
- int height = 32;
+ public Result load(Request request, int networkPolicy) {
+ int[] vector = mGameFile.getBanner();
+ int width = mGameFile.getBannerWidth();
+ int height = mGameFile.getBannerHeight();
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
bitmap.setPixels(vector, 0, width, 0, 0, width, height);
return new Result(bitmap, Picasso.LoadedFrom.DISK);
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/PicassoUtils.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/PicassoUtils.java
index 36a00d884d..4630a3cddc 100644
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/PicassoUtils.java
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/PicassoUtils.java
@@ -7,17 +7,18 @@ import android.widget.ImageView;
import com.squareup.picasso.Picasso;
import org.dolphinemu.dolphinemu.R;
+import org.dolphinemu.dolphinemu.model.GameFile;
import java.io.File;
import java.net.URI;
public class PicassoUtils {
- public static void loadGameBanner(ImageView imageView, String screenshotPath, String gamePath) {
- File file = new File(URI.create(screenshotPath));
- if (file.exists()) {
+ public static void loadGameBanner(ImageView imageView, GameFile gameFile) {
+ File screenshotFile = new File(URI.create(gameFile.getScreenshotPath()));
+ if (screenshotFile.exists()) {
// Fill in the view contents.
Picasso.with(imageView.getContext())
- .load(screenshotPath)
+ .load(gameFile.getScreenshotPath())
.fit()
.centerCrop()
.noFade()
@@ -27,11 +28,11 @@ public class PicassoUtils {
.into(imageView);
} else {
Picasso picassoInstance = new Picasso.Builder(imageView.getContext())
- .addRequestHandler(new GameBannerRequestHandler())
+ .addRequestHandler(new GameBannerRequestHandler(gameFile))
.build();
picassoInstance
- .load(Uri.parse("iso:/" + gamePath))
+ .load(Uri.parse("iso:/" + gameFile.getPath()))
.fit()
.noFade()
.noPlaceholder()
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 592a2ecf32..e993ba7961 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
@@ -6,6 +6,7 @@ import android.widget.ImageView;
import android.widget.TextView;
import org.dolphinemu.dolphinemu.R;
+import org.dolphinemu.dolphinemu.model.GameFile;
/**
* A simple class that stores references to views so that the GameAdapter doesn't need to
@@ -17,15 +18,7 @@ public class GameViewHolder extends RecyclerView.ViewHolder
public TextView textGameTitle;
public TextView textCompany;
- 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 GameFile gameFile;
public GameViewHolder(View itemView)
{
@@ -33,8 +26,8 @@ public class GameViewHolder extends RecyclerView.ViewHolder
itemView.setTag(this);
- imageScreenshot = (ImageView) itemView.findViewById(R.id.image_game_screen);
- textGameTitle = (TextView) itemView.findViewById(R.id.text_game_title);
- textCompany = (TextView) itemView.findViewById(R.id.text_company);
+ imageScreenshot = itemView.findViewById(R.id.image_game_screen);
+ textGameTitle = itemView.findViewById(R.id.text_game_title);
+ textCompany = itemView.findViewById(R.id.text_company);
}
}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/viewholders/TvGameViewHolder.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/viewholders/TvGameViewHolder.java
index dbd018be78..54615c6710 100644
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/viewholders/TvGameViewHolder.java
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/viewholders/TvGameViewHolder.java
@@ -5,6 +5,8 @@ import android.support.v17.leanback.widget.Presenter;
import android.view.View;
import android.widget.ImageView;
+import org.dolphinemu.dolphinemu.model.GameFile;
+
/**
* A simple class that stores references to views so that the GameAdapter doesn't need to
* keep calling findViewById(), which is expensive.
@@ -15,15 +17,7 @@ public final class TvGameViewHolder extends Presenter.ViewHolder
public ImageView imageScreenshot;
- 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 GameFile gameFile;
public TvGameViewHolder(View itemView)
{
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 efd17e74cb..61cc212f55 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
@@ -50,7 +50,7 @@
tools:text="Rhythm Heaven Fever"/>
@@ -96,13 +96,13 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBottom="@+id/icon_country"
- android:layout_alignStart="@+id/text_company"
+ android:layout_alignStart="@+id/text_description"
android:layout_alignTop="@+id/icon_country"
android:gravity="center_vertical"
tools:text="United States"/>
+
+#include
+
+std::string GetJString(JNIEnv* env, jstring jstr)
+{
+ std::string result = "";
+ if (!jstr)
+ return result;
+
+ const char* s = env->GetStringUTFChars(jstr, nullptr);
+ result = s;
+ env->ReleaseStringUTFChars(jstr, s);
+ return result;
+}
+
+jstring ToJString(JNIEnv* env, const std::string& str)
+{
+ return env->NewStringUTF(str.c_str());
+}
diff --git a/Source/Android/jni/AndroidCommon/AndroidCommon.h b/Source/Android/jni/AndroidCommon/AndroidCommon.h
new file mode 100644
index 0000000000..25ea59ed30
--- /dev/null
+++ b/Source/Android/jni/AndroidCommon/AndroidCommon.h
@@ -0,0 +1,12 @@
+// Copyright 2018 Dolphin Emulator Project
+// Licensed under GPLv2+
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include
+
+#include
+
+std::string GetJString(JNIEnv* env, jstring jstr);
+jstring ToJString(JNIEnv* env, const std::string& str);
diff --git a/Source/Android/jni/AndroidCommon/IDCache.cpp b/Source/Android/jni/AndroidCommon/IDCache.cpp
new file mode 100644
index 0000000000..2c457d6c9d
--- /dev/null
+++ b/Source/Android/jni/AndroidCommon/IDCache.cpp
@@ -0,0 +1,110 @@
+// Copyright 2018 Dolphin Emulator Project
+// Licensed under GPLv2+
+// Refer to the license.txt file included.
+
+#include "jni/AndroidCommon/IDCache.h"
+
+#include
+
+static constexpr jint JNI_VERSION = JNI_VERSION_1_6;
+
+static JavaVM* s_java_vm;
+
+static jclass s_native_library_class;
+static jmethodID s_display_alert_msg;
+
+static jclass s_game_file_class;
+static jfieldID s_game_file_pointer;
+static jmethodID s_game_file_constructor;
+
+static jclass s_game_file_cache_class;
+static jfieldID s_game_file_cache_pointer;
+
+namespace IDCache
+{
+JavaVM* GetJavaVM()
+{
+ return s_java_vm;
+}
+
+jclass GetNativeLibraryClass()
+{
+ return s_native_library_class;
+}
+
+jmethodID GetDisplayAlertMsg()
+{
+ return s_display_alert_msg;
+}
+
+jclass GetGameFileClass()
+{
+ return s_game_file_class;
+}
+
+jfieldID GetGameFilePointer()
+{
+ return s_game_file_pointer;
+}
+
+jmethodID GetGameFileConstructor()
+{
+ return s_game_file_constructor;
+}
+
+jclass GetGameFileCacheClass()
+{
+ return s_game_file_cache_class;
+}
+
+jfieldID GetGameFileCachePointer()
+{
+ return s_game_file_cache_pointer;
+}
+
+} // namespace IDCache
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+jint JNI_OnLoad(JavaVM* vm, void* reserved)
+{
+ s_java_vm = vm;
+
+ JNIEnv* env;
+ if (vm->GetEnv(reinterpret_cast(&env), JNI_VERSION) != JNI_OK)
+ return JNI_ERR;
+
+ const jclass native_library_class = env->FindClass("org/dolphinemu/dolphinemu/NativeLibrary");
+ s_native_library_class = reinterpret_cast(env->NewGlobalRef(native_library_class));
+ s_display_alert_msg = env->GetStaticMethodID(s_native_library_class, "displayAlertMsg",
+ "(Ljava/lang/String;Ljava/lang/String;Z)Z");
+
+ const jclass game_file_class = env->FindClass("org/dolphinemu/dolphinemu/model/GameFile");
+ s_game_file_class = reinterpret_cast(env->NewGlobalRef(game_file_class));
+ s_game_file_pointer = env->GetFieldID(game_file_class, "mPointer", "J");
+ s_game_file_constructor = env->GetMethodID(game_file_class, "", "(J)V");
+
+ const jclass game_file_cache_class =
+ env->FindClass("org/dolphinemu/dolphinemu/model/GameFileCache");
+ s_game_file_cache_class = reinterpret_cast(env->NewGlobalRef(game_file_cache_class));
+ s_game_file_cache_pointer = env->GetFieldID(game_file_cache_class, "mPointer", "J");
+
+ return JNI_VERSION;
+}
+
+void JNI_OnUnload(JavaVM* vm, void* reserved)
+{
+ JNIEnv* env;
+ if (vm->GetEnv(reinterpret_cast(&env), JNI_VERSION) != JNI_OK)
+ return;
+
+ env->DeleteGlobalRef(s_native_library_class);
+ env->DeleteGlobalRef(s_game_file_class);
+ env->DeleteGlobalRef(s_game_file_cache_class);
+}
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/Source/Android/jni/AndroidCommon/IDCache.h b/Source/Android/jni/AndroidCommon/IDCache.h
new file mode 100644
index 0000000000..584b0634ab
--- /dev/null
+++ b/Source/Android/jni/AndroidCommon/IDCache.h
@@ -0,0 +1,23 @@
+// Copyright 2018 Dolphin Emulator Project
+// Licensed under GPLv2+
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include
+
+namespace IDCache
+{
+JavaVM* GetJavaVM();
+
+jclass GetNativeLibraryClass();
+jmethodID GetDisplayAlertMsg();
+
+jclass GetGameFileClass();
+jfieldID GetGameFilePointer();
+jmethodID GetGameFileConstructor();
+
+jclass GetGameFileCacheClass();
+jfieldID GetGameFileCachePointer();
+
+} // namespace IDCache
diff --git a/Source/Android/jni/CMakeLists.txt b/Source/Android/jni/CMakeLists.txt
index 69b383019b..848da01823 100644
--- a/Source/Android/jni/CMakeLists.txt
+++ b/Source/Android/jni/CMakeLists.txt
@@ -1,4 +1,8 @@
add_library(main SHARED
+ AndroidCommon/AndroidCommon.cpp
+ AndroidCommon/IDCache.cpp
+ GameList/GameFile.cpp
+ GameList/GameFileCache.cpp
ButtonManager.cpp
MainAndroid.cpp
)
diff --git a/Source/Android/jni/GameList/GameFile.cpp b/Source/Android/jni/GameList/GameFile.cpp
new file mode 100644
index 0000000000..b2cf26cd52
--- /dev/null
+++ b/Source/Android/jni/GameList/GameFile.cpp
@@ -0,0 +1,140 @@
+// Copyright 2018 Dolphin Emulator Project
+// Licensed under GPLv2+
+// Refer to the license.txt file included.
+
+#include "jni/GameList/GameFile.h"
+
+#include
+#include
+#include
+
+#include
+
+#include "DiscIO/Enums.h"
+#include "UICommon/GameFile.h"
+#include "jni/AndroidCommon/AndroidCommon.h"
+#include "jni/AndroidCommon/IDCache.h"
+
+static std::shared_ptr* GetPointer(JNIEnv* env, jobject obj)
+{
+ return reinterpret_cast*>(
+ env->GetLongField(obj, IDCache::GetGameFilePointer()));
+}
+
+static std::shared_ptr& GetRef(JNIEnv* env, jobject obj)
+{
+ return *GetPointer(env, obj);
+}
+
+jobject GameFileToJava(JNIEnv* env, std::shared_ptr game_file)
+{
+ if (!game_file)
+ return nullptr;
+
+ return env->NewObject(
+ IDCache::GetGameFileClass(), IDCache::GetGameFileConstructor(),
+ reinterpret_cast(new std::shared_ptr(std::move(game_file))));
+}
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_model_GameFile_finalize(JNIEnv* env,
+ jobject obj);
+JNIEXPORT jint JNICALL Java_org_dolphinemu_dolphinemu_model_GameFile_getPlatform(JNIEnv* env,
+ jobject obj);
+JNIEXPORT jstring JNICALL Java_org_dolphinemu_dolphinemu_model_GameFile_getTitle(JNIEnv* env,
+ jobject obj);
+JNIEXPORT jstring JNICALL Java_org_dolphinemu_dolphinemu_model_GameFile_getDescription(JNIEnv* env,
+ jobject obj);
+JNIEXPORT jstring JNICALL Java_org_dolphinemu_dolphinemu_model_GameFile_getCompany(JNIEnv* env,
+ jobject obj);
+JNIEXPORT jint JNICALL Java_org_dolphinemu_dolphinemu_model_GameFile_getCountry(JNIEnv* env,
+ jobject obj);
+JNIEXPORT jstring JNICALL Java_org_dolphinemu_dolphinemu_model_GameFile_getPath(JNIEnv* env,
+ jobject obj);
+JNIEXPORT jstring JNICALL Java_org_dolphinemu_dolphinemu_model_GameFile_getGameId(JNIEnv* env,
+ jobject obj);
+JNIEXPORT jintArray JNICALL Java_org_dolphinemu_dolphinemu_model_GameFile_getBanner(JNIEnv* env,
+ jobject obj);
+JNIEXPORT jint JNICALL Java_org_dolphinemu_dolphinemu_model_GameFile_getBannerWidth(JNIEnv* env,
+ jobject obj);
+JNIEXPORT jint JNICALL Java_org_dolphinemu_dolphinemu_model_GameFile_getBannerHeight(JNIEnv* env,
+ jobject obj);
+
+JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_model_GameFile_finalize(JNIEnv* env,
+ jobject obj)
+{
+ delete GetPointer(env, obj);
+}
+
+JNIEXPORT jint JNICALL Java_org_dolphinemu_dolphinemu_model_GameFile_getPlatform(JNIEnv* env,
+ jobject obj)
+{
+ return static_cast(GetRef(env, obj)->GetPlatform());
+}
+
+JNIEXPORT jstring JNICALL Java_org_dolphinemu_dolphinemu_model_GameFile_getTitle(JNIEnv* env,
+ jobject obj)
+{
+ return ToJString(env, GetRef(env, obj)->GetName());
+}
+
+JNIEXPORT jstring JNICALL Java_org_dolphinemu_dolphinemu_model_GameFile_getDescription(JNIEnv* env,
+ jobject obj)
+{
+ return ToJString(env, GetRef(env, obj)->GetDescription());
+}
+
+JNIEXPORT jstring JNICALL Java_org_dolphinemu_dolphinemu_model_GameFile_getCompany(JNIEnv* env,
+ jobject obj)
+{
+ return ToJString(env, DiscIO::GetCompanyFromID(GetRef(env, obj)->GetMakerID()));
+}
+
+JNIEXPORT jint JNICALL Java_org_dolphinemu_dolphinemu_model_GameFile_getCountry(JNIEnv* env,
+ jobject obj)
+{
+ return static_cast(GetRef(env, obj)->GetCountry());
+}
+
+JNIEXPORT jstring JNICALL Java_org_dolphinemu_dolphinemu_model_GameFile_getPath(JNIEnv* env,
+ jobject obj)
+{
+ return ToJString(env, GetRef(env, obj)->GetFilePath());
+}
+
+JNIEXPORT jstring JNICALL Java_org_dolphinemu_dolphinemu_model_GameFile_getGameId(JNIEnv* env,
+ jobject obj)
+{
+ return ToJString(env, GetRef(env, obj)->GetGameID());
+}
+
+JNIEXPORT jintArray JNICALL Java_org_dolphinemu_dolphinemu_model_GameFile_getBanner(JNIEnv* env,
+ jobject obj)
+{
+ const std::vector& buffer = GetRef(env, obj)->GetBannerImage().buffer;
+ const jsize size = static_cast(buffer.size());
+ const jintArray out_array = env->NewIntArray(size);
+ if (!out_array)
+ return nullptr;
+ env->SetIntArrayRegion(out_array, 0, size, reinterpret_cast(buffer.data()));
+ return out_array;
+}
+
+JNIEXPORT jint JNICALL Java_org_dolphinemu_dolphinemu_model_GameFile_getBannerWidth(JNIEnv* env,
+ jobject obj)
+{
+ return static_cast(GetRef(env, obj)->GetBannerImage().width);
+}
+
+JNIEXPORT jint JNICALL Java_org_dolphinemu_dolphinemu_model_GameFile_getBannerHeight(JNIEnv* env,
+ jobject obj)
+{
+ return static_cast(GetRef(env, obj)->GetBannerImage().height);
+}
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/Source/Android/jni/GameList/GameFile.h b/Source/Android/jni/GameList/GameFile.h
new file mode 100644
index 0000000000..9bf4f1cce4
--- /dev/null
+++ b/Source/Android/jni/GameList/GameFile.h
@@ -0,0 +1,17 @@
+// Copyright 2018 Dolphin Emulator Project
+// Licensed under GPLv2+
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include
+#include
+
+#include
+
+namespace UICommon
+{
+class GameFile;
+}
+
+jobject GameFileToJava(JNIEnv* env, std::shared_ptr game_file);
diff --git a/Source/Android/jni/GameList/GameFileCache.cpp b/Source/Android/jni/GameList/GameFileCache.cpp
new file mode 100644
index 0000000000..4b3abf8c2f
--- /dev/null
+++ b/Source/Android/jni/GameList/GameFileCache.cpp
@@ -0,0 +1,121 @@
+// Copyright 2018 Dolphin Emulator Project
+// Licensed under GPLv2+
+// Refer to the license.txt file included.
+
+#include
+#include
+
+#include
+
+#include "UICommon/GameFileCache.h"
+#include "jni/AndroidCommon/AndroidCommon.h"
+#include "jni/AndroidCommon/IDCache.h"
+#include "jni/GameList/GameFile.h"
+
+namespace UICommon
+{
+class GameFile;
+}
+
+static UICommon::GameFileCache* GetPointer(JNIEnv* env, jobject obj)
+{
+ return reinterpret_cast(
+ env->GetLongField(obj, IDCache::GetGameFileCachePointer()));
+}
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+JNIEXPORT jlong JNICALL Java_org_dolphinemu_dolphinemu_model_GameFileCache_newGameFileCache(
+ JNIEnv* env, jobject obj, jstring path);
+JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_model_GameFileCache_finalize(JNIEnv* env,
+ jobject obj);
+JNIEXPORT jobjectArray JNICALL
+Java_org_dolphinemu_dolphinemu_model_GameFileCache_getAllGames(JNIEnv* env, jobject obj);
+JNIEXPORT jobject JNICALL Java_org_dolphinemu_dolphinemu_model_GameFileCache_addOrGet(JNIEnv* env,
+ jobject obj,
+ jstring path);
+JNIEXPORT jboolean JNICALL Java_org_dolphinemu_dolphinemu_model_GameFileCache_update(
+ JNIEnv* env, jobject obj, jobjectArray folder_paths);
+JNIEXPORT jboolean JNICALL
+Java_org_dolphinemu_dolphinemu_model_GameFileCache_updateAdditionalMetadata(JNIEnv* env,
+ jobject obj);
+JNIEXPORT jboolean JNICALL Java_org_dolphinemu_dolphinemu_model_GameFileCache_load(JNIEnv* env,
+ jobject obj);
+JNIEXPORT jboolean JNICALL Java_org_dolphinemu_dolphinemu_model_GameFileCache_save(JNIEnv* env,
+ jobject obj);
+
+JNIEXPORT jlong JNICALL Java_org_dolphinemu_dolphinemu_model_GameFileCache_newGameFileCache(
+ JNIEnv* env, jobject obj, jstring path)
+{
+ return reinterpret_cast(new UICommon::GameFileCache(GetJString(env, path)));
+}
+
+JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_model_GameFileCache_finalize(JNIEnv* env,
+ jobject obj)
+{
+ delete GetPointer(env, obj);
+}
+
+JNIEXPORT jobjectArray JNICALL
+Java_org_dolphinemu_dolphinemu_model_GameFileCache_getAllGames(JNIEnv* env, jobject obj)
+{
+ const UICommon::GameFileCache* ptr = GetPointer(env, obj);
+ const jobjectArray array =
+ env->NewObjectArray(static_cast(ptr->GetSize()), IDCache::GetGameFileClass(), nullptr);
+ jsize i = 0;
+ GetPointer(env, obj)->ForEach([env, array, &i](const auto& game_file) {
+ env->SetObjectArrayElement(array, i++, GameFileToJava(env, game_file));
+ });
+ return array;
+}
+
+JNIEXPORT jobject JNICALL Java_org_dolphinemu_dolphinemu_model_GameFileCache_addOrGet(JNIEnv* env,
+ jobject obj,
+ jstring path)
+{
+ bool cache_changed = false;
+ return GameFileToJava(env, GetPointer(env, obj)->AddOrGet(GetJString(env, path), &cache_changed));
+}
+
+JNIEXPORT jboolean JNICALL Java_org_dolphinemu_dolphinemu_model_GameFileCache_update(
+ JNIEnv* env, jobject obj, jobjectArray folder_paths)
+{
+ jsize size = env->GetArrayLength(folder_paths);
+
+ std::vector folder_paths_vector;
+ folder_paths_vector.reserve(size);
+
+ for (jsize i = 0; i < size; ++i)
+ {
+ const jstring path = reinterpret_cast(env->GetObjectArrayElement(folder_paths, i));
+ folder_paths_vector.push_back(GetJString(env, path));
+ env->DeleteLocalRef(path);
+ }
+
+ return GetPointer(env, obj)->Update(UICommon::FindAllGamePaths(folder_paths_vector, false));
+}
+
+JNIEXPORT jboolean JNICALL
+Java_org_dolphinemu_dolphinemu_model_GameFileCache_updateAdditionalMetadata(JNIEnv* env,
+ jobject obj)
+{
+ return GetPointer(env, obj)->UpdateAdditionalMetadata();
+}
+
+JNIEXPORT jboolean JNICALL Java_org_dolphinemu_dolphinemu_model_GameFileCache_load(JNIEnv* env,
+ jobject obj)
+{
+ return GetPointer(env, obj)->Load();
+}
+
+JNIEXPORT jboolean JNICALL Java_org_dolphinemu_dolphinemu_model_GameFileCache_save(JNIEnv* env,
+ jobject obj)
+{
+ return GetPointer(env, obj)->Save();
+}
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/Source/Android/jni/MainAndroid.cpp b/Source/Android/jni/MainAndroid.cpp
index 62d31580e7..730175e340 100644
--- a/Source/Android/jni/MainAndroid.cpp
+++ b/Source/Android/jni/MainAndroid.cpp
@@ -16,8 +16,6 @@
#include
#include
-#include "ButtonManager.h"
-
#include "Common/CPUDetect.h"
#include "Common/CommonPaths.h"
#include "Common/CommonTypes.h"
@@ -51,16 +49,15 @@
#include "VideoCommon/RenderBase.h"
#include "VideoCommon/VideoBackendBase.h"
-#define DOLPHIN_TAG "DolphinEmuNative"
-
-JavaVM* g_java_vm;
+#include "jni/AndroidCommon/AndroidCommon.h"
+#include "jni/AndroidCommon/IDCache.h"
+#include "jni/ButtonManager.h"
namespace
{
-ANativeWindow* s_surf;
+static constexpr char DOLPHIN_TAG[] = "DolphinEmuNative";
-jclass s_jni_class;
-jmethodID s_jni_method_alert;
+ANativeWindow* s_surf;
// The Core only supports using a single Host thread.
// If multiple threads want to call host functions then they need to queue
@@ -70,16 +67,6 @@ Common::Event s_update_main_frame_event;
bool s_have_wm_user_stop = false;
} // Anonymous namespace
-/*
- * Cache the JavaVM so that we can call into it later.
- */
-jint JNI_OnLoad(JavaVM* vm, void* reserved)
-{
- g_java_vm = vm;
-
- return JNI_VERSION_1_6;
-}
-
void Host_NotifyMapLoaded()
{
}
@@ -156,229 +143,19 @@ static bool MsgAlert(const char* caption, const char* text, bool yes_no, MsgType
// Associate the current Thread with the Java VM.
JNIEnv* env;
- g_java_vm->AttachCurrentThread(&env, NULL);
+ IDCache::GetJavaVM()->AttachCurrentThread(&env, nullptr);
// Execute the Java method.
- jboolean result =
- env->CallStaticBooleanMethod(s_jni_class, s_jni_method_alert, env->NewStringUTF(caption),
- env->NewStringUTF(text), yes_no ? JNI_TRUE : JNI_FALSE);
+ jboolean result = env->CallStaticBooleanMethod(
+ IDCache::GetNativeLibraryClass(), IDCache::GetDisplayAlertMsg(), ToJString(env, caption),
+ ToJString(env, text), yes_no ? JNI_TRUE : JNI_FALSE);
// Must be called before the current thread exits; might as well do it here.
- g_java_vm->DetachCurrentThread();
+ IDCache::GetJavaVM()->DetachCurrentThread();
return result != JNI_FALSE;
}
-#define DVD_BANNER_WIDTH 96
-#define DVD_BANNER_HEIGHT 32
-
-static inline u32 Average32(u32 a, u32 b)
-{
- return ((a >> 1) & 0x7f7f7f7f) + ((b >> 1) & 0x7f7f7f7f);
-}
-
-static inline u32 GetPixel(u32* buffer, unsigned int x, unsigned int y)
-{
- // thanks to unsignedness, these also check for <0 automatically.
- if (x > 191)
- return 0;
- if (y > 63)
- return 0;
- return buffer[y * 192 + x];
-}
-
-static bool LoadBanner(std::string filename, u32* Banner)
-{
- std::unique_ptr pVolume(DiscIO::CreateVolumeFromFilename(filename));
-
- if (pVolume != nullptr)
- {
- u32 Width, Height;
- std::vector BannerVec = pVolume->GetBanner(&Width, &Height);
- // This code (along with above inlines) is moved from
- // elsewhere. Someone who knows anything about Android
- // please get rid of it and use proper high-resolution
- // images.
- if (Height == 64 && Width == 192)
- {
- u32* Buffer = &BannerVec[0];
- for (int y = 0; y < 32; y++)
- {
- for (int x = 0; x < 96; x++)
- {
- // simplified plus-shaped "gaussian"
- u32 surround = Average32(
- Average32(GetPixel(Buffer, x * 2 - 1, y * 2), GetPixel(Buffer, x * 2 + 1, y * 2)),
- Average32(GetPixel(Buffer, x * 2, y * 2 - 1), GetPixel(Buffer, x * 2, y * 2 + 1)));
- Banner[y * 96 + x] = Average32(GetPixel(Buffer, x * 2, y * 2), surround);
- }
- }
- return true;
- }
- else if (Height == 32 && Width == 96)
- {
- memcpy(Banner, &BannerVec[0], 96 * 32 * 4);
- return true;
- }
- }
-
- return false;
-}
-
-static int GetCountry(std::string filename)
-{
- std::unique_ptr pVolume(DiscIO::CreateVolumeFromFilename(filename));
-
- if (pVolume != nullptr)
- {
- int country = static_cast(pVolume->GetCountry());
-
- __android_log_print(ANDROID_LOG_INFO, DOLPHIN_TAG, "Country Code: %i", country);
-
- return country;
- }
-
- return static_cast(DiscIO::Country::Unknown);
-}
-
-static int GetPlatform(std::string filename)
-{
- std::unique_ptr pVolume(DiscIO::CreateVolumeFromFilename(filename));
-
- if (pVolume != nullptr)
- {
- switch (pVolume->GetVolumeType())
- {
- case DiscIO::Platform::GameCubeDisc:
- __android_log_print(ANDROID_LOG_INFO, DOLPHIN_TAG, "Volume is a GameCube disc.");
- return 0;
- case DiscIO::Platform::WiiDisc:
- __android_log_print(ANDROID_LOG_INFO, DOLPHIN_TAG, "Volume is a Wii disc.");
- return 1;
- case DiscIO::Platform::WiiWAD:
- __android_log_print(ANDROID_LOG_INFO, DOLPHIN_TAG, "Volume is a Wii WAD.");
- return 2;
- }
- }
-
- return -1;
-}
-
-static std::string GetTitle(std::string filename)
-{
- __android_log_print(ANDROID_LOG_WARN, DOLPHIN_TAG, "Getting Title for file: %s",
- filename.c_str());
-
- std::unique_ptr pVolume(DiscIO::CreateVolumeFromFilename(filename));
-
- if (pVolume != nullptr)
- {
- std::map titles = pVolume->GetLongNames();
- if (titles.empty())
- titles = pVolume->GetShortNames();
-
- auto end = titles.end();
-
- // English tends to be a good fallback when the requested language isn't available
- // if (language != DiscIO::Language::English) {
- auto it = titles.find(DiscIO::Language::English);
- if (it != end)
- return it->second;
- //}
-
- // If English isn't available either, just pick something
- if (!titles.empty())
- return titles.cbegin()->second;
-
- // No usable name, return filename (better than nothing)
- std::string name;
- SplitPath(filename, nullptr, &name, nullptr);
- return name;
- }
-
- return std::string("");
-}
-
-static std::string GetDescription(std::string filename)
-{
- __android_log_print(ANDROID_LOG_WARN, DOLPHIN_TAG, "Getting Description for file: %s",
- filename.c_str());
-
- std::unique_ptr volume(DiscIO::CreateVolumeFromFilename(filename));
-
- if (volume != nullptr)
- {
- std::map descriptions = volume->GetDescriptions();
-
- auto end = descriptions.end();
-
- // English tends to be a good fallback when the requested language isn't available
- // if (language != DiscIO::Language::English) {
- auto it = descriptions.find(DiscIO::Language::English);
- if (it != end)
- return it->second;
- //}
-
- // If English isn't available either, just pick something
- if (!descriptions.empty())
- return descriptions.cbegin()->second;
- }
-
- return std::string();
-}
-
-static std::string GetGameId(std::string filename)
-{
- __android_log_print(ANDROID_LOG_WARN, DOLPHIN_TAG, "Getting ID for file: %s", filename.c_str());
-
- std::unique_ptr volume(DiscIO::CreateVolumeFromFilename(filename));
- if (volume == nullptr)
- return std::string();
-
- std::string id = volume->GetGameID();
- __android_log_print(ANDROID_LOG_INFO, DOLPHIN_TAG, "Game ID: %s", id.c_str());
- return id;
-}
-
-static std::string GetCompany(std::string filename)
-{
- __android_log_print(ANDROID_LOG_WARN, DOLPHIN_TAG, "Getting Company for file: %s",
- filename.c_str());
-
- std::unique_ptr volume(DiscIO::CreateVolumeFromFilename(filename));
- if (volume == nullptr)
- return std::string();
-
- std::string company = DiscIO::GetCompanyFromID(volume->GetMakerID());
- __android_log_print(ANDROID_LOG_INFO, DOLPHIN_TAG, "Company: %s", company.c_str());
- return company;
-}
-
-static u64 GetFileSize(std::string filename)
-{
- __android_log_print(ANDROID_LOG_WARN, DOLPHIN_TAG, "Getting size of file: %s", filename.c_str());
-
- std::unique_ptr volume(DiscIO::CreateVolumeFromFilename(filename));
- if (volume == nullptr)
- return -1;
-
- u64 size = volume->GetSize();
- __android_log_print(ANDROID_LOG_INFO, DOLPHIN_TAG, "Size: %" PRIu64, size);
- return size;
-}
-
-static std::string GetJString(JNIEnv* env, jstring jstr)
-{
- std::string result = "";
- if (!jstr)
- return result;
-
- const char* s = env->GetStringUTFChars(jstr, nullptr);
- result = s;
- env->ReleaseStringUTFChars(jstr, s);
- return result;
-}
-
#ifdef __cplusplus
extern "C" {
#endif
@@ -395,28 +172,6 @@ JNIEXPORT jboolean JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_onGamePa
JNIEnv* env, jobject obj, jstring jDevice, jint Button, jint Action);
JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_onGamePadMoveEvent(
JNIEnv* env, jobject obj, jstring jDevice, jint Axis, jfloat Value);
-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 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 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 jstring JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_GetGitRevision(JNIEnv* env,
@@ -461,8 +216,6 @@ JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_SetProfiling
jboolean enable);
JNIEXPORT void JNICALL
Java_org_dolphinemu_dolphinemu_NativeLibrary_WriteProfileResults(JNIEnv* env, jobject obj);
-JNIEXPORT void JNICALL
-Java_org_dolphinemu_dolphinemu_NativeLibrary_CacheClassesAndMethods(JNIEnv* env, jobject obj);
JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_Run__Ljava_lang_String_2(
JNIEnv* env, jobject obj, jstring jFile);
JNIEXPORT void JNICALL
@@ -518,93 +271,16 @@ JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_onGamePadMov
ButtonManager::GamepadAxisEvent(GetJString(env, jDevice), Axis, Value);
}
-JNIEXPORT jintArray JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_GetBanner(JNIEnv* env,
- jobject obj,
- jstring jFile)
-{
- std::string file = GetJString(env, jFile);
- u32 uBanner[DVD_BANNER_WIDTH * DVD_BANNER_HEIGHT];
- jintArray Banner = env->NewIntArray(DVD_BANNER_WIDTH * DVD_BANNER_HEIGHT);
-
- if (LoadBanner(file, uBanner))
- {
- env->SetIntArrayRegion(Banner, 0, DVD_BANNER_WIDTH * DVD_BANNER_HEIGHT, (jint*)uBanner);
- }
- return Banner;
-}
-
-JNIEXPORT jstring JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_GetTitle(JNIEnv* env,
- jobject obj,
- jstring jFilename)
-{
- std::string filename = GetJString(env, jFilename);
- std::string name = GetTitle(filename);
- return env->NewStringUTF(name.c_str());
-}
-
-JNIEXPORT jstring JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_GetDescription(
- JNIEnv* env, jobject obj, jstring jFilename)
-{
- std::string filename = GetJString(env, jFilename);
- std::string description = GetDescription(filename);
- return env->NewStringUTF(description.c_str());
-}
-
-JNIEXPORT jstring JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_GetGameId(JNIEnv* env,
- jobject obj,
- jstring jFilename)
-{
- std::string filename = GetJString(env, jFilename);
- std::string id = GetGameId(filename);
- return env->NewStringUTF(id.c_str());
-}
-
-JNIEXPORT jstring JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_GetCompany(JNIEnv* env,
- jobject obj,
- jstring jFilename)
-{
- std::string filename = GetJString(env, jFilename);
- 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)
-{
- std::string filename = GetJString(env, jFilename);
- u64 size = GetFileSize(filename);
- return size;
-}
-
-JNIEXPORT jint JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_GetPlatform(JNIEnv* env,
- jobject obj,
- jstring jFilename)
-{
- std::string filename = GetJString(env, jFilename);
- int platform = GetPlatform(filename);
- return platform;
-}
-
JNIEXPORT jstring JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_GetVersionString(JNIEnv* env,
jobject obj)
{
- return env->NewStringUTF(Common::scm_rev_str.c_str());
+ return ToJString(env, Common::scm_rev_str.c_str());
}
JNIEXPORT jstring JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_GetGitRevision(JNIEnv* env,
jobject obj)
{
- return env->NewStringUTF(Common::scm_rev_git_str.c_str());
+ return ToJString(env, Common::scm_rev_git_str.c_str());
}
JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_SaveScreenShot(JNIEnv* env,
@@ -646,7 +322,7 @@ JNIEXPORT jstring JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_GetUserSe
ini.GetOrCreateSection(section)->Get(key, &value, "-1");
- return env->NewStringUTF(value.c_str());
+ return ToJString(env, value.c_str());
}
JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_SetUserSetting(
@@ -686,8 +362,9 @@ JNIEXPORT jstring JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_GetConfig
ini.GetOrCreateSection(section)->Get(key, &value, defaultValue);
- return env->NewStringUTF(value.c_str());
+ return ToJString(env, value.c_str());
}
+
JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_SetConfig(
JNIEnv* env, jobject obj, jstring jFile, jstring jSection, jstring jKey, jstring jValue)
{
@@ -762,7 +439,7 @@ JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_SetUserDirec
JNIEXPORT jstring JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_GetUserDirectory(JNIEnv* env,
jobject obj)
{
- return env->NewStringUTF(File::GetUserPath(D_USER_IDX).c_str());
+ return ToJString(env, File::GetUserPath(D_USER_IDX).c_str());
}
JNIEXPORT jint JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_DefaultCPUCore(JNIEnv* env,
@@ -791,25 +468,6 @@ JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_WriteProfile
JitInterface::WriteProfileResults(filename);
}
-JNIEXPORT void JNICALL
-Java_org_dolphinemu_dolphinemu_NativeLibrary_CacheClassesAndMethods(JNIEnv* env, jobject obj)
-{
- // This class reference is only valid for the lifetime of this method.
- jclass localClass = env->FindClass("org/dolphinemu/dolphinemu/NativeLibrary");
-
- // This reference, however, is valid until we delete it.
- s_jni_class = reinterpret_cast(env->NewGlobalRef(localClass));
-
- // TODO Find a place for this.
- // So we don't leak a reference to NativeLibrary.class.
- // env->DeleteGlobalRef(s_jni_class);
-
- // Method signature taken from javap -s
- // Source/Android/app/build/intermediates/classes/arm/debug/org/dolphinemu/dolphinemu/NativeLibrary.class
- s_jni_method_alert = env->GetStaticMethodID(s_jni_class, "displayAlertMsg",
- "(Ljava/lang/String;Ljava/lang/String;Z)Z");
-}
-
// Surface Handling
JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_SurfaceChanged(JNIEnv* env,
jobject obj,
diff --git a/Source/Core/Core/HW/WiimoteReal/IOAndroid.cpp b/Source/Core/Core/HW/WiimoteReal/IOAndroid.cpp
index abc33c88a2..efc8ca4aaf 100644
--- a/Source/Core/Core/HW/WiimoteReal/IOAndroid.cpp
+++ b/Source/Core/Core/HW/WiimoteReal/IOAndroid.cpp
@@ -14,8 +14,7 @@
#include "Core/HW/WiimoteReal/IOAndroid.h"
-// Global java_vm class
-extern JavaVM* g_java_vm;
+#include "jni/AndroidCommon/IDCache.h"
namespace WiimoteReal
{
@@ -31,10 +30,11 @@ void WiimoteScannerAndroid::FindWiimotes(std::vector& found_wiimotes,
NOTICE_LOG(WIIMOTE, "Finding Wiimotes");
JNIEnv* env;
- int get_env_status = g_java_vm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6);
+ int get_env_status =
+ IDCache::GetJavaVM()->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6);
if (get_env_status == JNI_EDETACHED)
- g_java_vm->AttachCurrentThread(&env, nullptr);
+ IDCache::GetJavaVM()->AttachCurrentThread(&env, nullptr);
jmethodID openadapter_func = env->GetStaticMethodID(s_adapter_class, "OpenAdapter", "()Z");
jmethodID queryadapter_func = env->GetStaticMethodID(s_adapter_class, "QueryAdapter", "()Z");
@@ -47,7 +47,7 @@ void WiimoteScannerAndroid::FindWiimotes(std::vector& found_wiimotes,
}
if (get_env_status == JNI_EDETACHED)
- g_java_vm->DetachCurrentThread();
+ IDCache::GetJavaVM()->DetachCurrentThread();
}
WiimoteAndroid::WiimoteAndroid(int index) : Wiimote(), m_mayflash_index(index)
@@ -62,7 +62,7 @@ WiimoteAndroid::~WiimoteAndroid()
// Connect to a Wiimote with a known address.
bool WiimoteAndroid::ConnectInternal()
{
- g_java_vm->AttachCurrentThread(&m_env, nullptr);
+ IDCache::GetJavaVM()->AttachCurrentThread(&m_env, nullptr);
jfieldID payload_field = m_env->GetStaticFieldID(s_adapter_class, "wiimote_payload", "[[B");
jobjectArray payload_object =
@@ -81,7 +81,7 @@ bool WiimoteAndroid::ConnectInternal()
void WiimoteAndroid::DisconnectInternal()
{
- g_java_vm->DetachCurrentThread();
+ IDCache::GetJavaVM()->DetachCurrentThread();
}
bool WiimoteAndroid::IsConnected() const
@@ -117,7 +117,7 @@ int WiimoteAndroid::IOWrite(u8 const* buf, size_t len)
void InitAdapterClass()
{
JNIEnv* env;
- g_java_vm->AttachCurrentThread(&env, nullptr);
+ IDCache::GetJavaVM()->AttachCurrentThread(&env, nullptr);
jclass adapter_class = env->FindClass("org/dolphinemu/dolphinemu/utils/Java_WiimoteAdapter");
s_adapter_class = reinterpret_cast(env->NewGlobalRef(adapter_class));
diff --git a/Source/Core/InputCommon/GCAdapter_Android.cpp b/Source/Core/InputCommon/GCAdapter_Android.cpp
index d8628530a3..d71e665015 100644
--- a/Source/Core/InputCommon/GCAdapter_Android.cpp
+++ b/Source/Core/InputCommon/GCAdapter_Android.cpp
@@ -20,8 +20,7 @@
#include "InputCommon/GCAdapter.h"
#include "InputCommon/GCPadStatus.h"
-// Global java_vm class
-extern JavaVM* g_java_vm;
+#include "jni/AndroidCommon/IDCache.h"
namespace GCAdapter
{
@@ -67,7 +66,7 @@ static void ScanThreadFunc()
NOTICE_LOG(SERIALINTERFACE, "GC Adapter scanning thread started");
JNIEnv* env;
- g_java_vm->AttachCurrentThread(&env, NULL);
+ IDCache::GetJavaVM()->AttachCurrentThread(&env, NULL);
jmethodID queryadapter_func = env->GetStaticMethodID(s_adapter_class, "QueryAdapter", "()Z");
@@ -78,7 +77,7 @@ static void ScanThreadFunc()
Setup();
Common::SleepCurrentThread(1000);
}
- g_java_vm->DetachCurrentThread();
+ IDCache::GetJavaVM()->DetachCurrentThread();
NOTICE_LOG(SERIALINTERFACE, "GC Adapter scanning thread stopped");
}
@@ -89,7 +88,7 @@ static void Write()
NOTICE_LOG(SERIALINTERFACE, "GC Adapter write thread started");
JNIEnv* env;
- g_java_vm->AttachCurrentThread(&env, NULL);
+ IDCache::GetJavaVM()->AttachCurrentThread(&env, NULL);
jmethodID output_func = env->GetStaticMethodID(s_adapter_class, "Output", "([B)I");
while (s_write_adapter_thread_running.IsSet())
@@ -119,7 +118,7 @@ static void Write()
Common::YieldCPU();
}
- g_java_vm->DetachCurrentThread();
+ IDCache::GetJavaVM()->DetachCurrentThread();
NOTICE_LOG(SERIALINTERFACE, "GC Adapter write thread stopped");
}
@@ -131,7 +130,7 @@ static void Read()
bool first_read = true;
JNIEnv* env;
- g_java_vm->AttachCurrentThread(&env, NULL);
+ IDCache::GetJavaVM()->AttachCurrentThread(&env, NULL);
jfieldID payload_field = env->GetStaticFieldID(s_adapter_class, "controller_payload", "[B");
jobject payload_object = env->GetStaticObjectField(s_adapter_class, payload_field);
@@ -185,7 +184,7 @@ static void Read()
s_fd = 0;
s_detected = false;
- g_java_vm->DetachCurrentThread();
+ IDCache::GetJavaVM()->DetachCurrentThread();
NOTICE_LOG(SERIALINTERFACE, "GC Adapter read thread stopped");
}
@@ -204,7 +203,7 @@ void Init()
}
JNIEnv* env;
- g_java_vm->AttachCurrentThread(&env, NULL);
+ IDCache::GetJavaVM()->AttachCurrentThread(&env, NULL);
jclass adapter_class = env->FindClass("org/dolphinemu/dolphinemu/utils/Java_GCAdapter");
s_adapter_class = reinterpret_cast(env->NewGlobalRef(adapter_class));
diff --git a/Source/Core/UICommon/GameFile.cpp b/Source/Core/UICommon/GameFile.cpp
index 64d4740a0e..a666efdaa5 100644
--- a/Source/Core/UICommon/GameFile.cpp
+++ b/Source/Core/UICommon/GameFile.cpp
@@ -78,8 +78,15 @@ const std::string& GameFile::Lookup(DiscIO::Language language,
const std::string&
GameFile::LookupUsingConfigLanguage(const std::map& strings) const
{
+#ifdef ANDROID
+ // TODO: Make the Android app load the config at app start instead of emulation start
+ // so that we can access the user's preference here
+ const DiscIO::Language language = DiscIO::Language::English;
+#else
const bool wii = DiscIO::IsWii(m_platform);
- return Lookup(SConfig::GetInstance().GetCurrentLanguage(wii), strings);
+ const DiscIO::Language language = SConfig::GetInstance().GetCurrentLanguage(wii);
+#endif
+ return Lookup(language, strings);
}
GameFile::GameFile(const std::string& path)
diff --git a/Source/Core/UICommon/GameFileCache.cpp b/Source/Core/UICommon/GameFileCache.cpp
index 7a71ca75f1..074dc7fe62 100644
--- a/Source/Core/UICommon/GameFileCache.cpp
+++ b/Source/Core/UICommon/GameFileCache.cpp
@@ -12,6 +12,7 @@
#include
#include
#include
+#include
#include
#include "Common/ChunkFile.h"
@@ -38,12 +39,25 @@ std::vector FindAllGamePaths(const std::vector& direct
return Common::DoFileSearch(directories_to_scan, search_extensions, recursive_scan);
}
+GameFileCache::GameFileCache() : m_path(File::GetUserPath(D_CACHE_IDX) + "gamelist.cache")
+{
+}
+
+GameFileCache::GameFileCache(std::string path) : m_path(std::move(path))
+{
+}
+
void GameFileCache::ForEach(std::function&)> f) const
{
for (const std::shared_ptr& item : m_cached_files)
f(item);
}
+size_t GameFileCache::GetSize() const
+{
+ return m_cached_files.size();
+}
+
void GameFileCache::Clear()
{
m_cached_files.clear();
@@ -179,9 +193,8 @@ bool GameFileCache::Save()
bool GameFileCache::SyncCacheFile(bool save)
{
- std::string filename(File::GetUserPath(D_CACHE_IDX) + "gamelist.cache");
const char* open_mode = save ? "wb" : "rb";
- File::IOFile f(filename, open_mode);
+ File::IOFile f(m_path, open_mode);
if (!f)
return false;
bool success = false;
@@ -217,7 +230,7 @@ bool GameFileCache::SyncCacheFile(bool save)
{
// If some file operation failed, try to delete the probably-corrupted cache
f.Close();
- File::Delete(filename);
+ File::Delete(m_path);
}
return success;
}
diff --git a/Source/Core/UICommon/GameFileCache.h b/Source/Core/UICommon/GameFileCache.h
index 6e36838dd0..197cceb547 100644
--- a/Source/Core/UICommon/GameFileCache.h
+++ b/Source/Core/UICommon/GameFileCache.h
@@ -6,9 +6,7 @@
#include
#include
-#include
#include
-#include
#include
#include
@@ -26,8 +24,12 @@ std::vector FindAllGamePaths(const std::vector& direct
class GameFileCache
{
public:
+ GameFileCache(); // Uses the default path
+ explicit GameFileCache(std::string path);
+
void ForEach(std::function&)> f) const;
+ size_t GetSize() const;
void Clear();
// Returns nullptr if the file is invalid.
@@ -49,6 +51,7 @@ private:
bool SyncCacheFile(bool save);
void DoState(PointerWrap* p, u64 size = 0);
+ std::string m_path;
std::vector> m_cached_files;
};