[Android] Refactor AssetCopyService and the way we extract resources from the assets
This solves the following issues: 1. If user uninstall Dolphin and install it again the resources will not be copied unless the user manually clear the app cache because we are enabling the allowBackup flag in the manifest which will make the app restore the settings saved in the shared prefernces including the flag assetsCopied. This PR always copy the files everytime you open Dolphin. 2. If the AssetCopyService took long time and you tried to open the settings screen or start a game the behaviour was not expected or the emulator will crash, this PR make sure that whatever we add to the DirectoryInitializationService or how long it will take will still work as expected by blocking both the settings screen and the emulaion screen to wait untill all resources needed are copied. 3. Better communication between the DirectoryInitializationService and the UI screens using brocast messages.
This commit is contained in:
parent
9f9b4bc028
commit
1190afef51
|
@ -65,7 +65,7 @@
|
||||||
android:theme="@style/DolphinEmulationGamecube"/>
|
android:theme="@style/DolphinEmulationGamecube"/>
|
||||||
|
|
||||||
|
|
||||||
<service android:name=".services.AssetCopyService"/>
|
<service android:name=".services.DirectoryInitializationService"/>
|
||||||
|
|
||||||
<provider
|
<provider
|
||||||
android:name=".model.GameProvider"
|
android:name=".model.GameProvider"
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
package org.dolphinemu.dolphinemu.fragments;
|
package org.dolphinemu.dolphinemu.fragments;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.content.IntentFilter;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.preference.PreferenceManager;
|
import android.preference.PreferenceManager;
|
||||||
import android.support.v4.app.Fragment;
|
import android.support.v4.app.Fragment;
|
||||||
|
import android.support.v4.content.LocalBroadcastManager;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.Surface;
|
import android.view.Surface;
|
||||||
import android.view.SurfaceHolder;
|
import android.view.SurfaceHolder;
|
||||||
|
@ -12,13 +14,19 @@ import android.view.SurfaceView;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.Button;
|
import android.widget.Button;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
import org.dolphinemu.dolphinemu.NativeLibrary;
|
import org.dolphinemu.dolphinemu.NativeLibrary;
|
||||||
import org.dolphinemu.dolphinemu.R;
|
import org.dolphinemu.dolphinemu.R;
|
||||||
import org.dolphinemu.dolphinemu.activities.EmulationActivity;
|
import org.dolphinemu.dolphinemu.activities.EmulationActivity;
|
||||||
import org.dolphinemu.dolphinemu.overlay.InputOverlay;
|
import org.dolphinemu.dolphinemu.overlay.InputOverlay;
|
||||||
|
import org.dolphinemu.dolphinemu.services.DirectoryInitializationService;
|
||||||
|
import org.dolphinemu.dolphinemu.services.DirectoryInitializationService.DirectoryInitializationState;
|
||||||
|
import org.dolphinemu.dolphinemu.utils.DirectoryStateReceiver;
|
||||||
import org.dolphinemu.dolphinemu.utils.Log;
|
import org.dolphinemu.dolphinemu.utils.Log;
|
||||||
|
|
||||||
|
import rx.functions.Action1;
|
||||||
|
|
||||||
public final class EmulationFragment extends Fragment implements SurfaceHolder.Callback
|
public final class EmulationFragment extends Fragment implements SurfaceHolder.Callback
|
||||||
{
|
{
|
||||||
private static final String KEY_GAMEPATH = "gamepath";
|
private static final String KEY_GAMEPATH = "gamepath";
|
||||||
|
@ -29,6 +37,8 @@ public final class EmulationFragment extends Fragment implements SurfaceHolder.C
|
||||||
|
|
||||||
private EmulationState mEmulationState;
|
private EmulationState mEmulationState;
|
||||||
|
|
||||||
|
private DirectoryStateReceiver directoryStateReceiver;
|
||||||
|
|
||||||
public static EmulationFragment newInstance(String gamePath)
|
public static EmulationFragment newInstance(String gamePath)
|
||||||
{
|
{
|
||||||
|
|
||||||
|
@ -108,12 +118,25 @@ public final class EmulationFragment extends Fragment implements SurfaceHolder.C
|
||||||
public void onResume()
|
public void onResume()
|
||||||
{
|
{
|
||||||
super.onResume();
|
super.onResume();
|
||||||
|
if (DirectoryInitializationService.areDolphinDirectoriesReady())
|
||||||
|
{
|
||||||
mEmulationState.run();
|
mEmulationState.run();
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
setupDolphinDirectoriesThenStartEmulation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPause()
|
public void onPause()
|
||||||
{
|
{
|
||||||
|
if (directoryStateReceiver != null)
|
||||||
|
{
|
||||||
|
LocalBroadcastManager.getInstance(getActivity()).unregisterReceiver(directoryStateReceiver);
|
||||||
|
directoryStateReceiver = null;
|
||||||
|
}
|
||||||
|
|
||||||
mEmulationState.pause();
|
mEmulationState.pause();
|
||||||
super.onPause();
|
super.onPause();
|
||||||
}
|
}
|
||||||
|
@ -125,6 +148,27 @@ public final class EmulationFragment extends Fragment implements SurfaceHolder.C
|
||||||
super.onDetach();
|
super.onDetach();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void setupDolphinDirectoriesThenStartEmulation() {
|
||||||
|
IntentFilter statusIntentFilter = new IntentFilter(
|
||||||
|
DirectoryInitializationService.BROADCAST_ACTION);
|
||||||
|
|
||||||
|
directoryStateReceiver =
|
||||||
|
new DirectoryStateReceiver(directoryInitializationState -> {
|
||||||
|
if (directoryInitializationState == DirectoryInitializationState.DOLPHIN_DIRECTORIES_INITIALIZED) {
|
||||||
|
mEmulationState.run();
|
||||||
|
} else if (directoryInitializationState == DirectoryInitializationState.EXTERNAL_STORAGE_PERMISSION_NEEDED) {
|
||||||
|
Toast.makeText(getContext(), R.string.write_permission_needed, Toast.LENGTH_SHORT)
|
||||||
|
.show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Registers the DirectoryStateReceiver and its intent filters
|
||||||
|
LocalBroadcastManager.getInstance(getActivity()).registerReceiver(
|
||||||
|
directoryStateReceiver,
|
||||||
|
statusIntentFilter);
|
||||||
|
DirectoryInitializationService.startService(getActivity());
|
||||||
|
}
|
||||||
|
|
||||||
public void toggleInputOverlayVisibility()
|
public void toggleInputOverlayVisibility()
|
||||||
{
|
{
|
||||||
SharedPreferences.Editor editor = mPreferences.edit();
|
SharedPreferences.Editor editor = mPreferences.edit();
|
||||||
|
|
|
@ -1,112 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright 2014 Dolphin Emulator Project
|
|
||||||
* Licensed under GPLv2+
|
|
||||||
* Refer to the license.txt file included.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.dolphinemu.dolphinemu.services;
|
|
||||||
|
|
||||||
import android.app.IntentService;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.SharedPreferences;
|
|
||||||
import android.preference.PreferenceManager;
|
|
||||||
|
|
||||||
import org.dolphinemu.dolphinemu.NativeLibrary;
|
|
||||||
import org.dolphinemu.dolphinemu.utils.Log;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A service that spawns its own thread in order to copy several binary and shader files
|
|
||||||
* from the Dolphin APK to the external file system.
|
|
||||||
*/
|
|
||||||
public final class AssetCopyService extends IntentService
|
|
||||||
{
|
|
||||||
public AssetCopyService()
|
|
||||||
{
|
|
||||||
// Superclass constructor is called to name the thread on which this service executes.
|
|
||||||
super("AssetCopyService");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onHandleIntent(Intent intent)
|
|
||||||
{
|
|
||||||
String BaseDir = NativeLibrary.GetUserDirectory();
|
|
||||||
String ConfigDir = BaseDir + File.separator + "Config";
|
|
||||||
|
|
||||||
// Copy assets if needed
|
|
||||||
NativeLibrary.CreateUserFolders();
|
|
||||||
copyAssetFolder("GC", BaseDir + File.separator + "GC", false);
|
|
||||||
copyAssetFolder("Shaders", BaseDir + File.separator + "Shaders", false);
|
|
||||||
copyAssetFolder("Wii", BaseDir + File.separator + "Wii", false);
|
|
||||||
|
|
||||||
// Always copy over the GCPad config in case of change or corruption.
|
|
||||||
// Not a user configurable file.
|
|
||||||
copyAsset("GCPadNew.ini", ConfigDir + File.separator + "GCPadNew.ini", true);
|
|
||||||
copyAsset("WiimoteNew.ini", ConfigDir + File.separator + "WiimoteNew.ini", true);
|
|
||||||
|
|
||||||
// Record the fact that we've done this before, so we don't do it on every launch.
|
|
||||||
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
|
|
||||||
SharedPreferences.Editor editor = preferences.edit();
|
|
||||||
|
|
||||||
editor.putBoolean("assetsCopied", true);
|
|
||||||
editor.apply();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void copyAsset(String asset, String output, Boolean overwrite)
|
|
||||||
{
|
|
||||||
Log.verbose("[AssetCopyService] Copying File " + asset + " to " + output);
|
|
||||||
InputStream in;
|
|
||||||
OutputStream out;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
File file = new File(output);
|
|
||||||
if(!file.exists() || overwrite)
|
|
||||||
{
|
|
||||||
in = getAssets().open(asset);
|
|
||||||
out = new FileOutputStream(output);
|
|
||||||
copyFile(in, out);
|
|
||||||
in.close();
|
|
||||||
out.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (IOException e)
|
|
||||||
{
|
|
||||||
Log.error("[AssetCopyService] Failed to copy asset file: " + asset + e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void copyAssetFolder(String assetFolder, String outputFolder, Boolean overwrite)
|
|
||||||
{
|
|
||||||
Log.verbose("[AssetCopyService] Copying Folder " + assetFolder + " to " + outputFolder);
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
for (String file : getAssets().list(assetFolder))
|
|
||||||
{
|
|
||||||
copyAssetFolder(assetFolder + File.separator + file, outputFolder + File.separator + file, overwrite);
|
|
||||||
copyAsset(assetFolder + File.separator + file, outputFolder + File.separator + file, overwrite);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (IOException e)
|
|
||||||
{
|
|
||||||
Log.error("[AssetCopyService] Failed to copy asset folder: " + assetFolder + e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void copyFile(InputStream in, OutputStream out) throws IOException
|
|
||||||
{
|
|
||||||
byte[] buffer = new byte[1024];
|
|
||||||
int read;
|
|
||||||
|
|
||||||
while ((read = in.read(buffer)) != -1)
|
|
||||||
{
|
|
||||||
out.write(buffer, 0, read);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,154 @@
|
||||||
|
/**
|
||||||
|
* Copyright 2014 Dolphin Emulator Project
|
||||||
|
* Licensed under GPLv2+
|
||||||
|
* Refer to the license.txt file included.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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.NativeLibrary;
|
||||||
|
import org.dolphinemu.dolphinemu.utils.Log;
|
||||||
|
import org.dolphinemu.dolphinemu.utils.PermissionsHandler;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A service that spawns its own thread in order to copy several binary and shader files
|
||||||
|
* from the Dolphin APK to the external file system.
|
||||||
|
*/
|
||||||
|
public final class DirectoryInitializationService extends IntentService
|
||||||
|
{
|
||||||
|
public static final String BROADCAST_ACTION = "org.dolphinemu.dolphinemu.BROADCAST";
|
||||||
|
|
||||||
|
public static final String EXTRA_STATE = "directoryState";
|
||||||
|
private static DirectoryInitializationState directoryState = null;
|
||||||
|
|
||||||
|
public enum DirectoryInitializationState
|
||||||
|
{
|
||||||
|
DOLPHIN_DIRECTORIES_INITIALIZED,
|
||||||
|
EXTERNAL_STORAGE_PERMISSION_NEEDED
|
||||||
|
}
|
||||||
|
|
||||||
|
public DirectoryInitializationService()
|
||||||
|
{
|
||||||
|
// Superclass constructor is called to name the thread on which this service executes.
|
||||||
|
super("DirectoryInitializationService");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void startService(Context context)
|
||||||
|
{
|
||||||
|
Intent intent = new Intent(context, DirectoryInitializationService.class);
|
||||||
|
context.startService(intent);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onHandleIntent(Intent intent)
|
||||||
|
{
|
||||||
|
if (directoryState == DirectoryInitializationState.DOLPHIN_DIRECTORIES_INITIALIZED)
|
||||||
|
{
|
||||||
|
sendBroadcastState(DirectoryInitializationState.DOLPHIN_DIRECTORIES_INITIALIZED);
|
||||||
|
}
|
||||||
|
else if (PermissionsHandler.hasWriteAccess(this))
|
||||||
|
{
|
||||||
|
initDolphinDirectories();
|
||||||
|
directoryState = DirectoryInitializationState.DOLPHIN_DIRECTORIES_INITIALIZED;
|
||||||
|
sendBroadcastState(directoryState);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sendBroadcastState(DirectoryInitializationState.EXTERNAL_STORAGE_PERMISSION_NEEDED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initDolphinDirectories()
|
||||||
|
{
|
||||||
|
String BaseDir = NativeLibrary.GetUserDirectory();
|
||||||
|
String ConfigDir = BaseDir + File.separator + "Config";
|
||||||
|
|
||||||
|
// Copy assets if needed
|
||||||
|
NativeLibrary.CreateUserFolders();
|
||||||
|
copyAssetFolder("GC", BaseDir + File.separator + "GC", false);
|
||||||
|
copyAssetFolder("Shaders", BaseDir + File.separator + "Shaders", false);
|
||||||
|
copyAssetFolder("Wii", BaseDir + File.separator + "Wii", false);
|
||||||
|
|
||||||
|
// Always copy over the GCPad config in case of change or corruption.
|
||||||
|
// Not a user configurable file.
|
||||||
|
copyAsset("GCPadNew.ini", ConfigDir + File.separator + "GCPadNew.ini", true);
|
||||||
|
copyAsset("WiimoteNew.ini", ConfigDir + File.separator + "WiimoteNew.ini", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean areDolphinDirectoriesReady()
|
||||||
|
{
|
||||||
|
return directoryState == DirectoryInitializationState.DOLPHIN_DIRECTORIES_INITIALIZED;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendBroadcastState(DirectoryInitializationState state)
|
||||||
|
{
|
||||||
|
Intent localIntent =
|
||||||
|
new Intent(BROADCAST_ACTION)
|
||||||
|
.putExtra(EXTRA_STATE, state);
|
||||||
|
LocalBroadcastManager.getInstance(this).sendBroadcast(localIntent);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void copyAsset(String asset, String output, Boolean overwrite)
|
||||||
|
{
|
||||||
|
Log.verbose("[DirectoryInitializationService] Copying File " + asset + " to " + output);
|
||||||
|
InputStream in;
|
||||||
|
OutputStream out;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
File file = new File(output);
|
||||||
|
if (!file.exists() || overwrite)
|
||||||
|
{
|
||||||
|
in = getAssets().open(asset);
|
||||||
|
out = new FileOutputStream(output);
|
||||||
|
copyFile(in, out);
|
||||||
|
in.close();
|
||||||
|
out.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (IOException e)
|
||||||
|
{
|
||||||
|
Log.error("[DirectoryInitializationService] Failed to copy asset file: " + asset + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void copyAssetFolder(String assetFolder, String outputFolder, Boolean overwrite)
|
||||||
|
{
|
||||||
|
Log.verbose("[DirectoryInitializationService] Copying Folder " + assetFolder + " to " + outputFolder);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
for (String file : getAssets().list(assetFolder))
|
||||||
|
{
|
||||||
|
copyAssetFolder(assetFolder + File.separator + file, outputFolder + File.separator + file, overwrite);
|
||||||
|
copyAsset(assetFolder + File.separator + file, outputFolder + File.separator + file, overwrite);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (IOException e)
|
||||||
|
{
|
||||||
|
Log.error("[DirectoryInitializationService] Failed to copy asset folder: " + assetFolder + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void copyFile(InputStream in, OutputStream out) throws IOException
|
||||||
|
{
|
||||||
|
byte[] buffer = new byte[1024];
|
||||||
|
int read;
|
||||||
|
|
||||||
|
while ((read = in.read(buffer)) != -1)
|
||||||
|
{
|
||||||
|
out.write(buffer, 0, read);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -20,6 +20,7 @@ import org.dolphinemu.dolphinemu.R;
|
||||||
import org.dolphinemu.dolphinemu.activities.AddDirectoryActivity;
|
import org.dolphinemu.dolphinemu.activities.AddDirectoryActivity;
|
||||||
import org.dolphinemu.dolphinemu.adapters.PlatformPagerAdapter;
|
import org.dolphinemu.dolphinemu.adapters.PlatformPagerAdapter;
|
||||||
import org.dolphinemu.dolphinemu.model.GameProvider;
|
import org.dolphinemu.dolphinemu.model.GameProvider;
|
||||||
|
import org.dolphinemu.dolphinemu.services.DirectoryInitializationService;
|
||||||
import org.dolphinemu.dolphinemu.ui.platform.Platform;
|
import org.dolphinemu.dolphinemu.ui.platform.Platform;
|
||||||
import org.dolphinemu.dolphinemu.ui.platform.PlatformGamesView;
|
import org.dolphinemu.dolphinemu.ui.platform.PlatformGamesView;
|
||||||
import org.dolphinemu.dolphinemu.ui.settings.SettingsActivity;
|
import org.dolphinemu.dolphinemu.ui.settings.SettingsActivity;
|
||||||
|
@ -154,7 +155,7 @@ public final class MainActivity extends AppCompatActivity implements MainView
|
||||||
switch (requestCode) {
|
switch (requestCode) {
|
||||||
case PermissionsHandler.REQUEST_CODE_WRITE_PERMISSION:
|
case PermissionsHandler.REQUEST_CODE_WRITE_PERMISSION:
|
||||||
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||||
StartupHandler.copyAssetsIfNeeded(this);
|
DirectoryInitializationService.startService(this);
|
||||||
|
|
||||||
PlatformPagerAdapter platformPagerAdapter = new PlatformPagerAdapter(
|
PlatformPagerAdapter platformPagerAdapter = new PlatformPagerAdapter(
|
||||||
getSupportFragmentManager(), this);
|
getSupportFragmentManager(), this);
|
||||||
|
|
|
@ -28,6 +28,7 @@ import org.dolphinemu.dolphinemu.adapters.GameRowPresenter;
|
||||||
import org.dolphinemu.dolphinemu.adapters.SettingsRowPresenter;
|
import org.dolphinemu.dolphinemu.adapters.SettingsRowPresenter;
|
||||||
import org.dolphinemu.dolphinemu.model.Game;
|
import org.dolphinemu.dolphinemu.model.Game;
|
||||||
import org.dolphinemu.dolphinemu.model.TvSettingsItem;
|
import org.dolphinemu.dolphinemu.model.TvSettingsItem;
|
||||||
|
import org.dolphinemu.dolphinemu.services.DirectoryInitializationService;
|
||||||
import org.dolphinemu.dolphinemu.ui.platform.Platform;
|
import org.dolphinemu.dolphinemu.ui.platform.Platform;
|
||||||
import org.dolphinemu.dolphinemu.ui.settings.SettingsActivity;
|
import org.dolphinemu.dolphinemu.ui.settings.SettingsActivity;
|
||||||
import org.dolphinemu.dolphinemu.utils.PermissionsHandler;
|
import org.dolphinemu.dolphinemu.utils.PermissionsHandler;
|
||||||
|
@ -157,7 +158,7 @@ public final class TvMainActivity extends FragmentActivity implements MainView
|
||||||
switch (requestCode) {
|
switch (requestCode) {
|
||||||
case PermissionsHandler.REQUEST_CODE_WRITE_PERMISSION:
|
case PermissionsHandler.REQUEST_CODE_WRITE_PERMISSION:
|
||||||
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||||
StartupHandler.copyAssetsIfNeeded(this);
|
DirectoryInitializationService.startService(this);
|
||||||
loadGames();
|
loadGames();
|
||||||
} else {
|
} else {
|
||||||
Toast.makeText(this, R.string.write_permission_needed, Toast.LENGTH_SHORT)
|
Toast.makeText(this, R.string.write_permission_needed, Toast.LENGTH_SHORT)
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
package org.dolphinemu.dolphinemu.ui.settings;
|
package org.dolphinemu.dolphinemu.ui.settings;
|
||||||
|
|
||||||
|
import android.app.ProgressDialog;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.content.IntentFilter;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.v4.app.FragmentTransaction;
|
import android.support.v4.app.FragmentTransaction;
|
||||||
|
import android.support.v4.content.LocalBroadcastManager;
|
||||||
import android.support.v7.app.AppCompatActivity;
|
import android.support.v7.app.AppCompatActivity;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuInflater;
|
import android.view.MenuInflater;
|
||||||
|
@ -12,6 +15,8 @@ import android.widget.Toast;
|
||||||
|
|
||||||
import org.dolphinemu.dolphinemu.R;
|
import org.dolphinemu.dolphinemu.R;
|
||||||
import org.dolphinemu.dolphinemu.model.settings.SettingSection;
|
import org.dolphinemu.dolphinemu.model.settings.SettingSection;
|
||||||
|
import org.dolphinemu.dolphinemu.services.DirectoryInitializationService;
|
||||||
|
import org.dolphinemu.dolphinemu.utils.DirectoryStateReceiver;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
@ -22,6 +27,8 @@ public final class SettingsActivity extends AppCompatActivity implements Setting
|
||||||
private static final String FRAGMENT_TAG = "settings";
|
private static final String FRAGMENT_TAG = "settings";
|
||||||
private SettingsActivityPresenter mPresenter = new SettingsActivityPresenter(this);
|
private SettingsActivityPresenter mPresenter = new SettingsActivityPresenter(this);
|
||||||
|
|
||||||
|
private ProgressDialog dialog;
|
||||||
|
|
||||||
public static void launch(Context context, String menuTag)
|
public static void launch(Context context, String menuTag)
|
||||||
{
|
{
|
||||||
Intent settings = new Intent(context, SettingsActivity.class);
|
Intent settings = new Intent(context, SettingsActivity.class);
|
||||||
|
@ -65,6 +72,13 @@ public final class SettingsActivity extends AppCompatActivity implements Setting
|
||||||
mPresenter.saveState(outState);
|
mPresenter.saveState(outState);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onStart()
|
||||||
|
{
|
||||||
|
super.onStart();
|
||||||
|
mPresenter.onStart();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If this is called, the user has left the settings screen (potentially through the
|
* If this is called, the user has left the settings screen (potentially through the
|
||||||
* home button) and will expect their changes to be persisted. So we kick off an
|
* home button) and will expect their changes to be persisted. So we kick off an
|
||||||
|
@ -106,6 +120,47 @@ public final class SettingsActivity extends AppCompatActivity implements Setting
|
||||||
transaction.commit();
|
transaction.commit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void startDirectoryInitializationService(DirectoryStateReceiver receiver, IntentFilter filter)
|
||||||
|
{
|
||||||
|
LocalBroadcastManager.getInstance(this).registerReceiver(
|
||||||
|
receiver,
|
||||||
|
filter);
|
||||||
|
DirectoryInitializationService.startService(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void stopListeningToDirectoryInitializationService(DirectoryStateReceiver receiver)
|
||||||
|
{
|
||||||
|
LocalBroadcastManager.getInstance(this).unregisterReceiver(receiver);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void showLoading()
|
||||||
|
{
|
||||||
|
if (dialog == null)
|
||||||
|
{
|
||||||
|
dialog = new ProgressDialog(this);
|
||||||
|
dialog.setMessage(getString(R.string.load_settings));
|
||||||
|
dialog.setIndeterminate(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
dialog.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void hideLoading()
|
||||||
|
{
|
||||||
|
dialog.dismiss();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void showPermissionNeededHint()
|
||||||
|
{
|
||||||
|
Toast.makeText(this, R.string.write_permission_needed, Toast.LENGTH_SHORT)
|
||||||
|
.show();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public HashMap<String, SettingSection> getSettings(int file)
|
public HashMap<String, SettingSection> getSettings(int file)
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,15 +1,20 @@
|
||||||
package org.dolphinemu.dolphinemu.ui.settings;
|
package org.dolphinemu.dolphinemu.ui.settings;
|
||||||
|
|
||||||
|
import android.content.IntentFilter;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
|
||||||
import org.dolphinemu.dolphinemu.R;
|
import org.dolphinemu.dolphinemu.R;
|
||||||
import org.dolphinemu.dolphinemu.model.settings.SettingSection;
|
import org.dolphinemu.dolphinemu.model.settings.SettingSection;
|
||||||
|
import org.dolphinemu.dolphinemu.services.DirectoryInitializationService;
|
||||||
|
import org.dolphinemu.dolphinemu.utils.DirectoryStateReceiver;
|
||||||
import org.dolphinemu.dolphinemu.utils.Log;
|
import org.dolphinemu.dolphinemu.utils.Log;
|
||||||
import org.dolphinemu.dolphinemu.utils.SettingsFile;
|
import org.dolphinemu.dolphinemu.utils.SettingsFile;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
|
||||||
|
import rx.functions.Action1;
|
||||||
|
|
||||||
public final class SettingsActivityPresenter
|
public final class SettingsActivityPresenter
|
||||||
{
|
{
|
||||||
private static final String KEY_SHOULD_SAVE = "should_save";
|
private static final String KEY_SHOULD_SAVE = "should_save";
|
||||||
|
@ -22,6 +27,10 @@ public final class SettingsActivityPresenter
|
||||||
|
|
||||||
private boolean mShouldSave;
|
private boolean mShouldSave;
|
||||||
|
|
||||||
|
private DirectoryStateReceiver directoryStateReceiver;
|
||||||
|
|
||||||
|
private String menuTag;
|
||||||
|
|
||||||
public SettingsActivityPresenter(SettingsActivityView view)
|
public SettingsActivityPresenter(SettingsActivityView view)
|
||||||
{
|
{
|
||||||
mView = view;
|
mView = view;
|
||||||
|
@ -31,12 +40,10 @@ public final class SettingsActivityPresenter
|
||||||
{
|
{
|
||||||
if (savedInstanceState == null)
|
if (savedInstanceState == null)
|
||||||
{
|
{
|
||||||
mView.showSettingsFragment(menuTag, false);
|
|
||||||
|
|
||||||
mSettings.add(SettingsFile.SETTINGS_DOLPHIN, SettingsFile.readFile(SettingsFile.FILE_NAME_DOLPHIN, mView));
|
mSettings.add(SettingsFile.SETTINGS_DOLPHIN, SettingsFile.readFile(SettingsFile.FILE_NAME_DOLPHIN, mView));
|
||||||
mSettings.add(SettingsFile.SETTINGS_GFX, SettingsFile.readFile(SettingsFile.FILE_NAME_GFX, mView));
|
mSettings.add(SettingsFile.SETTINGS_GFX, SettingsFile.readFile(SettingsFile.FILE_NAME_GFX, mView));
|
||||||
mSettings.add(SettingsFile.SETTINGS_WIIMOTE, SettingsFile.readFile(SettingsFile.FILE_NAME_WIIMOTE, mView));
|
mSettings.add(SettingsFile.SETTINGS_WIIMOTE, SettingsFile.readFile(SettingsFile.FILE_NAME_WIIMOTE, mView));
|
||||||
mView.onSettingsFileLoaded(mSettings);
|
this.menuTag = menuTag;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -44,6 +51,41 @@ public final class SettingsActivityPresenter
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void onStart()
|
||||||
|
{
|
||||||
|
prepareDolphinDirectoriesIfNeeded();
|
||||||
|
}
|
||||||
|
|
||||||
|
void loadSettingsUI()
|
||||||
|
{
|
||||||
|
mView.showSettingsFragment(menuTag, false);
|
||||||
|
mView.onSettingsFileLoaded(mSettings);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void prepareDolphinDirectoriesIfNeeded()
|
||||||
|
{
|
||||||
|
if (DirectoryInitializationService.areDolphinDirectoriesReady()) {
|
||||||
|
loadSettingsUI();
|
||||||
|
} else {
|
||||||
|
mView.showLoading();
|
||||||
|
IntentFilter statusIntentFilter = new IntentFilter(
|
||||||
|
DirectoryInitializationService.BROADCAST_ACTION);
|
||||||
|
|
||||||
|
directoryStateReceiver =
|
||||||
|
new DirectoryStateReceiver(directoryInitializationState -> {
|
||||||
|
if (directoryInitializationState == DirectoryInitializationService.DirectoryInitializationState.DOLPHIN_DIRECTORIES_INITIALIZED) {
|
||||||
|
mView.hideLoading();
|
||||||
|
loadSettingsUI();
|
||||||
|
} else if (directoryInitializationState == DirectoryInitializationService.DirectoryInitializationState.EXTERNAL_STORAGE_PERMISSION_NEEDED) {
|
||||||
|
mView.showPermissionNeededHint();
|
||||||
|
mView.hideLoading();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
mView.startDirectoryInitializationService(directoryStateReceiver, statusIntentFilter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void setSettings(ArrayList<HashMap<String, SettingSection>> settings)
|
public void setSettings(ArrayList<HashMap<String, SettingSection>> settings)
|
||||||
{
|
{
|
||||||
mSettings = settings;
|
mSettings = settings;
|
||||||
|
@ -56,6 +98,12 @@ public final class SettingsActivityPresenter
|
||||||
|
|
||||||
public void onStop(boolean finishing)
|
public void onStop(boolean finishing)
|
||||||
{
|
{
|
||||||
|
if (directoryStateReceiver != null)
|
||||||
|
{
|
||||||
|
mView.stopListeningToDirectoryInitializationService(directoryStateReceiver);
|
||||||
|
directoryStateReceiver = null;
|
||||||
|
}
|
||||||
|
|
||||||
if (mSettings != null && finishing && mShouldSave)
|
if (mSettings != null && finishing && mShouldSave)
|
||||||
{
|
{
|
||||||
Log.debug("[SettingsActivity] Settings activity stopping. Saving settings to INI...");
|
Log.debug("[SettingsActivity] Settings activity stopping. Saving settings to INI...");
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
package org.dolphinemu.dolphinemu.ui.settings;
|
package org.dolphinemu.dolphinemu.ui.settings;
|
||||||
|
|
||||||
|
import android.content.IntentFilter;
|
||||||
|
|
||||||
import org.dolphinemu.dolphinemu.model.settings.SettingSection;
|
import org.dolphinemu.dolphinemu.model.settings.SettingSection;
|
||||||
|
import org.dolphinemu.dolphinemu.utils.DirectoryStateReceiver;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
@ -99,4 +102,34 @@ public interface SettingsActivityView
|
||||||
* @param value New setting for the extension.
|
* @param value New setting for the extension.
|
||||||
*/
|
*/
|
||||||
void onExtensionSettingChanged(String key, int value);
|
void onExtensionSettingChanged(String key, int value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show loading dialog while loading the settings
|
||||||
|
*/
|
||||||
|
void showLoading();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hide the loading the dialog
|
||||||
|
*/
|
||||||
|
void hideLoading();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show a hint to the user that the app needs write to external storage access
|
||||||
|
*/
|
||||||
|
void showPermissionNeededHint();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start the DirectoryInitializationService and listen for the result.
|
||||||
|
*
|
||||||
|
* @param receiver the broadcast receiver for the DirectoryInitializationService
|
||||||
|
* @param filter the Intent broadcasts to be received.
|
||||||
|
*/
|
||||||
|
void startDirectoryInitializationService(DirectoryStateReceiver receiver, IntentFilter filter);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stop listening to the DirectoryInitializationService.
|
||||||
|
*
|
||||||
|
* @param receiver The broadcast receiver to unregister.
|
||||||
|
*/
|
||||||
|
void stopListeningToDirectoryInitializationService(DirectoryStateReceiver receiver);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
package org.dolphinemu.dolphinemu.utils;
|
||||||
|
|
||||||
|
import android.content.BroadcastReceiver;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
|
||||||
|
import org.dolphinemu.dolphinemu.services.DirectoryInitializationService;
|
||||||
|
import org.dolphinemu.dolphinemu.services.DirectoryInitializationService.DirectoryInitializationState;
|
||||||
|
|
||||||
|
import rx.functions.Action1;
|
||||||
|
|
||||||
|
public class DirectoryStateReceiver extends BroadcastReceiver {
|
||||||
|
Action1<DirectoryInitializationState> callback;
|
||||||
|
public DirectoryStateReceiver(Action1<DirectoryInitializationState> callback) {
|
||||||
|
this.callback = callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context context, Intent intent)
|
||||||
|
{
|
||||||
|
DirectoryInitializationState state = (DirectoryInitializationState) intent.getSerializableExtra(DirectoryInitializationService.EXTRA_STATE);
|
||||||
|
callback.call(state);
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,6 +2,7 @@ package org.dolphinemu.dolphinemu.utils;
|
||||||
|
|
||||||
import android.annotation.TargetApi;
|
import android.annotation.TargetApi;
|
||||||
import android.app.AlertDialog;
|
import android.app.AlertDialog;
|
||||||
|
import android.content.Context;
|
||||||
import android.content.DialogInterface;
|
import android.content.DialogInterface;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
|
@ -40,9 +41,9 @@ public class PermissionsHandler {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean hasWriteAccess(FragmentActivity activity) {
|
public static boolean hasWriteAccess(Context context) {
|
||||||
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
int hasWritePermission = ContextCompat.checkSelfPermission(activity, WRITE_EXTERNAL_STORAGE);
|
int hasWritePermission = ContextCompat.checkSelfPermission(context, WRITE_EXTERNAL_STORAGE);
|
||||||
return hasWritePermission == PackageManager.PERMISSION_GRANTED;
|
return hasWritePermission == PackageManager.PERMISSION_GRANTED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,15 +1,13 @@
|
||||||
package org.dolphinemu.dolphinemu.utils;
|
package org.dolphinemu.dolphinemu.utils;
|
||||||
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.SharedPreferences;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.preference.PreferenceManager;
|
|
||||||
import android.support.v4.app.FragmentActivity;
|
import android.support.v4.app.FragmentActivity;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
|
|
||||||
import org.dolphinemu.dolphinemu.NativeLibrary;
|
import org.dolphinemu.dolphinemu.NativeLibrary;
|
||||||
import org.dolphinemu.dolphinemu.activities.EmulationActivity;
|
import org.dolphinemu.dolphinemu.activities.EmulationActivity;
|
||||||
import org.dolphinemu.dolphinemu.services.AssetCopyService;
|
import org.dolphinemu.dolphinemu.services.DirectoryInitializationService;
|
||||||
|
|
||||||
public final class StartupHandler
|
public final class StartupHandler
|
||||||
{
|
{
|
||||||
|
@ -17,9 +15,8 @@ public final class StartupHandler
|
||||||
{
|
{
|
||||||
NativeLibrary.SetUserDirectory(""); // Auto-Detect
|
NativeLibrary.SetUserDirectory(""); // Auto-Detect
|
||||||
|
|
||||||
// Only perform these extensive copy operations once.
|
|
||||||
if (PermissionsHandler.checkWritePermission(parent)) {
|
if (PermissionsHandler.checkWritePermission(parent)) {
|
||||||
copyAssetsIfNeeded(parent);
|
DirectoryInitializationService.startService(parent);
|
||||||
}
|
}
|
||||||
|
|
||||||
Intent intent = parent.getIntent();
|
Intent intent = parent.getIntent();
|
||||||
|
@ -45,16 +42,4 @@ public final class StartupHandler
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void copyAssetsIfNeeded(FragmentActivity parent) {
|
|
||||||
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(parent);
|
|
||||||
boolean assetsCopied = preferences.getBoolean("assetsCopied", false);
|
|
||||||
|
|
||||||
if (!assetsCopied)
|
|
||||||
{
|
|
||||||
// Copy assets into appropriate locations.
|
|
||||||
Intent copyAssets = new Intent(parent, AssetCopyService.class);
|
|
||||||
parent.startService(copyAssets);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -244,4 +244,6 @@
|
||||||
<string name="header_controllers">Controllers</string>
|
<string name="header_controllers">Controllers</string>
|
||||||
|
|
||||||
<string name="write_permission_needed">You need to allow write access to external storage for the emulator to work</string>
|
<string name="write_permission_needed">You need to allow write access to external storage for the emulator to work</string>
|
||||||
|
|
||||||
|
<string name="load_settings">Loading Settings...</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
Loading…
Reference in New Issue