Merge pull request #10229 from JosJuice/android-game-list-livedata

Android: Make GameFileCacheManager use LiveData
This commit is contained in:
Mai M 2021-11-17 17:21:39 -05:00 committed by GitHub
commit 8b57aad8ed
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 67 additions and 120 deletions

View File

@ -2,16 +2,12 @@
package org.dolphinemu.dolphinemu.activities; package org.dolphinemu.dolphinemu.activities;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.util.Log; import android.util.Log;
import androidx.fragment.app.FragmentActivity; import androidx.fragment.app.FragmentActivity;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import org.dolphinemu.dolphinemu.model.GameFile; import org.dolphinemu.dolphinemu.model.GameFile;
import org.dolphinemu.dolphinemu.services.GameFileCacheManager; import org.dolphinemu.dolphinemu.services.GameFileCacheManager;
@ -69,22 +65,13 @@ public class AppLinkActivity extends FragmentActivity
mAfterDirectoryInitializationRunner = new AfterDirectoryInitializationRunner(); mAfterDirectoryInitializationRunner = new AfterDirectoryInitializationRunner();
mAfterDirectoryInitializationRunner.run(this, true, () -> tryPlay(playAction)); mAfterDirectoryInitializationRunner.run(this, true, () -> tryPlay(playAction));
IntentFilter gameFileCacheIntentFilter = new IntentFilter(GameFileCacheManager.DONE_LOADING); GameFileCacheManager.isLoading().observe(this, (isLoading) ->
BroadcastReceiver gameFileCacheReceiver = new BroadcastReceiver()
{ {
@Override if (!isLoading && DirectoryInitialization.areDolphinDirectoriesReady())
public void onReceive(Context context, Intent intent)
{
if (DirectoryInitialization.areDolphinDirectoriesReady())
{ {
tryPlay(playAction); tryPlay(playAction);
} }
} });
};
LocalBroadcastManager broadcastManager = LocalBroadcastManager.getInstance(this);
broadcastManager.registerReceiver(gameFileCacheReceiver, gameFileCacheIntentFilter);
DirectoryInitialization.start(this); DirectoryInitialization.start(this);
GameFileCacheManager.startLoad(this); GameFileCacheManager.startLoad(this);
@ -110,7 +97,7 @@ public class AppLinkActivity extends FragmentActivity
// If game == null and the load isn't done, wait for the next GameFileCacheService broadcast. // If game == null and the load isn't done, wait for the next GameFileCacheService broadcast.
// If game == null and the load is done, call play with a null game, making us exit in failure. // If game == null and the load is done, call play with a null game, making us exit in failure.
if (game != null || !GameFileCacheManager.isLoading()) if (game != null || !GameFileCacheManager.isLoading().getValue())
{ {
play(action, game); play(action, game);
} }

View File

