feat: handle play protect (#72)
This commit is contained in:
parent
8898856766
commit
afba80fe4b
11 changed files with 206 additions and 13 deletions
|
|
@ -137,6 +137,7 @@ dependencies {
|
||||||
implementation(libs.bouncycastle)
|
implementation(libs.bouncycastle)
|
||||||
implementation(libs.binaryResources)
|
implementation(libs.binaryResources)
|
||||||
implementation(libs.coil)
|
implementation(libs.coil)
|
||||||
|
implementation(libs.microg)
|
||||||
implementation(variantOf(libs.zip) { artifactType("aar") })
|
implementation(variantOf(libs.zip) { artifactType("aar") })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ class KotlinInstallRunner(options: InstallOptions) : StepRunner() {
|
||||||
// Install
|
// Install
|
||||||
AlignmentStep(),
|
AlignmentStep(),
|
||||||
SigningStep(),
|
SigningStep(),
|
||||||
InstallStep(),
|
InstallStep(options),
|
||||||
CleanupStep(),
|
CleanupStep(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,11 +30,12 @@ abstract class StepRunner : KoinComponent {
|
||||||
/**
|
/**
|
||||||
* Get a step that has already been successfully executed.
|
* Get a step that has already been successfully executed.
|
||||||
* This is used to retrieve previously executed dependency steps from a later step.
|
* This is used to retrieve previously executed dependency steps from a later step.
|
||||||
|
* @param completed Only match steps that have finished executing.
|
||||||
*/
|
*/
|
||||||
inline fun <reified T : Step> getStep(): T {
|
inline fun <reified T : Step> getStep(completed: Boolean = true): T {
|
||||||
val step = steps.asSequence()
|
val step = steps.asSequence()
|
||||||
.filterIsInstance<T>()
|
.filterIsInstance<T>()
|
||||||
.filter { it.state.isFinished }
|
.filter { !completed || it.state.isFinished }
|
||||||
.firstOrNull()
|
.firstOrNull()
|
||||||
|
|
||||||
if (step == null) {
|
if (step == null) {
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,12 @@ import com.aliucord.manager.installer.steps.patch.CopyDependenciesStep
|
||||||
import com.aliucord.manager.installers.InstallerResult
|
import com.aliucord.manager.installers.InstallerResult
|
||||||
import com.aliucord.manager.manager.InstallerManager
|
import com.aliucord.manager.manager.InstallerManager
|
||||||
import com.aliucord.manager.manager.PreferencesManager
|
import com.aliucord.manager.manager.PreferencesManager
|
||||||
|
import com.aliucord.manager.ui.components.dialogs.PlayProtectDialog
|
||||||
|
import com.aliucord.manager.ui.screens.installopts.InstallOptions
|
||||||
import com.aliucord.manager.ui.util.InstallNotifications
|
import com.aliucord.manager.ui.util.InstallNotifications
|
||||||
|
import com.aliucord.manager.util.isPackageInstalled
|
||||||
|
import com.aliucord.manager.util.isPlayProtectEnabled
|
||||||
|
import kotlinx.coroutines.flow.*
|
||||||
import org.koin.core.component.KoinComponent
|
import org.koin.core.component.KoinComponent
|
||||||
import org.koin.core.component.inject
|
import org.koin.core.component.inject
|
||||||
|
|
||||||
|
|
@ -20,14 +25,25 @@ private const val READY_NOTIF_ID = 200001
|
||||||
/**
|
/**
|
||||||
* Install the final APK with the system's PackageManager.
|
* Install the final APK with the system's PackageManager.
|
||||||
*/
|
*/
|
||||||
class InstallStep : Step(), KoinComponent {
|
class InstallStep(private val options: InstallOptions) : Step(), KoinComponent {
|
||||||
private val context: Context by inject()
|
private val context: Context by inject()
|
||||||
private val installers: InstallerManager by inject()
|
private val installers: InstallerManager by inject()
|
||||||
private val prefs: PreferencesManager by inject()
|
private val prefs: PreferencesManager by inject()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Locks and unlocks the step's execution to start and wait until [PlayProtectDialog] dismissed.
|
||||||
|
*/
|
||||||
|
private val _gppWarningLock = MutableSharedFlow<Boolean>()
|
||||||
|
val gppWarningLock: Flow<Boolean>
|
||||||
|
get() = _gppWarningLock
|
||||||
|
|
||||||
override val group = StepGroup.Install
|
override val group = StepGroup.Install
|
||||||
override val localizedName = R.string.install_step_installing
|
override val localizedName = R.string.install_step_installing
|
||||||
|
|
||||||
|
suspend fun dismissGPPWarning() {
|
||||||
|
_gppWarningLock.emit(false)
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun execute(container: StepRunner) {
|
override suspend fun execute(container: StepRunner) {
|
||||||
val apk = container.getStep<CopyDependenciesStep>().patchedApk
|
val apk = container.getStep<CopyDependenciesStep>().patchedApk
|
||||||
|
|
||||||
|
|
@ -44,6 +60,15 @@ class InstallStep : Step(), KoinComponent {
|
||||||
// Wait until app resumed
|
// Wait until app resumed
|
||||||
ProcessLifecycleOwner.get().lifecycle.withResumed {}
|
ProcessLifecycleOwner.get().lifecycle.withResumed {}
|
||||||
|
|
||||||
|
// Show [PlayProtectDialog] and wait until it gets dismissed
|
||||||
|
if (!context.isPackageInstalled(options.packageName) && context.isPlayProtectEnabled() == true) {
|
||||||
|
_gppWarningLock.emit(true)
|
||||||
|
_gppWarningLock
|
||||||
|
.filter { show -> !show }
|
||||||
|
.take(1)
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
val result = installers.getActiveInstaller().waitInstall(
|
val result = installers.getActiveInstaller().waitInstall(
|
||||||
apks = listOf(apk),
|
apks = listOf(apk),
|
||||||
silent = !prefs.devMode,
|
silent = !prefs.devMode,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,74 @@
|
||||||
|
package com.aliucord.manager.ui.components.dialogs
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.material3.*
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.window.DialogProperties
|
||||||
|
import com.aliucord.manager.R
|
||||||
|
import com.aliucord.manager.ui.components.customColors
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun PlayProtectDialog(
|
||||||
|
onDismiss: () -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
val context = LocalContext.current
|
||||||
|
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = onDismiss,
|
||||||
|
dismissButton = {
|
||||||
|
FilledTonalButton(onClick = context::launchPlayProtect) {
|
||||||
|
Text(stringResource(R.string.play_protect_warning_open_gpp))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
confirmButton = {
|
||||||
|
FilledTonalButton(
|
||||||
|
onClick = onDismiss,
|
||||||
|
colors = ButtonDefaults.filledTonalButtonColors(
|
||||||
|
containerColor = MaterialTheme.colorScheme.primary,
|
||||||
|
contentColor = MaterialTheme.colorScheme.onPrimary,
|
||||||
|
),
|
||||||
|
) {
|
||||||
|
Text(stringResource(R.string.play_protect_warning_ok))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
icon = {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(R.drawable.ic_protect_warning),
|
||||||
|
tint = MaterialTheme.customColors.warning,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.size(36.dp),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
title = { Text(stringResource(R.string.play_protect_warning_title)) },
|
||||||
|
text = {
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.play_protect_warning_desc),
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
properties = DialogProperties(
|
||||||
|
dismissOnBackPress = false,
|
||||||
|
usePlatformDefaultWidth = false,
|
||||||
|
),
|
||||||
|
modifier = modifier
|
||||||
|
.padding(25.dp),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Context.launchPlayProtect() {
|
||||||
|
Intent("com.google.android.gms.settings.VERIFY_APPS_SETTINGS")
|
||||||
|
.setPackage("com.google.android.gms")
|
||||||
|
.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS)
|
||||||
|
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||||
|
.also(::startActivity)
|
||||||
|
}
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
@file:OptIn(ExperimentalCoroutinesApi::class)
|
||||||
|
|
||||||
package com.aliucord.manager.ui.screens.install
|
package com.aliucord.manager.ui.screens.install
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
|
|
@ -9,8 +11,7 @@ import cafe.adriel.voyager.core.model.StateScreenModel
|
||||||
import cafe.adriel.voyager.core.model.screenModelScope
|
import cafe.adriel.voyager.core.model.screenModelScope
|
||||||
import com.aliucord.manager.BuildConfig
|
import com.aliucord.manager.BuildConfig
|
||||||
import com.aliucord.manager.R
|
import com.aliucord.manager.R
|
||||||
import com.aliucord.manager.installer.steps.KotlinInstallRunner
|
import com.aliucord.manager.installer.steps.*
|
||||||
import com.aliucord.manager.installer.steps.StepGroup
|
|
||||||
import com.aliucord.manager.installer.steps.base.Step
|
import com.aliucord.manager.installer.steps.base.Step
|
||||||
import com.aliucord.manager.installer.steps.base.StepState
|
import com.aliucord.manager.installer.steps.base.StepState
|
||||||
import com.aliucord.manager.installer.steps.install.InstallStep
|
import com.aliucord.manager.installer.steps.install.InstallStep
|
||||||
|
|
@ -21,6 +22,7 @@ import com.aliucord.manager.util.*
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
import kotlinx.collections.immutable.ImmutableMap
|
import kotlinx.collections.immutable.ImmutableMap
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
|
import kotlinx.coroutines.flow.*
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
|
|
||||||
|
|
@ -29,13 +31,18 @@ class InstallModel(
|
||||||
private val paths: PathManager,
|
private val paths: PathManager,
|
||||||
private val options: InstallOptions,
|
private val options: InstallOptions,
|
||||||
) : StateScreenModel<InstallScreenState>(InstallScreenState.Pending) {
|
) : StateScreenModel<InstallScreenState>(InstallScreenState.Pending) {
|
||||||
private lateinit var startTime: Date
|
private var startTime: Date? = null
|
||||||
private var installJob: Job? = null
|
private var installJob: Job? = null
|
||||||
|
private var stepRunner: StepRunner? = null
|
||||||
|
|
||||||
private var autocloseCancelled: Boolean = false
|
private var autocloseCancelled: Boolean = false
|
||||||
|
|
||||||
var installSteps by mutableStateOf<ImmutableMap<StepGroup, ImmutableList<Step>>?>(null)
|
var installSteps by mutableStateOf<ImmutableMap<StepGroup, ImmutableList<Step>>?>(null)
|
||||||
private set
|
private set
|
||||||
|
|
||||||
|
var showGppWarning by mutableStateOf(false)
|
||||||
|
private set
|
||||||
|
|
||||||
init {
|
init {
|
||||||
restart()
|
restart()
|
||||||
}
|
}
|
||||||
|
|
@ -49,6 +56,7 @@ class InstallModel(
|
||||||
}
|
}
|
||||||
|
|
||||||
fun saveFailureLog() {
|
fun saveFailureLog() {
|
||||||
|
val startTime = startTime ?: return
|
||||||
val failureLog = (state.value as? InstallScreenState.Failed)?.failureLog
|
val failureLog = (state.value as? InstallScreenState.Failed)?.failureLog
|
||||||
?: return
|
?: return
|
||||||
|
|
||||||
|
|
@ -71,6 +79,20 @@ class InstallModel(
|
||||||
autocloseCancelled = true
|
autocloseCancelled = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hide the 'Google Play Protect is enabled on your device' warning dialog
|
||||||
|
*/
|
||||||
|
fun dismissGPPWarning() {
|
||||||
|
showGppWarning = false
|
||||||
|
|
||||||
|
// Continue executing step
|
||||||
|
screenModelScope.launch {
|
||||||
|
stepRunner
|
||||||
|
?.getStep<InstallStep>(completed = false)
|
||||||
|
?.dismissGPPWarning()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun restart() {
|
fun restart() {
|
||||||
installJob?.cancel("Manual cancellation")
|
installJob?.cancel("Manual cancellation")
|
||||||
installSteps = null
|
installSteps = null
|
||||||
|
|
@ -80,6 +102,14 @@ class InstallModel(
|
||||||
|
|
||||||
val newInstallJob = screenModelScope.launch {
|
val newInstallJob = screenModelScope.launch {
|
||||||
val runner = KotlinInstallRunner(options)
|
val runner = KotlinInstallRunner(options)
|
||||||
|
.also { stepRunner = it }
|
||||||
|
|
||||||
|
// Bind InstallStep's GPP Warning state to this
|
||||||
|
runner.getStep<InstallStep>(completed = false)
|
||||||
|
.gppWarningLock
|
||||||
|
.take(1) // Take only the initially trigger value
|
||||||
|
.onEach { showGppWarning = true }
|
||||||
|
.launchIn(this@launch)
|
||||||
|
|
||||||
installSteps = runner.steps.groupBy { it.group }
|
installSteps = runner.steps.groupBy { it.group }
|
||||||
.mapValues { it.value.toUnsafeImmutable() }
|
.mapValues { it.value.toUnsafeImmutable() }
|
||||||
|
|
@ -137,17 +167,24 @@ class InstallModel(
|
||||||
installJob = newInstallJob
|
installJob = newInstallJob
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getFailureInfo(stacktrace: Throwable): String {
|
private suspend fun getFailureInfo(stacktrace: Throwable): String {
|
||||||
val gitChanges = if (BuildConfig.GIT_LOCAL_CHANGES || BuildConfig.GIT_LOCAL_COMMITS) "(Changes present)" else ""
|
val gitChanges = if (BuildConfig.GIT_LOCAL_CHANGES || BuildConfig.GIT_LOCAL_COMMITS) "(Changes present)" else ""
|
||||||
val soc = if (Build.VERSION.SDK_INT >= 31) (Build.SOC_MANUFACTURER + ' ' + Build.SOC_MODEL) else "Unknown"
|
val soc = if (Build.VERSION.SDK_INT >= 31) (Build.SOC_MANUFACTURER + ' ' + Build.SOC_MODEL) else "Unavailable"
|
||||||
|
val playProtect = when (application.isPlayProtectEnabled()) {
|
||||||
|
null -> "Unavailable"
|
||||||
|
true -> "Enabled"
|
||||||
|
false -> "Disabled"
|
||||||
|
}
|
||||||
|
|
||||||
val header = """
|
val header = """
|
||||||
Aliucord Manager v${BuildConfig.VERSION_NAME}
|
Aliucord Manager v${BuildConfig.VERSION_NAME}
|
||||||
Built from commit ${BuildConfig.GIT_COMMIT} on ${BuildConfig.GIT_BRANCH} $gitChanges
|
Built from commit ${BuildConfig.GIT_COMMIT} on ${BuildConfig.GIT_BRANCH} $gitChanges
|
||||||
|
|
||||||
Running Android ${Build.VERSION.RELEASE}, API level ${Build.VERSION.SDK_INT}
|
Android API: ${Build.VERSION.SDK_INT}
|
||||||
|
ROM: Android ${Build.VERSION.RELEASE} (Patch ${Build.VERSION.SECURITY_PATCH})
|
||||||
Supported ABIs: ${Build.SUPPORTED_ABIS.joinToString()}
|
Supported ABIs: ${Build.SUPPORTED_ABIS.joinToString()}
|
||||||
Device: ${Build.MANUFACTURER} - ${Build.MODEL} (${Build.DEVICE})
|
Device: ${Build.MANUFACTURER} ${Build.MODEL} (${Build.DEVICE})
|
||||||
|
Play Protect: $playProtect
|
||||||
SOC: $soc
|
SOC: $soc
|
||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ import com.aliucord.manager.installer.steps.StepGroup
|
||||||
import com.aliucord.manager.ui.components.Wakelock
|
import com.aliucord.manager.ui.components.Wakelock
|
||||||
import com.aliucord.manager.ui.components.back
|
import com.aliucord.manager.ui.components.back
|
||||||
import com.aliucord.manager.ui.components.dialogs.InstallerAbortDialog
|
import com.aliucord.manager.ui.components.dialogs.InstallerAbortDialog
|
||||||
|
import com.aliucord.manager.ui.components.dialogs.PlayProtectDialog
|
||||||
import com.aliucord.manager.ui.screens.install.components.*
|
import com.aliucord.manager.ui.screens.install.components.*
|
||||||
import com.aliucord.manager.ui.screens.installopts.InstallOptions
|
import com.aliucord.manager.ui.screens.installopts.InstallOptions
|
||||||
import kotlinx.parcelize.IgnoredOnParcel
|
import kotlinx.parcelize.IgnoredOnParcel
|
||||||
|
|
@ -44,7 +45,7 @@ class InstallScreen(private val data: InstallOptions) : Screen, Parcelable {
|
||||||
override fun Content() {
|
override fun Content() {
|
||||||
val navigator = LocalNavigator.currentOrThrow
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
val model = getScreenModel<InstallModel> { parametersOf(data) }
|
val model = getScreenModel<InstallModel> { parametersOf(data) }
|
||||||
val state = model.state.collectAsState()
|
val state = model.state.collectAsState() // TODO: use `by`
|
||||||
|
|
||||||
LaunchedEffect(state.value) {
|
LaunchedEffect(state.value) {
|
||||||
if (state.value is InstallScreenState.CloseScreen)
|
if (state.value is InstallScreenState.CloseScreen)
|
||||||
|
|
@ -80,6 +81,10 @@ class InstallScreen(private val data: InstallOptions) : Screen, Parcelable {
|
||||||
BackHandler(onBack = onTryExit)
|
BackHandler(onBack = onTryExit)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (model.showGppWarning) {
|
||||||
|
PlayProtectDialog(onDismiss = model::dismissGPPWarning)
|
||||||
|
}
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = { InstallAppBar(onTryExit) },
|
topBar = { InstallAppBar(onTryExit) },
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ package com.aliucord.manager.util
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.content.*
|
import android.content.*
|
||||||
|
import android.content.pm.PackageManager
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.*
|
import android.os.*
|
||||||
import android.provider.Settings
|
import android.provider.Settings
|
||||||
|
|
@ -13,8 +14,11 @@ import androidx.annotation.AnyRes
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import com.aliucord.manager.BuildConfig
|
import com.aliucord.manager.BuildConfig
|
||||||
import com.aliucord.manager.R
|
import com.aliucord.manager.R
|
||||||
|
import com.google.android.gms.safetynet.SafetyNet
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
|
import kotlin.coroutines.resume
|
||||||
|
import kotlin.coroutines.suspendCoroutine
|
||||||
|
|
||||||
fun Context.copyToClipboard(text: String) {
|
fun Context.copyToClipboard(text: String) {
|
||||||
val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
||||||
|
|
@ -52,6 +56,15 @@ fun Context.getPackageVersion(pkg: String): Pair<String, Int> {
|
||||||
.let { it.versionName to it.versionCode }
|
.let { it.versionName to it.versionCode }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun Context.isPackageInstalled(packageName: String): Boolean {
|
||||||
|
return try {
|
||||||
|
packageManager.getPackageInfo(packageName, 0)
|
||||||
|
true
|
||||||
|
} catch (_: PackageManager.NameNotFoundException) {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun Context.findActivity(): Activity? {
|
fun Context.findActivity(): Activity? {
|
||||||
var context = this
|
var context = this
|
||||||
while (context is ContextWrapper) {
|
while (context is ContextWrapper) {
|
||||||
|
|
@ -90,7 +103,7 @@ fun Context.requestNoBatteryOptimizations() {
|
||||||
startActivity(intent)
|
startActivity(intent)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* Get the raw bytes for a resource.
|
* Get the raw bytes for a resource.
|
||||||
* @param id The resource identifier
|
* @param id The resource identifier
|
||||||
* @return The resource's raw bytes as stored inside the APK
|
* @return The resource's raw bytes as stored inside the APK
|
||||||
|
|
@ -110,3 +123,24 @@ fun Context.getResBytes(@AnyRes id: Int): ByteArray {
|
||||||
?.use(InputStream::readBytes)
|
?.use(InputStream::readBytes)
|
||||||
?: error("Failed to get resource file $resPath from APK")
|
?: error("Failed to get resource file $resPath from APK")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the Play Protect/Verify Apps feature is enabled on this device.
|
||||||
|
* @return `null` if failed to obtain, otherwise whether it's enabled.
|
||||||
|
*/
|
||||||
|
suspend fun Context.isPlayProtectEnabled(): Boolean? {
|
||||||
|
return suspendCoroutine { continuation ->
|
||||||
|
SafetyNet.getClient(this)
|
||||||
|
.isVerifyAppsEnabled
|
||||||
|
.addOnCompleteListener { task ->
|
||||||
|
if (task.isSuccessful) {
|
||||||
|
val enabled = task.result.isVerifyAppsEnabled
|
||||||
|
Log.d(BuildConfig.TAG, "Play Protect enabled: $enabled")
|
||||||
|
continuation.resume(enabled)
|
||||||
|
} else {
|
||||||
|
Log.d(BuildConfig.TAG, "Failed to check Play Protect status", task.exception)
|
||||||
|
continuation.resume(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
9
app/src/main/res/drawable/ic_protect_warning.xml
Normal file
9
app/src/main/res/drawable/ic_protect_warning.xml
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="960"
|
||||||
|
android:viewportHeight="960">
|
||||||
|
<path
|
||||||
|
android:fillColor="#FFFFFFFF"
|
||||||
|
android:pathData="M480,640q17,0 28.5,-11.5T520,600q0,-17 -11.5,-28.5T480,560q-17,0 -28.5,11.5T440,600q0,17 11.5,28.5T480,640ZM480,480q17,0 28.5,-11.5T520,440v-120q0,-17 -11.5,-28.5T480,280q-17,0 -28.5,11.5T440,320v120q0,17 11.5,28.5T480,480ZM480,876q-7,0 -13,-1t-12,-3q-135,-45 -215,-166.5T160,444v-189q0,-25 14.5,-45t37.5,-29l240,-90q14,-5 28,-5t28,5l240,90q23,9 37.5,29t14.5,45v189q0,140 -80,261.5T505,872q-6,2 -12,3t-13,1ZM480,796q104,-33 172,-132t68,-220v-189l-240,-90 -240,90v189q0,121 68,220t172,132ZM480,480Z" />
|
||||||
|
</vector>
|
||||||
|
|
@ -154,4 +154,9 @@
|
||||||
<string name="notif_install_ready_desc">Click to install…</string>
|
<string name="notif_install_ready_desc">Click to install…</string>
|
||||||
<string name="notif_install_fail_title">Aliucord failed to install</string>
|
<string name="notif_install_fail_title">Aliucord failed to install</string>
|
||||||
<string name="notif_install_fail_desc">Click to view more info…</string>
|
<string name="notif_install_fail_desc">Click to view more info…</string>
|
||||||
|
|
||||||
|
<string name="play_protect_warning_title">Play Protect</string>
|
||||||
|
<string name="play_protect_warning_desc">Google Play Protect appears to be enabled on your device. It may attempt to interfere with a new installation due to the usage of a unique signing key. You can disable it in Play Protect\'s settings.\n\nIf it does show a warning dialog, press\n\"More Details\" -> \"Install anyway\"</string>
|
||||||
|
<string name="play_protect_warning_ok">Continue</string>
|
||||||
|
<string name="play_protect_warning_open_gpp">Open Play Protect</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ kotlin = "1.9.22"
|
||||||
kotlinx-immutable = "0.3.7"
|
kotlinx-immutable = "0.3.7"
|
||||||
kotlinx-serialization = "1.5.0"
|
kotlinx-serialization = "1.5.0"
|
||||||
ktor = "2.3.7"
|
ktor = "2.3.7"
|
||||||
|
microg = "0.3.2.240913"
|
||||||
voyager = "1.0.0"
|
voyager = "1.0.0"
|
||||||
zip = "2.1.1"
|
zip = "2.1.1"
|
||||||
|
|
||||||
|
|
@ -72,6 +73,7 @@ axml = { module = "com.aliucord:axml", version.ref = "axml" }
|
||||||
binaryResources = { module = "com.aliucord:binary-resources", version.ref = "binary-resources" }
|
binaryResources = { module = "com.aliucord:binary-resources", version.ref = "binary-resources" }
|
||||||
bouncycastle = { module = "org.bouncycastle:bcpkix-jdk15on", version.ref = "bouncycastle" }
|
bouncycastle = { module = "org.bouncycastle:bcpkix-jdk15on", version.ref = "bouncycastle" }
|
||||||
coil = { module = "io.coil-kt:coil-compose", version.ref = "coil" }
|
coil = { module = "io.coil-kt:coil-compose", version.ref = "coil" }
|
||||||
|
microg = { module = "org.microg.gms:play-services-safetynet", version.ref = "microg" }
|
||||||
zip = { module = "io.github.diamondminer88:zip-android", version.ref = "zip" } # Only use the "aar" artifact
|
zip = { module = "io.github.diamondminer88:zip-android", version.ref = "zip" } # Only use the "aar" artifact
|
||||||
|
|
||||||
[bundles]
|
[bundles]
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue