diff --git a/app/src/main/java/com/topjohnwu/magisk/App.kt b/app/src/main/java/com/topjohnwu/magisk/App.kt index feba85a59..05495595e 100644 --- a/app/src/main/java/com/topjohnwu/magisk/App.kt +++ b/app/src/main/java/com/topjohnwu/magisk/App.kt @@ -4,7 +4,6 @@ import android.annotation.SuppressLint import android.app.Application import android.content.Context import android.content.res.Configuration -import android.os.AsyncTask import android.os.Build import androidx.appcompat.app.AppCompatDelegate import androidx.multidex.MultiDex @@ -23,7 +22,6 @@ import com.topjohnwu.superuser.Shell import org.koin.android.ext.koin.androidContext import org.koin.core.context.startKoin import timber.log.Timber -import java.util.concurrent.ThreadPoolExecutor open class App : Application() { @@ -68,17 +66,12 @@ open class App : Application() { @JvmStatic lateinit var self: App - @Deprecated("Use Rx or similar") - @JvmField - var THREAD_POOL: ThreadPoolExecutor - init { AppCompatDelegate.setCompatVectorFromResourcesEnabled(true) Shell.Config.setFlags(Shell.FLAG_MOUNT_MASTER or Shell.FLAG_USE_MAGISK_BUSYBOX) Shell.Config.verboseLogging(BuildConfig.DEBUG) Shell.Config.addInitializers(RootUtils::class.java) Shell.Config.setTimeout(2) - THREAD_POOL = AsyncTask.THREAD_POOL_EXECUTOR as ThreadPoolExecutor Room.setFactory { when (it) { WorkDatabase::class.java -> WorkDatabase_Impl() diff --git a/app/src/main/java/com/topjohnwu/magisk/Config.kt b/app/src/main/java/com/topjohnwu/magisk/Config.kt index ca099bd80..8e693f9d0 100644 --- a/app/src/main/java/com/topjohnwu/magisk/Config.kt +++ b/app/src/main/java/com/topjohnwu/magisk/Config.kt @@ -125,8 +125,7 @@ object Config : PreferenceModel, DBConfig { var suMntNamespaceMode by dbSettings(Key.SU_MNT_NS, Value.NAMESPACE_MODE_REQUESTER) var suMultiuserMode by dbSettings(Key.SU_MULTIUSER_MODE, Value.MULTIUSER_MODE_OWNER_ONLY) var suFingerprint by dbSettings(Key.SU_FINGERPRINT, false) - @JvmStatic - var suManager by dbStrings(Key.SU_MANAGER, "") + var suManager by dbStrings(Key.SU_MANAGER, "", true) // Always return a path in external storage where we can write val downloadDirectory get() = @@ -199,7 +198,6 @@ object Config : PreferenceModel, DBConfig { } } - @JvmStatic fun export() { // Flush prefs to disk prefs.edit().apply() diff --git a/app/src/main/java/com/topjohnwu/magisk/data/repository/DBConfig.kt b/app/src/main/java/com/topjohnwu/magisk/data/repository/DBConfig.kt index 0df96e77f..119cf3bf6 100644 --- a/app/src/main/java/com/topjohnwu/magisk/data/repository/DBConfig.kt +++ b/app/src/main/java/com/topjohnwu/magisk/data/repository/DBConfig.kt @@ -23,8 +23,9 @@ interface DBConfig { fun dbStrings( name: String, - default: String - ) = DBStringsValue(name, default) + default: String, + sync: Boolean = false + ) = DBStringsValue(name, default, sync) } @@ -70,7 +71,8 @@ class DBBoolSettings( class DBStringsValue( private val name: String, - private val default: String + private val default: String, + private val sync: Boolean ) : ReadWriteProperty { private var value: String? = null @@ -88,8 +90,12 @@ class DBStringsValue( synchronized(this) { this.value = value } - thisRef.stringDao.put(getKey(property), value) - .subscribeOn(Schedulers.io()) - .subscribe() + if (sync) { + thisRef.stringDao.put(getKey(property), value).blockingAwait() + } else { + thisRef.stringDao.put(getKey(property), value) + .subscribeOn(Schedulers.io()) + .subscribe() + } } } diff --git a/app/src/main/java/com/topjohnwu/magisk/ui/settings/SettingsFragment.kt b/app/src/main/java/com/topjohnwu/magisk/ui/settings/SettingsFragment.kt index 8fc2496fc..d59150a15 100644 --- a/app/src/main/java/com/topjohnwu/magisk/ui/settings/SettingsFragment.kt +++ b/app/src/main/java/com/topjohnwu/magisk/ui/settings/SettingsFragment.kt @@ -73,7 +73,7 @@ class SettingsFragment : BasePreferenceFragment() { val suCategory = findPreference("superuser") as PreferenceCategory val hideManager = findPreference("hide") hideManager.setOnPreferenceClickListener { - PatchAPK.hideManager() + PatchAPK.hideManager(requireContext()) true } val restoreManager = findPreference("restore") diff --git a/app/src/main/java/com/topjohnwu/magisk/utils/PatchAPK.java b/app/src/main/java/com/topjohnwu/magisk/utils/PatchAPK.java deleted file mode 100644 index 042444a16..000000000 --- a/app/src/main/java/com/topjohnwu/magisk/utils/PatchAPK.java +++ /dev/null @@ -1,152 +0,0 @@ -package com.topjohnwu.magisk.utils; - -import android.content.ComponentName; -import android.widget.Toast; - -import androidx.core.app.NotificationCompat; - -import com.topjohnwu.magisk.App; -import com.topjohnwu.magisk.BuildConfig; -import com.topjohnwu.magisk.ClassMap; -import com.topjohnwu.magisk.Config; -import com.topjohnwu.magisk.Const; -import com.topjohnwu.magisk.R; -import com.topjohnwu.magisk.ui.SplashActivity; -import com.topjohnwu.magisk.view.Notifications; -import com.topjohnwu.signing.JarMap; -import com.topjohnwu.signing.SignAPK; -import com.topjohnwu.superuser.Shell; - -import java.io.BufferedOutputStream; -import java.io.File; -import java.io.FileOutputStream; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.nio.CharBuffer; -import java.nio.IntBuffer; -import java.security.SecureRandom; -import java.util.ArrayList; -import java.util.List; -import java.util.jar.JarEntry; - -public class PatchAPK { - - public static final String LOWERALPHA = "abcdefghijklmnopqrstuvwxyz"; - public static final String UPPERALPHA = LOWERALPHA.toUpperCase(); - public static final String ALPHA = LOWERALPHA + UPPERALPHA; - public static final String DIGITS = "0123456789"; - public static final String ALPHANUM = ALPHA + DIGITS; - public static final String ALPHANUMDOTS = ALPHANUM + "............"; - - private static String genPackageName(String prefix, int length) { - StringBuilder builder = new StringBuilder(length); - builder.append(prefix); - length -= prefix.length(); - SecureRandom random = new SecureRandom(); - char next, prev = prefix.charAt(prefix.length() - 1); - for (int i = 0; i < length; ++i) { - if (prev == '.' || i == length - 1) { - next = ALPHA.charAt(random.nextInt(ALPHA.length())); - } else { - next = ALPHANUMDOTS.charAt(random.nextInt(ALPHANUMDOTS.length())); - } - builder.append(next); - prev = next; - } - return builder.toString(); - } - - private static boolean findAndPatch(byte[] xml, String from, String to) { - if (from.length() != to.length()) - return false; - CharBuffer buf = ByteBuffer.wrap(xml).order(ByteOrder.LITTLE_ENDIAN).asCharBuffer(); - List offList = new ArrayList<>(); - for (int i = 0; i < buf.length() - from.length(); ++i) { - boolean match = true; - for (int j = 0; j < from.length(); ++j) { - if (buf.get(i + j) != from.charAt(j)) { - match = false; - break; - } - } - if (match) { - offList.add(i); - i += from.length(); - } - } - if (offList.isEmpty()) - return false; - for (int off : offList) { - buf.position(off); - buf.put(to); - } - return true; - } - - private static boolean findAndPatch(byte[] xml, int a, int b) { - IntBuffer buf = ByteBuffer.wrap(xml).order(ByteOrder.LITTLE_ENDIAN).asIntBuffer(); - int len = xml.length / 4; - for (int i = 0; i < len; ++i) { - if (buf.get(i) == a) { - buf.put(i, b); - return true; - } - } - return false; - } - - private static boolean patchAndHide() { - App app = App.self; - - // Generate a new app with random package name - File repack = new File(app.getFilesDir(), "patched.apk"); - String pkg = genPackageName("com.", BuildConfig.APPLICATION_ID.length()); - - if (!patch(app.getPackageCodePath(), repack.getPath(), pkg)) - return false; - - // Install the application - repack.setReadable(true, false); - if (!Shell.su("pm install " + repack).exec().isSuccess()) - return false; - - Config.setSuManager(pkg); - Config.export(); - RootUtils.rmAndLaunch(BuildConfig.APPLICATION_ID, - new ComponentName(pkg, ClassMap.get(SplashActivity.class).getName())); - - return true; - } - - public static boolean patch(String in, String out, String pkg) { - try { - JarMap jar = new JarMap(in); - JarEntry je = jar.getJarEntry(Const.ANDROID_MANIFEST); - byte[] xml = jar.getRawData(je); - - if (!findAndPatch(xml, BuildConfig.APPLICATION_ID, pkg) || - !findAndPatch(xml, R.string.app_name, R.string.re_app_name)) - return false; - - // Write in changes - jar.getOutputStream(je).write(xml); - SignAPK.sign(jar, new BufferedOutputStream(new FileOutputStream(out))); - } catch (Exception e) { - e.printStackTrace(); - return false; - } - return true; - } - - public static void hideManager() { - App.THREAD_POOL.execute(() -> { - App app = App.self; - NotificationCompat.Builder progress = - Notifications.progress(app.getString(R.string.hide_manager_title)); - Notifications.mgr.notify(Const.ID.HIDE_MANAGER_NOTIFICATION_ID, progress.build()); - if(!patchAndHide()) - Utils.INSTANCE.toast(R.string.hide_manager_fail_toast, Toast.LENGTH_LONG); - Notifications.mgr.cancel(Const.ID.HIDE_MANAGER_NOTIFICATION_ID); - }); - } -} diff --git a/app/src/main/java/com/topjohnwu/magisk/utils/PatchAPK.kt b/app/src/main/java/com/topjohnwu/magisk/utils/PatchAPK.kt new file mode 100644 index 000000000..74996f23a --- /dev/null +++ b/app/src/main/java/com/topjohnwu/magisk/utils/PatchAPK.kt @@ -0,0 +1,142 @@ +package com.topjohnwu.magisk.utils + +import android.content.ComponentName +import android.content.Context +import android.widget.Toast +import com.skoumal.teanity.extensions.subscribeK +import com.topjohnwu.magisk.* +import com.topjohnwu.magisk.ui.SplashActivity +import com.topjohnwu.magisk.view.Notifications +import com.topjohnwu.signing.JarMap +import com.topjohnwu.signing.SignAPK +import com.topjohnwu.superuser.Shell +import io.reactivex.Completable +import timber.log.Timber +import java.io.File +import java.io.FileOutputStream +import java.nio.ByteBuffer +import java.nio.ByteOrder +import java.security.SecureRandom + +object PatchAPK { + + private const val LOWERALPHA = "abcdefghijklmnopqrstuvwxyz" + private val UPPERALPHA = LOWERALPHA.toUpperCase() + private val ALPHA = LOWERALPHA + UPPERALPHA + private const val DIGITS = "0123456789" + private val ALPHANUM = ALPHA + DIGITS + private val ALPHANUMDOTS = "$ALPHANUM............" + + private fun genPackageName(prefix: String, length: Int): String { + val builder = StringBuilder(length) + builder.append(prefix) + val len = length - prefix.length + val random = SecureRandom() + var next: Char + var prev = prefix[prefix.length - 1] + for (i in 0 until len) { + next = if (prev == '.' || i == len - 1) { + ALPHA[random.nextInt(ALPHA.length)] + } else { + ALPHANUMDOTS[random.nextInt(ALPHANUMDOTS.length)] + } + builder.append(next) + prev = next + } + return builder.toString() + } + + private fun findAndPatch(xml: ByteArray, from: String, to: String): Boolean { + if (from.length != to.length) + return false + val buf = ByteBuffer.wrap(xml).order(ByteOrder.LITTLE_ENDIAN).asCharBuffer() + val offList = mutableListOf() + var i = 0 + while (i < buf.length - from.length) { + var match = true + for (j in 0 until from.length) { + if (buf.get(i + j) != from[j]) { + match = false + break + } + } + if (match) { + offList.add(i) + i += from.length + } + ++i + } + if (offList.isEmpty()) + return false + for (off in offList) { + buf.position(off) + buf.put(to) + } + return true + } + + private fun findAndPatch(xml: ByteArray, a: Int, b: Int): Boolean { + val buf = ByteBuffer.wrap(xml).order(ByteOrder.LITTLE_ENDIAN).asIntBuffer() + val len = xml.size / 4 + for (i in 0 until len) { + if (buf.get(i) == a) { + buf.put(i, b) + return true + } + } + return false + } + + private fun patchAndHide(context: Context): Boolean { + // Generate a new app with random package name + val repack = File(context.filesDir, "patched.apk") + val pkg = genPackageName("com.", BuildConfig.APPLICATION_ID.length) + + if (!patch(context.packageCodePath, repack.path, pkg)) + return false + + // Install the application + repack.setReadable(true, false) + if (!Shell.su("pm install $repack").exec().isSuccess) + return false + + Config.suManager = pkg + Config.export() + RootUtils.rmAndLaunch(BuildConfig.APPLICATION_ID, + ComponentName(pkg, ClassMap.get>(SplashActivity::class.java).name)) + + return true + } + + @JvmStatic + fun patch(apk: String, out: String, pkg: String): Boolean { + try { + val jar = JarMap(apk) + val je = jar.getJarEntry(Const.ANDROID_MANIFEST) + val xml = jar.getRawData(je) + + if (!findAndPatch(xml, BuildConfig.APPLICATION_ID, pkg) || + !findAndPatch(xml, R.string.app_name, R.string.re_app_name)) + return false + + // Write apk changes + jar.getOutputStream(je).write(xml) + SignAPK.sign(jar, FileOutputStream(out).buffered()) + } catch (e: Exception) { + Timber.e(e) + return false + } + + return true + } + + fun hideManager(context: Context) { + Completable.fromAction { + val progress = Notifications.progress(context, context.getString(R.string.hide_manager_title)) + Notifications.mgr.notify(Const.ID.HIDE_MANAGER_NOTIFICATION_ID, progress.build()) + if (!patchAndHide(context)) + Utils.toast(R.string.hide_manager_fail_toast, Toast.LENGTH_LONG) + Notifications.mgr.cancel(Const.ID.HIDE_MANAGER_NOTIFICATION_ID) + }.subscribeK() + } +} diff --git a/app/src/main/java/com/topjohnwu/magisk/view/Notifications.java b/app/src/main/java/com/topjohnwu/magisk/view/Notifications.java index dd7f6197a..22b085b2e 100644 --- a/app/src/main/java/com/topjohnwu/magisk/view/Notifications.java +++ b/app/src/main/java/com/topjohnwu/magisk/view/Notifications.java @@ -97,10 +97,6 @@ public class Notifications { mgr.notify(Const.ID.DTBO_NOTIFICATION_ID, builder.build()); } - public static NotificationCompat.Builder progress(CharSequence title) { - return progress(App.self, title); - } - public static NotificationCompat.Builder progress(Context context, CharSequence title) { NotificationCompat.Builder builder = new NotificationCompat.Builder(context, Const.ID.PROGRESS_NOTIFICATION_CHANNEL); builder.setPriority(NotificationCompat.PRIORITY_LOW)