From 857afb60cd406341ef29529b411822258ef0b348 Mon Sep 17 00:00:00 2001 From: frankpreel Date: Sat, 19 Apr 2025 21:09:05 +0200 Subject: [PATCH 1/4] Behaviour if app installation according to content rating is selected https://gitlab.e.foundation/e/os/backlog/-/issues/3145 --- .../ParentalControlRepository.kt | 7 ++- .../apps/domain/ValidateAppAgeLimitUseCase.kt | 12 +++++ .../workmanager/AppInstallProcessor.kt | 24 +++++++-- .../java/foundation/e/apps/ui/MainActivity.kt | 49 +++++++++++++++---- .../subFrags/ApplicationDialogFragment.kt | 4 ++ .../utils/ParentalControlAuthenticator.kt | 41 ++++++++++++++++ .../e/apps/utils/eventBus/AppEvent.kt | 6 ++- 7 files changed, 126 insertions(+), 17 deletions(-) create mode 100644 app/src/main/java/foundation/e/apps/utils/ParentalControlAuthenticator.kt diff --git a/app/src/main/java/foundation/e/apps/data/parentalcontrol/ParentalControlRepository.kt b/app/src/main/java/foundation/e/apps/data/parentalcontrol/ParentalControlRepository.kt index d5279252a..4af7a9184 100644 --- a/app/src/main/java/foundation/e/apps/data/parentalcontrol/ParentalControlRepository.kt +++ b/app/src/main/java/foundation/e/apps/data/parentalcontrol/ParentalControlRepository.kt @@ -23,6 +23,7 @@ import android.net.Uri import dagger.hilt.android.qualifiers.ApplicationContext import javax.inject.Inject import javax.inject.Singleton +import androidx.core.net.toUri @Singleton class ParentalControlRepository @Inject constructor( @@ -32,16 +33,18 @@ class ParentalControlRepository @Inject constructor( companion object { private const val URI_PARENTAL_CONTROL_PROVIDER = "content://foundation.e.parentalcontrol.provider/age" + + const val KEY_PARENTAL_GUIDANCE = "parental guidance" } fun getSelectedAgeGroup(): Age { - val uri = Uri.parse(URI_PARENTAL_CONTROL_PROVIDER) + val uri = URI_PARENTAL_CONTROL_PROVIDER.toUri() val cursor = context.contentResolver.query(uri, null, null, null, null) cursor?.use { if (it.moveToFirst()) { val ageOrdinal = it.getInt(it.getColumnIndexOrThrow("age")) - return Age.values()[ageOrdinal] + return Age.entries[ageOrdinal] } } diff --git a/app/src/main/java/foundation/e/apps/domain/ValidateAppAgeLimitUseCase.kt b/app/src/main/java/foundation/e/apps/domain/ValidateAppAgeLimitUseCase.kt index 00b9f1776..7d1fb8522 100644 --- a/app/src/main/java/foundation/e/apps/domain/ValidateAppAgeLimitUseCase.kt +++ b/app/src/main/java/foundation/e/apps/domain/ValidateAppAgeLimitUseCase.kt @@ -28,6 +28,7 @@ import foundation.e.apps.data.install.models.AppInstall import foundation.e.apps.data.parentalcontrol.Age import foundation.e.apps.data.parentalcontrol.ContentRatingDao import foundation.e.apps.data.parentalcontrol.ParentalControlRepository +import foundation.e.apps.data.parentalcontrol.ParentalControlRepository.Companion.KEY_PARENTAL_GUIDANCE import foundation.e.apps.data.parentalcontrol.fdroid.FDroidAntiFeatureRepository import foundation.e.apps.data.parentalcontrol.googleplay.GPlayContentRatingGroup import foundation.e.apps.data.parentalcontrol.googleplay.GPlayContentRatingRepository @@ -75,6 +76,11 @@ class ValidateAppAgeLimitUseCase @Inject constructor( ) hasNoContentRatingOnGPlay(app) -> ResultSupreme.Error() + + isParentalGuidance(app) -> ResultSupreme.Success( + data = ContentRatingValidity(false) + ) + else -> validateAgeLimit(ageGroup, app) } } @@ -170,4 +176,10 @@ class ValidateAppAgeLimitUseCase @Inject constructor( return app.contentRating.title.isNotEmpty() && app.contentRating.id.isNotEmpty() } + + private suspend fun isParentalGuidance(app: AppInstall): Boolean { + val fetchedContentRating = + gPlayContentRatingRepository.getEnglishContentRating(app.packageName) + return fetchedContentRating?.id == KEY_PARENTAL_GUIDANCE + } } diff --git a/app/src/main/java/foundation/e/apps/install/workmanager/AppInstallProcessor.kt b/app/src/main/java/foundation/e/apps/install/workmanager/AppInstallProcessor.kt index 58cc4ca81..fa41b061c 100644 --- a/app/src/main/java/foundation/e/apps/install/workmanager/AppInstallProcessor.kt +++ b/app/src/main/java/foundation/e/apps/install/workmanager/AppInstallProcessor.kt @@ -36,15 +36,18 @@ import foundation.e.apps.data.login.AuthObject import foundation.e.apps.data.playstore.utils.GplayHttpRequestException import foundation.e.apps.data.preference.AppLoungeDataStore import foundation.e.apps.domain.ValidateAppAgeLimitUseCase +import foundation.e.apps.domain.model.ContentRatingValidity import foundation.e.apps.install.AppInstallComponents import foundation.e.apps.install.download.DownloadManagerUtils import foundation.e.apps.install.notification.StorageNotificationManager import foundation.e.apps.install.updates.UpdatesNotifier +import foundation.e.apps.utils.ParentalControlAuthenticator import foundation.e.apps.utils.StorageComputer import foundation.e.apps.utils.eventBus.AppEvent import foundation.e.apps.utils.eventBus.EventBus import foundation.e.apps.utils.getFormattedString import foundation.e.apps.utils.isNetworkAvailable +import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.flow.transformWhile import timber.log.Timber @@ -140,13 +143,18 @@ class AppInstallProcessor @Inject constructor( if (ageLimitValidationResult.data?.isValid != true) { if (ageLimitValidationResult.isSuccess()) { Timber.i("Content rating is not allowed for: ${appInstall.name}") - EventBus.invokeEvent(AppEvent.AgeLimitRestrictionEvent(appInstall.name)) + awaitInvokeAgeLimitEvent(appInstall.name) + if (ParentalControlAuthenticator.awaitAuthentication()) { + ageLimitValidationResult.setData(ContentRatingValidity(true)) + } } else { EventBus.invokeEvent(AppEvent.ErrorMessageDialogEvent(R.string.data_load_error_desc)) } - appInstallComponents.appManagerWrapper.cancelDownload(appInstall) - return + if (ageLimitValidationResult.data?.isValid != true) { + appInstallComponents.appManagerWrapper.cancelDownload(appInstall) + return + } } if (!context.isNetworkAvailable()) { @@ -167,13 +175,19 @@ class AppInstallProcessor @Inject constructor( InstallWorkManager.enqueueWork(appInstall, isAnUpdate) } catch (e: Exception) { Timber.e( - "Enqueuing App install work is failed for ${appInstall.packageName} exception: ${e.localizedMessage}", - e + e, + "Enqueuing App install work is failed for ${appInstall.packageName} exception: ${e.localizedMessage}" ) appInstallComponents.appManagerWrapper.installationIssue(appInstall) } } + suspend fun awaitInvokeAgeLimitEvent(type: String) { + val deferred = CompletableDeferred() + EventBus.invokeEvent(AppEvent.AgeLimitRestrictionEvent(type, deferred)) + deferred.await() // await closing dialog box + } + // returns TRUE if updating urls is successful, otherwise false. private suspend fun updateDownloadUrls(appInstall: AppInstall): Boolean { try { diff --git a/app/src/main/java/foundation/e/apps/ui/MainActivity.kt b/app/src/main/java/foundation/e/apps/ui/MainActivity.kt index 8d914dd59..f3a613a41 100644 --- a/app/src/main/java/foundation/e/apps/ui/MainActivity.kt +++ b/app/src/main/java/foundation/e/apps/ui/MainActivity.kt @@ -26,6 +26,7 @@ import android.view.View import android.widget.Toast import android.window.OnBackInvokedDispatcher.PRIORITY_DEFAULT import androidx.activity.OnBackPressedCallback +import androidx.activity.result.contract.ActivityResultContracts import androidx.appcompat.app.AppCompatActivity import androidx.lifecycle.Lifecycle import androidx.lifecycle.ViewModelProvider @@ -58,6 +59,7 @@ import foundation.e.apps.ui.application.subFrags.ApplicationDialogFragment import foundation.e.apps.ui.purchase.AppPurchaseFragmentDirections import foundation.e.apps.ui.settings.SettingsFragment import foundation.e.apps.ui.setup.signin.SignInViewModel +import foundation.e.apps.utils.ParentalControlAuthenticator import foundation.e.apps.utils.SystemInfoProvider import foundation.e.apps.utils.eventBus.AppEvent import foundation.e.apps.utils.eventBus.EventBus @@ -65,8 +67,12 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.filterIsInstance import kotlinx.coroutines.launch +import kotlinx.coroutines.suspendCancellableCoroutine import timber.log.Timber +import kotlin.coroutines.cancellation.CancellationException +import kotlin.coroutines.resume @AndroidEntryPoint class MainActivity : AppCompatActivity() { @@ -80,9 +86,21 @@ class MainActivity : AppCompatActivity() { private const val SESSION_DIALOG_TAG = "session_dialog" } + private val parentalControlAuthenticatorLauncher = registerForActivityResult( + ActivityResultContracts.StartActivityForResult()) { result -> + ParentalControlAuthenticator.setResult(false) + if (result.resultCode == RESULT_OK) { + val authenticationResult = result.data?.getBooleanExtra( + ParentalControlAuthenticator.KEY_PACO_RESULT, false) == true + ParentalControlAuthenticator.setResult(authenticationResult) + } + } + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + ParentalControlAuthenticator.initLauncher(parentalControlAuthenticatorLauncher) + // Add an OnBackPressedCallback to handle the back press onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) { override fun handleOnBackPressed() { @@ -244,15 +262,28 @@ class MainActivity : AppCompatActivity() { } private suspend fun observeAgeLimitRestrictionEvent() { - EventBus.events.filter { - it is AppEvent.AgeLimitRestrictionEvent - }.collectLatest { - ApplicationDialogFragment( - getString(R.string.restricted_app, it.data as String), - getString(R.string.age_rate_limit_message, it.data as String), - positiveButtonText = getString(R.string.ok), - ).show(supportFragmentManager, TAG) - } + EventBus.events.filterIsInstance() + .collectLatest { event -> + suspendCancellableCoroutine { continuation -> + val dialog = ApplicationDialogFragment( + getString(R.string.restricted_app, event.data as String), + getString(R.string.age_rate_limit_message, event.data), + positiveButtonText = getString(R.string.ok), + ) + + dialog.setOnDialogClosedListener { + event.onClose?.complete(Unit) + continuation.resume(Unit) + } + + dialog.show(supportFragmentManager, TAG) + + continuation.invokeOnCancellation { + dialog.dismissAllowingStateLoss() + event.onClose?.completeExceptionally(CancellationException("Popup dismissed")) + } + } + } } private fun observePurchaseDeclined() { diff --git a/app/src/main/java/foundation/e/apps/ui/application/subFrags/ApplicationDialogFragment.kt b/app/src/main/java/foundation/e/apps/ui/application/subFrags/ApplicationDialogFragment.kt index 597c437e5..92b0b4a38 100644 --- a/app/src/main/java/foundation/e/apps/ui/application/subFrags/ApplicationDialogFragment.kt +++ b/app/src/main/java/foundation/e/apps/ui/application/subFrags/ApplicationDialogFragment.kt @@ -73,6 +73,10 @@ class ApplicationDialogFragment() : DialogFragment() { isCancelable = cancelable } + fun setOnDialogClosedListener(listener: () -> Unit) { + onDismissListener = listener + } + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { val positiveButtonText = positiveButtonText?.ifEmpty { getString(R.string.ok) } diff --git a/app/src/main/java/foundation/e/apps/utils/ParentalControlAuthenticator.kt b/app/src/main/java/foundation/e/apps/utils/ParentalControlAuthenticator.kt new file mode 100644 index 000000000..eddb29184 --- /dev/null +++ b/app/src/main/java/foundation/e/apps/utils/ParentalControlAuthenticator.kt @@ -0,0 +1,41 @@ +package foundation.e.apps.utils + +import android.content.Intent +import androidx.activity.result.ActivityResultLauncher +import kotlinx.coroutines.CompletableDeferred +import timber.log.Timber + +/* +This class is used to to deal with PaCO, and get accurate password result +This class is a singleton + */ +object ParentalControlAuthenticator { + + private const val KEY_PACO_AUTHENTICATION = "foundation.e.parentalcontrol.START_AUTHENTICATE" //PaCo intent + const val KEY_PACO_RESULT = "authentication_result" //PaCo Key + + private var launcher: ActivityResultLauncher? = null + private var authResultDeferred: CompletableDeferred? = null + + fun initLauncher(launcher: ActivityResultLauncher) { + this.launcher = launcher + } + + suspend fun awaitAuthentication(): Boolean { + authResultDeferred = CompletableDeferred() + launcher?.let { + val intent = Intent(KEY_PACO_AUTHENTICATION) + it.launch(intent) + } ?: run { + Timber.e("Err: the launcher is NOT started") + authResultDeferred?.complete(false) + } + + // Await the Authenticate from PaCo + return authResultDeferred?.await() == true + } + + fun setResult(result: Boolean) { + authResultDeferred?.complete(result) + } +} \ No newline at end of file diff --git a/app/src/main/java/foundation/e/apps/utils/eventBus/AppEvent.kt b/app/src/main/java/foundation/e/apps/utils/eventBus/AppEvent.kt index 9c3cd7e5f..701312475 100644 --- a/app/src/main/java/foundation/e/apps/utils/eventBus/AppEvent.kt +++ b/app/src/main/java/foundation/e/apps/utils/eventBus/AppEvent.kt @@ -24,6 +24,7 @@ import foundation.e.apps.data.ResultSupreme import foundation.e.apps.data.enums.ResultStatus import foundation.e.apps.data.enums.User import foundation.e.apps.data.install.models.AppInstall +import kotlinx.coroutines.CompletableDeferred sealed class AppEvent(val data: Any) { class SignatureMissMatchError(packageName: String) : AppEvent(packageName) @@ -35,6 +36,9 @@ sealed class AppEvent(val data: Any) { class AppPurchaseEvent(appInstall: AppInstall) : AppEvent(appInstall) class NoInternetEvent(isInternetAvailable: Boolean) : AppEvent(isInternetAvailable) class TooManyRequests : AppEvent(Unit) - class AgeLimitRestrictionEvent(type: String) : AppEvent(type) + class AgeLimitRestrictionEvent( + type: String, + val onClose: CompletableDeferred? = null + ) : AppEvent(type) class SuccessfulLogin(user: User): AppEvent(user) } -- GitLab From ff3ab10f12957a219395fc4ca2bbad7c9792c273 Mon Sep 17 00:00:00 2001 From: frankpreel Date: Tue, 22 Apr 2025 14:46:17 +0200 Subject: [PATCH 2/4] Screen time setting when Paco is already enabled https://gitlab.e.foundation/e/os/backlog/-/issues/3139 --- .../ParentalControlRepository.kt | 24 +++++++++- .../apps/domain/ValidateAppAgeLimitUseCase.kt | 44 ++++++++++++------- .../domain/model/ContentRatingValidity.kt | 3 +- .../workmanager/AppInstallProcessor.kt | 8 ++-- .../java/foundation/e/apps/ui/MainActivity.kt | 3 +- 5 files changed, 58 insertions(+), 24 deletions(-) diff --git a/app/src/main/java/foundation/e/apps/data/parentalcontrol/ParentalControlRepository.kt b/app/src/main/java/foundation/e/apps/data/parentalcontrol/ParentalControlRepository.kt index 4af7a9184..b615e5606 100644 --- a/app/src/main/java/foundation/e/apps/data/parentalcontrol/ParentalControlRepository.kt +++ b/app/src/main/java/foundation/e/apps/data/parentalcontrol/ParentalControlRepository.kt @@ -19,7 +19,6 @@ package foundation.e.apps.data.parentalcontrol import android.content.Context -import android.net.Uri import dagger.hilt.android.qualifiers.ApplicationContext import javax.inject.Inject import javax.inject.Singleton @@ -33,7 +32,8 @@ class ParentalControlRepository @Inject constructor( companion object { private const val URI_PARENTAL_CONTROL_PROVIDER = "content://foundation.e.parentalcontrol.provider/age" - + private const val URI_PARENTAL_CONTROL_INSTALL_TYPE_APP_MANAGEMENT = + "content://foundation.e.parentalcontrol.provider/typeappmanagement" const val KEY_PARENTAL_GUIDANCE = "parental guidance" } @@ -50,6 +50,20 @@ class ParentalControlRepository @Inject constructor( return Age.PARENTAL_CONTROL_DISABLED } + + fun getSelectedTypeAppManagement(): TypeAppManagement { + val uri = URI_PARENTAL_CONTROL_INSTALL_TYPE_APP_MANAGEMENT.toUri() + val cursor = context.contentResolver.query(uri, null, null, null, null) + + cursor?.use { + if (it.moveToFirst()) { + val typeAppManagement = it.getInt(it.getColumnIndexOrThrow("typeappmanagement")) + return TypeAppManagement.entries[typeAppManagement] + } + } + + return TypeAppManagement.PARENTAL_CONTROL_DISABLED + } } enum class Age { @@ -60,3 +74,9 @@ enum class Age { SEVENTEEN, PARENTAL_CONTROL_DISABLED } + +enum class TypeAppManagement { + CONTENT_RATING, //Legacy mode + SECURITY_MODE, //Allow using parental code (pin or pass) + PARENTAL_CONTROL_DISABLED +} diff --git a/app/src/main/java/foundation/e/apps/domain/ValidateAppAgeLimitUseCase.kt b/app/src/main/java/foundation/e/apps/domain/ValidateAppAgeLimitUseCase.kt index 7d1fb8522..929eb613a 100644 --- a/app/src/main/java/foundation/e/apps/domain/ValidateAppAgeLimitUseCase.kt +++ b/app/src/main/java/foundation/e/apps/domain/ValidateAppAgeLimitUseCase.kt @@ -29,6 +29,7 @@ import foundation.e.apps.data.parentalcontrol.Age import foundation.e.apps.data.parentalcontrol.ContentRatingDao import foundation.e.apps.data.parentalcontrol.ParentalControlRepository import foundation.e.apps.data.parentalcontrol.ParentalControlRepository.Companion.KEY_PARENTAL_GUIDANCE +import foundation.e.apps.data.parentalcontrol.TypeAppManagement import foundation.e.apps.data.parentalcontrol.fdroid.FDroidAntiFeatureRepository import foundation.e.apps.data.parentalcontrol.googleplay.GPlayContentRatingGroup import foundation.e.apps.data.parentalcontrol.googleplay.GPlayContentRatingRepository @@ -52,6 +53,9 @@ class ValidateAppAgeLimitUseCase @Inject constructor( private val ageGroup: Age get() = parentalControlRepository.getSelectedAgeGroup() + private val typeAppManagement: TypeAppManagement + get() = parentalControlRepository.getSelectedTypeAppManagement() + suspend operator fun invoke(app: AppInstall): ResultSupreme { return when { @@ -78,7 +82,7 @@ class ValidateAppAgeLimitUseCase @Inject constructor( hasNoContentRatingOnGPlay(app) -> ResultSupreme.Error() isParentalGuidance(app) -> ResultSupreme.Success( - data = ContentRatingValidity(false) + data = ContentRatingValidity(false, askToSudo = true) ) else -> validateAgeLimit(ageGroup, app) @@ -119,22 +123,25 @@ class ValidateAppAgeLimitUseCase @Inject constructor( ageGroup: Age, app: AppInstall ): ResultSupreme { - val allowedContentRating = - gPlayContentRatingRepository.contentRatingGroups.find { it.id == ageGroup.toString() } + if (typeAppManagement == TypeAppManagement.CONTENT_RATING) { + val allowedContentRating = + gPlayContentRatingRepository.contentRatingGroups.find { it.id == ageGroup.toString() } - Timber.d( - "${app.packageName} - Content rating: ${app.contentRating.id} \n" + - "Selected age group: $ageGroup \n" + - "Allowed content rating: $allowedContentRating" - ) + Timber.d( + "${app.packageName} - Content rating: ${app.contentRating.id} \n" + + "Selected age group: $ageGroup \nAllowed content rating: $allowedContentRating") - return ResultSupreme.Success( - ContentRatingValidity( - isValidAppAgeRating( - app, - allowedContentRating - ), app.contentRating + val isValid = isValidAppAgeRating(app, allowedContentRating) + return ResultSupreme.Success( + ContentRatingValidity(isValid, app.contentRating, askToSudo = false) ) + } else if (typeAppManagement == TypeAppManagement.SECURITY_MODE) { + return ResultSupreme.Success( + data = ContentRatingValidity(false, askToSudo = true) + ) + } + return ResultSupreme.Success( + data = ContentRatingValidity(true) ) } @@ -178,8 +185,11 @@ class ValidateAppAgeLimitUseCase @Inject constructor( } private suspend fun isParentalGuidance(app: AppInstall): Boolean { - val fetchedContentRating = - gPlayContentRatingRepository.getEnglishContentRating(app.packageName) - return fetchedContentRating?.id == KEY_PARENTAL_GUIDANCE + if (typeAppManagement == TypeAppManagement.CONTENT_RATING) { + val fetchedContentRating = + gPlayContentRatingRepository.getEnglishContentRating(app.packageName) + return fetchedContentRating?.id == KEY_PARENTAL_GUIDANCE + } + return false } } diff --git a/app/src/main/java/foundation/e/apps/domain/model/ContentRatingValidity.kt b/app/src/main/java/foundation/e/apps/domain/model/ContentRatingValidity.kt index 57ff5b5c5..9f364d62e 100644 --- a/app/src/main/java/foundation/e/apps/domain/model/ContentRatingValidity.kt +++ b/app/src/main/java/foundation/e/apps/domain/model/ContentRatingValidity.kt @@ -21,4 +21,5 @@ package foundation.e.apps.domain.model import com.aurora.gplayapi.data.models.ContentRating -data class ContentRatingValidity(val isValid: Boolean, val contentRating: ContentRating? = null) \ No newline at end of file +data class ContentRatingValidity(val isValid: Boolean, val contentRating: ContentRating? = null, + val askToSudo: Boolean = false) // Ask to sudo (parent) legacy = false \ No newline at end of file diff --git a/app/src/main/java/foundation/e/apps/install/workmanager/AppInstallProcessor.kt b/app/src/main/java/foundation/e/apps/install/workmanager/AppInstallProcessor.kt index fa41b061c..b984df181 100644 --- a/app/src/main/java/foundation/e/apps/install/workmanager/AppInstallProcessor.kt +++ b/app/src/main/java/foundation/e/apps/install/workmanager/AppInstallProcessor.kt @@ -142,10 +142,12 @@ class AppInstallProcessor @Inject constructor( val ageLimitValidationResult = validateAppAgeLimitUseCase.invoke(appInstall) if (ageLimitValidationResult.data?.isValid != true) { if (ageLimitValidationResult.isSuccess()) { - Timber.i("Content rating is not allowed for: ${appInstall.name}") awaitInvokeAgeLimitEvent(appInstall.name) - if (ParentalControlAuthenticator.awaitAuthentication()) { - ageLimitValidationResult.setData(ContentRatingValidity(true)) + if (ageLimitValidationResult.data?.askToSudo == true){ + val isAuthenticated = ParentalControlAuthenticator.awaitAuthentication() + if (isAuthenticated) { + ageLimitValidationResult.setData(ContentRatingValidity(true)) + } } } else { EventBus.invokeEvent(AppEvent.ErrorMessageDialogEvent(R.string.data_load_error_desc)) diff --git a/app/src/main/java/foundation/e/apps/ui/MainActivity.kt b/app/src/main/java/foundation/e/apps/ui/MainActivity.kt index f3a613a41..6e1e35964 100644 --- a/app/src/main/java/foundation/e/apps/ui/MainActivity.kt +++ b/app/src/main/java/foundation/e/apps/ui/MainActivity.kt @@ -88,11 +88,12 @@ class MainActivity : AppCompatActivity() { private val parentalControlAuthenticatorLauncher = registerForActivityResult( ActivityResultContracts.StartActivityForResult()) { result -> - ParentalControlAuthenticator.setResult(false) if (result.resultCode == RESULT_OK) { val authenticationResult = result.data?.getBooleanExtra( ParentalControlAuthenticator.KEY_PACO_RESULT, false) == true ParentalControlAuthenticator.setResult(authenticationResult) + } else { + ParentalControlAuthenticator.setResult(false) } } -- GitLab From cd2ae606628a41cd692d5f3b20f0b91b59c5c2ca Mon Sep 17 00:00:00 2001 From: frankpreel Date: Tue, 22 Apr 2025 16:46:18 +0200 Subject: [PATCH 3/4] Linter --- app/detekt-baseline.xml | 4 +++- .../e/apps/domain/ValidateAppAgeLimitUseCase.kt | 8 ++------ .../e/apps/utils/ParentalControlAuthenticator.kt | 2 +- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/app/detekt-baseline.xml b/app/detekt-baseline.xml index c8f15598e..e8a4412f9 100644 --- a/app/detekt-baseline.xml +++ b/app/detekt-baseline.xml @@ -19,6 +19,7 @@ + CyclomaticComplexMethod:AppInstallProcessor.kt$AppInstallProcessor$suspend fun enqueueFusedDownload( appInstall: AppInstall, isAnUpdate: Boolean = false, isSystemApp: Boolean = false ) EmptyCatchBlock:NativeDeviceInfoProviderModule.kt$NativeDeviceInfoProviderModule${ } EmptyFunctionBlock:CleanApkAuthenticator.kt$CleanApkAuthenticator${} InstanceOfCheckForException:GPlayHttpClient.kt$GPlayHttpClient$e is SocketTimeoutException @@ -95,7 +96,8 @@ MaxLineLength:UpdatesWorker.kt$UpdatesWorker$applicationContext.checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED MayBeConst:EcloudApiInterface.kt$EcloudApiInterface.Companion$val BASE_URL = "https://eu.gtoken.ecloud.global/" MemberNameEqualsClassName:Stores.kt$Stores$private val stores = defaultStores.toMutableMap() - NewLineAtEndOfFile:ContentRatingValidity.kt$foundation.e.apps.domain.model.ContentRatingValidity.kt + NestedBlockDepth:AppInstallProcessor.kt$AppInstallProcessor$suspend fun enqueueFusedDownload( appInstall: AppInstall, isAnUpdate: Boolean = false, isSystemApp: Boolean = false ) + NewLineAtEndOfFile:ContentRatingValidity.kt$foundation.e.apps.domain.model.ContentRatingValidity.kt NewLineAtEndOfFile:HomeConverter.kt$foundation.e.apps.data.cleanapk.repositories.HomeConverter.kt NewLineAtEndOfFile:Stores.kt$foundation.e.apps.data.Stores.kt NewLineAtEndOfFile:SystemAppsUpdatesRepository.kt$foundation.e.apps.data.gitlab.SystemAppsUpdatesRepository.kt diff --git a/app/src/main/java/foundation/e/apps/domain/ValidateAppAgeLimitUseCase.kt b/app/src/main/java/foundation/e/apps/domain/ValidateAppAgeLimitUseCase.kt index 929eb613a..3d11445aa 100644 --- a/app/src/main/java/foundation/e/apps/domain/ValidateAppAgeLimitUseCase.kt +++ b/app/src/main/java/foundation/e/apps/domain/ValidateAppAgeLimitUseCase.kt @@ -135,13 +135,9 @@ class ValidateAppAgeLimitUseCase @Inject constructor( return ResultSupreme.Success( ContentRatingValidity(isValid, app.contentRating, askToSudo = false) ) - } else if (typeAppManagement == TypeAppManagement.SECURITY_MODE) { - return ResultSupreme.Success( - data = ContentRatingValidity(false, askToSudo = true) - ) } - return ResultSupreme.Success( - data = ContentRatingValidity(true) + return ResultSupreme.Success( + data = ContentRatingValidity(false, askToSudo = true) ) } diff --git a/app/src/main/java/foundation/e/apps/utils/ParentalControlAuthenticator.kt b/app/src/main/java/foundation/e/apps/utils/ParentalControlAuthenticator.kt index eddb29184..cac3b1037 100644 --- a/app/src/main/java/foundation/e/apps/utils/ParentalControlAuthenticator.kt +++ b/app/src/main/java/foundation/e/apps/utils/ParentalControlAuthenticator.kt @@ -38,4 +38,4 @@ object ParentalControlAuthenticator { fun setResult(result: Boolean) { authResultDeferred?.complete(result) } -} \ No newline at end of file +} -- GitLab From a596ea92476d93ab2c3d748b1a9cbcb64223dd41 Mon Sep 17 00:00:00 2001 From: frankpreel Date: Tue, 22 Apr 2025 17:26:54 +0200 Subject: [PATCH 4/4] Rename - TypeAppManagement -> ProtectionMode - askToSudo -> requestPin --- .../parentalcontrol/ParentalControlRepository.kt | 12 ++++++------ .../e/apps/domain/ValidateAppAgeLimitUseCase.kt | 14 +++++++------- .../e/apps/domain/model/ContentRatingValidity.kt | 2 +- .../install/workmanager/AppInstallProcessor.kt | 2 +- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/app/src/main/java/foundation/e/apps/data/parentalcontrol/ParentalControlRepository.kt b/app/src/main/java/foundation/e/apps/data/parentalcontrol/ParentalControlRepository.kt index b615e5606..04ad94c5f 100644 --- a/app/src/main/java/foundation/e/apps/data/parentalcontrol/ParentalControlRepository.kt +++ b/app/src/main/java/foundation/e/apps/data/parentalcontrol/ParentalControlRepository.kt @@ -51,18 +51,18 @@ class ParentalControlRepository @Inject constructor( return Age.PARENTAL_CONTROL_DISABLED } - fun getSelectedTypeAppManagement(): TypeAppManagement { + fun getSelectedTypeAppManagement(): ProtectionMode { val uri = URI_PARENTAL_CONTROL_INSTALL_TYPE_APP_MANAGEMENT.toUri() val cursor = context.contentResolver.query(uri, null, null, null, null) cursor?.use { if (it.moveToFirst()) { val typeAppManagement = it.getInt(it.getColumnIndexOrThrow("typeappmanagement")) - return TypeAppManagement.entries[typeAppManagement] + return ProtectionMode.entries[typeAppManagement] } } - return TypeAppManagement.PARENTAL_CONTROL_DISABLED + return ProtectionMode.PARENTAL_CONTROL_DISABLED } } @@ -75,8 +75,8 @@ enum class Age { PARENTAL_CONTROL_DISABLED } -enum class TypeAppManagement { - CONTENT_RATING, //Legacy mode - SECURITY_MODE, //Allow using parental code (pin or pass) +enum class ProtectionMode { + REGULAR_MODE, + REQUEST_PIN_MODE, PARENTAL_CONTROL_DISABLED } diff --git a/app/src/main/java/foundation/e/apps/domain/ValidateAppAgeLimitUseCase.kt b/app/src/main/java/foundation/e/apps/domain/ValidateAppAgeLimitUseCase.kt index 3d11445aa..89c49302a 100644 --- a/app/src/main/java/foundation/e/apps/domain/ValidateAppAgeLimitUseCase.kt +++ b/app/src/main/java/foundation/e/apps/domain/ValidateAppAgeLimitUseCase.kt @@ -29,7 +29,7 @@ import foundation.e.apps.data.parentalcontrol.Age import foundation.e.apps.data.parentalcontrol.ContentRatingDao import foundation.e.apps.data.parentalcontrol.ParentalControlRepository import foundation.e.apps.data.parentalcontrol.ParentalControlRepository.Companion.KEY_PARENTAL_GUIDANCE -import foundation.e.apps.data.parentalcontrol.TypeAppManagement +import foundation.e.apps.data.parentalcontrol.ProtectionMode import foundation.e.apps.data.parentalcontrol.fdroid.FDroidAntiFeatureRepository import foundation.e.apps.data.parentalcontrol.googleplay.GPlayContentRatingGroup import foundation.e.apps.data.parentalcontrol.googleplay.GPlayContentRatingRepository @@ -53,7 +53,7 @@ class ValidateAppAgeLimitUseCase @Inject constructor( private val ageGroup: Age get() = parentalControlRepository.getSelectedAgeGroup() - private val typeAppManagement: TypeAppManagement + private val protectionMode: ProtectionMode get() = parentalControlRepository.getSelectedTypeAppManagement() suspend operator fun invoke(app: AppInstall): ResultSupreme { @@ -82,7 +82,7 @@ class ValidateAppAgeLimitUseCase @Inject constructor( hasNoContentRatingOnGPlay(app) -> ResultSupreme.Error() isParentalGuidance(app) -> ResultSupreme.Success( - data = ContentRatingValidity(false, askToSudo = true) + data = ContentRatingValidity(false, requestPin = true) ) else -> validateAgeLimit(ageGroup, app) @@ -123,7 +123,7 @@ class ValidateAppAgeLimitUseCase @Inject constructor( ageGroup: Age, app: AppInstall ): ResultSupreme { - if (typeAppManagement == TypeAppManagement.CONTENT_RATING) { + if (protectionMode == ProtectionMode.REGULAR_MODE) { val allowedContentRating = gPlayContentRatingRepository.contentRatingGroups.find { it.id == ageGroup.toString() } @@ -133,11 +133,11 @@ class ValidateAppAgeLimitUseCase @Inject constructor( val isValid = isValidAppAgeRating(app, allowedContentRating) return ResultSupreme.Success( - ContentRatingValidity(isValid, app.contentRating, askToSudo = false) + ContentRatingValidity(isValid, app.contentRating, requestPin = false) ) } return ResultSupreme.Success( - data = ContentRatingValidity(false, askToSudo = true) + data = ContentRatingValidity(false, requestPin = true) ) } @@ -181,7 +181,7 @@ class ValidateAppAgeLimitUseCase @Inject constructor( } private suspend fun isParentalGuidance(app: AppInstall): Boolean { - if (typeAppManagement == TypeAppManagement.CONTENT_RATING) { + if (protectionMode == ProtectionMode.REGULAR_MODE) { val fetchedContentRating = gPlayContentRatingRepository.getEnglishContentRating(app.packageName) return fetchedContentRating?.id == KEY_PARENTAL_GUIDANCE diff --git a/app/src/main/java/foundation/e/apps/domain/model/ContentRatingValidity.kt b/app/src/main/java/foundation/e/apps/domain/model/ContentRatingValidity.kt index 9f364d62e..46b3d53b1 100644 --- a/app/src/main/java/foundation/e/apps/domain/model/ContentRatingValidity.kt +++ b/app/src/main/java/foundation/e/apps/domain/model/ContentRatingValidity.kt @@ -22,4 +22,4 @@ package foundation.e.apps.domain.model import com.aurora.gplayapi.data.models.ContentRating data class ContentRatingValidity(val isValid: Boolean, val contentRating: ContentRating? = null, - val askToSudo: Boolean = false) // Ask to sudo (parent) legacy = false \ No newline at end of file + val requestPin: Boolean = false) \ No newline at end of file diff --git a/app/src/main/java/foundation/e/apps/install/workmanager/AppInstallProcessor.kt b/app/src/main/java/foundation/e/apps/install/workmanager/AppInstallProcessor.kt index b984df181..120a3a0a6 100644 --- a/app/src/main/java/foundation/e/apps/install/workmanager/AppInstallProcessor.kt +++ b/app/src/main/java/foundation/e/apps/install/workmanager/AppInstallProcessor.kt @@ -143,7 +143,7 @@ class AppInstallProcessor @Inject constructor( if (ageLimitValidationResult.data?.isValid != true) { if (ageLimitValidationResult.isSuccess()) { awaitInvokeAgeLimitEvent(appInstall.name) - if (ageLimitValidationResult.data?.askToSudo == true){ + if (ageLimitValidationResult.data?.requestPin == true){ val isAuthenticated = ParentalControlAuthenticator.awaitAuthentication() if (isAuthenticated) { ageLimitValidationResult.setData(ContentRatingValidity(true)) -- GitLab