aboutsummaryrefslogtreecommitdiff
path: root/android
diff options
context:
space:
mode:
Diffstat (limited to 'android')
-rw-r--r--android/app/src/main/java/net/minetest/minetest/CopyZipTask.java82
-rw-r--r--android/app/src/main/java/net/minetest/minetest/GameActivity.java8
-rw-r--r--android/app/src/main/java/net/minetest/minetest/MainActivity.java63
-rw-r--r--android/app/src/main/java/net/minetest/minetest/UnzipService.java181
-rw-r--r--android/app/src/main/java/net/minetest/minetest/Utils.java39
-rw-r--r--android/app/src/main/res/layout/activity_main.xml7
-rw-r--r--android/app/src/main/res/values/strings.xml3
-rw-r--r--android/native/build.gradle4
8 files changed, 245 insertions, 142 deletions
diff --git a/android/app/src/main/java/net/minetest/minetest/CopyZipTask.java b/android/app/src/main/java/net/minetest/minetest/CopyZipTask.java
deleted file mode 100644
index 6d4b6ab0f..000000000
--- a/android/app/src/main/java/net/minetest/minetest/CopyZipTask.java
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
-Minetest
-Copyright (C) 2014-2020 MoNTE48, Maksim Gamarnik <MoNTE48@mail.ua>
-Copyright (C) 2014-2020 ubulem, Bektur Mambetov <berkut87@gmail.com>
-
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU Lesser General Public License as published by
-the Free Software Foundation; either version 2.1 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Lesser General Public License for more details.
-
-You should have received a copy of the GNU Lesser General Public License along
-with this program; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-*/
-
-package net.minetest.minetest;
-
-import android.content.Intent;
-import android.os.AsyncTask;
-import android.widget.Toast;
-
-import androidx.appcompat.app.AppCompatActivity;
-
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.lang.ref.WeakReference;
-
-public class CopyZipTask extends AsyncTask<String, Void, String> {
-
- private final WeakReference<AppCompatActivity> activityRef;
-
- CopyZipTask(AppCompatActivity activity) {
- activityRef = new WeakReference<>(activity);
- }
-
- protected String doInBackground(String... params) {
- copyAsset(params[0]);
- return params[0];
- }
-
- @Override
- protected void onPostExecute(String result) {
- startUnzipService(result);
- }
-
- private void copyAsset(String zipName) {
- String filename = zipName.substring(zipName.lastIndexOf("/") + 1);
- try (InputStream in = activityRef.get().getAssets().open(filename);
- OutputStream out = new FileOutputStream(zipName)) {
- copyFile(in, out);
- } catch (IOException e) {
- AppCompatActivity activity = activityRef.get();
- if (activity != null) {
- activity.runOnUiThread(() -> Toast.makeText(activityRef.get(), e.getLocalizedMessage(), Toast.LENGTH_LONG).show());
- }
- cancel(true);
- }
- }
-
- 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);
- }
-
- private void startUnzipService(String file) {
- Intent intent = new Intent(activityRef.get(), UnzipService.class);
- intent.putExtra(UnzipService.EXTRA_KEY_IN_FILE, file);
- AppCompatActivity activity = activityRef.get();
- if (activity != null) {
- activity.startService(intent);
- }
- }
-}
diff --git a/android/app/src/main/java/net/minetest/minetest/GameActivity.java b/android/app/src/main/java/net/minetest/minetest/GameActivity.java
index 38a388230..dc2e564be 100644
--- a/android/app/src/main/java/net/minetest/minetest/GameActivity.java
+++ b/android/app/src/main/java/net/minetest/minetest/GameActivity.java
@@ -146,4 +146,12 @@ public class GameActivity extends NativeActivity {
Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(uri));
startActivity(browserIntent);
}
+
+ public String getUserDataPath() {
+ return Utils.getUserDataDirectory(this).getAbsolutePath();
+ }
+
+ public String getCachePath() {
+ return Utils.getCacheDirectory(this).getAbsolutePath();
+ }
}
diff --git a/android/app/src/main/java/net/minetest/minetest/MainActivity.java b/android/app/src/main/java/net/minetest/minetest/MainActivity.java
index 2aa50d9ad..56615fca7 100644
--- a/android/app/src/main/java/net/minetest/minetest/MainActivity.java
+++ b/android/app/src/main/java/net/minetest/minetest/MainActivity.java
@@ -29,12 +29,14 @@ import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
+import android.os.Environment;
import android.view.View;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
+import androidx.annotation.StringRes;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
@@ -43,11 +45,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
-import static net.minetest.minetest.UnzipService.ACTION_FAILURE;
-import static net.minetest.minetest.UnzipService.ACTION_PROGRESS;
-import static net.minetest.minetest.UnzipService.ACTION_UPDATE;
-import static net.minetest.minetest.UnzipService.FAILURE;
-import static net.minetest.minetest.UnzipService.SUCCESS;
+import static net.minetest.minetest.UnzipService.*;
public class MainActivity extends AppCompatActivity {
private final static int versionCode = BuildConfig.VERSION_CODE;
@@ -56,26 +54,40 @@ public class MainActivity extends AppCompatActivity {
new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE};
private static final String SETTINGS = "MinetestSettings";
private static final String TAG_VERSION_CODE = "versionCode";
+
private ProgressBar mProgressBar;
private TextView mTextView;
private SharedPreferences sharedPreferences;
+
private final BroadcastReceiver myReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
int progress = 0;
- if (intent != null)
+ @StringRes int message = 0;
+ if (intent != null) {
progress = intent.getIntExtra(ACTION_PROGRESS, 0);
- if (progress >= 0) {
+ message = intent.getIntExtra(ACTION_PROGRESS_MESSAGE, 0);
+ }
+
+ if (progress == FAILURE) {
+ Toast.makeText(MainActivity.this, intent.getStringExtra(ACTION_FAILURE), Toast.LENGTH_LONG).show();
+ finish();
+ } else if (progress == SUCCESS) {
+ startNative();
+ } else {
if (mProgressBar != null) {
mProgressBar.setVisibility(View.VISIBLE);
- mProgressBar.setProgress(progress);
+ if (progress == INDETERMINATE) {
+ mProgressBar.setIndeterminate(true);
+ } else {
+ mProgressBar.setIndeterminate(false);
+ mProgressBar.setProgress(progress);
+ }
}
mTextView.setVisibility(View.VISIBLE);
- } else if (progress == FAILURE) {
- Toast.makeText(MainActivity.this, intent.getStringExtra(ACTION_FAILURE), Toast.LENGTH_LONG).show();
- finish();
- } else if (progress == SUCCESS)
- startNative();
+ if (message != 0)
+ mTextView.setText(message);
+ }
}
};
@@ -88,6 +100,7 @@ public class MainActivity extends AppCompatActivity {
mProgressBar = findViewById(R.id.progressBar);
mTextView = findViewById(R.id.textView);
sharedPreferences = getSharedPreferences(SETTINGS, Context.MODE_PRIVATE);
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
checkPermission();
else
@@ -120,6 +133,7 @@ public class MainActivity extends AppCompatActivity {
if (grantResult != PackageManager.PERMISSION_GRANTED) {
Toast.makeText(this, R.string.not_granted, Toast.LENGTH_LONG).show();
finish();
+ return;
}
}
checkAppVersion();
@@ -127,10 +141,27 @@ public class MainActivity extends AppCompatActivity {
}
private void checkAppVersion() {
- if (sharedPreferences.getInt(TAG_VERSION_CODE, 0) == versionCode)
+ if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
+ Toast.makeText(this, R.string.no_external_storage, Toast.LENGTH_LONG).show();
+ finish();
+ return;
+ }
+
+ if (UnzipService.getIsRunning()) {
+ mProgressBar.setVisibility(View.VISIBLE);
+ mProgressBar.setIndeterminate(true);
+ mTextView.setVisibility(View.VISIBLE);
+ } else if (sharedPreferences.getInt(TAG_VERSION_CODE, 0) == versionCode &&
+ Utils.isInstallValid(this)) {
startNative();
- else
- new CopyZipTask(this).execute(getCacheDir() + "/Minetest.zip");
+ } else {
+ mProgressBar.setVisibility(View.VISIBLE);
+ mProgressBar.setIndeterminate(true);
+ mTextView.setVisibility(View.VISIBLE);
+
+ Intent intent = new Intent(this, UnzipService.class);
+ startService(intent);
+ }
}
private void startNative() {
diff --git a/android/app/src/main/java/net/minetest/minetest/UnzipService.java b/android/app/src/main/java/net/minetest/minetest/UnzipService.java
index b69f7f36e..b513a7fe0 100644
--- a/android/app/src/main/java/net/minetest/minetest/UnzipService.java
+++ b/android/app/src/main/java/net/minetest/minetest/UnzipService.java
@@ -24,16 +24,21 @@ import android.app.IntentService;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
+import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.Environment;
-import android.widget.Toast;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.StringRes;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
+import java.io.InputStream;
import java.io.OutputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
@@ -42,32 +47,61 @@ import java.util.zip.ZipInputStream;
public class UnzipService extends IntentService {
public static final String ACTION_UPDATE = "net.minetest.minetest.UPDATE";
public static final String ACTION_PROGRESS = "net.minetest.minetest.PROGRESS";
+ public static final String ACTION_PROGRESS_MESSAGE = "net.minetest.minetest.PROGRESS_MESSAGE";
public static final String ACTION_FAILURE = "net.minetest.minetest.FAILURE";
- public static final String EXTRA_KEY_IN_FILE = "file";
public static final int SUCCESS = -1;
public static final int FAILURE = -2;
+ public static final int INDETERMINATE = -3;
private final int id = 1;
private NotificationManager mNotifyManager;
private boolean isSuccess = true;
private String failureMessage;
- public UnzipService() {
- super("net.minetest.minetest.UnzipService");
+ private static boolean isRunning = false;
+ public static synchronized boolean getIsRunning() {
+ return isRunning;
+ }
+ private static synchronized void setIsRunning(boolean v) {
+ isRunning = v;
}
- private void isDir(String dir, String location) {
- File f = new File(location, dir);
- if (!f.isDirectory())
- f.mkdirs();
+ public UnzipService() {
+ super("net.minetest.minetest.UnzipService");
}
@Override
protected void onHandleIntent(Intent intent) {
- createNotification();
- unzip(intent);
+ Notification.Builder notificationBuilder = createNotification();
+ final File zipFile = new File(getCacheDir(), "Minetest.zip");
+ try {
+ setIsRunning(true);
+ File userDataDirectory = Utils.getUserDataDirectory(this);
+ if (userDataDirectory == null) {
+ throw new IOException("Unable to find user data directory");
+ }
+
+ try (InputStream in = this.getAssets().open(zipFile.getName())) {
+ try (OutputStream out = new FileOutputStream(zipFile)) {
+ int readLen;
+ byte[] readBuffer = new byte[16384];
+ while ((readLen = in.read(readBuffer)) != -1) {
+ out.write(readBuffer, 0, readLen);
+ }
+ }
+ }
+
+ migrate(notificationBuilder, userDataDirectory);
+ unzip(notificationBuilder, zipFile, userDataDirectory);
+ } catch (IOException e) {
+ isSuccess = false;
+ failureMessage = e.getLocalizedMessage();
+ } finally {
+ setIsRunning(false);
+ zipFile.delete();
+ }
}
- private void createNotification() {
+ private Notification.Builder createNotification() {
String name = "net.minetest.minetest";
String channelId = "Minetest channel";
String description = "notifications from Minetest";
@@ -92,66 +126,129 @@ public class UnzipService extends IntentService {
} else {
builder = new Notification.Builder(this);
}
+
+ Intent notificationIntent = new Intent(this, MainActivity.class);
+ notificationIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP
+ | Intent.FLAG_ACTIVITY_SINGLE_TOP);
+ PendingIntent intent = PendingIntent.getActivity(this, 0,
+ notificationIntent, 0);
+
builder.setContentTitle(getString(R.string.notification_title))
.setSmallIcon(R.mipmap.ic_launcher)
- .setContentText(getString(R.string.notification_description));
+ .setContentText(getString(R.string.notification_description))
+ .setContentIntent(intent)
+ .setOngoing(true)
+ .setProgress(0, 0, true);
+
mNotifyManager.notify(id, builder.build());
+ return builder;
}
- private void unzip(Intent intent) {
- String zip = intent.getStringExtra(EXTRA_KEY_IN_FILE);
- isDir("Minetest", Environment.getExternalStorageDirectory().toString());
- String location = Environment.getExternalStorageDirectory() + File.separator + "Minetest" + File.separator;
+ private void unzip(Notification.Builder notificationBuilder, File zipFile, File userDataDirectory) throws IOException {
int per = 0;
- int size = getSummarySize(zip);
- File zipFile = new File(zip);
+
+ int size;
+ try (ZipFile zipSize = new ZipFile(zipFile)) {
+ size = zipSize.size();
+ }
+
int readLen;
- byte[] readBuffer = new byte[8192];
+ byte[] readBuffer = new byte[16384];
try (FileInputStream fileInputStream = new FileInputStream(zipFile);
ZipInputStream zipInputStream = new ZipInputStream(fileInputStream)) {
ZipEntry ze;
while ((ze = zipInputStream.getNextEntry()) != null) {
if (ze.isDirectory()) {
++per;
- isDir(ze.getName(), location);
- } else {
- publishProgress(100 * ++per / size);
- try (OutputStream outputStream = new FileOutputStream(location + ze.getName())) {
- while ((readLen = zipInputStream.read(readBuffer)) != -1) {
- outputStream.write(readBuffer, 0, readLen);
- }
+ Utils.createDirs(userDataDirectory, ze.getName());
+ continue;
+ }
+ publishProgress(notificationBuilder, R.string.loading, 100 * ++per / size);
+ try (OutputStream outputStream = new FileOutputStream(
+ new File(userDataDirectory, ze.getName()))) {
+ while ((readLen = zipInputStream.read(readBuffer)) != -1) {
+ outputStream.write(readBuffer, 0, readLen);
}
}
- zipFile.delete();
}
- } catch (IOException e) {
- isSuccess = false;
- failureMessage = e.getLocalizedMessage();
}
}
- private void publishProgress(int progress) {
+ void moveFileOrDir(@NonNull File src, @NonNull File dst) throws IOException {
+ try {
+ Process p = new ProcessBuilder("/system/bin/mv",
+ src.getAbsolutePath(), dst.getAbsolutePath()).start();
+ int exitcode = p.waitFor();
+ if (exitcode != 0)
+ throw new IOException("Move failed with exit code " + exitcode);
+ } catch (InterruptedException e) {
+ throw new IOException("Move operation interrupted");
+ }
+ }
+
+ boolean recursivelyDeleteDirectory(@NonNull File loc) {
+ try {
+ Process p = new ProcessBuilder("/system/bin/rm", "-rf",
+ loc.getAbsolutePath()).start();
+ return p.waitFor() == 0;
+ } catch (IOException | InterruptedException e) {
+ return false;
+ }
+ }
+
+ /**
+ * Migrates user data from deprecated external storage to app scoped storage
+ */
+ private void migrate(Notification.Builder notificationBuilder, File newLocation) throws IOException {
+ File oldLocation = new File(Environment.getExternalStorageDirectory(), "Minetest");
+ if (!oldLocation.isDirectory())
+ return;
+
+ publishProgress(notificationBuilder, R.string.migrating, 0);
+ newLocation.mkdir();
+
+ String[] dirs = new String[] { "worlds", "games", "mods", "textures", "client" };
+ for (int i = 0; i < dirs.length; i++) {
+ publishProgress(notificationBuilder, R.string.migrating, 100 * i / dirs.length);
+ File dir = new File(oldLocation, dirs[i]), dir2 = new File(newLocation, dirs[i]);
+ if (dir.isDirectory() && !dir2.isDirectory()) {
+ moveFileOrDir(dir, dir2);
+ }
+ }
+
+ for (String filename : new String[] { "minetest.conf" }) {
+ File file = new File(oldLocation, filename), file2 = new File(newLocation, filename);
+ if (file.isFile() && !file2.isFile()) {
+ moveFileOrDir(file, file2);
+ }
+ }
+
+ recursivelyDeleteDirectory(oldLocation);
+ }
+
+ private void publishProgress(@Nullable Notification.Builder notificationBuilder, @StringRes int message, int progress) {
Intent intentUpdate = new Intent(ACTION_UPDATE);
intentUpdate.putExtra(ACTION_PROGRESS, progress);
- if (!isSuccess) intentUpdate.putExtra(ACTION_FAILURE, failureMessage);
+ intentUpdate.putExtra(ACTION_PROGRESS_MESSAGE, message);
+ if (!isSuccess)
+ intentUpdate.putExtra(ACTION_FAILURE, failureMessage);
sendBroadcast(intentUpdate);
- }
- private int getSummarySize(String zip) {
- int size = 0;
- try {
- ZipFile zipSize = new ZipFile(zip);
- size += zipSize.size();
- } catch (IOException e) {
- Toast.makeText(this, e.getLocalizedMessage(), Toast.LENGTH_LONG).show();
+ if (notificationBuilder != null) {
+ notificationBuilder.setContentText(getString(message));
+ if (progress == INDETERMINATE) {
+ notificationBuilder.setProgress(100, 50, true);
+ } else {
+ notificationBuilder.setProgress(100, progress, false);
+ }
+ mNotifyManager.notify(id, notificationBuilder.build());
}
- return size;
}
@Override
public void onDestroy() {
super.onDestroy();
mNotifyManager.cancel(id);
- publishProgress(isSuccess ? SUCCESS : FAILURE);
+ publishProgress(null, R.string.loading, isSuccess ? SUCCESS : FAILURE);
}
}
diff --git a/android/app/src/main/java/net/minetest/minetest/Utils.java b/android/app/src/main/java/net/minetest/minetest/Utils.java
new file mode 100644
index 000000000..b2553c844
--- /dev/null
+++ b/android/app/src/main/java/net/minetest/minetest/Utils.java
@@ -0,0 +1,39 @@
+package net.minetest.minetest;
+
+import android.content.Context;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import java.io.File;
+
+public class Utils {
+ public static @NonNull File createDirs(File root, String dir) {
+ File f = new File(root, dir);
+ if (!f.isDirectory())
+ f.mkdirs();
+
+ return f;
+ }
+
+ public static @Nullable File getUserDataDirectory(Context context) {
+ File extDir = context.getExternalFilesDir(null);
+ if (extDir == null) {
+ return null;
+ }
+
+ return createDirs(extDir, "Minetest");
+ }
+
+ public static @Nullable File getCacheDirectory(Context context) {
+ return context.getCacheDir();
+ }
+
+ public static boolean isInstallValid(Context context) {
+ File userDataDirectory = getUserDataDirectory(context);
+ return userDataDirectory != null && userDataDirectory.isDirectory() &&
+ new File(userDataDirectory, "games").isDirectory() &&
+ new File(userDataDirectory, "builtin").isDirectory() &&
+ new File(userDataDirectory, "client").isDirectory() &&
+ new File(userDataDirectory, "textures").isDirectory();
+ }
+}
diff --git a/android/app/src/main/res/layout/activity_main.xml b/android/app/src/main/res/layout/activity_main.xml
index e6f461f14..93508c3cb 100644
--- a/android/app/src/main/res/layout/activity_main.xml
+++ b/android/app/src/main/res/layout/activity_main.xml
@@ -1,4 +1,5 @@
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
@@ -14,7 +15,8 @@
android:layout_marginRight="90dp"
android:indeterminate="false"
android:max="100"
- android:visibility="gone" />
+ android:visibility="gone"
+ tools:visibility="visible" />
<TextView
android:id="@+id/textView"
@@ -25,6 +27,7 @@
android:background="@android:color/transparent"
android:text="@string/loading"
android:textColor="#FEFEFE"
- android:visibility="gone" />
+ android:visibility="gone"
+ tools:visibility="visible" />
</RelativeLayout>
diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml
index a6fba70d5..99f948c99 100644
--- a/android/app/src/main/res/values/strings.xml
+++ b/android/app/src/main/res/values/strings.xml
@@ -3,8 +3,11 @@
<string name="label">Minetest</string>
<string name="loading">Loading&#8230;</string>
+ <string name="migrating">Migrating save data from old install&#8230; (this may take a while)</string>
<string name="not_granted">Required permission wasn\'t granted, Minetest can\'t run without it</string>
<string name="notification_title">Loading Minetest</string>
<string name="notification_description">Less than 1 minute&#8230;</string>
+ <string name="ime_dialog_done">Done</string>
+ <string name="no_external_storage">External storage isn\'t available. If you use an SDCard, please reinsert it. Otherwise, try restarting your phone or contacting the Minetest developers</string>
</resources>
diff --git a/android/native/build.gradle b/android/native/build.gradle
index 8ea6347b3..a7f095641 100644
--- a/android/native/build.gradle
+++ b/android/native/build.gradle
@@ -41,6 +41,10 @@ android {
arguments 'NDEBUG=1'
}
}
+
+ ndk {
+ debugSymbolLevel 'SYMBOL_TABLE'
+ }
}
}
}