Added safetynet to the rewritten home fragment

This commit is contained in:
Viktor De Pasquale 2019-04-15 18:36:38 +02:00
parent f332746188
commit ffec64d209
6 changed files with 202 additions and 300 deletions

View File

@ -13,3 +13,5 @@ class MagiskChangelogEvent : ViewEvent()
class UninstallEvent : ViewEvent()
class EnvFixEvent : ViewEvent()
class UpdateSafetyNetEvent : ViewEvent()

View File

@ -8,6 +8,7 @@ import com.topjohnwu.magisk.model.observer.Observer
import com.topjohnwu.magisk.tasks.CheckUpdates
import com.topjohnwu.magisk.ui.base.MagiskViewModel
import com.topjohnwu.magisk.utils.Event
import com.topjohnwu.magisk.utils.ISafetyNetHelper
import com.topjohnwu.magisk.utils.toggle
import com.topjohnwu.net.Networking
import com.topjohnwu.superuser.Shell
@ -66,6 +67,21 @@ class HomeViewModel(
""
}
val safetyNetTitle = KObservableField(resources.getString(R.string.safetyNet_check_text))
val ctsState = KObservableField(SafetyNetState.IDLE)
val basicIntegrityState = KObservableField(SafetyNetState.IDLE)
val safetyNetState = Observer(ctsState, basicIntegrityState) {
val cts = ctsState.value
val basic = basicIntegrityState.value
val states = listOf(cts, basic)
when {
states.any { it == SafetyNetState.LOADING } -> State.LOADING
states.any { it == SafetyNetState.IDLE } -> State.LOADING
else -> State.LOADED
}
}
val hasRoot = KObservableField(false)
private var shownDialog = false
@ -103,8 +119,46 @@ class HomeViewModel(
MagiskItem.MAGISK -> MagiskChangelogEvent().publish()
}
fun safetyNetPressed() {
ctsState.value = SafetyNetState.LOADING
basicIntegrityState.value = SafetyNetState.LOADING
safetyNetTitle.value = resources.getString(R.string.checking_safetyNet_status)
UpdateSafetyNetEvent().publish()
}
fun finishSafetyNetCheck(response: Int) = when {
response and 0x0F == 0 -> {
val hasCtsPassed = response and ISafetyNetHelper.CTS_PASS != 0
val hasBasicIntegrityPassed = response and ISafetyNetHelper.BASIC_PASS != 0
safetyNetTitle.value = resources.getString(R.string.safetyNet_check_success)
ctsState.value = if (hasCtsPassed) {
SafetyNetState.PASS
} else {
SafetyNetState.FAILED
}
basicIntegrityState.value = if (hasBasicIntegrityPassed) {
SafetyNetState.PASS
} else {
SafetyNetState.FAILED
}
}
response == -2 -> {
ctsState.value = SafetyNetState.IDLE
basicIntegrityState.value = SafetyNetState.IDLE
}
else -> {
ctsState.value = SafetyNetState.IDLE
basicIntegrityState.value = SafetyNetState.IDLE
val errorString = when (response) {
ISafetyNetHelper.RESPONSE_ERR -> R.string.safetyNet_res_invalid
else -> R.string.safetyNet_api_error
}
safetyNetTitle.value = resources.getString(errorString)
}
}
fun refresh() {
shownDialog = false
state = State.LOADING
magiskState.value = MagiskState.LOADING
managerState.value = MagiskState.LOADING
@ -155,7 +209,8 @@ class HomeViewModel(
}
private fun ensureEnv() {
val invalidStates = listOf(MagiskState.NOT_INSTALLED, MagiskState.NO_ROOT, MagiskState.LOADING)
val invalidStates =
listOf(MagiskState.NOT_INSTALLED, MagiskState.NO_ROOT, MagiskState.LOADING)
// Don't bother checking env when magisk is not installed, loading or already has been shown
if (invalidStates.any { it == magiskState.value } || shownDialog) return

View File

@ -3,56 +3,31 @@ package com.topjohnwu.magisk.ui.home
import com.skoumal.teanity.viewevents.ViewEvent
import com.topjohnwu.magisk.BuildConfig
import com.topjohnwu.magisk.Config
import com.topjohnwu.magisk.Const
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.databinding.FragmentMagiskBinding
import com.topjohnwu.magisk.model.events.*
import com.topjohnwu.magisk.utils.ISafetyNetHelper
import com.topjohnwu.magisk.view.MarkDownWindow
import com.topjohnwu.magisk.view.SafetyNet
import com.topjohnwu.magisk.view.SafetyNet.EXT_APK
import com.topjohnwu.magisk.view.dialogs.CustomAlertDialog
import com.topjohnwu.magisk.view.dialogs.EnvFixDialog
import com.topjohnwu.magisk.view.dialogs.ManagerInstallDialog
import com.topjohnwu.magisk.view.dialogs.UninstallDialog
import com.topjohnwu.net.Networking
import com.topjohnwu.superuser.Shell
import org.koin.androidx.viewmodel.ext.android.viewModel
import com.topjohnwu.magisk.ui.base.MagiskFragment as NewMagiskFragment
class MagiskFragment : NewMagiskFragment<HomeViewModel, com.topjohnwu.magisk.databinding.FragmentMagiskBinding>() {
/*@BindView(R.id.swipeRefreshLayout)
internal var mSwipeRefreshLayout: SwipeRefreshLayout? = null
@BindView(R.id.linearLayout)
internal var root: LinearLayout? = null
@BindView(R.id.install_option_card)
internal var installOptionCard: CardView? = null
@BindView(R.id.keep_force_enc)
internal var keepEncChkbox: CheckBox? = null
@BindView(R.id.keep_verity)
internal var keepVerityChkbox: CheckBox? = null
@BindView(R.id.install_option_expand)
internal var optionExpandLayout: ViewGroup? = null
@BindView(R.id.arrow)
internal var arrow: ImageView? = null
@BindView(R.id.uninstall_button)
internal var uninstallButton: CardView? = null
@BindColor(R.color.red500)
internal var colorBad: Int = 0
@BindColor(R.color.green500)
internal var colorOK: Int = 0
@BindColor(R.color.yellow500)
internal var colorWarn: Int = 0
@BindColor(R.color.green500)
internal var colorNeutral: Int = 0
@BindColor(R.color.blue500)
internal var colorInfo: Int = 0*/
/*private var magisk: UpdateCardHolder? = null
private var manager: UpdateCardHolder? = null
private var safetyNet: SafetyNet? = null
private var transition: Transition? = null
private var optionExpand: Expandable? = null*/
class MagiskFragment : NewMagiskFragment<HomeViewModel, FragmentMagiskBinding>(),
ISafetyNetHelper.Callback {
override val layoutRes: Int = R.layout.fragment_magisk
override val viewModel: HomeViewModel by viewModel()
override fun onResponse(responseCode: Int) = viewModel.finishSafetyNetCheck(responseCode)
override fun onEventDispatched(event: ViewEvent) {
super.onEventDispatched(event)
when (event) {
@ -62,6 +37,7 @@ class MagiskFragment : NewMagiskFragment<HomeViewModel, com.topjohnwu.magisk.dat
is UninstallEvent -> uninstall()
is ManagerChangelogEvent -> changelogManager()
is EnvFixEvent -> fixEnv()
is UpdateSafetyNetEvent -> updateSafetyNet(false)
}
}
@ -82,184 +58,37 @@ class MagiskFragment : NewMagiskFragment<HomeViewModel, com.topjohnwu.magisk.dat
private fun changelogManager() = MarkDownWindow
.show(requireActivity(), null, resources.openRawResource(R.raw.changelog))
/*override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val v = inflater.inflate(R.layout.fragment_magisk, container, false)
requireActivity().setTitle(R.string.magisk)
private fun downloadSafetyNet(requiresUserInput: Boolean = true) {
fun download() = Networking
.get(Const.Url.SNET_URL)
.getAsFile(EXT_APK) { updateSafetyNet(true) }
//safetyNet = SafetyNet(v)
*//*transition = TransitionSet()
.setOrdering(TransitionSet.ORDERING_TOGETHER)
.addTransition(Fade(Fade.OUT))
.addTransition(ChangeBounds())
.addTransition(Fade(Fade.IN))*//*
updateUI()
return v
}*/
private fun onRefresh() {
/*mSwipeRefreshLayout!!.isRefreshing = false
TransitionManager.beginDelayedTransition(root!!, transition)
safetyNet!!.reset()
magisk!!.reset()
manager!!.reset()*/
Config.loadMagiskInfo()
updateUI()
//FIXME requires old base
/*Event.reset(this)
Config.remoteMagiskVersionString = null
Config.remoteMagiskVersionCode = -1*/
shownDialog = false
// Trigger state check
/*if (Networking.checkNetworkStatus(app)) {
CheckUpdates.check()
}*/
}
private fun updateUI() {
/*(requireActivity() as MainActivity).checkHideSection()
val image: Int
val color: Int
val status: String
if (Config.magiskVersionCode < 0) {
color = colorBad
image = R.drawable.ic_cancel
status = getString(R.string.magisk_version_error)
magisk!!.status.text = status
magisk!!.currentVersion.visibility = View.GONE
} else {
color = colorOK
image = R.drawable.ic_check_circle
status = getString(R.string.magisk)
magisk!!.currentVersion.text = getString(
R.string.current_installed,
String.format(
Locale.US, "v%s (%d)",
Config.magiskVersionString, Config.magiskVersionCode
)
)
if (!requiresUserInput) {
download()
return
}
magisk!!.statusIcon.setColorFilter(color)
magisk!!.statusIcon.setImageResource(image)
manager!!.statusIcon.setColorFilter(colorOK)
manager!!.statusIcon.setImageResource(R.drawable.ic_check_circle)
manager!!.currentVersion.text = getString(
R.string.current_installed,
String.format(
Locale.US, "v%s (%d)",
BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE
)
)
if (!Networking.checkNetworkStatus(app)) {
// No network, updateCheckUI will not be triggered
magisk!!.status.text = status
manager!!.status.setText(R.string.app_name)
magisk!!.setValid(false)
manager!!.setValid(false)
}*/
CustomAlertDialog(requireActivity())
.setTitle(R.string.proprietary_title)
.setMessage(R.string.proprietary_notice)
.setCancelable(false)
.setPositiveButton(R.string.yes) { _, _ -> download() }
.setNegativeButton(R.string.no_thanks) { _, _ -> viewModel.finishSafetyNetCheck(-2) }
.show()
}
private fun updateCheckUI() {
/*var image: Int
var color: Int
var status: String
var button = ""
if (Config.remoteMagiskVersionCode < 0) {
color = colorNeutral
image = R.drawable.ic_help
status = getString(R.string.invalid_update_channel)
} else {
magisk!!.latestVersion.text = getString(
R.string.latest_version,
String.format(
Locale.US, "v%s (%d)",
Config.remoteMagiskVersionString, Config.remoteMagiskVersionCode
)
)
if (Config.remoteMagiskVersionCode > Config.magiskVersionCode) {
color = colorInfo
image = R.drawable.ic_update
status = getString(R.string.magisk_update_title)
button = getString(R.string.update)
} else {
color = colorOK
image = R.drawable.ic_check_circle
status = getString(R.string.magisk_up_to_date)
button = getString(R.string.install)
private fun updateSafetyNet(dieOnError: Boolean) {
try {
SafetyNet.dyRun(requireActivity(), this)
} catch (e: Exception) {
if (dieOnError) {
viewModel.finishSafetyNetCheck(-1)
return
}
Shell.sh("rm -rf " + EXT_APK.parent).exec()
EXT_APK.parentFile?.mkdir()
downloadSafetyNet(!dieOnError)
}
if (Config.magiskVersionCode > 0) {
// Only override status if Magisk is installed
magisk!!.statusIcon.setImageResource(image)
magisk!!.statusIcon.setColorFilter(color)
magisk!!.status.text = status
magisk!!.install.text = button
}
if (Config.remoteManagerVersionCode < 0) {
color = colorNeutral
image = R.drawable.ic_help
status = getString(R.string.invalid_update_channel)
} else {
manager!!.latestVersion.text = getString(
R.string.latest_version,
String.format(
Locale.US, "v%s (%d)",
Config.remoteManagerVersionString, Config.remoteManagerVersionCode
)
)
if (Config.remoteManagerVersionCode > BuildConfig.VERSION_CODE) {
color = colorInfo
image = R.drawable.ic_update
status = getString(R.string.manager_update_title)
manager!!.install.setText(R.string.update)
} else {
color = colorOK
image = R.drawable.ic_check_circle
status = getString(R.string.manager_up_to_date)
manager!!.install.setText(R.string.install)
}
}
manager!!.statusIcon.setImageResource(image)
manager!!.statusIcon.setColorFilter(color)
manager!!.status.text = status
magisk!!.setValid(Config.remoteMagiskVersionCode > 0)
manager!!.setValid(Config.remoteManagerVersionCode > 0)
if (Config.remoteMagiskVersionCode < 0) {
// Hide install related components
installOptionCard!!.visibility = View.GONE
uninstallButton!!.visibility = View.GONE
} else {
// Show install related components
installOptionCard!!.visibility = View.VISIBLE
uninstallButton!!.visibility = if (Shell.rootAccess()) View.VISIBLE else View.GONE
}
if (!shownDialog && Config.magiskVersionCode > 0 &&
!Shell.su("env_check").exec().isSuccess
) {
shownDialog = true
EnvFixDialog(requireActivity()).show()
}*/
}
companion object {
private var shownDialog = false
}
}

View File

@ -0,0 +1,5 @@
package com.topjohnwu.magisk.ui.home
enum class SafetyNetState {
LOADING, PASS, FAILED, IDLE
}

View File

@ -9,12 +9,7 @@ import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;
import androidx.annotation.StringRes;
import androidx.cardview.widget.CardView;
import butterknife.BindColor;
import butterknife.BindView;
import butterknife.OnClick;
import butterknife.Unbinder;
import com.topjohnwu.magisk.App;
import com.topjohnwu.magisk.Const;
import com.topjohnwu.magisk.R;
@ -22,13 +17,20 @@ import com.topjohnwu.magisk.utils.ISafetyNetHelper;
import com.topjohnwu.magisk.view.dialogs.CustomAlertDialog;
import com.topjohnwu.net.Networking;
import com.topjohnwu.superuser.Shell;
import dalvik.system.DexClassLoader;
import java.io.File;
import androidx.annotation.StringRes;
import androidx.cardview.widget.CardView;
import butterknife.BindColor;
import butterknife.BindView;
import butterknife.OnClick;
import butterknife.Unbinder;
import dalvik.system.DexClassLoader;
public class SafetyNet implements ISafetyNetHelper.Callback {
private static final File EXT_APK =
public static final File EXT_APK =
new File(App.self.getFilesDir().getParent() + "/snet", "snet.apk");
/*@BindView(R.id.safetyNet_card) */ CardView safetyNetCard;
@ -45,7 +47,7 @@ public class SafetyNet implements ISafetyNetHelper.Callback {
@BindColor(R.color.green500) int colorOK;
public Unbinder unbinder;
private ExpandableViewHolder expandable;
private final ExpandableViewHolder expandable;
public SafetyNet(View v) {
unbinder = new SafetyNet_ViewBinding(this, v);
@ -55,27 +57,16 @@ public class SafetyNet implements ISafetyNetHelper.Callback {
View.VISIBLE : View.GONE);
}
@OnClick(R.id.safetyNet_refresh)
void safetyNet(View v) {
Runnable task = () -> {
safetyNetProgress.setVisibility(View.VISIBLE);
safetyNetRefreshIcon.setVisibility(View.INVISIBLE);
safetyNetStatusText.setText(R.string.checking_safetyNet_status);
check((Activity) v.getContext());
expandable.collapse();
};
if (!SafetyNet.EXT_APK.exists()) {
// Show dialog
new CustomAlertDialog((Activity) v.getContext())
.setTitle(R.string.proprietary_title)
.setMessage(R.string.proprietary_notice)
.setCancelable(true)
.setPositiveButton(R.string.yes, (d, i) -> task.run())
.setNegativeButton(R.string.no_thanks, null)
.show();
} else {
task.run();
}
public static void dyRun(Activity activity, Object callback) throws Exception {
DexClassLoader loader = new DexClassLoader(EXT_APK.getPath(), EXT_APK.getParent(),
null, ISafetyNetHelper.class.getClassLoader());
Class<?> clazz = loader.loadClass("com.topjohnwu.snet.Snet");
ISafetyNetHelper helper = (ISafetyNetHelper) clazz.getMethod("newHelper",
Class.class, String.class, Activity.class, Object.class)
.invoke(null, ISafetyNetHelper.class, EXT_APK.getPath(), activity, callback);
if (helper.getVersion() < Const.SNET_EXT_VER)
throw new Exception();
helper.attest();
}
public void reset() {
@ -117,27 +108,38 @@ public class SafetyNet implements ISafetyNetHelper.Callback {
}
}
private void dyRun(Activity activity) throws Exception {
DexClassLoader loader = new DexClassLoader(EXT_APK.getPath(), EXT_APK.getParent(),
null, ISafetyNetHelper.class.getClassLoader());
Class<?> clazz = loader.loadClass("com.topjohnwu.snet.Snet");
ISafetyNetHelper helper = (ISafetyNetHelper) clazz.getMethod("newHelper",
Class.class, String.class, Activity.class, Object.class)
.invoke(null, ISafetyNetHelper.class, EXT_APK.getPath(), activity, this);
if (helper.getVersion() < Const.SNET_EXT_VER)
throw new Exception();
helper.attest();
@OnClick(R.id.safetyNet_refresh)
void safetyNet(View v) {
Runnable task = () -> {
safetyNetProgress.setVisibility(View.VISIBLE);
safetyNetRefreshIcon.setVisibility(View.INVISIBLE);
safetyNetStatusText.setText(R.string.checking_safetyNet_status);
check((Activity) v.getContext());
expandable.collapse();
};
if (!EXT_APK.exists()) {
// Show dialog
new CustomAlertDialog((Activity) v.getContext())
.setTitle(R.string.proprietary_title)
.setMessage(R.string.proprietary_notice)
.setCancelable(true)
.setPositiveButton(R.string.yes, (d, i) -> task.run())
.setNegativeButton(R.string.no_thanks, null)
.show();
} else {
task.run();
}
}
private void check(Activity activity) {
try {
dyRun(activity);
dyRun(activity, this);
} catch (Exception ignored) {
Shell.sh("rm -rf " + EXT_APK.getParent()).exec();
EXT_APK.getParentFile().mkdir();
Networking.get(Const.Url.SNET_URL).getAsFile(EXT_APK, f -> {
try {
dyRun(activity);
dyRun(activity, this);
} catch (Exception e) {
e.printStackTrace();
onResponse(-1);

View File

@ -1,13 +1,20 @@
<?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.skoumal.teanity.viewmodel.LoadingViewModel.State" />
<import type="com.topjohnwu.magisk.ui.home.SafetyNetState" />
<import type="com.topjohnwu.magisk.ui.home.MagiskState" />
<import type="com.topjohnwu.magisk.ui.home.MagiskItem" />
<import type="com.topjohnwu.magisk.R" />
<variable
name="viewModel"
type="com.topjohnwu.magisk.ui.home.HomeViewModel" />
@ -38,8 +45,8 @@
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:layout_marginBottom="5dp">
android:layout_marginBottom="5dp"
android:layout_marginTop="5dp">
<ImageView
android:id="@+id/icon"
@ -59,8 +66,8 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:paddingTop="10dp"
android:paddingBottom="10dp"
android:paddingTop="10dp"
android:text="@string/app_name"
android:textAppearance="@style/TextAppearance.AppCompat.Headline"
app:layout_constraintBottom_toBottomOf="parent"
@ -114,8 +121,8 @@
android:animateLayoutChanges="true"
android:onClick="@{() -> viewModel.advancedPressed()}"
android:orientation="vertical"
android:paddingTop="10dp"
android:paddingBottom="10dp">
android:paddingBottom="10dp"
android:paddingTop="10dp">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
@ -198,90 +205,87 @@
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
android:layout_height="wrap_content"
android:paddingBottom="@dimen/margin_generic_half"
android:paddingTop="@dimen/margin_generic_half">
<ImageView
android:id="@+id/sn_logo"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_marginTop="12dp"
android:layout_marginEnd="24dp"
android:layout_marginBottom="12dp"
android:tint="@color/green500"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/safetyNet_status"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/ic_safetynet" />
<TextView
android:id="@+id/safetyNet_status"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/margin_generic"
android:layout_marginRight="@dimen/margin_generic"
android:gravity="center"
android:maxLines="1"
android:text="@{viewModel.safetyNetTitle}"
android:textStyle="bold"
app:autoSizeMaxTextSize="14sp"
app:autoSizeTextType="uniform"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/safetyNet_refresh"
app:layout_constraintStart_toEndOf="@+id/sn_logo"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintWidth_min="200dp" />
<ImageView
android:id="@+id/safetyNet_refresh"
invisible="@{viewModel.ctsState == SafetyNetState.LOADING || viewModel.basicIntegrityState == SafetyNetState.LOADING}"
android:layout_width="25dp"
android:layout_height="25dp"
android:layout_marginStart="24dp"
android:onClick="@{() -> viewModel.safetyNetPressed()}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/safetyNet_status"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/ic_refresh" />
<ProgressBar
android:id="@+id/safetyNet_check_progress"
goneUnless="@{viewModel.ctsState == SafetyNetState.LOADING || viewModel.basicIntegrityState == SafetyNetState.LOADING}"
android:layout_width="0dp"
android:layout_height="0dp"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="@+id/safetyNet_refresh"
app:layout_constraintEnd_toEndOf="@+id/safetyNet_refresh"
app:layout_constraintStart_toStartOf="@+id/safetyNet_refresh"
app:layout_constraintTop_toTopOf="@+id/safetyNet_refresh" />
<TextView
android:id="@+id/safetyNet_status"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:gravity="center"
android:maxLines="1"
android:minWidth="200dp"
android:text="@string/safetyNet_check_text"
android:textStyle="bold"
app:autoSizeMaxTextSize="14sp"
app:autoSizeTextType="uniform"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/sn_status_end"
app:layout_constraintStart_toStartOf="@+id/sn_status_start"
app:layout_constraintTop_toTopOf="parent" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/sn_status_start"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_percent="0.25" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/sn_status_end"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_percent="0.75" />
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/safetyNet_expand"
gone="@{viewModel.safetyNetState == State.LOADING}"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingBottom="10dp">
<ImageView
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/cts_status_icon"
srcCompat="@{viewModel.ctsState == SafetyNetState.PASS ? R.drawable.ic_check_circle : R.drawable.ic_cancel}"
app:tint="@{viewModel.ctsState == SafetyNetState.PASS ? @color/colorCorrect : @color/colorError}"
android:layout_width="25dp"
android:layout_height="25dp"
android:layout_marginEnd="8dp"
app:layout_constraintBottom_toBottomOf="@+id/cts_status"
app:layout_constraintEnd_toStartOf="@+id/cts_status"
app:layout_constraintTop_toTopOf="@+id/cts_status" />
app:layout_constraintTop_toTopOf="@+id/cts_status"
tools:srcCompat="@drawable/ic_check_circle" />
<ImageView
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/basic_status_icon"
srcCompat="@{viewModel.basicIntegrityState == SafetyNetState.PASS ? R.drawable.ic_check_circle : R.drawable.ic_cancel}"
app:tint="@{viewModel.basicIntegrityState == SafetyNetState.PASS ? @color/colorCorrect : @color/colorError}"
android:layout_width="25dp"
android:layout_height="25dp"
android:layout_marginEnd="8dp"
@ -290,17 +294,20 @@
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/basic_status" />
app:layout_constraintTop_toTopOf="@+id/basic_status"
tools:srcCompat="@drawable/ic_check_circle" />
<TextView
android:id="@+id/cts_status"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:padding="6dp"
android:text="@{String.format(`ctsProfile: %b`, viewModel.ctsState == SafetyNetState.PASS)}"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="@+id/basic_status"
app:layout_constraintStart_toStartOf="@+id/basic_status"
app:layout_constraintTop_toTopOf="parent" />
app:layout_constraintTop_toTopOf="parent"
tools:text="ctsProfile: true" />
<TextView
android:id="@+id/basic_status"
@ -308,12 +315,14 @@
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:padding="6dp"
android:text="@{String.format(`basicIntegrity: %b`, viewModel.basicIntegrityState == SafetyNetState.PASS)}"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/basic_status_icon"
app:layout_constraintTop_toBottomOf="@+id/cts_status" />
app:layout_constraintTop_toBottomOf="@+id/cts_status"
tools:text="basicIntegrity: true" />
</androidx.constraintlayout.widget.ConstraintLayout>