@@ -1,179 +1,180 @@
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 org.dolphinemu.dolphinemu.utils.AfterDirectoryInitializationRunner;

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;

/**
* 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<GameFile[]> gameFiles = new AtomicReference<>(new GameFile[]{});
private static AtomicBoolean hasLoadedCache = new AtomicBoolean(false);
private static AtomicBoolean hasScannedLibrary = new AtomicBoolean(false);

public GameFileCacheService()
{
// Superclass constructor is called to name the thread on which this service executes.
super("GameFileCacheService");
}

public static List<GameFile> getGameFilesForPlatform(Platform platform)
{
GameFile[] allGames = gameFiles.get();
ArrayList<GameFile> platformGames = new ArrayList<>();
for (GameFile game : allGames)
{
if (Platform.fromNativeInt(game.getPlatform()) == platform)
{
platformGames.add(game);
}
}
return platformGames;
}

public static GameFile getGameFileByGameId(String gameId)
{
GameFile[] allGames = gameFiles.get();
for (GameFile game : allGames)
{
if (game.getGameId().equals(gameId))
{
return game;
}
}
return null;
}

public static GameFile findSecondDisc(GameFile game)
{
GameFile matchWithoutRevision = null;

GameFile[] allGames = gameFiles.get();
for (GameFile otherGame : allGames)
{
if (game.getGameId().equals(otherGame.getGameId()) &&
game.getDiscNumber() != otherGame.getDiscNumber())
{
if (game.getRevision() == otherGame.getRevision())
return otherGame;
else
matchWithoutRevision = otherGame;
}
}

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);
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)
{
new AfterDirectoryInitializationRunner().run(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)
{
new AfterDirectoryInitializationRunner().run(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();
hasLoadedCache.set(true);
sendBroadcast();
}
}

// Rescan the file system and update the game list cache with the results
if (ACTION_RESCAN.equals(intent.getAction()) && gameFileCache != null)
{
synchronized (gameFileCache)
{
boolean changed = gameFileCache.scanLibrary(this);
if (changed)
updateGameFileArray();
hasScannedLibrary.set(true);
sendBroadcast();
}
}
}

private void updateGameFileArray()
{
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));
}
}
package org.dolphinemu.dolphinemu.services;

import android.app.IntentService;
import android.content.Context;
import android.content.Intent;

import androidx.localbroadcastmanager.content.LocalBroadcastManager;

import org.dolphinemu.dolphinemu.model.GameFile;
import org.dolphinemu.dolphinemu.model.GameFileCache;
import org.dolphinemu.dolphinemu.ui.platform.Platform;
import org.dolphinemu.dolphinemu.utils.AfterDirectoryInitializationRunner;

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;

/**
* 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<GameFile[]> gameFiles = new AtomicReference<>(new GameFile[]{});
private static AtomicBoolean hasLoadedCache = new AtomicBoolean(false);
private static AtomicBoolean hasScannedLibrary = new AtomicBoolean(false);

public GameFileCacheService()
{
// Superclass constructor is called to name the thread on which this service executes.
super("GameFileCacheService");
}

public static List<GameFile> getGameFilesForPlatform(Platform platform)
{
GameFile[] allGames = gameFiles.get();
ArrayList<GameFile> platformGames = new ArrayList<>();
for (GameFile game : allGames)
{
if (Platform.fromNativeInt(game.getPlatform()) == platform)
{
platformGames.add(game);
}
}
return platformGames;
}

public static GameFile getGameFileByGameId(String gameId)
{
GameFile[] allGames = gameFiles.get();
for (GameFile game : allGames)
{
if (game.getGameId().equals(gameId))
{
return game;
}
}
return null;
}

public static GameFile findSecondDisc(GameFile game)
{
GameFile matchWithoutRevision = null;

GameFile[] allGames = gameFiles.get();
for (GameFile otherGame : allGames)
{
if (game.getGameId().equals(otherGame.getGameId()) &&
game.getDiscNumber() != otherGame.getDiscNumber())
{
if (game.getRevision() == otherGame.getRevision())
return otherGame;
else
matchWithoutRevision = otherGame;
}
}

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);
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)
{
new AfterDirectoryInitializationRunner().run(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)
{
new AfterDirectoryInitializationRunner().run(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();
hasLoadedCache.set(true);
sendBroadcast();
}
}

// Rescan the file system and update the game list cache with the results
if (ACTION_RESCAN.equals(intent.getAction()) && gameFileCache != null)
{
synchronized (gameFileCache)
{
boolean changed = gameFileCache.scanLibrary(this);
if (changed)
updateGameFileArray();
hasScannedLibrary.set(true);
sendBroadcast();
}
}
}

private void updateGameFileArray()
{
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));
}
}
@@ -11,9 +11,11 @@
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.support.media.tv.Channel;
import android.support.media.tv.ChannelLogoUtils;
import android.support.media.tv.TvContractCompat;

import androidx.tvprovider.media.tv.Channel;
import androidx.tvprovider.media.tv.ChannelLogoUtils;
import androidx.tvprovider.media.tv.TvContractCompat;

import android.util.Log;

import org.dolphinemu.dolphinemu.R;
@@ -6,9 +6,11 @@
import android.net.Uri;
import android.os.AsyncTask;
import android.os.PersistableBundle;
import android.support.media.tv.Channel;
import android.support.media.tv.PreviewProgram;
import android.support.media.tv.TvContractCompat;

import androidx.tvprovider.media.tv.Channel;
import androidx.tvprovider.media.tv.PreviewProgram;
import androidx.tvprovider.media.tv.TvContractCompat;

import android.util.Log;

import org.dolphinemu.dolphinemu.R;
@@ -5,8 +5,10 @@
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;

import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

import android.util.AttributeSet;
import android.view.View;

@@ -2,7 +2,9 @@

import android.content.Context;
import android.graphics.drawable.Drawable;
import android.support.v17.leanback.widget.TitleViewAdapter;

import androidx.leanback.widget.TitleViewAdapter;

import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
@@ -79,4 +81,4 @@ public TitleViewAdapter getTitleViewAdapter()
{
return mTitleViewAdapter;
}
}
}
@@ -3,12 +3,16 @@
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.TabLayout;
import android.support.v4.view.ViewPager;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;

import androidx.annotation.Nullable;

import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.android.material.tabs.TabLayout;

import androidx.viewpager.widget.ViewPager;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;

import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
@@ -4,7 +4,8 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.support.v4.content.LocalBroadcastManager;

import androidx.localbroadcastmanager.content.LocalBroadcastManager;

import org.dolphinemu.dolphinemu.BuildConfig;
import org.dolphinemu.dolphinemu.R;
@@ -3,15 +3,17 @@
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.support.v17.leanback.app.BrowseFragment;
import android.support.v17.leanback.app.BrowseSupportFragment;
import android.support.v17.leanback.widget.ArrayObjectAdapter;
import android.support.v17.leanback.widget.HeaderItem;
import android.support.v17.leanback.widget.ListRow;
import android.support.v17.leanback.widget.ListRowPresenter;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentManager;
import android.support.v4.content.ContextCompat;

import androidx.leanback.app.BrowseFragment;
import androidx.leanback.app.BrowseSupportFragment;
import androidx.leanback.widget.ArrayObjectAdapter;
import androidx.leanback.widget.HeaderItem;
import androidx.leanback.widget.ListRow;
import androidx.leanback.widget.ListRowPresenter;
import androidx.fragment.app.FragmentActivity;
import androidx.fragment.app.FragmentManager;
import androidx.core.content.ContextCompat;

import android.widget.Toast;

import org.dolphinemu.dolphinemu.R;
@@ -1,10 +1,12 @@
package org.dolphinemu.dolphinemu.ui.platform;

import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;

import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -2,7 +2,8 @@

import android.content.Context;
import android.content.IntentFilter;
import android.support.v4.content.LocalBroadcastManager;

import androidx.localbroadcastmanager.content.LocalBroadcastManager;

public class AfterDirectoryInitializationRunner
{
@@ -2,11 +2,9 @@

import android.app.AlertDialog;
import android.content.Context;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.os.Build;
import android.preference.PreferenceManager;
import android.support.v4.content.LocalBroadcastManager;

import com.android.volley.Request;
import com.android.volley.toolbox.StringRequest;
@@ -1,7 +1,8 @@
package org.dolphinemu.dolphinemu.utils;

import android.net.Uri;
import android.support.annotation.StringDef;

import androidx.annotation.StringDef;

import java.util.List;

@@ -11,7 +11,8 @@
import android.content.SharedPreferences;
import android.os.Environment;
import android.preference.PreferenceManager;
import android.support.v4.content.LocalBroadcastManager;

import androidx.localbroadcastmanager.content.LocalBroadcastManager;

import org.dolphinemu.dolphinemu.NativeLibrary;

@@ -3,8 +3,9 @@
import android.content.Intent;
import android.net.Uri;
import android.os.Environment;
import android.support.annotation.Nullable;
import android.support.v4.app.FragmentActivity;

import androidx.annotation.Nullable;
import androidx.fragment.app.FragmentActivity;

import com.nononsenseapps.filepicker.FilePickerActivity;
import com.nononsenseapps.filepicker.Utils;
@@ -6,8 +6,10 @@
import android.content.DialogInterface;
import android.content.pm.PackageManager;
import android.os.Build;
import android.support.v4.app.FragmentActivity;
import android.support.v4.content.ContextCompat;

import androidx.fragment.app.FragmentActivity;
import androidx.core.content.ContextCompat;

import android.widget.Toast;

import org.dolphinemu.dolphinemu.R;
@@ -1,11 +1,12 @@
package org.dolphinemu.dolphinemu.utils;

import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.v4.app.FragmentActivity;

import androidx.fragment.app.FragmentActivity;

import android.text.TextUtils;

import org.dolphinemu.dolphinemu.NativeLibrary;
@@ -18,10 +18,12 @@
import android.net.Uri;
import android.os.Build;
import android.os.PersistableBundle;
import android.support.annotation.AnyRes;
import android.support.annotation.NonNull;
import android.support.media.tv.Channel;
import android.support.media.tv.TvContractCompat;

import androidx.annotation.AnyRes;
import androidx.annotation.NonNull;
import androidx.tvprovider.media.tv.Channel;
import androidx.tvprovider.media.tv.TvContractCompat;

import android.util.Log;

import org.dolphinemu.dolphinemu.model.GameFile;
@@ -35,7 +37,7 @@
import java.util.List;

import static android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION;
import static android.support.v4.content.FileProvider.getUriForFile;
import static androidx.core.content.FileProvider.getUriForFile;

/**
* Assists in TV related services, e.g., home screen channels
@@ -1,6 +1,7 @@
package org.dolphinemu.dolphinemu.viewholders;

import android.support.v7.widget.RecyclerView;
import androidx.recyclerview.widget.RecyclerView;

import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
@@ -1,6 +1,7 @@
package org.dolphinemu.dolphinemu.viewholders;

import android.support.v7.widget.RecyclerView;
import androidx.recyclerview.widget.RecyclerView;

import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
@@ -1,7 +1,8 @@
package org.dolphinemu.dolphinemu.viewholders;

import android.support.v17.leanback.widget.ImageCardView;
import android.support.v17.leanback.widget.Presenter;
import androidx.leanback.widget.ImageCardView;
import androidx.leanback.widget.Presenter;

import android.view.View;
import android.widget.ImageView;

@@ -1,7 +1,8 @@
package org.dolphinemu.dolphinemu.viewholders;

import android.support.v17.leanback.widget.ImageCardView;
import android.support.v17.leanback.widget.Presenter;
import androidx.leanback.widget.ImageCardView;
import androidx.leanback.widget.Presenter;

import android.view.View;

public final class TvSettingsViewHolder extends Presenter.ViewHolder
@@ -5,7 +5,7 @@
android:layout_height="match_parent"
android:orientation="vertical">

<android.support.v7.widget.Toolbar
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar_folder_list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@@ -14,7 +14,7 @@
android:theme="@android:style/ThemeOverlay.Material.Dark.ActionBar"
android:elevation="6dp"/>

<android.support.v7.widget.RecyclerView
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/list_files"
android:layout_width="match_parent"
android:layout_height="match_parent"
@@ -24,4 +24,4 @@
android:elevation="4dp"
android:background="@android:color/white"/>

</LinearLayout>
</LinearLayout>
@@ -1,40 +1,40 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/coordinator_main"
android:layout_width="match_parent"
android:layout_height="match_parent">

<android.support.design.widget.AppBarLayout
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">

<android.support.v7.widget.Toolbar
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar_main"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
app:layout_scrollFlags="scroll|enterAlways"/>

<android.support.design.widget.TabLayout
<com.google.android.material.tabs.TabLayout
android:id="@+id/tabs_platforms"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:tabTextAppearance="@style/MyCustomTextAppearance"
app:tabMode="fixed"
app:tabGravity="fill"/>

</android.support.design.widget.AppBarLayout>
</com.google.android.material.appbar.AppBarLayout>

<android.support.v4.view.ViewPager
<androidx.viewpager.widget.ViewPager
android:id="@+id/pager_platforms"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior"/>

<android.support.design.widget.FloatingActionButton
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/button_add_directory"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
@@ -45,4 +45,4 @@
app:layout_anchor="@+id/pager_platforms"
app:layout_anchorGravity="bottom|right|end"/>

</android.support.design.widget.CoordinatorLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
tools:layout_width="160dp"
@@ -51,5 +51,5 @@

</LinearLayout>

</android.support.v7.widget.CardView>
</androidx.cardview.widget.CardView>

@@ -4,12 +4,12 @@
android:layout_width="match_parent"
android:layout_height="match_parent">

<android.support.v7.widget.RecyclerView
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/grid_games"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginLeft="@dimen/activity_horizontal_margin"
android:layout_marginRight="@dimen/activity_horizontal_margin"
tools:listitem="@layout/card_game"/>

</FrameLayout>
</FrameLayout>
@@ -3,7 +3,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent">

<android.support.v7.widget.RecyclerView
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/list_settings"
android:background="@android:color/white"
android:layout_width="match_parent"
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.v17.leanback.widget.TitleView
<androidx.leanback.widget.TitleView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
@@ -28,4 +28,4 @@
android:src="@drawable/ic_launcher"/>

</RelativeLayout>
</android.support.v17.leanback.widget.TitleView>
</androidx.leanback.widget.TitleView>
@@ -7,6 +7,8 @@
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
# Default value: -Xmx10248m -XX:MaxPermSize=256m
android.enableJetifier=true
android.useAndroidX=true
org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit