diff --git a/app/build.gradle b/app/build.gradle index 401a9f1b9..1c474ac40 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -35,13 +35,12 @@ android { } packagingOptions { - exclude '/META-INF/*.version' - exclude '/META-INF/*.kotlin_module' - exclude '/META-INF/rxkotlin.properties' + exclude '/META-INF/**' exclude '/androidsupportmultidexversion.txt' exclude '/org/bouncycastle/**' exclude '/kotlin/**' exclude '/kotlinx/**' + exclude '/okhttp3/**' } kotlinOptions { @@ -85,7 +84,7 @@ dependencies { implementation "com.github.topjohnwu.libsu:core:${vLibsu}" implementation "com.github.topjohnwu.libsu:io:${vLibsu}" - def vKoin = "2.0.1" + def vKoin = '2.0.1' implementation "org.koin:koin-core:${vKoin}" implementation "org.koin:koin-android:${vKoin}" implementation "org.koin:koin-androidx-viewmodel:${vKoin}" @@ -100,10 +99,10 @@ dependencies { implementation "com.squareup.okhttp3:okhttp:${vOkHttp}" implementation "com.squareup.okhttp3:logging-interceptor:${vOkHttp}" - def vMoshi = "1.8.0" + def vMoshi = '1.9.1' implementation "com.squareup.moshi:moshi:${vMoshi}" - def vKotshi = "2.0.1" + def vKotshi = '2.0.2' implementation "se.ansman.kotshi:api:${vKotshi}" kapt "se.ansman.kotshi:compiler:${vKotshi}" @@ -112,20 +111,22 @@ dependencies { replacedBy('com.github.topjohnwu:room-runtime') } } - def vRoom = "2.2.1" + def vRoom = '2.2.1' implementation "com.github.topjohnwu:room-runtime:${vRoom}" + implementation "androidx.room:room-rxjava2:${vRoom}" kapt "androidx.room:room-compiler:${vRoom}" - def vNav = "2.1.0" - implementation "androidx.navigation:navigation-fragment-ktx:$vNav" - implementation "androidx.navigation:navigation-ui-ktx:$vNav" + def vNav = '2.1.0' + implementation "androidx.navigation:navigation-fragment-ktx:${vNav}" + implementation "androidx.navigation:navigation-ui-ktx:${vNav}" + implementation 'androidx.biometric:biometric:1.0.0' implementation 'androidx.constraintlayout:constraintlayout:1.1.3' implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0-alpha03' implementation 'androidx.browser:browser:1.0.0' implementation 'androidx.preference:preference:1.1.0' implementation 'androidx.recyclerview:recyclerview:1.1.0-rc01' - implementation 'androidx.fragment:fragment-ktx:1.2.0-rc01' + implementation 'androidx.fragment:fragment-ktx:1.2.0-rc02' implementation 'androidx.work:work-runtime:2.2.0' implementation 'androidx.transition:transition:1.2.0' implementation 'androidx.multidex:multidex:2.0.1' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index a9852a70b..dc1867923 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,45 +1,21 @@ - - - - + tools:ignore="UnusedAttribute,GoogleAppIndexingWarning"> - @@ -48,11 +24,7 @@ - - + - - + - + android:exported="false" + android:theme="@android:style/Theme.Translucent.NoTitleBar" + tools:ignore="AppLinkUrlError"> + + + + + - - @@ -103,10 +74,7 @@ - - + + + + + + + diff --git a/app/src/main/java/a/a.java b/app/src/main/java/a/a.java index 23927a021..dcff5fbfa 100644 --- a/app/src/main/java/a/a.java +++ b/app/src/main/java/a/a.java @@ -1,11 +1,9 @@ package a; -import androidx.core.app.AppComponentFactory; - import com.topjohnwu.magisk.utils.PatchAPK; import com.topjohnwu.signing.BootSigner; -public class a extends AppComponentFactory { +public class a { @Deprecated public static boolean patchAPK(String in, String out, String pkg) { diff --git a/app/src/main/java/com/topjohnwu/magisk/App.kt b/app/src/main/java/com/topjohnwu/magisk/App.kt index 0569ac57a..3603a01ea 100644 --- a/app/src/main/java/com/topjohnwu/magisk/App.kt +++ b/app/src/main/java/com/topjohnwu/magisk/App.kt @@ -11,11 +11,15 @@ import androidx.work.impl.WorkDatabase import androidx.work.impl.WorkDatabase_Impl import com.topjohnwu.magisk.data.database.RepoDatabase import com.topjohnwu.magisk.data.database.RepoDatabase_Impl +import com.topjohnwu.magisk.data.database.SuLogDatabase +import com.topjohnwu.magisk.data.database.SuLogDatabase_Impl import com.topjohnwu.magisk.di.ActivityTracker import com.topjohnwu.magisk.di.koinModules import com.topjohnwu.magisk.extensions.get import com.topjohnwu.magisk.extensions.unwrap import com.topjohnwu.magisk.utils.RootInit +import com.topjohnwu.magisk.utils.SuHandler +import com.topjohnwu.magisk.utils.updateConfig import com.topjohnwu.superuser.Shell import org.koin.android.ext.koin.androidContext import org.koin.core.context.startKoin @@ -33,10 +37,12 @@ open class App() : Application() { Shell.Config.verboseLogging(BuildConfig.DEBUG) Shell.Config.addInitializers(RootInit::class.java) Shell.Config.setTimeout(2) + FileProvider.callHandler = SuHandler Room.setFactory { when (it) { WorkDatabase::class.java -> WorkDatabase_Impl() RepoDatabase::class.java -> RepoDatabase_Impl() + SuLogDatabase::class.java -> SuLogDatabase_Impl() else -> null } } @@ -58,15 +64,15 @@ open class App() : Application() { app = this impl = base } - ResourceMgr.init(impl) - super.attachBaseContext(impl.wrap()) + val wrapped = impl.wrap() + super.attachBaseContext(wrapped) // Normal startup startKoin { - androidContext(baseContext) + androidContext(wrapped) modules(koinModules) } - ResourceMgr.reload() + ResourceMgr.init(impl) app.registerActivityLifecycleCallbacks(get()) WorkManager.initialize(impl.wrapJob(), androidx.work.Configuration.Builder().build()) } @@ -77,7 +83,7 @@ open class App() : Application() { } override fun onConfigurationChanged(newConfig: Configuration) { - ResourceMgr.reload(newConfig) + resources.updateConfig(newConfig) if (!isRunningAsStub) super.onConfigurationChanged(newConfig) } diff --git a/app/src/main/java/com/topjohnwu/magisk/Config.kt b/app/src/main/java/com/topjohnwu/magisk/Config.kt index 2d159e1e1..cd5b229f7 100644 --- a/app/src/main/java/com/topjohnwu/magisk/Config.kt +++ b/app/src/main/java/com/topjohnwu/magisk/Config.kt @@ -12,9 +12,8 @@ import com.topjohnwu.magisk.data.repository.DBConfig import com.topjohnwu.magisk.di.Protected import com.topjohnwu.magisk.extensions.get import com.topjohnwu.magisk.extensions.inject -import com.topjohnwu.magisk.extensions.packageName import com.topjohnwu.magisk.model.preference.PreferenceModel -import com.topjohnwu.magisk.utils.FingerprintHelper +import com.topjohnwu.magisk.utils.BiometricHelper import com.topjohnwu.magisk.utils.Utils import com.topjohnwu.superuser.Shell import com.topjohnwu.superuser.io.SuFile @@ -33,7 +32,7 @@ object Config : PreferenceModel, DBConfig { const val ROOT_ACCESS = "root_access" const val SU_MULTIUSER_MODE = "multiuser_mode" const val SU_MNT_NS = "mnt_ns" - const val SU_FINGERPRINT = "su_fingerprint" + const val SU_BIOMETRIC = "su_biometric" const val SU_MANAGER = "requester" const val KEYSTORE = "keystore" @@ -139,7 +138,7 @@ object Config : PreferenceModel, DBConfig { var rootMode by dbSettings(Key.ROOT_ACCESS, Value.ROOT_ACCESS_APPS_AND_ADB) 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) + var suBiometric by dbSettings(Key.SU_BIOMETRIC, false) var suManager by dbStrings(Key.SU_MANAGER, "", true) var keyStoreRaw by dbStrings(Key.KEYSTORE, "", true) @@ -147,9 +146,18 @@ object Config : PreferenceModel, DBConfig { val downloadDirectory get() = Utils.ensureDownloadPath(downloadPath) ?: get().getExternalFilesDir(null)!! - fun initialize() = prefs.edit { + private const val SU_FINGERPRINT = "su_fingerprint" + + fun initialize() = prefs.also { + if (it.getBoolean(SU_FINGERPRINT, false)) { + suBiometric = true + } + }.edit { parsePrefs(this) + // Legacy stuff + remove(SU_FINGERPRINT) + // Get actual state putBoolean(Key.COREONLY, Const.MAGISK_DISABLE_FILE.exists()) @@ -157,7 +165,7 @@ object Config : PreferenceModel, DBConfig { putString(Key.ROOT_ACCESS, rootMode.toString()) putString(Key.SU_MNT_NS, suMntNamespaceMode.toString()) putString(Key.SU_MULTIUSER_MODE, suMultiuserMode.toString()) - putBoolean(Key.SU_FINGERPRINT, FingerprintHelper.useFingerprint()) + putBoolean(Key.SU_BIOMETRIC, BiometricHelper.isEnabled) }.also { if (!prefs.contains(Key.UPDATE_CHANNEL)) prefs.edit().putString(Key.UPDATE_CHANNEL, defaultChannel.toString()).apply() @@ -166,7 +174,7 @@ object Config : PreferenceModel, DBConfig { private fun parsePrefs(editor: SharedPreferences.Editor) = editor.apply { val config = SuFile.open("/data/adb", Const.MANAGER_CONFIGS) if (config.exists()) runCatching { - val input = SuFileInputStream(config).buffered() + val input = SuFileInputStream(config) val parser = Xml.newPullParser() parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false) parser.setInput(input, "UTF-8") @@ -217,9 +225,10 @@ object Config : PreferenceModel, DBConfig { fun export() { // Flush prefs to disk prefs.edit().commit() + val context = get(Protected) val xml = File( - "${get(Protected).filesDir.parent}/shared_prefs", - "${packageName}_preferences.xml" + "${context.filesDir.parent}/shared_prefs", + "${context.packageName}_preferences.xml" ) Shell.su("cat $xml > /data/adb/${Const.MANAGER_CONFIGS}").exec() } diff --git a/app/src/main/java/com/topjohnwu/magisk/Const.kt b/app/src/main/java/com/topjohnwu/magisk/Const.kt index 3ea6121b9..63be1a2dc 100644 --- a/app/src/main/java/com/topjohnwu/magisk/Const.kt +++ b/app/src/main/java/com/topjohnwu/magisk/Const.kt @@ -23,8 +23,10 @@ object Const { val USER_ID = Process.myUid() / 100000 object Version { - const val MIN_SUPPORT = 18000 - const val CONNECT_MODE = 20002 + const val MIN_VERSION = "v18.0" + const val MIN_VERCODE = 18000 + const val CONNECT_MODE = 20100 + const val PROVIDER_CONNECT = 20102 } object ID { diff --git a/app/src/main/java/com/topjohnwu/magisk/Hacks.kt b/app/src/main/java/com/topjohnwu/magisk/Hacks.kt index c71e79eae..5437ec9d2 100644 --- a/app/src/main/java/com/topjohnwu/magisk/Hacks.kt +++ b/app/src/main/java/com/topjohnwu/magisk/Hacks.kt @@ -14,8 +14,7 @@ import android.content.res.AssetManager import android.content.res.Configuration import android.content.res.Resources import androidx.annotation.RequiresApi -import androidx.annotation.StringRes -import com.topjohnwu.magisk.extensions.langTagToLocale +import com.topjohnwu.magisk.extensions.forceGetDeclaredField import com.topjohnwu.magisk.model.download.DownloadService import com.topjohnwu.magisk.model.receiver.GeneralReceiver import com.topjohnwu.magisk.model.update.UpdateCheckService @@ -23,6 +22,8 @@ import com.topjohnwu.magisk.ui.MainActivity import com.topjohnwu.magisk.ui.SplashActivity import com.topjohnwu.magisk.ui.flash.FlashActivity import com.topjohnwu.magisk.ui.surequest.SuRequestActivity +import com.topjohnwu.magisk.utils.refreshLocale +import com.topjohnwu.magisk.utils.updateConfig import com.topjohnwu.magisk.utils.currentLocale import com.topjohnwu.magisk.utils.defaultLocale import java.util.* @@ -52,49 +53,22 @@ fun Context.wrapJob(): Context = object : GlobalResContext(this) { } } -// Override locale and inject resources from dynamic APK -private fun Resources.patch(config: Configuration = Configuration(configuration)): Resources { - config.setLocale(currentLocale) - updateConfiguration(config, displayMetrics) - if (isRunningAsStub) - assets.addAssetPath(ResourceMgr.resApk) - return this -} - -fun Class<*>.cmp(pkg: String = BuildConfig.APPLICATION_ID): ComponentName { +fun Class<*>.cmp(pkg: String): ComponentName { val name = ClassMap[this].name - return ComponentName(pkg, Info.stub?.componentMap?.get(name) ?: name) + return ComponentName(pkg, Info.stub?.classToComponent?.get(name) ?: name) } -fun Context.intent(c: Class<*>): Intent { - val cls = ClassMap[c] - return Info.stub?.let { - val className = it.componentMap.getOrElse(cls.name) { cls.name } - Intent().setComponent(ComponentName(this, className)) - } ?: Intent(this, cls) -} - -fun resolveRes(idx: Int): Int { - return Info.stub?.resourceMap?.get(idx) ?: when (idx) { - DynAPK.NOTIFICATION -> R.drawable.ic_magisk_outline - DynAPK.DOWNLOAD -> R.drawable.sc_cloud_download - DynAPK.SUPERUSER -> R.drawable.sc_superuser - DynAPK.MODULES -> R.drawable.sc_extension - DynAPK.MAGISKHIDE -> R.drawable.sc_magiskhide - else -> -1 - } -} +inline fun Context.intent() = Intent().setComponent(T::class.java.cmp(packageName)) private open class GlobalResContext(base: Context) : ContextWrapper(base) { open val mRes: Resources get() = ResourceMgr.resource - private val loader by lazy { javaClass.classLoader!! } override fun getResources(): Resources { return mRes } override fun getClassLoader(): ClassLoader { - return loader + return javaClass.classLoader!! } override fun createConfigurationContext(config: Configuration): Context { @@ -104,38 +78,31 @@ private open class GlobalResContext(base: Context) : ContextWrapper(base) { private class ResContext(base: Context) : GlobalResContext(base) { override val mRes by lazy { base.resources.patch() } + + private fun Resources.patch(): Resources { + updateConfig() + if (isRunningAsStub) + assets.addAssetPath(ResourceMgr.resApk) + return this + } } object ResourceMgr { - internal lateinit var resource: Resources - internal lateinit var resApk: String + lateinit var resource: Resources + lateinit var resApk: String fun init(context: Context) { resource = context.resources - if (isRunningAsStub) + refreshLocale() + if (isRunningAsStub) { resApk = DynAPK.current(context).path - } - - fun reload(config: Configuration = Configuration(resource.configuration)) { - val localeConfig = Config.locale - currentLocale = when { - localeConfig.isEmpty() -> defaultLocale - else -> localeConfig.langTagToLocale() + resource.assets.addAssetPath(resApk) } - Locale.setDefault(currentLocale) - resource.patch(config) } - - fun getString(locale: Locale, @StringRes id: Int): String { - val config = Configuration() - config.setLocale(locale) - return Resources(resource.assets, resource.displayMetrics, config).getString(id) - } - } -@RequiresApi(api = 28) +@RequiresApi(28) private class JobSchedulerWrapper(private val base: JobScheduler) : JobScheduler() { override fun schedule(job: JobInfo): Int { @@ -163,49 +130,15 @@ private class JobSchedulerWrapper(private val base: JobScheduler) : JobScheduler } private fun JobInfo.patch(): JobInfo { - // We need to patch the component of JobInfo to access WorkManager SystemJobService - + // We need to swap out the service of JobInfo val name = service.className val component = ComponentName( service.packageName, - Info.stub!!.componentMap[name] ?: name + Info.stub!!.classToComponent[name] ?: name ) - // Clone the JobInfo except component - val builder = JobInfo.Builder(id, component) - .setExtras(extras) - .setTransientExtras(transientExtras) - .setClipData(clipData, clipGrantFlags) - .setRequiredNetwork(requiredNetwork) - .setEstimatedNetworkBytes(estimatedNetworkDownloadBytes, estimatedNetworkUploadBytes) - .setRequiresCharging(isRequireCharging) - .setRequiresDeviceIdle(isRequireDeviceIdle) - .setRequiresBatteryNotLow(isRequireBatteryNotLow) - .setRequiresStorageNotLow(isRequireStorageNotLow) - .also { - triggerContentUris?.let { uris -> - for (uri in uris) - it.addTriggerContentUri(uri) - } - } - .setTriggerContentUpdateDelay(triggerContentUpdateDelay) - .setTriggerContentMaxDelay(triggerContentMaxDelay) - .setImportantWhileForeground(isImportantWhileForeground) - .setPrefetch(isPrefetch) - .setPersisted(isPersisted) - - if (isPeriodic) { - builder.setPeriodic(intervalMillis, flexMillis) - } else { - if (minLatencyMillis > 0) - builder.setMinimumLatency(minLatencyMillis) - if (maxExecutionDelayMillis > 0) - builder.setOverrideDeadline(maxExecutionDelayMillis) - } - if (!isRequireDeviceIdle) - builder.setBackoffCriteria(initialBackoffMillis, backoffPolicy) - - return builder.build() + javaClass.forceGetDeclaredField("service")?.set(this, component) + return this } } @@ -220,8 +153,9 @@ object ClassMap { GeneralReceiver::class.java to a.h::class.java, DownloadService::class.java to a.j::class.java, SuRequestActivity::class.java to a.m::class.java, + ProcessPhoenix::class.java to a.r::class.java, RedesignActivity::class.java to a.i::class.java ) - operator fun get(c: Class<*>) = map.getOrElse(c) { throw IllegalArgumentException() } + operator fun get(c: Class<*>) = map.getOrElse(c) { c } } diff --git a/app/src/main/java/com/topjohnwu/magisk/Info.kt b/app/src/main/java/com/topjohnwu/magisk/Info.kt index 3fa227b99..d1f7db086 100644 --- a/app/src/main/java/com/topjohnwu/magisk/Info.kt +++ b/app/src/main/java/com/topjohnwu/magisk/Info.kt @@ -40,24 +40,21 @@ object Info { val str = ShellUtils.fastCmd("magisk -v").split(":".toRegex())[0] val code = ShellUtils.fastCmd("magisk -V").toInt() val hide = Shell.su("magiskhide --status").exec().isSuccess - var mode = -1 - if (code >= Const.Version.CONNECT_MODE) { - mode = Shell.su("magisk --connect-mode").exec().code - if (mode == 0) { - // Manually trigger broadcast test - Shell.su("magisk --broadcast-test").exec() - } - } - Env(code, str, hide, mode) + Env(str, code, hide) }.getOrElse { Env() } class Env( - val magiskVersionCode: Int = -1, val magiskVersionString: String = "", - hide: Boolean = false, - var connectionMode: Int = -1 + code: Int = -1, + hide: Boolean = false ) { val magiskHide get() = Config.magiskHide + val magiskVersionCode = when (code) { + in Int.MIN_VALUE..Const.Version.MIN_VERCODE -> -1 + else -> if(Shell.rootAccess()) code else -1 + } + val isUnsupported = code > 0 && code < Const.Version.MIN_VERCODE + val isActive = magiskVersionCode >= 0 init { Config.magiskHide = hide diff --git a/app/src/main/java/com/topjohnwu/magisk/base/viewmodel/BaseViewModel.kt b/app/src/main/java/com/topjohnwu/magisk/base/viewmodel/BaseViewModel.kt index 13697e12b..8b938c7eb 100644 --- a/app/src/main/java/com/topjohnwu/magisk/base/viewmodel/BaseViewModel.kt +++ b/app/src/main/java/com/topjohnwu/magisk/base/viewmodel/BaseViewModel.kt @@ -1,6 +1,6 @@ package com.topjohnwu.magisk.base.viewmodel -import android.app.Activity +import com.topjohnwu.magisk.base.BaseActivity import com.topjohnwu.magisk.extensions.doOnSubscribeUi import com.topjohnwu.magisk.model.events.BackPressEvent import com.topjohnwu.magisk.model.events.PermissionEvent @@ -17,7 +17,7 @@ abstract class BaseViewModel( val isConnected = Observer(gIsConnected) { gIsConnected.value } - fun withView(action: Activity.() -> Unit) { + fun withView(action: BaseActivity<*, *>.() -> Unit) { ViewActionEvent(action).publish() } diff --git a/app/src/main/java/com/topjohnwu/magisk/data/database/LogDao.kt b/app/src/main/java/com/topjohnwu/magisk/data/database/LogDao.kt deleted file mode 100644 index 5e6bfb782..000000000 --- a/app/src/main/java/com/topjohnwu/magisk/data/database/LogDao.kt +++ /dev/null @@ -1,33 +0,0 @@ -package com.topjohnwu.magisk.data.database - -import com.topjohnwu.magisk.data.database.base.* -import com.topjohnwu.magisk.model.entity.MagiskLog -import com.topjohnwu.magisk.model.entity.toLog -import com.topjohnwu.magisk.model.entity.toMap -import java.util.concurrent.TimeUnit - -class LogDao : BaseDao() { - - override val table = DatabaseDefinition.Table.LOG - - fun deleteOutdated( - suTimeout: Long = TimeUnit.DAYS.toMillis(14) - ) = query { - condition { - lessThan("time", suTimeout.toString()) - } - }.ignoreElement() - - fun deleteAll() = query {}.ignoreElement() - - fun fetchAll() = query