From 23c84a780361129ad196f4492d6071058247beea Mon Sep 17 00:00:00 2001 From: topjohnwu Date: Wed, 15 Feb 2017 05:24:02 +0800 Subject: [PATCH] Massive Zip flashing refactoring --- .../com/topjohnwu/magisk/AboutActivity.java | 6 +- .../com/topjohnwu/magisk/InstallFragment.java | 19 +++- .../topjohnwu/magisk/MagiskLogFragment.java | 5 +- .../com/topjohnwu/magisk/ModulesFragment.java | 4 +- .../topjohnwu/magisk/SettingsActivity.java | 3 +- .../com/topjohnwu/magisk/StatusFragment.java | 5 +- .../magisk/adapters/ApplicationAdapter.java | 10 +- .../magisk/adapters/ModulesAdapter.java | 5 +- .../magisk/adapters/PolicyAdapter.java | 13 +-- .../magisk/adapters/ReposAdapter.java | 13 ++- .../asyncs/{FlashZIP.java => FlashZip.java} | 98 ++++++++----------- .../magisk/asyncs/ProcessMagiskZip.java | 93 ++++++++++++++++++ .../magisk/asyncs/ProcessRepoZip.java | 77 +++++++++++++++ .../magisk/components/SnackbarMaker.java | 41 ++++++++ .../magisk/receivers/MagiskDlReceiver.java | 68 ------------- .../magisk/receivers/RepoDlReceiver.java | 42 -------- .../com/topjohnwu/magisk/utils/Utils.java | 40 +++++--- .../com/topjohnwu/magisk/utils/WebWindow.java | 4 +- .../com/topjohnwu/magisk/utils/ZipUtils.java | 61 +++++++++--- app/src/main/res/values/strings.xml | 3 + 20 files changed, 388 insertions(+), 222 deletions(-) rename app/src/main/java/com/topjohnwu/magisk/asyncs/{FlashZIP.java => FlashZip.java} (61%) create mode 100644 app/src/main/java/com/topjohnwu/magisk/asyncs/ProcessMagiskZip.java create mode 100644 app/src/main/java/com/topjohnwu/magisk/asyncs/ProcessRepoZip.java create mode 100644 app/src/main/java/com/topjohnwu/magisk/components/SnackbarMaker.java delete mode 100644 app/src/main/java/com/topjohnwu/magisk/receivers/MagiskDlReceiver.java delete mode 100644 app/src/main/java/com/topjohnwu/magisk/receivers/RepoDlReceiver.java diff --git a/app/src/main/java/com/topjohnwu/magisk/AboutActivity.java b/app/src/main/java/com/topjohnwu/magisk/AboutActivity.java index 547f8f6b0..942079345 100644 --- a/app/src/main/java/com/topjohnwu/magisk/AboutActivity.java +++ b/app/src/main/java/com/topjohnwu/magisk/AboutActivity.java @@ -18,8 +18,8 @@ import android.widget.TextView; import com.topjohnwu.magisk.components.AboutCardRow; import com.topjohnwu.magisk.components.Activity; +import com.topjohnwu.magisk.components.AlertDialogBuilder; import com.topjohnwu.magisk.utils.Logger; -import com.topjohnwu.magisk.utils.Utils; import java.io.IOException; import java.io.InputStream; @@ -86,7 +86,7 @@ public class AboutActivity extends Activity { result = Html.fromHtml(changes); } appChangelog.setOnClickListener(v -> { - AlertDialog d = Utils.getAlertDialogBuilder(this) + AlertDialog d = new AlertDialogBuilder(this) .setTitle(R.string.app_changelog) .setMessage(result) .setPositiveButton(android.R.string.ok, null) @@ -105,7 +105,7 @@ public class AboutActivity extends Activity { } else { result = Html.fromHtml(getString(R.string.app_developers_)); } - AlertDialog d = Utils.getAlertDialogBuilder(this) + AlertDialog d = new AlertDialogBuilder(this) .setTitle(R.string.app_developers) .setMessage(result) .setPositiveButton(android.R.string.ok, null) diff --git a/app/src/main/java/com/topjohnwu/magisk/InstallFragment.java b/app/src/main/java/com/topjohnwu/magisk/InstallFragment.java index 0912188df..3c14f5c64 100644 --- a/app/src/main/java/com/topjohnwu/magisk/InstallFragment.java +++ b/app/src/main/java/com/topjohnwu/magisk/InstallFragment.java @@ -16,8 +16,10 @@ import android.widget.CheckBox; import android.widget.Spinner; import android.widget.TextView; +import com.topjohnwu.magisk.asyncs.ProcessMagiskZip; +import com.topjohnwu.magisk.components.AlertDialogBuilder; import com.topjohnwu.magisk.components.Fragment; -import com.topjohnwu.magisk.receivers.MagiskDlReceiver; +import com.topjohnwu.magisk.receivers.DownloadReceiver; import com.topjohnwu.magisk.utils.CallbackEvent; import com.topjohnwu.magisk.utils.Shell; import com.topjohnwu.magisk.utils.Utils; @@ -67,13 +69,22 @@ public class InstallFragment extends Fragment implements CallbackEvent.Listener< bootImage = getApplication().blockList.get(spinner.getSelectedItemPosition()); } String filename = "Magisk-v" + getApplication().remoteMagiskVersion + ".zip"; - Utils.getAlertDialogBuilder(getActivity()) + new AlertDialogBuilder(getActivity()) .setTitle(getString(R.string.repo_install_title, getString(R.string.magisk))) .setMessage(getString(R.string.repo_install_msg, filename)) .setCancelable(true) .setPositiveButton(R.string.download_install, (dialogInterface, i) -> Utils.dlAndReceive( getActivity(), - new MagiskDlReceiver(bootImage, keepEncChkbox.isChecked(), keepVerityChkbox.isChecked()), + new DownloadReceiver() { + private String boot = bootImage; + private boolean enc = keepEncChkbox.isChecked(); + private boolean verity = keepVerityChkbox.isChecked(); + + @Override + public void onDownloadDone(Uri uri) { + new ProcessMagiskZip(getActivity(), uri, boot, enc, verity).exec(); + } + }, getApplication().magiskLink, Utils.getLegalFilename(filename))) .setNeutralButton(R.string.check_release_notes, (dialog, which) -> { @@ -86,7 +97,7 @@ public class InstallFragment extends Fragment implements CallbackEvent.Listener< uninstallButton.setVisibility(View.GONE); } else { uninstallButton.setOnClickListener(vi -> { - Utils.getAlertDialogBuilder(getActivity()) + new AlertDialogBuilder(getActivity()) .setTitle("Uninstall Magisk") .setMessage("This will remove all modules, MagiskSU, and potentially re-encrypt your device\nAre you sure to process?") .setPositiveButton(R.string.yes, (dialogInterface, i) -> { diff --git a/app/src/main/java/com/topjohnwu/magisk/MagiskLogFragment.java b/app/src/main/java/com/topjohnwu/magisk/MagiskLogFragment.java index 68b5712bc..2cbe5886a 100644 --- a/app/src/main/java/com/topjohnwu/magisk/MagiskLogFragment.java +++ b/app/src/main/java/com/topjohnwu/magisk/MagiskLogFragment.java @@ -26,6 +26,7 @@ import android.widget.Toast; import com.topjohnwu.magisk.asyncs.SerialTask; import com.topjohnwu.magisk.components.Fragment; +import com.topjohnwu.magisk.components.SnackbarMaker; import com.topjohnwu.magisk.utils.Shell; import com.topjohnwu.magisk.utils.Utils; @@ -121,7 +122,7 @@ public class MagiskLogFragment extends Fragment { new Handler().postDelayed(() -> onOptionsItemSelected(mClickedMenuItem), 500); } } else { - Snackbar.make(txtLog, R.string.permissionNotGranted, Snackbar.LENGTH_LONG).show(); + SnackbarMaker.make(txtLog, R.string.permissionNotGranted, Snackbar.LENGTH_LONG).show(); } } } @@ -150,7 +151,7 @@ public class MagiskLogFragment extends Fragment { case 1: Shell.su("echo > " + MAGISK_LOG); - Snackbar.make(txtLog, R.string.logs_cleared, Snackbar.LENGTH_SHORT).show(); + SnackbarMaker.make(txtLog, R.string.logs_cleared, Snackbar.LENGTH_SHORT).show(); return ""; case 2: diff --git a/app/src/main/java/com/topjohnwu/magisk/ModulesFragment.java b/app/src/main/java/com/topjohnwu/magisk/ModulesFragment.java index c0123e102..40768b52f 100644 --- a/app/src/main/java/com/topjohnwu/magisk/ModulesFragment.java +++ b/app/src/main/java/com/topjohnwu/magisk/ModulesFragment.java @@ -14,7 +14,7 @@ import android.widget.TextView; import com.github.clans.fab.FloatingActionButton; import com.topjohnwu.magisk.adapters.ModulesAdapter; -import com.topjohnwu.magisk.asyncs.FlashZIP; +import com.topjohnwu.magisk.asyncs.FlashZip; import com.topjohnwu.magisk.asyncs.LoadModules; import com.topjohnwu.magisk.components.Fragment; import com.topjohnwu.magisk.module.Module; @@ -87,7 +87,7 @@ public class ModulesFragment extends Fragment implements CallbackEvent.Listener< if (requestCode == FETCH_ZIP_CODE && resultCode == Activity.RESULT_OK && data != null) { // Get the URI of the selected file final Uri uri = data.getData(); - new FlashZIP(getActivity(), uri).exec(); + new FlashZip(getActivity(), uri).exec(); } } diff --git a/app/src/main/java/com/topjohnwu/magisk/SettingsActivity.java b/app/src/main/java/com/topjohnwu/magisk/SettingsActivity.java index b7b1c7c00..871e76ac3 100644 --- a/app/src/main/java/com/topjohnwu/magisk/SettingsActivity.java +++ b/app/src/main/java/com/topjohnwu/magisk/SettingsActivity.java @@ -16,6 +16,7 @@ import android.widget.Toast; import com.topjohnwu.magisk.asyncs.MagiskHide; import com.topjohnwu.magisk.asyncs.SerialTask; import com.topjohnwu.magisk.components.Activity; +import com.topjohnwu.magisk.components.AlertDialogBuilder; import com.topjohnwu.magisk.utils.Logger; import com.topjohnwu.magisk.utils.Shell; import com.topjohnwu.magisk.utils.Utils; @@ -188,7 +189,7 @@ public class SettingsActivity extends Activity { enabled = prefs.getBoolean("magiskhide", false); if (enabled) { if (!getApplication().isSuClient) { - Utils.getAlertDialogBuilder(getActivity()) + new AlertDialogBuilder(getActivity()) .setTitle(R.string.no_magisksu_title) .setMessage(R.string.no_magisksu_msg) .setPositiveButton(R.string.understand, (dialog, which) -> new MagiskHide().enable()) diff --git a/app/src/main/java/com/topjohnwu/magisk/StatusFragment.java b/app/src/main/java/com/topjohnwu/magisk/StatusFragment.java index 1f68af727..f0b3f0d59 100644 --- a/app/src/main/java/com/topjohnwu/magisk/StatusFragment.java +++ b/app/src/main/java/com/topjohnwu/magisk/StatusFragment.java @@ -15,6 +15,7 @@ import android.widget.ProgressBar; import android.widget.TextView; import com.topjohnwu.magisk.asyncs.CheckUpdates; +import com.topjohnwu.magisk.components.AlertDialogBuilder; import com.topjohnwu.magisk.components.Fragment; import com.topjohnwu.magisk.utils.CallbackEvent; import com.topjohnwu.magisk.utils.Logger; @@ -97,7 +98,7 @@ public class StatusFragment extends Fragment implements CallbackEvent.Listener { - Snackbar snackbar = Snackbar.make(holder.itemView, R.string.safetyNet_hide_notice, Snackbar.LENGTH_LONG); - ((TextView) snackbar.getView().findViewById(android.support.design.R.id.snackbar_text)).setMaxLines(2); - snackbar.show(); - }); + holder.itemView.setOnClickListener(v -> + SnackbarMaker.make(holder.itemView, + R.string.safetyNet_hide_notice, Snackbar.LENGTH_LONG).show() + ); } else { holder.checkBox.setEnabled(true); holder.checkBox.setChecked(mHideList.contains(info.packageName)); diff --git a/app/src/main/java/com/topjohnwu/magisk/adapters/ModulesAdapter.java b/app/src/main/java/com/topjohnwu/magisk/adapters/ModulesAdapter.java index c03dc936c..62042bdcf 100644 --- a/app/src/main/java/com/topjohnwu/magisk/adapters/ModulesAdapter.java +++ b/app/src/main/java/com/topjohnwu/magisk/adapters/ModulesAdapter.java @@ -13,6 +13,7 @@ import android.widget.TextView; import com.topjohnwu.magisk.R; import com.topjohnwu.magisk.asyncs.SerialTask; +import com.topjohnwu.magisk.components.SnackbarMaker; import com.topjohnwu.magisk.module.Module; import com.topjohnwu.magisk.utils.Shell; @@ -66,7 +67,7 @@ public class ModulesAdapter extends RecyclerView.Adapter Utils.getAlertDialogBuilder(v.getContext()) + holder.delete.setOnClickListener(v -> new AlertDialogBuilder(v.getContext()) .setTitle(R.string.su_revoke_title) .setMessage(v.getContext().getString(R.string.su_revoke_msg, policy.appName)) .setPositiveButton(R.string.yes, (dialog, which) -> { policyList.remove(position); notifyItemRemoved(position); notifyItemRangeChanged(position, policyList.size()); - Snackbar.make(holder.itemView, v.getContext().getString(R.string.su_snack_revoke, policy.appName), + SnackbarMaker.make(holder.itemView, v.getContext().getString(R.string.su_snack_revoke, policy.appName), Snackbar.LENGTH_SHORT).show(); dbHelper.deletePolicy(policy.uid); }) diff --git a/app/src/main/java/com/topjohnwu/magisk/adapters/ReposAdapter.java b/app/src/main/java/com/topjohnwu/magisk/adapters/ReposAdapter.java index 3f6a9f370..5a661c92e 100644 --- a/app/src/main/java/com/topjohnwu/magisk/adapters/ReposAdapter.java +++ b/app/src/main/java/com/topjohnwu/magisk/adapters/ReposAdapter.java @@ -17,8 +17,10 @@ import android.widget.LinearLayout; import android.widget.TextView; import com.topjohnwu.magisk.R; +import com.topjohnwu.magisk.asyncs.ProcessRepoZip; +import com.topjohnwu.magisk.components.AlertDialogBuilder; import com.topjohnwu.magisk.module.Repo; -import com.topjohnwu.magisk.receivers.RepoDlReceiver; +import com.topjohnwu.magisk.receivers.DownloadReceiver; import com.topjohnwu.magisk.utils.Utils; import com.topjohnwu.magisk.utils.WebWindow; @@ -75,13 +77,18 @@ public class ReposAdapter extends RecyclerView.Adapter }); holder.updateImage.setOnClickListener(view -> { String filename = repo.getName() + "-" + repo.getVersion() + ".zip"; - Utils.getAlertDialogBuilder(context) + new AlertDialogBuilder(context) .setTitle(context.getString(R.string.repo_install_title, repo.getName())) .setMessage(context.getString(R.string.repo_install_msg, filename)) .setCancelable(true) .setPositiveButton(R.string.download_install, (dialogInterface, i) -> Utils.dlAndReceive( context, - new RepoDlReceiver(), + new DownloadReceiver() { + @Override + public void onDownloadDone(Uri uri) { + new ProcessRepoZip(activity, uri).exec(); + } + }, repo.getZipUrl(), Utils.getLegalFilename(filename))) .setNegativeButton(R.string.no_thanks, null) diff --git a/app/src/main/java/com/topjohnwu/magisk/asyncs/FlashZIP.java b/app/src/main/java/com/topjohnwu/magisk/asyncs/FlashZip.java similarity index 61% rename from app/src/main/java/com/topjohnwu/magisk/asyncs/FlashZIP.java rename to app/src/main/java/com/topjohnwu/magisk/asyncs/FlashZip.java index 76c7c5603..cbb6fb30d 100644 --- a/app/src/main/java/com/topjohnwu/magisk/asyncs/FlashZIP.java +++ b/app/src/main/java/com/topjohnwu/magisk/asyncs/FlashZip.java @@ -2,13 +2,12 @@ package com.topjohnwu.magisk.asyncs; import android.app.Activity; import android.app.ProgressDialog; -import android.database.Cursor; import android.net.Uri; -import android.provider.OpenableColumns; import android.widget.Toast; import com.topjohnwu.magisk.MagiskManager; import com.topjohnwu.magisk.R; +import com.topjohnwu.magisk.components.AlertDialogBuilder; import com.topjohnwu.magisk.utils.Logger; import com.topjohnwu.magisk.utils.Shell; import com.topjohnwu.magisk.utils.Utils; @@ -22,46 +21,29 @@ import java.io.InputStream; import java.io.OutputStream; import java.util.List; -public class FlashZIP extends SerialTask { +public class FlashZip extends SerialTask { - protected Uri mUri; - protected File mCachedFile; + private Uri mUri; + private File mCachedFile, mScriptFile, mCheckFile; private String mFilename; private ProgressDialog progress; - public FlashZIP(Activity context, Uri uri, String filename) { + public FlashZip(Activity context, Uri uri) { super(context); mUri = uri; - mFilename = filename; - } - public FlashZIP(Activity context, Uri uri) { - super(context); - mUri = uri; + mCachedFile = new File(magiskManager.getCacheDir(), "install.zip"); + mScriptFile = new File(magiskManager.getCacheDir(), "/META-INF/com/google/android/update-binary"); + mCheckFile = new File(mScriptFile.getParent(), "updater-script"); // Try to get the filename ourselves - Cursor c = magiskManager.getContentResolver().query(uri, null, null, null, null); - if (c != null) { - int nameIndex = c.getColumnIndex(OpenableColumns.DISPLAY_NAME); - c.moveToFirst(); - if (nameIndex != -1) { - mFilename = c.getString(nameIndex); - } - c.close(); - } - if (mFilename == null) { - int idx = uri.getPath().lastIndexOf('/'); - mFilename = uri.getPath().substring(idx + 1); - } + mFilename = Utils.getNameFromUri(magiskManager, mUri); } - protected void preProcessing() throws Throwable { - } - - protected void copyToCache() throws Throwable { + private void copyToCache() throws Throwable { publishProgress(magiskManager.getString(R.string.copying_msg)); - mCachedFile = new File(magiskManager.getCacheDir().getAbsolutePath() + "/install.zip"); + if (mCachedFile.exists() && !mCachedFile.delete()) { Logger.error("FlashZip: Error while deleting already existing file"); throw new IOException(); @@ -73,9 +55,9 @@ public class FlashZIP extends SerialTask { byte buffer[] = new byte[1024]; int length; if (in == null) throw new FileNotFoundException(); - while ((length = in.read(buffer)) > 0) { + while ((length = in.read(buffer)) > 0) outputStream.write(buffer, 0, length); - } + Logger.dev("FlashZip: File created successfully - " + mCachedFile.getPath()); } catch (FileNotFoundException e) { Logger.error("FlashZip: Invalid Uri"); @@ -86,13 +68,21 @@ public class FlashZIP extends SerialTask { } } - protected boolean unzipAndCheck() { + protected boolean unzipAndCheck() throws Exception { ZipUtils.unzip(mCachedFile, mCachedFile.getParentFile(), "META-INF/com/google/android"); List ret; - ret = Utils.readFile(mCachedFile.getParent() + "/META-INF/com/google/android/updater-script"); + ret = Utils.readFile(mCheckFile.getPath()); return Utils.isValidShellResponse(ret) && ret.get(0).contains("#MAGISK"); } + private int cleanup(int ret) { + Shell.su( + "rm -rf " + mCachedFile.getParent() + "/*", + "rm -rf " + MagiskManager.TMP_FOLDER_PATH + ); + return ret; + } + @Override protected void onPreExecute() { progress = new ProgressDialog(activity); @@ -110,32 +100,25 @@ public class FlashZIP extends SerialTask { Logger.dev("FlashZip Running... " + mFilename); List ret; try { - preProcessing(); copyToCache(); + if (!unzipAndCheck()) return cleanup(0); + publishProgress(magiskManager.getString(R.string.zip_install_progress_msg, mFilename)); + ret = Shell.su( + "BOOTMODE=true sh " + mScriptFile + " dummy 1 " + mCachedFile, + "if [ $? -eq 0 ]; then echo true; else echo false; fi" + ); + if (!Utils.isValidShellResponse(ret)) return -1; + Logger.dev("FlashZip: Console log:"); + for (String line : ret) { + Logger.dev(line); + } + if (Boolean.parseBoolean(ret.get(ret.size() - 1))) + return cleanup(1); + } catch (Throwable e) { e.printStackTrace(); - return -1; } - if (!unzipAndCheck()) return 0; - publishProgress(magiskManager.getString(R.string.zip_install_progress_msg, mFilename)); - ret = Shell.su( - "BOOTMODE=true sh " + mCachedFile.getParent() + - "/META-INF/com/google/android/update-binary dummy 1 " + mCachedFile.getPath(), - "if [ $? -eq 0 ]; then echo true; else echo false; fi" - ); - if (!Utils.isValidShellResponse(ret)) return -1; - Logger.dev("FlashZip: Console log:"); - for (String line : ret) { - Logger.dev(line); - } - Shell.su( - "rm -rf " + mCachedFile.getParent() + "/*", - "rm -rf " + MagiskManager.TMP_FOLDER_PATH - ); - if (Boolean.parseBoolean(ret.get(ret.size() - 1))) { - return 1; - } - return -1; + return cleanup(-1); } // -1 = error, manual install; 0 = invalid zip; 1 = success @@ -146,8 +129,7 @@ public class FlashZIP extends SerialTask { switch (result) { case -1: Toast.makeText(magiskManager, magiskManager.getString(R.string.install_error), Toast.LENGTH_LONG).show(); - Toast.makeText(magiskManager, magiskManager.getString(R.string.manual_install_1, mUri.getPath()), Toast.LENGTH_LONG).show(); - Toast.makeText(magiskManager, magiskManager.getString(R.string.manual_install_2), Toast.LENGTH_LONG).show(); + Utils.showUriSnack(activity, mUri); break; case 0: Toast.makeText(magiskManager, magiskManager.getString(R.string.invalid_zip), Toast.LENGTH_LONG).show(); @@ -162,7 +144,7 @@ public class FlashZIP extends SerialTask { magiskManager.updateCheckDone.trigger(); new LoadModules(activity).exec(); - Utils.getAlertDialogBuilder(activity) + new AlertDialogBuilder(activity) .setTitle(R.string.reboot_title) .setMessage(R.string.reboot_msg) .setPositiveButton(R.string.reboot, (dialogInterface, i) -> Shell.su(true, "reboot")) diff --git a/app/src/main/java/com/topjohnwu/magisk/asyncs/ProcessMagiskZip.java b/app/src/main/java/com/topjohnwu/magisk/asyncs/ProcessMagiskZip.java new file mode 100644 index 000000000..ea623d566 --- /dev/null +++ b/app/src/main/java/com/topjohnwu/magisk/asyncs/ProcessMagiskZip.java @@ -0,0 +1,93 @@ +package com.topjohnwu.magisk.asyncs; + +import android.app.Activity; +import android.app.ProgressDialog; +import android.net.Uri; + +import com.topjohnwu.magisk.MagiskManager; +import com.topjohnwu.magisk.R; +import com.topjohnwu.magisk.utils.Logger; +import com.topjohnwu.magisk.utils.Shell; +import com.topjohnwu.magisk.utils.Utils; +import com.topjohnwu.magisk.utils.ZipUtils; + +import java.io.File; + +public class ProcessMagiskZip extends ParallelTask { + + private Uri mUri; + private ProgressDialog progressDialog; + private String mBoot; + private boolean mEnc, mVerity; + + public ProcessMagiskZip(Activity context, Uri uri, String boot, boolean enc, boolean verity) { + super(context); + mUri = uri; + mBoot = boot; + mEnc = enc; + mVerity = verity; + } + + @Override + protected void onPreExecute() { + progressDialog = ProgressDialog.show(activity, + activity.getString(R.string.zip_install_progress_title), + activity.getString(R.string.zip_install_unzip_zip_msg)); + } + + @Override + protected Boolean doInBackground(Void... params) { + if (Shell.rootAccess()) { + try { + // We might not have busybox yet, unzip with Java + // We shall have complete busybox after Magisk installation + File tempdir = new File(magiskManager.getCacheDir(), "magisk"); + ZipUtils.unzip(magiskManager.getContentResolver().openInputStream(mUri), tempdir); + // Running in parallel mode, open new shell + Shell.su(true, + "echo \"BOOTIMAGE=/dev/block/" + mBoot + "\" > /dev/.magisk", + "echo \"KEEPFORCEENCRYPT=" + String.valueOf(mEnc) + "\" >> /dev/.magisk", + "echo \"KEEPVERITY=" + String.valueOf(mVerity) + "\" >> /dev/.magisk", + "mkdir -p " + MagiskManager.TMP_FOLDER_PATH, + "cp -af " + tempdir + "/. " + MagiskManager.TMP_FOLDER_PATH + "/magisk", + "mv -f " + tempdir + "/META-INF " + magiskManager.getCacheDir() + "/META-INF", + "rm -rf " + tempdir + ); + } catch (Exception e) { + Logger.error("ProcessMagiskZip: Error!"); + e.printStackTrace(); + return false; + } + return true; + } + return false; + } + + @Override + protected void onPostExecute(Boolean result) { + progressDialog.dismiss(); + if (result) + new FlashZip(activity, mUri) { + @Override + protected boolean unzipAndCheck() throws Exception { + // Don't need to check, as it is downloaded in correct form + return true; + } + @Override + protected void onSuccess() { + new SerialTask(activity) { + @Override + protected Void doInBackground(Void... params) { + Shell.su("setprop magisk.version " + + String.valueOf(magiskManager.remoteMagiskVersion)); + magiskManager.updateCheckDone.trigger(); + return null; + } + }.exec(); + super.onSuccess(); + } + }.exec(); + else + Utils.showUriSnack(activity, mUri); + } +} diff --git a/app/src/main/java/com/topjohnwu/magisk/asyncs/ProcessRepoZip.java b/app/src/main/java/com/topjohnwu/magisk/asyncs/ProcessRepoZip.java new file mode 100644 index 000000000..2e73c3ddc --- /dev/null +++ b/app/src/main/java/com/topjohnwu/magisk/asyncs/ProcessRepoZip.java @@ -0,0 +1,77 @@ +package com.topjohnwu.magisk.asyncs; + +import android.app.Activity; +import android.app.ProgressDialog; +import android.net.Uri; +import android.widget.Toast; + +import com.topjohnwu.magisk.R; +import com.topjohnwu.magisk.utils.ByteArrayInOutStream; +import com.topjohnwu.magisk.utils.Logger; +import com.topjohnwu.magisk.utils.Shell; +import com.topjohnwu.magisk.utils.Utils; +import com.topjohnwu.magisk.utils.ZipUtils; + +import java.io.OutputStream; + +public class ProcessRepoZip extends ParallelTask { + + private Uri mUri; + private ProgressDialog progressDialog; + + public ProcessRepoZip(Activity context, Uri uri) { + super(context); + mUri = uri; + } + + @Override + protected void onPreExecute() { + progressDialog = ProgressDialog.show(activity, + activity.getString(R.string.zip_install_progress_title), + activity.getString(R.string.zip_install_process_zip_msg)); + } + + @Override + protected Boolean doInBackground(Void... params) { + // Create a buffer in memory for input/output + ByteArrayInOutStream buffer = new ByteArrayInOutStream(); + + try { + // First remove top folder (the folder with the repo name) in Github source zip + ZipUtils.removeTopFolder(activity.getContentResolver().openInputStream(mUri), buffer); + + // Then sign the zip for the first time + ZipUtils.signZip(activity, buffer.getInputStream(), buffer, false); + + // Adjust the zip to prevent unzip issues + ZipUtils.adjustZip(buffer); + + // Finally, sign the whole zip file again + ZipUtils.signZip(activity, buffer.getInputStream(), buffer, true); + + // Write it back to the downloaded zip + try (OutputStream out = activity.getContentResolver().openOutputStream(mUri)) { + buffer.writeTo(out); + } + return true; + } catch (Exception e) { + Logger.error("ProcessRepoZip: Error!"); + e.printStackTrace(); + return false; + } + } + + @Override + protected void onPostExecute(Boolean result) { + progressDialog.dismiss(); + if (result) { + if (Shell.rootAccess()) + new FlashZip(activity, mUri).exec(); + else + Utils.showUriSnack(activity, mUri); + + } else { + Toast.makeText(activity, R.string.process_error, Toast.LENGTH_LONG).show(); + } + } +} diff --git a/app/src/main/java/com/topjohnwu/magisk/components/SnackbarMaker.java b/app/src/main/java/com/topjohnwu/magisk/components/SnackbarMaker.java new file mode 100644 index 000000000..bdbfd174c --- /dev/null +++ b/app/src/main/java/com/topjohnwu/magisk/components/SnackbarMaker.java @@ -0,0 +1,41 @@ +package com.topjohnwu.magisk.components; + +import android.app.Activity; +import android.support.annotation.StringRes; +import android.support.design.widget.Snackbar; +import android.text.TextUtils; +import android.view.View; +import android.widget.TextView; + +import butterknife.ButterKnife; + +public class SnackbarMaker { + + public static Snackbar make(Activity activity, CharSequence text, int duration) { + View view = activity.findViewById(android.R.id.content); + return make(view, text, duration); + } + + public static Snackbar make(Activity activity, @StringRes int resId, int duration) { + return make(activity, activity.getString(resId), duration); + } + + public static Snackbar make(View view, CharSequence text, int duration) { + Snackbar snack = Snackbar.make(view, text, duration); + setup(snack); + return snack; + } + + public static Snackbar make(View view, @StringRes int resId, int duration) { + Snackbar snack = Snackbar.make(view, resId, duration); + setup(snack); + return snack; + } + + private static void setup(Snackbar snack) { + TextView text = ButterKnife.findById(snack.getView(), android.support.design.R.id.snackbar_text); + text.setMaxLines(2); + text.setEllipsize(TextUtils.TruncateAt.START); + } + +} diff --git a/app/src/main/java/com/topjohnwu/magisk/receivers/MagiskDlReceiver.java b/app/src/main/java/com/topjohnwu/magisk/receivers/MagiskDlReceiver.java deleted file mode 100644 index 54e99a371..000000000 --- a/app/src/main/java/com/topjohnwu/magisk/receivers/MagiskDlReceiver.java +++ /dev/null @@ -1,68 +0,0 @@ -package com.topjohnwu.magisk.receivers; - -import android.net.Uri; - -import com.topjohnwu.magisk.MagiskManager; -import com.topjohnwu.magisk.R; -import com.topjohnwu.magisk.asyncs.FlashZIP; -import com.topjohnwu.magisk.asyncs.SerialTask; -import com.topjohnwu.magisk.utils.Shell; -import com.topjohnwu.magisk.utils.ZipUtils; - -import java.io.File; - -public class MagiskDlReceiver extends DownloadReceiver { - - String mBoot; - boolean mEnc, mVerity; - - public MagiskDlReceiver(String bootImage, boolean keepEnc, boolean keepVerity) { - mBoot = bootImage; - mEnc = keepEnc; - mVerity = keepVerity; - } - - @Override - public void onDownloadDone(Uri uri) { - new FlashZIP(activity, uri, mFilename) { - - @Override - protected void preProcessing() throws Throwable { - Shell.su( - "echo \"BOOTIMAGE=/dev/block/" + mBoot + "\" > /dev/.magisk", - "echo \"KEEPFORCEENCRYPT=" + String.valueOf(mEnc) + "\" >> /dev/.magisk", - "echo \"KEEPVERITY=" + String.valueOf(mVerity) + "\" >> /dev/.magisk" - ); - } - - @Override - protected boolean unzipAndCheck() { - publishProgress(activity.getString(R.string.zip_install_unzip_zip_msg)); - if (Shell.rootAccess()) { - // We might not have busybox yet, unzip with Java - // We will have complete busybox after Magisk installation - ZipUtils.unzip(mCachedFile, new File(mCachedFile.getParent(), "magisk")); - Shell.su( - "mkdir -p " + MagiskManager.TMP_FOLDER_PATH + "/magisk", - "cp -af " + mCachedFile.getParent() + "/magisk/. " + MagiskManager.TMP_FOLDER_PATH + "/magisk", - "mv -f " + mCachedFile.getParent() + "/magisk/META-INF " + mCachedFile.getParent() + "/META-INF" - ); - } - return true; - } - - @Override - protected void onSuccess() { - new SerialTask() { - @Override - protected Void doInBackground(Void... params) { - Shell.su("setprop magisk.version " - + String.valueOf(((MagiskManager) activity.getApplicationContext()).remoteMagiskVersion)); - return null; - } - }.exec(); - super.onSuccess(); - } - }.exec(); - } -} diff --git a/app/src/main/java/com/topjohnwu/magisk/receivers/RepoDlReceiver.java b/app/src/main/java/com/topjohnwu/magisk/receivers/RepoDlReceiver.java deleted file mode 100644 index 37b5a360c..000000000 --- a/app/src/main/java/com/topjohnwu/magisk/receivers/RepoDlReceiver.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.topjohnwu.magisk.receivers; - -import android.net.Uri; - -import com.topjohnwu.magisk.R; -import com.topjohnwu.magisk.asyncs.FlashZIP; -import com.topjohnwu.magisk.utils.ByteArrayInOutStream; -import com.topjohnwu.magisk.utils.ZipUtils; - -import java.io.OutputStream; - -public class RepoDlReceiver extends DownloadReceiver { - @Override - public void onDownloadDone(Uri uri) { - // Flash the zip - new FlashZIP(activity, uri, mFilename){ - @Override - protected void preProcessing() throws Throwable { - // Process and sign the zip - publishProgress(activity.getString(R.string.zip_install_process_zip_msg)); - ByteArrayInOutStream buffer = new ByteArrayInOutStream(); - - // First remove top folder (the folder with the repo name) in Github source zip - ZipUtils.removeTopFolder(activity.getContentResolver().openInputStream(mUri), buffer); - - // Then sign the zip for the first time - ZipUtils.signZip(activity, buffer.getInputStream(), buffer, false); - - // Adjust the zip to prevent unzip issues - ZipUtils.adjustZip(buffer); - - // Finally, sign the whole zip file again - ZipUtils.signZip(activity, buffer.getInputStream(), buffer, true); - - // Write it back to the downloaded zip - try (OutputStream out = activity.getContentResolver().openOutputStream(mUri)) { - buffer.writeTo(out); - } - } - }.exec(); - } -} diff --git a/app/src/main/java/com/topjohnwu/magisk/utils/Utils.java b/app/src/main/java/com/topjohnwu/magisk/utils/Utils.java index f43040863..d55dd6aed 100644 --- a/app/src/main/java/com/topjohnwu/magisk/utils/Utils.java +++ b/app/src/main/java/com/topjohnwu/magisk/utils/Utils.java @@ -7,17 +7,19 @@ import android.content.Context; import android.content.IntentFilter; import android.content.SharedPreferences; import android.content.pm.PackageManager; +import android.database.Cursor; import android.net.Uri; import android.os.Environment; +import android.provider.OpenableColumns; +import android.support.design.widget.Snackbar; import android.support.v4.app.ActivityCompat; -import android.support.v7.app.AlertDialog; import android.text.TextUtils; import android.widget.Toast; import com.topjohnwu.magisk.MagiskManager; import com.topjohnwu.magisk.R; import com.topjohnwu.magisk.asyncs.LoadRepos; -import com.topjohnwu.magisk.components.AlertDialogBuilder; +import com.topjohnwu.magisk.components.SnackbarMaker; import com.topjohnwu.magisk.database.RepoDatabaseHelper; import com.topjohnwu.magisk.receivers.DownloadReceiver; @@ -117,15 +119,6 @@ public class Utils { return null; } - public static AlertDialog.Builder getAlertDialogBuilder(Context context) { -// if (((MagiskManager) context.getApplicationContext()).isDarkTheme) { -// return new AlertDialog.Builder(context, R.style.AlertDialog_dh); -// } else { -// return new AlertDialog.Builder(context); -// } - return new AlertDialogBuilder(context); - } - public static boolean lowercaseContains(CharSequence string, CharSequence nonNullLowercaseSearch) { return !TextUtils.isEmpty(string) && string.toString().toLowerCase().contains(nonNullLowercaseSearch); } @@ -164,4 +157,29 @@ public class Utils { new RepoDatabaseHelper(activity).clearRepo(); Toast.makeText(activity, R.string.repo_cache_cleared, Toast.LENGTH_SHORT).show(); } + + public static String getNameFromUri(Context context, Uri uri) { + String name = null; + try (Cursor c = context.getContentResolver().query(uri, null, null, null, null)) { + if (c != null) { + int nameIndex = c.getColumnIndex(OpenableColumns.DISPLAY_NAME); + if (nameIndex != -1) { + c.moveToFirst(); + name = c.getString(nameIndex); + } + } + } + if (name == null) { + int idx = uri.getPath().lastIndexOf('/'); + name = uri.getPath().substring(idx + 1); + } + return name; + } + + public static void showUriSnack(Activity activity, Uri uri) { + SnackbarMaker.make(activity, activity.getString(R.string.internal_storage, + "/MagiskManager/" + Utils.getNameFromUri(activity, uri)), + Snackbar.LENGTH_LONG) + .setAction(R.string.ok, (v)->{}).show(); + } } \ No newline at end of file diff --git a/app/src/main/java/com/topjohnwu/magisk/utils/WebWindow.java b/app/src/main/java/com/topjohnwu/magisk/utils/WebWindow.java index c8d55e746..a713f9198 100644 --- a/app/src/main/java/com/topjohnwu/magisk/utils/WebWindow.java +++ b/app/src/main/java/com/topjohnwu/magisk/utils/WebWindow.java @@ -6,10 +6,12 @@ import android.webkit.WebResourceRequest; import android.webkit.WebView; import android.webkit.WebViewClient; +import com.topjohnwu.magisk.components.AlertDialogBuilder; + public class WebWindow { public WebWindow(String title, String url, Context context) { - AlertDialog.Builder alert = Utils.getAlertDialogBuilder(context); + AlertDialog.Builder alert = new AlertDialogBuilder(context); alert.setTitle(title); Logger.dev("WebView: URL = " + url); diff --git a/app/src/main/java/com/topjohnwu/magisk/utils/ZipUtils.java b/app/src/main/java/com/topjohnwu/magisk/utils/ZipUtils.java index 6b4fa363f..699535448 100644 --- a/app/src/main/java/com/topjohnwu/magisk/utils/ZipUtils.java +++ b/app/src/main/java/com/topjohnwu/magisk/utils/ZipUtils.java @@ -48,6 +48,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Enumeration; +import java.util.Iterator; import java.util.Locale; import java.util.Map; import java.util.TreeMap; @@ -88,7 +89,7 @@ public class ZipUtils { buffer.setBuffer(zipAdjust(buffer.toByteArray(), buffer.size())); } - public static void removeTopFolder(InputStream in, OutputStream out) { + public static void removeTopFolder(InputStream in, OutputStream out) throws IOException { try { JarInputStream source = new JarInputStream(in); JarOutputStream dest = new JarOutputStream(out); @@ -113,16 +114,20 @@ public class ZipUtils { dest.close(); in.close(); } catch (IOException e) { - e.printStackTrace(); Logger.dev("ZipUtils: removeTopFolder IO error!"); + throw e; } } - public static void unzip(File file, File folder) { + public static void unzip(File file, File folder) throws Exception { unzip(file, folder, ""); } - public static void unzip(File file, File folder, String path) { + public static void unzip(InputStream file, File folder) throws Exception { + unzip(file, folder, ""); + } + + public static void unzip(File file, File folder, String path) throws Exception { int count; FileOutputStream out; File dest; @@ -133,32 +138,56 @@ public class ZipUtils { Enumeration e = zipfile.entries(); while(e.hasMoreElements()) { entry = e.nextElement(); - if (!entry.getName().contains(path) - || entry.getName().charAt(entry.getName().length() - 1) == '/') { + if (!entry.getName().contains(path) || entry.isDirectory()) // Ignore directories, only create files continue; - } Logger.dev("ZipUtils: Extracting: " + entry); is = zipfile.getInputStream(entry); dest = new File(folder, entry.getName()); - if (dest.getParentFile().mkdirs()) { + if (dest.getParentFile().mkdirs()) dest.createNewFile(); - } out = new FileOutputStream(dest); - while ((count = is.read(data, 0, 4096)) != -1) { + while ((count = is.read(data, 0, 4096)) != -1) out.write(data, 0, count); - } out.flush(); out.close(); is.close(); } } catch(Exception e) { e.printStackTrace(); + throw e; + } + } + + public static void unzip(InputStream file, File folder, String path) throws Exception { + int count; + FileOutputStream out; + File dest; + JarEntry entry; + byte data[] = new byte[4096]; + try (JarInputStream zipfile = new JarInputStream(file)) { + while((entry = zipfile.getNextJarEntry()) != null) { + if (!entry.getName().contains(path) || entry.isDirectory()) + // Ignore directories, only create files + continue; + Logger.dev("ZipUtils: Extracting: " + entry); + dest = new File(folder, entry.getName()); + if (dest.getParentFile().mkdirs()) + dest.createNewFile(); + out = new FileOutputStream(dest); + while ((count = zipfile.read(data, 0, 4096)) != -1) + out.write(data, 0, count); + out.flush(); + out.close(); + } + } catch(Exception e) { + e.printStackTrace(); + throw e; } } public static void signZip(Context context, InputStream inputStream, - OutputStream outputStream, boolean signWholeFile) { + OutputStream outputStream, boolean signWholeFile) throws Exception { JarMap inputJar; int hashes = 0; try { @@ -192,6 +221,7 @@ public class ZipUtils { } } catch (Exception e) { e.printStackTrace(); + throw e; } } @@ -227,6 +257,13 @@ public class ZipUtils { public Manifest getManifest() { return manifest; } + public Enumeration entries() { + Iterator >> i = entrySet().iterator(); + ArrayList list = new ArrayList<>(); + while (i.hasNext()) + list.add(i.next().getValue().first); + return Collections.enumeration(list); + } } /** diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 8b0099553..db04bc1ff 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -90,6 +90,7 @@ This feature will not work without permission to write external storage. No thanks Yes + OK Install %1$s Do you want to install %1$s ? Download & install @@ -120,6 +121,8 @@ Not using MagiskSU! You are not rooted with MagiskSU, using MagiskHide itself might not be enough!\nIt\'s not officially supported, and you would need additional tools (e.g suhide) to pass Safety Net. I understand + Process error + The zip is stored in:\n[Internal Storage]%1$s General