@ -3,11 +3,10 @@
package org.dolphinemu.dolphinemu.services; package org.dolphinemu.dolphinemu.services;
import android.content.Context; import android.content.Context;
import android.content.Intent;
import androidx.localbroadcastmanager.content.LocalBroadcastManager; import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import org.dolphinemu.dolphinemu.DolphinApplication;
import org.dolphinemu.dolphinemu.model.GameFile; import org.dolphinemu.dolphinemu.model.GameFile;
import org.dolphinemu.dolphinemu.model.GameFileCache; import org.dolphinemu.dolphinemu.model.GameFileCache;
import org.dolphinemu.dolphinemu.ui.platform.Platform; import org.dolphinemu.dolphinemu.ui.platform.Platform;
@ -18,41 +17,32 @@ import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
/** /**
* Loads game list data on a separate thread. * Loads game list data on a separate thread.
*/ */
public final class GameFileCacheManager public final class GameFileCacheManager
{ {
/**
* This is broadcast when the contents of the cache change.
*/
public static final String CACHE_UPDATED = "org.dolphinemu.dolphinemu.GAME_FILE_CACHE_UPDATED";
/**
* This is broadcast when the service is done with all requested work, regardless of whether
* the contents of the cache actually changed. (Maybe the cache was already up to date.)
*/
public static final String DONE_LOADING =
"org.dolphinemu.dolphinemu.GAME_FILE_CACHE_DONE_LOADING";
private static GameFileCache gameFileCache = null; private static GameFileCache gameFileCache = null;
private static final AtomicReference<GameFile[]> gameFiles = private static final MutableLiveData<GameFile[]> gameFiles =
new AtomicReference<>(new GameFile[]{}); new MutableLiveData<>(new GameFile[]{});
private static final ExecutorService executor = Executors.newFixedThreadPool(1); private static final ExecutorService executor = Executors.newFixedThreadPool(1);
private static final AtomicBoolean loadInProgress = new AtomicBoolean(false); private static final MutableLiveData<Boolean> loadInProgress = new MutableLiveData<>(false);
private static final AtomicBoolean rescanInProgress = new AtomicBoolean(false); private static final MutableLiveData<Boolean> rescanInProgress = new MutableLiveData<>(false);
private GameFileCacheManager() private GameFileCacheManager()
{ {
} }
public static LiveData<GameFile[]> getGameFiles()
{
return gameFiles;
}
public static List<GameFile> getGameFilesForPlatform(Platform platform) public static List<GameFile> getGameFilesForPlatform(Platform platform)
{ {
GameFile[] allGames = gameFiles.get(); GameFile[] allGames = gameFiles.getValue();
ArrayList<GameFile> platformGames = new ArrayList<>(); ArrayList<GameFile> platformGames = new ArrayList<>();
for (GameFile game : allGames) for (GameFile game : allGames)
{ {
@ -66,7 +56,7 @@ public final class GameFileCacheManager
public static GameFile getGameFileByGameId(String gameId) public static GameFile getGameFileByGameId(String gameId)
{ {
GameFile[] allGames = gameFiles.get(); GameFile[] allGames = gameFiles.getValue();
for (GameFile game : allGames) for (GameFile game : allGames)
{ {
if (game.getGameId().equals(gameId)) if (game.getGameId().equals(gameId))
@ -81,7 +71,7 @@ public final class GameFileCacheManager
{ {
GameFile matchWithoutRevision = null; GameFile matchWithoutRevision = null;
GameFile[] allGames = gameFiles.get(); GameFile[] allGames = gameFiles.getValue();
for (GameFile otherGame : allGames) for (GameFile otherGame : allGames)
{ {
if (game.getGameId().equals(otherGame.getGameId()) && if (game.getGameId().equals(otherGame.getGameId()) &&
@ -107,19 +97,24 @@ public final class GameFileCacheManager
} }
/** /**
* Returns true if in the process of either loading the cache or rescanning. * Returns true if in the process of loading the cache for the first time.
*/ */
public static boolean isLoading() public static LiveData<Boolean> isLoading()
{ {
return loadInProgress.get(); return loadInProgress;
} }
/** /**
* Returns true if in the process of rescanning. * Returns true if in the process of rescanning.
*/ */
public static boolean isRescanning() public static LiveData<Boolean> isRescanning()
{ {
return rescanInProgress.get(); return rescanInProgress;
}
public static boolean isLoadingOrRescanning()
{
return loadInProgress.getValue() || rescanInProgress.getValue();
} }
/** /**
@ -129,8 +124,9 @@ public final class GameFileCacheManager
*/ */
public static void startLoad(Context context) public static void startLoad(Context context)
{ {
if (loadInProgress.compareAndSet(false, true)) if (!loadInProgress.getValue())
{ {
loadInProgress.setValue(true);
new AfterDirectoryInitializationRunner().run(context, false, new AfterDirectoryInitializationRunner().run(context, false,
() -> executor.execute(GameFileCacheManager::load)); () -> executor.execute(GameFileCacheManager::load));
} }
@ -143,8 +139,9 @@ public final class GameFileCacheManager
*/ */
public static void startRescan(Context context) public static void startRescan(Context context)
{ {
if (rescanInProgress.compareAndSet(false, true)) if (!rescanInProgress.getValue())
{ {
rescanInProgress.setValue(true);
new AfterDirectoryInitializationRunner().run(context, false, new AfterDirectoryInitializationRunner().run(context, false,
() -> executor.execute(GameFileCacheManager::rescan)); () -> executor.execute(GameFileCacheManager::rescan));
} }
@ -155,7 +152,7 @@ public final class GameFileCacheManager
// Common case: The game is in the cache, so just grab it from there. // Common case: The game is in the cache, so just grab it from there.
// (Actually, addOrGet already checks for this case, but we want to avoid calling it if possible // (Actually, addOrGet already checks for this case, but we want to avoid calling it if possible
// because onHandleIntent may hold a lock on gameFileCache for extended periods of time.) // because onHandleIntent may hold a lock on gameFileCache for extended periods of time.)
GameFile[] allGames = gameFiles.get(); GameFile[] allGames = gameFiles.getValue();
for (GameFile game : allGames) for (GameFile game : allGames)
{ {
if (game.getPath().equals(gamePath)) if (game.getPath().equals(gamePath))
@ -189,14 +186,11 @@ public final class GameFileCacheManager
if (gameFileCache.getSize() != 0) if (gameFileCache.getSize() != 0)
{ {
updateGameFileArray(); updateGameFileArray();
sendBroadcast(CACHE_UPDATED);
} }
} }
} }
loadInProgress.set(false); loadInProgress.postValue(false);
if (!rescanInProgress.get())
sendBroadcast(DONE_LOADING);
} }
/** /**
@ -218,14 +212,12 @@ public final class GameFileCacheManager
if (changed) if (changed)
{ {
updateGameFileArray(); updateGameFileArray();
sendBroadcast(CACHE_UPDATED);
} }
boolean additionalMetadataChanged = gameFileCache.updateAdditionalMetadata(); boolean additionalMetadataChanged = gameFileCache.updateAdditionalMetadata();
if (additionalMetadataChanged) if (additionalMetadataChanged)
{ {
updateGameFileArray(); updateGameFileArray();
sendBroadcast(CACHE_UPDATED);
} }
if (changed || additionalMetadataChanged) if (changed || additionalMetadataChanged)
@ -234,21 +226,13 @@ public final class GameFileCacheManager
} }
} }
rescanInProgress.set(false); rescanInProgress.postValue(false);
if (!loadInProgress.get())
sendBroadcast(DONE_LOADING);
} }
private static void updateGameFileArray() private static void updateGameFileArray()
{ {
GameFile[] gameFilesTemp = gameFileCache.getAllGames(); GameFile[] gameFilesTemp = gameFileCache.getAllGames();
Arrays.sort(gameFilesTemp, (lhs, rhs) -> lhs.getTitle().compareToIgnoreCase(rhs.getTitle())); Arrays.sort(gameFilesTemp, (lhs, rhs) -> lhs.getTitle().compareToIgnoreCase(rhs.getTitle()));
gameFiles.set(gameFilesTemp); gameFiles.postValue(gameFilesTemp);
}
private static void sendBroadcast(String action)
{
LocalBroadcastManager.getInstance(DolphinApplication.getAppContext())
.sendBroadcast(new Intent(action));
} }
} }

View File

@ -2,16 +2,14 @@
package org.dolphinemu.dolphinemu.ui.main; package org.dolphinemu.dolphinemu.ui.main;
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.ContentResolver; import android.content.ContentResolver;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter;
import android.net.Uri; import android.net.Uri;
import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AlertDialog;
import androidx.localbroadcastmanager.content.LocalBroadcastManager; import androidx.core.app.ComponentActivity;
import androidx.lifecycle.Observer;
import org.dolphinemu.dolphinemu.BuildConfig; import org.dolphinemu.dolphinemu.BuildConfig;
import org.dolphinemu.dolphinemu.R; import org.dolphinemu.dolphinemu.R;
@ -42,14 +40,13 @@ public final class MainPresenter
private static boolean sShouldRescanLibrary = true; private static boolean sShouldRescanLibrary = true;
private final MainView mView; private final MainView mView;
private final Context mContext; private final ComponentActivity mActivity;
private BroadcastReceiver mBroadcastReceiver = null;
private String mDirToAdd; private String mDirToAdd;
public MainPresenter(MainView view, Context context) public MainPresenter(MainView view, ComponentActivity activity)
{ {
mView = view; mView = view;
mContext = context; mActivity = activity;
} }
public void onCreate() public void onCreate()
@ -57,34 +54,18 @@ public final class MainPresenter
String versionName = BuildConfig.VERSION_NAME; String versionName = BuildConfig.VERSION_NAME;
mView.setVersionString(versionName); mView.setVersionString(versionName);
IntentFilter filter = new IntentFilter(); GameFileCacheManager.getGameFiles().observe(mActivity, (gameFiles) -> mView.showGames());
filter.addAction(GameFileCacheManager.CACHE_UPDATED);
filter.addAction(GameFileCacheManager.DONE_LOADING); Observer<Boolean> refreshObserver = (isLoading) ->
mBroadcastReceiver = new BroadcastReceiver()
{ {
@Override mView.setRefreshing(GameFileCacheManager.isLoadingOrRescanning());
public void onReceive(Context context, Intent intent)
{
switch (intent.getAction())
{
case GameFileCacheManager.CACHE_UPDATED:
mView.showGames();
break;
case GameFileCacheManager.DONE_LOADING:
mView.setRefreshing(false);
break;
}
}
}; };
LocalBroadcastManager.getInstance(mContext).registerReceiver(mBroadcastReceiver, filter); GameFileCacheManager.isLoading().observe(mActivity, refreshObserver);
GameFileCacheManager.isRescanning().observe(mActivity, refreshObserver);
} }
public void onDestroy() public void onDestroy()
{ {
if (mBroadcastReceiver != null)
{
LocalBroadcastManager.getInstance(mContext).unregisterReceiver(mBroadcastReceiver);
}
} }
public void onFabClick() public void onFabClick()
@ -140,12 +121,11 @@ public final class MainPresenter
mDirToAdd = null; mDirToAdd = null;
} }
if (sShouldRescanLibrary && !GameFileCacheManager.isRescanning()) if (sShouldRescanLibrary && !GameFileCacheManager.isRescanning().getValue())
{ {
new AfterDirectoryInitializationRunner().run(mContext, false, () -> new AfterDirectoryInitializationRunner().run(mActivity, false, () ->
{ {
mView.setRefreshing(true); GameFileCacheManager.startRescan(mActivity);
GameFileCacheManager.startRescan(mContext);
}); });
} }
@ -172,20 +152,20 @@ public final class MainPresenter
if (Arrays.stream(childNames).noneMatch((name) -> FileBrowserHelper.GAME_EXTENSIONS.contains( if (Arrays.stream(childNames).noneMatch((name) -> FileBrowserHelper.GAME_EXTENSIONS.contains(
FileBrowserHelper.getExtension(name, false)))) FileBrowserHelper.getExtension(name, false))))
{ {
AlertDialog.Builder builder = new AlertDialog.Builder(mContext, R.style.DolphinDialogBase); AlertDialog.Builder builder = new AlertDialog.Builder(mActivity, R.style.DolphinDialogBase);
builder.setMessage(mContext.getString(R.string.wrong_file_extension_in_directory, builder.setMessage(mActivity.getString(R.string.wrong_file_extension_in_directory,
FileBrowserHelper.setToSortedDelimitedString(FileBrowserHelper.GAME_EXTENSIONS))); FileBrowserHelper.setToSortedDelimitedString(FileBrowserHelper.GAME_EXTENSIONS)));
builder.setPositiveButton(R.string.ok, null); builder.setPositiveButton(R.string.ok, null);
builder.show(); builder.show();
} }
ContentResolver contentResolver = mContext.getContentResolver(); ContentResolver contentResolver = mActivity.getContentResolver();
Uri canonicalizedUri = contentResolver.canonicalize(uri); Uri canonicalizedUri = contentResolver.canonicalize(uri);
if (canonicalizedUri != null) if (canonicalizedUri != null)
uri = canonicalizedUri; uri = canonicalizedUri;
int takeFlags = result.getFlags() & Intent.FLAG_GRANT_READ_URI_PERMISSION; int takeFlags = result.getFlags() & Intent.FLAG_GRANT_READ_URI_PERMISSION;
mContext.getContentResolver().takePersistableUriPermission(uri, takeFlags); mActivity.getContentResolver().takePersistableUriPermission(uri, takeFlags);
mDirToAdd = uri.toString(); mDirToAdd = uri.toString();
} }
@ -196,24 +176,22 @@ public final class MainPresenter
{ {
boolean success = WiiUtils.installWAD(path); boolean success = WiiUtils.installWAD(path);
int message = success ? R.string.wad_install_success : R.string.wad_install_failure; int message = success ? R.string.wad_install_success : R.string.wad_install_failure;
return mContext.getResources().getString(message); return mActivity.getResources().getString(message);
}); });
} }
public void importWiiSave(String path) public void importWiiSave(String path)
{ {
final Activity mainPresenterActivity = (Activity) mContext;
CompletableFuture<Boolean> canOverwriteFuture = new CompletableFuture<>(); CompletableFuture<Boolean> canOverwriteFuture = new CompletableFuture<>();
runOnThreadAndShowResult(R.string.import_in_progress, 0, () -> runOnThreadAndShowResult(R.string.import_in_progress, 0, () ->
{ {
BooleanSupplier canOverwrite = () -> BooleanSupplier canOverwrite = () ->
{ {
mainPresenterActivity.runOnUiThread(() -> mActivity.runOnUiThread(() ->
{ {
AlertDialog.Builder builder = AlertDialog.Builder builder =
new AlertDialog.Builder(mContext, R.style.DolphinDialogBase); new AlertDialog.Builder(mActivity, R.style.DolphinDialogBase);
builder.setMessage(R.string.wii_save_exists); builder.setMessage(R.string.wii_save_exists);
builder.setCancelable(false); builder.setCancelable(false);
builder.setPositiveButton(R.string.yes, (dialog, i) -> canOverwriteFuture.complete(true)); builder.setPositiveButton(R.string.yes, (dialog, i) -> canOverwriteFuture.complete(true));
@ -252,14 +230,14 @@ public final class MainPresenter
message = R.string.wii_save_import_error; message = R.string.wii_save_import_error;
break; break;
} }
return mContext.getResources().getString(message); return mActivity.getResources().getString(message);
}); });
} }
public void importNANDBin(String path) public void importNANDBin(String path)
{ {
AlertDialog.Builder builder = AlertDialog.Builder builder =
new AlertDialog.Builder(mContext, R.style.DolphinDialogBase); new AlertDialog.Builder(mActivity, R.style.DolphinDialogBase);
builder.setMessage(R.string.nand_import_warning); builder.setMessage(R.string.nand_import_warning);
builder.setNegativeButton(R.string.no, (dialog, i) -> dialog.dismiss()); builder.setNegativeButton(R.string.no, (dialog, i) -> dialog.dismiss());
@ -281,33 +259,31 @@ public final class MainPresenter
private void runOnThreadAndShowResult(int progressTitle, int progressMessage, Supplier<String> f) private void runOnThreadAndShowResult(int progressTitle, int progressMessage, Supplier<String> f)
{ {
final Activity mainPresenterActivity = (Activity) mContext; AlertDialog progressDialog = new AlertDialog.Builder(mActivity, R.style.DolphinDialogBase)
AlertDialog progressDialog = new AlertDialog.Builder(mContext, R.style.DolphinDialogBase)
.create(); .create();
progressDialog.setTitle(progressTitle); progressDialog.setTitle(progressTitle);
if (progressMessage != 0) if (progressMessage != 0)
progressDialog.setMessage(mContext.getResources().getString(progressMessage)); progressDialog.setMessage(mActivity.getResources().getString(progressMessage));
progressDialog.setCancelable(false); progressDialog.setCancelable(false);
progressDialog.show(); progressDialog.show();
new Thread(() -> new Thread(() ->
{ {
String result = f.get(); String result = f.get();
mainPresenterActivity.runOnUiThread(() -> mActivity.runOnUiThread(() ->
{ {
progressDialog.dismiss(); progressDialog.dismiss();
if (result != null) if (result != null)
{ {
AlertDialog.Builder builder = AlertDialog.Builder builder =
new AlertDialog.Builder(mContext, R.style.DolphinDialogBase); new AlertDialog.Builder(mActivity, R.style.DolphinDialogBase);
builder.setMessage(result); builder.setMessage(result);
builder.setPositiveButton(R.string.ok, (dialog, i) -> dialog.dismiss()); builder.setPositiveButton(R.string.ok, (dialog, i) -> dialog.dismiss());
builder.show(); builder.show();
} }
}); });
}, mContext.getResources().getString(progressTitle)).start(); }, mActivity.getResources().getString(progressTitle)).start();
} }
public static void skipRescanningLibrary() public static void skipRescanningLibrary()

View File

@ -122,7 +122,7 @@ public final class TvMainActivity extends FragmentActivity
mSwipeRefresh.setOnRefreshListener(this); mSwipeRefresh.setOnRefreshListener(this);
setRefreshing(GameFileCacheManager.isLoading()); setRefreshing(GameFileCacheManager.isLoadingOrRescanning());
final FragmentManager fragmentManager = getSupportFragmentManager(); final FragmentManager fragmentManager = getSupportFragmentManager();
mBrowseFragment = new BrowseSupportFragment(); mBrowseFragment = new BrowseSupportFragment();

View File

@ -73,7 +73,7 @@ public final class PlatformGamesFragment extends Fragment implements PlatformGam
mRecyclerView.addItemDecoration(new GameAdapter.SpacesItemDecoration(8)); mRecyclerView.addItemDecoration(new GameAdapter.SpacesItemDecoration(8));
setRefreshing(GameFileCacheManager.isLoading()); setRefreshing(GameFileCacheManager.isLoadingOrRescanning());
showGames(); showGames();
} }