Add boot image file patch

This commit is contained in:
topjohnwu 2017-08-31 03:07:33 +08:00
parent dbe6e5b3d7
commit db8fd2c913
9 changed files with 325 additions and 85 deletions

View File

@ -1,5 +1,6 @@
package com.topjohnwu.magisk;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.support.v7.app.ActionBar;
@ -8,12 +9,17 @@ import android.support.v7.widget.Toolbar;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.topjohnwu.magisk.asyncs.FlashZip;
import com.topjohnwu.magisk.asyncs.PatchBootImage;
import com.topjohnwu.magisk.components.Activity;
import com.topjohnwu.magisk.utils.AdaptiveList;
import com.topjohnwu.magisk.utils.Shell;
import java.util.List;
import butterknife.BindView;
import butterknife.ButterKnife;
@ -21,11 +27,15 @@ import butterknife.OnClick;
public class FlashActivity extends Activity {
public static final String SET_ACTION = "action";
public static final String SET_BOOT_URI = "boot_uri";
public static final String FLASH_ZIP = "flash";
public static final String PATCH_BOOT = "patch";
@BindView(R.id.toolbar) Toolbar toolbar;
@BindView(R.id.flash_logs) RecyclerView flashLogs;
@BindView(R.id.button_panel) LinearLayout buttonPanel;
private AdaptiveList<String> rootShellOutput;
@BindView(R.id.reboot) Button reboot;
@OnClick(R.id.no_thanks)
public void dismiss() {
@ -42,22 +52,33 @@ public class FlashActivity extends Activity {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_flash);
ButterKnife.bind(this);
rootShellOutput = new AdaptiveList<>(flashLogs);
AdaptiveList<String> rootShellOutput = new AdaptiveList<>(flashLogs);
setSupportActionBar(toolbar);
ActionBar ab = getSupportActionBar();
if (ab != null) {
ab.setTitle(R.string.flashing);
}
setFloating();
if (!Shell.rootAccess())
reboot.setVisibility(View.GONE);
flashLogs.setAdapter(new FlashLogAdapter());
flashLogs.setAdapter(new FlashLogAdapter(rootShellOutput));
// We must receive a Uri of the target zip
Uri uri = getIntent().getData();
Intent intent = getIntent();
Uri uri = intent.getData();
switch (getIntent().getStringExtra(SET_ACTION)) {
case FLASH_ZIP:
new FlashZip(this, uri, rootShellOutput)
.setCallBack(() -> buttonPanel.setVisibility(View.VISIBLE))
.exec();
break;
case PATCH_BOOT:
new PatchBootImage(this, uri, intent.getParcelableExtra(SET_BOOT_URI), rootShellOutput)
.setCallBack(() -> buttonPanel.setVisibility(View.VISIBLE))
.exec();
}
}
@Override
@ -65,7 +86,13 @@ public class FlashActivity extends Activity {
// Prevent user accidentally press back button
}
private class FlashLogAdapter extends RecyclerView.Adapter<ViewHolder> {
private static class FlashLogAdapter extends RecyclerView.Adapter<ViewHolder> {
private List<String> mList;
FlashLogAdapter(List<String> list) {
mList = list;
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
@ -76,16 +103,16 @@ public class FlashActivity extends Activity {
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
holder.text.setText(rootShellOutput.get(position));
holder.text.setText(mList.get(position));
}
@Override
public int getItemCount() {
return rootShellOutput.size();
return mList.size();
}
}
public class ViewHolder extends RecyclerView.ViewHolder {
public static class ViewHolder extends RecyclerView.ViewHolder {
@BindView(R.id.textView) TextView text;

View File

@ -1,5 +1,7 @@
package com.topjohnwu.magisk;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.NotificationManager;
import android.app.ProgressDialog;
import android.content.Context;
@ -49,15 +51,15 @@ import butterknife.Unbinder;
public class MagiskFragment extends Fragment
implements Topic.Subscriber, SwipeRefreshLayout.OnRefreshListener, ExpandableView {
public static final String SHOW_DIALOG = "dialog";
private static final String UNINSTALLER = "magisk_uninstaller.sh";
private static final String UTIL_FUNCTIONS= "util_functions.sh";
private static final int SELECT_BOOT_IMG = 3;
private Container expandableContainer = new Container();
private MagiskManager magiskManager;
private Unbinder unbinder;
private static boolean shownDialog = false;
@BindView(R.id.swipeRefreshLayout) SwipeRefreshLayout mSwipeRefreshLayout;
@ -108,6 +110,7 @@ public class MagiskFragment extends Fragment
@OnClick(R.id.install_button)
public void install() {
shownDialog = true;
String bootImage = null;
if (Shell.rootAccess()) {
if (magiskManager.bootBlock != null) {
@ -117,44 +120,73 @@ public class MagiskFragment extends Fragment
if (idx > 0) {
bootImage = magiskManager.blockList.get(idx - 1);
} else {
SnackbarMaker.make(getActivity(), R.string.manual_boot_image, Snackbar.LENGTH_LONG).show();
SnackbarMaker.make(getActivity(),
R.string.manual_boot_image, Snackbar.LENGTH_LONG).show();
return;
}
}
}
final String boot = bootImage;
((NotificationManager) getActivity().getSystemService(Context.NOTIFICATION_SERVICE)).cancelAll();
final String finalBootImage = bootImage;
String filename = "Magisk-v" + magiskManager.remoteMagiskVersionString + ".zip";
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(Shell.rootAccess() ? R.string.install : R.string.download,
(d, i) ->
Utils.dlAndReceive(
getActivity(),
new DownloadReceiver() {
private String boot = finalBootImage;
private boolean enc = keepEncChkbox.isChecked();
private boolean verity = keepVerityChkbox.isChecked();
.setPositiveButton(
R.string.install,
(d, i) -> {
List<String> options = new ArrayList<>();
options.add(getString(R.string.download_zip_only));
options.add(getString(R.string.patch_boot_file));
if (Shell.rootAccess()) {
options.add(getString(R.string.direct_install));
}
new AlertDialog.Builder(getActivity())
.setTitle(R.string.select_method)
.setItems(
options.toArray(new String [0]),
(dialog, idx) -> {
DownloadReceiver receiver = null;
switch (idx) {
case 1:
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.setType("*/*");
startActivityForResult(intent, SELECT_BOOT_IMG);
return;
case 0:
receiver = new DownloadReceiver() {
@Override
public void onDownloadDone(Uri uri) {
if (Shell.rootAccess()) {
getShell().su_raw(
"rm -f /dev/.magisk",
"echo \"BOOTIMAGE=" + boot + "\" >> /dev/.magisk",
"echo \"KEEPFORCEENCRYPT=" + String.valueOf(enc) + "\" >> /dev/.magisk",
"echo \"KEEPVERITY=" + String.valueOf(verity) + "\" >> /dev/.magisk"
);
startActivity(new Intent(getActivity(), FlashActivity.class).setData(uri));
} else {
Utils.showUriSnack(getActivity(), uri);
}
};
break;
case 2:
receiver = new DownloadReceiver() {
@Override
public void onDownloadDone(Uri uri) {
getShell().su_raw(
"echo \"BOOTIMAGE=" + boot + "\" > /dev/.magisk",
"echo \"KEEPFORCEENCRYPT=" + keepEncChkbox.isChecked() + "\" >> /dev/.magisk",
"echo \"KEEPVERITY=" + keepVerityChkbox.isChecked() + "\" >> /dev/.magisk"
);
Intent intent = new Intent(getActivity(), FlashActivity.class);
intent.setData(uri).putExtra(FlashActivity.SET_ACTION, FlashActivity.FLASH_ZIP);
startActivity(intent);
}
},
};
break;
}
Utils.dlAndReceive(
getActivity(),
receiver,
magiskManager.magiskLink,
Utils.getLegalFilename(filename))
Utils.getLegalFilename(filename)
);
}
).show();
}
)
.setNeutralButton(R.string.release_notes, (d, i) -> {
if (magiskManager.releaseNoteLink != null) {
@ -172,7 +204,7 @@ public class MagiskFragment extends Fragment
new AlertDialogBuilder(getActivity())
.setTitle(R.string.uninstall_magisk_title)
.setMessage(R.string.uninstall_magisk_msg)
.setPositiveButton(R.string.yes, (dialogInterface, i) -> {
.setPositiveButton(R.string.yes, (d, i) -> {
try {
InputStream in = magiskManager.getAssets().open(UNINSTALLER);
File uninstaller = new File(magiskManager.getCacheDir(), UNINSTALLER);
@ -254,6 +286,8 @@ public class MagiskFragment extends Fragment
magiskManager.remoteMagiskVersionCode = -1;
collapse();
shownDialog = false;
// Trigger state check
if (Utils.checkNetworkStatus(magiskManager)) {
new CheckUpdates(getActivity()).exec();
@ -262,6 +296,28 @@ public class MagiskFragment extends Fragment
}
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == SELECT_BOOT_IMG && resultCode == Activity.RESULT_OK && data != null) {
Utils.dlAndReceive(
getActivity(),
new DownloadReceiver() {
@Override
public void onDownloadDone(Uri uri) {
// Get the URI of the selected file
Intent intent = new Intent(getActivity(), FlashActivity.class);
intent.setData(uri)
.putExtra(FlashActivity.SET_BOOT_URI, data.getData())
.putExtra(FlashActivity.SET_ACTION, FlashActivity.PATCH_BOOT);
startActivity(intent);
}
},
magiskManager.magiskLink,
Utils.getLegalFilename("Magisk-v" + magiskManager.remoteMagiskVersionString + ".zip")
);
}
}
@Override
public void onTopicPublished(Topic topic) {
if (topic == magiskManager.updateCheckDone) {
@ -341,7 +397,7 @@ public class MagiskFragment extends Fragment
rootStatusIcon.setColorFilter(color);
if (!Shell.rootAccess()) {
installText.setText(R.string.download);
installText.setText(R.string.install);
} else {
if (magiskManager.remoteMagiskVersionCode > magiskManager.magiskVersionCode) {
installText.setText(R.string.update);
@ -384,7 +440,7 @@ public class MagiskFragment extends Fragment
magiskUpdateProgress.setVisibility(View.GONE);
mSwipeRefreshLayout.setRefreshing(false);
if (magiskManager.remoteMagiskVersionCode > magiskManager.magiskVersionCode)
if (magiskManager.remoteMagiskVersionCode > magiskManager.magiskVersionCode && !shownDialog)
install();
}

View File

@ -87,7 +87,7 @@ public class ModulesFragment extends Fragment implements Topic.Subscriber {
if (requestCode == FETCH_ZIP_CODE && resultCode == Activity.RESULT_OK && data != null) {
// Get the URI of the selected file
Intent intent = new Intent(getActivity(), FlashActivity.class);
intent.setData(data.getData()).putExtra("ACTION", "flash");
intent.setData(data.getData()).putExtra(FlashActivity.SET_ACTION, FlashActivity.FLASH_ZIP);
startActivity(intent);
}
}

View File

@ -139,6 +139,7 @@ public class SettingsActivity extends Activity implements Topic.Subscriber {
if (!Shell.rootAccess()) {
prefScreen.removePreference(magiskCategory);
prefScreen.removePreference(suCategory);
generalCatagory.removePreference(hideManager);
} else {
if (!magiskManager.isSuClient) {
prefScreen.removePreference(suCategory);

View File

@ -18,7 +18,7 @@ import java.io.InputStream;
import java.io.OutputStream;
import java.util.List;
public class FlashZip extends ParallelTask<Void, String, Integer> {
public class FlashZip extends ParallelTask<Void, Void, Integer> {
private Uri mUri;
private File mCachedFile, mScriptFile, mCheckFile;
@ -40,7 +40,7 @@ public class FlashZip extends ParallelTask<Void, String, Integer> {
}
private boolean unzipAndCheck() throws Exception {
ZipUtils.unzip(mCachedFile, mCachedFile.getParentFile(), "META-INF/com/google/android");
ZipUtils.unzip(mCachedFile, mCachedFile.getParentFile(), "META-INF/com/google/android", false);
List<String> ret = Utils.readFile(getShell(), mCheckFile.getPath());
return Utils.isValidShellResponse(ret) && ret.get(0).contains("#MAGISK");
}
@ -52,7 +52,7 @@ public class FlashZip extends ParallelTask<Void, String, Integer> {
}
@Override
protected void onProgressUpdate(String... values) {
protected void onProgressUpdate(Void... values) {
mList.updateView();
}
@ -61,18 +61,18 @@ public class FlashZip extends ParallelTask<Void, String, Integer> {
MagiskManager magiskManager = getMagiskManager();
if (magiskManager == null) return -1;
try {
mList.add(magiskManager.getString(R.string.copying_msg));
mList.add("- Copying zip to temp directory");
mCachedFile.delete();
try (
InputStream in = magiskManager.getContentResolver().openInputStream(mUri);
OutputStream outputStream = new FileOutputStream(mCachedFile)
OutputStream out = new FileOutputStream(mCachedFile)
) {
if (in == null) throw new FileNotFoundException();
byte buffer[] = new byte[1024];
int length;
while ((length = in.read(buffer)) > 0)
outputStream.write(buffer, 0, length);
out.write(buffer, 0, length);
} catch (FileNotFoundException e) {
mList.add("! Invalid Uri");
throw e;
@ -81,7 +81,7 @@ public class FlashZip extends ParallelTask<Void, String, Integer> {
throw e;
}
if (!unzipAndCheck()) return 0;
mList.add(magiskManager.getString(R.string.zip_install_progress_msg, mFilename));
mList.add("- Installing " + mFilename);
getShell().su(mList,
"BOOTMODE=true sh " + mScriptFile + " dummy 1 " + mCachedFile +
" && echo 'Success!' || echo 'Failed!'"

View File

@ -0,0 +1,145 @@
package com.topjohnwu.magisk.asyncs;
import android.app.Activity;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.text.TextUtils;
import com.topjohnwu.magisk.MagiskManager;
import com.topjohnwu.magisk.utils.AdaptiveList;
import com.topjohnwu.magisk.utils.ZipUtils;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Arrays;
import java.util.List;
public class PatchBootImage extends ParallelTask<Void, Void, Boolean> {
private Uri mBootImg, mZip;
private AdaptiveList<String> mList;
private File dest;
public PatchBootImage(Activity context, Uri zip, Uri boot, AdaptiveList<String> list) {
super(context);
mBootImg = boot;
mList = list;
mZip = zip;
dest = new File(Environment.getExternalStorageDirectory() + "/MagiskManager/" + "patched_boot.img");
}
@Override
protected void onPreExecute() {
// UI updates must run in the UI thread
mList.setCallback(this::publishProgress);
}
@Override
protected void onProgressUpdate(Void... values) {
mList.updateView();
}
@Override
protected Boolean doInBackground(Void... voids) {
MagiskManager magiskManager = getMagiskManager();
if (magiskManager == null) return false;
File install = new File(magiskManager.getApplicationInfo().dataDir, "install");
getShell().sh_raw("rm -rf " + install);
List<String> abis = Arrays.asList(Build.SUPPORTED_ABIS);
String arch;
if (abis.contains("x86_64")) arch = "x64";
else if (abis.contains("arm64-v8a")) arch = "arm64";
else if (abis.contains("x86")) arch = "x86";
else arch = "arm";
mList.add("- Device platform: " + arch);
try {
// Unzip files
mList.add("- Extracting files");
try (InputStream in = magiskManager.getContentResolver().openInputStream(mZip)) {
if (in == null) throw new FileNotFoundException();
BufferedInputStream buf = new BufferedInputStream(in);
buf.mark(Integer.MAX_VALUE);
ZipUtils.unzip(buf, install, arch, true);
buf.reset();
ZipUtils.unzip(buf, install, "common", true);
buf.reset();
ZipUtils.unzip(buf, install, "chromeos", false);
buf.reset();
ZipUtils.unzip(buf, install, "META-INF/com/google/android/update-binary", true);
} catch (FileNotFoundException e) {
mList.add("! Invalid Uri");
throw e;
} catch (Exception e) {
mList.add("! Cannot unzip zip");
throw e;
}
// Copy boot image
File boot = new File(install, "boot.img");
try (
InputStream in = magiskManager.getContentResolver().openInputStream(mBootImg);
OutputStream out = new FileOutputStream(boot);
) {
if (in == null) throw new FileNotFoundException();
byte buffer[] = new byte[1024];
int length;
while ((length = in.read(buffer)) > 0)
out.write(buffer, 0, length);
} catch (FileNotFoundException e) {
mList.add("! Invalid Uri");
throw e;
} catch (IOException e) {
mList.add("! Copy failed");
throw e;
}
mList.add("- Use boot image: " + boot);
// Patch boot image
getShell().sh(mList,
"chmod -R 755 " + install,
"cd " + install,
"sh update-binary indep boot_patch.sh " + boot +
" && echo 'Success!' || echo 'Failed!'"
);
if (!TextUtils.equals(mList.get(mList.size() - 1), "Success!"))
return false;
// Move boot image
File source = new File(install, "new-boot.img");
dest.getParentFile().mkdirs();
getShell().sh_raw("mv " + source + " " + dest);
// Finals
getShell().sh_raw(
"mv bin/busybox busybox",
"rm -rf bin *.img update-binary");
} catch (Exception e) {
e.printStackTrace();
return false;
}
return true;
}
@Override
protected void onPostExecute(Boolean result) {
if (result) {
mList.add("");
mList.add("*********************************");
mList.add(" Patched Boot Image is placed in ");
mList.add(" " + dest + " ");
mList.add("*********************************");
}
super.onPostExecute(result);
}
}

View File

@ -94,12 +94,15 @@ public class ProcessRepoZip extends ParallelTask<Void, Void, Boolean> {
progressDialog.dismiss();
if (result) {
if (Shell.rootAccess() && mInstall) {
activity.startActivity(new Intent(activity, FlashActivity.class).setData(mUri));
Intent intent = new Intent(getActivity(), FlashActivity.class);
intent.setData(mUri).putExtra(FlashActivity.SET_ACTION, FlashActivity.FLASH_ZIP);
activity.startActivity(intent);
} else {
Utils.showUriSnack(activity, mUri);
}
} else {
Utils.getMagiskManager(activity).toast(R.string.process_error, Toast.LENGTH_LONG);
}
super.onPostExecute(result);
}
}

View File

@ -156,34 +156,38 @@ public class ZipUtils {
}
}
public static void unzip(File file, File folder, String path) throws Exception {
int count;
FileOutputStream out;
File dest;
InputStream is;
JarEntry entry;
public static void unzip(File zip, File folder, String path, boolean junkPath) throws Exception {
JarInputStream in = new JarInputStream(new FileInputStream(zip));
unzip(in, folder, path, junkPath);
in.close();
}
public static void unzip(InputStream zip, File folder, String path, boolean junkPath) throws Exception {
byte data[] = new byte[4096];
try (JarFile zipfile = new JarFile(file)) {
Enumeration<JarEntry> e = zipfile.entries();
while(e.hasMoreElements()) {
entry = e.nextElement();
if (!entry.getName().contains(path) || entry.isDirectory()){
try {
JarInputStream zipfile = new JarInputStream(zip);
JarEntry entry;
while ((entry = zipfile.getNextJarEntry()) != null) {
if (!entry.getName().startsWith(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()) {
dest.createNewFile();
String name;
if (junkPath) {
name = entry.getName().substring(entry.getName().lastIndexOf('/') + 1);
} else {
name = entry.getName();
}
out = new FileOutputStream(dest);
while ((count = is.read(data)) != -1) {
Logger.dev("ZipUtils: Extracting: " + entry);
File dest = new File(folder, name);
dest.getParentFile().mkdirs();
FileOutputStream out = new FileOutputStream(dest);
int count;
while ((count = zipfile.read(data)) != -1) {
out.write(data, 0, count);
}
out.flush();
out.close();
is.close();
}
} catch(Exception e) {
e.printStackTrace();

View File

@ -138,6 +138,10 @@
<string name="flashing">Flashing</string>
<string name="hide_manager_toast">Hiding Magisk Manager…</string>
<string name="hide_manager_fail_toast">Hide Magisk Manager failed…</string>
<string name="download_zip_only">Download Zip Only</string>
<string name="patch_boot_file">Patch Boot Image File</string>
<string name="direct_install">Direct Install (Recommend)</string>
<string name="select_method">Select Method</string>
<!--Settings Activity -->
<string name="settings_general_category">General</string>