3 Copyright (C) 2014-2020 MoNTE48, Maksim Gamarnik <MoNTE48@mail.ua>
4 Copyright (C) 2014-2020 ubulem, Bektur Mambetov <berkut87@gmail.com>
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU Lesser General Public License as published by
8 the Free Software Foundation; either version 2.1 of the License, or
9 (at your option) any later version.
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU Lesser General Public License for more details.
16 You should have received a copy of the GNU Lesser General Public License along
17 with this program; if not, write to the Free Software Foundation, Inc.,
18 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21 package net.minetest.minetest;
23 import android.app.IntentService;
24 import android.app.Notification;
25 import android.app.NotificationChannel;
26 import android.app.NotificationManager;
27 import android.app.PendingIntent;
28 import android.content.Context;
29 import android.content.Intent;
30 import android.os.Build;
31 import android.os.Environment;
33 import androidx.annotation.NonNull;
34 import androidx.annotation.Nullable;
35 import androidx.annotation.StringRes;
38 import java.io.FileInputStream;
39 import java.io.FileOutputStream;
40 import java.io.IOException;
41 import java.io.InputStream;
42 import java.io.OutputStream;
43 import java.util.zip.ZipEntry;
44 import java.util.zip.ZipFile;
45 import java.util.zip.ZipInputStream;
47 public class UnzipService extends IntentService {
48 public static final String ACTION_UPDATE = "net.minetest.minetest.UPDATE";
49 public static final String ACTION_PROGRESS = "net.minetest.minetest.PROGRESS";
50 public static final String ACTION_PROGRESS_MESSAGE = "net.minetest.minetest.PROGRESS_MESSAGE";
51 public static final String ACTION_FAILURE = "net.minetest.minetest.FAILURE";
52 public static final int SUCCESS = -1;
53 public static final int FAILURE = -2;
54 public static final int INDETERMINATE = -3;
55 private final int id = 1;
56 private NotificationManager mNotifyManager;
57 private boolean isSuccess = true;
58 private String failureMessage;
60 private static boolean isRunning = false;
61 public static synchronized boolean getIsRunning() {
64 private static synchronized void setIsRunning(boolean v) {
68 public UnzipService() {
69 super("net.minetest.minetest.UnzipService");
73 protected void onHandleIntent(Intent intent) {
74 Notification.Builder notificationBuilder = createNotification();
75 final File zipFile = new File(getCacheDir(), "Minetest.zip");
78 File userDataDirectory = Utils.getUserDataDirectory(this);
79 if (userDataDirectory == null) {
80 throw new IOException("Unable to find user data directory");
83 try (InputStream in = this.getAssets().open(zipFile.getName())) {
84 try (OutputStream out = new FileOutputStream(zipFile)) {
86 byte[] readBuffer = new byte[16384];
87 while ((readLen = in.read(readBuffer)) != -1) {
88 out.write(readBuffer, 0, readLen);
93 migrate(notificationBuilder, userDataDirectory);
94 unzip(notificationBuilder, zipFile, userDataDirectory);
95 } catch (IOException e) {
97 failureMessage = e.getLocalizedMessage();
104 private Notification.Builder createNotification() {
105 String name = "net.minetest.minetest";
106 String channelId = "Minetest channel";
107 String description = "notifications from Minetest";
108 Notification.Builder builder;
109 if (mNotifyManager == null)
110 mNotifyManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
111 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
112 int importance = NotificationManager.IMPORTANCE_LOW;
113 NotificationChannel mChannel = null;
114 if (mNotifyManager != null)
115 mChannel = mNotifyManager.getNotificationChannel(channelId);
116 if (mChannel == null) {
117 mChannel = new NotificationChannel(channelId, name, importance);
118 mChannel.setDescription(description);
119 // Configure the notification channel, NO SOUND
120 mChannel.setSound(null, null);
121 mChannel.enableLights(false);
122 mChannel.enableVibration(false);
123 mNotifyManager.createNotificationChannel(mChannel);
125 builder = new Notification.Builder(this, channelId);
127 builder = new Notification.Builder(this);
130 Intent notificationIntent = new Intent(this, MainActivity.class);
131 notificationIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP
132 | Intent.FLAG_ACTIVITY_SINGLE_TOP);
133 PendingIntent intent = PendingIntent.getActivity(this, 0,
134 notificationIntent, 0);
136 builder.setContentTitle(getString(R.string.notification_title))
137 .setSmallIcon(R.mipmap.ic_launcher)
138 .setContentText(getString(R.string.notification_description))
139 .setContentIntent(intent)
141 .setProgress(0, 0, true);
143 mNotifyManager.notify(id, builder.build());
147 private void unzip(Notification.Builder notificationBuilder, File zipFile, File userDataDirectory) throws IOException {
151 try (ZipFile zipSize = new ZipFile(zipFile)) {
152 size = zipSize.size();
156 byte[] readBuffer = new byte[16384];
157 try (FileInputStream fileInputStream = new FileInputStream(zipFile);
158 ZipInputStream zipInputStream = new ZipInputStream(fileInputStream)) {
160 while ((ze = zipInputStream.getNextEntry()) != null) {
161 if (ze.isDirectory()) {
163 Utils.createDirs(userDataDirectory, ze.getName());
166 publishProgress(notificationBuilder, R.string.loading, 100 * ++per / size);
167 try (OutputStream outputStream = new FileOutputStream(
168 new File(userDataDirectory, ze.getName()))) {
169 while ((readLen = zipInputStream.read(readBuffer)) != -1) {
170 outputStream.write(readBuffer, 0, readLen);
177 void moveFileOrDir(@NonNull File src, @NonNull File dst) throws IOException {
179 Process p = new ProcessBuilder("/system/bin/mv",
180 src.getAbsolutePath(), dst.getAbsolutePath()).start();
181 int exitcode = p.waitFor();
183 throw new IOException("Move failed with exit code " + exitcode);
184 } catch (InterruptedException e) {
185 throw new IOException("Move operation interrupted");
189 boolean recursivelyDeleteDirectory(@NonNull File loc) {
191 Process p = new ProcessBuilder("/system/bin/rm", "-rf",
192 loc.getAbsolutePath()).start();
193 return p.waitFor() == 0;
194 } catch (IOException | InterruptedException e) {
200 * Migrates user data from deprecated external storage to app scoped storage
202 private void migrate(Notification.Builder notificationBuilder, File newLocation) throws IOException {
203 File oldLocation = new File(Environment.getExternalStorageDirectory(), "Minetest");
204 if (!oldLocation.isDirectory())
207 publishProgress(notificationBuilder, R.string.migrating, 0);
210 String[] dirs = new String[] { "worlds", "games", "mods", "textures", "client" };
211 for (int i = 0; i < dirs.length; i++) {
212 publishProgress(notificationBuilder, R.string.migrating, 100 * i / dirs.length);
213 File dir = new File(oldLocation, dirs[i]), dir2 = new File(newLocation, dirs[i]);
214 if (dir.isDirectory() && !dir2.isDirectory()) {
215 moveFileOrDir(dir, dir2);
219 for (String filename : new String[] { "minetest.conf" }) {
220 File file = new File(oldLocation, filename), file2 = new File(newLocation, filename);
221 if (file.isFile() && !file2.isFile()) {
222 moveFileOrDir(file, file2);
226 recursivelyDeleteDirectory(oldLocation);
229 private void publishProgress(@Nullable Notification.Builder notificationBuilder, @StringRes int message, int progress) {
230 Intent intentUpdate = new Intent(ACTION_UPDATE);
231 intentUpdate.putExtra(ACTION_PROGRESS, progress);
232 intentUpdate.putExtra(ACTION_PROGRESS_MESSAGE, message);
234 intentUpdate.putExtra(ACTION_FAILURE, failureMessage);
235 sendBroadcast(intentUpdate);
237 if (notificationBuilder != null) {
238 notificationBuilder.setContentText(getString(message));
239 if (progress == INDETERMINATE) {
240 notificationBuilder.setProgress(100, 50, true);
242 notificationBuilder.setProgress(100, progress, false);
244 mNotifyManager.notify(id, notificationBuilder.build());
249 public void onDestroy() {
251 mNotifyManager.cancel(id);
252 publishProgress(null, R.string.loading, isSuccess ? SUCCESS : FAILURE);