improve zip handler
parent
dddbf6d1c7
commit
e17e839f2d
@ -0,0 +1,49 @@
|
|||||||
|
package com.fox2code.mmm.utils;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.res.Resources;
|
||||||
|
import android.util.TypedValue;
|
||||||
|
import android.view.Gravity;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.ProgressBar;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.appcompat.app.AlertDialog;
|
||||||
|
import androidx.appcompat.widget.LinearLayoutCompat;
|
||||||
|
|
||||||
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||||
|
|
||||||
|
// ProgressDialog is deprecated because it's an bad UX pattern, but sometimes we have no other choice...
|
||||||
|
public class BudgetProgressDialog {
|
||||||
|
public static AlertDialog build(Context context, String title, String message) {
|
||||||
|
Resources r = context.getResources();
|
||||||
|
int padding = Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 20, r.getDisplayMetrics()));
|
||||||
|
LinearLayoutCompat v = new LinearLayoutCompat(context);
|
||||||
|
v.setOrientation(LinearLayoutCompat.HORIZONTAL);
|
||||||
|
ProgressBar pb = new ProgressBar(context);
|
||||||
|
v.addView(pb, new LinearLayoutCompat.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, 1));
|
||||||
|
TextView t = new TextView(context);
|
||||||
|
t.setGravity(Gravity.CENTER);
|
||||||
|
v.addView(t, new LinearLayoutCompat.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT, 4));
|
||||||
|
v.setPadding(padding, padding, padding, padding);
|
||||||
|
|
||||||
|
t.setText(message);
|
||||||
|
return new MaterialAlertDialogBuilder(context)
|
||||||
|
.setTitle(title)
|
||||||
|
.setView(v)
|
||||||
|
.setCancelable(false)
|
||||||
|
.create();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AlertDialog build(Context context, int title, String message) {
|
||||||
|
return build(context, context.getString(title), message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AlertDialog build(Context context, String title, int message) {
|
||||||
|
return build(context, title, context.getString(message));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AlertDialog build(Context context, int title, int message) {
|
||||||
|
return build(context, title, context.getString(message));
|
||||||
|
}
|
||||||
|
}
|
@ -1,101 +1,176 @@
|
|||||||
package com.fox2code.mmm.utils;
|
package com.fox2code.mmm.utils;
|
||||||
|
|
||||||
import static androidx.fragment.app.FragmentManager.TAG;
|
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import androidx.appcompat.app.AlertDialog;
|
||||||
|
|
||||||
import com.fox2code.foxcompat.app.FoxActivity;
|
import com.fox2code.foxcompat.app.FoxActivity;
|
||||||
import com.fox2code.mmm.BuildConfig;
|
import com.fox2code.mmm.BuildConfig;
|
||||||
import com.fox2code.mmm.R;
|
import com.fox2code.mmm.R;
|
||||||
import com.fox2code.mmm.installer.InstallerInitializer;
|
import com.fox2code.mmm.installer.InstallerInitializer;
|
||||||
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
import java.util.zip.ZipEntry;
|
||||||
|
import java.util.zip.ZipFile;
|
||||||
|
|
||||||
public class ZipFileOpener extends FoxActivity {
|
public class ZipFileOpener extends FoxActivity {
|
||||||
|
AlertDialog loading = null;
|
||||||
|
|
||||||
// Adds us as a handler for zip files, so we can pass them to the installer
|
// Adds us as a handler for zip files, so we can pass them to the installer
|
||||||
// We should have a content uri provided to us.
|
// We should have a content uri provided to us.
|
||||||
@SuppressLint("RestrictedApi")
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
if (BuildConfig.DEBUG) {
|
loading = BudgetProgressDialog.build(this, R.string.loading, R.string.zip_unpacking);
|
||||||
Log.d("ZipFileOpener", "onCreate: " + getIntent());
|
new Thread(() -> {
|
||||||
}
|
if (BuildConfig.DEBUG) {
|
||||||
File zipFile;
|
Log.d("ZipFileOpener", "onCreate: " + getIntent());
|
||||||
Uri uri = getIntent().getData();
|
}
|
||||||
if (uri == null) {
|
File zipFile;
|
||||||
Log.e("ZipFileOpener", "onCreate: No data provided");
|
Uri uri = getIntent().getData();
|
||||||
Toast.makeText(this, R.string.zip_load_failed, Toast.LENGTH_LONG).show();
|
if (uri == null) {
|
||||||
finish();
|
Log.e("ZipFileOpener", "onCreate: No data provided");
|
||||||
return;
|
runOnUiThread(() -> {
|
||||||
}
|
|
||||||
// Try to copy the file to our cache
|
|
||||||
try {
|
|
||||||
zipFile = File.createTempFile("module", ".zip", getCacheDir());
|
|
||||||
try (InputStream inputStream = getContentResolver().openInputStream(uri); FileOutputStream outputStream = new FileOutputStream(zipFile)) {
|
|
||||||
if (inputStream == null) {
|
|
||||||
Log.e(TAG, "onCreate: Failed to open input stream");
|
|
||||||
Toast.makeText(this, R.string.zip_load_failed, Toast.LENGTH_LONG).show();
|
Toast.makeText(this, R.string.zip_load_failed, Toast.LENGTH_LONG).show();
|
||||||
finishAndRemoveTask();
|
finishAndRemoveTask();
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Try to copy the file to our cache
|
||||||
|
try {
|
||||||
|
// check if its a file over 10MB
|
||||||
|
Long fileSize = Files.getFileSize(this, uri);
|
||||||
|
if (fileSize == null) fileSize = 0L;
|
||||||
|
if (1000L * 1000 * 10 < fileSize) {
|
||||||
|
runOnUiThread(() -> loading.show());
|
||||||
|
}
|
||||||
|
zipFile = File.createTempFile("module", ".zip", getCacheDir());
|
||||||
|
try (InputStream inputStream = getContentResolver().openInputStream(uri); FileOutputStream outputStream = new FileOutputStream(zipFile)) {
|
||||||
|
if (inputStream == null) {
|
||||||
|
Log.e("ZipFileOpener", "onCreate: Failed to open input stream");
|
||||||
|
runOnUiThread(() -> {
|
||||||
|
Toast.makeText(this, R.string.zip_load_failed, Toast.LENGTH_LONG).show();
|
||||||
|
finishAndRemoveTask();
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
byte[] buffer = new byte[4096];
|
||||||
|
int read;
|
||||||
|
while ((read = inputStream.read(buffer)) != -1) {
|
||||||
|
outputStream.write(buffer, 0, read);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (
|
||||||
|
Exception e) {
|
||||||
|
Log.e("ZipFileOpener", "onCreate: Failed to copy zip file", e);
|
||||||
|
runOnUiThread(() -> {
|
||||||
|
Toast.makeText(this, R.string.zip_load_failed, Toast.LENGTH_LONG).show();
|
||||||
|
finishAndRemoveTask();
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Ensure zip is not empty
|
||||||
|
if (zipFile.length() == 0) {
|
||||||
|
Log.e("ZipFileOpener", "onCreate: Zip file is empty");
|
||||||
|
runOnUiThread(() -> {
|
||||||
|
Toast.makeText(this, R.string.zip_load_failed, Toast.LENGTH_LONG).show();
|
||||||
|
finishAndRemoveTask();
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
if (BuildConfig.DEBUG) {
|
||||||
|
Log.d("ZipFileOpener", "onCreate: Zip file is " + zipFile.length() + " bytes");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ZipEntry entry;
|
||||||
|
ZipFile zip = null;
|
||||||
|
// Unpack the zip to validate it's a valid magisk module
|
||||||
|
// It needs to have, at the bare minimum, a module.prop file. Everything else is technically optional.
|
||||||
|
// First, check if it's a zip file
|
||||||
|
try {
|
||||||
|
zip = new ZipFile(zipFile);
|
||||||
|
if ((entry = zip.getEntry("module.prop")) == null) {
|
||||||
|
Log.e("ZipFileOpener", "onCreate: Zip file is not a valid magisk module");
|
||||||
|
runOnUiThread(() -> {
|
||||||
|
Toast.makeText(this, R.string.invalid_format, Toast.LENGTH_LONG).show();
|
||||||
|
finishAndRemoveTask();
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
byte[] buffer = new byte[4096];
|
} catch (
|
||||||
int read;
|
Exception e) {
|
||||||
while ((read = inputStream.read(buffer)) != -1) {
|
Log.e("ZipFileOpener", "onCreate: Failed to open zip file", e);
|
||||||
outputStream.write(buffer, 0, read);
|
runOnUiThread(() -> {
|
||||||
|
Toast.makeText(this, R.string.zip_load_failed, Toast.LENGTH_LONG).show();
|
||||||
|
finishAndRemoveTask();
|
||||||
|
});
|
||||||
|
if (zip != null) {
|
||||||
|
try {
|
||||||
|
zip.close();
|
||||||
|
} catch (IOException exception) {
|
||||||
|
Log.e("ZipFileOpener", Log.getStackTraceString(exception));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
} catch (
|
|
||||||
Exception e) {
|
|
||||||
Log.e(TAG, "onCreate: Failed to copy zip file", e);
|
|
||||||
Toast.makeText(this, R.string.zip_load_failed, Toast.LENGTH_LONG).show();
|
|
||||||
finishAndRemoveTask();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Ensure zip is not empty
|
|
||||||
if (zipFile.length() == 0) {
|
|
||||||
Log.e(TAG, "onCreate: Zip file is empty");
|
|
||||||
Toast.makeText(this, R.string.zip_load_failed, Toast.LENGTH_LONG).show();
|
|
||||||
finishAndRemoveTask();
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
if (BuildConfig.DEBUG) {
|
if (BuildConfig.DEBUG) {
|
||||||
Log.d("ZipFileOpener", "onCreate: Zip file is " + zipFile.length() + " bytes");
|
Log.d("ZipFileOpener", "onCreate: Zip file is valid");
|
||||||
}
|
}
|
||||||
}
|
String moduleInfo;
|
||||||
// Unpack the zip to validate it's a valid magisk module
|
try {
|
||||||
// It needs to have, at the bare minimum, a module.prop file. Everything else is technically optional.
|
moduleInfo = PropUtils.readModulePropSimple(zip.getInputStream(entry), "name");
|
||||||
// First, check if it's a zip file
|
if (moduleInfo == null) {
|
||||||
try (java.util.zip.ZipFile zip = new java.util.zip.ZipFile(zipFile)) {
|
throw new NullPointerException("moduleInfo is null, check earlier logs for root cause");
|
||||||
if (zip.getEntry("module.prop") == null) {
|
}
|
||||||
Log.e(TAG, "onCreate: Zip file is not a valid magisk module");
|
} catch (
|
||||||
Toast.makeText(this, R.string.invalid_format, Toast.LENGTH_LONG).show();
|
Exception e) {
|
||||||
finishAndRemoveTask();
|
Log.e("ZipFileOpener", "onCreate: Failed to load module id", e);
|
||||||
|
runOnUiThread(() -> {
|
||||||
|
Toast.makeText(this, R.string.zip_prop_load_failed, Toast.LENGTH_LONG).show();
|
||||||
|
finishAndRemoveTask();
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
zip.close();
|
||||||
|
} catch (IOException exception) {
|
||||||
|
Log.e("ZipFileOpener", Log.getStackTraceString(exception));
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} catch (
|
try {
|
||||||
IOException e) {
|
zip.close();
|
||||||
Log.e(TAG, "onCreate: Failed to open zip file", e);
|
} catch (IOException exception) {
|
||||||
Toast.makeText(this, R.string.zip_load_failed, Toast.LENGTH_LONG).show();
|
Log.e("ZipFileOpener", Log.getStackTraceString(exception));
|
||||||
finishAndRemoveTask();
|
}
|
||||||
return;
|
runOnUiThread(() -> {
|
||||||
}
|
new MaterialAlertDialogBuilder(this)
|
||||||
if (BuildConfig.DEBUG) {
|
.setTitle(R.string.zip_security_warning)
|
||||||
Log.d("ZipFileOpener", "onCreate: Zip file is valid");
|
.setMessage(getString(R.string.zip_intent_module_install, moduleInfo, Files.getFileName(this, uri)))
|
||||||
}
|
.setCancelable(false)
|
||||||
// Pass the file to the installer
|
.setNegativeButton(R.string.no, (d, i) -> {
|
||||||
FoxActivity compatActivity = FoxActivity.getFoxActivity(this);
|
d.dismiss();
|
||||||
IntentHelper.openInstaller(compatActivity, zipFile.getAbsolutePath(),
|
finishAndRemoveTask();
|
||||||
compatActivity.getString(
|
})
|
||||||
R.string.local_install_title), null, null, false,
|
.setPositiveButton(R.string.yes, (d, i) -> {
|
||||||
BuildConfig.DEBUG && // Use debug mode if no root
|
d.dismiss();
|
||||||
InstallerInitializer.peekMagiskPath() == null);
|
// Pass the file to the installer
|
||||||
|
FoxActivity compatActivity = FoxActivity.getFoxActivity(this);
|
||||||
|
IntentHelper.openInstaller(compatActivity, zipFile.getAbsolutePath(),
|
||||||
|
compatActivity.getString(
|
||||||
|
R.string.local_install_title), null, null, false,
|
||||||
|
BuildConfig.DEBUG && // Use debug mode if no root
|
||||||
|
InstallerInitializer.peekMagiskPath() == null);
|
||||||
|
finish();
|
||||||
|
})
|
||||||
|
.show();
|
||||||
|
loading.dismiss();
|
||||||
|
});
|
||||||
|
}).start();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue