Merge pull request #6258 from mahdihijazi/extract_service

[Android] Refactor AssetCopyService and the way we extract resources …
This commit is contained in:
JosJuice 2017-12-24 14:59:14 +01:00 committed by GitHub
commit bfa7c14fef
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 374 additions and 138 deletions

View File

@ -65,7 +65,7 @@
android:theme="@style/DolphinEmulationGamecube"/>
<service android:name=".services.AssetCopyService"/>
<service android:name=".services.DirectoryInitializationService"/>
<provider
android:name=".model.GameProvider"

View File

@ -1,10 +1,12 @@
package org.dolphinemu.dolphinemu.fragments;
import android.content.Context;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.v4.app.Fragment;
import android.support.v4.content.LocalBroadcastManager;
import android.view.LayoutInflater;
import android.view.Surface;
import android.view.SurfaceHolder;
@ -12,13 +14,19 @@ import android.view.SurfaceView;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.Toast;
import org.dolphinemu.dolphinemu.NativeLibrary;
import org.dolphinemu.dolphinemu.R;
import org.dolphinemu.dolphinemu.activities.EmulationActivity;
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 rx.functions.Action1;
public final class EmulationFragment extends Fragment implements SurfaceHolder.Callback
{
private static final String KEY_GAMEPATH = "gamepath";
@ -29,6 +37,8 @@ public final class EmulationFragment extends Fragment implements SurfaceHolder.C
private EmulationState mEmulationState;
private DirectoryStateReceiver directoryStateReceiver;
public static EmulationFragment newInstance(String gamePath)
{
@ -108,12 +118,25 @@ public final class EmulationFragment extends Fragment implements SurfaceHolder.C
public void onResume()
{
super.onResume();
mEmulationState.run();
if (DirectoryInitializationService.areDolphinDirectoriesReady())
{
mEmulationState.run();
}
else
{
setupDolphinDirectoriesThenStartEmulation();
}
}
@Override
public void onPause()
{
if (directoryStateReceiver != null)
{
LocalBroadcastManager.getInstance(getActivity()).unregisterReceiver(directoryStateReceiver);
directoryStateReceiver = null;
}
mEmulationState.pause();
super.onPause();
}
@ -125,6 +148,27 @@ public final class EmulationFragment extends Fragment implements SurfaceHolder.C
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()
{
SharedPreferences.Editor editor = mPreferences.edit();

View File

@ -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);
}
}
}

View File

@ -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", false);
}
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);
}
}
}

View File

@ -20,6 +20,7 @@ import org.dolphinemu.dolphinemu.R;
import org.dolphinemu.dolphinemu.activities.AddDirectoryActivity;
import org.dolphinemu.dolphinemu.adapters.PlatformPagerAdapter;
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.PlatformGamesView;
import org.dolphinemu.dolphinemu.ui.settings.SettingsActivity;
@ -154,7 +155,7 @@ public final class MainActivity extends AppCompatActivity implements MainView
switch (requestCode) {
case PermissionsHandler.REQUEST_CODE_WRITE_PERMISSION:
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
StartupHandler.copyAssetsIfNeeded(this);
DirectoryInitializationService.startService(this);
PlatformPagerAdapter platformPagerAdapter = new PlatformPagerAdapter(
getSupportFragmentManager(), this);

View File

@ -28,6 +28,7 @@ import org.dolphinemu.dolphinemu.adapters.GameRowPresenter;
import org.dolphinemu.dolphinemu.adapters.SettingsRowPresenter;
import org.dolphinemu.dolphinemu.model.Game;
import org.dolphinemu.dolphinemu.model.TvSettingsItem;
import org.dolphinemu.dolphinemu.services.DirectoryInitializationService;
import org.dolphinemu.dolphinemu.ui.platform.Platform;
import org.dolphinemu.dolphinemu.ui.settings.SettingsActivity;
import org.dolphinemu.dolphinemu.utils.PermissionsHandler;
@ -157,7 +158,7 @@ public final class TvMainActivity extends FragmentActivity implements MainView
switch (requestCode) {
case PermissionsHandler.REQUEST_CODE_WRITE_PERMISSION:
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
StartupHandler.copyAssetsIfNeeded(this);
DirectoryInitializationService.startService(this);
loadGames();
} else {
Toast.makeText(this, R.string.write_permission_needed, Toast.LENGTH_SHORT)

View File

@ -1,9 +1,12 @@
package org.dolphinemu.dolphinemu.ui.settings;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.support.v4.app.FragmentTransaction;
import android.support.v4.content.LocalBroadcastManager;
import android.support.v7.app.AppCompatActivity;
import android.view.Menu;
import android.view.MenuInflater;
@ -12,6 +15,8 @@ import android.widget.Toast;
import org.dolphinemu.dolphinemu.R;
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.HashMap;
@ -22,6 +27,8 @@ public final class SettingsActivity extends AppCompatActivity implements Setting
private static final String FRAGMENT_TAG = "settings";
private SettingsActivityPresenter mPresenter = new SettingsActivityPresenter(this);
private ProgressDialog dialog;
public static void launch(Context context, String menuTag)
{
Intent settings = new Intent(context, SettingsActivity.class);
@ -65,6 +72,13 @@ public final class SettingsActivity extends AppCompatActivity implements Setting
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
* 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();
}
@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
public HashMap<String, SettingSection> getSettings(int file)
{

View File

@ -1,15 +1,20 @@
package org.dolphinemu.dolphinemu.ui.settings;
import android.content.IntentFilter;
import android.os.Bundle;
import org.dolphinemu.dolphinemu.R;
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.SettingsFile;
import java.util.ArrayList;
import java.util.HashMap;
import rx.functions.Action1;
public final class SettingsActivityPresenter
{
private static final String KEY_SHOULD_SAVE = "should_save";
@ -22,6 +27,10 @@ public final class SettingsActivityPresenter
private boolean mShouldSave;
private DirectoryStateReceiver directoryStateReceiver;
private String menuTag;
public SettingsActivityPresenter(SettingsActivityView view)
{
mView = view;
@ -31,12 +40,10 @@ public final class SettingsActivityPresenter
{
if (savedInstanceState == null)
{
mView.showSettingsFragment(menuTag, false);
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_WIIMOTE, SettingsFile.readFile(SettingsFile.FILE_NAME_WIIMOTE, mView));
mView.onSettingsFileLoaded(mSettings);
this.menuTag = menuTag;
}
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)
{
mSettings = settings;
@ -56,6 +98,12 @@ public final class SettingsActivityPresenter
public void onStop(boolean finishing)
{
if (directoryStateReceiver != null)
{
mView.stopListeningToDirectoryInitializationService(directoryStateReceiver);
directoryStateReceiver = null;
}
if (mSettings != null && finishing && mShouldSave)
{
Log.debug("[SettingsActivity] Settings activity stopping. Saving settings to INI...");

View File

@ -1,6 +1,9 @@
package org.dolphinemu.dolphinemu.ui.settings;
import android.content.IntentFilter;
import org.dolphinemu.dolphinemu.model.settings.SettingSection;
import org.dolphinemu.dolphinemu.utils.DirectoryStateReceiver;
import java.util.ArrayList;
import java.util.HashMap;
@ -99,4 +102,34 @@ public interface SettingsActivityView
* @param value New setting for the extension.
*/
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);
}

View File

@ -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);
}
}

View File

@ -2,6 +2,7 @@ package org.dolphinemu.dolphinemu.utils;
import android.annotation.TargetApi;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.pm.PackageManager;
import android.os.Build;
@ -40,9 +41,9 @@ public class PermissionsHandler {
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) {
int hasWritePermission = ContextCompat.checkSelfPermission(activity, WRITE_EXTERNAL_STORAGE);
int hasWritePermission = ContextCompat.checkSelfPermission(context, WRITE_EXTERNAL_STORAGE);
return hasWritePermission == PackageManager.PERMISSION_GRANTED;
}

View File

@ -1,15 +1,13 @@
package org.dolphinemu.dolphinemu.utils;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.v4.app.FragmentActivity;
import android.text.TextUtils;
import org.dolphinemu.dolphinemu.NativeLibrary;
import org.dolphinemu.dolphinemu.activities.EmulationActivity;
import org.dolphinemu.dolphinemu.services.AssetCopyService;
import org.dolphinemu.dolphinemu.services.DirectoryInitializationService;
public final class StartupHandler
{
@ -17,9 +15,8 @@ public final class StartupHandler
{
NativeLibrary.SetUserDirectory(""); // Auto-Detect
// Only perform these extensive copy operations once.
if (PermissionsHandler.checkWritePermission(parent)) {
copyAssetsIfNeeded(parent);
DirectoryInitializationService.startService(parent);
}
Intent intent = parent.getIntent();
@ -45,16 +42,4 @@ public final class StartupHandler
}
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);
}
}
}

View File

@ -247,4 +247,6 @@
<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="load_settings">Loading Settings...</string>
</resources>