Browse Source

[Android] Locate external App data path and migrate data files

pull/2458/head
Anders Jenbo 5 years ago
parent
commit
9818ad4891
  1. 12
      Source/init.cpp
  2. 4
      android-project/app/build.gradle
  3. 2
      android-project/app/src/main/AndroidManifest.xml
  4. 291
      android-project/app/src/main/java/org/diasurgical/devilutionx/DevilutionXSDLActivity.java

12
Source/init.cpp

@ -138,22 +138,12 @@ void init_cleanup()
void init_archives()
{
std::vector<std::string> paths;
paths.reserve(5);
#ifdef __ANDROID__
paths.push_back(std::string(getenv("EXTERNAL_STORAGE")) + "/devilutionx/");
#else
paths.push_back(paths::BasePath());
#endif
paths.push_back(paths::PrefPath());
if (paths[0] == paths[1])
paths.pop_back();
#ifdef __ANDROID__
if (getenv("SECONDARY_STORAGE") != nullptr)
paths.emplace_back(std::string(getenv("SECONDARY_STORAGE")) + "/devilutionx/");
if (getenv("EXTERNAL_SDCARD_STORAGE") != nullptr)
paths.emplace_back(std::string(getenv("EXTERNAL_SDCARD_STORAGE")) + "/devilutionx/");
#elif defined(__linux__)
#if defined(__linux__) && !defined(__ANDROID__)
paths.emplace_back("/usr/share/diasurgical/devilutionx/");
paths.emplace_back("/usr/local/share/diasurgical/devilutionx/");
#elif defined(__3DS__)

4
android-project/app/build.gradle

@ -8,7 +8,7 @@ else {
}
android {
compileSdkVersion 29
compileSdkVersion 29 // Upgrade to 30 after november 2021
aaptOptions {
noCompress 'mpq'
}
@ -17,7 +17,7 @@ android {
applicationId "org.diasurgical.devilutionx"
}
minSdkVersion 21
targetSdkVersion 29
targetSdkVersion 29 // Upgrade to 30 after november 2021
versionCode 18
versionName "1.2.1"
externalNativeBuild {

2
android-project/app/src/main/AndroidManifest.xml

@ -27,7 +27,7 @@
android:name="android.hardware.type.pc"
android:required="false" />
<!-- Allow read from external storage -->
<!-- TODO: Remove this after November 2021 -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<!-- Allow access to Bluetooth devices -->
<uses-permission android:name="android.permission.BLUETOOTH" />

291
android-project/app/src/main/java/org/diasurgical/devilutionx/DevilutionXSDLActivity.java

@ -1,136 +1,155 @@
package org.diasurgical.devilutionx;
import android.Manifest;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.preference.PreferenceManager;
import org.libsdl.app.SDLActivity;
import java.io.File;
public class DevilutionXSDLActivity extends SDLActivity {
final String permission = Manifest.permission.READ_EXTERNAL_STORAGE;
private Handler permissionHandler;
private AlertDialog PermissionDialog;
private final String SharewareTitle = "Shareware version";
private SharedPreferences mPrefs;
final private String shouldShowInstallationGuidePref = "shouldShowInstallationGuide";
protected void onCreate(Bundle savedInstanceState) {
checkStoragePermission();
super.onCreate(savedInstanceState);
}
private boolean shouldShowInstallationGuide(boolean isDataAccessible) {
mPrefs = PreferenceManager.getDefaultSharedPreferences(this);
return mPrefs.getBoolean(shouldShowInstallationGuidePref, !isDataAccessible);
}
private void installationGuideShown() {
SharedPreferences.Editor editor = mPrefs.edit();
editor.putBoolean(shouldShowInstallationGuidePref, false);
editor.apply();
}
private void checkStoragePermission() {
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.LOLLIPOP_MR1) {
checkStoragePermissionLollipop();
return;
}
if (PackageManager.PERMISSION_GRANTED != checkSelfPermission(permission)) {
requestPermissions(new String[]{permission}, 1);
}
boolean dataFileFound = hasDataFile();
boolean shouldShowInstallationGuide = shouldShowInstallationGuide(dataFileFound && PackageManager.PERMISSION_GRANTED == checkSelfPermission(permission));
if (!shouldShowInstallationGuide && !shouldShowRequestPermissionRationale(permission)) {
return;
}
String message = "To play the full version you must grant the application read access to " + System.getenv("EXTERNAL_STORAGE") + ".";
if (!dataFileFound) {
message = "To play the full version you must place DIABDAT.MPQ from the original game in " + System.getenv("EXTERNAL_STORAGE") + " and grant the application read access.\n\nIf you don't have the original game then you can buy Diablo from GOG.com.";
installationGuideShown();
}
permissionHandler = new Handler();
PermissionDialog = new AlertDialog.Builder(this)
.setTitle(SharewareTitle)
.setPositiveButton("OK", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
permissionHandler.post(new Runnable() {
@Override
public void run() {
throw new RuntimeException();
}
});
}
})
.setMessage(message)
.show();
if (dataFileFound) {
triggerPulse(); // Start full game as soon as we have file access
}
try {
Looper.loop();
} catch (RuntimeException ignored) {
}
}
private void checkStoragePermissionLollipop() {
boolean dataFileFound = hasDataFile();
boolean shouldShowInstallationGuide = shouldShowInstallationGuide(dataFileFound);
if (!shouldShowInstallationGuide) {
return;
}
String message = "To play the full version you must place DIABDAT.MPQ from the original game in " + System.getenv("EXTERNAL_STORAGE") + ".\n\nIf you don't have the original game then you can buy Diablo from GOG.com.";
new AlertDialog.Builder(this)
.setTitle(SharewareTitle)
.setPositiveButton("OK", null)
.setMessage(message)
.show();
installationGuideShown();
}
private void triggerPulse() {
permissionHandler.postDelayed(new Runnable() {
@Override
public void run() {
if (PackageManager.PERMISSION_GRANTED == checkSelfPermission(permission)) {
PermissionDialog.hide();
throw new RuntimeException();
}
triggerPulse();
}
}, 50);
}
private boolean hasDataFile() {
File fileLower = new File(System.getenv("EXTERNAL_STORAGE") + "/diabdat.mpq");
File fileUpper = new File(System.getenv("EXTERNAL_STORAGE") + "/DIABDAT.MPQ");
return fileUpper.exists() || fileLower.exists();
}
protected String[] getLibraries() {
return new String[]{
"SDL2",
"SDL2_ttf",
"devilutionx"
};
}
}
package org.diasurgical.devilutionx;
import android.Manifest;
import android.app.AlertDialog;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.preference.PreferenceManager;
import android.util.Log;
import org.libsdl.app.SDLActivity;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
public class DevilutionXSDLActivity extends SDLActivity {
private final String SharewareTitle = "Shareware version";
private SharedPreferences mPrefs;
final private String shouldShowInstallationGuidePref = "shouldShowInstallationGuide";
private String externalDir;
protected void onCreate(Bundle savedInstanceState) {
externalDir = getExternalFilesDir(null).getAbsolutePath();
migrateAppData();
if (shouldShowInstallationGuide()) {
showInstallationGuide();
}
super.onCreate(savedInstanceState);
}
/**
* Check if this is the first time the app is run and the full game data is not already present
*/
private boolean shouldShowInstallationGuide() {
mPrefs = PreferenceManager.getDefaultSharedPreferences(this);
return mPrefs.getBoolean(shouldShowInstallationGuidePref, !hasFullGameData());
}
private boolean hasFullGameData() {
File fileLower = new File(externalDir + "/diabdat.mpq");
File fileUpper = new File(externalDir + "/DIABDAT.MPQ");
return fileUpper.exists() || fileLower.exists();
}
private void installationGuideShown() {
SharedPreferences.Editor editor = mPrefs.edit();
editor.putBoolean(shouldShowInstallationGuidePref, false);
editor.apply();
}
private void showInstallationGuide() {
String message = "To play the full version you must place DIABDAT.MPQ from the original game in Android"
+ externalDir.split("/Android")[1]
+ ".\n\nIf you don't have the original game then you can buy Diablo from GOG.com.";
new AlertDialog.Builder(this)
.setTitle(SharewareTitle)
.setPositiveButton("OK", null)
.setMessage(message)
.show();
installationGuideShown();
}
private boolean copyFile(File src, File dst) {
try {
try (InputStream in = new FileInputStream(src)) {
try (OutputStream out = new FileOutputStream(dst)) {
// Transfer bytes from in to out
byte[] buf = new byte[1024];
int len;
while ((len = in.read(buf)) > 0) {
out.write(buf, 0, len);
}
return true;
}
}
} catch (IOException exception) {
Log.e("copyFile", exception.getMessage());
}
return false;
}
private void migrateFile(File file) {
if (!file.exists() || !file.canRead()) {
return;
}
File newPath = new File(externalDir + "/" + file.getName());
if (newPath.exists()) {
if (file.canWrite()) {
file.delete();
}
return;
}
if (!new File(newPath.getParent()).canWrite()) {
return;
}
if (!file.renameTo(newPath)) {
if (copyFile(file, newPath) && file.canWrite()) {
file.delete();
}
}
}
/**
* This can be removed Nov 2021 and Google will no longer permit access to the old folder from that point on
*/
private void migrateAppData() {
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP_MR1) {
if (PackageManager.PERMISSION_GRANTED != checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE)) {
return;
}
}
migrateFile(new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS) + "/diabdat.mpq"));
migrateFile(new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS) + "/DIABDAT.MPQ"));
migrateFile(new File("/sdcard/diabdat.mpq"));
migrateFile(new File("/sdcard/devilutionx/diabdat.mpq"));
migrateFile(new File("/sdcard/devilutionx/spawn.mpq"));
for (File internalFile : getFilesDir().listFiles()) {
migrateFile(internalFile);
}
}
protected String[] getArguments() {
return new String[]{
"--data-dir",
externalDir,
"--config-dir",
externalDir,
"--save-dir",
externalDir
};
}
protected String[] getLibraries() {
return new String[]{
"SDL2",
"SDL2_ttf",
"devilutionx"
};
}
}

Loading…
Cancel
Save