Merge remote-tracking branch 'john/master' into feature/redesign

# Conflicts:
#	app/build.gradle
#	app/src/main/java/com/topjohnwu/magisk/Hacks.kt
#	app/src/main/java/com/topjohnwu/magisk/data/database/RepoDatabase.kt
#	app/src/main/java/com/topjohnwu/magisk/data/repository/LogRepository.kt
#	app/src/main/java/com/topjohnwu/magisk/di/DatabaseModule.kt
#	app/src/main/java/com/topjohnwu/magisk/extensions/RxJava.kt
#	app/src/main/java/com/topjohnwu/magisk/extensions/XAndroid.kt
#	app/src/main/java/com/topjohnwu/magisk/extensions/XJava.kt
#	app/src/main/java/com/topjohnwu/magisk/model/download/RemoteFileService.kt
#	app/src/main/java/com/topjohnwu/magisk/model/entity/recycler/LogRvItem.kt
#	app/src/main/java/com/topjohnwu/magisk/model/events/ViewEvents.kt
#	app/src/main/java/com/topjohnwu/magisk/ui/SplashActivity.kt
#	app/src/main/res/xml/app_settings.xml
This commit is contained in:
Viktor De Pasquale 2019-11-21 17:46:59 +01:00
commit 4f0e1c6c61
172 changed files with 2899 additions and 3184 deletions

View File

@ -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'

View File

@ -1,45 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
** Special Requirements **
This AndroidManifest.xml will be copied into the stub
APK to allow APK delegation. This is why these special
requirements exist.
* Class names *
Class names a.a, a.c, a.e should not be changed as they are used
externally. All other class names can be changed.
* Resource IDs *
All resource IDs referred in AndroidManifest.xml is required to be
included into the "shared" module to make the ID match with stub.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.topjohnwu.magisk">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
<application
android:name="a.e"
android:appComponentFactory="a.a"
android:allowBackup="true"
android:usesCleartextTraffic="true"
tools:ignore="UnusedAttribute,GoogleAppIndexingWarning"
tools:replace="android:appComponentFactory">
tools:ignore="UnusedAttribute,GoogleAppIndexingWarning">
<!-- Splash -->
<activity
android:name="a.c"
android:configChanges="orientation|screenSize"
android:theme="@style/SplashTheme">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@ -48,11 +24,7 @@
</activity>
<!-- Main -->
<activity
android:name="a.b"
android:configChanges="orientation|screenSize"
android:exported="true" />
<activity android:name="a.b" />
<activity
android:name="a.i"
@ -69,29 +41,28 @@
</activity-alias>
<!-- Flashing -->
<activity
android:name="a.f"
android:configChanges="keyboardHidden|orientation|screenSize"
android:screenOrientation="nosensor" />
<activity android:name="a.f" />
<!-- Superuser -->
<activity
android:name="a.m"
android:theme="@android:style/Theme.Translucent.NoTitleBar"
android:directBootAware="true"
android:excludeFromRecents="true"
android:exported="false" />
android:exported="false"
android:theme="@android:style/Theme.Translucent.NoTitleBar"
tools:ignore="AppLinkUrlError">
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
<!-- Receiver -->
<receiver
android:name="a.h"
android:directBootAware="true">
<intent-filter>
<action android:name="android.intent.action.REBOOT" />
<action android:name="android.intent.action.BOOT_COMPLETED" />
<action android:name="android.intent.action.LOCALE_CHANGED" />
</intent-filter>
<intent-filter>
@ -103,10 +74,7 @@
</receiver>
<!-- DownloadService -->
<service
android:name="a.j"
android:exported="false" />
<service android:name="a.j" />
<!-- Hardcode GMS version -->
<meta-data
@ -119,6 +87,16 @@
android:authorities="${applicationId}.workmanager-init"
tools:node="remove" />
<!-- We don't invalidate Room -->
<service
android:name="androidx.room.MultiInstanceInvalidationService"
tools:node="remove"/>
<!-- We don't use Device Credentials -->
<activity
android:name="androidx.biometric.DeviceCredentialHandlerActivity"
tools:node="remove" />
</application>
</manifest>

View File

@ -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) {

View File

@ -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<ActivityTracker>())
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)
}

View File

@ -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<Context>().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<Context>(Protected)
val xml = File(
"${get<Context>(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()
}

View File

@ -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 {

View File

@ -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 <reified T> 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 }
}

View File

@ -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

View File

@ -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()
}

View File

@ -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<Delete> {
condition {
lessThan("time", suTimeout.toString())
}
}.ignoreElement()
fun deleteAll() = query<Delete> {}.ignoreElement()
fun fetchAll() = query<Select> {
orderBy("time", Order.DESC)
}.flattenAsFlowable { it }
.map { it.toLog() }
.toList()
fun put(log: MagiskLog) = query<Insert> {
values(log.toMap())
}.ignoreElement()
}

View File

@ -3,7 +3,10 @@ package com.topjohnwu.magisk.data.database
import android.content.Context
import android.content.pm.PackageManager
import com.topjohnwu.magisk.Const
import com.topjohnwu.magisk.data.database.base.*
import com.topjohnwu.magisk.data.database.magiskdb.Delete
import com.topjohnwu.magisk.data.database.magiskdb.BaseDao
import com.topjohnwu.magisk.data.database.magiskdb.Replace
import com.topjohnwu.magisk.data.database.magiskdb.Select
import com.topjohnwu.magisk.extensions.now
import com.topjohnwu.magisk.model.entity.MagiskPolicy
import com.topjohnwu.magisk.model.entity.toMap
@ -16,7 +19,7 @@ class PolicyDao(
private val context: Context
) : BaseDao() {
override val table: String = DatabaseDefinition.Table.POLICY
override val table: String = Table.POLICY
fun deleteOutdated(
nowSeconds: Long = TimeUnit.MILLISECONDS.toSeconds(now)
@ -72,4 +75,4 @@ class PolicyDao(
}
}
}
}

View File

@ -4,8 +4,14 @@ import androidx.room.*
import com.topjohnwu.magisk.Config
import com.topjohnwu.magisk.model.entity.module.Repo
@Database(version = 6, entities = [Repo::class, RepoEtag::class])
abstract class RepoDatabase : RoomDatabase() {
abstract fun repoDao() : RepoDao
}
@Dao
abstract class RepoDao {
abstract class RepoDao(private val db: RepoDatabase) {
val repoIDList get() = getRepoID().map { it.id }
@ -15,13 +21,10 @@ abstract class RepoDao {
}
var etagKey: String
set(etag) = addEtagRaw(RepoEtag(0, etag))
set(value) = addEtagRaw(RepoEtag(0, value))
get() = etagRaw()?.key.orEmpty()
fun clear() {
clearRepos()
clearEtag()
}
fun clear() = db.clearAllTables()
@Query("SELECT * FROM repos ORDER BY last_update DESC")
protected abstract fun getReposDateOrder(): List<Repo>
@ -52,12 +55,6 @@ abstract class RepoDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
protected abstract fun addEtagRaw(etag: RepoEtag)
@Query("DELETE FROM repos")
protected abstract fun clearRepos()
@Query("DELETE FROM etag")
protected abstract fun clearEtag()
}
data class RepoID(

View File

@ -1,10 +1,13 @@
package com.topjohnwu.magisk.data.database
import com.topjohnwu.magisk.data.database.base.*
import com.topjohnwu.magisk.data.database.magiskdb.Delete
import com.topjohnwu.magisk.data.database.magiskdb.BaseDao
import com.topjohnwu.magisk.data.database.magiskdb.Replace
import com.topjohnwu.magisk.data.database.magiskdb.Select
class SettingsDao : BaseDao() {
override val table = DatabaseDefinition.Table.SETTINGS
override val table = Table.SETTINGS
fun delete(key: String) = query<Delete> {
condition { equals("key", key) }
@ -19,4 +22,4 @@ class SettingsDao : BaseDao() {
condition { equals("key", key) }
}.map { it.firstOrNull()?.values?.firstOrNull()?.toIntOrNull() ?: default }
}
}

View File

@ -1,10 +1,13 @@
package com.topjohnwu.magisk.data.database
import com.topjohnwu.magisk.data.database.base.*
import com.topjohnwu.magisk.data.database.magiskdb.Delete
import com.topjohnwu.magisk.data.database.magiskdb.BaseDao
import com.topjohnwu.magisk.data.database.magiskdb.Replace
import com.topjohnwu.magisk.data.database.magiskdb.Select
class StringDao : BaseDao() {
override val table = DatabaseDefinition.Table.STRINGS
override val table = Table.STRINGS
fun delete(key: String) = query<Delete> {
condition { equals("key", key) }
@ -19,4 +22,4 @@ class StringDao : BaseDao() {
condition { equals("key", key) }
}.map { it.firstOrNull()?.values?.firstOrNull() ?: default }
}
}

View File

@ -0,0 +1,36 @@
package com.topjohnwu.magisk.data.database
import androidx.room.*
import com.topjohnwu.magisk.model.entity.MagiskLog
import io.reactivex.Completable
import io.reactivex.Single
import java.util.*
@Database(version = 1, entities = [MagiskLog::class])
abstract class SuLogDatabase : RoomDatabase() {
abstract fun suLogDao(): SuLogDao
}
@Dao
abstract class SuLogDao(private val db: SuLogDatabase) {
private val twoWeeksAgo =
Calendar.getInstance().apply { add(Calendar.WEEK_OF_YEAR, -2) }.timeInMillis
fun deleteAll() = Completable.fromAction { db.clearAllTables() }
fun fetchAll() = deleteOutdated().andThen(fetch())
@Query("SELECT * FROM logs ORDER BY time DESC")
protected abstract fun fetch(): Single<MutableList<MagiskLog>>
@Insert
abstract fun insert(log: MagiskLog): Completable
@Query("DELETE FROM logs WHERE time < :timeout")
protected abstract fun deleteOutdated(
timeout: Long = twoWeeksAgo
): Completable
}

View File

@ -1,15 +0,0 @@
package com.topjohnwu.magisk.data.database.base
abstract class BaseDao {
abstract val table: String
inline fun <reified Builder : MagiskQueryBuilder> query(builder: Builder.() -> Unit) =
Builder::class.java.newInstance()
.apply { table = this@BaseDao.table }
.apply(builder)
.toString()
.let { MagiskQuery(it) }
.query()
}

View File

@ -1,30 +0,0 @@
package com.topjohnwu.magisk.data.database.base
import androidx.annotation.AnyThread
import com.topjohnwu.superuser.Shell
import io.reactivex.Single
object DatabaseDefinition {
object Table {
const val POLICY = "policies"
const val LOG = "logs"
const val SETTINGS = "settings"
const val STRINGS = "strings"
}
}
@AnyThread
fun MagiskQuery.query() = query.su()
fun String.suRaw() = Single.fromCallable { Shell.su(this).exec().out }
fun String.su() = suRaw().map { it.toMap() }
fun List<String>.toMap() = map { it.split(Regex("\\|")) }
.map { it.toMapInternal() }
private fun List<String>.toMapInternal() = map { it.split("=", limit = 2) }
.filter { it.size == 2 }
.map { Pair(it[0], it[1]) }
.toMap()

View File

@ -1,5 +0,0 @@
package com.topjohnwu.magisk.data.database.base
inline class MagiskQuery(private val _query: String) {
val query get() = "magisk --sqlite '$_query'"
}

View File

@ -0,0 +1,44 @@
package com.topjohnwu.magisk.data.database.magiskdb
import androidx.annotation.StringDef
import com.topjohnwu.superuser.Shell
import io.reactivex.Single
abstract class BaseDao {
object Table {
const val POLICY = "policies"
const val LOG = "logs"
const val SETTINGS = "settings"
const val STRINGS = "strings"
}
@StringDef(Table.POLICY, Table.LOG, Table.SETTINGS, Table.STRINGS)
@Retention(AnnotationRetention.SOURCE)
annotation class TableStrict
@TableStrict
abstract val table: String
inline fun <reified Builder : Query.Builder> query(builder: Builder.() -> Unit = {}) =
Builder::class.java.newInstance()
.apply { table = this@BaseDao.table }
.apply(builder)
.toString()
.let { Query(it) }
.query()
}
fun Query.query() = query.su()
private fun String.suRaw() = Single.fromCallable { Shell.su(this).exec().out }
private fun String.su() = suRaw().map { it.toMap() }
private fun List<String>.toMap() = map { it.split(Regex("\\|")) }
.map { it.toMapInternal() }
private fun List<String>.toMapInternal() = map { it.split("=", limit = 2) }
.filter { it.size == 2 }
.map { Pair(it[0], it[1]) }
.toMap()

View File

@ -1,27 +1,17 @@
package com.topjohnwu.magisk.data.database.base
package com.topjohnwu.magisk.data.database.magiskdb
import androidx.annotation.StringDef
import com.topjohnwu.magisk.data.database.base.Order.Companion.ASC
import com.topjohnwu.magisk.data.database.base.Order.Companion.DESC
interface MagiskQueryBuilder {
class Query(private val _query: String) {
val query get() = "magisk --sqlite '$_query'"
val requestType: String
var table: String
companion object {
inline operator fun <reified Builder : MagiskQueryBuilder> invoke(builder: Builder.() -> Unit): MagiskQuery =
Builder::class.java.newInstance()
.apply(builder)
.toString()
.let {
MagiskQuery(it)
}
interface Builder {
val requestType: String
var table: String
}
}
class Delete : MagiskQueryBuilder {
class Delete : Query.Builder {
override val requestType: String = "DELETE FROM"
override var table = ""
@ -36,7 +26,7 @@ class Delete : MagiskQueryBuilder {
}
}
class Select : MagiskQueryBuilder {
class Select : Query.Builder {
override val requestType: String get() = "SELECT $fields FROM"
override lateinit var table: String
@ -69,7 +59,7 @@ class Replace : Insert() {
override val requestType: String = "REPLACE INTO"
}
open class Insert : MagiskQueryBuilder {
open class Insert : Query.Builder {
override val requestType: String = "INSERT INTO"
override lateinit var table: String
@ -137,19 +127,11 @@ class Condition {
}
}
class Order {
@set:OrderStrict
var order = DESC
var field = ""
companion object {
const val ASC = "ASC"
const val DESC = "DESC"
}
object Order {
const val ASC = "ASC"
const val DESC = "DESC"
}
@StringDef(ASC, DESC)
@StringDef(Order.ASC, Order.DESC)
@Retention(AnnotationRetention.SOURCE)
annotation class OrderStrict
annotation class OrderStrict

View File

@ -29,8 +29,8 @@ interface DBConfig {
}
class DBSettingsValue(
private val name: String,
private val default: Int
private val name: String,
private val default: Int
) : ReadWriteProperty<DBConfig, Int> {
private var value: Int? = null
@ -47,29 +47,29 @@ class DBSettingsValue(
this.value = value
}
thisRef.settingsDao.put(name, value)
.subscribeOn(Schedulers.io())
.subscribe()
.subscribeOn(Schedulers.io())
.subscribe()
}
}
class DBBoolSettings(
name: String,
default: Boolean
name: String,
default: Boolean
) : ReadWriteProperty<DBConfig, Boolean> {
val base = DBSettingsValue(name, if (default) 1 else 0)
override fun getValue(thisRef: DBConfig, property: KProperty<*>): Boolean
= base.getValue(thisRef, property) != 0
override fun getValue(thisRef: DBConfig, property: KProperty<*>): Boolean =
base.getValue(thisRef, property) != 0
override fun setValue(thisRef: DBConfig, property: KProperty<*>, value: Boolean) =
base.setValue(thisRef, property, if (value) 1 else 0)
base.setValue(thisRef, property, if (value) 1 else 0)
}
class DBStringsValue(
private val name: String,
private val default: String,
private val sync: Boolean
private val name: String,
private val default: String,
private val sync: Boolean
) : ReadWriteProperty<DBConfig, String> {
private var value: String? = null
@ -90,16 +90,16 @@ class DBStringsValue(
thisRef.stringDao.delete(name).blockingAwait()
} else {
thisRef.stringDao.delete(name)
.subscribeOn(Schedulers.io())
.subscribe()
.subscribeOn(Schedulers.io())
.subscribe()
}
} else {
if (sync) {
thisRef.stringDao.put(name, value).blockingAwait()
} else {
thisRef.stringDao.put(name, value)
.subscribeOn(Schedulers.io())
.subscribe()
.subscribeOn(Schedulers.io())
.subscribe()
}
}
}

View File

@ -1,42 +1,40 @@
package com.topjohnwu.magisk.data.repository
import com.topjohnwu.magisk.Const
import com.topjohnwu.magisk.data.database.LogDao
import com.topjohnwu.magisk.data.database.base.suRaw
import com.topjohnwu.magisk.extensions.toSingle
import com.topjohnwu.magisk.data.database.SuLogDao
import com.topjohnwu.magisk.model.entity.MagiskLog
import com.topjohnwu.magisk.model.entity.WrappedMagiskLog
import com.topjohnwu.superuser.Shell
import io.reactivex.Completable
import io.reactivex.Single
import java.util.concurrent.TimeUnit
class LogRepository(
private val logDao: LogDao
private val logDao: SuLogDao
) {
fun fetchLogsNowrap() = logDao.fetchAll()
.map { it.sortedByDescending { it.date.time } }
fun fetchLogs() = fetchLogsNowrap()
.map { it.wrap() }
fun fetchLogs() = fetchLogsNowrap().map { it.wrap() }
fun fetchMagiskLogs() = "tail -n 5000 ${Const.MAGISK_LOG}".suRaw()
.filter { it.isNotEmpty() }
fun fetchMagiskLogs() = Single.fromCallable {
Shell.su("tail -n 5000 ${Const.MAGISK_LOG}").exec().out
}.flattenAsFlowable { it }.filter { it.isNotEmpty() }
fun clearLogs() = logDao.deleteAll()
fun clearOutdated() = logDao.deleteOutdated()
fun clearMagiskLogs() = Shell.su("echo -n > " + Const.MAGISK_LOG)
.toSingle()
.map { it.exec() }
fun clearMagiskLogs() = Completable.fromAction {
Shell.su("echo -n > ${Const.MAGISK_LOG}").exec()
}
fun put(log: MagiskLog) = logDao.put(log)
fun insert(log: MagiskLog) = logDao.insert(log)
private fun List<MagiskLog>.wrap(): List<WrappedMagiskLog> {
val day = TimeUnit.DAYS.toMillis(1)
return groupBy { it.date.time / day }
return groupBy { it.time / day }
.map { WrappedMagiskLog(it.key * day, it.value) }
}
}
}

View File

@ -3,7 +3,6 @@ package com.topjohnwu.magisk.data.repository
import android.content.pm.PackageManager
import com.topjohnwu.magisk.Config
import com.topjohnwu.magisk.Info
import com.topjohnwu.magisk.data.database.base.su
import com.topjohnwu.magisk.data.network.GithubRawServices
import com.topjohnwu.magisk.extensions.getLabel
import com.topjohnwu.magisk.extensions.packageName
@ -57,7 +56,7 @@ class MagiskRepository(
.toList()
fun toggleHide(isEnabled: Boolean, packageName: String, process: String) =
"magiskhide --%s %s %s".format(isEnabled.state, packageName, process).su().ignoreElement()
Shell.su("magiskhide --${isEnabled.state} $packageName $process").submit()
private val Boolean.state get() = if (this) "add" else "rm"

View File

@ -8,7 +8,6 @@ import org.koin.dsl.module
val databaseModule = module {
single { LogDao() }
single { PolicyDao(get()) }
single { SettingsDao() }
single { StringDao() }
@ -16,10 +15,16 @@ val databaseModule = module {
single { get<RepoDatabase>().repoDao() }
single { get<RepoDatabase>().repoByNameDao() }
single { get<RepoDatabase>().repoByUpdatedDao() }
single { createSuLogDatabase(get(Protected)).suLogDao() }
single { RepoUpdater(get(), get()) }
}
fun createRepoDatabase(context: Context) =
Room.databaseBuilder(context, RepoDatabase::class.java, "repo.db")
.fallbackToDestructiveMigration()
.build()
Room.databaseBuilder(context, RepoDatabase::class.java, "repo.db")
.fallbackToDestructiveMigration()
.build()
fun createSuLogDatabase(context: Context) =
Room.databaseBuilder(context, SuLogDatabase::class.java, "sulogs.db")
.fallbackToDestructiveMigration()
.build()

View File

@ -1,6 +0,0 @@
package com.topjohnwu.magisk.extensions
import android.os.Handler
import android.os.Looper
fun ui(body: () -> Unit) = Handler(Looper.getMainLooper()).post(body)

View File

@ -2,6 +2,7 @@ package com.topjohnwu.magisk.extensions
import androidx.databinding.ObservableField
import com.topjohnwu.magisk.utils.KObservableField
import com.topjohnwu.superuser.internal.UiThreadHandler
import io.reactivex.*
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposables
@ -53,14 +54,14 @@ fun <T> Single<T>.subscribeK(
onError: OnErrorListener = { it.printStackTrace() },
onNext: OnSuccessListener<T> = {}
) = applySchedulers()
.subscribe(onNext, onError)
.subscribe(onSuccess, onError)
fun <T> Maybe<T>.subscribeK(
onError: OnErrorListener = { it.printStackTrace() },
onComplete: OnCompleteListener = {},
onNext: OnSuccessListener<T> = {}
onSuccess: OnSuccessListener<T> = {}
) = applySchedulers()
.subscribe(onNext, onError, onComplete)
.subscribe(onSuccess, onError, onComplete)
fun <T> Flowable<T>.subscribeK(
onError: OnErrorListener = { it.printStackTrace() },
@ -104,54 +105,54 @@ fun Completable.updateBy(
fun <T> Observable<T>.doOnSubscribeUi(body: () -> Unit) =
doOnSubscribe { ui { body() } }
doOnSubscribe { UiThreadHandler.run { body() } }
fun <T> Single<T>.doOnSubscribeUi(body: () -> Unit) =
doOnSubscribe { ui { body() } }
doOnSubscribe { UiThreadHandler.run { body() } }
fun <T> Maybe<T>.doOnSubscribeUi(body: () -> Unit) =
doOnSubscribe { ui { body() } }
doOnSubscribe { UiThreadHandler.run { body() } }
fun <T> Flowable<T>.doOnSubscribeUi(body: () -> Unit) =
doOnSubscribe { ui { body() } }
doOnSubscribe { UiThreadHandler.run { body() } }
fun Completable.doOnSubscribeUi(body: () -> Unit) =
doOnSubscribe { ui { body() } }
doOnSubscribe { UiThreadHandler.run { body() } }
fun <T> Observable<T>.doOnErrorUi(body: (Throwable) -> Unit) =
doOnError { ui { body(it) } }
doOnError { UiThreadHandler.run { body(it) } }
fun <T> Single<T>.doOnErrorUi(body: (Throwable) -> Unit) =
doOnError { ui { body(it) } }
doOnError { UiThreadHandler.run { body(it) } }
fun <T> Maybe<T>.doOnErrorUi(body: (Throwable) -> Unit) =
doOnError { ui { body(it) } }
doOnError { UiThreadHandler.run { body(it) } }
fun <T> Flowable<T>.doOnErrorUi(body: (Throwable) -> Unit) =
doOnError { ui { body(it) } }
doOnError { UiThreadHandler.run { body(it) } }
fun Completable.doOnErrorUi(body: (Throwable) -> Unit) =
doOnError { ui { body(it) } }
doOnError { UiThreadHandler.run { body(it) } }
fun <T> Observable<T>.doOnNextUi(body: (T) -> Unit) =
doOnNext { ui { body(it) } }
doOnNext { UiThreadHandler.run { body(it) } }
fun <T> Flowable<T>.doOnNextUi(body: (T) -> Unit) =
doOnNext { ui { body(it) } }
doOnNext { UiThreadHandler.run { body(it) } }
fun <T> Single<T>.doOnSuccessUi(body: (T) -> Unit) =
doOnSuccess { ui { body(it) } }
doOnSuccess { UiThreadHandler.run { body(it) } }
fun <T> Maybe<T>.doOnSuccessUi(body: (T) -> Unit) =
doOnSuccess { ui { body(it) } }
doOnSuccess { UiThreadHandler.run { body(it) } }
fun <T> Maybe<T>.doOnCompleteUi(body: () -> Unit) =
doOnComplete { ui { body() } }
doOnComplete { UiThreadHandler.run { body() } }
fun Completable.doOnCompleteUi(body: () -> Unit) =
doOnComplete { ui { body() } }
doOnComplete { UiThreadHandler.run { body() } }
fun <T, R> Observable<List<T>>.mapList(

View File

@ -12,13 +12,21 @@ import android.content.pm.PackageManager.*
import android.content.res.Configuration
import android.content.res.Resources
import android.database.Cursor
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.drawable.AdaptiveIconDrawable
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.LayerDrawable
import android.net.Uri
import android.os.Build
import android.os.Build.VERSION.SDK_INT
import android.provider.OpenableColumns
import android.view.View
import androidx.annotation.ColorRes
import androidx.annotation.DrawableRes
import androidx.appcompat.content.res.AppCompatResources
import androidx.core.content.ContextCompat
import androidx.core.net.toFile
import androidx.core.net.toUri
import com.topjohnwu.magisk.Const
import com.topjohnwu.magisk.FileProvider
@ -100,6 +108,23 @@ fun Context.rawResource(id: Int) = resources.openRawResource(id)
fun Context.readUri(uri: Uri) =
contentResolver.openInputStream(uri) ?: throw FileNotFoundException()
fun Context.getBitmap(id: Int): Bitmap {
var drawable = AppCompatResources.getDrawable(this, id)!!
if (drawable is BitmapDrawable)
return drawable.bitmap
if (SDK_INT >= 26 && drawable is AdaptiveIconDrawable) {
drawable = LayerDrawable(arrayOf(drawable.background, drawable.foreground))
}
val bitmap = Bitmap.createBitmap(
drawable.intrinsicWidth, drawable.intrinsicHeight,
Bitmap.Config.ARGB_8888
)
val canvas = Canvas(bitmap)
drawable.setBounds(0, 0, canvas.width, canvas.height)
drawable.draw(canvas)
return bitmap
}
fun Intent.startActivity(context: Context) = context.startActivity(this)
fun Intent.startActivityWithRoot() {
@ -286,6 +311,8 @@ fun Context.unwrap(): Context {
return context
}
fun Uri.writeTo(file: File) = toFile().copyTo(file)
fun Context.hasPermissions(vararg permissions: String) = permissions.all {
ContextCompat.checkSelfPermission(this, it) == PERMISSION_GRANTED
}

View File

@ -1,12 +1,13 @@
package com.topjohnwu.magisk.extensions
import android.net.Uri
import android.os.Build
import androidx.core.net.toFile
import timber.log.Timber
import java.io.File
import java.io.InputStream
import java.io.OutputStream
import java.lang.reflect.Field
import java.lang.reflect.Method
import java.text.SimpleDateFormat
import java.util.*
import java.util.zip.ZipEntry
@ -21,8 +22,6 @@ fun ZipInputStream.forEach(callback: (ZipEntry) -> Unit) {
}
}
fun Uri.writeTo(file: File) = toFile().copyTo(file)
fun InputStream.writeTo(file: File) =
withStreams(this, file.outputStream()) { reader, writer -> reader.copyTo(writer) }
@ -105,4 +104,33 @@ fun Locale.toLangTag(): String {
}
fun SimpleDateFormat.parseOrNull(date: String) =
runCatching { parse(date) }.onFailure { Timber.e(it) }.getOrNull()
runCatching { parse(date) }.onFailure { Timber.e(it) }.getOrNull()
// Reflection hacks
private val loadClass = ClassLoader::class.java.getMethod("loadClass", String::class.java)
private val getDeclaredMethod = Class::class.java.getMethod("getDeclaredMethod",
String::class.java, arrayOf<Class<*>>()::class.java)
private val getDeclaredField = Class::class.java.getMethod("getDeclaredField", String::class.java)
fun ClassLoader.forceLoadClass(name: String) =
runCatching { loadClass.invoke(this, name) }.getOrNull() as Class<*>?
fun Class<*>.forceGetDeclaredMethod(name: String, vararg types: Class<*>) =
(runCatching { getDeclaredMethod.invoke(this, name, types) }.getOrNull() as Method?)?.also {
it.isAccessible = true
}
fun Class<*>.forceGetDeclaredField(name: String) =
(runCatching { getDeclaredField.invoke(this, name) }.getOrNull() as Field?)?.also {
it.isAccessible = true
}
inline fun <reified T> T.forceGetClass(name: String) =
T::class.java.classLoader?.forceLoadClass(name)
fun Class<*>.forceGetField(name: String): Field? =
forceGetDeclaredField(name) ?: superclass?.forceGetField(name)
fun Class<*>.forceGetMethod(name: String, vararg types: Class<*>): Method? =
forceGetDeclaredMethod(name, *types) ?: superclass?.forceGetMethod(name, *types)

View File

@ -1,12 +1,12 @@
package com.topjohnwu.magisk.model.download
import android.annotation.SuppressLint
import android.app.Notification
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.os.Build
import android.webkit.MimeTypeMap
import androidx.core.app.NotificationCompat
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.extensions.chooser
import com.topjohnwu.magisk.extensions.exists
@ -69,14 +69,14 @@ open class DownloadService : RemoteFileService() {
// ---
override fun NotificationCompat.Builder.addActions(subject: DownloadSubject)
override fun Notification.Builder.addActions(subject: DownloadSubject)
= when (subject) {
is Magisk -> addActionsInternal(subject)
is Module -> addActionsInternal(subject)
is Manager -> addActionsInternal(subject)
}
private fun NotificationCompat.Builder.addActionsInternal(subject: Magisk)
private fun Notification.Builder.addActionsInternal(subject: Magisk)
= when (val conf = subject.configuration) {
Download -> this.apply {
fileIntent(subject.file.parentFile!!)
@ -92,7 +92,7 @@ open class DownloadService : RemoteFileService() {
else -> this
}
private fun NotificationCompat.Builder.addActionsInternal(subject: Module)
private fun Notification.Builder.addActionsInternal(subject: Module)
= when (subject.configuration) {
Download -> this.apply {
fileIntent(subject.file.parentFile!!)
@ -106,19 +106,19 @@ open class DownloadService : RemoteFileService() {
else -> this
}
private fun NotificationCompat.Builder.addActionsInternal(subject: Manager)
private fun Notification.Builder.addActionsInternal(subject: Manager)
= when (subject.configuration) {
APK.Upgrade -> setContentIntent(APKInstall.installIntent(context, subject.file))
else -> this
}
@Suppress("ReplaceSingleLineLet")
private fun NotificationCompat.Builder.setContentIntent(intent: Intent) =
private fun Notification.Builder.setContentIntent(intent: Intent) =
PendingIntent.getActivity(context, nextInt(), intent, PendingIntent.FLAG_ONE_SHOT)
.let { setContentIntent(it) }
@Suppress("ReplaceSingleLineLet")
private fun NotificationCompat.Builder.addAction(icon: Int, title: Int, intent: Intent) =
private fun Notification.Builder.addAction(icon: Int, title: Int, intent: Intent) =
PendingIntent.getActivity(context, nextInt(), intent, PendingIntent.FLAG_ONE_SHOT)
.let { addAction(icon, getString(title), it) }
@ -140,7 +140,7 @@ open class DownloadService : RemoteFileService() {
inline operator fun invoke(context: Context, argBuilder: Builder.() -> Unit) {
val app = context.applicationContext
val builder = Builder().apply(argBuilder)
val intent = app.intent(DownloadService::class.java).putExtra(ARG_URL, builder.subject)
val intent = app.intent<DownloadService>().putExtra(ARG_URL, builder.subject)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
app.startForegroundService(intent)

View File

@ -38,7 +38,7 @@ private fun RemoteFileService.upgrade(apk: File, id: Int) {
patch(apk, id)
} else {
// Simply relaunch the app
ProcessPhoenix.triggerRebirth(this)
ProcessPhoenix.triggerRebirth(this, intent<ProcessPhoenix>())
}
} else {
patch(apk, id)

View File

@ -3,22 +3,20 @@ package com.topjohnwu.magisk.model.download
import android.app.Notification
import android.content.Intent
import android.os.IBinder
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import com.topjohnwu.magisk.base.BaseService
import com.topjohnwu.magisk.view.Notifications
import org.koin.core.KoinComponent
import java.util.*
import kotlin.random.Random.Default.nextInt
abstract class NotificationService : BaseService(), KoinComponent {
abstract val defaultNotification: NotificationCompat.Builder
abstract val defaultNotification: Notification.Builder
private val manager by lazy { NotificationManagerCompat.from(this) }
private val hasNotifications get() = notifications.isNotEmpty()
private val notifications =
Collections.synchronizedMap(mutableMapOf<Int, NotificationCompat.Builder>())
Collections.synchronizedMap(mutableMapOf<Int, Notification.Builder>())
override fun onTaskRemoved(rootIntent: Intent?) {
super.onTaskRemoved(rootIntent)
@ -30,7 +28,7 @@ abstract class NotificationService : BaseService(), KoinComponent {
fun update(
id: Int,
body: (NotificationCompat.Builder) -> Unit = {}
body: (Notification.Builder) -> Unit = {}
) {
val notification = notifications.getOrPut(id) { defaultNotification }
@ -43,7 +41,7 @@ abstract class NotificationService : BaseService(), KoinComponent {
protected fun finishNotify(
id: Int,
editBody: (NotificationCompat.Builder) -> NotificationCompat.Builder? = { null }
editBody: (Notification.Builder) -> Notification.Builder? = { null }
) : Int {
val currentNotification = remove(id)?.run(editBody)
@ -62,11 +60,11 @@ abstract class NotificationService : BaseService(), KoinComponent {
// ---
private fun notify(id: Int, notification: Notification) {
manager.notify(id, notification)
Notifications.mgr.notify(id, notification)
}
private fun cancel(id: Int) {
manager.cancel(id)
Notifications.mgr.cancel(id)
}
protected fun remove(id: Int) = notifications.remove(id).also {
@ -84,4 +82,4 @@ abstract class NotificationService : BaseService(), KoinComponent {
// --
override fun onBind(p0: Intent?): IBinder? = null
}
}

View File

@ -1,6 +1,7 @@
package com.topjohnwu.magisk.model.download
import android.app.Activity
import android.app.Notification
import android.content.Intent
import androidx.core.app.NotificationCompat
import androidx.lifecycle.LiveData
@ -27,7 +28,7 @@ abstract class RemoteFileService : NotificationService() {
val service: GithubRawServices by inject()
override val defaultNotification: NotificationCompat.Builder
override val defaultNotification
get() = Notifications.progress(this, "")
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
@ -117,8 +118,8 @@ abstract class RemoteFileService : NotificationService() {
@Throws(Throwable::class)
protected abstract fun onFinished(subject: DownloadSubject, id: Int)
protected abstract fun NotificationCompat.Builder.addActions(subject: DownloadSubject)
: NotificationCompat.Builder
protected abstract fun Notification.Builder.addActions(subject: DownloadSubject)
: Notification.Builder
companion object : KoinComponent {
const val ARG_URL = "arg_url"

View File

@ -1,10 +1,14 @@
package com.topjohnwu.magisk.model.entity
import androidx.room.Entity
import androidx.room.Ignore
import androidx.room.PrimaryKey
import com.topjohnwu.magisk.extensions.now
import com.topjohnwu.magisk.extensions.timeFormatTime
import com.topjohnwu.magisk.extensions.toTime
import com.topjohnwu.magisk.model.entity.MagiskPolicy.Companion.ALLOW
import java.util.*
@Entity(tableName = "logs")
data class MagiskLog(
val fromUid: Int,
val toUid: Int,
@ -13,9 +17,10 @@ data class MagiskLog(
val appName: String,
val command: String,
val action: Boolean,
val date: Date
val time: Long = -1
) {
val timeString = date.time.toTime(timeFormatTime)
@PrimaryKey(autoGenerate = true) var id: Int = 0
@Ignore val timeString = time.toTime(timeFormatTime)
}
data class WrappedMagiskLog(
@ -23,35 +28,8 @@ data class WrappedMagiskLog(
val items: List<MagiskLog>
)
fun Map<String, String>.toLog(): MagiskLog {
return MagiskLog(
fromUid = get("from_uid")?.toIntOrNull() ?: -1,
toUid = get("to_uid")?.toIntOrNull() ?: -1,
fromPid = get("from_pid")?.toIntOrNull() ?: -1,
packageName = get("package_name").orEmpty(),
appName = get("app_name").orEmpty(),
command = get("command").orEmpty(),
action = get("action")?.toIntOrNull() != 0,
date = get("time")?.toLongOrNull()?.toDate() ?: Date()
)
}
fun Long.toDate() = Date(this)
fun MagiskLog.toMap() = mapOf(
"from_uid" to fromUid,
"to_uid" to toUid,
"from_pid" to fromPid,
"package_name" to packageName,
"app_name" to appName,
"command" to command,
"action" to action,
"time" to date.time
)
fun MagiskPolicy.toLog(
toUid: Int,
fromPid: Int,
command: String,
date: Date
) = MagiskLog(uid, toUid, fromPid, packageName, appName, command, policy == ALLOW, date)
command: String
) = MagiskLog(uid, toUid, fromPid, packageName, appName, command, policy == ALLOW, now)

View File

@ -7,11 +7,11 @@ import com.topjohnwu.magisk.model.entity.MagiskPolicy.Companion.INTERACTIVE
data class MagiskPolicy(
val uid: Int,
var uid: Int,
val packageName: String,
val appName: String,
val policy: Int = INTERACTIVE,
val until: Long = -1L,
var policy: Int = INTERACTIVE,
var until: Long = -1L,
val logging: Boolean = true,
val notification: Boolean = true,
val applicationInfo: ApplicationInfo
@ -38,7 +38,7 @@ fun MagiskPolicy.toMap() = mapOf(
fun Map<String, String>.toPolicy(pm: PackageManager): MagiskPolicy {
val uid = get("uid")?.toIntOrNull() ?: -1
val packageName = get("package_name").orEmpty()
val info = pm.getApplicationInfo(packageName, 0)
val info = pm.getApplicationInfo(packageName, PackageManager.GET_UNINSTALLED_PACKAGES)
if (info.uid != uid)
throw PackageManager.NameNotFoundException()
@ -56,14 +56,15 @@ fun Map<String, String>.toPolicy(pm: PackageManager): MagiskPolicy {
}
@Throws(PackageManager.NameNotFoundException::class)
fun Int.toPolicy(pm: PackageManager): MagiskPolicy {
fun Int.toPolicy(pm: PackageManager, policy: Int = INTERACTIVE): MagiskPolicy {
val pkg = pm.getPackagesForUid(this)?.firstOrNull()
?: throw PackageManager.NameNotFoundException()
val info = pm.getApplicationInfo(pkg, 0)
val info = pm.getApplicationInfo(pkg, PackageManager.GET_UNINSTALLED_PACKAGES)
return MagiskPolicy(
uid = this,
uid = info.uid,
packageName = pkg,
policy = policy,
applicationInfo = info,
appName = info.loadLabel(pm).toString()
appName = info.getLabel(pm)
)
}
}

View File

@ -65,7 +65,7 @@ class Module(path: String) : BaseModule() {
val module = Module(Const.MAGISK_PATH + "/" + file.name)
moduleList.add(module)
}
return moduleList.sortedBy { it.name }
return moduleList.sortedBy { it.name.toLowerCase() }
}
}
}

View File

@ -40,8 +40,10 @@ class LogItemRvItem(
fun toggle() = isExpanded.toggle()
override fun contentSameAs(other: LogItemRvItem): Boolean = items
.any { !other.items.contains(it) }
override fun contentSameAs(other: LogItemRvItem): Boolean {
if (items.size != other.items.size) return false
return items.all { it in other.items }
}
override fun itemSameAs(other: LogItemRvItem): Boolean = date == other.date
}
@ -53,13 +55,7 @@ class LogItemEntryRvItem(val item: MagiskLog) : ComparableRvItem<LogItemEntryRvI
fun toggle() = isExpanded.toggle()
override fun contentSameAs(other: LogItemEntryRvItem) = item.fromUid == other.item.fromUid &&
item.toUid == other.item.toUid &&
item.fromPid == other.item.fromPid &&
item.packageName == other.item.packageName &&
item.command == other.item.command &&
item.action == other.item.action &&
item.date == other.item.date
override fun contentSameAs(other: LogItemEntryRvItem) = item == other.item
override fun itemSameAs(other: LogItemEntryRvItem) = item.appName == other.item.appName
}

View File

@ -1,6 +1,5 @@
package com.topjohnwu.magisk.model.events
import android.app.Activity
import android.content.Context
import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
@ -15,6 +14,7 @@ import com.topjohnwu.magisk.data.repository.MagiskRepository
import com.topjohnwu.magisk.extensions.DynamicClassLoader
import com.topjohnwu.magisk.extensions.subscribeK
import com.topjohnwu.magisk.extensions.writeTo
import com.topjohnwu.magisk.base.BaseActivity
import com.topjohnwu.magisk.model.entity.module.Repo
import com.topjohnwu.magisk.model.permissions.PermissionRequestBuilder
import com.topjohnwu.magisk.utils.RxBus
@ -135,7 +135,7 @@ class UpdateSafetyNetEvent : ViewEvent(), ContextExecutor, KoinComponent, Safety
}
}
class ViewActionEvent(val action: Activity.() -> Unit) : ViewEvent(), ActivityExecutor {
class ViewActionEvent(val action: BaseActivity<*, *>.() -> Unit) : ViewEvent(), ActivityExecutor {
override fun invoke(activity: AppCompatActivity) = activity.run(action)
}

View File

@ -2,37 +2,25 @@ package com.topjohnwu.magisk.model.receiver
import android.content.ContextWrapper
import android.content.Intent
import android.os.Build.VERSION.SDK_INT
import com.topjohnwu.magisk.*
import com.topjohnwu.magisk.Config
import com.topjohnwu.magisk.Const
import com.topjohnwu.magisk.Info
import com.topjohnwu.magisk.base.BaseReceiver
import com.topjohnwu.magisk.data.database.PolicyDao
import com.topjohnwu.magisk.data.database.base.su
import com.topjohnwu.magisk.extensions.reboot
import com.topjohnwu.magisk.extensions.startActivity
import com.topjohnwu.magisk.extensions.startActivityWithRoot
import com.topjohnwu.magisk.model.download.DownloadService
import com.topjohnwu.magisk.model.entity.ManagerJson
import com.topjohnwu.magisk.model.entity.internal.Configuration
import com.topjohnwu.magisk.model.entity.internal.DownloadSubject
import com.topjohnwu.magisk.ui.surequest.SuRequestActivity
import com.topjohnwu.magisk.utils.SuLogger
import com.topjohnwu.magisk.view.Notifications
import com.topjohnwu.magisk.utils.SuHandler
import com.topjohnwu.magisk.view.Shortcuts
import com.topjohnwu.superuser.Shell
import org.koin.core.inject
import timber.log.Timber
open class GeneralReceiver : BaseReceiver() {
private val policyDB: PolicyDao by inject()
companion object {
const val REQUEST = "request"
const val LOG = "log"
const val NOTIFY = "notify"
const val TEST = "test"
}
private fun getPkg(intent: Intent): String {
return intent.data?.encodedSchemeSpecificPart.orEmpty()
}
@ -40,59 +28,19 @@ open class GeneralReceiver : BaseReceiver() {
override fun onReceive(context: ContextWrapper, intent: Intent?) {
intent ?: return
// Debug messages
if (BuildConfig.DEBUG) {
Timber.d(intent.action)
intent.extras?.let { bundle ->
bundle.keySet().forEach {
Timber.d("[%s]=[%s]", it, bundle[it])
}
}
}
when (intent.action ?: return) {
Intent.ACTION_REBOOT, Intent.ACTION_BOOT_COMPLETED -> {
val action = intent.getStringExtra("action")
if (action == null) {
// Actual boot completed event
Shell.su("mm_patch_dtbo").submit {
if (it.isSuccess)
Notifications.dtboPatched(context)
}
return
}
when (action) {
REQUEST -> {
val i = context.intent(SuRequestActivity::class.java)
.setAction(action)
.putExtra("socket", intent.getStringExtra("socket"))
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK)
if (SDK_INT >= 29) {
// Android Q does not allow starting activity from background
i.startActivityWithRoot()
} else {
i.startActivity(context)
}
}
LOG -> SuLogger.handleLogs(context, intent)
NOTIFY -> SuLogger.handleNotify(context, intent)
TEST -> {
val mode = intent.getIntExtra("mode", 1 shl 1)
if (mode > Info.env.connectionMode)
Info.env.connectionMode = mode
Shell.su("magisk --connect-mode $mode").submit()
}
}
Intent.ACTION_REBOOT -> {
SuHandler(context, intent.getStringExtra("action"), intent.extras)
}
Intent.ACTION_PACKAGE_REPLACED ->
Intent.ACTION_PACKAGE_REPLACED -> {
// This will only work pre-O
if (Config.suReAuth)
policyDB.delete(getPkg(intent)).blockingGet()
}
Intent.ACTION_PACKAGE_FULLY_REMOVED -> {
val pkg = getPkg(intent)
policyDB.delete(pkg).blockingGet()
"magiskhide --rm $pkg".su().blockingGet()
Shell.su("magiskhide --rm $pkg").submit()
}
Intent.ACTION_LOCALE_CHANGED -> Shortcuts.setup(context)
Const.Key.BROADCAST_MANAGER_UPDATE -> {

View File

@ -266,7 +266,7 @@ abstract class MagiskInstaller {
val patched = File(installDir, "new-boot.img")
if (isSigned) {
console.add("- Signing boot image with test keys")
console.add("- Signing boot image with verity keys")
val signed = File(installDir, "signed.img")
try {
withStreams(SuFileInputStream(patched), signed.outputStream().buffered()) {

View File

@ -2,11 +2,13 @@ package com.topjohnwu.magisk.ui
import android.content.Intent
import android.os.Bundle
import androidx.appcompat.app.AlertDialog
import androidx.core.view.GravityCompat
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentTransaction
import com.ncapdevi.fragnav.FragNavController
import com.ncapdevi.fragnav.FragNavTransactionOptions
import com.topjohnwu.magisk.Const
import com.topjohnwu.magisk.Const.Key.OPEN_SECTION
import com.topjohnwu.magisk.Info
import com.topjohnwu.magisk.R
@ -29,7 +31,6 @@ import com.topjohnwu.magisk.ui.module.ReposFragment
import com.topjohnwu.magisk.ui.settings.SettingsFragment
import com.topjohnwu.magisk.ui.superuser.SuperuserFragment
import com.topjohnwu.magisk.utils.Utils
import com.topjohnwu.superuser.Shell
import org.koin.androidx.viewmodel.ext.android.viewModel
import timber.log.Timber
import kotlin.reflect.KClass
@ -60,12 +61,21 @@ open class MainActivity : BaseActivity<MainViewModel, ActivityMainBinding>(), Na
override fun onCreate(savedInstanceState: Bundle?) {
if (!SplashActivity.DONE) {
startActivity(intent(SplashActivity::class.java))
startActivity(intent<SplashActivity>())
finish()
}
super.onCreate(savedInstanceState)
if (Info.env.isUnsupported && !viewModel.shownUnsupportedDialog) {
viewModel.shownUnsupportedDialog = true
AlertDialog.Builder(this)
.setTitle(R.string.unsupport_magisk_title)
.setMessage(getString(R.string.unsupport_magisk_msg, Const.Version.MIN_VERSION))
.setPositiveButton(android.R.string.ok, null)
.show()
}
navigationController.apply {
rootFragmentListener = this@MainActivity
transactionListener = this@MainActivity
@ -153,16 +163,11 @@ open class MainActivity : BaseActivity<MainViewModel, ActivityMainBinding>(), Na
private fun checkHideSection() {
val menu = binding.navView.menu
menu.findItem(R.id.magiskHideFragment).isVisible =
Shell.rootAccess() && Info.env.magiskHide
menu.findItem(R.id.modulesFragment).isVisible =
Shell.rootAccess() && Info.env.magiskVersionCode >= 0
menu.findItem(R.id.reposFragment).isVisible =
(viewModel.isConnected.value && Shell.rootAccess() && Info.env.magiskVersionCode >= 0)
menu.findItem(R.id.logFragment).isVisible =
Shell.rootAccess()
menu.findItem(R.id.superuserFragment).isVisible =
Utils.showSuperUser()
menu.findItem(R.id.magiskHideFragment).isVisible = Info.env.isActive && Info.env.magiskHide
menu.findItem(R.id.modulesFragment).isVisible = Info.env.isActive
menu.findItem(R.id.reposFragment).isVisible = Info.isConnected.value && Info.env.isActive
menu.findItem(R.id.logFragment).isVisible = Info.env.isActive
menu.findItem(R.id.superuserFragment).isVisible = Utils.showSuperUser()
}
private fun FragNavTransactionOptions.Builder.customAnimations(options: MagiskAnimBuilder) =

View File

@ -8,6 +8,8 @@ import com.topjohnwu.magisk.model.navigation.Navigation
class MainViewModel : BaseViewModel() {
var shownUnsupportedDialog = false
fun navPressed() = Navigation.Main.OPEN_NAV.publish()
fun navigationItemPressed(item: MenuItem): Boolean {

View File

@ -7,9 +7,13 @@ import android.text.TextUtils
import androidx.appcompat.app.AlertDialog
import com.topjohnwu.magisk.*
import com.topjohnwu.magisk.model.navigation.Navigation
import com.topjohnwu.magisk.BuildConfig
import com.topjohnwu.magisk.Config
import com.topjohnwu.magisk.intent
import com.topjohnwu.magisk.utils.Utils
import com.topjohnwu.magisk.view.Notifications
import com.topjohnwu.magisk.view.Shortcuts
import com.topjohnwu.magisk.wrap
import com.topjohnwu.superuser.Shell
open class SplashActivity : Activity() {
@ -20,19 +24,7 @@ open class SplashActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Shell.getShell {
if (Info.env.magiskVersionCode > 0 && Info.env.magiskVersionCode < Const.Version.MIN_SUPPORT) {
AlertDialog.Builder(this)
.setTitle(R.string.unsupport_magisk_title)
.setMessage(R.string.unsupport_magisk_message)
.setNegativeButton(android.R.string.ok, null)
.setOnDismissListener { finish() }
.show()
} else {
initAndStart()
}
}
Shell.getShell { initAndStart() }
}
private fun initAndStart() {
@ -41,7 +33,7 @@ open class SplashActivity : Activity() {
Config.suManager = ""
Shell.su("pm uninstall $pkg").submit()
}
if (TextUtils.equals(pkg, packageName)) {
if (pkg == packageName) {
runCatching {
// We are the manager, remove com.topjohnwu.magisk as it could be malware
packageManager.getApplicationInfo(BuildConfig.APPLICATION_ID, 0)
@ -61,6 +53,11 @@ open class SplashActivity : Activity() {
// Setup shortcuts
Shortcuts.setup(this)
Shell.su("mm_patch_dtbo").submit {
if (it.isSuccess)
Notifications.dtboPatched(this)
}
DONE = true
Navigation.start(intent, this)
finish()

View File

@ -2,9 +2,9 @@ package com.topjohnwu.magisk.ui.flash
import android.content.Context
import android.content.Intent
import android.content.pm.ActivityInfo
import android.net.Uri
import android.os.Bundle
import androidx.core.app.NotificationManagerCompat
import androidx.core.net.toUri
import com.topjohnwu.magisk.Const
import com.topjohnwu.magisk.R
@ -16,6 +16,7 @@ import com.topjohnwu.magisk.model.events.BackPressEvent
import com.topjohnwu.magisk.model.events.PermissionEvent
import com.topjohnwu.magisk.model.events.SnackbarEvent
import com.topjohnwu.magisk.model.events.ViewEvent
import com.topjohnwu.magisk.view.Notifications
import org.koin.androidx.viewmodel.ext.android.viewModel
import org.koin.core.parameter.parametersOf
import java.io.File
@ -32,10 +33,11 @@ open class FlashActivity : BaseActivity<FlashViewModel, ActivityFlashBinding>()
}
override fun onCreate(savedInstanceState: Bundle?) {
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_NOSENSOR
super.onCreate(savedInstanceState)
val id = intent.getIntExtra(Const.Key.DISMISS_ID, -1)
if (id != -1)
NotificationManagerCompat.from(this).cancel(id)
Notifications.mgr.cancel(id)
}
override fun onBackPressed() {
@ -60,7 +62,7 @@ open class FlashActivity : BaseActivity<FlashViewModel, ActivityFlashBinding>()
companion object {
private fun intent(context: Context) = context.intent(FlashActivity::class.java)
private fun intent(context: Context) = context.intent<FlashActivity>()
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
private fun intent(context: Context, file: File) = intent(context).setData(file.toUri())

View File

@ -90,9 +90,7 @@ class HideViewModel(
.toList()
.map { it to items.calculateDiff(it) }
private fun toggleItem(item: HideProcessRvItem) = magiskRepo
.toggleHide(item.isHidden.value, item.packageName, item.process)
.subscribeK()
.add()
private fun toggleItem(item: HideProcessRvItem) =
magiskRepo.toggleHide(item.isHidden.value, item.packageName, item.process)
}
}

View File

@ -77,7 +77,7 @@ class HomeViewModel(
""
}
val safetyNetTitle = KObservableField(R.string.safetyNet_check_text)
val safetyNetTitle = KObservableField(R.string.safetyNet_check_text.res())
val ctsState = KObservableField(SafetyNetState.IDLE)
val basicIntegrityState = KObservableField(SafetyNetState.IDLE)
val safetyNetState = Observer(ctsState, basicIntegrityState) {
@ -92,7 +92,7 @@ class HomeViewModel(
}
}
val hasRoot = KObservableField(false)
val isActive = KObservableField(false)
private var shownDialog = false
@ -135,7 +135,7 @@ class HomeViewModel(
fun safetyNetPressed() {
ctsState.value = SafetyNetState.LOADING
basicIntegrityState.value = SafetyNetState.LOADING
safetyNetTitle.value = R.string.checking_safetyNet_status
safetyNetTitle.value = R.string.checking_safetyNet_status.res()
UpdateSafetyNetEvent().publish()
}
@ -144,7 +144,7 @@ class HomeViewModel(
response and 0x0F == 0 -> {
val hasCtsPassed = response and SafetyNetHelper.CTS_PASS != 0
val hasBasicIntegrityPassed = response and SafetyNetHelper.BASIC_PASS != 0
safetyNetTitle.value = R.string.safetyNet_check_success
safetyNetTitle.value = R.string.safetyNet_check_success.res()
ctsState.value = if (hasCtsPassed) {
SafetyNetState.PASS
} else {
@ -164,8 +164,8 @@ class HomeViewModel(
ctsState.value = SafetyNetState.IDLE
basicIntegrityState.value = SafetyNetState.IDLE
safetyNetTitle.value = when (response) {
SafetyNetHelper.RESPONSE_ERR -> R.string.safetyNet_res_invalid
else -> R.string.safetyNet_api_error
SafetyNetHelper.RESPONSE_ERR -> R.string.safetyNet_res_invalid.res()
else -> R.string.safetyNet_api_error.res()
}
}
}
@ -175,7 +175,7 @@ class HomeViewModel(
if (invalidate)
Info.envRef.invalidate()
hasRoot.value = Shell.rootAccess()
isActive.value = Info.env.isActive
val fetchUpdate = if (isConnected.value)
magiskRepo.fetchUpdate().ignoreElement()
@ -192,7 +192,7 @@ class HomeViewModel(
_managerState.value = MagiskState.LOADING
ctsState.value = SafetyNetState.IDLE
basicIntegrityState.value = SafetyNetState.IDLE
safetyNetTitle.value = R.string.safetyNet_check_text
safetyNetTitle.value = R.string.safetyNet_check_text.res()
}.subscribeK {
updateSelf()
ensureEnv()
@ -215,8 +215,8 @@ class HomeViewModel(
private fun updateSelf() {
magiskState.value = when (Info.env.magiskVersionCode) {
in Int.MIN_VALUE until 0 -> MagiskState.NOT_INSTALLED
in 1 until (Info.remote.magisk.versionCode - 1) -> MagiskState.OBSOLETE
in Int.MIN_VALUE .. 0 -> MagiskState.NOT_INSTALLED
in 1 until Info.remote.magisk.versionCode -> MagiskState.OBSOLETE
else -> MagiskState.UP_TO_DATE
}
@ -224,10 +224,10 @@ class HomeViewModel(
VERSION_FMT.format(Info.remote.magisk.version, Info.remote.magisk.versionCode)
_managerState.value = when (Info.remote.app.versionCode) {
in Int.MIN_VALUE until 0 -> MagiskState.NOT_INSTALLED //wrong update channel
in (BuildConfig.VERSION_CODE + 1) until Int.MAX_VALUE -> MagiskState.OBSOLETE
in Int.MIN_VALUE .. 0 -> MagiskState.NOT_INSTALLED //wrong update channel
in (BuildConfig.VERSION_CODE + 1) .. Int.MAX_VALUE -> MagiskState.OBSOLETE
else -> {
if (isRunningAsStub && Info.stub!!.version < Info.remote.stub.versionCode)
if (Info.stub?.version ?: Int.MAX_VALUE < Info.remote.stub.versionCode)
MagiskState.OBSOLETE
else
MagiskState.UP_TO_DATE

View File

@ -104,7 +104,6 @@ class LogViewModel(
.add()
private fun clearMagiskLogs(callback: () -> Unit) = logRepo.clearMagiskLogs()
.ignoreElement()
.doOnComplete(callback)
.subscribeK { SnackbarEvent(R.string.logs_cleared).publish() }
.add()
@ -115,8 +114,7 @@ class LogViewModel(
.toList()
private fun fetchMagiskLog() = logRepo.fetchMagiskLogs()
.flattenAsFlowable { it }
.map { ConsoleRvItem(it) }
.toList()
}
}

View File

@ -28,7 +28,7 @@ class ModulesFragment : BaseFragment<ModuleViewModel, FragmentModulesBinding>()
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (requestCode == Const.ID.FETCH_ZIP && resultCode == Activity.RESULT_OK && data != null) {
// Get the URI of the selected file
val intent = activity.intent(FlashActivity::class.java)
val intent = activity.intent<FlashActivity>()
intent.setData(data.data).putExtra(Const.Key.FLASH_ACTION, Const.Value.FLASH_ZIP)
startActivity(intent)
}

View File

@ -19,13 +19,11 @@ import com.topjohnwu.magisk.data.database.RepoDao
import com.topjohnwu.magisk.databinding.CustomDownloadDialogBinding
import com.topjohnwu.magisk.databinding.DialogCustomNameBinding
import com.topjohnwu.magisk.extensions.subscribeK
import com.topjohnwu.magisk.extensions.toLangTag
import com.topjohnwu.magisk.model.download.DownloadService
import com.topjohnwu.magisk.model.entity.internal.Configuration
import com.topjohnwu.magisk.model.entity.internal.DownloadSubject
import com.topjohnwu.magisk.model.observer.Observer
import com.topjohnwu.magisk.utils.*
import com.topjohnwu.magisk.view.dialogs.FingerprintAuthDialog
import com.topjohnwu.superuser.Shell
import io.reactivex.Completable
import org.koin.android.ext.android.inject
@ -64,8 +62,8 @@ class SettingsFragment : BasePreferenceFragment() {
multiuserConfig = findPreference(Config.Key.SU_MULTIUSER_MODE)!!
nsConfig = findPreference(Config.Key.SU_MNT_NS)!!
val reauth = findPreference<SwitchPreferenceCompat>(Config.Key.SU_REAUTH)!!
val fingerprint = findPreference<SwitchPreferenceCompat>(Config.Key.SU_FINGERPRINT)!!
val generalCatagory = findPreference<PreferenceCategory>("general")!!
val biometric = findPreference<SwitchPreferenceCompat>(Config.Key.SU_BIOMETRIC)!!
val generalCategory = findPreference<PreferenceCategory>("general")!!
val magiskCategory = findPreference<PreferenceCategory>("magisk")!!
val suCategory = findPreference<PreferenceCategory>("superuser")!!
val hideManager = findPreference<Preference>("hide")!!
@ -91,16 +89,16 @@ class SettingsFragment : BasePreferenceFragment() {
suCategory.removePreference(reauth)
}
// Disable fingerprint option if not possible
if (!FingerprintHelper.canUseFingerprint()) {
fingerprint.isEnabled = false
fingerprint.isChecked = false
fingerprint.setSummary(R.string.disable_fingerprint)
// Disable biometric option if not possible
if (!BiometricHelper.isSupported) {
biometric.isEnabled = false
biometric.isChecked = false
biometric.setSummary(R.string.no_biometric)
}
if (Const.USER_ID == 0 && Info.isConnected.value && Shell.rootAccess()) {
if (Const.USER_ID == 0 && Info.isConnected.value && Info.env.isActive) {
if (activity.packageName == BuildConfig.APPLICATION_ID) {
generalCatagory.removePreference(restoreManager)
generalCategory.removePreference(restoreManager)
hideManager.setOnPreferenceClickListener {
showManagerNameDialog {
PatchAPK.hideManager(requireContext(), it)
@ -108,7 +106,7 @@ class SettingsFragment : BasePreferenceFragment() {
true
}
} else {
generalCatagory.removePreference(hideManager)
generalCategory.removePreference(hideManager)
restoreManager.setOnPreferenceClickListener {
DownloadService(requireContext()) {
subject = DownloadSubject.Manager(Configuration.APK.Restore)
@ -118,25 +116,32 @@ class SettingsFragment : BasePreferenceFragment() {
}
} else {
// Remove if not primary user, no connection, or no root
generalCatagory.removePreference(restoreManager)
generalCatagory.removePreference(hideManager)
generalCategory.removePreference(restoreManager)
generalCategory.removePreference(hideManager)
}
if (!Utils.showSuperUser()) {
preferenceScreen.removePreference(suCategory)
}
if (!Shell.rootAccess()) {
if (!Info.env.isActive) {
preferenceScreen.removePreference(magiskCategory)
generalCatagory.removePreference(hideManager)
generalCategory.removePreference(hideManager)
}
findPreference<Preference>("clear")?.setOnPreferenceClickListener {
Completable.fromAction { repoDB.clear() }.subscribeK {
Utils.toast(R.string.repo_cache_cleared, Toast.LENGTH_SHORT)
findPreference<Preference>("clear")?.also {
if (Info.env.isActive) {
it.setOnPreferenceClickListener {
Completable.fromAction { repoDB.clear() }.subscribeK {
Utils.toast(R.string.repo_cache_cleared, Toast.LENGTH_SHORT)
}
true
}
} else {
generalCategory.removePreference(it)
}
true
}
findPreference<Preference>("hosts")?.setOnPreferenceClickListener {
Shell.su("add_hosts_module").submit {
Utils.toast(R.string.settings_hosts_toast, Toast.LENGTH_SHORT)
@ -146,20 +151,21 @@ class SettingsFragment : BasePreferenceFragment() {
findPreference<Preference>(Config.Key.DOWNLOAD_PATH)?.apply {
summary = Config.downloadPath
}?.setOnPreferenceClickListener { preference ->
activity.withExternalRW {
onSuccess {
showDownloadDialog {
Config.downloadPath = it
preference.summary = it
setOnPreferenceClickListener { pref ->
activity.withExternalRW {
onSuccess {
showDownloadDialog {
Config.downloadPath = it
pref.summary = it
}
}
}
true
}
true
}
updateChannel.setOnPreferenceChangeListener { _, value ->
val channel = Integer.parseInt(value as String)
val channel = value.toString().toInt()
val previous = Config.updateChannel
if (channel == Config.Value.CUSTOM_CHANNEL) {
@ -201,7 +207,7 @@ class SettingsFragment : BasePreferenceFragment() {
Shell.su("magiskhide --disable").submit()
}
Config.Key.LOCALE -> {
ResourceMgr.reload()
refreshLocale()
activity.recreate()
}
Config.Key.CHECK_UPDATES -> Utils.scheduleUpdateCheck(activity)
@ -211,13 +217,13 @@ class SettingsFragment : BasePreferenceFragment() {
override fun onPreferenceTreeClick(preference: Preference): Boolean {
when (preference.key) {
Config.Key.SU_FINGERPRINT -> {
Config.Key.SU_BIOMETRIC -> {
val checked = (preference as SwitchPreferenceCompat).isChecked
preference.isChecked = !checked
FingerprintAuthDialog(requireActivity()) {
BiometricHelper.authenticate(requireActivity()) {
preference.isChecked = checked
Config.suFingerprint = checked
}.show()
Config.suBiometric = checked
}
}
}
return true
@ -225,22 +231,7 @@ class SettingsFragment : BasePreferenceFragment() {
private fun setLocalePreference(lp: ListPreference) {
lp.isEnabled = false
availableLocales.map {
val names = mutableListOf<String>()
val values = mutableListOf<String>()
names.add(
ResourceMgr.getString(defaultLocale, R.string.system_default)
)
values.add("")
it.forEach { locale ->
names.add(locale.getDisplayName(locale))
values.add(locale.toLangTag())
}
Pair(names.toTypedArray(), values.toTypedArray())
}.subscribeK { (names, values) ->
availableLocales.subscribeK { (names, values) ->
lp.isEnabled = true
lp.entries = names
lp.entryValues = values

View File

@ -15,11 +15,10 @@ import com.topjohnwu.magisk.model.entity.recycler.PolicyRvItem
import com.topjohnwu.magisk.model.events.PolicyEnableEvent
import com.topjohnwu.magisk.model.events.PolicyUpdateEvent
import com.topjohnwu.magisk.model.events.SnackbarEvent
import com.topjohnwu.magisk.utils.BiometricHelper
import com.topjohnwu.magisk.utils.DiffObservableList
import com.topjohnwu.magisk.utils.FingerprintHelper
import com.topjohnwu.magisk.utils.RxBus
import com.topjohnwu.magisk.view.dialogs.CustomAlertDialog
import com.topjohnwu.magisk.view.dialogs.FingerprintAuthDialog
import io.reactivex.Single
import io.reactivex.disposables.Disposable
import me.tatarka.bindingcollectionadapter2.ItemBinding
@ -42,6 +41,11 @@ class SuperuserViewModel(
init {
rxBus.register<PolicyEnableEvent>()
.filter {
val isIgnored = it.item == ignoreNext
if (isIgnored) ignoreNext = null
!isIgnored
}
.subscribeK { togglePolicy(it.item, it.enable) }
.add()
rxBus.register<PolicyUpdateEvent>()
@ -78,8 +82,8 @@ class SuperuserViewModel(
.add()
withView {
if (FingerprintHelper.useFingerprint()) {
FingerprintAuthDialog(this) { updateState() }.show()
if (BiometricHelper.isEnabled) {
BiometricHelper.authenticate(this) { updateState() }
} else {
CustomAlertDialog(this)
.setTitle(R.string.su_revoke_title)
@ -126,12 +130,12 @@ class SuperuserViewModel(
.add()
}
if (FingerprintHelper.useFingerprint()) {
if (BiometricHelper.isEnabled) {
withView {
FingerprintAuthDialog(this, { updateState() }, {
BiometricHelper.authenticate(this, onError = {
ignoreNext = item
item.isEnabled.toggle()
}).show()
}) { updateState() }
}
} else {
updateState()

View File

@ -1,5 +1,6 @@
package com.topjohnwu.magisk.ui.surequest
import android.content.Intent
import android.content.pm.ActivityInfo
import android.os.Build
import android.os.Bundle
@ -7,11 +8,11 @@ import android.view.Window
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.base.BaseActivity
import com.topjohnwu.magisk.databinding.ActivityRequestBinding
import com.topjohnwu.magisk.model.entity.MagiskPolicy
import com.topjohnwu.magisk.model.events.DieEvent
import com.topjohnwu.magisk.model.events.ViewActionEvent
import com.topjohnwu.magisk.model.events.ViewEvent
import com.topjohnwu.magisk.model.receiver.GeneralReceiver
import com.topjohnwu.magisk.utils.SuLogger
import com.topjohnwu.magisk.utils.SuHandler
import com.topjohnwu.magisk.utils.SuHandler.REQUEST
import org.koin.androidx.viewmodel.ext.android.viewModel
open class SuRequestActivity : BaseActivity<SuRequestViewModel, ActivityRequestBinding>() {
@ -21,7 +22,7 @@ open class SuRequestActivity : BaseActivity<SuRequestViewModel, ActivityRequestB
override val viewModel: SuRequestViewModel by viewModel()
override fun onBackPressed() {
viewModel.handler?.handleAction(MagiskPolicy.DENY, -1)
viewModel.denyPressed()
}
override fun onCreate(savedInstanceState: Bundle?) {
@ -29,24 +30,34 @@ open class SuRequestActivity : BaseActivity<SuRequestViewModel, ActivityRequestB
lockOrientation()
super.onCreate(savedInstanceState)
val intent = intent
when (intent?.action) {
GeneralReceiver.REQUEST -> {
if (!viewModel.handleRequest(intent))
finish()
return
}
GeneralReceiver.LOG -> SuLogger.handleLogs(this, intent)
GeneralReceiver.NOTIFY -> SuLogger.handleNotify(this, intent)
fun showRequest() {
if (!viewModel.handleRequest(intent))
finish()
}
finish()
fun runHandler(action: String?) {
SuHandler(this, action, intent.extras)
finish()
}
if (intent.action == Intent.ACTION_VIEW) {
val action = intent.getStringExtra("action")
if (action == REQUEST) {
showRequest()
} else {
runHandler(action)
}
} else if (intent.action == REQUEST) {
showRequest()
} else {
runHandler(intent.action)
}
}
override fun onEventDispatched(event: ViewEvent) {
super.onEventDispatched(event)
when (event) {
is ViewActionEvent -> event.action(this)
is DieEvent -> finish()
}
}

View File

@ -1,28 +1,25 @@
package com.topjohnwu.magisk.ui.surequest
import android.annotation.SuppressLint
import android.content.Intent
import android.content.SharedPreferences
import android.content.pm.PackageManager
import android.content.res.Resources
import android.graphics.drawable.Drawable
import android.hardware.fingerprint.FingerprintManager
import android.os.CountDownTimer
import android.text.TextUtils
import com.topjohnwu.magisk.BuildConfig
import com.topjohnwu.magisk.Config
import com.topjohnwu.magisk.Const
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.base.viewmodel.BaseViewModel
import com.topjohnwu.magisk.data.database.PolicyDao
import com.topjohnwu.magisk.databinding.ComparableRvItem
import com.topjohnwu.magisk.extensions.addOnPropertyChangedCallback
import com.topjohnwu.magisk.extensions.now
import com.topjohnwu.magisk.model.entity.MagiskPolicy
import com.topjohnwu.magisk.model.entity.recycler.SpinnerRvItem
import com.topjohnwu.magisk.model.entity.toPolicy
import com.topjohnwu.magisk.model.events.DieEvent
import com.topjohnwu.magisk.utils.BiometricHelper
import com.topjohnwu.magisk.utils.DiffObservableList
import com.topjohnwu.magisk.utils.FingerprintHelper
import com.topjohnwu.magisk.utils.KObservableField
import com.topjohnwu.magisk.utils.SuConnector
import me.tatarka.bindingcollectionadapter2.BindingListViewAdapter
@ -45,7 +42,6 @@ class SuRequestViewModel(
val denyText = KObservableField(resources.getString(R.string.deny))
val warningText = KObservableField<CharSequence>(resources.getString(R.string.su_warning))
val canUseFingerprint = KObservableField(FingerprintHelper.useFingerprint())
val selectedItemPosition = KObservableField(0)
private val items = DiffObservableList(ComparableRvItem.callback)
@ -58,48 +54,33 @@ class SuRequestViewModel(
setItems(items)
}
private val cancelTasks = mutableListOf<() -> Unit>()
var handler: ActionHandler? = null
private var timer: CountDownTimer? = null
private var policy: MagiskPolicy? = null
set(value) {
field = value
updatePolicy(value)
}
init {
resources.getStringArray(R.array.allow_timeout)
.map { SpinnerRvItem(it) }
.let { items.update(it) }
selectedItemPosition.addOnPropertyChangedCallback {
Timber.e("Changed position to $it")
}
}
private fun updatePolicy(policy: MagiskPolicy?) {
policy ?: return
icon.value = policy.applicationInfo.loadIcon(packageManager)
title.value = policy.appName
packageName.value = policy.packageName
selectedItemPosition.value = timeoutPrefs.getInt(policy.packageName, 0)
}
private lateinit var timer: CountDownTimer
private lateinit var policy: MagiskPolicy
private lateinit var connector: SuConnector
private fun cancelTimer() {
timer?.cancel()
timer.cancel()
denyText.value = resources.getString(R.string.deny)
}
fun grantPressed() {
handler?.handleAction(MagiskPolicy.ALLOW)
timer?.cancel()
cancelTimer()
if (BiometricHelper.isEnabled) {
withView {
BiometricHelper.authenticate(this) {
handleAction(MagiskPolicy.ALLOW)
}
}
} else {
handleAction(MagiskPolicy.ALLOW)
}
}
fun denyPressed() {
handler?.handleAction(MagiskPolicy.DENY)
timer?.cancel()
handleAction(MagiskPolicy.DENY)
timer.cancel()
}
fun spinnerTouched(): Boolean {
@ -110,75 +91,27 @@ class SuRequestViewModel(
fun handleRequest(intent: Intent): Boolean {
val socketName = intent.getStringExtra("socket") ?: return false
val connector: SuConnector
try {
connector = object : SuConnector(socketName) {
@Throws(IOException::class)
override fun onResponse() {
out.writeInt(policy?.policy ?: return)
}
}
val bundle = connector.readSocketInput()
val uid = bundle.getString("uid")?.toIntOrNull() ?: return false
policyDB.deleteOutdated().blockingGet() // wrong!
policy = runCatching { policyDB.fetch(uid).blockingGet() }
.getOrDefault(uid.toPolicy(packageManager))
} catch (e: IOException) {
e.printStackTrace()
connector = Connector(socketName)
val map = connector.readRequest()
val uid = map["uid"]?.toIntOrNull() ?: return false
policy = uid.toPolicy(packageManager)
} catch (e: Exception) {
Timber.e(e)
return false
} catch (e: PackageManager.NameNotFoundException) {
e.printStackTrace()
return false
}
handler = object : ActionHandler() {
override fun handleAction() {
connector.response()
done()
}
@SuppressLint("ApplySharedPref")
override fun handleAction(action: Int) {
val pos = selectedItemPosition.value
timeoutPrefs.edit().putInt(policy?.packageName, pos).commit()
handleAction(action, Config.Value.TIMEOUT_LIST[pos])
}
override fun handleAction(action: Int, time: Int) {
val until = if (time >= 0) {
if (time == 0) {
0
} else {
MILLISECONDS.toSeconds(now) + MINUTES.toSeconds(time.toLong())
}
} else {
policy?.until ?: 0
}
policy = policy?.copy(policy = action, until = until)?.apply {
policyDB.update(this).blockingGet()
}
handleAction()
}
}
// Never allow com.topjohnwu.magisk (could be malware)
if (TextUtils.equals(policy?.packageName, BuildConfig.APPLICATION_ID))
if (policy.packageName == BuildConfig.APPLICATION_ID)
return false
// If not interactive, response directly
if (policy?.policy != MagiskPolicy.INTERACTIVE) {
handler?.handleAction()
return true
}
when (Config.suAutoReponse) {
Config.Value.SU_AUTO_DENY -> {
handler?.handleAction(MagiskPolicy.DENY, 0)
handleAction(MagiskPolicy.DENY, 0)
return true
}
Config.Value.SU_AUTO_ALLOW -> {
handler?.handleAction(MagiskPolicy.ALLOW, 0)
handleAction(MagiskPolicy.ALLOW, 0)
return true
}
}
@ -187,78 +120,65 @@ class SuRequestViewModel(
return true
}
@SuppressLint("ClickableViewAccessibility")
private fun showUI() {
resources.getStringArray(R.array.allow_timeout)
.map { SpinnerRvItem(it) }
.let { items.update(it) }
icon.value = policy.applicationInfo.loadIcon(packageManager)
title.value = policy.appName
packageName.value = policy.packageName
selectedItemPosition.value = timeoutPrefs.getInt(policy.packageName, 0)
val millis = SECONDS.toMillis(Config.suDefaultTimeout.toLong())
timer = object : CountDownTimer(millis, 1000) {
override fun onTick(remains: Long) {
denyText.value = "%s (%d)"
.format(resources.getString(R.string.deny), remains / 1000)
denyText.value = "${resources.getString(R.string.deny)} (${remains / 1000})"
}
override fun onFinish() {
denyText.value = resources.getString(R.string.deny)
handler?.handleAction(MagiskPolicy.DENY)
handleAction(MagiskPolicy.DENY)
}
}
timer?.start()
handler?.addCancel(Runnable { cancelTimer() })
val useFP = canUseFingerprint.value
if (useFP)
try {
val helper = SuFingerprint()
helper.authenticate()
handler?.addCancel(Runnable { helper.cancel() })
} catch (e: Exception) {
e.printStackTrace()
}
timer.start()
cancelTasks.add { cancelTimer() }
}
private inner class SuFingerprint @Throws(Exception::class)
internal constructor() : FingerprintHelper() {
private fun handleAction() {
connector.response()
cancelTasks.forEach { it() }
DieEvent().publish()
}
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
warningText.value = errString
}
private fun handleAction(action: Int) {
val pos = selectedItemPosition.value
timeoutPrefs.edit().putInt(policy.packageName, pos).apply()
handleAction(action, Config.Value.TIMEOUT_LIST[pos])
}
override fun onAuthenticationHelp(helpCode: Int, helpString: CharSequence) {
warningText.value = helpString
}
private fun handleAction(action: Int, time: Int) {
val until = if (time > 0)
MILLISECONDS.toSeconds(now) + MINUTES.toSeconds(time.toLong())
else
time.toLong()
override fun onAuthenticationSucceeded(result: FingerprintManager.AuthenticationResult) {
handler?.handleAction(MagiskPolicy.ALLOW)
}
policy.policy = action
policy.until = until
policy.uid = policy.uid % 100000 + Const.USER_ID * 100000
override fun onAuthenticationFailed() {
warningText.value = resources.getString(R.string.auth_fail)
if (until >= 0)
policyDB.update(policy).blockingAwait()
handleAction()
}
private inner class Connector @Throws(Exception::class)
internal constructor(name: String) : SuConnector(name) {
@Throws(IOException::class)
override fun onResponse() {
out.writeInt(policy.policy)
}
}
open inner class ActionHandler {
private val cancelTasks = mutableListOf<Runnable>()
internal open fun handleAction() {
done()
}
internal open fun handleAction(action: Int) {
done()
}
internal open fun handleAction(action: Int, time: Int) {
done()
}
internal fun addCancel(r: Runnable) {
cancelTasks.add(r)
}
internal fun done() {
cancelTasks.forEach { it.run() }
DieEvent().publish()
}
}
}
}

View File

@ -0,0 +1,60 @@
package com.topjohnwu.magisk.utils
import androidx.biometric.BiometricManager
import androidx.biometric.BiometricPrompt
import androidx.core.content.ContextCompat
import androidx.fragment.app.FragmentActivity
import com.topjohnwu.magisk.Config
import com.topjohnwu.magisk.R
import org.koin.core.KoinComponent
import org.koin.core.get
object BiometricHelper: KoinComponent {
private val mgr by lazy { BiometricManager.from(get()) }
val isSupported get() = when (mgr.canAuthenticate()) {
BiometricManager.BIOMETRIC_SUCCESS -> true
else -> false
}
val isEnabled: Boolean get() {
val enabled = Config.suBiometric
if (enabled && !isSupported) {
Config.suBiometric = false
return false
}
return enabled
}
fun authenticate(
activity: FragmentActivity,
onError: () -> Unit = {},
onSuccess: () -> Unit): BiometricPrompt {
val prompt = BiometricPrompt(activity,
ContextCompat.getMainExecutor(activity),
object : BiometricPrompt.AuthenticationCallback() {
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
onError()
}
override fun onAuthenticationFailed() {
onError()
}
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
onSuccess()
}
}
)
val info = BiometricPrompt.PromptInfo.Builder()
.setConfirmationRequired(true)
.setDeviceCredentialAllowed(false)
.setTitle(activity.getString(R.string.authenticate))
.setNegativeButtonText(activity.getString(android.R.string.cancel))
.build()
prompt.authenticate(info)
return prompt
}
}

View File

@ -1,121 +0,0 @@
package com.topjohnwu.magisk.utils
import android.annotation.TargetApi
import android.app.KeyguardManager
import android.content.Context
import android.hardware.fingerprint.FingerprintManager
import android.os.Build
import android.os.CancellationSignal
import android.security.keystore.KeyGenParameterSpec
import android.security.keystore.KeyProperties
import com.topjohnwu.magisk.Config
import com.topjohnwu.magisk.extensions.get
import com.topjohnwu.magisk.extensions.inject
import java.security.KeyStore
import javax.crypto.Cipher
import javax.crypto.KeyGenerator
import javax.crypto.SecretKey
@TargetApi(Build.VERSION_CODES.M)
abstract class FingerprintHelper @Throws(Exception::class)
protected constructor() {
private val manager: FingerprintManager?
private val cipher: Cipher
private var cancel: CancellationSignal? = null
private val context: Context by inject()
init {
val keyStore = KeyStore.getInstance("AndroidKeyStore")
manager = context.getSystemService(FingerprintManager::class.java)
cipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/"
+ KeyProperties.BLOCK_MODE_CBC + "/"
+ KeyProperties.ENCRYPTION_PADDING_PKCS7)
keyStore.load(null)
var key = keyStore.getKey(SU_KEYSTORE_KEY, null) as SecretKey? ?: generateKey()
runCatching {
cipher.init(Cipher.ENCRYPT_MODE, key)
}.onFailure {
// Only happens on Marshmallow
key = generateKey()
cipher.init(Cipher.ENCRYPT_MODE, key)
}
}
abstract fun onAuthenticationError(errorCode: Int, errString: CharSequence)
abstract fun onAuthenticationHelp(helpCode: Int, helpString: CharSequence)
abstract fun onAuthenticationSucceeded(result: FingerprintManager.AuthenticationResult)
abstract fun onAuthenticationFailed()
fun authenticate() {
cancel = CancellationSignal()
val cryptoObject = FingerprintManager.CryptoObject(cipher)
manager!!.authenticate(cryptoObject, cancel, 0, Callback(), null)
}
fun cancel() {
if (cancel != null)
cancel!!.cancel()
}
@Throws(Exception::class)
private fun generateKey(): SecretKey {
val keygen = KeyGenerator
.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore")
val builder = KeyGenParameterSpec.Builder(
SU_KEYSTORE_KEY,
KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_CBC)
.setUserAuthenticationRequired(true)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
builder.setInvalidatedByBiometricEnrollment(false)
}
keygen.init(builder.build())
return keygen.generateKey()
}
private inner class Callback : FingerprintManager.AuthenticationCallback() {
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
this@FingerprintHelper.onAuthenticationError(errorCode, errString)
}
override fun onAuthenticationHelp(helpCode: Int, helpString: CharSequence) {
this@FingerprintHelper.onAuthenticationHelp(helpCode, helpString)
}
override fun onAuthenticationSucceeded(result: FingerprintManager.AuthenticationResult) {
this@FingerprintHelper.onAuthenticationSucceeded(result)
}
override fun onAuthenticationFailed() {
this@FingerprintHelper.onAuthenticationFailed()
}
}
companion object {
private const val SU_KEYSTORE_KEY = "su_key"
fun useFingerprint(): Boolean {
var fp = Config.suFingerprint
if (fp && !canUseFingerprint()) {
Config.suFingerprint = false
fp = false
}
return fp
}
fun canUseFingerprint(context: Context = get()): Boolean {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M)
return false
val km = context.getSystemService(KeyguardManager::class.java)
val fm = context.getSystemService(FingerprintManager::class.java)
return km?.isKeyguardSecure ?: false &&
fm != null && fm.isHardwareDetected && fm.hasEnrolledFingerprints()
}
}
}

View File

@ -5,6 +5,7 @@ import android.util.Base64
import android.util.Base64OutputStream
import com.topjohnwu.magisk.Config
import com.topjohnwu.magisk.di.koinModules
import com.topjohnwu.magisk.utils.PatchAPK.ALPHANUM
import com.topjohnwu.signing.CryptoUtils.readCertificate
import com.topjohnwu.signing.CryptoUtils.readPrivateKey
import com.topjohnwu.superuser.internal.InternalUtils
@ -38,7 +39,6 @@ class Keygen: CertKeyProvider {
private const val ALIAS = "magisk"
private val PASSWORD get() = "magisk".toCharArray()
private const val TESTKEY_CERT = "61ed377e85d386a8dfee6b864bd85b0bfaa5af81"
private const val DNAME = "C=US,ST=California,L=Mountain View,O=Google Inc.,OU=Android,CN=Android"
private const val BASE64_FLAG = Base64.NO_PADDING or Base64.NO_WRAP
}
@ -88,6 +88,17 @@ class Keygen: CertKeyProvider {
}
}
private fun randomString(): String {
val rand = kotlin.random.Random.Default
val len = rand.nextInt(5, 10)
val sb = StringBuilder(len)
for (i in 0..len) {
val idx = rand.nextInt(ALPHANUM.length)
sb.append(ALPHANUM[idx])
}
return sb.toString()
}
private fun init(): KeyStore {
GlobalContext.getOrNull() ?: {
// Invoked externally, do some basic initialization
@ -113,7 +124,7 @@ class Keygen: CertKeyProvider {
// Generate new private key and certificate
val kp = KeyPairGenerator.getInstance("RSA").apply { initialize(4096) }.genKeyPair()
val dname = X500Name(DNAME)
val dname = X500Name("CN=${randomString()}")
val builder = JcaX509v3CertificateBuilder(dname, BigInteger(160, Random()),
start.time, end.time, dname, kp.public)
val signer = JcaContentSignerBuilder("SHA256WithRSA").build(kp.private)

View File

@ -1,24 +1,32 @@
@file:Suppress("DEPRECATION")
package com.topjohnwu.magisk.utils
import android.annotation.SuppressLint
import android.content.res.Configuration
import android.content.res.Resources
import com.topjohnwu.magisk.Config
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.ResourceMgr
import com.topjohnwu.magisk.extensions.langTagToLocale
import com.topjohnwu.magisk.extensions.toLangTag
import io.reactivex.Single
import java.util.*
import kotlin.Comparator
import kotlin.collections.ArrayList
var currentLocale: Locale = Locale.getDefault()
@SuppressLint("ConstantLocale")
val defaultLocale: Locale = Locale.getDefault()
@Suppress("DEPRECATION")
val availableLocales = Single.fromCallable {
val compareId = R.string.app_changelog
mutableListOf<Locale>().apply {
val config = ResourceMgr.resource.configuration
val metrics = ResourceMgr.resource.displayMetrics
val res = Resources(ResourceMgr.resource.assets, metrics, config)
val locales = mutableListOf<Locale>().apply {
// Add default locale
add(Locale.ENGLISH)
@ -26,24 +34,56 @@ val availableLocales = Single.fromCallable {
add(Locale.TAIWAN)
add(Locale("pt", "BR"))
val config = Configuration()
val metrics = ResourceMgr.resource.displayMetrics
val res = Resources(ResourceMgr.resource.assets, metrics, config)
// Other locales
val otherLocales = ResourceMgr.resource.assets.locales
.map { it.langTagToLocale() }
.distinctBy {
config.setLocale(it)
res.updateConfiguration(config, metrics)
res.getString(compareId)
}
listOf("", "").toTypedArray()
.map { it.langTagToLocale() }
.distinctBy {
config.setLocale(it)
res.updateConfiguration(config, metrics)
res.getString(compareId)
}
addAll(otherLocales)
}.sortedWith(Comparator { a, b ->
a.getDisplayName(a).toLowerCase(a)
.compareTo(b.getDisplayName(b).toLowerCase(b))
.compareTo(b.getDisplayName(b).toLowerCase(b))
})
config.setLocale(defaultLocale)
res.updateConfiguration(config, metrics)
val defName = res.getString(R.string.system_default)
// Restore back to current locale
config.setLocale(currentLocale)
res.updateConfiguration(config, metrics)
Pair(locales, defName)
}.map { (locales, defName) ->
val names = ArrayList<String>(locales.size + 1)
val values = ArrayList<String>(locales.size + 1)
names.add(defName)
values.add("")
locales.forEach { locale ->
names.add(locale.getDisplayName(locale))
values.add(locale.toLangTag())
}
Pair(names.toTypedArray(), values.toTypedArray())
}.cache()!!
fun Resources.updateConfig(config: Configuration = configuration) {
config.setLocale(currentLocale)
updateConfiguration(config, displayMetrics)
}
fun refreshLocale() {
val localeConfig = Config.locale
currentLocale = when {
localeConfig.isEmpty() -> defaultLocale
else -> localeConfig.langTagToLocale()
}
Locale.setDefault(currentLocale)
ResourceMgr.resource.updateConfig()
}

View File

@ -27,7 +27,7 @@ object PatchAPK {
private val UPPERALPHA = LOWERALPHA.toUpperCase()
private val ALPHA = LOWERALPHA + UPPERALPHA
private const val DIGITS = "0123456789"
private val ALPHANUM = ALPHA + DIGITS
val ALPHANUM = ALPHA + DIGITS
private val ALPHANUMDOTS = "$ALPHANUM............"
private fun genPackageName(prefix: String, length: Int): String {
@ -77,8 +77,9 @@ object PatchAPK {
}
private fun patchAndHide(context: Context, label: String): Boolean {
// If not running as stub, and we are compatible with stub, use stub
val src = if (!isRunningAsStub && SDK_INT >= 28 && Info.env.connectionMode == 3) {
val src = if (!isRunningAsStub && SDK_INT >= 28 &&
Info.env.magiskVersionCode >= Const.Version.PROVIDER_CONNECT) {
// If not running as stub, and we are compatible with stub, use stub
val stub = File(context.cacheDir, "stub.apk")
val svc = get<GithubRawServices>()
runCatching {
@ -118,7 +119,7 @@ object PatchAPK {
@JvmOverloads
fun patch(apk: String, out: String, pkg: String, label: String = "Manager"): Boolean {
try {
val jar = JarMap(apk)
val jar = JarMap.open(apk)
val je = jar.getJarEntry(Const.ANDROID_MANIFEST)
val xml = jar.getRawData(je)

View File

@ -2,11 +2,9 @@ package com.topjohnwu.magisk.utils
import android.net.LocalSocket
import android.net.LocalSocketAddress
import android.os.Bundle
import android.text.TextUtils
import androidx.collection.ArrayMap
import timber.log.Timber
import java.io.*
import java.nio.charset.Charset
abstract class SuConnector @Throws(IOException::class)
protected constructor(name: String) {
@ -21,24 +19,23 @@ protected constructor(name: String) {
input = DataInputStream(BufferedInputStream(socket.inputStream))
}
@Throws(IOException::class)
private fun readString(): String {
val len = input.readInt()
val buf = ByteArray(len)
input.readFully(buf)
return String(buf, Charset.forName("UTF-8"))
return String(buf, Charsets.UTF_8)
}
@Throws(IOException::class)
fun readSocketInput(): Bundle {
val bundle = Bundle()
fun readRequest(): Map<String, String> {
val ret = ArrayMap<String, String>()
while (true) {
val name = readString()
if (TextUtils.equals(name, "eof"))
if (name == "eof")
break
bundle.putString(name, readString())
ret[name] = readString()
}
return bundle
return ret
}
fun response() {

View File

@ -0,0 +1,135 @@
package com.topjohnwu.magisk.utils
import android.content.Context
import android.content.Intent
import android.os.Build
import android.os.Bundle
import android.os.Process
import android.widget.Toast
import com.topjohnwu.magisk.*
import com.topjohnwu.magisk.data.repository.LogRepository
import com.topjohnwu.magisk.extensions.get
import com.topjohnwu.magisk.extensions.startActivity
import com.topjohnwu.magisk.extensions.startActivityWithRoot
import com.topjohnwu.magisk.extensions.subscribeK
import com.topjohnwu.magisk.model.entity.MagiskPolicy
import com.topjohnwu.magisk.model.entity.toLog
import com.topjohnwu.magisk.model.entity.toPolicy
import com.topjohnwu.magisk.ui.surequest.SuRequestActivity
import com.topjohnwu.superuser.Shell
import timber.log.Timber
object SuHandler : ProviderCallHandler {
const val REQUEST = "request"
const val LOG = "log"
const val NOTIFY = "notify"
const val TEST = "test"
override fun call(context: Context, method: String, arg: String?, extras: Bundle?): Bundle? {
invoke(context.wrap(), method, extras)
return null
}
operator fun invoke(context: Context, action: String?, data: Bundle?) {
data ?: return
// Debug messages
if (BuildConfig.DEBUG) {
Timber.d(action)
data.let { bundle ->
bundle.keySet().forEach {
Timber.d("[%s]=[%s]", it, bundle[it])
}
}
}
when (action) {
REQUEST -> {
val intent = context.intent<SuRequestActivity>()
.setAction(action)
.putExtras(data)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK)
if (Build.VERSION.SDK_INT >= 29) {
// Android Q does not allow starting activity from background
intent.startActivityWithRoot()
} else {
intent.startActivity(context)
}
}
LOG -> handleLogs(context, data)
NOTIFY -> handleNotify(context, data)
TEST -> {
val mode = data.getInt("mode", 2)
Shell.su(
"magisk --connect-mode $mode",
"magisk --use-broadcast"
).submit()
}
}
}
private fun Any?.toInt(): Int? {
return when (this) {
is Int -> this
is Long -> this.toInt()
else -> null
}
}
private fun handleLogs(context: Context, data: Bundle) {
val fromUid = data["from.uid"].toInt() ?: return
if (fromUid == Process.myUid())
return
val pm = context.packageManager
val notify = data.getBoolean("notify", true)
val allow = data["policy"].toInt() ?: return
val policy = runCatching { fromUid.toPolicy(pm, allow) }.getOrElse { return }
if (notify)
notify(context, policy)
val toUid = data["to.uid"].toInt() ?: return
val pid = data["pid"].toInt() ?: return
val command = data.getString("command") ?: return
val log = policy.toLog(
toUid = toUid,
fromPid = pid,
command = command
)
val logRepo = get<LogRepository>()
logRepo.insert(log).subscribeK(onError = { Timber.e(it) })
}
private fun handleNotify(context: Context, data: Bundle) {
val fromUid = data["from.uid"].toInt() ?: return
if (fromUid == Process.myUid())
return
val pm = context.packageManager
val allow = data["policy"].toInt() ?: return
runCatching {
val policy = fromUid.toPolicy(pm, allow)
if (policy.policy >= 0)
notify(context, policy)
}
}
private fun notify(context: Context, policy: MagiskPolicy) {
if (policy.notification && Config.suNotification == Config.Value.NOTIFICATION_TOAST) {
val resId = if (policy.policy == MagiskPolicy.ALLOW)
R.string.su_allow_toast
else
R.string.su_deny_toast
Utils.toast(context.getString(resId, policy.appName), Toast.LENGTH_SHORT)
}
}
}

View File

@ -1,92 +0,0 @@
package com.topjohnwu.magisk.utils
import android.content.Context
import android.content.Intent
import android.os.Process
import android.widget.Toast
import com.topjohnwu.magisk.Config
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.data.database.PolicyDao
import com.topjohnwu.magisk.data.repository.LogRepository
import com.topjohnwu.magisk.extensions.get
import com.topjohnwu.magisk.model.entity.MagiskPolicy
import com.topjohnwu.magisk.model.entity.toLog
import com.topjohnwu.magisk.model.entity.toPolicy
import java.util.*
object SuLogger {
fun handleLogs(context: Context, intent: Intent) {
val fromUid = intent.getIntExtra("from.uid", -1)
if (fromUid < 0) return
if (fromUid == Process.myUid()) return
val pm = context.packageManager
val notify: Boolean
val data = intent.extras
val policy: MagiskPolicy = if (data!!.containsKey("notify")) {
notify = data.getBoolean("notify")
runCatching {
fromUid.toPolicy(pm)
}.getOrElse { return }
} else {
// Doesn't report whether notify or not, check database ourselves
val policyDB = get<PolicyDao>()
val policy = policyDB.fetch(fromUid).blockingGet() ?: return
notify = policy.notification
policy
}.copy(policy = data.getInt("policy", -1))
if (policy.policy < 0)
return
if (notify)
handleNotify(context, policy)
val toUid = intent.getIntExtra("to.uid", -1)
if (toUid < 0) return
val pid = intent.getIntExtra("pid", -1)
if (pid < 0) return
val command = intent.getStringExtra("command") ?: return
val log = policy.toLog(
toUid = toUid,
fromPid = pid,
command = command,
date = Date()
)
val logRepo = get<LogRepository>()
logRepo.put(log).blockingGet()?.printStackTrace()
}
private fun handleNotify(context: Context, policy: MagiskPolicy) {
if (policy.notification && Config.suNotification == Config.Value.NOTIFICATION_TOAST) {
Utils.toast(
context.getString(
if (policy.policy == MagiskPolicy.ALLOW)
R.string.su_allow_toast
else
R.string.su_deny_toast, policy.appName
),
Toast.LENGTH_SHORT
)
}
}
fun handleNotify(context: Context, intent: Intent) {
val fromUid = intent.getIntExtra("from.uid", -1)
if (fromUid < 0) return
if (fromUid == Process.myUid()) return
runCatching {
val pm = context.packageManager
val policy = fromUid.toPolicy(pm)
.copy(policy = intent.getIntExtra("policy", -1))
if (policy.policy >= 0)
handleNotify(context, policy)
}
}
}

View File

@ -11,7 +11,6 @@ import com.topjohnwu.magisk.*
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.extensions.get
import com.topjohnwu.magisk.model.update.UpdateCheckService
import com.topjohnwu.superuser.Shell
import com.topjohnwu.superuser.internal.UiThreadHandler
import java.io.File
import java.util.concurrent.TimeUnit
@ -34,7 +33,7 @@ object Utils {
}
fun showSuperUser(): Boolean {
return Shell.rootAccess() && (Const.USER_ID == 0
return Info.env.isActive && (Const.USER_ID == 0
|| Config.suMultiuserMode != Config.Value.MULTIUSER_MODE_OWNER_MANAGED)
}

View File

@ -1,37 +1,53 @@
package com.topjohnwu.magisk.view
import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Context
import android.os.Build
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import android.os.Build.VERSION.SDK_INT
import androidx.core.app.TaskStackBuilder
import androidx.core.content.getSystemService
import androidx.core.graphics.drawable.toIcon
import com.topjohnwu.magisk.*
import com.topjohnwu.magisk.Const.ID.PROGRESS_NOTIFICATION_CHANNEL
import com.topjohnwu.magisk.Const.ID.UPDATE_NOTIFICATION_CHANNEL
import com.topjohnwu.magisk.extensions.get
import com.topjohnwu.magisk.extensions.getBitmap
import com.topjohnwu.magisk.model.receiver.GeneralReceiver
import com.topjohnwu.magisk.ui.SplashActivity
object Notifications {
val mgr by lazy { NotificationManagerCompat.from(get()) }
private val icon by lazy { resolveRes(DynAPK.NOTIFICATION) }
val mgr by lazy { get<Context>().getSystemService<NotificationManager>()!! }
fun setup(context: Context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
mgr.deleteNotificationChannel("magisk_notification")
var channel = NotificationChannel(Const.ID.UPDATE_NOTIFICATION_CHANNEL,
if (SDK_INT >= 26) {
var channel = NotificationChannel(UPDATE_NOTIFICATION_CHANNEL,
context.getString(R.string.update_channel), NotificationManager.IMPORTANCE_DEFAULT)
mgr.createNotificationChannel(channel)
channel = NotificationChannel(Const.ID.PROGRESS_NOTIFICATION_CHANNEL,
channel = NotificationChannel(PROGRESS_NOTIFICATION_CHANNEL,
context.getString(R.string.progress_channel), NotificationManager.IMPORTANCE_LOW)
mgr.createNotificationChannel(channel)
}
}
private fun updateBuilder(context: Context): Notification.Builder {
return Notification.Builder(context).apply {
val bitmap = context.getBitmap(R.drawable.ic_magisk_outline)
setLargeIcon(bitmap)
if (SDK_INT >= 26) {
setSmallIcon(bitmap.toIcon())
setChannelId(UPDATE_NOTIFICATION_CHANNEL)
} else {
setSmallIcon(R.drawable.ic_magisk_outline)
setVibrate(longArrayOf(0, 100, 100, 100))
}
}
}
fun magiskUpdate(context: Context) {
val intent = context.intent(SplashActivity::class.java)
val intent = context.intent<SplashActivity>()
.putExtra(Const.Key.OPEN_SECTION, "magisk")
val stackBuilder = TaskStackBuilder.create(context)
stackBuilder.addParentStack(SplashActivity::class.java.cmp(context.packageName))
@ -39,59 +55,66 @@ object Notifications {
val pendingIntent = stackBuilder.getPendingIntent(Const.ID.MAGISK_UPDATE_NOTIFICATION_ID,
PendingIntent.FLAG_UPDATE_CURRENT)
val builder = NotificationCompat.Builder(context, Const.ID.UPDATE_NOTIFICATION_CHANNEL)
builder.setSmallIcon(icon)
.setContentTitle(context.getString(R.string.magisk_update_title))
.setContentText(context.getString(R.string.manager_download_install))
.setVibrate(longArrayOf(0, 100, 100, 100))
.setAutoCancel(true)
.setContentIntent(pendingIntent)
val builder = updateBuilder(context)
.setContentTitle(context.getString(R.string.magisk_update_title))
.setContentText(context.getString(R.string.manager_download_install))
.setAutoCancel(true)
.setContentIntent(pendingIntent)
mgr.notify(Const.ID.MAGISK_UPDATE_NOTIFICATION_ID, builder.build())
}
fun managerUpdate(context: Context) {
val intent = context.intent(GeneralReceiver::class.java)
val intent = context.intent<GeneralReceiver>()
.setAction(Const.Key.BROADCAST_MANAGER_UPDATE)
.putExtra(Const.Key.INTENT_SET_APP, Info.remote.app)
val pendingIntent = PendingIntent.getBroadcast(context,
Const.ID.APK_UPDATE_NOTIFICATION_ID, intent, PendingIntent.FLAG_UPDATE_CURRENT)
val builder = NotificationCompat.Builder(context, Const.ID.UPDATE_NOTIFICATION_CHANNEL)
builder.setSmallIcon(icon)
.setContentTitle(context.getString(R.string.manager_update_title))
.setContentText(context.getString(R.string.manager_download_install))
.setVibrate(longArrayOf(0, 100, 100, 100))
.setAutoCancel(true)
.setContentIntent(pendingIntent)
val builder = updateBuilder(context)
.setContentTitle(context.getString(R.string.manager_update_title))
.setContentText(context.getString(R.string.manager_download_install))
.setAutoCancel(true)
.setContentIntent(pendingIntent)
mgr.notify(Const.ID.APK_UPDATE_NOTIFICATION_ID, builder.build())
}
fun dtboPatched(context: Context) {
val intent = context.intent(GeneralReceiver::class.java)
val intent = context.intent<GeneralReceiver>()
.setAction(Const.Key.BROADCAST_REBOOT)
val pendingIntent = PendingIntent.getBroadcast(context,
Const.ID.DTBO_NOTIFICATION_ID, intent, PendingIntent.FLAG_UPDATE_CURRENT)
val builder = NotificationCompat.Builder(context, Const.ID.UPDATE_NOTIFICATION_CHANNEL)
builder.setSmallIcon(icon)
.setContentTitle(context.getString(R.string.dtbo_patched_title))
.setContentText(context.getString(R.string.dtbo_patched_reboot))
.setVibrate(longArrayOf(0, 100, 100, 100))
.addAction(R.drawable.ic_refresh, context.getString(R.string.reboot), pendingIntent)
val builder = updateBuilder(context)
.setContentTitle(context.getString(R.string.dtbo_patched_title))
.setContentText(context.getString(R.string.dtbo_patched_reboot))
if (SDK_INT >= 23) {
val action = Notification.Action.Builder(
context.getBitmap(R.drawable.ic_refresh).toIcon(),
context.getString(R.string.reboot), pendingIntent).build()
builder.addAction(action)
} else {
builder.addAction(
R.drawable.ic_refresh,
context.getString(R.string.reboot), pendingIntent)
}
mgr.notify(Const.ID.DTBO_NOTIFICATION_ID, builder.build())
}
fun progress(context: Context, title: CharSequence): NotificationCompat.Builder {
val builder = NotificationCompat.Builder(context, Const.ID.PROGRESS_NOTIFICATION_CHANNEL)
builder.setPriority(NotificationCompat.PRIORITY_LOW)
.setSmallIcon(android.R.drawable.stat_sys_download)
.setContentTitle(title)
.setProgress(0, 0, true)
.setOngoing(true)
fun progress(context: Context, title: CharSequence): Notification.Builder {
val builder = if (SDK_INT >= 26) {
Notification.Builder(context, PROGRESS_NOTIFICATION_CHANNEL)
} else {
Notification.Builder(context).setPriority(Notification.PRIORITY_LOW)
}
builder.setSmallIcon(android.R.drawable.stat_sys_download)
.setContentTitle(title)
.setProgress(0, 0, true)
.setOngoing(true)
return builder
}
}

View File

@ -7,16 +7,19 @@ import android.content.pm.ShortcutManager
import android.graphics.drawable.Icon
import android.os.Build
import androidx.annotation.RequiresApi
import androidx.core.content.getSystemService
import androidx.core.graphics.drawable.toAdaptiveIcon
import androidx.core.graphics.drawable.toIcon
import com.topjohnwu.magisk.*
import com.topjohnwu.magisk.extensions.getBitmap
import com.topjohnwu.magisk.ui.SplashActivity
import com.topjohnwu.magisk.utils.Utils
import com.topjohnwu.superuser.Shell
object Shortcuts {
fun setup(context: Context) {
if (Build.VERSION.SDK_INT >= 25) {
val manager = context.getSystemService(ShortcutManager::class.java)
val manager = context.getSystemService<ShortcutManager>()
manager?.dynamicShortcuts = getShortCuts(context)
}
}
@ -24,49 +27,72 @@ object Shortcuts {
@RequiresApi(api = 25)
private fun getShortCuts(context: Context): List<ShortcutInfo> {
val shortCuts = mutableListOf<ShortcutInfo>()
val root = Shell.rootAccess()
val intent = context.intent(SplashActivity::class.java)
val intent = context.intent<SplashActivity>()
fun getIcon(id: Int): Icon {
return if (Build.VERSION.SDK_INT >= 26)
context.getBitmap(id).toAdaptiveIcon()
else
context.getBitmap(id).toIcon()
}
if (Utils.showSuperUser()) {
shortCuts.add(ShortcutInfo.Builder(context, "superuser")
shortCuts.add(
ShortcutInfo.Builder(context, "superuser")
.setShortLabel(context.getString(R.string.superuser))
.setIntent(Intent(intent)
.setIntent(
Intent(intent)
.putExtra(Const.Key.OPEN_SECTION, "superuser")
.setAction(Intent.ACTION_VIEW)
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK))
.setIcon(Icon.createWithResource(context, resolveRes(DynAPK.SUPERUSER)))
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
)
.setIcon(getIcon(R.drawable.sc_superuser))
.setRank(0)
.build())
.build()
)
}
if (root && Info.env.magiskHide) {
shortCuts.add(ShortcutInfo.Builder(context, "magiskhide")
if (Info.env.magiskHide) {
shortCuts.add(
ShortcutInfo.Builder(context, "magiskhide")
.setShortLabel(context.getString(R.string.magiskhide))
.setIntent(Intent(intent)
.setIntent(
Intent(intent)
.putExtra(Const.Key.OPEN_SECTION, "magiskhide")
.setAction(Intent.ACTION_VIEW)
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK))
.setIcon(Icon.createWithResource(context, resolveRes(DynAPK.MAGISKHIDE)))
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
)
.setIcon(getIcon(R.drawable.sc_magiskhide))
.setRank(1)
.build())
.build()
)
}
if (!Config.coreOnly && root && Info.env.magiskVersionCode >= 0) {
shortCuts.add(ShortcutInfo.Builder(context, "modules")
if (!Config.coreOnly && Info.env.isActive) {
shortCuts.add(
ShortcutInfo.Builder(context, "modules")
.setShortLabel(context.getString(R.string.modules))
.setIntent(Intent(intent)
.setIntent(
Intent(intent)
.putExtra(Const.Key.OPEN_SECTION, "modules")
.setAction(Intent.ACTION_VIEW)
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK))
.setIcon(Icon.createWithResource(context, resolveRes(DynAPK.MODULES)))
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
)
.setIcon(getIcon(R.drawable.sc_extension))
.setRank(3)
.build())
shortCuts.add(ShortcutInfo.Builder(context, "downloads")
.build()
)
shortCuts.add(
ShortcutInfo.Builder(context, "downloads")
.setShortLabel(context.getString(R.string.downloads))
.setIntent(Intent(intent)
.setIntent(
Intent(intent)
.putExtra(Const.Key.OPEN_SECTION, "downloads")
.setAction(Intent.ACTION_VIEW)
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK))
.setIcon(Icon.createWithResource(context, resolveRes(DynAPK.DOWNLOAD)))
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
)
.setIcon(getIcon(R.drawable.sc_cloud_download))
.setRank(2)
.build())
.build()
)
}
return shortCuts
}

View File

@ -1,88 +0,0 @@
package com.topjohnwu.magisk.view.dialogs
import android.annotation.TargetApi
import android.app.Activity
import android.graphics.Color
import android.hardware.fingerprint.FingerprintManager
import android.os.Build
import android.view.Gravity
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.core.content.ContextCompat
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.utils.FingerprintHelper
import com.topjohnwu.magisk.utils.Utils
@TargetApi(Build.VERSION_CODES.M)
class FingerprintAuthDialog(activity: Activity, private val callback: () -> Unit)
: CustomAlertDialog(activity) {
private var failureCallback: (() -> Unit)? = null
private var helper: DialogFingerprintHelper? = null
init {
val fingerprint = ContextCompat.getDrawable(activity, R.drawable.ic_fingerprint)
fingerprint?.setBounds(0, 0, Utils.dpInPx(50), Utils.dpInPx(50))
val theme = activity.theme
val ta = theme.obtainStyledAttributes(intArrayOf(R.attr.imageColorTint))
fingerprint?.setTint(ta.getColor(0, Color.GRAY))
ta.recycle()
binding.message.setCompoundDrawables(null, null, null, fingerprint)
binding.message.compoundDrawablePadding = Utils.dpInPx(20)
binding.message.gravity = Gravity.CENTER
setMessage(R.string.auth_fingerprint)
setNegativeButton(android.R.string.cancel) { _, _ ->
helper?.cancel()
failureCallback?.invoke()
}
setOnCancelListener {
helper?.cancel()
failureCallback?.invoke()
}
runCatching {
helper = DialogFingerprintHelper()
}
}
constructor(activity: Activity, onSuccess: () -> Unit, onFailure: () -> Unit)
: this(activity, onSuccess) {
failureCallback = onFailure
}
override fun show(): AlertDialog {
return create().apply {
if (helper == null) {
dismiss()
Utils.toast(R.string.auth_fail, Toast.LENGTH_SHORT)
} else {
helper?.authenticate()
show()
}
}
}
internal inner class DialogFingerprintHelper @Throws(Exception::class)
constructor() : FingerprintHelper() {
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
binding.message.setTextColor(Color.RED)
binding.message.text = errString
}
override fun onAuthenticationHelp(helpCode: Int, helpString: CharSequence) {
binding.message.setTextColor(Color.RED)
binding.message.text = helpString
}
override fun onAuthenticationFailed() {
binding.message.setTextColor(Color.RED)
binding.message.setText(R.string.auth_fail)
}
override fun onAuthenticationSucceeded(result: FingerprintManager.AuthenticationResult) {
dismiss()
callback()
}
}
}

View File

@ -129,24 +129,14 @@
<com.google.android.material.button.MaterialButton
android:id="@+id/grant_btn"
style="@style/Widget.Button.Text"
gone="@{viewModel.canUseFingerprint}"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:onClick="@{() -> viewModel.grantPressed()}"
android:text="@string/grant" />
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/fingerprint"
gone="@{!viewModel.canUseFingerprint}"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:padding="7dp"
android:tint="?attr/colorAccent"
app:srcCompat="@drawable/ic_fingerprint" />
</LinearLayout>
</LinearLayout>
</layout>
</layout>

View File

@ -15,8 +15,6 @@
<import type="com.topjohnwu.magisk.R" />
<import type="com.topjohnwu.magisk.extensions.XStringKt" />
<variable
name="viewModel"
type="com.topjohnwu.magisk.ui.home.HomeViewModel" />
@ -203,7 +201,7 @@
<View
style="@style/Widget.Divider.Horizontal"
gone="@{!viewModel.hasRoot || !viewModel.isConnected}"
gone="@{!viewModel.isActive || !viewModel.isConnected}"
android:layout_width="match_parent"
android:layout_margin="@dimen/margin_generic" />
@ -239,7 +237,7 @@
android:layout_marginRight="@dimen/margin_generic"
android:gravity="center"
android:maxLines="1"
android:text="@{XStringKt.res(viewModel.safetyNetTitle)}"
android:text="@{viewModel.safetyNetTitle}"
android:textStyle="bold"
app:autoSizeMaxTextSize="14sp"
app:autoSizeTextType="uniform"
@ -484,13 +482,13 @@
<View
style="@style/Widget.Divider.Horizontal"
gone="@{!viewModel.hasRoot}"
gone="@{!viewModel.isActive}"
android:layout_width="match_parent"
android:layout_margin="@dimen/margin_generic" />
<com.google.android.material.button.MaterialButton
style="@style/Widget.Button.Text"
gone="@{!viewModel.hasRoot}"
gone="@{!viewModel.isActive}"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="@{() -> viewModel.uninstallPressed()}"

View File

@ -1,4 +1,5 @@
# v7.3.5
- Sort installed modules by name
- Better pre-5.0 support
- Fix potential issues when patching tar files
# v7.4.0
- Hide Magisk Manager with stub APKs on Android 9.0+. Not all devices will be supported, please refer to Magisk v20.1 release notes.
- Allow customizing app name when hiding Magisk Manager
- Generate random keys to sign the hidden Magisk Manager to prevent signature detections
- Fix fingerprint UI infinite loop

View File

@ -9,7 +9,6 @@
<string name="settings">الإعدادات</string>
<string name="install">التثبيت</string>
<string name="unsupport_magisk_title">إصدار Magisk غير مدعوم</string>
<string name="unsupport_magisk_message">لا يدعم هذا الإصدار من Magisk Manager إصدارا لـ Magisk vأقل من 18\n\n بإمكانك تحديث Magisk يدويا أو تثبيت إصدار أدنى.</string>
<!--Status Fragment-->
<string name="magisk_version_error">Magisk غير مثبت</string>
@ -164,9 +163,6 @@
<string name="request_timeout_summary">%1$d ثانية</string>
<string name="settings_su_reauth_title">إعادة المصادقة بعد الترقية</string>
<string name="settings_su_reauth_summary">أعد المصادقة على صلاحيات المستخدم المتميز بعد إجراء ترقيات للتطبيق</string>
<string name="settings_su_fingerprint_summary">استخدام قارئ بصمات الأصابع للسماح بطلبات المستخدم المتميز</string>
<string name="settings_su_fingerprint_title">تمكين مصادقة البصمة</string>
<string name="auth_fingerprint">مصادقة البصمة</string>
<string name="multiuser_mode">نمط تعدد المستخدمين</string>
<string name="settings_owner_only">مالك الجهاز فقط</string>
@ -183,7 +179,6 @@
<string name="global_summary">تستخدم كافة جلسات العمل للروت مساحة الاسم ذات التركيب العامة</string>
<string name="requester_summary">سترث جلسات العمل للروت مساحة الأسماء لطالبيها</string>
<string name="isolate_summary">سيكون لكل جلسة عمل للروت مساحة اسم معزولة خاصة بها </string>
<string name="disable_fingerprint">لم تُعين بصمات الأصابع أو لا يوجد قارئ بصمات</string>
<string name="settings_download_path_error">خطأ عند إنشاء مجلد. عليه أن يكون سهلا الوصول إليه من خلال مجلد التخزين للروت و ألا يكون ملفا.</string>
<!--Superuser-->
@ -211,7 +206,6 @@
<string name="su_revoke_msg">تأكيد لسحب صلاحيات %1$s ?</string>
<string name="toast">ملاحظة منبثقة</string>
<string name="none">بدون</string>
<string name="auth_fail">فشل المصادقة</string>
<!--Superuser logs-->
<string name="pid">PID: %1$d</string>

View File

@ -8,7 +8,6 @@
<string name="settings">Tənzimləmələr</string>
<string name="install">Quraşdır</string>
<string name="unsupport_magisk_title">Dəstəklənməyən Magisk Versiyası</string>
<string name="unsupport_magisk_message">Magisk Manager\'in bu versiyası Magisk\'in v18.0 versiyasndan aşağısını dəstəkləmir.\n\nMagisk\'i əllə yüksəldə, yaxud tətbiqi əvvəlki versiyalarına qaytara bilərsiniz.</string>
<!--Status Fragment-->
<string name="magisk_version_error">Magisk yüklənməyib.</string>
@ -152,9 +151,6 @@
<string name="request_timeout_summary">%1$d saniyə</string>
<string name="settings_su_reauth_title">Yüksəltmədən sonra Yenidən İdentifikasiya et</string>
<string name="settings_su_reauth_summary">Tətbiq yeniləmələridən sonra superuser icazələrini yenidən identifikasiya et</string>
<string name="settings_su_fingerprint_title">Barmaq İzi İdentifikasiyasını</string>
<string name="settings_su_fingerprint_summary">Barmaq izi oxuyucunu superuser icazələri üçün işlət</string>
<string name="auth_fingerprint">Barmaq izini İdentifikasiya et</string>
<string name="multiuser_mode">Çox-istifadəçi modu</string>
<string name="settings_owner_only">Yalnız cihaz sahibi</string>
<string name="settings_owner_manage">Cihaz sahibinin idarəçiliyində</string>
@ -169,7 +165,6 @@
<string name="global_summary">Bütün root sessyaları qlobal qoşma namespace\'dən istifadə edir.</string>
<string name="requester_summary">Root sessyaları soruşulan namespace\'ləri birindən digərinə keçirəcək.</string>
<string name="isolate_summary">Hər bir root sessyasının ayrılmış namespace\'i olacaq.</string>
<string name="disable_fingerprint">Barmaq izi təyin edilməyib ya da dəstəklənmir.</string>
<!--Superuser-->
<string name="su_request_title">Superuser Tələbi</string>
@ -196,7 +191,6 @@
<string name="su_revoke_msg">%1$s üçün haqları ləğv etməyi təsdiq edirsiniz?</string>
<string name="toast">Tost</string>
<string name="none">Heçnə</string>
<string name="auth_fail">İdentifikasiya xətası</string>
<!--Superuser logs-->
<string name="pid">PID: %1$d</string>

View File

@ -142,9 +142,6 @@
<string name="request_timeout_summary">%1$d секунди</string>
<string name="settings_su_reauth_title">Повторно запитване след актуализация</string>
<string name="settings_su_reauth_summary">Повторно запитване за Superuser достъп след актуализация на приложенията.</string>
<string name="settings_su_fingerprint_title">Superuser права само с пръстов отпечатък</string>
<string name="settings_su_fingerprint_summary">Използване на сензора за пръстови отпечатъци за разрешаване на Superuser достъп.</string>
<string name="auth_fingerprint">Удостоверете с пръстов отпечатък.</string>
<string name="multiuser_mode">Потребителски достъп</string>
<string name="settings_owner_only">Само собственик</string>
@ -161,7 +158,6 @@
<string name="global_summary">Всички сесии с руут достъп използват глобалното именно пространство.</string>
<string name="requester_summary">Всички сесии с руут достъп наследяват именното пространство на запитващото приложение.</string>
<string name="isolate_summary">Всички сесии с руут достъп имат собствени именни пространства.</string>
<string name="disable_fingerprint">Не са добавени пръстови отпечатъци или устройството не поддържа тази функция.</string>
<!--Superuser-->
<string name="su_request_title">Запитване за Superuser достъп</string>
@ -188,7 +184,6 @@
<string name="su_revoke_msg">Потвърждавате ли анулирането на настройките за достъп на %1$s?</string>
<string name="toast">Toast</string>
<string name="none">Без</string>
<string name="auth_fail">Неуспешна заверка.</string>
<!--Superuser logs-->
<string name="target_uid">Целеви UID: %1$d</string>

View File

@ -8,7 +8,6 @@
<string name="settings">Configuració</string>
<string name="install">Instal·lar</string>
<string name="unsupport_magisk_title">Versió de Magisk incompatible</string>
<string name="unsupport_magisk_message">Aquesta versió de Magisk Manager no suporta una versió inferior a la 18.0.\n\nPots actualitzar Magisk manualment o pots instalar una versió anterior de la app.</string>
<!--Status Fragment-->
<string name="magisk_version_error">Magisk no està instal·lat</string>
@ -167,9 +166,6 @@
<string name="request_timeout_summary">%1$d segons</string>
<string name="settings_su_reauth_title">Demanar després d\'una actualització</string>
<string name="settings_su_reauth_summary">Demanar permisos de superusuari novament si una aplicació és actualitzada o reinstal·lada</string>
<string name="settings_su_fingerprint_title">Autenticació per Empremta Dactilar</string>
<string name="settings_su_fingerprint_summary">Utilitza el sensor d\'Empremta Dactilar per permetre les sol·licituds de superusuari</string>
<string name="auth_fingerprint">Autenticar Emprempta Digital</string>
<string name="multiuser_mode">Mode Multiusuari</string>
<string name="settings_owner_only">Només Administrador del Dispositiu</string>
@ -186,7 +182,6 @@
<string name="global_summary">Totes les sessions d\'arrel utilitzen el suport Namespace Global</string>
<string name="requester_summary">Les sessions d\'arrel heretaran les peticiones Namespace</string>
<string name="isolate_summary">Totes les sessions d\'arrel tindran la seva pròpia Namespace</string>
<string name="disable_fingerprint">No s\'han establert empremtes dactilars o no existeix el suport del dispositiu</string>
<string name="settings_download_path_error">Error al crear la carpeta. El directori ha de ser accesible desde el directori arrel i no pot ser un arxiu.</string>
<!--Superuser-->
@ -214,7 +209,6 @@
<string name="su_revoke_msg">Confirma per revocar drets de %1$s</string>
<string name="toast">Avís</string>
<string name="none">Cap</string>
<string name="auth_fail">Autenticació fallida</string>
<!--Superuser logs-->
<string name="pid">PID: %1$d</string>

View File

@ -8,7 +8,6 @@
<string name="settings">Nastavení</string>
<string name="install">Instalovat</string>
<string name="unsupport_magisk_title">Nepodporovaná verze Magisk</string>
<string name="unsupport_magisk_message">Tato verze Magisk Managera nepodporuje verzi Magisk nižší než v18.0.\n\nMůžete buď ručně aktualizovat Magisk, nebo aplikaci downgradovat na starší verzi.</string>
<!--Status Fragment-->
<string name="magisk_version_error">Magisk není nainstalován.</string>
@ -155,9 +154,6 @@
<string name="request_timeout_summary">%1$d sekund</string>
<string name="settings_su_reauth_title">Opětovné ověření po aktualizaci</string>
<string name="settings_su_reauth_summary">Opětovné ověření oprávnění Superuser po aktualizaci aplikace</string>
<string name="settings_su_fingerprint_title">Povolit ověřování otisky prstů</string>
<string name="settings_su_fingerprint_summary">Chcete-li povolit požadavky Superuser, použijte snímač otisků prstů</string>
<string name="auth_fingerprint">Ověřování otisky prstů</string>
<string name="multiuser_mode">Režim více uživatelů</string>
<string name="settings_owner_only">Pouze vlastník zařízení</string>
@ -174,7 +170,6 @@
<string name="global_summary">Všechny relace root používají globální připojení jmenného prostoru.</string>
<string name="requester_summary">Kořenové relace dědí jmenný prostor žadatele.</string>
<string name="isolate_summary">Každá relace root bude mít svůj vlastní izolovaný jmenný prostor.</string>
<string name="disable_fingerprint">Nebyly nastaveny žádné otisky prstů ani žádná podpora zařízení.</string>
<!--Superuser-->
<string name="su_request_title">Požadavek Superuser</string>
@ -201,7 +196,6 @@
<string name="su_revoke_msg">Smazat záznam ohledně oprávnění pro %1$s?</string>
<string name="toast">Informační text</string>
<string name="none">Žádný</string>
<string name="auth_fail">Ověření se nezdařilo</string>
<!--Superuser logs-->
<string name="pid">PID: %1$d</string>

View File

@ -8,7 +8,6 @@
<string name="settings">Einstellungen</string>
<string name="install">Installieren</string>
<string name="unsupport_magisk_title">Nicht unterstützte Magisk Version</string>
<string name="unsupport_magisk_message">Diese Version von Magisk Manager unterstützt keine Magisk-Version kleiner als 18.0. \n\nSie können entweder Magisk manuell aktualisieren oder die App auf eine ältere Version herabstufen.</string>
<!--Status Fragment-->
<string name="magisk_version_error">Magisk ist nicht installiert</string>
@ -157,9 +156,6 @@
<string name="request_timeout_summary">%1$d Sekunden</string>
<string name="settings_su_reauth_title">Nach Aktualisierung erneut authentifizieren</string>
<string name="settings_su_reauth_summary">Superuser-Zugriff nach App-Aktualisierung erneut abfragen</string>
<string name="settings_su_fingerprint_title">Aktiviere Authentifizierung durch Fingerabdruck</string>
<string name="settings_su_fingerprint_summary">Fingerabdrucksensor benutzen um Superuser-Anfragen zu erlauben</string>
<string name="auth_fingerprint">Authentifiziere Fingerabdruck</string>
<string name="multiuser_mode">Mehrbenutzermodus</string>
<string name="settings_owner_only">Nur der Gerätebesitzer</string>
@ -176,7 +172,6 @@
<string name="global_summary">Alle Root-Sitzungen benutzen den global angelegten Namespace</string>
<string name="requester_summary">Root-Sitzungen erben den Namespace des Abfragenden</string>
<string name="isolate_summary">Jede Root-Sitzung hat ihren isolierten Namespace</string>
<string name="disable_fingerprint">Keine Fingerabdrücke gespeichert oder keine Geräteunterstützung</string>
<!--Superuser-->
<string name="su_request_title">Superuser-Anfrage</string>
@ -203,7 +198,6 @@
<string name="su_revoke_msg">Möchtest du die Rechte für %1$s entziehen?</string>
<string name="toast">Popup</string>
<string name="none">Keine</string>
<string name="auth_fail">Authentifizierung fehlgeschlagen</string>
<!--Superuser logs-->
<string name="pid">PID: %1$d</string>

View File

@ -8,7 +8,6 @@
<string name="settings">Ajustes</string>
<string name="install">Instalar</string>
<string name="unsupport_magisk_title">Versión de Magisk no soportada</string>
<string name="unsupport_magisk_message">Esta versión de Magisk Manager no admite una versión de Magisk inferior a la v18.0.\n\nPuede actualizar Magisk de forma manual o instalar una versión anterior de la aplicación.</string>
<!--Status Fragment-->
<string name="magisk_version_error">Magisk no está instalado</string>
@ -114,6 +113,7 @@
<string name="env_fix_title">Se Requiere una Instalación Adicional</string>
<string name="env_fix_msg">Su dispositivo requiere una instalación adicional para que Magisk funcione correctamente. Se descargará el zip de instalación de Magisk, desea continuar ahora?</string>
<string name="setup_msg">Ejecutando Configuración de Entorno</string>
<string name="authenticate">Autenticar</string>
<!--Settings Activity -->
<string name="settings_general_category">General</string>
@ -160,10 +160,10 @@
<string name="request_timeout_summary">%1$d segundos</string>
<string name="settings_su_reauth_title">Re-autenticación</string>
<string name="settings_su_reauth_summary">Pedir permisos de superusuario nuevamente si una aplicación es actualizada o reinstalada</string>
<string name="settings_su_fingerprint_title">Autenticación por Huella Dactilar</string>
<string name="settings_su_fingerprint_summary">Utilizar el sensor de Huella Dactilar para permitir las solicitudes de superusuario</string>
<string name="auth_fingerprint">Autenticar Huella Dactilar</string>
<string name="settings_su_biometric_title">Habilitar autenticación biométrica</string>
<string name="settings_su_biometric_summary">Usar autenticación biométrica para permitir solicitudes de superusuario</string>
<string name="no_biometric">Dispositivo no compatible o las configuraciones biométricas no están habilitadas</string>
<string name="multiuser_mode">Modo MultiUsuario</string>
<string name="settings_owner_only">Sólo Administrador del Dispositivo</string>
<string name="settings_owner_manage">Administrador del Dispositivo</string>
@ -179,7 +179,6 @@
<string name="global_summary">Todas las sesiones de root utilizan el soporte Global Namespace</string>
<string name="requester_summary">Las sesiones de root heredarán las peticiones Namespace</string>
<string name="isolate_summary">Cada sesión root tendrá su propia Namespace</string>
<string name="disable_fingerprint">No se establecieron huellas dactilares o no existe soporte del dispositivo</string>
<string name="settings_download_path_error">Error al crear la carpeta. Debe ser accesible desde el directorio raíz de almacenamiento y no debe ser un archivo.</string>
<!--Superuser-->
@ -207,7 +206,6 @@
<string name="su_revoke_msg">¿Confirmar para revocar derechos de %1$s?</string>
<string name="toast">Aviso</string>
<string name="none">Nada</string>
<string name="auth_fail">Autenticación fallida</string>
<!--Superuser logs-->
<string name="pid">PID: %1$d</string>

View File

@ -8,7 +8,6 @@
<string name="settings">Seaded</string>
<string name="install">Installi</string>
<string name="unsupport_magisk_title">Mittetoetatud Magisk\'i versioon</string>
<string name="unsupport_magisk_message">See Magisk Manager\'i versioon ei toeta Magisk\'ist vanemat versiooni kui v18.0.\n\nSa võid kas Magisk\'i käsitsi täiendada või alandad rakenduse vanemale versioonile.</string>
<!--Status Fragment-->
<string name="magisk_version_error">Magisk pole installitud</string>
@ -163,9 +162,6 @@
<string name="request_timeout_summary">%1$d sekundit</string>
<string name="settings_su_reauth_title">Taas-autendi peale täiendust</string>
<string name="settings_su_reauth_summary">Taas-autendi superkasutaja õigused peale rakenduse täiendamist</string>
<string name="settings_su_fingerprint_title">Luba sõrmejäljega autentimine</string>
<string name="settings_su_fingerprint_summary">Kasuta sõrmejäljelugejat superkasutaja taotluste lubamiseks</string>
<string name="auth_fingerprint">Autendi sõrmejälg</string>
<string name="multiuser_mode">Mitmikkasutaja režiim</string>
<string name="settings_owner_only">Ainult seadme omanik</string>
@ -182,7 +178,6 @@
<string name="global_summary">Kõik juurkasutaja sessioonid kasutavad globaalset monteerimise nimeruumi.</string>
<string name="requester_summary">Juurkasutaja sessioonid võtavad üle selle taotleja nimeruumi.</string>
<string name="isolate_summary">Iga juurkasutaja sessioon saab oma isoleeritud nimeruumi.</string>
<string name="disable_fingerprint">Sõrmejälgi pole määratud või seade pole toetatud.</string>
<string name="settings_download_path_error">Faili loomisel esines viga. See peab olema ligipääsetav mäluruumi juurkaustast ning ei tohi olla fail.</string>
<!--Superuser-->
@ -210,7 +205,6 @@
<string name="su_revoke_msg">Kinnitad rakenduse %1$s õiguste eemaldamise?</string>
<string name="toast">Hüpik</string>
<string name="none">Puudub</string>
<string name="auth_fail">Autentimine ebaõnnestus</string>
<!--Superuser logs-->
<string name="pid">PID: %1$d</string>

View File

@ -8,7 +8,6 @@
<string name="settings">Paramètres</string>
<string name="install">Installer</string>
<string name="unsupport_magisk_title">Version de Magisk non prise en charge</string>
<string name="unsupport_magisk_message">Cette version de Magisk Manager ne prend pas en charge les versions de Magisk inférieures à v18.0.\n\nSi vous continuez, toutes les fonctionnalités de Magisk ne seront pas disponibles et vous ne pourrez que mettre à jour Magisk.</string>
<!--Status Fragment-->
<string name="magisk_version_error">Magisk nest pas installé.</string>
@ -145,7 +144,10 @@
<string name="settings_hosts_title">Fichier hosts sans système</string>
<string name="settings_hosts_summary">Prise en charge du fichier hosts sans système pour les applications de type AdBlock.</string>
<string name="settings_hosts_toast">Ajout dun module hosts sans système</string>
<string name="settings_app_name">Taper le nom de l\'application désirée</string>
<string name="settings_app_name_hint">Nouveau nom</string>
<string name="settings_app_name_helper">L\'application sera réempacter sous ce nom</string>
<string name="settings_app_name_error">Format invalide</string>
<string name="settings_su_app_adb">Applications et ADB</string>
<string name="settings_su_app">Applications uniquement</string>
<string name="settings_su_adb">ADB uniquement</string>
@ -163,9 +165,6 @@
<string name="request_timeout_summary">%1$d secondes</string>
<string name="settings_su_reauth_title">Authentifier à nouveau après la mise à niveau</string>
<string name="settings_su_reauth_summary">Authentifier à nouveau les autorisations superutilisateur après une mise à jour de lapplication</string>
<string name="settings_su_fingerprint_title">Activer lauthentification par empreinte digitale</string>
<string name="settings_su_fingerprint_summary">Utiliser le lecteur dempreintes digitales pour autoriser les demandes superutilisateur</string>
<string name="auth_fingerprint">Authentifier lempreinte digitale</string>
<string name="multiuser_mode">Mode multiutilisateur</string>
<string name="settings_owner_only">Propriétaire de lappareil uniquement</string>
@ -182,7 +181,6 @@
<string name="global_summary">Toutes les sessions superutilisateur utilisent lespace de noms global du montage.</string>
<string name="requester_summary">Les sessions superutilisateur hériteront de lespace de noms de leur demandeur.</string>
<string name="isolate_summary">Chaque session superutilisateur aura son propre espace de noms isolé.</string>
<string name="disable_fingerprint">Aucune empreinte digitale na été définie ou le lecteur dempreinte nest pas pris en charge.</string>
<string name="settings_download_path_error">Erreur lors de la création du dossier. Il doit être accessible depuis le répertoire racine du stockage et ne doit pas être un fichier.</string>
<!--Superuser-->
@ -210,7 +208,6 @@
<string name="su_revoke_msg">Confirmezvous lannulation des droits pour %1$s?</string>
<string name="toast">Toast</string>
<string name="none">Aucun</string>
<string name="auth_fail">Échec de lauthentification</string>
<!--Superuser logs-->
<string name="pid">PID : %1$d</string>

View File

@ -8,7 +8,6 @@
<string name="settings">सेटिंग्स</string>
<string name="install">स्थापित करें</string>
<string name="unsupport_magisk_title">असमर्थित Magisk संस्करण</string>
<string name="unsupport_magisk_message">Magisk Manager का यह संस्करण Magisk के v18.0 संस्करण से कम का समर्थन नहीं करता है.\n\nआप या तो खुद से Magisk को अपग्रेड करें, या फिर एप्लीकेशन को पुराने संस्करण पे डाउनग्रेड करें .</string>
<!--Status Fragment-->
<string name="magisk_version_error">Magisk स्थापित नहीं है</string>
@ -163,9 +162,6 @@
<string name="request_timeout_summary">%1$d सेकंड्‌स</string>
<string name="settings_su_reauth_title">अपग्रेड के बाद फिर से प्रमाणित करें</string>
<string name="settings_su_reauth_summary">एप्लीकेशन अपग्रेड होने के बाद उत्तम उपयोगकर्ता की अनुमतियों को फिर से प्रमाणित करें</string>
<string name="settings_su_fingerprint_title">फिंगरप्रिंट प्रमाणीकरण सक्षम करें</string>
<string name="settings_su_fingerprint_summary">उत्तम उपयोगकर्ता के अनुरोधों की अनुमति के लिए फिंगरप्रिंट स्कैनर का उपयोग करें</string>
<string name="auth_fingerprint">फिंगरप्रिंट को प्रमाणित करें</string>
<string name="multiuser_mode">बहु उपयोगकर्ता मोड</string>
<string name="settings_owner_only">केवल डिवाइस का मालिक</string>
@ -182,7 +178,6 @@
<string name="global_summary">सभी रूट सत्र वैश्विक माउंट नेमस्पेस का उपयोग करते हैं.</string>
<string name="requester_summary">रूट सत्रों को उनके अनुरोधकर्ताओं के नेमस्पेस विरासत में मिलेंगे.</string>
<string name="isolate_summary">प्रत्येक रूट सत्र का अपना अलग नेमस्पेस होगा.</string>
<string name="disable_fingerprint">कोई फ़िंगरप्रिंट नहीं सेट किया गया या डिवाइस का समर्थन नहीं है.</string>
<string name="settings_download_path_error">फोल्डर बनाने में त्रुटि. यह स्टोरेज रूट डायरेक्टरी से एक्सेस होना चाहिए और फाइल नहीं होना चाहिए.</string>
<!--Superuser-->
@ -210,7 +205,6 @@
<string name="su_revoke_msg">%1$s के अधिकारों को वापस लेने की पुष्टि करें?</string>
<string name="toast">पॉप-अप नोट</string>
<string name="none">कोई नहीं</string>
<string name="auth_fail">प्रमाणीकरण विफल हुआ</string>
<!--Superuser logs-->
<string name="pid">PID: %1$d</string>

View File

@ -7,7 +7,6 @@
<string name="settings">Setelan</string>
<string name="install">Pasang</string>
<string name="unsupport_magisk_title">Versi Magisk Tidak Didukung</string>
<string name="unsupport_magisk_message">Magisk Manager versi ini tidak mendukung versi Magisk di bawah v18.0.\n\nAnda dapat memperbarui Magisk secara manual, atau menurunkan aplikasi ke versi sebelumnya.</string>
<!--Status Fragment-->
<string name="magisk_version_error">Magisk tidak terpasang.</string>
@ -161,9 +160,6 @@
<string name="request_timeout_summary">%1$d detik</string>
<string name="settings_su_reauth_title">Otentikasi ulang setelah pembaruan</string>
<string name="settings_su_reauth_summary">Otentikasi ulang izin superuser setelah pembaruan sebuah aplikasi</string>
<string name="settings_su_fingerprint_title">Aktifkan Otentikasi Sidik Jari</string>
<string name="settings_su_fingerprint_summary">Gunakan pemindai sidik jari untuk mengizinkan permintaan superuser</string>
<string name="auth_fingerprint">Otentikasi Sidik Jari</string>
<string name="multiuser_mode">Mode Multipengguna</string>
<string name="settings_owner_only">Pemilik Perangkat Saja</string>
@ -180,7 +176,6 @@
<string name="global_summary">Semua sesi root menggunakan mount ruang nama global.</string>
<string name="requester_summary">Sesi root akan mewarisi ruang nama peminta mereka.</string>
<string name="isolate_summary">Setiap sesi root akan memiliki ruang nama tersendiri.</string>
<string name="disable_fingerprint">Tidak ada sidik jari diatur atau tidak ada dukungan perangkat.</string>
<string name="settings_download_path_error">Kesalahan membuat folder. Folder harus dapat diakses dari direktori penyimpanan root dan bukan merupakan file.</string>
<!--Superuser-->
@ -208,7 +203,6 @@
<string name="su_revoke_msg">Konfirmasi untuk mencabut akses %1$s?</string>
<string name="toast">Toast</string>
<string name="none">Tidak ada</string>
<string name="auth_fail">Otentikasi Gagal</string>
<!--Superuser logs-->
<string name="pid">PID: %1$d</string>

View File

@ -8,7 +8,6 @@
<string name="settings">Impostazioni</string>
<string name="install">Installa</string>
<string name="unsupport_magisk_title">Versione di Magisk non supportata</string>
<string name="unsupport_magisk_message">Questa versione di Magisk Manager non supporta versioni di Magisk inferiori alla v18.0.\n\nPuoi aggiornare manualmente Magisk o tornare a una versione meno recente dell\'app.</string>
<!--Status Fragment-->
<string name="magisk_version_error">Magisk non è installato.</string>
@ -18,7 +17,7 @@
<string name="checking_safetyNet_status">Controllo stato SafetyNet…</string>
<string name="safetyNet_check_success">Controllo SafetyNet Superato</string>
<string name="safetyNet_api_error">Errore API SafetyNet</string>
<string name="safetyNet_res_invalid">La risposta non è valida.</string>
<string name="safetyNet_res_invalid">La risposta non è valida</string>
<string name="magisk_up_to_date">Magisk è aggiornato</string>
<string name="manager_up_to_date">Magisk Manager è aggiornato</string>
<string name="advanced_settings_title">Impostazioni avanzate</string>
@ -28,7 +27,7 @@
<string name="latest_version">Ultima: %1$s</string>
<string name="uninstall">Disinstalla</string>
<string name="uninstall_magisk_title">Disinstalla Magisk</string>
<string name="uninstall_magisk_msg">Tutti i moduli verranno disabilitati/rimossi. Il root verrà rimosso e i tuoi dati potrebbero essere criptati, nel caso non lo siano già.</string>
<string name="uninstall_magisk_msg">Tutti i moduli verranno disabilitati/rimossi!\nIl root verrà rimosso e i tuoi dati potrebbero essere criptati, nel caso non lo siano già.</string>
<string name="update">Aggiorna</string>
<string name="core_only_enabled">(Modalità solo Core abilitata)</string>
@ -74,7 +73,7 @@
<string name="manager_update_title">È disponibile un aggiornamento di Magisk Manager!</string>
<!-- Installation -->
<string name="manager_download_install">Apri questa notifica per scaricare e installare.</string>
<string name="manager_download_install">Apri questa notifica per scaricare e installare</string>
<string name="download_zip_only">Scarica solo il file zip</string>
<string name="direct_install">Installazione diretta (raccomandata)</string>
<string name="install_inactive_slot">Installa nello slot inattivo (dopo un OTA)</string>
@ -90,7 +89,7 @@
<string name="repo_install_msg">Vuoi installare %1$s?</string>
<string name="download">Download</string>
<string name="reboot">Riavvia</string>
<string name="settings_reboot_toast">Riavvia per applicare i cambiamenti.</string>
<string name="settings_reboot_toast">Riavvia per applicare i cambiamenti</string>
<string name="release_notes">Note di rilascio</string>
<string name="repo_cache_cleared">La cache delle repository è stata svuotata</string>
@ -101,7 +100,7 @@
<string name="failure">Fallito</string>
<string name="hide_manager_title">Nascondendo Magisk Manager…</string>
<string name="hide_manager_fail_toast">Non è stato possibile nascondere Magisk Manager.</string>
<string name="open_link_failed_toast">Nessuna app disponibile per aprire il link.</string>
<string name="open_link_failed_toast">Nessuna app disponibile per aprire il link</string>
<string name="warning">Attenzione</string>
<string name="complete_uninstall">Disinstallazione completa</string>
<string name="restore_img">Ripristina Immagini</string>
@ -110,7 +109,7 @@
<string name="restore_fail">Non esiste un\'immagine originale di boot!</string>
<string name="proprietary_title">Scarica codice proprietario</string>
<string name="proprietary_notice">Magisk Manager è FOSS e non contiene codice proprietario delle API Google SafetyNet.\n\nVuoi scaricare un\'estensione (contenente GoogleApiClient) per controllare lo stato di SafetyNet?</string>
<string name="setup_fail">Configurazione fallita.</string>
<string name="setup_fail">Configurazione fallita</string>
<string name="env_fix_title">Configurazione aggiuntiva richiesta</string>
<string name="env_fix_msg">Il tuo dispositivo necessita di una configurazione aggiuntiva per far funzionare Magisk correttamente. Verrà scaricato il file zip di Magisk, vuoi procedere ora?</string>
<string name="setup_msg">Configurazione dell\'ambiente in corso…</string>
@ -118,20 +117,20 @@
<!--Settings Activity -->
<string name="settings_general_category">Generale</string>
<string name="settings_dark_theme_title">Tema scuro</string>
<string name="settings_dark_theme_summary">Abilita il tema scuro.</string>
<string name="settings_dark_theme_summary">Abilita il tema scuro</string>
<string name="settings_download_path_title">Percorso di download</string>
<string name="settings_download_path_message">I file verranno salvati in %1$s</string>
<string name="settings_clear_cache_title">Svuota cache repository</string>
<string name="settings_clear_cache_summary">Svuota la cache delle repository. Questa opzione forza l\'aggiornamento online dell\'app.</string>
<string name="settings_clear_cache_summary">Svuota la cache delle repository. Questa opzione forza l\'aggiornamento online dell\'app</string>
<string name="settings_hide_manager_title">Nascondi Magisk Manager</string>
<string name="settings_hide_manager_summary">Reinstalla Magisk Manager con un nome pacchetto casuale.</string>
<string name="settings_hide_manager_summary">Reinstalla Magisk Manager con un nome pacchetto e app casuali</string>
<string name="settings_restore_manager_title">Ripristina Magisk Manager</string>
<string name="settings_restore_manager_summary">Ripristina Magisk Manager con il nome pacchetto originale</string>
<string name="settings_restore_manager_summary">Ripristina Magisk Manager con il nome pacchetto e app originali</string>
<string name="language">Lingua</string>
<string name="system_default">(Sistema)</string>
<string name="settings_update">Impostazioni aggiornamento</string>
<string name="settings_check_update_title">Controlla aggiornamenti</string>
<string name="settings_check_update_summary">Controlla automaticamente gli aggiornamenti in background.</string>
<string name="settings_check_update_summary">Controlla automaticamente gli aggiornamenti in background</string>
<string name="settings_update_channel_title">Canale di aggiornamento</string>
<string name="settings_update_stable">Stabile</string>
<string name="settings_update_beta">Beta</string>
@ -139,12 +138,16 @@
<string name="settings_update_custom_msg">Inserisci un URL personalizzato</string>
<string name="settings_core_only_title">Modalità Magisk Core</string>
<string name="settings_core_only_summary">Abilita solo le funzioni principali. Nessun modulo verrà caricato. MagiskSU e MagiskHide rimarranno abilitati</string>
<string name="settings_magiskhide_summary">Nasconde Magisk da numerose rilevazioni.</string>
<string name="settings_magiskhide_summary">Nasconde Magisk da numerose rilevazioni</string>
<string name="settings_hosts_title">Host systemless</string>
<string name="settings_hosts_summary">Supporto a host systemless per le app che bloccano le pubblicità.</string>
<string name="settings_hosts_summary">Supporto a host systemless per le app che bloccano le pubblicità</string>
<string name="settings_hosts_toast">Aggiunto modulo per host systemless</string>
<string name="settings_su_app_adb">App e ADB</string>
<string name="settings_app_name">Inserisci il nome desiderato per l\'app</string>
<string name="settings_app_name_hint">Nuovo nome</string>
<string name="settings_app_name_helper">L\'app sarà ricreata con questo nome</string>
<string name="settings_app_name_error">Formato non valido</string>
<string name="settings_su_app_adb">App e ADB</string>
<string name="settings_su_app">Solo app</string>
<string name="settings_su_adb">Solo ADB</string>
<string name="settings_su_disable">Disabilitato</string>
@ -161,26 +164,22 @@
<string name="request_timeout_summary">%1$d secondi</string>
<string name="settings_su_reauth_title">Riautentica dopo aggiornamento</string>
<string name="settings_su_reauth_summary">Riautentica i permessi Superuser dopo un aggiornamento dell\'app</string>
<string name="settings_su_fingerprint_title">Abilita autenticazione impronta</string>
<string name="settings_su_fingerprint_summary">Utilizza il sensore di impronte per accettare le richieste Superuser</string>
<string name="auth_fingerprint">Conferma impronta</string>
<string name="multiuser_mode">Modalità multiutente</string>
<string name="settings_owner_only">Solo proprietario del dispositivo</string>
<string name="settings_owner_manage">Gestito dal proprietario utente</string>
<string name="settings_user_independent">Idipendente dall\'utente</string>
<string name="owner_only_summary">Solo il proprietario ha i permessi di root.</string>
<string name="owner_manage_summary">Solo il proprietario può gestire accesso root e ricevere richieste.</string>
<string name="user_indepenent_summary">Ogni utente ha le sue regole di root indpendenti.</string>
<string name="owner_only_summary">Solo il proprietario ha i permessi di root</string>
<string name="owner_manage_summary">Solo il proprietario può gestire accesso root e ricevere richieste</string>
<string name="user_indepenent_summary">Ogni utente ha le sue regole di root indpendenti</string>
<string name="mount_namespace_mode">Modalità mount namespace</string>
<string name="settings_ns_global">Namespace globale</string>
<string name="settings_ns_requester">Namespace ereditato</string>
<string name="settings_ns_isolate">Namespace isolato</string>
<string name="global_summary">Tutte le sessioni di root erediteranno il namespace globale.</string>
<string name="requester_summary">Le sessioni di root erediteranno il namespace del loro richiedente.</string>
<string name="isolate_summary">Ogni sessione di root avrà il suo namespace isolato.</string>
<string name="disable_fingerprint">Non è presente alcuna impronta o il dispositivo non è supportato.</string>
<string name="global_summary">Tutte le sessioni di root erediteranno il namespace globale</string>
<string name="requester_summary">Le sessioni di root erediteranno il namespace del loro richiedente</string>
<string name="isolate_summary">Ogni sessione di root avrà il suo namespace isolato</string>
<string name="settings_download_path_error">Errore durante la creazione della cartella. Deve essere accessibile dalla radice della memoria di archiviazione e non essere un file.</string>
<!--Superuser-->
@ -208,7 +207,6 @@
<string name="su_revoke_msg">Confermi la revoca dei diritti di %1$s?</string>
<string name="toast">Toast</string>
<string name="none">Nessuno</string>
<string name="auth_fail">Autenticatione fallita</string>
<!--Superuser logs-->
<string name="pid">PID: %1$d</string>

View File

@ -8,7 +8,6 @@
<string name="settings">設定</string>
<string name="install">インストール</string>
<string name="unsupport_magisk_title">対応していないMagiskバージョン</string>
<string name="unsupport_magisk_message">このバージョンのMagisk ManagerはMagisk v18.0以下には対応していません。\n\n手動でMagiskを更新するか、または古いバージョンのMagisk Managerをインストールしてください。</string>
<!--Status Fragment-->
<string name="magisk_version_error">Magiskがインストールされていません</string>
@ -149,9 +148,6 @@
<string name="request_timeout_summary">%1$d秒</string>
<string name="settings_su_reauth_title">アップグレード後の再認証</string>
<string name="settings_su_reauth_summary">アプリのアップグレード後にスーパーユーザー権限を再認証します</string>
<string name="settings_su_fingerprint_title">指紋認証の有効化</string>
<string name="settings_su_fingerprint_summary">スーパーユーザー権限のリクエストの許可に指紋認証を使います</string>
<string name="auth_fingerprint">指紋認証</string>
<string name="multiuser_mode">マルチユーザーモード</string>
<string name="settings_owner_only">端末の管理者のみ</string>
@ -168,7 +164,6 @@
<string name="global_summary">すべてのrootセッションがグローバル名前空間を使用します</string>
<string name="requester_summary">rootセッションはリクエスト者の名前空間を継承します</string>
<string name="isolate_summary">rootセッション毎に分離された名前空間を使用します</string>
<string name="disable_fingerprint">指紋が登録されていないか、お使いの端末でサポートされていません。</string>
<!--Superuser-->
<string name="su_request_title">スーパーユーザーリクエスト</string>
@ -195,7 +190,6 @@
<string name="su_revoke_msg">%1$s の権限を取り消しますか?</string>
<string name="toast">トースト通知</string>
<string name="none">なし</string>
<string name="auth_fail">認証に失敗しました</string>
<!--Superuser logs-->
<string name="pid">PID: %1$d</string>

View File

@ -8,7 +8,6 @@
<string name="settings">설정</string>
<string name="install">설치</string>
<string name="unsupport_magisk_title">지원되지 않는 Magisk 버전</string>
<string name="unsupport_magisk_message">이 버전의 Magisk Manager는 v18.0보다 낮은 버전의 Magisk를 지원하지 않습니다.\n\n직접 Magisk를 업데이트 하거나 이전 버전의 앱으로 다운그레이드하십시오..</string>
<!--Status Fragment-->
<string name="magisk_version_error">Magisk가 설치되지 않음</string>
@ -156,9 +155,6 @@
<string name="request_timeout_summary">%1$d초</string>
<string name="settings_su_reauth_title">업데이트 후 재승인</string>
<string name="settings_su_reauth_summary">앱 업데이트 후에 슈퍼유저 권한 재승인</string>
<string name="settings_su_fingerprint_title">지문 인식 사용</string>
<string name="settings_su_fingerprint_summary">지문 인식으로 슈퍼유저 권한을 허가합니다.</string>
<string name="auth_fingerprint">지문 인식</string>
<string name="multiuser_mode">다중 사용자 모드</string>
<string name="settings_owner_only">기기 소유자만</string>
@ -175,7 +171,6 @@
<string name="global_summary">모든 루트 세션이 전역 마운트 이름공간을 사용합니다.</string>
<string name="requester_summary">루트 세션은 요청자의 이름공간을 상속합니다.</string>
<string name="isolate_summary">각각의 루트 세션은 자신만의 독립된 이름공간을 사용합니다.</string>
<string name="disable_fingerprint">지문이 등록되지 않았거나 기기가 지문 인식을 지원하지 않습니다.</string>
<!--Superuser-->
<string name="su_request_title">슈퍼유저 요청</string>
@ -202,7 +197,6 @@
<string name="su_revoke_msg">정말 %1$s의 권한을 취소하시겠습니까?</string>
<string name="toast">토스트</string>
<string name="none">없음</string>
<string name="auth_fail">인증 실패</string>
<!--Superuser logs-->
<string name="pid">PID: %1$d</string>

View File

@ -137,8 +137,6 @@
<string name="request_timeout_summary">%1$d sekundžių</string>
<string name="settings_su_reauth_title">Pakartotinai patvirtinti po atnaujinimo</string>
<string name="settings_su_reauth_summary">Pakartotinai patvirtinti supervartotojo leidimus po programėlės atnaujinimo</string>
<string name="settings_su_fingerprint_title">Įgalinti patvirtinimą piršto antspaudu</string>
<string name="settings_su_fingerprint_summary">Naudoti piršto antspaudą supervartotojo leidimo prašymų atsakymui</string>
<string name="multiuser_mode">Daugialypio vartotojo režimas</string>
<string name="settings_owner_only">Tik įrenginio savininkas</string>
@ -155,7 +153,6 @@
<string name="global_summary">Visos root sesijos naudoja globalią vardų sritį</string>
<string name="requester_summary">Root sesijos paveldi jos išprašytojo/s vardų sritį</string>
<string name="isolate_summary">Kiekviena root sesija turi savo izoliuotą vardų sritį</string>
<string name="disable_fingerprint">Jūsų įrenginyje nebuvo surasta pirštų antspaudų arba jūsų įrenginys neturi pirštų antspaudų skaitytuvo</string>
<!--Superuser-->
<string name="su_request_title">Supervartotojo prašymas</string>
@ -182,7 +179,6 @@
<string name="su_revoke_msg">Neleisti %1$s naudotis supervartotojo teisėmis?</string>
<string name="toast">Išmesti</string>
<string name="none">Nėra</string>
<string name="auth_fail">Patvirtinimas žlugo</string>
<!--Superuser logs-->
<string name="target_uid">Target UID: %1$d</string>

View File

@ -8,7 +8,6 @@
<string name="settings">Поставки</string>
<string name="install">Инсталирај</string>
<string name="unsupport_magisk_title">Неподдржана верзија на Magisk</string>
<string name="unsupport_magisk_message">Оваа верзија на Magisk Manager не ја поддржува верзијата на Magisk пониска од v18.0.\n\nМожно е да рачно да го надградите Magisk или да ја вратите апликацијата на постара верзија.</string>
<!--Status Fragment-->
<string name="magisk_version_error">Magisk не е инсталиран.</string>
@ -155,9 +154,6 @@
<string name="request_timeout_summary">%1$d секунди</string>
<string name="settings_su_reauth_title">Повторна автентикација по надградба</string>
<string name="settings_su_reauth_summary">Повторна автентикација за супер-корисник дозвола по надградбата на апликацијата</string>
<string name="settings_su_fingerprint_title">Овозможи автентикација на отпечатоци</string>
<string name="settings_su_fingerprint_summary">Користете скенер за отпечатоци за да дозволите супер-корисник барања</string>
<string name="auth_fingerprint">Автентикација на отпечатоци</string>
<string name="multiuser_mode">Режим на повеќе корисници</string>
<string name="settings_owner_only">Само сопственикот на уредот</string>
@ -174,7 +170,6 @@
<string name="global_summary">Сите рут сесии го користат глобалниот именски простор.</string>
<string name="requester_summary">Рут сесиите ќе го наследат именскиот простор на нивниот барател.</string>
<string name="isolate_summary">Секоја рут сесија ќе има свој изолиран именски простор.</string>
<string name="disable_fingerprint">Нема регистрирано отпечатоци од прсти или уредот не ја поддржува оваа функција.</string>
<!--Superuser-->
<string name="su_request_title">Супер-корисник барање</string>
@ -201,7 +196,6 @@
<string name="su_revoke_msg">Дали потврдувате анулирање на поставките за пристап на %1$s?</string>
<string name="toast">Тост</string>
<string name="none">Ниеден</string>
<string name="auth_fail">Неуспешна автентикација</string>
<!--Superuser logs-->
<string name="pid">PID: %1$d</string>

View File

@ -146,9 +146,6 @@
<string name="request_timeout_summary">%1$d sekunder</string>
<string name="settings_su_reauth_title">Autentiser på nytt etter oppdateringer after upgrade</string>
<string name="settings_su_reauth_summary">Autentiser superbruker-tillatelser på nytt etter at en app har blitt oppdatert</string>
<string name="settings_su_fingerprint_title">Skru på fingeravtrykksautentisering</string>
<string name="settings_su_fingerprint_summary">Bruk fingeravtrykksskanneren for å godkjenne superbruker-forespørsler</string>
<string name="auth_fingerprint">Autentiser fingeravtrykk</string>
<string name="multiuser_mode">Flerbrukermoduse</string>
<string name="settings_owner_only">Kun enhetens eier</string>
@ -165,7 +162,6 @@
<string name="global_summary">Alle root-økter benytter det altdekkende monteringsnavnefeltet.</string>
<string name="requester_summary">Root-økter vil arve forespørrerens navnefelt.</string>
<string name="isolate_summary">Hver root-økt vil ha sitt eget isolerte navnefelt.</string>
<string name="disable_fingerprint">Ingen fingeravtrykk ble gitt, eller så støttes det ikke av enheten.</string>
<!--Superuser-->
<string name="su_request_title">Superbruker-forespørsel</string>
@ -192,7 +188,6 @@
<string name="su_revoke_msg">Vil du bekrefte for å oppheve %1$s sine rettigheter?</string>
<string name="toast">Varselfelt</string>
<string name="none">Ingen</string>
<string name="auth_fail">Autentisering mislyktes</string>
<!--Superuser logs-->
<string name="target_uid">Mål-UID: %1$d</string>

View File

@ -132,8 +132,6 @@
<string name="request_timeout_summary">%1$d seconden</string>
<string name="settings_su_reauth_title">Opnieuw verzoeken na bijwerken</string>
<string name="settings_su_reauth_summary">Superuser rechten opnieuw opvragen na bijwerken applicatie</string>
<string name="settings_su_fingerprint_title">Vingerafdruk authenticatie inschakelen</string>
<string name="settings_su_fingerprint_summary">Vingerafdruk gebruiken om superuser verzoeken toe te staan</string>
<string name="multiuser_mode">Multi-gebruiker modus</string>
<string name="settings_owner_only">Alleen apparaateigenaar</string>
@ -150,7 +148,6 @@
<string name="global_summary">Alle rootsessies gebruiken de globale naamruimte</string>
<string name="requester_summary">Rootsessies verkrijgen de verzoeker\'s naamruimte</string>
<string name="isolate_summary">Iedere rootsessie heeft een eigen geïsoleerde naamruimte</string>
<string name="disable_fingerprint">Geen vingerafdrukken ingesteld, of geen apparaatondersteuning</string>
<!--Superuser-->
<string name="su_request_title">Superuser verzoek</string>
@ -177,7 +174,6 @@
<string name="su_revoke_msg">De rechten van %1$s intrekken?</string>
<string name="toast">Toast</string>
<string name="none">Geen</string>
<string name="auth_fail">Authenticatie mislukt</string>
<!--Superuser logs-->
<string name="target_uid">Doel UID: %1$d</string>

View File

@ -8,7 +8,6 @@
<string name="settings">Ustawienia</string>
<string name="install">Instaluj</string>
<string name="unsupport_magisk_title">Nieobsługiwana Wersja Magisk</string>
<string name="unsupport_magisk_message">Ta wersja Magisk Managera nie obsługuje wersji Magisk niższej niż v18.0.\n\nMożesz albo ręcznie zaktualizować Magisk lub obniżyć w aplikacji do starszej wersji.</string>
<!--Status Fragment-->
<string name="magisk_version_error">Magisk nie jest zainstalowany.</string>
@ -24,6 +23,7 @@
<string name="advanced_settings_title">Zaawansowane Ustawienia</string>
<string name="keep_force_encryption">Zachowaj force encryption (wymuszenie szyfrowania)</string>
<string name="keep_dm_verity">Zachowaj AVB 2.0/dm-verity (weryfikację procesu bootowania)</string>
<string name="recovery_mode">Tryb Recovery</string>
<string name="current_installed">Zainstalowana: %1$s</string>
<string name="latest_version">Ostatnia: %1$s</string>
<string name="uninstall">Odinstaluj</string>
@ -145,6 +145,10 @@
<string name="settings_hosts_summary">Wsparcie systemless dla aplikacji Adblock</string>
<string name="settings_hosts_toast">Dodano moduł systemless hosts</string>
<string name="settings_app_name">Wpisz żądaną nazwę aplikacji</string>
<string name="settings_app_name_hint">Nowa Nazwa</string>
<string name="settings_app_name_helper">Aplikacja zostanie ponownie spakowana do tej nazwy</string>
<string name="settings_app_name_error">Zły format</string>
<string name="settings_su_app_adb">Aplikacje i ADB</string>
<string name="settings_su_app">Tylko Aplikacje</string>
<string name="settings_su_adb">Tylko ADB</string>
@ -162,9 +166,6 @@
<string name="request_timeout_summary">%1$d sekund</string>
<string name="settings_su_reauth_title">Ponowienie uwierzytelnienia po aktualizacji</string>
<string name="settings_su_reauth_summary">Ponowne uwierzytelnianie uprawnienia superużytkownika po aktualizacji aplikacji</string>
<string name="settings_su_fingerprint_title">Włącz Uwierzytelnienie Odciskiem Palca</string>
<string name="settings_su_fingerprint_summary">Użyj skanera linii papilarnych, aby zezwolić na żądania supersu</string>
<string name="auth_fingerprint">Uwierzytelnianie Odciskiem Palca</string>
<string name="multiuser_mode">Tryb Multiusera</string>
<string name="settings_owner_only">Tylko Właściciel Urządzenia</string>
@ -181,7 +182,6 @@
<string name="global_summary">Wszystkie sesje root za pomocą globalnej przestrzeni montowań nazw</string>
<string name="requester_summary">Sesje Root będzie dziedziczyć prośby i nazwy</string>
<string name="isolate_summary">W każdej sesji root będzie miał własną odosobnioną nazwę</string>
<string name="disable_fingerprint">Nie ustawiono żadnych odcisków palców lub brak obsługi urządzenia</string>
<string name="settings_download_path_error">Błąd podczas tworzenia folderu. Musi być dostępny z głównego katalogu pamięci i nie może być plikiem.</string>
<!--Superuser-->
@ -209,7 +209,6 @@
<string name="su_revoke_msg">Potwierdzasz odwołanie uprawnień %1$s?</string>
<string name="toast">Powiadomienie</string>
<string name="none">Brak</string>
<string name="auth_fail">Uwierzytelnienie Nieudane</string>
<!--Superuser logs-->
<string name="pid">PID: %1$d</string>

View File

@ -134,8 +134,6 @@
<string name="request_timeout_summary">%1$d segundos</string>
<string name="settings_su_reauth_title">Reautenticar após atualizar</string>
<string name="settings_su_reauth_summary">Reautenticar permissões de superusuário após um app atualizar</string>
<string name="settings_su_fingerprint_title">Ativar Autenticação de Impressão Digital</string>
<string name="settings_su_fingerprint_summary">Usar escaneador de impressão digital para permitir solicitações de superusuário</string>
<string name="multiuser_mode">Modo de Multiusuário</string>
<string name="settings_owner_only">Proprietário do Dispositivo Apenas</string>
@ -152,7 +150,6 @@
<string name="global_summary">Todas as sessões root usam montagem de espaço de nome global</string>
<string name="requester_summary">As sessões root herdarão espaço de nome de seu solicitante</string>
<string name="isolate_summary">Cada sessão root terá seu próprio espaço de nome isolado</string>
<string name="disable_fingerprint">Nenhuma impressão digital foi definida ou o dispostivo não tem suporte</string>
<!--Superuser-->
<string name="su_request_title">Solicitação de Superusuário</string>
@ -179,7 +176,6 @@
<string name="su_revoke_msg">Revogar os direitos de %1$s?</string>
<string name="toast">Notificação toast</string>
<string name="none">Nenhuma</string>
<string name="auth_fail">Falha de Autenticação</string>
<!--Superuser logs-->
<string name="pid">PID: %1$d</string>

View File

@ -8,7 +8,6 @@
<string name="settings">Setări</string>
<string name="install">Instalează</string>
<string name="unsupport_magisk_title">Versiune Magisk nesuportată</string>
<string name="unsupport_magisk_message">Această versiune de Magisk Manager nu suportă versiunea Magisk mai mică de v18.0.\n\nPoți fie să actualizezi manual Magisk, fie să treci la o versiune mai veche.</string>
<!--Status Fragment-->
<string name="magisk_version_error">Magisk nu este instalat.</string>
@ -162,9 +161,6 @@
<string name="request_timeout_summary">%1$d secunde</string>
<string name="settings_su_reauth_title">Reautentificare după actualizare</string>
<string name="settings_su_reauth_summary">Reautentifică permisiunile pentru superutilizator după o actualizare a aplicației</string>
<string name="settings_su_fingerprint_title">Activează autentificarea cu amprenta</string>
<string name="settings_su_fingerprint_summary">Folosește scannerul de amprente pentru a permite solicitările de superutilizator</string>
<string name="auth_fingerprint">Autentifică amprenta</string>
<string name="multiuser_mode">Mod de multiutilizator</string>
<string name="settings_owner_only">Numai proprietarul dispozitivului</string>
@ -181,7 +177,6 @@
<string name="global_summary">Toate sesiunile de root folosesc spațiul de nume global.</string>
<string name="requester_summary">Sesiunile de root vor moșteni spațiul de nume al solicitantului.</string>
<string name="isolate_summary">Fiecare sesiune de root va avea propriul spațiu de nume izolat.</string>
<string name="disable_fingerprint">Nu au fost setate amprente sau scannerul de amprentă lipsește.</string>
<string name="settings_download_path_error">Eroare la crearea dosarului. Acesta trebuie să fie accesibil din directorul rădăcină al stocării și nu trebuie să fie un fișier.</string>
<!--Superuser-->
@ -209,7 +204,6 @@
<string name="su_revoke_msg">Confirmi revocarea drepturilor pentru %1$s?</string>
<string name="toast">Mesaj</string>
<string name="none">Nimic</string>
<string name="auth_fail">Autentificare eșuată</string>
<!--Superuser logs-->
<string name="pid">PID: %1$d</string>

View File

@ -8,7 +8,7 @@
<string name="settings">Настройки</string>
<string name="install">Установка</string>
<string name="unsupport_magisk_title">Неподдерживаемая версия Magisk</string>
<string name="unsupport_magisk_message">Эта версия Magisk Manager не поддерживает версию Magisk ниже v18.0.\n\nВы можете вручную обновить Magisk или понизить версию приложения до более старой.</string>
<string name="unsupport_magisk_msg">Эта версия Magisk Manager не поддерживает версию Magisk ниже %1$s.\n\nПриложение будет работать так, как будто Magisk не установлен, пожалуйста, обновите Magisk как можно быстрее.</string>
<!--Status Fragment-->
<string name="magisk_version_error">Magisk не установлен</string>
@ -63,7 +63,7 @@
<string name="logs_cleared">Лог успешно очищен</string>
<!--About Activity-->
<string name="app_changelog">Список изменений</string>
<string name="app_changelog">Изменения</string>
<!-- System Components, Notifications -->
<string name="update_channel">Обновления Magisk</string>
@ -77,13 +77,13 @@
<!-- Installation -->
<string name="manager_download_install">Нажмите, чтобы загрузить и установить</string>
<string name="download_zip_only">Только загрузка ZIP</string>
<string name="download_zip_only">Загрузка установочного ZIP</string>
<string name="direct_install">Прямая установка (Рекомендуется)</string>
<string name="install_inactive_slot">Установка во второй слот (OTA)</string>
<string name="install_inactive_slot_msg">Ваше устройство будет принудительно перезагружено в неактивный (противоположный) слот!\nИспользуйте эту опцию только при интеграции после OTA.\nПродолжить?</string>
<string name="select_method">Выбор способа</string>
<string name="select_method">Способ установки</string>
<string name="setup_title">Дополнительная установка</string>
<string name="select_patch_file">Вручную пропатчить образ</string>
<string name="select_patch_file">Пропатчить образ вручную</string>
<string name="patch_file_msg">Выберите файл образа (*.img) или архив ODIN (*.tar)</string>
<string name="reboot_delay_toast">Перезагрузка через 5 секунд…</string>
@ -95,7 +95,6 @@
<string name="settings_reboot_toast">Для применения настроек перезагрузите устройство</string>
<string name="release_notes">О версии</string>
<string name="repo_cache_cleared">Кэш репозитория очищен</string>
<string name="dtbo_patched_title">DTBO пропатчен!</string>
<string name="dtbo_patched_reboot">Magisk Manager пропатчил dtbo.img. Перезагрузите устройство.</string>
<string name="flashing">Прошивка…</string>
@ -116,15 +115,16 @@
<string name="env_fix_title">Требуется дополнительная установка</string>
<string name="env_fix_msg">Вашему устройству требуется дополнительная установка Magisk для корректной работы. Будет загружен установочный ZIP Magisk, продолжить?</string>
<string name="setup_msg">Настройка рабочей среды…</string>
<string name="authenticate">Аутентификация</string>
<!--Settings Activity -->
<string name="settings_general_category">Основные</string>
<string name="settings_dark_theme_title">Тёмная тема</string>
<string name="settings_dark_theme_summary">Включить тёмное оформление</string>
<string name="settings_download_path_title">Папка загрузки</string>
<string name="settings_download_path_title">Папка для загрузки</string>
<string name="settings_download_path_message">Файлы будут загружаться в %1$s</string>
<string name="settings_clear_cache_title">Очистка кэша репозитория</string>
<string name="settings_clear_cache_summary">Очистить кэш репозитория. Будет загружен заново</string>
<string name="settings_clear_cache_summary">Очистить кэш репозитория. Кэш будет загружен заново</string>
<string name="settings_hide_manager_title">Скрытие Magisk Manager</string>
<string name="settings_hide_manager_summary">Пересобрать Magisk Manager со случайным названием и именем пакета</string>
<string name="settings_restore_manager_title">Восстановление Magisk Manager</string>
@ -142,9 +142,9 @@
<string name="settings_core_only_title">Magisk Core</string>
<string name="settings_core_only_summary">Активировать только основные возможности. Модули не будут загружены. MagiskSU и Magisk Hide останутся активными</string>
<string name="settings_magiskhide_summary">Скрывать Magisk от различных обнаружений</string>
<string name="settings_hosts_title">Внесистемные хосты</string>
<string name="settings_hosts_summary">Поддержка внесистемных хостов для приложений, блокирующих рекламу</string>
<string name="settings_hosts_toast">Добавлен модуль внесистемных хостов</string>
<string name="settings_hosts_title">Внесистемный hosts файл</string>
<string name="settings_hosts_summary">Поддержка внесистемного hosts файла для приложений, блокирующих рекламу</string>
<string name="settings_hosts_toast">Добавлен модуль внесистемного hosts файла</string>
<string name="settings_app_name">Укажите имя приложения</string>
<string name="settings_app_name_hint">Новое имя</string>
@ -167,9 +167,9 @@
<string name="request_timeout_summary">%1$d секунд</string>
<string name="settings_su_reauth_title">Повторная аутентификация</string>
<string name="settings_su_reauth_summary">Повторный запрос прав суперпользователя после обновления приложений</string>
<string name="settings_su_fingerprint_title">Биометрическая аутентификация</string>
<string name="settings_su_fingerprint_summary">Использовать сканер отпечатков пальцев для запросов прав суперпользователя</string>
<string name="auth_fingerprint">Подтвердите отпечаток пальца</string>
<string name="settings_su_biometric_title">Биометрическая аутентификация</string>
<string name="settings_su_biometric_summary">Использовать биометрическую аутентификацию для запросов прав суперпользователя</string>
<string name="no_biometric">Эта функция не поддерживается устройством или не заданы настройки блокировки экрана</string>
<string name="multiuser_mode">Многопользовательский режим</string>
<string name="settings_owner_only">Только владелец</string>
@ -186,7 +186,6 @@
<string name="global_summary">Сессии суперпользователя используют общее пространство имён</string>
<string name="requester_summary">Сессии суперпользователя наследуют пространство имён запрашивающего</string>
<string name="isolate_summary">Сессии суперпользователя используют изолированные пространства имён</string>
<string name="disable_fingerprint">Не поддерживается устройством или не заданы отпечатки</string>
<string name="settings_download_path_error">Ошибка создания папки. Она должна быть доступна из корневой директории хранилища и не должна быть файлом.</string>
<!--Superuser-->
@ -214,7 +213,6 @@
<string name="su_revoke_msg">Сбросить настройки для %1$s?</string>
<string name="toast">Всплывающие уведомления</string>
<string name="none">Нет</string>
<string name="auth_fail">Ошибка аутентификации</string>
<!--Superuser logs-->
<string name="pid">PID: %1$d</string>

View File

@ -8,8 +8,6 @@
<string name="settings">Nastavenia</string>
<string name="install">Inštalovať</string>
<string name="unsupport_magisk_title">Nepodporovaná verzia Magisku</string>
<string name="unsupport_magisk_message">Táto verzia Magisk Managera podporuje Magisk od verzie v18.0.\n\nBuď upgradujte Magisk manuálne alebo nainštalujte staršiu verziu aplikácie.</string>
<!--Status Fragment-->
<string name="magisk_version_error">Magisk nie je nainštalovaný</string>
@ -25,26 +23,28 @@
<string name="advanced_settings_title">Pokročilé nastavenia</string>
<string name="keep_force_encryption">Ponechať vynútené šifrovanie</string>
<string name="keep_dm_verity">Ponechať AVB 2.0/dm-verity</string>
<string name="recovery_mode">Režim Recovery</string>
<string name="current_installed">Nainštalovaná verzia: %1$s</string>
<string name="latest_version">Najnovšia verzia: %1$s</string>
<string name="uninstall">Odinštalovať</string>
<string name="uninstall_magisk_msg">Všetky moduly budú zakázané/odstránené. Root bude odstránený a teoreticky môže dôjsť k zašifrovaniu vašich dát, ak ich momentálne nemáte zašifrované</string>
<string name="uninstall_magisk_title">Odinštalovať Magisk</string>
<string name="uninstall_magisk_msg">Všetky moduly budú zakázané/odstránené!\nRoot bude odstránený!\nMôže dôjsť k zašifrovaniu vašich dát, ak už nie sú zašifrované!</string>
<string name="update">Aktualizovať</string>
<string name="core_only_enabled">(Režim Core povolený)</string>
<string name="core_only_enabled">(Povolený iba režim Core)</string>
<!--Module Fragment-->
<string name="no_info_provided">(Nie sú k dispozícii žiadne informácie)</string>
<string name="no_modules_found">Neboli nájdené žiadne moduly</string>
<string name="update_file_created">Modul bude aktualizovaný pri ďalšom reštarte</string>
<string name="remove_file_created">Modul bude odstránený pri ďalšom reštarte</string>
<string name="remove_file_deleted">Modul nebude odstránený pri ďalšom reštarte</string>
<string name="disable_file_created">Modul bude zakázaný pri ďalšom reštarte</string>
<string name="disable_file_removed">Modul bude povolený pri ďalšom reštarte</string>
<string name="update_file_created">Modul bude aktualizovaný pri ďalšom reštarte!</string>
<string name="remove_file_created">Modul bude odstránený pri ďalšom reštarte!</string>
<string name="remove_file_deleted">Modul nebude odstránený pri ďalšom reštarte!</string>
<string name="disable_file_created">Modul bude zakázaný pri ďalšom reštarte!</string>
<string name="disable_file_removed">Modul bude povolený pri ďalšom reštarte!</string>
<string name="author">Vytvoril %1$s</string>
<string name="reboot_recovery">Reštartovať do Recovery</string>
<string name="reboot_bootloader">Reštartovať do Bootloader</string>
<string name="reboot_download">Reštartovať do Download</string>
<string name="reboot_edl">Reštartovať do EDL</string>
<!--Repo Fragment-->
<string name="update_available">Dostupná aktualizácia</string>
@ -59,18 +59,33 @@
<string name="menuSaveLog">Uložiť záznam</string>
<string name="menuReload">Obnoviť</string>
<string name="menuClearLog">Odstrániť záznamy</string>
<string name="logs_cleared">Záznamy boli odstránené</string>
<string name="logs_cleared">Záznamy boli odstránené.</string>
<!--About Activity-->
<string name="app_changelog">Zoznam zmien</string>
<!-- System Components, Notifications -->
<string name="update_channel">Aktualizácie Magisk</string>
<string name="progress_channel">Notifkikácie priebehu</string>
<string name="progress_channel">Upozornenia o priebehu</string>
<string name="download_complete">Sťahovanie ukončené</string>
<string name="download_file_error">Chyba sťahovania súboru</string>
<string name="download_open_parent">Zobraziť v nadradenom priečinku</string>
<string name="download_open_self">Zobraziť súbor</string>
<string name="magisk_update_title">Je dostupná aktualizácia Magisk!</string>
<string name="manager_update_title">Je dostupná aktualizácia Magisk Manager!</string>
<!-- Installation -->
<string name="manager_download_install">Stlačte pre stiahnutie a inštaláciu</string>
<string name="download_zip_only">Stiahnuť iba zip</string>
<string name="direct_install">Priama inštalácia (Odporúča sa)</string>
<string name="install_inactive_slot">Inštalovať na aktívny slot (Po OTA)</string>
<string name="install_inactive_slot_msg">Vaše zariadenie bude po reštarte PRINÚTENÉ nabootovať do aktuálne neaktívneho slotu!\nTúto voľbu použite iba po skončení OTA.\nPokračovať?</string>
<string name="select_method">Vyberte metódu</string>
<string name="setup_title">Ďalšie nastavenie</string>
<string name="select_patch_file">Vybrať a zaplátať súbor</string>
<string name="patch_file_msg">Vyberte raw súbor (*.img) alebo tar súbor ODIN (*.tar)</string>
<string name="reboot_delay_toast">Reštart o 5 sekúnd…</string>
<!--Toasts, Dialogs-->
<string name="repo_install_title">Nainštalovať %1$s</string>
<string name="repo_install_msg">Chcete teraz nainštalovať %1$s?</string>
@ -79,17 +94,16 @@
<string name="settings_reboot_toast">Pre aplikovanie nastavení je potrebný reštart</string>
<string name="release_notes">Poznámky k vydaniu</string>
<string name="repo_cache_cleared">Cache repo odstránená</string>
<string name="manager_download_install">Stlačte pre stiahnutie a inštaláciu</string>
<string name="dtbo_patched_title">DTBO bol zaplátaný!</string>
<string name="dtbo_patched_reboot">Magisk Manager zaplátal dtbo.img, prosím, reštartujte</string>
<string name="dtbo_patched_reboot">Magisk Manager zaplátal dtbo.img, prosím, reštartujte.</string>
<string name="flashing">Flashovanie</string>
<string name="done">Hotovo!</string>
<string name="failure">Zlyhalo</string>
<string name="hide_manager_title">Skrytie Magisk Managera…</string>
<string name="hide_manager_fail_toast">Skrytie Magisk Manager zlyhalo.</string>
<string name="open_link_failed_toast">Nepodarilo sa nájsť vhodnú aplikáciu na otvorenie odkazu…</string>
<string name="download_zip_only">Stiahnuť iba zip</string>
<string name="direct_install">Priama inštalácia (Odporúča sa)</string>
<string name="install_inactive_slot">Inštalovať na aktívny slot (Po OTA)</string>
<string name="warning">Upozornenie</string>
<string name="install_inactive_slot_msg">Vaše zariadenie bude po reštarte PRINÚTENÉ nabootovať do aktuálne neaktívneho slotu!\nTúto voľbu použite iba po skončení OTA.\nPokračovať?</string>
<string name="select_method">Vyberte metódu</string>
<string name="complete_uninstall">Úplne odinštalovať</string>
<string name="restore_img">Obnoviť obrazy</string>
<string name="restore_img_msg">Obnovovanie…</string>
@ -100,14 +114,15 @@
<string name="setup_fail">Nastavenie zlyhalo</string>
<string name="env_fix_title">Vyžaduje sa ďalšie nastavenie</string>
<string name="env_fix_msg">Vaše zariadenie si vyžaduje ďalšie nastavenie pre správne fungovanie Magisk. Stiahne sa inštalačný zip súbor Magisk, chcete pokračovať teraz?</string>
<string name="setup_title">Ďalšie nastavenie</string>
<string name="setup_msg">Spustené prostredie nastavenia…</string>
<string name="setup_msg">Nastavenie je spustené…</string>
<!--Settings Activity -->
<string name="settings_general_category">Všeobecné</string>
<string name="settings_dark_theme_title">Tmavý motív</string>
<string name="settings_dark_theme_summary">Povoliť tmavý motív</string>
<string name="settings_clear_cache_title">Vyčistiť cache repo </string>
<string name="settings_download_path_title">Cesta na sťahovanie</string>
<string name="settings_download_path_message">Súbory budú uložené do %1$s</string>
<string name="settings_clear_cache_title">Vyčistiť cache repo</string>
<string name="settings_clear_cache_summary">Vyčistí uložené informácie pre online repo, aplikácia si nové informácie stiahne online</string>
<string name="settings_hide_manager_title">Skryť Magisk Manager</string>
<string name="settings_hide_manager_summary">Prebalí Magisk Manager s náhodným názvom balíčka</string>
@ -124,12 +139,16 @@
<string name="settings_update_custom">Vlastný</string>
<string name="settings_update_custom_msg">Zadajte vlastnú URL</string>
<string name="settings_core_only_title">Režim Magisk Core</string>
<string name="settings_core_only_summary">Povolí iba hlavné funkcie. Stále budú povolené MagiskSU, MagiskHide a systemless hosts, ale žiadne moduly nebudú zavedené.</string>
<string name="settings_core_only_summary">Povolí iba hlavné funkcie. Stále budú povolené MagiskSU, MagiskHide a systemless hosts, ale žiadne moduly nebudú zavedené</string>
<string name="settings_magiskhide_summary">Ukryje Magisk pre rôznymi detekciami</string>
<string name="settings_hosts_title">Systemless hosts</string>
<string name="settings_hosts_summary">Podpora pre aplikácie systemless hosts Adblock</string>
<string name="settings_hosts_toast">Pridaný modul systemless hosts</string>
<string name="settings_app_name">Zadajte požadovaný názov aplikácie</string>
<string name="settings_app_name_hint">Nový názov</string>
<string name="settings_app_name_helper">Zadajte požadovaný názov aplikácie</string>
<string name="settings_app_name_error">Neplatný formát</string>
<string name="settings_su_app_adb">Aplikácie a ADB</string>
<string name="settings_su_app">Iba aplikácie</string>
<string name="settings_su_adb">Iba ADB</string>
@ -147,9 +166,6 @@
<string name="request_timeout_summary">%1$d sekúnd</string>
<string name="settings_su_reauth_title">Overenie autentifikácie po upgrade</string>
<string name="settings_su_reauth_summary">Overí autentifikáciu oprávnení superuser po upgrade aplikácie</string>
<string name="settings_su_fingerprint_title">Povoliť autentifikáciu odtlačkom prsta</string>
<string name="settings_su_fingerprint_summary">Použite snímač odtlačkov prstov pre povolenie žiadostí superuser</string>
<string name="auth_fingerprint">Autentifikovať odtlačok prsta</string>
<string name="multiuser_mode">Režim viacerých používateľov</string>
<string name="settings_owner_only">Iba majiteľ zariadenia</string>
@ -160,13 +176,13 @@
<string name="user_indepenent_summary">Každý používateľ má vlastné pravidlá pre root</string>
<string name="mount_namespace_mode">Režim Mount Namespace</string>
<string name="settings_ns_global">Globálne Namespace</string>
<string name="settings_ns_requester">Zdedené Namespace</string>
<string name="settings_ns_isolate">Izolované Namespace</string>
<string name="global_summary">Všetky relácie root použijú globálne mount namespace</string>
<string name="requester_summary">Relácie root zdedia namespace od požadovateľa</string>
<string name="settings_ns_global">Globálny Namespace</string>
<string name="settings_ns_requester">Zdedený Namespace</string>
<string name="settings_ns_isolate">Izolovaný Namespace</string>
<string name="global_summary">Všetky relácie root použijú globálny mount namespace</string>
<string name="requester_summary">Relácie root zdedia namespace od žiadateľa</string>
<string name="isolate_summary">Každá relácia root bude mať vlastný izolovaný namespace</string>
<string name="disable_fingerprint">Neboli odoslané žiadne odtlačky prsta alebo ich zariadenie nepodporuje</string>
<string name="settings_download_path_error">Chyba pri vytváraní priečinka. Musí byť prístupný z koreňového adresára a nemôže to byť súbor.</string>
<!--Superuser-->
<string name="su_request_title">Žiadosť superuser</string>
@ -193,12 +209,12 @@
<string name="su_revoke_msg">Potvrdzujete zrušenie práv %1$s?</string>
<string name="toast">Toast</string>
<string name="none">Nič</string>
<string name="auth_fail">Autentifikácia zlyhala</string>
<!--Superuser logs-->
<string name="pid">PID:%1$d</string>
<string name="target_uid">Cieľový UID: %1$d</string>
<string name="command">Príkaz: %1$s</string>
<!-- MagiskHide -->
<string name="show_system_app">Zobraziť systémové aplikácie</string>

View File

@ -146,9 +146,6 @@
<string name="request_timeout_summary">%1$d วินาที</string>
<string name="settings_su_reauth_title">ขอสิทธิ์ใหม่หลังจากอัพเกรด</string>
<string name="settings_su_reauth_summary">ขอสิทธิ์ superuser ใหม่หลังจากแอปถูกอัพเกรด</string>
<string name="settings_su_fingerprint_title">ใช้การยืนยันลายนิ้วมือ</string>
<string name="settings_su_fingerprint_summary">ใช้ตัวแสกนลายนิ้วมือเพื่ออนุญาตารขอเข้าถึง superuser</string>
<string name="auth_fingerprint">ยืนยันลายนิ้วมือ</string>
<string name="multiuser_mode">โหมดผู้ใช้หลายคน</string>
<string name="settings_owner_only">เจ้าของอุปกรณ์เท่านั้น</string>
@ -165,7 +162,6 @@
<string name="global_summary">All root sessions use the global mount namespace.</string>
<string name="requester_summary">Root sessions will inherit their requester\'s namespace.</string>
<string name="isolate_summary">Each root session will have its own isolated namespace.</string>
<string name="disable_fingerprint">ไม่มีลายนิ้วมือหรืออุปกรณ์แสกน</string>
<!--Superuser-->
<string name="su_request_title">การขอเข้าถึง Superuser</string>
@ -192,7 +188,6 @@
<string name="su_revoke_msg">ยืนยันการลบสิทธิ์ของ %1$s?</string>
<string name="toast">การเตือน</string>
<string name="none">ไม่มี</string>
<string name="auth_fail">การยืนยันล้มเหลว</string>
<!--Superuser logs-->
<string name="target_uid">UID เป้าหมาย: %1$d</string>

View File

@ -8,7 +8,6 @@
<string name="settings">Ayarlar</string>
<string name="install">Yükle</string>
<string name="unsupport_magisk_title">Desteklenmeyen Magisk Sürümü</string>
<string name="unsupport_magisk_message">Magisk Manager\'ın bu sürümü v18.0\'dan daha düşük Magisk versiyonlarını desteklememektedir.\n\nMagisk\'i manuel olarak yükseltebilir veya uygulamayı daha eski bir sürüme düşürebilirsiniz.</string>
<!--Status Fragment-->
<string name="magisk_version_error">Magisk yüklü değil</string>
@ -167,9 +166,6 @@
<string name="request_timeout_summary">%1$d saniye</string>
<string name="settings_su_reauth_title">Yükseltmeden sonra yeniden kimlik doğrula</string>
<string name="settings_su_reauth_summary">Uygulama yükseltmeleri sonrasında yetkili kullanıcı izinlerini yeniden doğrula</string>
<string name="settings_su_fingerprint_title">Parmak İzi Kimlik Doğrulamayı Etkinleştir</string>
<string name="settings_su_fingerprint_summary">Yetkili kullanıcı isteklerine izin vermek için parmak izi tarayıcısını kullan</string>
<string name="auth_fingerprint">Parmak izini doğrula</string>
<string name="multiuser_mode">Çok Kullanıcılı Mod</string>
<string name="settings_owner_only">Yalnızca Cihaz Sahibi</string>
@ -186,7 +182,6 @@
<string name="global_summary">Tüm kök oturumları genel bağlama ad alanını kullanır</string>
<string name="requester_summary">Kök oturumları, istekte bulunanın ad alanını devralır</string>
<string name="isolate_summary">Her bir kök oturumunun kendi izole ad alanı olacaktır</string>
<string name="disable_fingerprint">Parmak izi ayarlanmadı veya cihaz desteği yok</string>
<string name="settings_download_path_error">Klasör oluşturma hatası. Depolama kök dizininden erişilebilir olmalı ve bir dosya olmamalıdır.</string>
<!--Superuser-->
@ -214,7 +209,6 @@
<string name="su_revoke_msg">%1$s hakları geri alınsın mı?</string>
<string name="toast">Pencere</string>
<string name="none">Hiçbiri</string>
<string name="auth_fail">Kimlik Doğrulama Başarısız</string>
<!--Superuser logs-->
<string name="pid">PID: %1$d</string>

Some files were not shown because too many files have changed in this diff Show More