Merge pull request #8196 from JosJuice/android-applinkactivity-race

Android: Fix race condition in AppLinkActivity
This commit is contained in:
Anthony 2019-08-21 10:14:08 -07:00 committed by GitHub
commit 998c171b65
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 64 additions and 11 deletions

View File

@ -1,5 +1,7 @@
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.content.IntentFilter;
import android.net.Uri; import android.net.Uri;
@ -26,6 +28,7 @@ public class AppLinkActivity extends FragmentActivity
private AppLinkHelper.PlayAction playAction; private AppLinkHelper.PlayAction playAction;
private DirectoryStateReceiver directoryStateReceiver; private DirectoryStateReceiver directoryStateReceiver;
private BroadcastReceiver gameFileCacheReceiver;
@Override @Override
protected void onCreate(Bundle savedInstanceState) protected void onCreate(Bundle savedInstanceState)
@ -63,16 +66,19 @@ public class AppLinkActivity extends FragmentActivity
*/ */
private void initResources() private void initResources()
{ {
IntentFilter statusIntentFilter = new IntentFilter( IntentFilter directoryStateIntentFilter = new IntentFilter(
DirectoryInitialization.BROADCAST_ACTION); DirectoryInitialization.BROADCAST_ACTION);
IntentFilter gameFileCacheIntentFilter = new IntentFilter(
GameFileCacheService.BROADCAST_ACTION);
directoryStateReceiver = directoryStateReceiver =
new DirectoryStateReceiver(directoryInitializationState -> new DirectoryStateReceiver(directoryInitializationState ->
{ {
if (directoryInitializationState == if (directoryInitializationState ==
DirectoryInitialization.DirectoryInitializationState.DOLPHIN_DIRECTORIES_INITIALIZED) DirectoryInitialization.DirectoryInitializationState.DOLPHIN_DIRECTORIES_INITIALIZED)
{ {
play(playAction); tryPlay(playAction);
} }
else if (directoryInitializationState == else if (directoryInitializationState ==
DirectoryInitialization.DirectoryInitializationState.EXTERNAL_STORAGE_PERMISSION_NEEDED) DirectoryInitialization.DirectoryInitializationState.EXTERNAL_STORAGE_PERMISSION_NEEDED)
@ -88,10 +94,23 @@ public class AppLinkActivity extends FragmentActivity
} }
}); });
// Registers the DirectoryStateReceiver and its intent filters gameFileCacheReceiver =
LocalBroadcastManager.getInstance(this).registerReceiver( new BroadcastReceiver()
directoryStateReceiver, {
statusIntentFilter); @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); DirectoryInitialization.start(this);
GameFileCacheService.startLoad(this); GameFileCacheService.startLoad(this);
} }
@ -107,17 +126,31 @@ public class AppLinkActivity extends FragmentActivity
finish(); 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 * 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 " Log.d(TAG, "Playing game "
+ action.getGameId() + action.getGameId()
+ " from channel " + " from channel "
+ action.getChannelId()); + action.getChannelId());
GameFile game = GameFileCacheService.getGameFileByGameId(action.getGameId());
if (game == null) if (game == null)
Log.e(TAG, "Invalid Game: " + action.getGameId()); Log.e(TAG, "Invalid Game: " + action.getGameId());
else else

View File

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