mirror of
https://github.com/topjohnwu/Magisk.git
synced 2025-11-12 22:43:35 +00:00
Massive project restructure
This commit is contained in:
@@ -1,56 +0,0 @@
|
||||
package com.topjohnwu.magisk.utils;
|
||||
|
||||
import android.support.annotation.Keep;
|
||||
|
||||
import com.topjohnwu.crypto.SignBoot;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarFile;
|
||||
|
||||
public class BootSigner {
|
||||
|
||||
@Keep
|
||||
public static void main(String[] args) throws Exception {
|
||||
if (args.length > 0 && "-verify".equals(args[0])) {
|
||||
String certPath = "";
|
||||
if (args.length >= 3 && "-certificate".equals(args[1])) {
|
||||
/* args[2] is the path to a public key certificate */
|
||||
certPath = args[2];
|
||||
}
|
||||
/* args[1] is the path to a signed boot image */
|
||||
boolean signed = SignBoot.verifySignature(System.in,
|
||||
certPath.isEmpty() ? null : new FileInputStream(certPath));
|
||||
System.exit(signed ? 0 : 1);
|
||||
} else if (args.length > 0 && "-sign".equals(args[0])) {
|
||||
InputStream keyIn, certIn;
|
||||
if (args.length >= 3) {
|
||||
keyIn = new FileInputStream(args[1]);
|
||||
certIn = new FileInputStream(args[2]);
|
||||
} else {
|
||||
/* Use internal test keys */
|
||||
JarFile apk = new JarFile(System.getProperty("java.class.path"));
|
||||
JarEntry keyEntry = apk.getJarEntry("assets/" + Const.PRIVATE_KEY_NAME);
|
||||
JarEntry sigEntry = apk.getJarEntry("assets/" + Const.PUBLIC_KEY_NAME);
|
||||
|
||||
keyIn = apk.getInputStream(keyEntry);
|
||||
certIn = apk.getInputStream(sigEntry);
|
||||
}
|
||||
|
||||
boolean success = SignBoot.doSignature("/boot", System.in, System.out, keyIn, certIn);
|
||||
System.exit(success ? 0 : 1);
|
||||
} else {
|
||||
System.err.println(
|
||||
"BootSigner <actions> [args]\n" +
|
||||
"Input from stdin, outputs to stdout\n" +
|
||||
"\n" +
|
||||
"Actions:\n" +
|
||||
" -verify [x509.pem]\n" +
|
||||
" verify image, cert is optional\n" +
|
||||
" -sign [pk8] [x509.pem]\n" +
|
||||
" sign image, key and cert are optional\n"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,163 +0,0 @@
|
||||
package com.topjohnwu.magisk.utils;
|
||||
|
||||
import android.os.Environment;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public class Const {
|
||||
|
||||
public static final String DEBUG_TAG = "MagiskManager";
|
||||
public static final String ORIG_PKG_NAME = "com.topjohnwu.magisk";
|
||||
public static final String SNET_PKG = "com.topjohnwu.snet";
|
||||
public static final String MAGISKHIDE_PROP = "persist.magisk.hide";
|
||||
|
||||
// APK content
|
||||
public static final String PUBLIC_KEY_NAME = "public.certificate.x509.pem";
|
||||
public static final String PRIVATE_KEY_NAME = "private.key.pk8";
|
||||
public static final String UNINSTALLER = "magisk_uninstaller.sh";
|
||||
public static final String UTIL_FUNCTIONS= "util_functions.sh";
|
||||
public static final String ANDROID_MANIFEST = "AndroidManifest.xml";
|
||||
|
||||
// Paths
|
||||
public static final String MAGISK_DISABLE_FILE = "/cache/.disable_magisk";
|
||||
public static final String TMP_FOLDER_PATH = "/dev/tmp";
|
||||
public static final String MAGISK_LOG = "/cache/magisk.log";
|
||||
public static final File EXTERNAL_PATH = new File(Environment.getExternalStorageDirectory(), "MagiskManager");
|
||||
public static final String MANAGER_CONFIGS = "/data/.tmp.magisk.config";
|
||||
|
||||
public static String BUSYBOX_PATH() {
|
||||
if (Utils.itemExist("/sbin/.core/busybox/busybox")) {
|
||||
return "/sbin/.core/busybox";
|
||||
} else {
|
||||
return "/dev/magisk/bin";
|
||||
}
|
||||
}
|
||||
|
||||
public static String MAGISK_PATH() {
|
||||
if (Utils.itemExist("/sbin/.core/img")) {
|
||||
return "/sbin/.core/img";
|
||||
} else if (Utils.itemExist("/dev/magisk/img")) {
|
||||
return "/dev/magisk/img";
|
||||
} else {
|
||||
return "/magisk";
|
||||
}
|
||||
}
|
||||
|
||||
public static String MAGISK_HOST_FILE() {
|
||||
return MAGISK_PATH() + "/.core/hosts";
|
||||
}
|
||||
|
||||
/* A list of apps that should not be shown as hide-able */
|
||||
public static final List<String> SN_BLACKLIST = Arrays.asList(
|
||||
"android",
|
||||
"com.topjohnwu.magisk",
|
||||
"com.google.android.gms"
|
||||
);
|
||||
|
||||
/* A list of apps that already uses SafetyNet
|
||||
* They DO NOT need to be added to hide list */
|
||||
public static final List<String> SN_DEFAULTLIST = Arrays.asList(
|
||||
"com.google.android.apps.walletnfcrel",
|
||||
"com.nianticlabs.pokemongo"
|
||||
);
|
||||
|
||||
public static class ID {
|
||||
public static final int UPDATE_SERVICE_ID = 1;
|
||||
public static final int FETCH_ZIP = 2;
|
||||
public static final int SELECT_BOOT = 3;
|
||||
|
||||
// notifications
|
||||
public static final int MAGISK_UPDATE_NOTIFICATION_ID = 4;
|
||||
public static final int APK_UPDATE_NOTIFICATION_ID = 5;
|
||||
public static final int ONBOOT_NOTIFICATION_ID = 6;
|
||||
public static final int DTBO_NOTIFICATION_ID = 7;
|
||||
public static final String NOTIFICATION_CHANNEL = "magisk_notification";
|
||||
}
|
||||
|
||||
public static class Url {
|
||||
public static final String STABLE_URL = "https://raw.githubusercontent.com/topjohnwu/MagiskManager/update/stable.json";
|
||||
public static final String BETA_URL = "https://raw.githubusercontent.com/topjohnwu/MagiskManager/update/beta.json";
|
||||
public static final String SNET_URL = "https://github.com/topjohnwu/MagiskManager/raw/43745edac091dd8b5ba5f6076006677d1d6c5899/snet.apk";
|
||||
public static final String REPO_URL = "https://api.github.com/users/Magisk-Modules-Repo/repos?per_page=100&page=%d";
|
||||
public static final String FILE_URL = "https://raw.githubusercontent.com/Magisk-Modules-Repo/%s/master/%s";
|
||||
public static final String ZIP_URL = "https://github.com/Magisk-Modules-Repo/%s/archive/master.zip";
|
||||
public static final String DONATION_URL = "https://www.paypal.me/topjohnwu";
|
||||
public static final String XDA_THREAD = "http://forum.xda-developers.com/showthread.php?t=3432382";
|
||||
public static final String SOURCE_CODE_URL = "https://github.com/topjohnwu/MagiskManager";
|
||||
}
|
||||
|
||||
|
||||
public static class Key {
|
||||
// su
|
||||
public static final String ROOT_ACCESS = "root_access";
|
||||
public static final String SU_MULTIUSER_MODE = "multiuser_mode";
|
||||
public static final String SU_MNT_NS = "mnt_ns";
|
||||
public static final String SU_REQUESTER = "requester";
|
||||
public static final String SU_REQUEST_TIMEOUT = "su_request_timeout";
|
||||
public static final String SU_AUTO_RESPONSE = "su_auto_response";
|
||||
public static final String SU_NOTIFICATION = "su_notification";
|
||||
public static final String SU_REAUTH = "su_reauth";
|
||||
|
||||
// intents
|
||||
public static final String OPEN_SECTION = "section";
|
||||
public static final String INTENT_SET_VERSION = "version";
|
||||
public static final String INTENT_SET_LINK = "link";
|
||||
public static final String INTENT_PERM = "perm_dialog";
|
||||
public static final String FLASH_ACTION = "action";
|
||||
public static final String FLASH_SET_BOOT = "boot";
|
||||
public static final String FLASH_SET_ENC = "enc";
|
||||
public static final String FLASH_SET_VERITY = "verity";
|
||||
|
||||
|
||||
// others
|
||||
public static final String UPDATE_NOTIFICATION = "notification";
|
||||
public static final String UPDATE_CHANNEL = "update_channel";
|
||||
public static final String CUSTOM_CHANNEL = "custom_channel";
|
||||
public static final String BOOT_FORMAT = "boot_format";
|
||||
public static final String SNET_VER = "snet_version";
|
||||
public static final String UPDATE_SERVICE_VER = "update_service_version";
|
||||
public static final String APP_VER = "app_version";
|
||||
public static final String MAGISKHIDE = "magiskhide";
|
||||
public static final String HOSTS = "hosts";
|
||||
public static final String COREONLY = "disable";
|
||||
public static final String LOCALE = "locale";
|
||||
public static final String DARK_THEME = "dark_theme";
|
||||
public static final String ETAG_KEY = "ETag";
|
||||
public static final String LINK_KEY = "Link";
|
||||
public static final String IF_NONE_MATCH = "If-None-Match";
|
||||
}
|
||||
|
||||
|
||||
public static class Value {
|
||||
public static final int STABLE_CHANNEL = 0;
|
||||
public static final int BETA_CHANNEL = 1;
|
||||
public static final int CUSTOM_CHANNEL = 2;
|
||||
public static final int ROOT_ACCESS_DISABLED = 0;
|
||||
public static final int ROOT_ACCESS_APPS_ONLY = 1;
|
||||
public static final int ROOT_ACCESS_ADB_ONLY = 2;
|
||||
public static final int ROOT_ACCESS_APPS_AND_ADB = 3;
|
||||
public static final int MULTIUSER_MODE_OWNER_ONLY = 0;
|
||||
public static final int MULTIUSER_MODE_OWNER_MANAGED = 1;
|
||||
public static final int MULTIUSER_MODE_USER = 2;
|
||||
public static final int NAMESPACE_MODE_GLOBAL = 0;
|
||||
public static final int NAMESPACE_MODE_REQUESTER = 1;
|
||||
public static final int NAMESPACE_MODE_ISOLATE = 2;
|
||||
public static final int NO_NOTIFICATION = 0;
|
||||
public static final int NOTIFICATION_TOAST = 1;
|
||||
public static final int NOTIFY_NORMAL_LOG = 0;
|
||||
public static final int NOTIFY_USER_TOASTS = 1;
|
||||
public static final int NOTIFY_USER_TO_OWNER = 2;
|
||||
public static final int SU_PROMPT = 0;
|
||||
public static final int SU_AUTO_DENY = 1;
|
||||
public static final int SU_AUTO_ALLOW = 2;
|
||||
public static final String FLASH_ZIP = "flash";
|
||||
public static final String PATCH_BOOT = "patch";
|
||||
public static final String FLASH_MAGISK = "magisk";
|
||||
public static final int[] timeoutList = {0, -1, 10, 20, 30, 60};
|
||||
public static final int UPDATE_SERVICE_VER = 1;
|
||||
public static final int SNET_VER = 5;
|
||||
public static final int MIN_MODULE_VER = 1400;
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
package com.topjohnwu.magisk.utils;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
public class Logger {
|
||||
|
||||
private static final boolean SHELL_LOGGING = false;
|
||||
|
||||
public static void debug(String line) {
|
||||
Log.d(Const.DEBUG_TAG, "DEBUG: " + line);
|
||||
}
|
||||
|
||||
public static void debug(String fmt, Object... args) {
|
||||
debug(String.format(Locale.US, fmt, args));
|
||||
}
|
||||
|
||||
public static void error(String line) {
|
||||
Log.e(Const.DEBUG_TAG, "ERROR: " + line);
|
||||
}
|
||||
|
||||
public static void error(String fmt, Object... args) {
|
||||
error(String.format(Locale.US, fmt, args));
|
||||
}
|
||||
|
||||
public static void shell(boolean in, String line) {
|
||||
if (SHELL_LOGGING) {
|
||||
Log.d(Const.DEBUG_TAG, (in ? "SHELLIN : " : "SHELLOUT: ") + line);
|
||||
}
|
||||
}
|
||||
|
||||
public static void shell(boolean in, String fmt, Object... args) {
|
||||
shell(in, String.format(Locale.US, fmt, args));
|
||||
}
|
||||
}
|
||||
@@ -1,209 +0,0 @@
|
||||
package com.topjohnwu.magisk.utils;
|
||||
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Modified by topjohnwu, based on Chainfire's libsuperuser
|
||||
*/
|
||||
|
||||
public class Shell {
|
||||
|
||||
// -2 = not initialized; -1 = no shell; 0 = non root shell; 1 = root shell
|
||||
public static int status = -2;
|
||||
|
||||
private final Process process;
|
||||
private final OutputStream STDIN;
|
||||
private final InputStream STDOUT;
|
||||
private final InputStream STDERR;
|
||||
|
||||
private static void testRootShell(Shell shell) throws IOException {
|
||||
shell.STDIN.write(("id\n").getBytes("UTF-8"));
|
||||
shell.STDIN.flush();
|
||||
String s = new BufferedReader(new InputStreamReader(shell.STDOUT)).readLine();
|
||||
if (TextUtils.isEmpty(s) || !s.contains("uid=0")) {
|
||||
shell.STDIN.close();
|
||||
shell.STDIN.close();
|
||||
throw new IOException();
|
||||
}
|
||||
}
|
||||
|
||||
public Shell(String command) throws IOException {
|
||||
process = Runtime.getRuntime().exec(command);
|
||||
STDIN = process.getOutputStream();
|
||||
STDOUT = process.getInputStream();
|
||||
STDERR = process.getErrorStream();
|
||||
}
|
||||
|
||||
public static Shell getShell() {
|
||||
MagiskManager mm = MagiskManager.get();
|
||||
boolean needNewShell = mm.shell == null;
|
||||
|
||||
if (!needNewShell) {
|
||||
try {
|
||||
mm.shell.process.exitValue();
|
||||
// The process is dead
|
||||
needNewShell = true;
|
||||
} catch (IllegalThreadStateException ignored) {
|
||||
// This should be the expected result
|
||||
}
|
||||
}
|
||||
|
||||
if (needNewShell) {
|
||||
status = 1;
|
||||
try {
|
||||
mm.shell = new Shell("su --mount-master");
|
||||
testRootShell(mm.shell);
|
||||
} catch (IOException e) {
|
||||
// Mount master not implemented
|
||||
try {
|
||||
mm.shell = new Shell("su");
|
||||
testRootShell(mm.shell);
|
||||
} catch (IOException e1) {
|
||||
// No root exists
|
||||
status = 0;
|
||||
try {
|
||||
mm.shell = new Shell("sh");
|
||||
} catch (IOException e2) {
|
||||
status = -1;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (rootAccess()) {
|
||||
// Load utility shell scripts
|
||||
try (InputStream in = mm.getAssets().open(Const.UTIL_FUNCTIONS)) {
|
||||
mm.shell.loadInputStream(in);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
// Root shell initialization
|
||||
String bbpath = Const.BUSYBOX_PATH();
|
||||
mm.shell.run_raw(false, false,
|
||||
"export PATH=" + bbpath + ":$PATH",
|
||||
"mount_partitions",
|
||||
"find_boot_image",
|
||||
"migrate_boot_backup");
|
||||
}
|
||||
}
|
||||
|
||||
return mm.shell;
|
||||
}
|
||||
|
||||
public static boolean rootAccess() {
|
||||
if (status == -2) getShell();
|
||||
return status > 0;
|
||||
}
|
||||
|
||||
public void run(Collection<String> output, Collection<String> error, String... commands) {
|
||||
StreamGobbler out, err;
|
||||
synchronized (process) {
|
||||
try {
|
||||
out = new StreamGobbler(STDOUT, output);
|
||||
err = new StreamGobbler(STDERR, error);
|
||||
out.start();
|
||||
err.start();
|
||||
run_raw(output != null, error != null, commands);
|
||||
STDIN.write("echo \'-shell-done-\'\necho \'-shell-done-\' >&2\n".getBytes("UTF-8"));
|
||||
STDIN.flush();
|
||||
try {
|
||||
out.join();
|
||||
err.join();
|
||||
} catch (InterruptedException ignored) {}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
process.destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void run_raw(boolean stdout, boolean stderr, String... commands) {
|
||||
String suffix = "\n";
|
||||
if (!stderr) suffix = " 2>/dev/null" + suffix;
|
||||
if (!stdout) suffix = " >/dev/null" + suffix;
|
||||
synchronized (process) {
|
||||
try {
|
||||
for (String command : commands) {
|
||||
Logger.shell(true, command);
|
||||
STDIN.write((command + suffix).getBytes("UTF-8"));
|
||||
STDIN.flush();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
process.destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void loadInputStream(InputStream in) {
|
||||
synchronized (process) {
|
||||
try {
|
||||
Utils.inToOut(in, STDIN);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static List<String> sh(String... commands) {
|
||||
List<String> res = new ArrayList<>();
|
||||
sh(res, commands);
|
||||
return res;
|
||||
}
|
||||
|
||||
public static void sh(Collection<String> output, String... commands) {
|
||||
Shell shell = getShell();
|
||||
if (shell == null)
|
||||
return;
|
||||
shell.run(output, null, commands);
|
||||
}
|
||||
|
||||
public static void sh_raw(String... commands) {
|
||||
Shell shell = getShell();
|
||||
if (shell == null)
|
||||
return;
|
||||
shell.run_raw(false, false, commands);
|
||||
}
|
||||
|
||||
public static List<String> su(String... commands) {
|
||||
if (!rootAccess()) return sh();
|
||||
return sh(commands);
|
||||
}
|
||||
|
||||
public static void su(Collection<String> output, String... commands) {
|
||||
if (!rootAccess()) return;
|
||||
sh(output, commands);
|
||||
}
|
||||
|
||||
public static void su_raw(String... commands) {
|
||||
if (!rootAccess()) return;
|
||||
sh_raw(commands);
|
||||
}
|
||||
|
||||
public static abstract class AbstractList<E> extends java.util.AbstractList<E> {
|
||||
|
||||
@Override
|
||||
public abstract boolean add(E e);
|
||||
|
||||
@Override
|
||||
public E get(int i) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,299 +0,0 @@
|
||||
package com.topjohnwu.magisk.utils;
|
||||
|
||||
import android.Manifest;
|
||||
import android.app.Activity;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.support.v4.app.NotificationCompat;
|
||||
import android.support.v4.app.TaskStackBuilder;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.topjohnwu.magisk.FlashActivity;
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.SplashActivity;
|
||||
import com.topjohnwu.magisk.asyncs.RestoreStockBoot;
|
||||
import com.topjohnwu.magisk.components.AlertDialogBuilder;
|
||||
import com.topjohnwu.magisk.receivers.DownloadReceiver;
|
||||
import com.topjohnwu.magisk.receivers.ManagerUpdate;
|
||||
import com.topjohnwu.magisk.receivers.RebootReceiver;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class ShowUI {
|
||||
|
||||
public static void magiskUpdateNotification() {
|
||||
MagiskManager mm = MagiskManager.get();
|
||||
|
||||
Intent intent = new Intent(mm, SplashActivity.class);
|
||||
intent.putExtra(Const.Key.OPEN_SECTION, "magisk");
|
||||
TaskStackBuilder stackBuilder = TaskStackBuilder.create(mm);
|
||||
stackBuilder.addParentStack(SplashActivity.class);
|
||||
stackBuilder.addNextIntent(intent);
|
||||
PendingIntent pendingIntent = stackBuilder.getPendingIntent(Const.ID.MAGISK_UPDATE_NOTIFICATION_ID,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
|
||||
NotificationCompat.Builder builder = new NotificationCompat.Builder(mm, Const.ID.NOTIFICATION_CHANNEL);
|
||||
builder.setSmallIcon(R.drawable.ic_magisk)
|
||||
.setContentTitle(mm.getString(R.string.magisk_update_title))
|
||||
.setContentText(mm.getString(R.string.magisk_update_available, mm.remoteMagiskVersionString))
|
||||
.setVibrate(new long[]{0, 100, 100, 100})
|
||||
.setAutoCancel(true)
|
||||
.setContentIntent(pendingIntent);
|
||||
|
||||
NotificationManager notificationManager =
|
||||
(NotificationManager) mm.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
notificationManager.notify(Const.ID.MAGISK_UPDATE_NOTIFICATION_ID, builder.build());
|
||||
}
|
||||
|
||||
public static void managerUpdateNotification() {
|
||||
MagiskManager mm = MagiskManager.get();
|
||||
|
||||
Intent intent = new Intent(mm, ManagerUpdate.class);
|
||||
intent.putExtra(Const.Key.INTENT_SET_LINK, mm.managerLink);
|
||||
intent.putExtra(Const.Key.INTENT_SET_VERSION, mm.remoteManagerVersionString);
|
||||
PendingIntent pendingIntent = PendingIntent.getBroadcast(mm,
|
||||
Const.ID.APK_UPDATE_NOTIFICATION_ID, intent, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
|
||||
NotificationCompat.Builder builder = new NotificationCompat.Builder(mm, Const.ID.NOTIFICATION_CHANNEL);
|
||||
builder.setSmallIcon(R.drawable.ic_magisk)
|
||||
.setContentTitle(mm.getString(R.string.manager_update_title))
|
||||
.setContentText(mm.getString(R.string.manager_download_install))
|
||||
.setVibrate(new long[]{0, 100, 100, 100})
|
||||
.setAutoCancel(true)
|
||||
.setContentIntent(pendingIntent);
|
||||
|
||||
NotificationManager notificationManager =
|
||||
(NotificationManager) mm.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
notificationManager.notify(Const.ID.APK_UPDATE_NOTIFICATION_ID, builder.build());
|
||||
}
|
||||
|
||||
public static void dtboPatchedNotification() {
|
||||
MagiskManager mm = MagiskManager.get();
|
||||
|
||||
Intent intent = new Intent(mm, RebootReceiver.class);
|
||||
PendingIntent pendingIntent = PendingIntent.getBroadcast(mm,
|
||||
Const.ID.DTBO_NOTIFICATION_ID, intent, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
|
||||
NotificationCompat.Builder builder = new NotificationCompat.Builder(mm, Const.ID.NOTIFICATION_CHANNEL);
|
||||
builder.setSmallIcon(R.drawable.ic_magisk)
|
||||
.setContentTitle(mm.getString(R.string.dtbo_patched_title))
|
||||
.setContentText(mm.getString(R.string.dtbo_patched_reboot))
|
||||
.setVibrate(new long[]{0, 100, 100, 100})
|
||||
.addAction(R.drawable.ic_refresh, mm.getString(R.string.reboot), pendingIntent);
|
||||
|
||||
NotificationManager notificationManager =
|
||||
(NotificationManager) mm.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
notificationManager.notify(Const.ID.DTBO_NOTIFICATION_ID, builder.build());
|
||||
}
|
||||
|
||||
public static void magiskInstallDialog(Activity activity, boolean enc, boolean verity) {
|
||||
MagiskManager mm = Utils.getMagiskManager(activity);
|
||||
String filename = Utils.getLegalFilename("Magisk-v" + mm.remoteMagiskVersionString + ".zip");
|
||||
new AlertDialogBuilder(activity)
|
||||
.setTitle(mm.getString(R.string.repo_install_title, mm.getString(R.string.magisk)))
|
||||
.setMessage(mm.getString(R.string.repo_install_msg, filename))
|
||||
.setCancelable(true)
|
||||
.setPositiveButton(R.string.install, (d, i) -> {
|
||||
List<String> options = new ArrayList<>();
|
||||
options.add(mm.getString(R.string.download_zip_only));
|
||||
options.add(mm.getString(R.string.patch_boot_file));
|
||||
if (Shell.rootAccess()) {
|
||||
options.add(mm.getString(R.string.direct_install));
|
||||
}
|
||||
List<String> res = Shell.su("echo $SLOT");
|
||||
if (Utils.isValidShellResponse(res)) {
|
||||
options.add(mm.getString(R.string.install_second_slot));
|
||||
}
|
||||
char[] slot = Utils.isValidShellResponse(res) ? res.get(0).toCharArray() : null;
|
||||
new AlertDialog.Builder(activity)
|
||||
.setTitle(R.string.select_method)
|
||||
.setItems(
|
||||
options.toArray(new String [0]),
|
||||
(dialog, idx) -> {
|
||||
String boot;
|
||||
DownloadReceiver receiver = null;
|
||||
switch (idx) {
|
||||
case 1:
|
||||
if (mm.remoteMagiskVersionCode < 1400) {
|
||||
MagiskManager.toast(R.string.no_boot_file_patch_support, Toast.LENGTH_LONG);
|
||||
return;
|
||||
}
|
||||
MagiskManager.toast(R.string.boot_file_patch_msg, Toast.LENGTH_LONG);
|
||||
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
|
||||
intent.setType("*/*");
|
||||
((com.topjohnwu.magisk.components.Activity) activity)
|
||||
.startActivityForResult(intent, Const.ID.SELECT_BOOT,
|
||||
(requestCode, resultCode, data) -> {
|
||||
if (requestCode == Const.ID.SELECT_BOOT
|
||||
&& resultCode == Activity.RESULT_OK && data != null) {
|
||||
Utils.dlAndReceive(
|
||||
activity,
|
||||
new DownloadReceiver() {
|
||||
@Override
|
||||
public void onDownloadDone(Uri uri) {
|
||||
Intent intent = new Intent(mm, FlashActivity.class);
|
||||
intent.setData(uri)
|
||||
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
.putExtra(Const.Key.FLASH_SET_BOOT, data.getData())
|
||||
.putExtra(Const.Key.FLASH_SET_ENC, enc)
|
||||
.putExtra(Const.Key.FLASH_SET_VERITY, verity)
|
||||
.putExtra(Const.Key.FLASH_ACTION, Const.Value.PATCH_BOOT);
|
||||
mm.startActivity(intent);
|
||||
}
|
||||
},
|
||||
mm.magiskLink,
|
||||
filename
|
||||
);
|
||||
}
|
||||
});
|
||||
return;
|
||||
case 0:
|
||||
receiver = new DownloadReceiver() {
|
||||
@Override
|
||||
public void onDownloadDone(Uri uri) {
|
||||
Utils.showUriSnack(activity, uri);
|
||||
}
|
||||
};
|
||||
break;
|
||||
case 2:
|
||||
boot = mm.bootBlock;
|
||||
if (boot == null)
|
||||
return;
|
||||
receiver = new DownloadReceiver() {
|
||||
@Override
|
||||
public void onDownloadDone(Uri uri) {
|
||||
Intent intent = new Intent(mm, FlashActivity.class);
|
||||
intent.setData(uri)
|
||||
.putExtra(Const.Key.FLASH_SET_BOOT, boot)
|
||||
.putExtra(Const.Key.FLASH_SET_ENC, enc)
|
||||
.putExtra(Const.Key.FLASH_SET_VERITY, verity)
|
||||
.putExtra(Const.Key.FLASH_ACTION, Const.Value.FLASH_MAGISK);
|
||||
activity.startActivity(intent);
|
||||
}
|
||||
};
|
||||
break;
|
||||
case 3:
|
||||
assert (slot != null);
|
||||
// Choose the other slot
|
||||
if (slot[1] == 'a') slot[1] = 'b';
|
||||
else slot[1] = 'a';
|
||||
// Then find the boot image again
|
||||
List<String> ret = Shell.su(
|
||||
"SLOT=" + String.valueOf(slot),
|
||||
"find_boot_image",
|
||||
"echo \"$BOOTIMAGE\""
|
||||
);
|
||||
boot = Utils.isValidShellResponse(ret) ? ret.get(ret.size() - 1) : null;
|
||||
Shell.su_raw("mount_partitions");
|
||||
if (boot == null)
|
||||
return;
|
||||
receiver = new DownloadReceiver() {
|
||||
@Override
|
||||
public void onDownloadDone(Uri uri) {
|
||||
Intent intent = new Intent(mm, FlashActivity.class);
|
||||
intent.setData(uri)
|
||||
.putExtra(Const.Key.FLASH_SET_BOOT, boot)
|
||||
.putExtra(Const.Key.FLASH_SET_ENC, enc)
|
||||
.putExtra(Const.Key.FLASH_SET_VERITY, verity)
|
||||
.putExtra(Const.Key.FLASH_ACTION, Const.Value.FLASH_MAGISK);
|
||||
activity.startActivity(intent);
|
||||
}
|
||||
};
|
||||
default:
|
||||
}
|
||||
Utils.dlAndReceive(
|
||||
activity,
|
||||
receiver,
|
||||
mm.magiskLink,
|
||||
filename
|
||||
);
|
||||
}
|
||||
).show();
|
||||
})
|
||||
.setNeutralButton(R.string.release_notes, (d, i) -> {
|
||||
if (mm.releaseNoteLink != null) {
|
||||
Intent openLink = new Intent(Intent.ACTION_VIEW, Uri.parse(mm.releaseNoteLink));
|
||||
openLink.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
mm.startActivity(openLink);
|
||||
}
|
||||
})
|
||||
.setNegativeButton(R.string.no_thanks, null)
|
||||
.show();
|
||||
}
|
||||
|
||||
public static void managerInstallDialog(Activity activity) {
|
||||
MagiskManager mm = Utils.getMagiskManager(activity);
|
||||
new AlertDialogBuilder(activity)
|
||||
.setTitle(mm.getString(R.string.repo_install_title, mm.getString(R.string.app_name)))
|
||||
.setMessage(mm.getString(R.string.repo_install_msg,
|
||||
Utils.getLegalFilename("MagiskManager-v" +
|
||||
mm.remoteManagerVersionString + ".apk")))
|
||||
.setCancelable(true)
|
||||
.setPositiveButton(R.string.install, (d, i) -> {
|
||||
Utils.dumpPrefs();
|
||||
Utils.runWithPermission(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE, () -> {
|
||||
Intent intent = new Intent(mm, ManagerUpdate.class);
|
||||
intent.putExtra(Const.Key.INTENT_SET_LINK, mm.managerLink);
|
||||
intent.putExtra(Const.Key.INTENT_SET_VERSION, mm.remoteManagerVersionString);
|
||||
mm.sendBroadcast(intent);
|
||||
});
|
||||
})
|
||||
.setNegativeButton(R.string.no_thanks, null)
|
||||
.show();
|
||||
}
|
||||
|
||||
public static void uninstallDialog(Activity activity) {
|
||||
MagiskManager mm = Utils.getMagiskManager(activity);
|
||||
new AlertDialogBuilder(activity)
|
||||
.setTitle(R.string.uninstall_magisk_title)
|
||||
.setMessage(R.string.uninstall_magisk_msg)
|
||||
.setPositiveButton(R.string.complete_uninstall, (d, i) -> {
|
||||
try {
|
||||
InputStream in = mm.getAssets().open(Const.UNINSTALLER);
|
||||
File uninstaller = new File(mm.getCacheDir(), Const.UNINSTALLER);
|
||||
FileOutputStream out = new FileOutputStream(uninstaller);
|
||||
byte[] bytes = new byte[1024];
|
||||
int read;
|
||||
while ((read = in.read(bytes)) != -1) {
|
||||
out.write(bytes, 0, read);
|
||||
}
|
||||
in.close();
|
||||
out.close();
|
||||
in = mm.getAssets().open(Const.UTIL_FUNCTIONS);
|
||||
File utils = new File(mm.getCacheDir(), Const.UTIL_FUNCTIONS);
|
||||
out = new FileOutputStream(utils);
|
||||
while ((read = in.read(bytes)) != -1) {
|
||||
out.write(bytes, 0, read);
|
||||
}
|
||||
in.close();
|
||||
out.close();
|
||||
Shell.su(
|
||||
"cat " + uninstaller + " > /cache/" + Const.UNINSTALLER,
|
||||
"cat " + utils + " > /data/magisk/" + Const.UTIL_FUNCTIONS
|
||||
);
|
||||
MagiskManager.toast(R.string.uninstall_toast, Toast.LENGTH_LONG);
|
||||
Shell.su_raw(
|
||||
"sleep 5",
|
||||
"pm uninstall " + mm.getApplicationInfo().packageName
|
||||
);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
})
|
||||
.setNeutralButton(R.string.restore_stock_boot, (d, i) -> {
|
||||
new RestoreStockBoot().exec();
|
||||
})
|
||||
.setNegativeButton(R.string.no_thanks, null)
|
||||
.show();
|
||||
}
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
package com.topjohnwu.magisk.utils;
|
||||
|
||||
import android.text.TextUtils;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
/**
|
||||
* Modified by topjohnwu, based on Chainfire's libsuperuser
|
||||
*/
|
||||
|
||||
public class StreamGobbler extends Thread {
|
||||
|
||||
private BufferedReader reader;
|
||||
private Collection<String> writer;
|
||||
|
||||
/**
|
||||
* <p>StreamGobbler constructor</p>
|
||||
*
|
||||
* <p>We use this class because sh STDOUT and STDERR should be read as quickly as
|
||||
* possible to prevent a deadlock from occurring, or Process.waitFor() never
|
||||
* returning (as the buffer is full, pausing the native process)</p>
|
||||
*
|
||||
* @param in InputStream to read from
|
||||
* @param out {@literal List<String>} to write to, or null
|
||||
*/
|
||||
public StreamGobbler(InputStream in, Collection<String> out) {
|
||||
try {
|
||||
while (in.available() != 0) {
|
||||
in.skip(in.available());
|
||||
}
|
||||
} catch (IOException ignored) {}
|
||||
reader = new BufferedReader(new InputStreamReader(in));
|
||||
writer = out == null ? null : Collections.synchronizedCollection(out);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
// keep reading the InputStream until it ends (or an error occurs)
|
||||
try {
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
if (TextUtils.equals(line, "-shell-done-"))
|
||||
return;
|
||||
if (writer != null) writer.add(line);
|
||||
Logger.shell(false, line);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
// reader probably closed, expected exit condition
|
||||
}
|
||||
|
||||
// make sure our stream is closed and resources will be freed
|
||||
try {
|
||||
reader.close();
|
||||
} catch (IOException e) {
|
||||
// read already closed
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
package com.topjohnwu.magisk.utils;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
public class Topic {
|
||||
|
||||
public boolean hasPublished = false;
|
||||
private List<WeakReference<Subscriber>> subscribers;
|
||||
|
||||
public void subscribe(Subscriber sub) {
|
||||
if (subscribers == null) {
|
||||
subscribers = new LinkedList<>();
|
||||
}
|
||||
subscribers.add(new WeakReference<>(sub));
|
||||
}
|
||||
|
||||
public void unsubscribe() {
|
||||
subscribers = null;
|
||||
}
|
||||
|
||||
public void unsubscribe(Subscriber sub) {
|
||||
for (Iterator<WeakReference<Subscriber>> i = subscribers.iterator(); i.hasNext();) {
|
||||
WeakReference<Subscriber> subscriber = i.next();
|
||||
if (subscriber.get() == null || subscriber.get() == sub) {
|
||||
i.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void publish() {
|
||||
publish(true, null);
|
||||
}
|
||||
|
||||
public void publish(boolean record) {
|
||||
publish(record, null);
|
||||
}
|
||||
|
||||
public void publish(boolean record, Object result) {
|
||||
hasPublished = record;
|
||||
if (subscribers != null) {
|
||||
for (WeakReference<Subscriber> subscriber : subscribers) {
|
||||
if (subscriber.get() != null)
|
||||
subscriber.get().onTopicPublished(this, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public interface Subscriber {
|
||||
default void subscribeTopics() {
|
||||
for (Topic topic : getSubscription()) {
|
||||
if (topic.hasPublished) {
|
||||
onTopicPublished(topic);
|
||||
}
|
||||
topic.subscribe(this);
|
||||
}
|
||||
}
|
||||
default void unsubscribeTopics() {
|
||||
for (Topic event : getSubscription()) {
|
||||
event.unsubscribe(this);
|
||||
}
|
||||
}
|
||||
default void onTopicPublished() {
|
||||
onTopicPublished(null, null);
|
||||
}
|
||||
default void onTopicPublished(Topic topic) {
|
||||
onTopicPublished(topic, null);
|
||||
}
|
||||
void onTopicPublished(Topic topic, Object result);
|
||||
Topic[] getSubscription();
|
||||
}
|
||||
}
|
||||
@@ -1,288 +0,0 @@
|
||||
package com.topjohnwu.magisk.utils;
|
||||
|
||||
import android.Manifest;
|
||||
import android.app.Activity;
|
||||
import android.app.DownloadManager;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.res.AssetManager;
|
||||
import android.content.res.Configuration;
|
||||
import android.database.Cursor;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.NetworkInfo;
|
||||
import android.net.Uri;
|
||||
import android.provider.OpenableColumns;
|
||||
import android.support.annotation.StringRes;
|
||||
import android.support.design.widget.Snackbar;
|
||||
import android.support.v4.app.ActivityCompat;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.text.TextUtils;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.SplashActivity;
|
||||
import com.topjohnwu.magisk.components.SnackbarMaker;
|
||||
import com.topjohnwu.magisk.receivers.DownloadReceiver;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
public class Utils {
|
||||
|
||||
public static boolean isDownloading = false;
|
||||
|
||||
public static boolean itemExist(String path) {
|
||||
String command = "[ -e " + path + " ] && echo true || echo false";
|
||||
List<String> ret = Shell.su(command);
|
||||
return isValidShellResponse(ret) && Boolean.parseBoolean(ret.get(0));
|
||||
}
|
||||
|
||||
public static void createFile(String path) {
|
||||
String folder = path.substring(0, path.lastIndexOf('/'));
|
||||
String command = "mkdir -p " + folder + " 2>/dev/null; touch " + path + " 2>/dev/null;";
|
||||
Shell.su_raw(command);
|
||||
}
|
||||
|
||||
public static void removeItem(String path) {
|
||||
String command = "rm -rf " + path + " 2>/dev/null";
|
||||
Shell.su_raw(command);
|
||||
}
|
||||
|
||||
public static List<String> readFile(String path) {
|
||||
String command = "cat " + path + " | sed '$a\\ ' | sed '$d'";
|
||||
return Shell.su(command);
|
||||
}
|
||||
|
||||
public static void dlAndReceive(Context context, DownloadReceiver receiver, String link, String filename) {
|
||||
if (isDownloading)
|
||||
return;
|
||||
|
||||
runWithPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE, () -> {
|
||||
File file = new File(Const.EXTERNAL_PATH, filename);
|
||||
|
||||
if ((!file.getParentFile().exists() && !file.getParentFile().mkdirs())
|
||||
|| (file.exists() && !file.delete())) {
|
||||
Toast.makeText(context, R.string.permissionNotGranted, Toast.LENGTH_LONG).show();
|
||||
return;
|
||||
}
|
||||
|
||||
Toast.makeText(context, context.getString(R.string.downloading_toast, filename), Toast.LENGTH_LONG).show();
|
||||
isDownloading = true;
|
||||
|
||||
DownloadManager downloadManager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
|
||||
|
||||
if (link != null) {
|
||||
DownloadManager.Request request = new DownloadManager.Request(Uri.parse(link));
|
||||
request.setDestinationUri(Uri.fromFile(file));
|
||||
receiver.setDownloadID(downloadManager.enqueue(request));
|
||||
}
|
||||
receiver.setFilename(filename);
|
||||
context.getApplicationContext().registerReceiver(receiver, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
|
||||
});
|
||||
}
|
||||
|
||||
public static String getLegalFilename(CharSequence filename) {
|
||||
return filename.toString().replace(" ", "_").replace("'", "").replace("\"", "")
|
||||
.replace("$", "").replace("`", "").replace("(", "").replace(")", "")
|
||||
.replace("#", "").replace("@", "").replace("*", "");
|
||||
}
|
||||
|
||||
public static boolean isValidShellResponse(List<String> list) {
|
||||
if (list != null && list.size() != 0) {
|
||||
// Check if all empty
|
||||
for (String res : list) {
|
||||
if (!TextUtils.isEmpty(res)) return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static int getPrefsInt(SharedPreferences prefs, String key, int def) {
|
||||
return Integer.parseInt(prefs.getString(key, String.valueOf(def)));
|
||||
}
|
||||
|
||||
public static int getPrefsInt(SharedPreferences prefs, String key) {
|
||||
return getPrefsInt(prefs, key, 0);
|
||||
}
|
||||
|
||||
public static MagiskManager getMagiskManager(Context context) {
|
||||
return (MagiskManager) context.getApplicationContext();
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
public static boolean checkNetworkStatus() {
|
||||
ConnectivityManager manager = (ConnectivityManager)
|
||||
MagiskManager.get().getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||
NetworkInfo networkInfo = manager.getActiveNetworkInfo();
|
||||
return networkInfo != null && networkInfo.isConnected();
|
||||
}
|
||||
|
||||
public static String getLocaleString(Locale locale, @StringRes int id) {
|
||||
Context context = MagiskManager.get();
|
||||
Configuration config = context.getResources().getConfiguration();
|
||||
config.setLocale(locale);
|
||||
Context localizedContext = context.createConfigurationContext(config);
|
||||
return localizedContext.getString(id);
|
||||
}
|
||||
|
||||
public static List<Locale> getAvailableLocale() {
|
||||
List<Locale> locales = new ArrayList<>();
|
||||
HashSet<String> set = new HashSet<>();
|
||||
Locale locale;
|
||||
|
||||
@StringRes int compareId = R.string.download_file_error;
|
||||
|
||||
// Add default locale
|
||||
locales.add(Locale.ENGLISH);
|
||||
set.add(getLocaleString(Locale.ENGLISH, compareId));
|
||||
|
||||
// Add some special locales
|
||||
locales.add(Locale.TAIWAN);
|
||||
set.add(getLocaleString(Locale.TAIWAN, compareId));
|
||||
locale = new Locale("pt", "BR");
|
||||
locales.add(locale);
|
||||
set.add(getLocaleString(locale, compareId));
|
||||
|
||||
// Other locales
|
||||
for (String s : MagiskManager.get().getAssets().getLocales()) {
|
||||
locale = Locale.forLanguageTag(s);
|
||||
if (set.add(getLocaleString(locale, compareId))) {
|
||||
locales.add(locale);
|
||||
}
|
||||
}
|
||||
|
||||
Collections.sort(locales, (l1, l2) -> l1.getDisplayName(l1).compareTo(l2.getDisplayName(l2)));
|
||||
|
||||
return locales;
|
||||
}
|
||||
|
||||
public static void runWithPermission(Context context, String permission, Runnable callback) {
|
||||
if (ContextCompat.checkSelfPermission(context, permission) != PackageManager.PERMISSION_GRANTED) {
|
||||
// Passed in context should be an activity if not granted, need to show dialog!
|
||||
Utils.getMagiskManager(context).setPermissionGrantCallback(callback);
|
||||
if (!(context instanceof com.topjohnwu.magisk.components.Activity)) {
|
||||
// Start activity to show dialog
|
||||
Intent intent = new Intent(context, SplashActivity.class);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
intent.putExtra(Const.Key.INTENT_PERM, permission);
|
||||
context.startActivity(intent);
|
||||
} else {
|
||||
ActivityCompat.requestPermissions((Activity) context, new String[] { permission }, 0);
|
||||
}
|
||||
|
||||
} else {
|
||||
callback.run();
|
||||
}
|
||||
}
|
||||
|
||||
public static File getDatabasePath(Context context, String dbName) {
|
||||
return new File(context.getFilesDir().getParent() + "/databases", dbName);
|
||||
}
|
||||
|
||||
public static AssetManager getAssets(String apk) {
|
||||
try {
|
||||
AssetManager asset = AssetManager.class.newInstance();
|
||||
AssetManager.class.getMethod("addAssetPath", String.class).invoke(asset, apk);
|
||||
return asset;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static int inToOut(InputStream in, OutputStream out) throws IOException {
|
||||
int read, total = 0;
|
||||
byte buffer[] = new byte[4096];
|
||||
while ((read = in.read(buffer)) > 0) {
|
||||
out.write(buffer, 0, read);
|
||||
total += read;
|
||||
}
|
||||
out.flush();
|
||||
return total;
|
||||
}
|
||||
|
||||
public static void patchDTBO() {
|
||||
if (MagiskManager.get().magiskVersionCode >= 1446) {
|
||||
List<String> ret = Shell.su("patch_dtbo_image && echo true || echo false");
|
||||
if (Utils.isValidShellResponse(ret) && Boolean.parseBoolean(ret.get(ret.size() - 1))) {
|
||||
ShowUI.dtboPatchedNotification();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static int dpInPx(int dp) {
|
||||
Context context = MagiskManager.get();
|
||||
float scale = context.getResources().getDisplayMetrics().density;
|
||||
return (int) (dp * scale + 0.5);
|
||||
}
|
||||
|
||||
public static void dumpPrefs() {
|
||||
Map<String, ?> prefMap = MagiskManager.get().prefs.getAll();
|
||||
Gson gson = new Gson();
|
||||
String json = gson.toJson(prefMap, new TypeToken<Map<String, ?>>(){}.getType());
|
||||
Shell.su("echo '" + json + "' > " + Const.MANAGER_CONFIGS);
|
||||
}
|
||||
|
||||
public static void loadPrefs() {
|
||||
List<String> ret = Utils.readFile(Const.MANAGER_CONFIGS);
|
||||
if (isValidShellResponse(ret)) {
|
||||
removeItem(Const.MANAGER_CONFIGS);
|
||||
SharedPreferences.Editor editor = MagiskManager.get().prefs.edit();
|
||||
String json = ret.get(0);
|
||||
Gson gson = new Gson();
|
||||
Map<String, ?> prefMap = gson.fromJson(json, new TypeToken<Map<String, ?>>(){}.getType());
|
||||
editor.clear();
|
||||
for (Map.Entry<String, ?> entry : prefMap.entrySet()) {
|
||||
Object value = entry.getValue();
|
||||
if (value instanceof String) {
|
||||
editor.putString(entry.getKey(), (String) value);
|
||||
} else if (value instanceof Boolean) {
|
||||
editor.putBoolean(entry.getKey(), (boolean) value);
|
||||
} else if (value instanceof Integer) {
|
||||
editor.putInt(entry.getKey(), (int) value);
|
||||
}
|
||||
}
|
||||
editor.remove(Const.Key.ETAG_KEY);
|
||||
editor.apply();
|
||||
MagiskManager.get().loadConfig();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
package com.topjohnwu.magisk.utils;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class WebService {
|
||||
|
||||
public static String getString(String url) {
|
||||
return getString(url, null);
|
||||
}
|
||||
|
||||
public static String getString(String url, Map<String, String> header) {
|
||||
HttpURLConnection conn = request(url, header);
|
||||
if (conn == null) return "";
|
||||
return getString(conn);
|
||||
}
|
||||
|
||||
public static String getString(HttpURLConnection conn) {
|
||||
try {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) {
|
||||
BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream()));
|
||||
int len;
|
||||
char buf[] = new char[4096];
|
||||
while ((len = br.read(buf)) != -1) {
|
||||
builder.append(buf, 0, len);
|
||||
}
|
||||
}
|
||||
conn.disconnect();
|
||||
return builder.toString();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
public static HttpURLConnection request(String address, Map<String, String> header) {
|
||||
try {
|
||||
URL url = new URL(address);
|
||||
|
||||
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
|
||||
conn.setReadTimeout(15000);
|
||||
conn.setConnectTimeout(15000);
|
||||
|
||||
if (header != null) {
|
||||
for (Map.Entry<String, String> entry : header.entrySet()) {
|
||||
conn.setRequestProperty(entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
conn.connect();
|
||||
|
||||
if (header != null) {
|
||||
header.clear();
|
||||
for (Map.Entry<String, List<String>> entry : conn.getHeaderFields().entrySet()) {
|
||||
List<String> l = entry.getValue();
|
||||
header.put(entry.getKey(), l.get(l.size() - 1));
|
||||
}
|
||||
}
|
||||
|
||||
return conn;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
package com.topjohnwu.magisk.utils;
|
||||
|
||||
import android.content.res.AssetManager;
|
||||
|
||||
import com.topjohnwu.crypto.JarMap;
|
||||
import com.topjohnwu.crypto.SignAPK;
|
||||
import com.topjohnwu.magisk.MagiskManager;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarInputStream;
|
||||
|
||||
public class ZipUtils {
|
||||
|
||||
static {
|
||||
System.loadLibrary("zipadjust");
|
||||
}
|
||||
|
||||
public native static void zipAdjust(String filenameIn, String filenameOut);
|
||||
|
||||
public static void unzip(File zip, File folder, String path, boolean junkPath) throws Exception {
|
||||
InputStream in = new BufferedInputStream(new FileInputStream(zip));
|
||||
unzip(in, folder, path, junkPath);
|
||||
in.close();
|
||||
}
|
||||
|
||||
public static void unzip(InputStream zip, File folder, String path, boolean junkPath) throws Exception {
|
||||
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;
|
||||
}
|
||||
String name;
|
||||
if (junkPath) {
|
||||
name = entry.getName().substring(entry.getName().lastIndexOf('/') + 1);
|
||||
} else {
|
||||
name = entry.getName();
|
||||
}
|
||||
File dest = new File(folder, name);
|
||||
dest.getParentFile().mkdirs();
|
||||
try (FileOutputStream out = new FileOutputStream(dest)) {
|
||||
Utils.inToOut(zipfile, out);
|
||||
}
|
||||
}
|
||||
} catch(Exception e) {
|
||||
e.printStackTrace();
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
public static void signZip(InputStream is, File output, boolean minSign) throws Exception {
|
||||
signZip(new JarMap(is, false), output, minSign);
|
||||
}
|
||||
|
||||
public static void signZip(File input, File output, boolean minSign) throws Exception {
|
||||
signZip(new JarMap(input, false), output, minSign);
|
||||
}
|
||||
|
||||
public static void signZip(JarMap input, File output, boolean minSign) throws Exception {
|
||||
AssetManager assets = MagiskManager.get().getAssets();
|
||||
SignAPK.signZip(
|
||||
assets.open(Const.PUBLIC_KEY_NAME), assets.open(Const.PRIVATE_KEY_NAME),
|
||||
input, output, minSign);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user