From 3af66b72f212d78e87416c34fa0db12a7f0b7252 Mon Sep 17 00:00:00 2001 From: topjohnwu Date: Sun, 2 Dec 2018 23:41:16 -0500 Subject: [PATCH] Use notifications when downloading modules --- .../magisk/adapters/ReposAdapter.java | 21 +- .../magisk/asyncs/DownloadModule.java | 136 ++++++++++++ .../magisk/asyncs/ProcessRepoZip.java | 196 ------------------ .../components/NotificationProgress.java | 7 + app/src/full/res/values/strings.xml | 2 +- 5 files changed, 154 insertions(+), 208 deletions(-) create mode 100644 app/src/full/java/com/topjohnwu/magisk/asyncs/DownloadModule.java delete mode 100644 app/src/full/java/com/topjohnwu/magisk/asyncs/ProcessRepoZip.java diff --git a/app/src/full/java/com/topjohnwu/magisk/adapters/ReposAdapter.java b/app/src/full/java/com/topjohnwu/magisk/adapters/ReposAdapter.java index 32b2c7562..a72e12184 100644 --- a/app/src/full/java/com/topjohnwu/magisk/adapters/ReposAdapter.java +++ b/app/src/full/java/com/topjohnwu/magisk/adapters/ReposAdapter.java @@ -12,8 +12,8 @@ import android.widget.LinearLayout; import android.widget.TextView; import com.topjohnwu.magisk.R; +import com.topjohnwu.magisk.asyncs.DownloadModule; import com.topjohnwu.magisk.asyncs.MarkDownWindow; -import com.topjohnwu.magisk.asyncs.ProcessRepoZip; import com.topjohnwu.magisk.components.BaseActivity; import com.topjohnwu.magisk.components.CustomAlertDialog; import com.topjohnwu.magisk.container.Module; @@ -105,16 +105,15 @@ public class ReposAdapter extends SectionedAdapter { new CustomAlertDialog((BaseActivity) context) - .setTitle(context.getString(R.string.repo_install_title, repo.getName())) - .setMessage(context.getString(R.string.repo_install_msg, repo.getDownloadFilename())) - .setCancelable(true) - .setPositiveButton(R.string.install, (d, i) -> - new ProcessRepoZip((BaseActivity) context, repo, true).exec() - ) - .setNeutralButton(R.string.download, (d, i) -> - new ProcessRepoZip((BaseActivity) context, repo, false).exec()) - .setNegativeButton(R.string.no_thanks, null) - .show(); + .setTitle(context.getString(R.string.repo_install_title, repo.getName())) + .setMessage(context.getString(R.string.repo_install_msg, repo.getDownloadFilename())) + .setCancelable(true) + .setPositiveButton(R.string.install, (d, i) -> + DownloadModule.exec((BaseActivity) context, repo, true)) + .setNeutralButton(R.string.download, (d, i) -> + DownloadModule.exec((BaseActivity) context, repo, false)) + .setNegativeButton(R.string.no_thanks, null) + .show(); }); } diff --git a/app/src/full/java/com/topjohnwu/magisk/asyncs/DownloadModule.java b/app/src/full/java/com/topjohnwu/magisk/asyncs/DownloadModule.java new file mode 100644 index 000000000..3d7160fdf --- /dev/null +++ b/app/src/full/java/com/topjohnwu/magisk/asyncs/DownloadModule.java @@ -0,0 +1,136 @@ +package com.topjohnwu.magisk.asyncs; + +import android.Manifest; +import android.content.Intent; +import android.net.Uri; +import android.os.AsyncTask; + +import com.topjohnwu.magisk.Const; +import com.topjohnwu.magisk.Data; +import com.topjohnwu.magisk.FlashActivity; +import com.topjohnwu.magisk.MagiskManager; +import com.topjohnwu.magisk.R; +import com.topjohnwu.magisk.components.BaseActivity; +import com.topjohnwu.magisk.components.NotificationProgress; +import com.topjohnwu.magisk.container.Repo; +import com.topjohnwu.magisk.utils.WebService; +import com.topjohnwu.magisk.utils.ZipUtils; +import com.topjohnwu.superuser.ShellUtils; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; +import java.util.zip.ZipOutputStream; + +public class DownloadModule { + + public static void exec(BaseActivity activity, Repo repo, boolean install) { + activity.runWithPermission(new String[] { Manifest.permission.WRITE_EXTERNAL_STORAGE }, + () -> AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> dlProcessInstall(repo, install))); + } + + private static void dlProcessInstall(Repo repo, boolean install) { + File output = new File(Const.EXTERNAL_PATH, repo.getDownloadFilename()); + NotificationProgress progress = new NotificationProgress(output.getName()); + try { + MagiskManager mm = Data.MM(); + File temp1 = new File(mm.getCacheDir(), "temp.zip"); + + HttpURLConnection conn = WebService.mustRequest(repo.getZipUrl()); + ProgressInputStream pis = new ProgressInputStream(conn.getInputStream(), + conn.getContentLength(), progress); + removeTopFolder(new BufferedInputStream(pis), + new BufferedOutputStream(new FileOutputStream(temp1))); + conn.disconnect(); + progress.getNotification() + .setProgress(0, 0, true) + .setContentTitle(mm.getString(R.string.zip_process_msg)) + .setContentText(""); + progress.update(); + ZipUtils.signZip(temp1, output); + temp1.delete(); + if (install) { + progress.dismiss(); + Intent intent = new Intent(mm, Data.classMap.get(FlashActivity.class)); + intent.setData(Uri.fromFile(output)) + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + .putExtra(Const.Key.FLASH_ACTION, Const.Value.FLASH_ZIP); + mm.startActivity(intent); + } else { + progress.getNotification().setContentTitle(output.getName()); + progress.dlDone(); + } + } catch (Exception e) { + e.printStackTrace(); + progress.dlFail(); + } + } + + private static void removeTopFolder(InputStream in, OutputStream out) throws IOException { + try (ZipInputStream zin = new ZipInputStream(in); + ZipOutputStream zout = new ZipOutputStream(out)) { + ZipEntry entry; + int off = -1; + while ((entry = zin.getNextEntry()) != null) { + if (off < 0) + off = entry.getName().indexOf('/') + 1; + String path = entry.getName().substring(off); + if (path.isEmpty()) + continue; + zout.putNextEntry(new ZipEntry(path)); + if (!entry.isDirectory()) + ShellUtils.pump(zin, zout); + } + } + } + + private static class ProgressInputStream extends FilterInputStream { + + private long totalBytes; + private long bytesDownloaded; + private NotificationProgress progress; + + protected ProgressInputStream(InputStream in, long size, NotificationProgress p) { + super(in); + totalBytes = size; + progress = p; + } + + private void updateProgress() { + progress.onProgress(bytesDownloaded, totalBytes); + } + + @Override + public int read() throws IOException { + int b = super.read(); + if (b >= 0) { + bytesDownloaded++; + updateProgress(); + } + return b; + } + + @Override + public int read(byte[] b) throws IOException { + return read(b, 0, b.length); + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + int sz = super.read(b, off, len); + if (sz > 0) { + bytesDownloaded += sz; + updateProgress(); + } + return sz; + } + } +} diff --git a/app/src/full/java/com/topjohnwu/magisk/asyncs/ProcessRepoZip.java b/app/src/full/java/com/topjohnwu/magisk/asyncs/ProcessRepoZip.java deleted file mode 100644 index 40ca8b284..000000000 --- a/app/src/full/java/com/topjohnwu/magisk/asyncs/ProcessRepoZip.java +++ /dev/null @@ -1,196 +0,0 @@ -package com.topjohnwu.magisk.asyncs; - -import android.Manifest; -import android.app.ProgressDialog; -import android.content.Intent; -import android.net.Uri; -import android.widget.Toast; - -import com.topjohnwu.magisk.Const; -import com.topjohnwu.magisk.Data; -import com.topjohnwu.magisk.FlashActivity; -import com.topjohnwu.magisk.R; -import com.topjohnwu.magisk.components.BaseActivity; -import com.topjohnwu.magisk.components.SnackbarMaker; -import com.topjohnwu.magisk.container.Repo; -import com.topjohnwu.magisk.utils.Utils; -import com.topjohnwu.magisk.utils.WebService; -import com.topjohnwu.magisk.utils.ZipUtils; -import com.topjohnwu.superuser.Shell; -import com.topjohnwu.superuser.ShellUtils; - -import java.io.BufferedInputStream; -import java.io.BufferedOutputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.FilterInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.net.HttpURLConnection; -import java.util.jar.JarEntry; -import java.util.jar.JarInputStream; -import java.util.jar.JarOutputStream; - -import androidx.annotation.NonNull; - -public class ProcessRepoZip extends ParallelTask { - - private ProgressDialog progressDialog; - private boolean mInstall; - private File mFile; - private Repo mRepo; - private int progress = 0, total = -1; - - public ProcessRepoZip(BaseActivity context, Repo repo, boolean install) { - super(context); - mRepo = repo; - mInstall = install && Shell.rootAccess(); - mFile = new File(Const.EXTERNAL_PATH, repo.getDownloadFilename()); - } - - private void removeTopFolder(File input, File output) throws IOException { - JarEntry entry; - try ( - JarInputStream in = new JarInputStream(new BufferedInputStream(new FileInputStream(input))); - JarOutputStream out = new JarOutputStream(new BufferedOutputStream(new FileOutputStream(output))) - ) { - String path; - while ((entry = in.getNextJarEntry()) != null) { - // Remove the top directory from the path - path = entry.getName().substring(entry.getName().indexOf("/") + 1); - // If it's the top folder, ignore it - if (path.isEmpty()) { - continue; - } - // Don't include placeholder - if (path.equals("system/placeholder")) { - continue; - } - out.putNextEntry(new JarEntry(path)); - ShellUtils.pump(in, out); - } - } - } - - @Override - protected BaseActivity getActivity() { - return (BaseActivity) super.getActivity(); - } - - @Override - protected void onPreExecute() { - BaseActivity activity = getActivity(); - mFile.getParentFile().mkdirs(); - progressDialog = ProgressDialog.show(activity, activity.getString(R.string.zip_download_title), activity.getString(R.string.zip_download_msg, 0)); - } - - @Override - protected Boolean doInBackground(Void... params) { - BaseActivity activity = getActivity(); - if (activity == null) return null; - try { - // Request zip from Internet - HttpURLConnection conn = WebService.mustRequest(mRepo.getZipUrl()); - total = conn.getContentLength(); - - // Temp files - File temp1 = new File(activity.getCacheDir(), "1.zip"); - File temp2 = new File(temp1.getParentFile(), "2.zip"); - temp1.getParentFile().mkdir(); - - // First upgrade the zip, Web -> temp1 - try ( - InputStream in = new BufferedInputStream(new ProgressInputStream(conn.getInputStream())); - OutputStream out = new BufferedOutputStream(new FileOutputStream(temp1)) - ) { - ShellUtils.pump(in, out); - in.close(); - } - conn.disconnect(); - - Data.mainHandler.post(() -> { - progressDialog.setTitle(R.string.zip_process_title); - progressDialog.setMessage(getActivity().getString(R.string.zip_process_msg)); - }); - - // First remove top folder in Github source zip, temp1 -> temp2 - removeTopFolder(temp1, temp2); - - // Then sign the zip - ZipUtils.signZip(temp2, mFile); - - // Delete temp files - temp1.delete(); - temp2.delete(); - - return true; - } catch (Exception e) { - e.printStackTrace(); - return false; - } - } - - @Override - protected void onPostExecute(Boolean result) { - BaseActivity activity = getActivity(); - if (activity == null) return; - progressDialog.dismiss(); - if (result) { - Uri uri = Uri.fromFile(mFile); - if (mInstall) { - Intent intent = new Intent(activity, Data.classMap.get(FlashActivity.class)); - intent.setData(uri).putExtra(Const.Key.FLASH_ACTION, Const.Value.FLASH_ZIP); - activity.startActivity(intent); - } else { - SnackbarMaker.showUri(activity, uri); - } - } else { - Utils.toast(R.string.process_error, Toast.LENGTH_LONG); - } - super.onPostExecute(result); - } - - @Override - public void exec(Void... voids) { - getActivity().runWithPermission( - new String[] { Manifest.permission.WRITE_EXTERNAL_STORAGE }, super::exec); - } - - private class ProgressInputStream extends FilterInputStream { - - ProgressInputStream(InputStream in) { - super(in); - } - - private void updateDlProgress(int step) { - progress += step; - progressDialog.setMessage(getActivity().getString(R.string.zip_download_msg, - (int) (100 * (double) progress / total + 0.5))); - } - - @Override - public synchronized int read() throws IOException { - int b = super.read(); - if (b > 0) { - Data.mainHandler.post(() -> updateDlProgress(1)); - } - return b; - } - - @Override - public int read(@NonNull byte[] b) throws IOException { - return read(b, 0, b.length); - } - - @Override - public synchronized int read(@NonNull byte[] b, int off, int len) throws IOException { - int read = super.read(b, off, len); - if (read > 0) { - Data.mainHandler.post(() -> updateDlProgress(read)); - } - return read; - } - } -} diff --git a/app/src/full/java/com/topjohnwu/magisk/components/NotificationProgress.java b/app/src/full/java/com/topjohnwu/magisk/components/NotificationProgress.java index 43883051b..d7b353467 100644 --- a/app/src/full/java/com/topjohnwu/magisk/components/NotificationProgress.java +++ b/app/src/full/java/com/topjohnwu/magisk/components/NotificationProgress.java @@ -55,6 +55,13 @@ public class NotificationProgress implements DownloadProgressListener { update(); } + public void dlFail() { + builder.setProgress(0, 0, false) + .setContentText(Data.MM().getString(R.string.download_file_error)) + .setSmallIcon(R.drawable.ic_cancel); + update(); + } + public void dismiss() { mgr.cancel(Const.ID.DOWNLOAD_PROGRESS_ID); } diff --git a/app/src/full/res/values/strings.xml b/app/src/full/res/values/strings.xml index 58d7520e3..488d5aa75 100644 --- a/app/src/full/res/values/strings.xml +++ b/app/src/full/res/values/strings.xml @@ -74,6 +74,7 @@ Magisk Updates Progress Notifications Download complete + Error downloading file New Magisk Update Available! New Magisk Manager Update Available! @@ -123,7 +124,6 @@ Your device needs additional setup for Magisk to work properly. It will download the Magisk setup zip, do you want to proceed now? Additional Setup Running environment setup… - Error downloading file Downloading %1$s This feature will not work without permission to write external storage.