Use stub APK hiding method for Android 5.0+

At the same time, disable app hiding on devices lower than 5.0
to simplify the logic in the app. By doing so, a hidden app always
implies running as stub.
This commit is contained in:
topjohnwu 2021-01-26 04:17:37 -08:00
parent fba83e2330
commit 6ae2c9387d
4 changed files with 35 additions and 58 deletions

View File

@ -2,7 +2,6 @@ package com.topjohnwu.magisk.core.download
import android.content.Context import android.content.Context
import androidx.core.net.toFile import androidx.core.net.toFile
import com.topjohnwu.magisk.BuildConfig
import com.topjohnwu.magisk.DynAPK import com.topjohnwu.magisk.DynAPK
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.Info import com.topjohnwu.magisk.core.Info
@ -14,7 +13,7 @@ import java.io.File
private fun Context.patch(apk: File) { private fun Context.patch(apk: File) {
val patched = File(apk.parent, "patched.apk") val patched = File(apk.parent, "patched.apk")
HideAPK.patch(this, apk.path, patched.path, packageName, applicationInfo.nonLocalizedLabel) HideAPK.patch(this, apk, patched, packageName, applicationInfo.nonLocalizedLabel)
apk.delete() apk.delete()
patched.renameTo(apk) patched.renameTo(apk)
} }
@ -28,24 +27,21 @@ private fun BaseDownloader.notifyHide(id: Int) {
} }
suspend fun BaseDownloader.handleAPK(subject: Subject.Manager) { suspend fun BaseDownloader.handleAPK(subject: Subject.Manager) {
if (!isRunningAsStub)
return
val apk = subject.file.toFile() val apk = subject.file.toFile()
val id = subject.notifyID() val id = subject.notifyID()
if (isRunningAsStub) { // Move to upgrade location
// Move to upgrade location apk.copyTo(DynAPK.update(this), overwrite = true)
apk.copyTo(DynAPK.update(this), overwrite = true) apk.delete()
apk.delete() if (Info.stub!!.version < subject.stub.versionCode) {
if (Info.stub!!.version < subject.stub.versionCode) {
notifyHide(id)
// Also upgrade stub
service.fetchFile(subject.stub.link).byteStream().writeTo(apk)
patch(apk)
} else {
// Simply relaunch the app
stopSelf()
relaunchApp(this)
}
} else if (packageName != BuildConfig.APPLICATION_ID) {
notifyHide(id) notifyHide(id)
// Also upgrade stub
service.fetchFile(subject.stub.link).byteStream().writeTo(apk)
patch(apk) patch(apk)
} else {
// Simply relaunch the app
stopSelf()
relaunchApp(this)
} }
} }

View File

