Loading app/src/main/java/foundation/e/apps/MainActivity.kt +2 −2 Original line number Diff line number Diff line Loading @@ -222,8 +222,8 @@ class MainActivity : AppCompatActivity() { it is AppEvent.AgeLimitRestrictionEvent }.collectLatest { ApplicationDialogFragment( getString(R.string.unknown_error), getString(R.string.age_rate_limit_message), 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) } Loading app/src/main/java/foundation/e/apps/data/blockedApps/ContentRatingParser.kt 0 → 100644 +89 −0 Original line number Diff line number Diff line /* * Copyright MURENA SAS 2024 * Apps Quickly and easily install Android apps onto your device! * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <https://www.gnu.org/licenses/>. * */ package foundation.e.apps.data.blockedApps import com.google.gson.Gson import com.google.gson.JsonSyntaxException import com.google.gson.reflect.TypeToken import foundation.e.apps.data.install.FileManager import timber.log.Timber import java.io.File import java.io.IOException import javax.inject.Inject import javax.inject.Named class ContentRatingParser @Inject constructor( private val gson: Gson, @Named("cacheDir") private val cacheDir: String ) { companion object { private const val CONTENT_RATINGS_FILE_NAME = "content_ratings.json" } fun parseContentRatingData(): List<ContentRatingGroup> { return try { val outputPath = moveFile() val contentRatingJson = readJsonFromFile(outputPath) Timber.d("ContentRatings file contents: $contentRatingJson") parseJsonOfContentRatingGroup(contentRatingJson) } catch (exception: IOException) { handleException(exception) } catch (exception: JsonSyntaxException) { handleException(exception) } } private fun readJsonFromFile(outputPath: String): String { val downloadedFile = File(outputPath + CONTENT_RATINGS_FILE_NAME) val contentRatingJson = String(downloadedFile.inputStream().readBytes()) return contentRatingJson } private fun moveFile(): String { val outputPath = "$cacheDir/content_ratings/" FileManager.moveFile( "$cacheDir/", CONTENT_RATINGS_FILE_NAME, outputPath ) return outputPath } private fun parseJsonOfContentRatingGroup(contentRatingJson: String): List<ContentRatingGroup> { val contentRatingsListTypeGroup = object : TypeToken<List<ContentRatingGroup>>() {}.type val contentRatingGroups: List<ContentRatingGroup> = gson.fromJson(contentRatingJson, contentRatingsListTypeGroup) return contentRatingGroups.map { it.ratings = it.ratings.map { rating -> rating.lowercase() } it } } private fun handleException(exception: Exception): List<ContentRatingGroup> { Timber.e(exception.localizedMessage ?: "", exception) return listOf() } } app/src/main/java/foundation/e/apps/data/blockedApps/ContentRatingsRepository.kt +2 −38 Original line number Diff line number Diff line Loading @@ -33,8 +33,7 @@ import javax.inject.Singleton @Singleton class ContentRatingsRepository @Inject constructor( private val downloadManager: DownloadManager, private val gson: Gson, @Named("cacheDir") private val cacheDir: String private val contentRatingParser: ContentRatingParser ) { private var _contentRatingGroups = listOf<ContentRatingGroup>() Loading @@ -54,43 +53,8 @@ class ContentRatingsRepository @Inject constructor( fileName = CONTENT_RATINGS_FILE_NAME ) { success, _ -> if (success) { parseContentRatingData() contentRatingParser.parseContentRatingData() } } } private fun parseContentRatingData() { _contentRatingGroups = try { val outputPath = "$cacheDir/warning_list/" FileManager.moveFile( "$cacheDir/", CONTENT_RATINGS_FILE_NAME, outputPath ) val downloadedFile = File(outputPath + CONTENT_RATINGS_FILE_NAME) val contentRatingJson = String(downloadedFile.inputStream().readBytes()) Timber.d("ContentRatings file contents: $contentRatingJson") parseJsonOfContentRatingGroup(contentRatingJson) } catch (exception: JsonSyntaxException) { handleException(exception) } } private fun parseJsonOfContentRatingGroup(contentRatingJson: String): List<ContentRatingGroup> { val contentRatingsListTypeGroup = object : TypeToken<List<ContentRatingGroup>>() {}.type val contentRatingGroups: List<ContentRatingGroup> = gson.fromJson(contentRatingJson, contentRatingsListTypeGroup) return contentRatingGroups.map { it.ratings = it.ratings.map { rating -> rating.lowercase() } it } } private fun handleException(exception: Exception): MutableList<ContentRatingGroup> { Timber.e(exception.localizedMessage ?: "", exception) return mutableListOf() } } app/src/main/java/foundation/e/apps/data/blockedApps/ParentalControlRepository.kt +5 −4 Original line number Diff line number Diff line Loading @@ -35,25 +35,26 @@ class ParentalControlRepository @Inject constructor( "content://foundation.e.parentalcontrol.provider/age" } fun getSelectedAgeGroup(): Ages? { fun getSelectedAgeGroup(): Age { val uri = Uri.parse(URI_PARENTAL_CONTROL_PROVIDER) val cursor = context.contentResolver.query(uri, null, null, null, null) cursor?.use { if (it.moveToFirst()) { val ageOrdinal = it.getInt(it.getColumnIndexOrThrow("age")) return Ages.values()[ageOrdinal] return Age.values()[ageOrdinal] } } return null return Age.PARENTAL_CONTROL_DISABLED } } enum class Ages { enum class Age { THREE, SIX, ELEVEN, FIFTEEN, SEVENTEEN, PARENTAL_CONTROL_DISABLED } app/src/main/java/foundation/e/apps/domain/ValidateAppAgeLimitUseCase.kt +12 −9 Original line number Diff line number Diff line Loading @@ -20,8 +20,9 @@ package foundation.e.apps.domain import com.aurora.gplayapi.data.models.AuthData import foundation.e.apps.data.ResultSupreme import foundation.e.apps.data.application.ApplicationRepository import foundation.e.apps.data.blockedApps.Ages import foundation.e.apps.data.blockedApps.Age import foundation.e.apps.data.blockedApps.ContentRatingGroup import foundation.e.apps.data.blockedApps.ContentRatingsRepository import foundation.e.apps.data.blockedApps.ParentalControlRepository Loading @@ -40,14 +41,17 @@ class ValidateAppAgeLimitUseCase @Inject constructor( private val playStoreRepository: PlayStoreRepository ) { suspend operator fun invoke(appInstall: AppInstall): Pair<Boolean, ResultStatus> { suspend operator fun invoke(appInstall: AppInstall): ResultSupreme<Boolean> { val authData = dataStoreManager.getAuthData() val selectedAgeGroup = parentalControlRepository.getSelectedAgeGroup() if (isParentalControlDisabled(selectedAgeGroup)) { return ResultSupreme.Success( true) } if (!verifyContentRatingExists(appInstall, authData)) { return Pair(false, ResultStatus.UNKNOWN) return ResultSupreme.Error(false) } val selectedAgeGroup = parentalControlRepository.getSelectedAgeGroup() val allowedContentRating = contentRatingRepository.contentRatingGroups.find { it.id == selectedAgeGroup.toString() } Loading @@ -58,9 +62,8 @@ class ValidateAppAgeLimitUseCase @Inject constructor( "Allowed content rating: $allowedContentRating" ) val isAppAgeLimitedValidated = isParentalControlDisabled(selectedAgeGroup) || isAppAgeRatingValid(appInstall, allowedContentRating) return Pair(isAppAgeLimitedValidated, ResultStatus.OK) val isAppAgeLimitValid = isAppAgeRatingValid(appInstall, allowedContentRating) return ResultSupreme.Success(isAppAgeLimitValid) } private fun isAppAgeRatingValid( Loading @@ -69,8 +72,8 @@ class ValidateAppAgeLimitUseCase @Inject constructor( ) = (appInstall.contentRating.id.isNotEmpty() && allowedContentRating?.ratings?.contains(appInstall.contentRating.id) == true) private fun isParentalControlDisabled(selectedAgeGroup: Ages?) = selectedAgeGroup == null private fun isParentalControlDisabled(selectedAgeGroup: Age?) = selectedAgeGroup == Age.PARENTAL_CONTROL_DISABLED private suspend fun verifyContentRatingExists( appInstall: AppInstall, Loading Loading
app/src/main/java/foundation/e/apps/MainActivity.kt +2 −2 Original line number Diff line number Diff line Loading @@ -222,8 +222,8 @@ class MainActivity : AppCompatActivity() { it is AppEvent.AgeLimitRestrictionEvent }.collectLatest { ApplicationDialogFragment( getString(R.string.unknown_error), getString(R.string.age_rate_limit_message), 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) } Loading
app/src/main/java/foundation/e/apps/data/blockedApps/ContentRatingParser.kt 0 → 100644 +89 −0 Original line number Diff line number Diff line /* * Copyright MURENA SAS 2024 * Apps Quickly and easily install Android apps onto your device! * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <https://www.gnu.org/licenses/>. * */ package foundation.e.apps.data.blockedApps import com.google.gson.Gson import com.google.gson.JsonSyntaxException import com.google.gson.reflect.TypeToken import foundation.e.apps.data.install.FileManager import timber.log.Timber import java.io.File import java.io.IOException import javax.inject.Inject import javax.inject.Named class ContentRatingParser @Inject constructor( private val gson: Gson, @Named("cacheDir") private val cacheDir: String ) { companion object { private const val CONTENT_RATINGS_FILE_NAME = "content_ratings.json" } fun parseContentRatingData(): List<ContentRatingGroup> { return try { val outputPath = moveFile() val contentRatingJson = readJsonFromFile(outputPath) Timber.d("ContentRatings file contents: $contentRatingJson") parseJsonOfContentRatingGroup(contentRatingJson) } catch (exception: IOException) { handleException(exception) } catch (exception: JsonSyntaxException) { handleException(exception) } } private fun readJsonFromFile(outputPath: String): String { val downloadedFile = File(outputPath + CONTENT_RATINGS_FILE_NAME) val contentRatingJson = String(downloadedFile.inputStream().readBytes()) return contentRatingJson } private fun moveFile(): String { val outputPath = "$cacheDir/content_ratings/" FileManager.moveFile( "$cacheDir/", CONTENT_RATINGS_FILE_NAME, outputPath ) return outputPath } private fun parseJsonOfContentRatingGroup(contentRatingJson: String): List<ContentRatingGroup> { val contentRatingsListTypeGroup = object : TypeToken<List<ContentRatingGroup>>() {}.type val contentRatingGroups: List<ContentRatingGroup> = gson.fromJson(contentRatingJson, contentRatingsListTypeGroup) return contentRatingGroups.map { it.ratings = it.ratings.map { rating -> rating.lowercase() } it } } private fun handleException(exception: Exception): List<ContentRatingGroup> { Timber.e(exception.localizedMessage ?: "", exception) return listOf() } }
app/src/main/java/foundation/e/apps/data/blockedApps/ContentRatingsRepository.kt +2 −38 Original line number Diff line number Diff line Loading @@ -33,8 +33,7 @@ import javax.inject.Singleton @Singleton class ContentRatingsRepository @Inject constructor( private val downloadManager: DownloadManager, private val gson: Gson, @Named("cacheDir") private val cacheDir: String private val contentRatingParser: ContentRatingParser ) { private var _contentRatingGroups = listOf<ContentRatingGroup>() Loading @@ -54,43 +53,8 @@ class ContentRatingsRepository @Inject constructor( fileName = CONTENT_RATINGS_FILE_NAME ) { success, _ -> if (success) { parseContentRatingData() contentRatingParser.parseContentRatingData() } } } private fun parseContentRatingData() { _contentRatingGroups = try { val outputPath = "$cacheDir/warning_list/" FileManager.moveFile( "$cacheDir/", CONTENT_RATINGS_FILE_NAME, outputPath ) val downloadedFile = File(outputPath + CONTENT_RATINGS_FILE_NAME) val contentRatingJson = String(downloadedFile.inputStream().readBytes()) Timber.d("ContentRatings file contents: $contentRatingJson") parseJsonOfContentRatingGroup(contentRatingJson) } catch (exception: JsonSyntaxException) { handleException(exception) } } private fun parseJsonOfContentRatingGroup(contentRatingJson: String): List<ContentRatingGroup> { val contentRatingsListTypeGroup = object : TypeToken<List<ContentRatingGroup>>() {}.type val contentRatingGroups: List<ContentRatingGroup> = gson.fromJson(contentRatingJson, contentRatingsListTypeGroup) return contentRatingGroups.map { it.ratings = it.ratings.map { rating -> rating.lowercase() } it } } private fun handleException(exception: Exception): MutableList<ContentRatingGroup> { Timber.e(exception.localizedMessage ?: "", exception) return mutableListOf() } }
app/src/main/java/foundation/e/apps/data/blockedApps/ParentalControlRepository.kt +5 −4 Original line number Diff line number Diff line Loading @@ -35,25 +35,26 @@ class ParentalControlRepository @Inject constructor( "content://foundation.e.parentalcontrol.provider/age" } fun getSelectedAgeGroup(): Ages? { fun getSelectedAgeGroup(): Age { val uri = Uri.parse(URI_PARENTAL_CONTROL_PROVIDER) val cursor = context.contentResolver.query(uri, null, null, null, null) cursor?.use { if (it.moveToFirst()) { val ageOrdinal = it.getInt(it.getColumnIndexOrThrow("age")) return Ages.values()[ageOrdinal] return Age.values()[ageOrdinal] } } return null return Age.PARENTAL_CONTROL_DISABLED } } enum class Ages { enum class Age { THREE, SIX, ELEVEN, FIFTEEN, SEVENTEEN, PARENTAL_CONTROL_DISABLED }
app/src/main/java/foundation/e/apps/domain/ValidateAppAgeLimitUseCase.kt +12 −9 Original line number Diff line number Diff line Loading @@ -20,8 +20,9 @@ package foundation.e.apps.domain import com.aurora.gplayapi.data.models.AuthData import foundation.e.apps.data.ResultSupreme import foundation.e.apps.data.application.ApplicationRepository import foundation.e.apps.data.blockedApps.Ages import foundation.e.apps.data.blockedApps.Age import foundation.e.apps.data.blockedApps.ContentRatingGroup import foundation.e.apps.data.blockedApps.ContentRatingsRepository import foundation.e.apps.data.blockedApps.ParentalControlRepository Loading @@ -40,14 +41,17 @@ class ValidateAppAgeLimitUseCase @Inject constructor( private val playStoreRepository: PlayStoreRepository ) { suspend operator fun invoke(appInstall: AppInstall): Pair<Boolean, ResultStatus> { suspend operator fun invoke(appInstall: AppInstall): ResultSupreme<Boolean> { val authData = dataStoreManager.getAuthData() val selectedAgeGroup = parentalControlRepository.getSelectedAgeGroup() if (isParentalControlDisabled(selectedAgeGroup)) { return ResultSupreme.Success( true) } if (!verifyContentRatingExists(appInstall, authData)) { return Pair(false, ResultStatus.UNKNOWN) return ResultSupreme.Error(false) } val selectedAgeGroup = parentalControlRepository.getSelectedAgeGroup() val allowedContentRating = contentRatingRepository.contentRatingGroups.find { it.id == selectedAgeGroup.toString() } Loading @@ -58,9 +62,8 @@ class ValidateAppAgeLimitUseCase @Inject constructor( "Allowed content rating: $allowedContentRating" ) val isAppAgeLimitedValidated = isParentalControlDisabled(selectedAgeGroup) || isAppAgeRatingValid(appInstall, allowedContentRating) return Pair(isAppAgeLimitedValidated, ResultStatus.OK) val isAppAgeLimitValid = isAppAgeRatingValid(appInstall, allowedContentRating) return ResultSupreme.Success(isAppAgeLimitValid) } private fun isAppAgeRatingValid( Loading @@ -69,8 +72,8 @@ class ValidateAppAgeLimitUseCase @Inject constructor( ) = (appInstall.contentRating.id.isNotEmpty() && allowedContentRating?.ratings?.contains(appInstall.contentRating.id) == true) private fun isParentalControlDisabled(selectedAgeGroup: Ages?) = selectedAgeGroup == null private fun isParentalControlDisabled(selectedAgeGroup: Age?) = selectedAgeGroup == Age.PARENTAL_CONTROL_DISABLED private suspend fun verifyContentRatingExists( appInstall: AppInstall, Loading