Added safetynet implementation

This commit is contained in:
Viktor De Pasquale 2019-10-24 17:33:42 +02:00
parent 21f2f86cb8
commit 9e3afcfe7a
6 changed files with 342 additions and 45 deletions

View File

@ -1,9 +1,19 @@
package com.topjohnwu.magisk.redesign.safetynet
import android.os.Build
import android.view.View
import android.view.ViewAnimationUtils
import androidx.core.animation.doOnEnd
import androidx.core.view.isInvisible
import androidx.core.view.isVisible
import androidx.databinding.BindingAdapter
import androidx.interpolator.view.animation.FastOutSlowInInterpolator
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.databinding.FragmentSafetynetMd2Binding
import com.topjohnwu.magisk.redesign.compat.CompatFragment
import org.koin.androidx.viewmodel.ext.android.viewModel
import kotlin.math.hypot
import kotlin.math.roundToInt
class SafetynetFragment : CompatFragment<SafetynetViewModel, FragmentSafetynetMd2Binding>() {
@ -15,4 +25,34 @@ class SafetynetFragment : CompatFragment<SafetynetViewModel, FragmentSafetynetMd
activity.setTitle(R.string.safetyNet)
}
}
@BindingAdapter("revealSafetyNet")
fun View.revealOnCenter(expand: Boolean) {
val anim = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
isInvisible = expand
return
} else {
val x = measuredWidth.toDouble()
val y = measuredHeight.toDouble()
val maxRadius = hypot(x, y).toFloat()
val start = if (expand) 0f else maxRadius
val end = if (expand) maxRadius else 0f
ViewAnimationUtils.createCircularReveal(
this,
(x / 2).roundToInt(),
(y / 2).roundToInt(),
start,
end
).apply {
interpolator = FastOutSlowInInterpolator()
doOnEnd { if (!expand) isVisible = false }
}
}
post {
isVisible = true
anim.start()
}
}

View File