@ -3,12 +3,14 @@ package com.topjohnwu.magisk.core.tasks
import android.app.ProgressDialog import android.app.ProgressDialog
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Build.VERSION.SDK_INT
import android.widget.Toast import android.widget.Toast
import com.topjohnwu.magisk.BuildConfig.APPLICATION_ID import com.topjohnwu.magisk.BuildConfig.APPLICATION_ID
import com.topjohnwu.magisk.DynAPK import com.topjohnwu.magisk.DynAPK
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.core.* import com.topjohnwu.magisk.core.Config
import com.topjohnwu.magisk.core.Const
import com.topjohnwu.magisk.core.Info
import com.topjohnwu.magisk.core.Provider
import com.topjohnwu.magisk.core.utils.AXML import com.topjohnwu.magisk.core.utils.AXML
import com.topjohnwu.magisk.core.utils.Keygen import com.topjohnwu.magisk.core.utils.Keygen
import com.topjohnwu.magisk.data.repository.NetworkService import com.topjohnwu.magisk.data.repository.NetworkService
@ -67,11 +69,11 @@ object HideAPK {
fun patch( fun patch(
context: Context, context: Context,
apk: String, out: String, apk: File, out: File,
pkg: String, label: CharSequence pkg: String, label: CharSequence
): Boolean { ): Boolean {
try { try {
val jar = JarMap.open(apk) val jar = JarMap.open(apk, true)
val je = jar.getJarEntry(ANDROID_MANIFEST) val je = jar.getJarEntry(ANDROID_MANIFEST)
val xml = AXML(jar.getRawData(je)) val xml = AXML(jar.getRawData(je))
@ -91,17 +93,12 @@ object HideAPK {
} }
private suspend fun patchAndHide(context: Context, label: String): Boolean { private suspend fun patchAndHide(context: Context, label: String): Boolean {
val src = if (!isRunningAsStub && SDK_INT >= 28) { val stub = File(context.cacheDir, "stub.apk")
val stub = File(context.cacheDir, "stub.apk") try {
try { svc.fetchFile(Info.remote.stub.link).byteStream().writeTo(stub)
svc.fetchFile(Info.remote.stub.link).byteStream().writeTo(stub) } catch (e: IOException) {
} catch (e: IOException) { Timber.e(e)
Timber.e(e) return false
return false
}
stub.path
} else {
context.packageCodePath
} }
// Generate a new random package name and signature // Generate a new random package name and signature
@ -109,7 +106,7 @@ object HideAPK {
val pkg = genPackageName() val pkg = genPackageName()
Config.keyStoreRaw = "" Config.keyStoreRaw = ""
if (!patch(context, src, repack.path, pkg, label)) if (!patch(context, stub, repack, pkg, label))
return false return false
// Install the application // Install the application
@ -142,20 +139,8 @@ object HideAPK {
} }
} }
private suspend fun downloadAndRestore(context: Context): Boolean { private fun restoreImpl(context: Context): Boolean {
val apk = if (isRunningAsStub) { val apk = DynAPK.current(context)
DynAPK.current(context)
} else {
File(context.cacheDir, "manager.apk").also { apk ->
try {
svc.fetchFile(Info.remote.magisk.link).byteStream().writeTo(apk)
} catch (e: IOException) {
Timber.e(e)
return false
}
}
}
if (!Shell.su("adb_pm_install $apk").exec().isSuccess) if (!Shell.su("adb_pm_install $apk").exec().isSuccess)
return false return false
@ -176,7 +161,7 @@ object HideAPK {
val dialog = ProgressDialog.show(context, context.getString(R.string.restore_img_msg), "", true) val dialog = ProgressDialog.show(context, context.getString(R.string.restore_img_msg), "", true)
GlobalScope.launch { GlobalScope.launch {
val result = withContext(Dispatchers.IO) { val result = withContext(Dispatchers.IO) {
downloadAndRestore(context) restoreImpl(context)
} }
if (!result) { if (!result) {
Utils.toast(R.string.restore_manager_fail_toast, Toast.LENGTH_LONG) Utils.toast(R.string.restore_manager_fail_toast, Toast.LENGTH_LONG)

View File

@ -59,8 +59,12 @@ class SettingsViewModel(
)) ))
if (Info.env.isActive) { if (Info.env.isActive) {
list.add(ClearRepoCache) list.add(ClearRepoCache)
if (Const.USER_ID == 0 && Info.isConnected.get()) if (Build.VERSION.SDK_INT >= 21 && Const.USER_ID == 0) {
list.add(if (hidden) Restore else Hide) if (hidden)
list.add(Restore)
else if (Info.isConnected.get())
list.add(Hide)
}
} }
// Magisk // Magisk

View File

@ -19,18 +19,10 @@ public abstract class JarMap implements Closeable {
LinkedHashMap<String, JarEntry> entryMap; LinkedHashMap<String, JarEntry> entryMap;
public static JarMap open(String file) throws IOException {
return new FileMap(new File(file), true, ZipFile.OPEN_READ);
}
public static JarMap open(File file, boolean verify) throws IOException { public static JarMap open(File file, boolean verify) throws IOException {
return new FileMap(file, verify, ZipFile.OPEN_READ); return new FileMap(file, verify, ZipFile.OPEN_READ);
} }
public static JarMap open(String file, boolean verify) throws IOException {
return new FileMap(new File(file), verify, ZipFile.OPEN_READ);
}
public static JarMap open(InputStream is, boolean verify) throws IOException { public static JarMap open(InputStream is, boolean verify) throws IOException {
return new StreamMap(is, verify); return new StreamMap(is, verify);
} }