Browse Source

[android][launcher] copy existing oh3 files using Java

Andrey Filipenkov 1 year ago
parent
commit
d7bf6b7dbe

+ 1 - 0
android/vcmi-app/build.gradle

@@ -157,4 +157,5 @@ def LoadSigningConfig(final signingConfigKey) {
 dependencies {
 	implementation fileTree(dir: '../libs', include: ['*.jar', '*.aar'])
 	implementation 'androidx.annotation:annotation:1.7.1'
+	implementation 'androidx.documentfile:documentfile:1.0.1'
 }

+ 32 - 0
android/vcmi-app/src/main/java/eu/vcmi/vcmi/ActivityLauncher.java

@@ -1,10 +1,18 @@
 package eu.vcmi.vcmi;
 
+import android.app.Activity;
 import android.content.Intent;
+import android.net.Uri;
 import android.os.Bundle;
+import android.os.Environment;
+import android.provider.DocumentsContract;
+
 import androidx.annotation.Nullable;
 
+import java.io.File;
+
 import eu.vcmi.vcmi.VcmiSDLActivity;
+import eu.vcmi.vcmi.util.FileUtil;
 
 import org.libsdl.app.SDL;
 
@@ -13,6 +21,8 @@ import org.libsdl.app.SDL;
  */
 public class ActivityLauncher extends org.qtproject.qt5.android.bindings.QtActivity
 {
+    private static final int PICK_EXTERNAL_VCMI_DATA_TO_COPY = 1;
+
     public boolean justLaunched = true;
 
     @Override
@@ -23,6 +33,28 @@ public class ActivityLauncher extends org.qtproject.qt5.android.bindings.QtActiv
         SDL.setContext(this);
     }
 
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent resultData)
+    {
+        if (requestCode == PICK_EXTERNAL_VCMI_DATA_TO_COPY && resultCode == Activity.RESULT_OK)
+        {
+            if (resultData != null && FileUtil.copyData(resultData.getData(), this))
+                NativeMethods.heroesDataUpdate();
+            return;
+        }
+
+        super.onActivityResult(requestCode, resultCode, resultData);
+    }
+
+    public void copyHeroesData()
+    {
+        Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
+        intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI,
+            Uri.fromFile(new File(Environment.getExternalStorageDirectory(), "vcmi-data"))
+        );
+        startActivityForResult(intent, PICK_EXTERNAL_VCMI_DATA_TO_COPY);
+    }
+
     public void onLaunchGameBtnPressed()
     {
         startActivity(new Intent(ActivityLauncher.this, VcmiSDLActivity.class));

+ 0 - 2
android/vcmi-app/src/main/java/eu/vcmi/vcmi/Const.java

@@ -8,8 +8,6 @@ import android.os.Build;
 public class Const
 {
     public static final String JNI_METHOD_SUPPRESS = "unused"; // jni methods are marked as unused, because IDE doesn't understand jni calls
-    // used to disable lint errors about try-with-resources being unsupported on api <19 (it is supported, because retrolambda backports it)
-    public static final int SUPPRESS_TRY_WITH_RESOURCES_WARNING = Build.VERSION_CODES.KITKAT;
 
     public static final String VCMI_DATA_ROOT_FOLDER_NAME = "vcmi-data";
 }

+ 1 - 1
android/vcmi-app/src/main/java/eu/vcmi/vcmi/NativeMethods.java

@@ -26,7 +26,7 @@ public class NativeMethods
     }
 
     public static native void initClassloader();
-
+    public static native void heroesDataUpdate();
     public static native boolean tryToSaveTheGame();
 
     public static void setupMsg(final Messenger msg)

+ 107 - 0
android/vcmi-app/src/main/java/eu/vcmi/vcmi/util/FileUtil.java

@@ -0,0 +1,107 @@
+package eu.vcmi.vcmi.util;
+
+import android.app.Activity;
+import android.net.Uri;
+
+import androidx.annotation.Nullable;
+import androidx.documentfile.provider.DocumentFile;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.List;
+
+import eu.vcmi.vcmi.Storage;
+
+/**
+ * @author F
+ */
+public class FileUtil
+{
+    private static final int BUFFER_SIZE = 4096;
+
+    public static boolean copyData(Uri folderToCopy, Activity activity)
+    {
+        File targetDir = Storage.getVcmiDataDir(activity);
+        DocumentFile sourceDir = DocumentFile.fromTreeUri(activity, folderToCopy);
+        return copyDirectory(targetDir, sourceDir, List.of("Data", "Maps", "Mp3"), activity);
+    }
+
+    private static boolean copyDirectory(File targetDir, DocumentFile sourceDir, @Nullable List<String> allowed, Activity activity)
+    {
+        if (!targetDir.exists())
+        {
+            targetDir.mkdir();
+        }
+
+        for (DocumentFile child : sourceDir.listFiles())
+        {
+            if (allowed != null)
+            {
+                boolean fileAllowed = false;
+
+                for (String str : allowed)
+                {
+                    if (str.equalsIgnoreCase(child.getName()))
+                    {
+                        fileAllowed = true;
+                        break;
+                    }
+                }
+
+                if (!fileAllowed)
+                    continue;
+            }
+
+            File exported = new File(targetDir, child.getName());
+
+            if (child.isFile())
+            {
+                if (!exported.exists())
+                {
+                    try
+                    {
+                        exported.createNewFile();
+                    }
+                    catch (IOException e)
+                    {
+                        Log.e(activity, "createNewFile failed: " + e);
+                        return false;
+                    }
+                }
+
+                try (
+                    final OutputStream targetStream = new FileOutputStream(exported, false);
+                    final InputStream sourceStream = activity.getContentResolver()
+                            .openInputStream(child.getUri()))
+                {
+                    copyStream(sourceStream, targetStream);
+                }
+                catch (IOException e)
+                {
+                    Log.e(activity, "copyStream failed: " + e);
+                    return false;
+                }
+            }
+
+            if (child.isDirectory() && !copyDirectory(exported, child, null, activity))
+            {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    private static void copyStream(InputStream source, OutputStream target) throws IOException
+    {
+        final byte[] buffer = new byte[BUFFER_SIZE];
+        int read;
+        while ((read = source.read(buffer)) != -1)
+        {
+            target.write(buffer, 0, read);
+        }
+    }
+}

+ 30 - 3
launcher/firstLaunch/firstlaunch_moc.cpp

@@ -26,6 +26,17 @@
 #include "cli/extract.hpp"
 #endif
 
+#ifdef VCMI_ANDROID
+#include <QAndroidJniObject>
+#include <QtAndroid>
+
+static FirstLaunchView * thiz;
+extern "C" JNIEXPORT void JNICALL Java_eu_vcmi_vcmi_NativeMethods_heroesDataUpdate(JNIEnv * env, jclass cls)
+{
+	thiz->heroesDataUpdate();
+}
+#endif
+
 FirstLaunchView::FirstLaunchView(QWidget * parent)
 	: QWidget(parent)
 	, ui(new Ui::FirstLaunchView)
@@ -97,7 +108,12 @@ void FirstLaunchView::on_pushButtonDataSearch_clicked()
 
 void FirstLaunchView::on_pushButtonDataCopy_clicked()
 {
+#ifdef VCMI_ANDROID
+	thiz = this;
+	QtAndroid::androidActivity().callMethod<void>("copyHeroesData");
+#else
 	copyHeroesData();
+#endif
 }
 
 void FirstLaunchView::on_pushButtonGogInstall_clicked()
@@ -198,11 +214,22 @@ void FirstLaunchView::heroesDataMissing()
 	ui->lineEditDataSystem->setPalette(newPalette);
 	ui->lineEditDataUser->setPalette(newPalette);
 
+	ui->labelDataSearch->setVisible(true);
 	ui->pushButtonDataSearch->setVisible(true);
-	ui->pushButtonDataCopy->setVisible(true);
 
-	ui->labelDataSearch->setVisible(true);
-	ui->labelDataCopy->setVisible(true);
+#ifdef VCMI_ANDROID
+	// selecting directory with ACTION_OPEN_DOCUMENT_TREE is available only since API level 21
+	if (QtAndroid::androidSdkVersion() < 21)
+	{
+		ui->labelDataCopy->hide();
+		ui->pushButtonDataCopy->hide();
+	}
+	else
+#endif
+	{
+		ui->labelDataCopy->show();
+		ui->pushButtonDataCopy->show();
+	}
 
 	ui->labelDataFound->setVisible(false);
 	ui->pushButtonDataNext->setEnabled(false);

+ 3 - 1
launcher/firstLaunch/firstlaunch_moc.h

@@ -35,7 +35,6 @@ class FirstLaunchView : public QWidget
 	void languageSelected(const QString & languageCode);
 
 	// Tab Heroes III Data
-	bool heroesDataUpdate();
 	bool heroesDataDetect();
 
 	void heroesDataMissing();
@@ -64,6 +63,9 @@ class FirstLaunchView : public QWidget
 public:
 	explicit FirstLaunchView(QWidget * parent = nullptr);
 
+	// Tab Heroes III Data
+	bool heroesDataUpdate();
+
 public slots:
 
 private slots: