mirror of
https://github.com/topjohnwu/Magisk.git
synced 2025-04-16 15:01:25 +00:00
Remove SafetyNet check
This commit is contained in:
parent
8d59caf635
commit
470fc97d1f
@ -25,9 +25,4 @@
|
|||||||
android:usesCleartextTraffic="true"
|
android:usesCleartextTraffic="true"
|
||||||
tools:ignore="UnusedAttribute" />
|
tools:ignore="UnusedAttribute" />
|
||||||
|
|
||||||
<!-- Hardcode GMS version -->
|
|
||||||
<meta-data
|
|
||||||
android:name="com.google.android.gms.version"
|
|
||||||
android:value="12451000" />
|
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
|
@ -75,9 +75,6 @@ class HomeViewModel(
|
|||||||
var stateManagerProgress = 0
|
var stateManagerProgress = 0
|
||||||
set(value) = set(value, field, { field = it }, BR.stateManagerProgress)
|
set(value) = set(value, field, { field = it }, BR.stateManagerProgress)
|
||||||
|
|
||||||
@get:Bindable
|
|
||||||
val showSafetyNet get() = Info.hasGMS && isConnected.get()
|
|
||||||
|
|
||||||
val itemBinding = itemBindingOf<IconLink> {
|
val itemBinding = itemBindingOf<IconLink> {
|
||||||
it.bindExtra(BR.viewModel, this)
|
it.bindExtra(BR.viewModel, this)
|
||||||
}
|
}
|
||||||
@ -86,7 +83,6 @@ class HomeViewModel(
|
|||||||
|
|
||||||
override fun refresh() = viewModelScope.launch {
|
override fun refresh() = viewModelScope.launch {
|
||||||
state = State.LOADING
|
state = State.LOADING
|
||||||
notifyPropertyChanged(BR.showSafetyNet)
|
|
||||||
Info.getRemote(svc)?.apply {
|
Info.getRemote(svc)?.apply {
|
||||||
state = State.LOADED
|
state = State.LOADED
|
||||||
|
|
||||||
@ -101,10 +97,10 @@ class HomeViewModel(
|
|||||||
launch {
|
launch {
|
||||||
ensureEnv()
|
ensureEnv()
|
||||||
}
|
}
|
||||||
} ?: {
|
} ?: run {
|
||||||
state = State.LOADING_FAILED
|
state = State.LOADING_FAILED
|
||||||
managerRemoteVersion = R.string.not_available.asText()
|
managerRemoteVersion = R.string.not_available.asText()
|
||||||
}()
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val showTest = false
|
val showTest = false
|
||||||
@ -134,9 +130,6 @@ class HomeViewModel(
|
|||||||
HomeFragmentDirections.actionHomeFragmentToInstallFragment().navigate()
|
HomeFragmentDirections.actionHomeFragmentToInstallFragment().navigate()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onSafetyNetPressed() =
|
|
||||||
HomeFragmentDirections.actionHomeFragmentToSafetynetFragment().navigate()
|
|
||||||
|
|
||||||
fun hideNotice() {
|
fun hideNotice() {
|
||||||
Config.safetyNotice = false
|
Config.safetyNotice = false
|
||||||
isNoticeVisible = false
|
isNoticeVisible = false
|
||||||
|
@ -1,225 +0,0 @@
|
|||||||
@file:Suppress("DEPRECATION")
|
|
||||||
|
|
||||||
package com.topjohnwu.magisk.ui.safetynet
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.util.Base64
|
|
||||||
import com.squareup.moshi.Json
|
|
||||||
import com.squareup.moshi.JsonClass
|
|
||||||
import com.squareup.moshi.Moshi
|
|
||||||
import com.topjohnwu.magisk.R
|
|
||||||
import com.topjohnwu.magisk.arch.ContextExecutor
|
|
||||||
import com.topjohnwu.magisk.arch.ViewEventWithScope
|
|
||||||
import com.topjohnwu.magisk.core.Const
|
|
||||||
import com.topjohnwu.magisk.di.ServiceLocator
|
|
||||||
import com.topjohnwu.magisk.ktx.createClassLoader
|
|
||||||
import com.topjohnwu.magisk.ktx.reflectField
|
|
||||||
import com.topjohnwu.magisk.ktx.writeTo
|
|
||||||
import com.topjohnwu.magisk.signing.CryptoUtils
|
|
||||||
import com.topjohnwu.magisk.view.MagiskDialog
|
|
||||||
import com.topjohnwu.superuser.Shell
|
|
||||||
import dalvik.system.BaseDexClassLoader
|
|
||||||
import dalvik.system.DexFile
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
import org.bouncycastle.asn1.ASN1Encoding
|
|
||||||
import org.bouncycastle.asn1.ASN1Primitive
|
|
||||||
import org.bouncycastle.est.jcajce.JsseDefaultHostnameAuthorizer
|
|
||||||
import timber.log.Timber
|
|
||||||
import java.io.ByteArrayInputStream
|
|
||||||
import java.io.File
|
|
||||||
import java.io.IOException
|
|
||||||
import java.lang.reflect.InvocationHandler
|
|
||||||
import java.lang.reflect.Proxy
|
|
||||||
import java.security.SecureRandom
|
|
||||||
import java.security.Signature
|
|
||||||
|
|
||||||
class CheckSafetyNetEvent(
|
|
||||||
private val callback: (SafetyNetResult) -> Unit = {}
|
|
||||||
) : ViewEventWithScope(), ContextExecutor, SafetyNetHelper.Callback {
|
|
||||||
|
|
||||||
private val svc get() = ServiceLocator.networkService
|
|
||||||
|
|
||||||
private lateinit var jar: File
|
|
||||||
private lateinit var nonce: ByteArray
|
|
||||||
|
|
||||||
override fun invoke(context: Context) {
|
|
||||||
jar = File("${context.filesDir.parent}/snet", "snet.jar")
|
|
||||||
|
|
||||||
scope.launch(Dispatchers.IO) {
|
|
||||||
attest(context) {
|
|
||||||
// Download and retry
|
|
||||||
Shell.sh("rm -rf " + jar.parent).exec()
|
|
||||||
jar.parentFile?.mkdir()
|
|
||||||
withContext(Dispatchers.Main) {
|
|
||||||
showDialog(context)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun attest(context: Context, onError: suspend (Exception) -> Unit) {
|
|
||||||
val helper: SafetyNetHelper
|
|
||||||
try {
|
|
||||||
val loader = createClassLoader(jar)
|
|
||||||
|
|
||||||
// Scan through the dex and find our helper class
|
|
||||||
var clazz: Class<*>? = null
|
|
||||||
loop@for (dex in loader.getDexFiles()) {
|
|
||||||
for (name in dex.entries()) {
|
|
||||||
val cls = loader.loadClass(name)
|
|
||||||
if (InvocationHandler::class.java.isAssignableFrom(cls)) {
|
|
||||||
clazz = cls
|
|
||||||
break@loop
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
clazz ?: throw Exception("Cannot find SafetyNetHelper implementation")
|
|
||||||
|
|
||||||
helper = Proxy.newProxyInstance(
|
|
||||||
loader, arrayOf(SafetyNetHelper::class.java),
|
|
||||||
clazz.newInstance() as InvocationHandler) as SafetyNetHelper
|
|
||||||
|
|
||||||
if (helper.version != Const.SNET_EXT_VER)
|
|
||||||
throw Exception("snet extension version mismatch")
|
|
||||||
} catch (e: Exception) {
|
|
||||||
onError(e)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
val random = SecureRandom()
|
|
||||||
nonce = ByteArray(24)
|
|
||||||
random.nextBytes(nonce)
|
|
||||||
helper.attest(context, nonce, this)
|
|
||||||
}
|
|
||||||
|
|
||||||
// All of these fields are whitelisted
|
|
||||||
private fun BaseDexClassLoader.getDexFiles(): List<DexFile> {
|
|
||||||
val pathList = BaseDexClassLoader::class.java.reflectField("pathList").get(this)
|
|
||||||
val dexElements = pathList.javaClass.reflectField("dexElements").get(pathList) as Array<*>
|
|
||||||
val fileField = dexElements.javaClass.componentType.reflectField("dexFile")
|
|
||||||
return dexElements.map { fileField.get(it) as DexFile }
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun download(context: Context) = scope.launch(Dispatchers.IO) {
|
|
||||||
val abort: suspend (Exception) -> Unit = {
|
|
||||||
Timber.e(it)
|
|
||||||
withContext(Dispatchers.Main) {
|
|
||||||
callback(SafetyNetResult())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
svc.fetchSafetynet().byteStream().writeTo(jar)
|
|
||||||
attest(context, abort)
|
|
||||||
} catch (e: IOException) {
|
|
||||||
abort(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun showDialog(context: Context) {
|
|
||||||
MagiskDialog(context)
|
|
||||||
.applyTitle(R.string.proprietary_title)
|
|
||||||
.applyMessage(R.string.proprietary_notice)
|
|
||||||
.cancellable(false)
|
|
||||||
.applyButton(MagiskDialog.ButtonType.POSITIVE) {
|
|
||||||
titleRes = android.R.string.ok
|
|
||||||
onClick { download(context) }
|
|
||||||
}
|
|
||||||
.applyButton(MagiskDialog.ButtonType.NEGATIVE) {
|
|
||||||
titleRes = android.R.string.cancel
|
|
||||||
onClick { callback(SafetyNetResult(dismiss = true)) }
|
|
||||||
}
|
|
||||||
.onCancel {
|
|
||||||
callback(SafetyNetResult(dismiss = true))
|
|
||||||
}
|
|
||||||
.reveal()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun String.decode(): ByteArray {
|
|
||||||
return if (contains("[+/]".toRegex()))
|
|
||||||
Base64.decode(this, Base64.DEFAULT)
|
|
||||||
else
|
|
||||||
Base64.decode(this, Base64.URL_SAFE)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun String.parseJws(): SafetyNetResponse {
|
|
||||||
val jws = split('.')
|
|
||||||
val secondDot = lastIndexOf('.')
|
|
||||||
val rawHeader = String(jws[0].decode())
|
|
||||||
val payload = String(jws[1].decode())
|
|
||||||
var signature = jws[2].decode()
|
|
||||||
val signedBytes = substring(0, secondDot).toByteArray()
|
|
||||||
|
|
||||||
val moshi = Moshi.Builder().build()
|
|
||||||
val header = moshi.adapter(JwsHeader::class.java).fromJson(rawHeader)
|
|
||||||
?: error("Invalid JWS header")
|
|
||||||
|
|
||||||
val alg = when (header.algorithm) {
|
|
||||||
"RS256" -> "SHA256withRSA"
|
|
||||||
"ES256" -> {
|
|
||||||
// Convert to DER encoding
|
|
||||||
signature = ASN1Primitive.fromByteArray(signature).getEncoded(ASN1Encoding.DER)
|
|
||||||
"SHA256withECDSA"
|
|
||||||
}
|
|
||||||
else -> error("Unsupported algorithm: ${header.algorithm}")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify signature
|
|
||||||
val certB64 = header.certificates?.first() ?: error("Cannot find certificate in JWS")
|
|
||||||
val bis = ByteArrayInputStream(certB64.decode())
|
|
||||||
val cert = CryptoUtils.readCertificate(bis)
|
|
||||||
val verifier = Signature.getInstance(alg)
|
|
||||||
verifier.initVerify(cert.publicKey)
|
|
||||||
verifier.update(signedBytes)
|
|
||||||
if (!verifier.verify(signature))
|
|
||||||
error("Signature mismatch")
|
|
||||||
|
|
||||||
// Verify hostname
|
|
||||||
val hostnameVerifier = JsseDefaultHostnameAuthorizer(setOf())
|
|
||||||
if (!hostnameVerifier.verify("attest.android.com", cert))
|
|
||||||
error("Hostname mismatch")
|
|
||||||
|
|
||||||
val response = moshi.adapter(SafetyNetResponse::class.java).fromJson(payload)
|
|
||||||
?: error("Invalid SafetyNet response")
|
|
||||||
|
|
||||||
// Verify results
|
|
||||||
if (!response.nonce.decode().contentEquals(nonce))
|
|
||||||
error("nonce mismatch")
|
|
||||||
|
|
||||||
return response
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onResponse(response: String?) {
|
|
||||||
if (response != null) {
|
|
||||||
scope.launch(Dispatchers.Default) {
|
|
||||||
val res = runCatching { response.parseJws() }.getOrElse {
|
|
||||||
Timber.e(it)
|
|
||||||
INVALID_RESPONSE
|
|
||||||
}
|
|
||||||
withContext(Dispatchers.Main) {
|
|
||||||
callback(SafetyNetResult(res))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
callback(SafetyNetResult())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@JsonClass(generateAdapter = true)
|
|
||||||
data class JwsHeader(
|
|
||||||
@Json(name = "alg") val algorithm: String,
|
|
||||||
@Json(name = "x5c") val certificates: List<String>?
|
|
||||||
)
|
|
||||||
|
|
||||||
@JsonClass(generateAdapter = true)
|
|
||||||
data class SafetyNetResponse(
|
|
||||||
val nonce: String,
|
|
||||||
val ctsProfileMatch: Boolean,
|
|
||||||
val basicIntegrity: Boolean,
|
|
||||||
val evaluationType: String = ""
|
|
||||||
)
|
|
||||||
|
|
||||||
// Special instance to indicate invalid SafetyNet response
|
|
||||||
val INVALID_RESPONSE = SafetyNetResponse("", ctsProfileMatch = false, basicIntegrity = false)
|
|
@ -1,14 +0,0 @@
|
|||||||
package com.topjohnwu.magisk.ui.safetynet
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
|
|
||||||
interface SafetyNetHelper {
|
|
||||||
|
|
||||||
val version: Int
|
|
||||||
|
|
||||||
fun attest(context: Context, nonce: ByteArray, callback: Callback)
|
|
||||||
|
|
||||||
interface Callback {
|
|
||||||
fun onResponse(response: String?)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,31 +0,0 @@
|
|||||||
package com.topjohnwu.magisk.ui.safetynet
|
|
||||||
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import com.topjohnwu.magisk.R
|
|
||||||
import com.topjohnwu.magisk.arch.BaseUIFragment
|
|
||||||
import com.topjohnwu.magisk.databinding.FragmentSafetynetMd2Binding
|
|
||||||
import com.topjohnwu.magisk.di.viewModel
|
|
||||||
|
|
||||||
class SafetynetFragment : BaseUIFragment<SafetynetViewModel, FragmentSafetynetMd2Binding>() {
|
|
||||||
|
|
||||||
override val layoutRes = R.layout.fragment_safetynet_md2
|
|
||||||
override val viewModel by viewModel<SafetynetViewModel>()
|
|
||||||
|
|
||||||
override fun onStart() {
|
|
||||||
super.onStart()
|
|
||||||
activity.setTitle(R.string.safetynet)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
|
||||||
super.onCreateView(inflater, container, savedInstanceState)
|
|
||||||
|
|
||||||
// Set barrier reference IDs in code, since resource IDs will be stripped in release mode
|
|
||||||
binding.snetBarrier.referencedIds = intArrayOf(R.id.basic_text, R.id.cts_text)
|
|
||||||
|
|
||||||
return binding.root
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,95 +0,0 @@
|
|||||||
package com.topjohnwu.magisk.ui.safetynet
|
|
||||||
|
|
||||||
import androidx.databinding.Bindable
|
|
||||||
import com.topjohnwu.magisk.BR
|
|
||||||
import com.topjohnwu.magisk.R
|
|
||||||
import com.topjohnwu.magisk.arch.BaseViewModel
|
|
||||||
import com.topjohnwu.magisk.databinding.set
|
|
||||||
|
|
||||||
class SafetyNetResult(
|
|
||||||
val response: SafetyNetResponse? = null,
|
|
||||||
val dismiss: Boolean = false
|
|
||||||
)
|
|
||||||
|
|
||||||
class SafetynetViewModel : BaseViewModel() {
|
|
||||||
|
|
||||||
@get:Bindable
|
|
||||||
var safetyNetTitle = R.string.empty
|
|
||||||
set(value) = set(value, field, { field = it }, BR.safetyNetTitle)
|
|
||||||
|
|
||||||
@get:Bindable
|
|
||||||
var ctsState = false
|
|
||||||
set(value) = set(value, field, { field = it }, BR.ctsState)
|
|
||||||
|
|
||||||
@get:Bindable
|
|
||||||
var basicIntegrityState = false
|
|
||||||
set(value) = set(value, field, { field = it }, BR.basicIntegrityState)
|
|
||||||
|
|
||||||
@get:Bindable
|
|
||||||
var evalType = ""
|
|
||||||
set(value) = set(value, field, { field = it }, BR.evalType)
|
|
||||||
|
|
||||||
@get:Bindable
|
|
||||||
var isChecking = false
|
|
||||||
set(value) = set(value, field, { field = it }, BR.checking)
|
|
||||||
|
|
||||||
@get:Bindable
|
|
||||||
var isSuccess = false
|
|
||||||
set(value) = set(value, field, { field = it }, BR.success, BR.textColorAttr)
|
|
||||||
|
|
||||||
@get:Bindable
|
|
||||||
val textColorAttr get() = if (isSuccess) R.attr.colorOnPrimary else R.attr.colorOnError
|
|
||||||
|
|
||||||
init {
|
|
||||||
cachedResult?.also {
|
|
||||||
handleResult(SafetyNetResult(it))
|
|
||||||
} ?: attest()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun attest() {
|
|
||||||
isChecking = true
|
|
||||||
CheckSafetyNetEvent(::handleResult).publish()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun reset() = attest()
|
|
||||||
|
|
||||||
private fun handleResult(result: SafetyNetResult) {
|
|
||||||
isChecking = false
|
|
||||||
|
|
||||||
if (result.dismiss) {
|
|
||||||
back()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
result.response?.apply {
|
|
||||||
cachedResult = this
|
|
||||||
if (this === INVALID_RESPONSE) {
|
|
||||||
isSuccess = false
|
|
||||||
ctsState = false
|
|
||||||
basicIntegrityState = false
|
|
||||||
evalType = "N/A"
|
|
||||||
safetyNetTitle = R.string.safetynet_res_invalid
|
|
||||||
} else {
|
|
||||||
val success = ctsProfileMatch && basicIntegrity
|
|
||||||
isSuccess = success
|
|
||||||
ctsState = ctsProfileMatch
|
|
||||||
basicIntegrityState = basicIntegrity
|
|
||||||
evalType = if (evaluationType.contains("HARDWARE")) "HARDWARE" else "BASIC"
|
|
||||||
safetyNetTitle =
|
|
||||||
if (success) R.string.safetynet_attest_success
|
|
||||||
else R.string.safetynet_attest_failure
|
|
||||||
}
|
|
||||||
} ?: run {
|
|
||||||
isSuccess = false
|
|
||||||
ctsState = false
|
|
||||||
basicIntegrityState = false
|
|
||||||
evalType = "N/A"
|
|
||||||
safetyNetTitle = R.string.safetynet_api_error
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private var cachedResult: SafetyNetResponse? = null
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -108,24 +108,10 @@
|
|||||||
app:layout_constraintTop_toBottomOf="@+id/home_magisk_wrapper" />
|
app:layout_constraintTop_toBottomOf="@+id/home_magisk_wrapper" />
|
||||||
|
|
||||||
<Space
|
<Space
|
||||||
gone="@{!viewModel.showSafetyNet && !Info.env.isActive}"
|
goneUnless="@{Info.env.isActive}"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="@dimen/l1" />
|
android:layout_height="@dimen/l1" />
|
||||||
|
|
||||||
<Button
|
|
||||||
style="@style/WidgetFoundation.Button.Outlined"
|
|
||||||
gone="@{!viewModel.showSafetyNet}"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginStart="@dimen/l1"
|
|
||||||
android:layout_marginEnd="@dimen/l1"
|
|
||||||
android:onClick="@{() -> viewModel.onSafetyNetPressed()}"
|
|
||||||
android:text="@string/home_check_safetynet"
|
|
||||||
android:textAllCaps="false"
|
|
||||||
android:textSize="12sp"
|
|
||||||
app:cornerRadius="@dimen/r1"
|
|
||||||
app:icon="@drawable/ic_safetynet_md2" />
|
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
style="@style/WidgetFoundation.Button.Outlined.Error"
|
style="@style/WidgetFoundation.Button.Outlined.Error"
|
||||||
goneUnless="@{Info.env.isActive}"
|
goneUnless="@{Info.env.isActive}"
|
||||||
|
@ -1,234 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools">
|
|
||||||
|
|
||||||
<data>
|
|
||||||
|
|
||||||
<import type="android.os.Build" />
|
|
||||||
<import type="com.topjohnwu.magisk.core.Info" />
|
|
||||||
<import type="com.topjohnwu.magisk.BuildConfig" />
|
|
||||||
<import type="com.topjohnwu.magisk.R" />
|
|
||||||
|
|
||||||
<variable
|
|
||||||
name="viewModel"
|
|
||||||
type="com.topjohnwu.magisk.ui.safetynet.SafetynetViewModel" />
|
|
||||||
|
|
||||||
</data>
|
|
||||||
|
|
||||||
|
|
||||||
<androidx.core.widget.NestedScrollView
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:clipToPadding="false"
|
|
||||||
android:fillViewport="true"
|
|
||||||
android:paddingTop="@dimen/internal_action_bar_size"
|
|
||||||
app:fitsSystemWindowsInsets="top|bottom"
|
|
||||||
tools:paddingBottom="48dp"
|
|
||||||
tools:paddingTop="24dp">
|
|
||||||
|
|
||||||
<FrameLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
invisibleUnless="@{viewModel.checking}"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="center"
|
|
||||||
android:text="@string/safetynet_attest_loading"
|
|
||||||
android:textAppearance="@style/AppearanceFoundation.Title" />
|
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
|
||||||
invisible="@{viewModel.checking}"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:paddingTop="@dimen/l1"
|
|
||||||
android:paddingBottom="@dimen/l1">
|
|
||||||
|
|
||||||
<com.google.android.material.card.MaterialCardView
|
|
||||||
android:id="@+id/safetynet_attestation"
|
|
||||||
style="@style/WidgetFoundation.Card.Elevated"
|
|
||||||
cardBackgroundColorAttr="@{viewModel.success ? R.attr.colorPrimary : R.attr.colorError}"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginStart="@dimen/l1"
|
|
||||||
android:layout_marginTop="@dimen/l2"
|
|
||||||
android:layout_marginEnd="@dimen/l1"
|
|
||||||
app:cardCornerRadius="@dimen/l1"
|
|
||||||
app:cardElevation="@dimen/l1"
|
|
||||||
app:layout_constraintHorizontal_chainStyle="packed"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
|
||||||
app:layout_constraintVertical_bias=".35"
|
|
||||||
app:layout_constraintWidth_max="300dp"
|
|
||||||
tools:cardBackgroundColor="?colorPrimary">
|
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/safetynet_title"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginStart="@dimen/l1"
|
|
||||||
android:layout_marginTop="@dimen/l2"
|
|
||||||
android:layout_marginEnd="@dimen/l1"
|
|
||||||
android:gravity="center"
|
|
||||||
android:text="@{viewModel.safetyNetTitle}"
|
|
||||||
android:textAppearance="@style/AppearanceFoundation.Display.OnPrimary"
|
|
||||||
android:textStyle="bold"
|
|
||||||
app:textColorAttr="@{viewModel.textColorAttr}"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintHorizontal_chainStyle="packed"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
|
||||||
tools:text="@string/safetynet_attest_success" />
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/safetynet_divider"
|
|
||||||
android:layout_width="50dp"
|
|
||||||
android:layout_height="4dp"
|
|
||||||
android:layout_marginTop="@dimen/l2"
|
|
||||||
app:srcCompat="@drawable/bg_divider_rounded_on_primary"
|
|
||||||
app:tintAttr="@{viewModel.textColorAttr}"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toBottomOf="@+id/safetynet_title"
|
|
||||||
tools:tint="?colorOnPrimary"/>
|
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
|
||||||
android:id="@+id/checkbox_layout"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:paddingBottom="@dimen/l1"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toBottomOf="@+id/safetynet_divider">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/basic_text"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="center_vertical"
|
|
||||||
android:gravity="start"
|
|
||||||
android:text="basicIntegrity"
|
|
||||||
android:textAppearance="@style/AppearanceFoundation.Body.OnPrimary"
|
|
||||||
android:textStyle="bold"
|
|
||||||
android:layout_marginTop="@dimen/l2"
|
|
||||||
app:textColorAttr="@{viewModel.textColorAttr}"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintEnd_toEndOf="@id/snet_barrier"
|
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
|
||||||
tools:ignore="HardcodedText" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/cts_text"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="center_vertical"
|
|
||||||
android:gravity="start"
|
|
||||||
android:text="ctsProfile"
|
|
||||||
android:textAppearance="@style/AppearanceFoundation.Body.OnPrimary"
|
|
||||||
android:textStyle="bold"
|
|
||||||
android:layout_marginTop="@dimen/l2"
|
|
||||||
app:textColorAttr="@{viewModel.textColorAttr}"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintEnd_toEndOf="@id/snet_barrier"
|
|
||||||
app:layout_constraintTop_toBottomOf="@id/basic_text"
|
|
||||||
tools:ignore="HardcodedText" />
|
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.Barrier
|
|
||||||
android:id="@+id/snet_barrier"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
app:barrierDirection="end"
|
|
||||||
app:constraint_referenced_ids="basic_text,cts_text"/>
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
style="@style/WidgetFoundation.Icon.OnPrimary"
|
|
||||||
isSelected="@{viewModel.basicIntegrityState}"
|
|
||||||
android:layout_gravity="center"
|
|
||||||
app:srcCompat="@drawable/ic_check_circle_md2"
|
|
||||||
app:tintAttr="@{viewModel.textColorAttr}"
|
|
||||||
app:layout_constraintStart_toEndOf="@id/snet_barrier"
|
|
||||||
app:layout_constraintTop_toTopOf="@id/basic_text"
|
|
||||||
app:layout_constraintBottom_toBottomOf="@id/basic_text"
|
|
||||||
tools:tint="?colorOnPrimary"/>
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
style="@style/WidgetFoundation.Icon.OnPrimary"
|
|
||||||
isSelected="@{viewModel.ctsState}"
|
|
||||||
android:layout_gravity="center"
|
|
||||||
app:srcCompat="@drawable/ic_check_circle_md2"
|
|
||||||
app:tintAttr="@{viewModel.textColorAttr}"
|
|
||||||
app:layout_constraintStart_toEndOf="@id/snet_barrier"
|
|
||||||
app:layout_constraintTop_toTopOf="@id/cts_text"
|
|
||||||
app:layout_constraintBottom_toBottomOf="@id/cts_text"
|
|
||||||
tools:tint="?colorOnPrimary"/>
|
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="horizontal"
|
|
||||||
android:paddingBottom="@dimen/l2"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toBottomOf="@+id/checkbox_layout">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="center_vertical"
|
|
||||||
android:gravity="start"
|
|
||||||
android:text="evalType"
|
|
||||||
android:textAppearance="@style/AppearanceFoundation.Body.OnPrimary"
|
|
||||||
android:textStyle="bold"
|
|
||||||
app:textColorAttr="@{viewModel.textColorAttr}"
|
|
||||||
tools:ignore="HardcodedText" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="center_vertical"
|
|
||||||
android:textAppearance="@style/AppearanceFoundation.Body.OnPrimary"
|
|
||||||
android:textStyle="bold"
|
|
||||||
android:layout_marginStart="@dimen/l1"
|
|
||||||
android:text="@{viewModel.evalType}"
|
|
||||||
app:textColorAttr="@{viewModel.textColorAttr}"
|
|
||||||
tools:text="HARDWARE"/>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
||||||
|
|
||||||
</com.google.android.material.card.MaterialCardView>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
style="@style/WidgetFoundation.Button.Text"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="@dimen/l1"
|
|
||||||
android:layout_marginBottom="@dimen/l1"
|
|
||||||
android:onClick="@{() -> viewModel.reset()}"
|
|
||||||
android:text="@string/safetynet_attest_restart"
|
|
||||||
android:textAllCaps="false"
|
|
||||||
android:textColor="?colorOnSurfaceVariant"
|
|
||||||
app:icon="@drawable/ic_refresh_safetynet_md2"
|
|
||||||
app:iconTint="?colorOnSurfaceVariant"
|
|
||||||
app:layout_constraintEnd_toEndOf="@+id/safetynet_attestation"
|
|
||||||
app:layout_constraintTop_toBottomOf="@+id/safetynet_attestation" />
|
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
||||||
|
|
||||||
</FrameLayout>
|
|
||||||
|
|
||||||
|
|
||||||
</androidx.core.widget.NestedScrollView>
|
|
||||||
|
|
||||||
</layout>
|
|
@ -33,14 +33,6 @@
|
|||||||
app:popEnterAnim="@anim/fragment_enter_pop"
|
app:popEnterAnim="@anim/fragment_enter_pop"
|
||||||
app:popExitAnim="@anim/fragment_exit_pop" />
|
app:popExitAnim="@anim/fragment_exit_pop" />
|
||||||
|
|
||||||
<action
|
|
||||||
android:id="@+id/action_homeFragment_to_safetynetFragment"
|
|
||||||
app:destination="@id/safetynetFragment"
|
|
||||||
app:enterAnim="@anim/fragment_enter"
|
|
||||||
app:exitAnim="@anim/fragment_exit"
|
|
||||||
app:popEnterAnim="@anim/fragment_enter_pop"
|
|
||||||
app:popExitAnim="@anim/fragment_exit_pop" />
|
|
||||||
|
|
||||||
</fragment>
|
</fragment>
|
||||||
|
|
||||||
<fragment
|
<fragment
|
||||||
@ -84,12 +76,6 @@
|
|||||||
android:label="ModuleFragment"
|
android:label="ModuleFragment"
|
||||||
tools:layout="@layout/fragment_module_md2" />
|
tools:layout="@layout/fragment_module_md2" />
|
||||||
|
|
||||||
<fragment
|
|
||||||
android:id="@+id/safetynetFragment"
|
|
||||||
android:name="com.topjohnwu.magisk.ui.safetynet.SafetynetFragment"
|
|
||||||
android:label="SafetynetFragment"
|
|
||||||
tools:layout="@layout/fragment_safetynet_md2" />
|
|
||||||
|
|
||||||
<fragment
|
<fragment
|
||||||
android:id="@+id/settingsFragment"
|
android:id="@+id/settingsFragment"
|
||||||
android:name="com.topjohnwu.magisk.ui.settings.SettingsFragment"
|
android:name="com.topjohnwu.magisk.ui.settings.SettingsFragment"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user