Use notifications when downloading modules

This commit is contained in:
topjohnwu 2018-12-02 23:41:16 -05:00
parent 422c24bd68
commit 3af66b72f2
5 changed files with 154 additions and 208 deletions

View File

@ -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<ReposAdapter.SectionHolder, R
holder.downloadImage.setOnClickListener(v -> {
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();
});
}

View File

@ -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;
}
}
}

View File

@ -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<Void, Object, Boolean> {
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;
}
}
}

View File

@ -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);
}

View File

@ -74,6 +74,7 @@
<string name="update_channel">Magisk Updates</string>
<string name="progress_channel">Progress Notifications</string>
<string name="download_complete">Download complete</string>
<string name="download_file_error">Error downloading file</string>
<string name="magisk_update_title">New Magisk Update Available!</string>
<string name="manager_update_title">New Magisk Manager Update Available!</string>
@ -123,7 +124,6 @@
<string name="env_fix_msg">Your device needs additional setup for Magisk to work properly. It will download the Magisk setup zip, do you want to proceed now?</string>
<string name="setup_title">Additional Setup</string>
<string name="setup_msg">Running environment setup…</string>
<string name="download_file_error">Error downloading file</string>
<string name="downloading_toast">Downloading %1$s</string>
<string name="no_rw_storage">This feature will not work without permission to write external storage.</string>