summaryrefslogtreecommitdiff
path: root/build/android/app
diff options
context:
space:
mode:
authorMaksim <MoNTE48@mail.ua>2020-04-15 16:27:40 +0200
committerGitHub <noreply@github.com>2020-04-15 16:27:40 +0200
commit62ae7adab2bebde04864c12543caefbffab24963 (patch)
tree1cad2b08c94c2de26a39124f3b3dd0d513cbcc99 /build/android/app
parent8ae8c1600a66c724565ce7a70e0e5f542f12e38e (diff)
downloadminetest-62ae7adab2bebde04864c12543caefbffab24963.tar.gz
minetest-62ae7adab2bebde04864c12543caefbffab24963.tar.bz2
minetest-62ae7adab2bebde04864c12543caefbffab24963.zip
Android: add Android Studio support, completely redone java part (#9066)
Diffstat (limited to 'build/android/app')
-rw-r--r--build/android/app/build.gradle108
-rw-r--r--build/android/app/src/main/AndroidManifest.xml60
-rw-r--r--build/android/app/src/main/java/net/minetest/minetest/CopyZipTask.java82
-rw-r--r--build/android/app/src/main/java/net/minetest/minetest/GameActivity.java120
-rw-r--r--build/android/app/src/main/java/net/minetest/minetest/InputDialogActivity.java98
-rw-r--r--build/android/app/src/main/java/net/minetest/minetest/MainActivity.java139
-rw-r--r--build/android/app/src/main/java/net/minetest/minetest/UnzipService.java153
-rw-r--r--build/android/app/src/main/res/drawable/background.pngbin0 -> 83 bytes
-rw-r--r--build/android/app/src/main/res/drawable/bg.xml4
-rw-r--r--build/android/app/src/main/res/layout/activity_main.xml30
-rw-r--r--build/android/app/src/main/res/mipmap/ic_launcher.pngbin0 -> 5780 bytes
-rw-r--r--build/android/app/src/main/res/values/strings.xml10
-rw-r--r--build/android/app/src/main/res/values/styles.xml22
13 files changed, 826 insertions, 0 deletions
diff --git a/build/android/app/build.gradle b/build/android/app/build.gradle
new file mode 100644
index 000000000..6a34a6d70
--- /dev/null
+++ b/build/android/app/build.gradle
@@ -0,0 +1,108 @@
+apply plugin: 'com.android.application'
+android {
+ compileSdkVersion 29
+ buildToolsVersion '29.0.3'
+ ndkVersion '21.0.6113669'
+ defaultConfig {
+ applicationId 'net.minetest.minetest'
+ minSdkVersion 16
+ //noinspection OldTargetApi
+ targetSdkVersion 28 // Workaround for using `/sdcard` instead of the `data` patch for assets
+ versionName "${versionMajor}.${versionMinor}.${versionPatch}"
+ versionCode project.versionCode
+ }
+
+ Properties props = new Properties()
+ props.load(new FileInputStream(file('../local.properties')))
+
+ if (props.getProperty('keystore') != null) {
+ signingConfigs {
+ release {
+ storeFile file(props['keystore'])
+ storePassword props['keystore.password']
+ keyAlias props['key']
+ keyPassword props['key.password']
+ }
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled true
+ signingConfig signingConfigs.release
+ }
+ }
+ }
+
+ // for multiple APKs
+ splits {
+ abi {
+ enable true
+ reset()
+ include 'armeabi-v7a', 'arm64-v8a'
+ }
+ }
+
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+}
+
+task prepareAssets() {
+ def assetsFolder = "build/assets"
+ def projRoot = "../../.."
+ def gameToCopy = "minetest_game"
+
+ copy {
+ from "${projRoot}/minetest.conf.example", "${projRoot}/README.md" into assetsFolder
+ }
+ copy {
+ from "${projRoot}/doc/lgpl-2.1.txt" into "${assetsFolder}"
+ rename("lgpl-2.1.txt", "LICENSE.txt")
+ }
+ copy {
+ from "${projRoot}/builtin" into "${assetsFolder}/builtin"
+ }
+ copy {
+ from "${projRoot}/client/shaders" into "${assetsFolder}/client/shaders"
+ }
+ copy {
+ from "${projRoot}/fonts" include "*.ttf" into "${assetsFolder}/fonts"
+ }
+ copy {
+ from "${projRoot}/games/${gameToCopy}" into "${assetsFolder}/games/${gameToCopy}"
+ }
+ /*copy {
+ // locales broken right now
+ // ToDo: fix it!
+ from "${projRoot}/po" into "${assetsFolder}/po"
+ }*/
+ copy {
+ from "${projRoot}/textures" into "${assetsFolder}/textures"
+ }
+
+ task zipAssets(type: Zip) {
+ archiveName "Minetest.zip"
+ from "${assetsFolder}"
+ destinationDir file("src/main/assets")
+ }
+}
+
+preBuild.dependsOn zipAssets
+
+// Map for the version code that gives each ABI a value.
+import com.android.build.OutputFile
+
+def abiCodes = ['armeabi-v7a': 0, 'arm64-v8a': 1]
+android.applicationVariants.all { variant ->
+ variant.outputs.each {
+ output ->
+ def abiName = output.getFilter(OutputFile.ABI)
+ output.versionCodeOverride = abiCodes.get(abiName, 0) + variant.versionCode
+ }
+}
+
+dependencies {
+ implementation project(':native')
+ implementation 'androidx.appcompat:appcompat:1.1.0'
+}
diff --git a/build/android/app/src/main/AndroidManifest.xml b/build/android/app/src/main/AndroidManifest.xml
new file mode 100644
index 000000000..3a5342751
--- /dev/null
+++ b/build/android/app/src/main/AndroidManifest.xml
@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ package="net.minetest.minetest"
+ android:installLocation="auto">
+
+ <uses-permission android:name="android.permission.INTERNET" />
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+
+ <application
+ android:allowBackup="false"
+ android:icon="@mipmap/ic_launcher"
+ android:label="@string/label"
+ android:resizeableActivity="false"
+ tools:ignore="UnusedAttribute">
+
+ <meta-data
+ android:name="android.max_aspect"
+ android:value="3" />
+
+ <activity
+ android:name=".MainActivity"
+ android:configChanges="orientation|keyboardHidden|navigation|screenSize"
+ android:maxAspectRatio="3"
+ android:screenOrientation="sensorLandscape"
+ android:theme="@style/AppTheme">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+
+ <activity
+ android:name=".GameActivity"
+ android:configChanges="orientation|keyboard|keyboardHidden|navigation|screenSize|smallestScreenSize"
+ android:hardwareAccelerated="true"
+ android:launchMode="singleTask"
+ android:maxAspectRatio="3"
+ android:screenOrientation="sensorLandscape"
+ android:theme="@style/AppTheme">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ </intent-filter>
+ <meta-data
+ android:name="android.app.lib_name"
+ android:value="Minetest" />
+ </activity>
+
+ <activity
+ android:name=".InputDialogActivity"
+ android:maxAspectRatio="3"
+ android:theme="@style/InputTheme" />
+
+ <service
+ android:name=".UnzipService"
+ android:enabled="true"
+ android:exported="false" />
+ </application>
+
+</manifest>
diff --git a/build/android/app/src/main/java/net/minetest/minetest/CopyZipTask.java b/build/android/app/src/main/java/net/minetest/minetest/CopyZipTask.java
new file mode 100644
index 000000000..770995502
--- /dev/null
+++ b/build/android/app/src/main/java/net/minetest/minetest/CopyZipTask.java
@@ -0,0 +1,82 @@
+/*
+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.Context;
+import android.content.Intent;
+import android.os.AsyncTask;
+import android.util.Log;
+
+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<Context> contextRef;
+
+ CopyZipTask(Context context) {
+ contextRef = new WeakReference<>(context);
+ }
+
+ protected String doInBackground(String... params) {
+ copyAssets(params);
+ return params[0];
+ }
+
+ @Override
+ protected void onPostExecute(String result) {
+ startUnzipService(result);
+ }
+
+ private void copyAsset(String zipName) throws IOException {
+ String filename = zipName.substring(zipName.lastIndexOf("/") + 1);
+ try (InputStream in = contextRef.get().getAssets().open(filename);
+ OutputStream out = new FileOutputStream(zipName)) {
+ copyFile(in, out);
+ }
+ }
+
+ private void copyAssets(String[] zips) {
+ try {
+ for (String zipName : zips)
+ copyAsset(zipName);
+ } catch (IOException e) {
+ Log.e("CopyZipTask", e.getLocalizedMessage());
+ 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(contextRef.get(), UnzipService.class);
+ intent.putExtra(UnzipService.EXTRA_KEY_IN_FILE, file);
+ contextRef.get().startService(intent);
+ }
+}
diff --git a/build/android/app/src/main/java/net/minetest/minetest/GameActivity.java b/build/android/app/src/main/java/net/minetest/minetest/GameActivity.java
new file mode 100644
index 000000000..02b61b598
--- /dev/null
+++ b/build/android/app/src/main/java/net/minetest/minetest/GameActivity.java
@@ -0,0 +1,120 @@
+/*
+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.app.NativeActivity;
+import android.content.Intent;
+import android.os.Build;
+import android.os.Bundle;
+import android.view.View;
+import android.view.WindowManager;
+
+public class GameActivity extends NativeActivity {
+ static {
+ System.loadLibrary("c++_shared");
+ System.loadLibrary("Minetest");
+ }
+
+ private int messageReturnCode;
+ private String messageReturnValue;
+
+ public static native void putMessageBoxResult(String text);
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+ messageReturnCode = -1;
+ messageReturnValue = "";
+ }
+
+ private void makeFullScreen() {
+ if (Build.VERSION.SDK_INT >= 19)
+ this.getWindow().getDecorView().setSystemUiVisibility(
+ View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
+ View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
+ View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
+ }
+
+ @Override
+ public void onWindowFocusChanged(boolean hasFocus) {
+ super.onWindowFocusChanged(hasFocus);
+ if (hasFocus)
+ makeFullScreen();
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ makeFullScreen();
+ }
+
+ @Override
+ public void onBackPressed() {
+ // Ignore the back press so Minetest can handle it
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ if (requestCode == 101) {
+ if (resultCode == RESULT_OK) {
+ String text = data.getStringExtra("text");
+ messageReturnCode = 0;
+ messageReturnValue = text;
+ } else
+ messageReturnCode = 1;
+ }
+ }
+
+ public void showDialog(String acceptButton, String hint, String current, int editType) {
+ Intent intent = new Intent(this, InputDialogActivity.class);
+ Bundle params = new Bundle();
+ params.putString("acceptButton", acceptButton);
+ params.putString("hint", hint);
+ params.putString("current", current);
+ params.putInt("editType", editType);
+ intent.putExtras(params);
+ startActivityForResult(intent, 101);
+ messageReturnValue = "";
+ messageReturnCode = -1;
+ }
+
+ public int getDialogState() {
+ return messageReturnCode;
+ }
+
+ public String getDialogValue() {
+ messageReturnCode = -1;
+ return messageReturnValue;
+ }
+
+ public float getDensity() {
+ return getResources().getDisplayMetrics().density;
+ }
+
+ public int getDisplayHeight() {
+ return getResources().getDisplayMetrics().heightPixels;
+ }
+
+ public int getDisplayWidth() {
+ return getResources().getDisplayMetrics().widthPixels;
+ }
+}
diff --git a/build/android/app/src/main/java/net/minetest/minetest/InputDialogActivity.java b/build/android/app/src/main/java/net/minetest/minetest/InputDialogActivity.java
new file mode 100644
index 000000000..7c6aa111d
--- /dev/null
+++ b/build/android/app/src/main/java/net/minetest/minetest/InputDialogActivity.java
@@ -0,0 +1,98 @@
+/*
+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.app.Activity;
+import android.content.Intent;
+import android.os.Build;
+import android.os.Bundle;
+import android.text.InputType;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.EditText;
+
+import androidx.appcompat.app.AlertDialog;
+import androidx.appcompat.app.AppCompatActivity;
+
+import java.util.Objects;
+
+public class InputDialogActivity extends AppCompatActivity {
+ private AlertDialog alertDialog;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ Bundle b = getIntent().getExtras();
+ int editType = Objects.requireNonNull(b).getInt("editType");
+ String hint = b.getString("hint");
+ String current = b.getString("current");
+ final AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ EditText editText = new EditText(this);
+ builder.setView(editText);
+ editText.requestFocus();
+ editText.setHint(hint);
+ editText.setText(current);
+ final InputMethodManager imm = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);
+ Objects.requireNonNull(imm).toggleSoftInput(InputMethodManager.SHOW_FORCED,
+ InputMethodManager.HIDE_IMPLICIT_ONLY);
+ if (editType == 3)
+ editText.setInputType(InputType.TYPE_CLASS_TEXT |
+ InputType.TYPE_TEXT_VARIATION_PASSWORD);
+ else
+ editText.setInputType(InputType.TYPE_CLASS_TEXT);
+ editText.setOnKeyListener((view, KeyCode, event) -> {
+ if (KeyCode == KeyEvent.KEYCODE_ENTER) {
+ imm.hideSoftInputFromWindow(editText.getWindowToken(), 0);
+ pushResult(editText.getText().toString());
+ return true;
+ }
+ return false;
+ });
+ alertDialog = builder.create();
+ if (!this.isFinishing())
+ alertDialog.show();
+ alertDialog.setOnCancelListener(dialog -> {
+ pushResult(editText.getText().toString());
+ setResult(Activity.RESULT_CANCELED);
+ alertDialog.dismiss();
+ makeFullScreen();
+ finish();
+ });
+ }
+
+ private void pushResult(String text) {
+ Intent resultData = new Intent();
+ resultData.putExtra("text", text);
+ setResult(AppCompatActivity.RESULT_OK, resultData);
+ alertDialog.dismiss();
+ makeFullScreen();
+ finish();
+ }
+
+ private void makeFullScreen() {
+ if (Build.VERSION.SDK_INT >= 19)
+ this.getWindow().getDecorView().setSystemUiVisibility(
+ View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
+ View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
+ View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
+ }
+}
diff --git a/build/android/app/src/main/java/net/minetest/minetest/MainActivity.java b/build/android/app/src/main/java/net/minetest/minetest/MainActivity.java
new file mode 100644
index 000000000..f37ae6d4b
--- /dev/null
+++ b/build/android/app/src/main/java/net/minetest/minetest/MainActivity.java
@@ -0,0 +1,139 @@
+/*
+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.Manifest;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.SharedPreferences;
+import android.content.pm.PackageManager;
+import android.os.Build;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import androidx.annotation.NonNull;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.core.app.ActivityCompat;
+import androidx.core.content.ContextCompat;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+public class MainActivity extends AppCompatActivity {
+ private final static int versionCode = BuildConfig.VERSION_CODE;
+ private final static int PERMISSIONS = 1;
+ private static final String[] REQUIRED_SDK_PERMISSIONS =
+ 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)
+ progress = intent.getIntExtra(UnzipService.ACTION_PROGRESS, 0);
+ if (progress >= 0) {
+ if (mProgressBar != null) {
+ mProgressBar.setVisibility(View.VISIBLE);
+ mProgressBar.setProgress(progress);
+ }
+ mTextView.setVisibility(View.VISIBLE);
+ } else
+ startNative();
+ }
+ };
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_main);
+ IntentFilter filter = new IntentFilter(UnzipService.ACTION_UPDATE);
+ registerReceiver(myReceiver, filter);
+ 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
+ checkAppVersion();
+ }
+
+ private void checkPermission() {
+ final List<String> missingPermissions = new ArrayList<>();
+ for (final String permission : REQUIRED_SDK_PERMISSIONS) {
+ final int result = ContextCompat.checkSelfPermission(this, permission);
+ if (result != PackageManager.PERMISSION_GRANTED)
+ missingPermissions.add(permission);
+ }
+ if (!missingPermissions.isEmpty()) {
+ final String[] permissions = missingPermissions
+ .toArray(new String[0]);
+ ActivityCompat.requestPermissions(this, permissions, PERMISSIONS);
+ } else {
+ final int[] grantResults = new int[REQUIRED_SDK_PERMISSIONS.length];
+ Arrays.fill(grantResults, PackageManager.PERMISSION_GRANTED);
+ onRequestPermissionsResult(PERMISSIONS, REQUIRED_SDK_PERMISSIONS, grantResults);
+ }
+ }
+
+ @Override
+ public void onRequestPermissionsResult(int requestCode,
+ @NonNull String[] permissions, @NonNull int[] grantResults) {
+ if (requestCode == PERMISSIONS) {
+ for (int grantResult : grantResults) {
+ if (grantResult != PackageManager.PERMISSION_GRANTED) {
+ Toast.makeText(this, R.string.not_granted, Toast.LENGTH_LONG).show();
+ finish();
+ }
+ }
+ checkAppVersion();
+ }
+ }
+
+ private void checkAppVersion() {
+ if (sharedPreferences.getInt(TAG_VERSION_CODE, 0) == versionCode)
+ startNative();
+ else
+ new CopyZipTask(this).execute(getCacheDir() + "/Minetest.zip");
+ }
+
+ private void startNative() {
+ sharedPreferences.edit().putInt(TAG_VERSION_CODE, versionCode).apply();
+ Intent intent = new Intent(this, GameActivity.class);
+ intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ startActivity(intent);
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ unregisterReceiver(myReceiver);
+ }
+}
diff --git a/build/android/app/src/main/java/net/minetest/minetest/UnzipService.java b/build/android/app/src/main/java/net/minetest/minetest/UnzipService.java
new file mode 100644
index 000000000..ac9116994
--- /dev/null
+++ b/build/android/app/src/main/java/net/minetest/minetest/UnzipService.java
@@ -0,0 +1,153 @@
+/*
+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.app.IntentService;
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Build;
+import android.os.Environment;
+import android.util.Log;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+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 EXTRA_KEY_IN_FILE = "file";
+ private static final String TAG = "UnzipService";
+ private final int id = 1;
+ private NotificationManager mNotifyManager;
+
+ public UnzipService() {
+ super("net.minetest.minetest.UnzipService");
+ }
+
+ private void isDir(String dir, String location) {
+ File f = new File(location + dir);
+ if (!f.isDirectory())
+ f.mkdirs();
+ }
+
+ @Override
+ protected void onHandleIntent(Intent intent) {
+ createNotification();
+ unzip(intent);
+ }
+
+ private void createNotification() {
+ String name = "net.minetest.minetest";
+ String channelId = "Minetest channel";
+ String description = "notifications from Minetest";
+ Notification.Builder builder;
+ if (mNotifyManager == null)
+ mNotifyManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ int importance = NotificationManager.IMPORTANCE_LOW;
+ NotificationChannel mChannel = null;
+ if (mNotifyManager != null)
+ mChannel = mNotifyManager.getNotificationChannel(channelId);
+ if (mChannel == null) {
+ mChannel = new NotificationChannel(channelId, name, importance);
+ mChannel.setDescription(description);
+ // Configure the notification channel, NO SOUND
+ mChannel.setSound(null, null);
+ mChannel.enableLights(false);
+ mChannel.enableVibration(false);
+ mNotifyManager.createNotificationChannel(mChannel);
+ }
+ builder = new Notification.Builder(this, channelId);
+ } else {
+ builder = new Notification.Builder(this);
+ }
+ builder.setContentTitle(getString(R.string.notification_title))
+ .setSmallIcon(R.mipmap.ic_launcher)
+ .setContentText(getString(R.string.notification_description));
+ mNotifyManager.notify(id, builder.build());
+ }
+
+ private void unzip(Intent intent) {
+ String zip = intent.getStringExtra(EXTRA_KEY_IN_FILE);
+ String location = Environment.getExternalStorageDirectory() + "/Minetest/";
+ int per = 0;
+ int size = getSummarySize(zip);
+ File zipFile = new File(zip);
+ int readLen;
+ byte[] readBuffer = new byte[8192];
+ 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);
+ }
+ }
+ }
+ zipFile.delete();
+ }
+ } catch (FileNotFoundException e) {
+ Log.e(TAG, e.getLocalizedMessage());
+ } catch (IOException e) {
+ Log.e(TAG, e.getLocalizedMessage());
+ }
+ }
+
+ private void publishProgress(int progress) {
+ Intent intentUpdate = new Intent(ACTION_UPDATE);
+ intentUpdate.putExtra(ACTION_PROGRESS, progress);
+ sendBroadcast(intentUpdate);
+ }
+
+ private int getSummarySize(String zip) {
+ int size = 0;
+ try {
+ ZipFile zipSize = new ZipFile(zip);
+ size += zipSize.size();
+ } catch (IOException e) {
+ Log.e(TAG, e.getLocalizedMessage());
+ }
+ return size;
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ mNotifyManager.cancel(id);
+ publishProgress(-1);
+ }
+}
diff --git a/build/android/app/src/main/res/drawable/background.png b/build/android/app/src/main/res/drawable/background.png
new file mode 100644
index 000000000..43bd6089e
--- /dev/null
+++ b/build/android/app/src/main/res/drawable/background.png
Binary files differ
diff --git a/build/android/app/src/main/res/drawable/bg.xml b/build/android/app/src/main/res/drawable/bg.xml
new file mode 100644
index 000000000..903335ed9
--- /dev/null
+++ b/build/android/app/src/main/res/drawable/bg.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
+ android:src="@drawable/background"
+ android:tileMode="repeat" />
diff --git a/build/android/app/src/main/res/layout/activity_main.xml b/build/android/app/src/main/res/layout/activity_main.xml
new file mode 100644
index 000000000..e6f461f14
--- /dev/null
+++ b/build/android/app/src/main/res/layout/activity_main.xml
@@ -0,0 +1,30 @@
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/activity_main"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@drawable/bg">
+
+ <ProgressBar
+ android:id="@+id/progressBar"
+ style="@style/CustomProgressBar"
+ android:layout_width="match_parent"
+ android:layout_height="30dp"
+ android:layout_centerInParent="true"
+ android:layout_marginLeft="90dp"
+ android:layout_marginRight="90dp"
+ android:indeterminate="false"
+ android:max="100"
+ android:visibility="gone" />
+
+ <TextView
+ android:id="@+id/textView"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_below="@id/progressBar"
+ android:layout_centerInParent="true"
+ android:background="@android:color/transparent"
+ android:text="@string/loading"
+ android:textColor="#FEFEFE"
+ android:visibility="gone" />
+
+</RelativeLayout>
diff --git a/build/android/app/src/main/res/mipmap/ic_launcher.png b/build/android/app/src/main/res/mipmap/ic_launcher.png
new file mode 100644
index 000000000..88a83782c
--- /dev/null
+++ b/build/android/app/src/main/res/mipmap/ic_launcher.png
Binary files differ
diff --git a/build/android/app/src/main/res/values/strings.xml b/build/android/app/src/main/res/values/strings.xml
new file mode 100644
index 000000000..a6fba70d5
--- /dev/null
+++ b/build/android/app/src/main/res/values/strings.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+ <string name="label">Minetest</string>
+ <string name="loading">Loading&#8230;</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>
+
+</resources>
diff --git a/build/android/app/src/main/res/values/styles.xml b/build/android/app/src/main/res/values/styles.xml
new file mode 100644
index 000000000..618507e63
--- /dev/null
+++ b/build/android/app/src/main/res/values/styles.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources xmlns:tools="http://schemas.android.com/tools">
+
+ <style name="AppTheme" parent="Theme.AppCompat.DayNight.NoActionBar">
+ <item name="windowActionBar">false</item>
+ <item name="android:windowFullscreen">true</item>
+ <item name="android:windowBackground">@drawable/bg</item>
+ <item name="android:windowLayoutInDisplayCutoutMode" tools:targetApi="p">shortEdges</item>
+ </style>
+
+ <style name="InputTheme" parent="Theme.AppCompat.DayNight.Dialog">
+ <item name="windowNoTitle">true</item>
+ <item name="android:windowBackground">@android:color/transparent</item>
+ </style>
+
+ <style name="CustomProgressBar" parent="@style/Widget.AppCompat.ProgressBar.Horizontal">
+ <item name="android:indeterminateOnly">false</item>
+ <item name="android:minHeight">10dip</item>
+ <item name="android:maxHeight">20dip</item>
+ </style>
+
+</resources>