@ -1,5 +1,7 @@
package com.topjohnwu.magisk.redesign.safetynet
import androidx.databinding.Bindable
import com.topjohnwu.magisk.BR
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.extensions.subscribeK
import com.topjohnwu.magisk.model.events.SafetyNetResult
@ -14,44 +16,63 @@ class SafetynetViewModel(
rxBus: RxBus
) : CompatViewModel() {
private var currentState = IDLE
set(value) {
field = value
notifyStateChanged()
}
val safetyNetTitle = KObservableField(R.string.empty)
val ctsState = KObservableField(IDLE)
val basicIntegrityState = KObservableField(IDLE)
val ctsState = KObservableField(false)
val basicIntegrityState = KObservableField(false)
val isChecking @Bindable get() = currentState == LOADING
val isFailed @Bindable get() = currentState == FAILED
val isSuccess @Bindable get() = currentState == PASS
init {
rxBus.register<SafetyNetResult>()
.subscribeK { resolveResponse(it.responseCode) }
.add()
attest()
}
fun attest() = UpdateSafetyNetEvent().publish()
override fun notifyStateChanged() {
super.notifyStateChanged()
notifyPropertyChanged(BR.loading)
notifyPropertyChanged(BR.failed)
notifyPropertyChanged(BR.success)
}
private fun attest() {
currentState = LOADING
UpdateSafetyNetEvent().publish()
}
fun reset() = attest()
private fun resolveResponse(response: Int) = when {
//todo animate (reveal) to result (green/error)
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
ctsState.value = if (hasCtsPassed) {
PASS
} else {
FAILED
}
basicIntegrityState.value = if (hasBasicIntegrityPassed) {
PASS
} else {
FAILED
}
val result = hasCtsPassed && hasBasicIntegrityPassed
ctsState.value = hasCtsPassed
basicIntegrityState.value = hasBasicIntegrityPassed
currentState = if (result) PASS else FAILED
safetyNetTitle.value =
if (result) R.string.safetynet_attest_success
else R.string.safetynet_attest_failure
}
//todo animate (collapse) back to initial (fade error)
response == -2 -> {
ctsState.value = IDLE
basicIntegrityState.value = IDLE
currentState = FAILED
ctsState.value = false
basicIntegrityState.value = false
back()
}
//todo animate (collapse) back to initial (surface)
else -> {
ctsState.value = IDLE
basicIntegrityState.value = IDLE
currentState = FAILED
ctsState.value = false
basicIntegrityState.value = false
safetyNetTitle.value = when (response) {
SafetyNetHelper.RESPONSE_ERR -> R.string.safetyNet_res_invalid
else -> R.string.safetyNet_api_error

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<size android:height="4dp" />
<corners android:radius="2dp" />
<solid android:color="?colorOnPrimary" />
</shape>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="?colorOnSurface"
android:pathData="M12 21C8.25 20 5 15.54 5 11.22V6.3L12 3.18L19 6.3V12.07A6.45 6.45 0 0 1 20.91 12.67A11.63 11.63 0 0 0 21 11V5L12 1L3 5V11C3 16.55 6.84 21.74 12 23C12.35 22.91 12.7 22.8 13 22.68A6.3 6.3 0 0 1 12 21M18 14.5V13L15.75 15.25L18 17.5V16A2.5 2.5 0 0 1 20.24 19.62L21.33 20.71A4 4 0 0 0 18 14.5M18 21A2.5 2.5 0 0 1 15.76 17.38L14.67 16.29A4 4 0 0 0 18 22.5V24L20.25 21.75L18 19.5Z" />
</vector>

View File

@ -1,50 +1,264 @@
<?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:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<import type="com.topjohnwu.magisk.Info" />
<import type="com.topjohnwu.magisk.BuildConfig" />
<variable
name="viewModel"
type="com.topjohnwu.magisk.redesign.safetynet.SafetynetViewModel" />
</data>
<FrameLayout
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true">
android:clipToPadding="false"
android:fillViewport="true"
android:paddingTop="@{viewModel.insets.top + (int) @dimen/internal_action_bar_size}"
android:paddingBottom="@{viewModel.insets.bottom}"
tools:paddingBottom="48dp"
tools:paddingTop="24dp">
<androidx.constraintlayout.widget.ConstraintLayout
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.button.MaterialButton
style="?styleButtonText"
<androidx.appcompat.widget.AppCompatTextView
invisibleUnless="@{viewModel.checking}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="@{() -> viewModel.attest()}"
android:text="Attest"
android:textAllCaps="false"
app:icon="@drawable/ic_test"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
android:layout_gravity="center"
android:text="@string/safetynet_attest_loading"
android:textAppearance="?appearanceTextTitleNormal" />
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.constraintlayout.widget.ConstraintLayout
invisibleUnless="@{viewModel.success || viewModel.failed}"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingTop="@dimen/l1"
android:paddingBottom="@dimen/l1">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?colorPrimary">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/safetynet_icon"
style="?styleImageBig"
app:layout_constraintBottom_toTopOf="@+id/safetynet_magisk_title"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias=".3"
app:layout_constraintVertical_chainStyle="packed"
app:srcCompat="@drawable/ic_magisk"
app:tint="?colorOnSurface" />
<androidx.appcompat.widget.AppCompatTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/safetynet_magisk_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/l1"
android:text="@string/magisk"
android:textAppearance="?appearanceTextCaptionVariant"
app:layout_constraintBottom_toTopOf="@+id/safetynet_manager_title"
app:layout_constraintEnd_toStartOf="@+id/safetynet_magisk_version"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/safetynet_icon" />
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/safetynet_magisk_version"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/l_50"
android:text="@{Info.magiskVersionString}"
android:textAppearance="?appearanceTextCaptionNormal"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="@+id/safetynet_magisk_title"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toEndOf="@+id/safetynet_magisk_title"
app:layout_constraintTop_toTopOf="@+id/safetynet_magisk_title"
tools:text="20.0" />
</FrameLayout>
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/safetynet_manager_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/manager"
android:textAppearance="?appearanceTextCaptionVariant"
app:layout_constraintBottom_toTopOf="@+id/safetynet_attestation"
app:layout_constraintEnd_toStartOf="@+id/safetynet_manager_version"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/safetynet_magisk_title" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/safetynet_manager_version"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/l_50"
android:text="@{BuildConfig.VERSION_NAME}"
android:textAppearance="?appearanceTextCaptionNormal"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="@+id/safetynet_manager_title"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toEndOf="@+id/safetynet_manager_title"
app:layout_constraintTop_toTopOf="@+id/safetynet_manager_title"
tools:text="10.0.0" />
<com.google.android.material.card.MaterialCardView
android:id="@+id/safetynet_attestation"
style="?styleCardElevated"
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_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/safetynet_manager_title"
app:layout_constraintWidth_max="300dp">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<View
goneUnless="@{viewModel.failed}"
android:layout_width="match_parent"
android:layout_height="0dp"
android:alpha=".5"
android:background="?colorError"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<View
goneUnless="@{viewModel.success}"
android:layout_width="match_parent"
android:layout_height="0dp"
android:alpha=".5"
android:background="?colorSecondary"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.appcompat.widget.AppCompatTextView
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="?appearanceTextDisplayOnPrimary"
android:textStyle="bold"
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" />
<View
android:id="@+id/safetynet_divider"
android:layout_width="50dp"
android:layout_height="4dp"
android:layout_marginTop="@dimen/l2"
android:background="@drawable/bg_divider_rounded_on_primary"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/safetynet_title" />
<LinearLayout
android:id="@+id/safetynet_cts"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/l1"
android:orientation="horizontal"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/safetynet_divider">
<androidx.appcompat.widget.AppCompatTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:gravity="start"
android:text="ctsProfile"
android:textAppearance="?appearanceTextBodyOnPrimary"
android:textStyle="bold"
tools:ignore="HardcodedText" />
<androidx.appcompat.widget.AppCompatImageView
style="?styleIconOnPrimary"
isSelected="@{viewModel.ctsState}"
android:layout_gravity="center"
app:srcCompat="@drawable/ic_check_circle_md2" />
</LinearLayout>
<LinearLayout
android:id="@+id/safetynet_basicintegrity"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/l1"
android:orientation="horizontal"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/safetynet_cts">
<androidx.appcompat.widget.AppCompatTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:gravity="start"
android:text="basicIntegrity"
android:textAppearance="?appearanceTextBodyOnPrimary"
android:textStyle="bold"
tools:ignore="HardcodedText" />
<androidx.appcompat.widget.AppCompatImageView
style="?styleIconOnPrimary"
isSelected="@{viewModel.basicIntegrityState}"
android:layout_gravity="center"
app:srcCompat="@drawable/ic_check_circle_md2" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.button.MaterialButton
style="?styleButtonText"
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>

View File

@ -29,6 +29,11 @@
<!--ref. Magisk Hide-->
<string name="magisk_hide_md2">Hide</string>
<string name="safetynet_attest_success">Success!</string>
<string name="safetynet_attest_failure">Attestation failed!</string>
<string name="safetynet_attest_loading">Just a sec…</string>
<string name="safetynet_attest_restart">Try again</string>
<string name="settings_dark_mode_title">Theme Mode</string>
<string name="settings_dark_mode_message">Select mode which best suits your style!</string>
<string name="settings_dark_mode_light">Always Light</string>