Android: Fix race condition in AppLinkActivity

https://bugs.dolphin-emu.org/issues/11767
This commit is contained in:
JosJuice 2019-06-16 20:39:54 +02:00
parent c34388f75b
commit e4ef2193e0
2 changed files with 64 additions and 11 deletions

View File

@ -1,5 +1,7 @@
package org.dolphinemu.dolphinemu.activities;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.Uri;
@ -26,6 +28,7 @@ public class AppLinkActivity extends FragmentActivity
private AppLinkHelper.PlayAction playAction;
private DirectoryStateReceiver directoryStateReceiver;
private BroadcastReceiver gameFileCacheReceiver;
@Override
protected void onCreate(Bundle savedInstanceState)
@ -63,16 +66,19 @@ public class AppLinkActivity extends FragmentActivity
*/
private void initResources()
{
IntentFilter statusIntentFilter = new IntentFilter(
IntentFilter directoryStateIntentFilter = new IntentFilter(
DirectoryInitialization.BROADCAST_ACTION);
IntentFilter gameFileCacheIntentFilter = new IntentFilter(
GameFileCacheService.BROADCAST_ACTION);
directoryStateReceiver =
new DirectoryStateReceiver(directoryInitializationState ->
{
if (directoryInitializationState ==
DirectoryInitialization.DirectoryInitializationState.DOLPHIN_DIRECTORIES_INITIALIZED)
{
play(playAction);
tryPlay(playAction);
}
else if (directoryInitializationState ==
DirectoryInitialization.DirectoryInitializationState.EXTERNAL_STORAGE_PERMISSION_NEEDED)
@ -88,10 +94,23 @@ public class AppLinkActivity extends FragmentActivity
}
});
// Registers the DirectoryStateReceiver and its intent filters
LocalBroadcastManager.getInstance(this).registerReceiver(
directoryStateReceiver,
statusIntentFilter);
gameFileCacheReceiver =
new BroadcastReceiver()
{
@Override
public void onReceive(Context context, Intent intent)
{
if (DirectoryInitialization.areDolphinDirectoriesReady())
{
tryPlay(playAction);
}
}
};
LocalBroadcastManager broadcastManager = LocalBroadcastManager.getInstance(this);
broadcastManager.registerReceiver(directoryStateReceiver, directoryStateIntentFilter);
broadcastManager.registerReceiver(gameFileCacheReceiver, gameFileCacheIntentFilter);
DirectoryInitialization.start(this);
GameFileCacheService.startLoad(this);
}
@ -107,17 +126,31 @@ public class AppLinkActivity extends FragmentActivity
finish();
}
private void tryPlay(AppLinkHelper.PlayAction action)
{
// TODO: This approach of getting the game from the game file cache without rescanning
// the library means that we can fail to launch games if the cache file has been deleted.
GameFile game = GameFileCacheService.getGameFileByGameId(action.getGameId());
// 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 || GameFileCacheService.hasLoadedCache())
{
play(action, game);
}
}
/**
* Action if program(game) is selected
*/
private void play(AppLinkHelper.PlayAction action)
private void play(AppLinkHelper.PlayAction action, GameFile game)
{
Log.d(TAG, "Playing game "
+ action.getGameId()
+ " from channel "
+ action.getChannelId());
GameFile game = GameFileCacheService.getGameFileByGameId(action.getGameId());
if (game == null)
Log.e(TAG, "Invalid Game: " + action.getGameId());
else

View File

@ -13,6 +13,7 @@ import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
/**
@ -27,6 +28,8 @@ public final class GameFileCacheService extends IntentService
private static GameFileCache gameFileCache = null;
private static AtomicReference<GameFile[]> gameFiles = new AtomicReference<>(new GameFile[]{});
private static AtomicBoolean hasLoadedCache = new AtomicBoolean(false);
private static AtomicBoolean hasScannedLibrary = new AtomicBoolean(false);
public GameFileCacheService()
{
@ -81,6 +84,16 @@ public final class GameFileCacheService extends IntentService
return matchWithoutRevision;
}
public static boolean hasLoadedCache()
{
return hasLoadedCache.get();
}
public static boolean hasScannedLibrary()
{
return hasScannedLibrary.get();
}
private static void startService(Context context, String action)
{
Intent intent = new Intent(context, GameFileCacheService.class);
@ -130,6 +143,8 @@ public final class GameFileCacheService extends IntentService
gameFileCache = temp;
gameFileCache.load();
updateGameFileArray();
hasLoadedCache.set(true);
sendBroadcast();
}
}
@ -138,10 +153,11 @@ public final class GameFileCacheService extends IntentService
{
synchronized (gameFileCache)
{
if (gameFileCache.scanLibrary(this))
{
boolean changed = gameFileCache.scanLibrary(this);
if (changed)
updateGameFileArray();
}
hasScannedLibrary.set(true);
sendBroadcast();
}
}
}
@ -151,6 +167,10 @@ public final class GameFileCacheService extends IntentService
GameFile[] gameFilesTemp = gameFileCache.getAllGames();
Arrays.sort(gameFilesTemp, (lhs, rhs) -> lhs.getTitle().compareToIgnoreCase(rhs.getTitle()));
gameFiles.set(gameFilesTemp);
}
private void sendBroadcast()
{
LocalBroadcastManager.getInstance(this).sendBroadcast(new Intent(BROADCAST_ACTION));
}
}