From 516af1917241c9107ba7cb2188ce2fbcfde03f23 Mon Sep 17 00:00:00 2001 From: Aayush Gupta Date: Tue, 24 Dec 2024 13:43:23 +0700 Subject: [PATCH] AppDetailsFragment: Implement compatibility section using plexus Signed-off-by: Aayush Gupta --- app/src/main/java/com/aurora/Constants.kt | 3 ++ .../com/aurora/store/data/model/Plexus.kt | 42 +++++++++++++++ .../store/data/network/OkHttpClientModule.kt | 1 + .../java/com/aurora/store/util/PackageUtil.kt | 8 +-- .../view/custom/layouts/DevInfoLayout.kt | 10 ++++ .../view/ui/details/AppDetailsFragment.kt | 42 +++++++++++++++ .../viewmodel/details/AppDetailsViewModel.kt | 34 ++++++++++--- app/src/main/res/drawable/ic_android.xml | 11 ++++ app/src/main/res/layout/fragment_details.xml | 4 ++ .../layout/layout_details_compatibility.xml | 51 +++++++++++++++++++ app/src/main/res/values/strings.xml | 13 +++++ 11 files changed, 207 insertions(+), 12 deletions(-) create mode 100644 app/src/main/java/com/aurora/store/data/model/Plexus.kt create mode 100644 app/src/main/res/drawable/ic_android.xml create mode 100644 app/src/main/res/layout/layout_details_compatibility.xml diff --git a/app/src/main/java/com/aurora/Constants.kt b/app/src/main/java/com/aurora/Constants.kt index 16732cfe3..d87ddcd3b 100644 --- a/app/src/main/java/com/aurora/Constants.kt +++ b/app/src/main/java/com/aurora/Constants.kt @@ -32,6 +32,9 @@ object Constants { const val EXODUS_REPORT_URL = "https://reports.exodus-privacy.eu.org/reports/" const val EXODUS_SEARCH_URL = "https://reports.exodus-privacy.eu.org/api/search/" + const val PLEXUS_API_URL = "https://plexus.techlore.tech/api/v1/apps" + const val PLEXUS_SEARCH_URL = "https://plexus.techlore.tech/?q=" + const val SHARE_URL = "https://play.google.com/store/apps/details?id=" const val UPDATE_URL_STABLE = "https://gitlab.com/AuroraOSS/AuroraStore/raw/master/updates.json" diff --git a/app/src/main/java/com/aurora/store/data/model/Plexus.kt b/app/src/main/java/com/aurora/store/data/model/Plexus.kt new file mode 100644 index 000000000..1e5fcf492 --- /dev/null +++ b/app/src/main/java/com/aurora/store/data/model/Plexus.kt @@ -0,0 +1,42 @@ +package com.aurora.store.data.model + +import androidx.annotation.StringRes +import com.aurora.store.R +import com.google.gson.annotations.SerializedName + +data class PlexusReport( + @SerializedName("data") + val report: Data? +) + +data class Data( + val name: String, + val scores: Scores, + @SerializedName("updated_at") + val updatedAt: String +) + +data class Scores( + @SerializedName("micro_g") + val microG: Rating, + @SerializedName("native") + val aosp: Rating +) + +data class Rating( + val denominator: Float, + val numerator: Float, + val rating_type: String, + val total_count: Long +) { + private val fraction get() = numerator / denominator + + @get:StringRes + val status: Int + get() = when { + fraction == 0F -> R.string.details_compatibility_status_unknown + fraction >= 0.90 -> R.string.details_compatibility_status_compatible + fraction >= 0.50 -> R.string.details_compatibility_status_limited + else -> R.string.details_compatibility_status_unsupported + } +} diff --git a/app/src/main/java/com/aurora/store/data/network/OkHttpClientModule.kt b/app/src/main/java/com/aurora/store/data/network/OkHttpClientModule.kt index 8278521d4..21de9b5c6 100644 --- a/app/src/main/java/com/aurora/store/data/network/OkHttpClientModule.kt +++ b/app/src/main/java/com/aurora/store/data/network/OkHttpClientModule.kt @@ -90,6 +90,7 @@ object OkHttpClientModule { .add("auroraoss.com", "sha256/mEflZT5enoR1FuXLgYYGqnVEoZvmf9c2bVBpiOjYQ0c=") // GTS Root R4 .add("*.exodus-privacy.eu.org", "sha256/C5+lpZ7tcVwmwQIMcRtPbsQtWLABXhQzejna0wHFr8M=") // ISRG Root X1 .add("gitlab.com", "sha256/x4QzPSC810K5/cMjb05Qm4k3Bw5zBn4lTdO/nEW/Td4=") // USERTrust RSA Certification Authority + .add("plexus.techlore.tech", "sha256/C5+lpZ7tcVwmwQIMcRtPbsQtWLABXhQzejna0wHFr8M=") // ISRG Root X1 .build() } diff --git a/app/src/main/java/com/aurora/store/util/PackageUtil.kt b/app/src/main/java/com/aurora/store/util/PackageUtil.kt index e0764269d..a485d19af 100644 --- a/app/src/main/java/com/aurora/store/util/PackageUtil.kt +++ b/app/src/main/java/com/aurora/store/util/PackageUtil.kt @@ -50,7 +50,7 @@ object PackageUtil { private const val TAG = "PackageUtil" - private const val PACKAGE_NAME_MICRO_G = "com.google.android.gms" + const val PACKAGE_NAME_GMS = "com.google.android.gms" private const val VERSION_CODE_MICRO_G = 240913402 private const val VERSION_CODE_MICRO_G_HUAWEI = 240913007 @@ -66,15 +66,15 @@ object PackageUtil { } fun hasSupportedMicroG(context: Context): Boolean { - val isMicroG = CertUtil.isMicroGGMS(context, PACKAGE_NAME_MICRO_G) + val isMicroG = CertUtil.isMicroGGMS(context, PACKAGE_NAME_GMS) // Do not proceed if MicroG variant is not installed if (!isMicroG) return false return if (isHuawei) { - isInstalled(context, PACKAGE_NAME_MICRO_G, VERSION_CODE_MICRO_G_HUAWEI) + isInstalled(context, PACKAGE_NAME_GMS, VERSION_CODE_MICRO_G_HUAWEI) } else { - isInstalled(context, PACKAGE_NAME_MICRO_G, VERSION_CODE_MICRO_G) + isInstalled(context, PACKAGE_NAME_GMS, VERSION_CODE_MICRO_G) } } diff --git a/app/src/main/java/com/aurora/store/view/custom/layouts/DevInfoLayout.kt b/app/src/main/java/com/aurora/store/view/custom/layouts/DevInfoLayout.kt index 4ce3fcbe1..056b626b8 100644 --- a/app/src/main/java/com/aurora/store/view/custom/layouts/DevInfoLayout.kt +++ b/app/src/main/java/com/aurora/store/view/custom/layouts/DevInfoLayout.kt @@ -22,7 +22,9 @@ package com.aurora.store.view.custom.layouts import android.content.Context import android.util.AttributeSet import android.widget.RelativeLayout +import androidx.annotation.ColorRes import androidx.appcompat.widget.AppCompatImageView +import androidx.core.content.ContextCompat import androidx.core.view.isVisible import com.aurora.store.R import com.aurora.store.databinding.ViewDevInfoBinding @@ -87,4 +89,12 @@ class DevInfoLayout : RelativeLayout { binding.txtSubtitle.isVisible = text != null invalidate() } + + fun setTitleColor(@ColorRes color: Int) { + binding.txtTitle.setTextColor(ContextCompat.getColor(context, color)) + } + + fun setSubtitleColor(@ColorRes color: Int) { + binding.txtSubtitle.setTextColor(ContextCompat.getColor(context, color)) + } } diff --git a/app/src/main/java/com/aurora/store/view/ui/details/AppDetailsFragment.kt b/app/src/main/java/com/aurora/store/view/ui/details/AppDetailsFragment.kt index 4243f71f4..33f579ab8 100644 --- a/app/src/main/java/com/aurora/store/view/ui/details/AppDetailsFragment.kt +++ b/app/src/main/java/com/aurora/store/view/ui/details/AppDetailsFragment.kt @@ -205,6 +205,7 @@ class AppDetailsFragment : BaseFragment() { updateToolbar(app) updateAppHeader(app) // Re-inflate the app details, as web data may vary. updateExtraDetails(app) + updateCompatibilityInfo() if (app.versionCode == 0) { warnAppUnavailable(app) @@ -331,6 +332,27 @@ class AppDetailsFragment : BaseFragment() { } } + // Plexus Report + viewModel.plexusReport.onEach { plexusReport -> + if (plexusReport?.report?.scores == null) { + binding.layoutDetailsCompatibility.txtStatusMicroG.subTitle = + getString(R.string.details_compatibility_status_unknown) + + binding.layoutDetailsCompatibility.txtStatusAosp.subTitle = + getString(R.string.details_compatibility_status_unknown) + } else { + binding.layoutDetailsCompatibility.headerCompatibility.addClickListener { + requireContext().browse("${Constants.PLEXUS_SEARCH_URL}${app.packageName}") + } + + binding.layoutDetailsCompatibility.txtStatusMicroG.subTitle = + getString(plexusReport.report.scores.microG.status) + + binding.layoutDetailsCompatibility.txtStatusAosp.subTitle = + getString(plexusReport.report.scores.microG.status) + } + }.launchIn(viewLifecycleOwner.lifecycleScope) + // Beta program viewLifecycleOwner.lifecycleScope.launch { viewModel.testingProgramStatus.collect { @@ -1013,6 +1035,26 @@ class AppDetailsFragment : BaseFragment() { } } + private fun updateCompatibilityInfo() { + if (app.dependencies.dependentPackages.contains(PackageUtil.PACKAGE_NAME_GMS)) { + viewModel.fetchPlexusReport(app.packageName) + + binding.layoutDetailsCompatibility.txtGmsDependency.apply { + title = getString(R.string.details_compatibility_gms_required_title) + subTitle = getString(R.string.details_compatibility_gms_required_subtitle) + setTitleColor(R.color.colorRed) + } + + binding.layoutDetailsCompatibility.compatibilityStatusLayout.isVisible = true + } else { + binding.layoutDetailsCompatibility.txtGmsDependency.apply { + title = getString(R.string.details_compatibility_gms_not_required_title) + subTitle = getString(R.string.details_compatibility_gms_not_required_subtitle) + setTitleColor(R.color.colorGreen) + } + } + } + private fun warnAppUnavailable(app: App) { AuroraApp.events.send(InstallerEvent.Failed(app.packageName).apply { error = getString(R.string.status_unavailable) diff --git a/app/src/main/java/com/aurora/store/viewmodel/details/AppDetailsViewModel.kt b/app/src/main/java/com/aurora/store/viewmodel/details/AppDetailsViewModel.kt index 5e60fee9e..6deb54f5f 100644 --- a/app/src/main/java/com/aurora/store/viewmodel/details/AppDetailsViewModel.kt +++ b/app/src/main/java/com/aurora/store/viewmodel/details/AppDetailsViewModel.kt @@ -13,16 +13,16 @@ import com.aurora.gplayapi.helpers.AppDetailsHelper import com.aurora.gplayapi.helpers.ReviewsHelper import com.aurora.gplayapi.helpers.web.WebDataSafetyHelper import com.aurora.gplayapi.network.IHttpClient -import com.aurora.store.AuroraApp import com.aurora.store.BuildConfig import com.aurora.store.R import com.aurora.store.data.helper.DownloadHelper import com.aurora.store.data.model.ExodusReport +import com.aurora.store.data.model.PlexusReport import com.aurora.store.data.model.Report import com.aurora.store.data.room.favourite.Favourite import com.aurora.store.data.room.favourite.FavouriteDao import com.aurora.store.util.PackageUtil -import com.google.gson.GsonBuilder +import com.google.gson.Gson import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.Dispatchers @@ -35,7 +35,6 @@ import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch import org.json.JSONObject -import java.lang.reflect.Modifier import javax.inject.Inject import com.aurora.gplayapi.data.models.datasafety.Report as DataSafetyReport @@ -47,7 +46,8 @@ class AppDetailsViewModel @Inject constructor( private val webDataSafetyHelper: WebDataSafetyHelper, private val downloadHelper: DownloadHelper, private val favouriteDao: FavouriteDao, - private val httpClient: IHttpClient + private val httpClient: IHttpClient, + private val gson: Gson ) : ViewModel() { private val TAG = AppDetailsViewModel::class.java.simpleName @@ -72,6 +72,10 @@ class AppDetailsViewModel @Inject constructor( private val _exodusReport = MutableSharedFlow() val exodusReport = _exodusReport.asSharedFlow() + private val plexusReportStash = mutableMapOf() + private val _plexusReport = MutableSharedFlow() + val plexusReport = _plexusReport.asSharedFlow() + private val testProgramStatusStash = mutableMapOf() private val _testingProgramStatus = MutableSharedFlow() val testingProgramStatus = _testingProgramStatus.asSharedFlow() @@ -147,6 +151,24 @@ class AppDetailsViewModel @Inject constructor( } } + fun fetchPlexusReport(packageName: String) { + viewModelScope.launch(Dispatchers.IO) { + try { + val plexusReport = plexusReportStash.getOrPut(packageName) { + val url = "${Constants.PLEXUS_API_URL}/${packageName}/?scores=true" + val playResponse = httpClient.get(url, emptyMap()) + gson.fromJson(String(playResponse.responseBytes), PlexusReport::class.java) + } + + _plexusReport.emit(plexusReport) + } catch (exception: Exception) { + Log.e(TAG, "Failed to fetch compatibility report", exception) + plexusReportStash[packageName] = null + _plexusReport.emit(null) + } + } + } + fun fetchTestingProgramStatus(packageName: String, subscribe: Boolean) { viewModelScope.launch(Dispatchers.IO) { try { @@ -258,10 +280,6 @@ class AppDetailsViewModel @Inject constructor( private fun parseExodusResponse(response: String, packageName: String): List { try { - val gson = GsonBuilder() - .excludeFieldsWithModifiers(Modifier.TRANSIENT, Modifier.STATIC) - .create() - val jsonObject = JSONObject(response) val exodusObject = jsonObject.getJSONObject(packageName) val exodusReport: ExodusReport = gson.fromJson( diff --git a/app/src/main/res/drawable/ic_android.xml b/app/src/main/res/drawable/ic_android.xml new file mode 100644 index 000000000..dddde987c --- /dev/null +++ b/app/src/main/res/drawable/ic_android.xml @@ -0,0 +1,11 @@ + + + + diff --git a/app/src/main/res/layout/fragment_details.xml b/app/src/main/res/layout/fragment_details.xml index e5159311e..87493c1cb 100644 --- a/app/src/main/res/layout/fragment_details.xml +++ b/app/src/main/res/layout/fragment_details.xml @@ -103,6 +103,10 @@ app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" tools:listitem="@layout/view_app" /> + + diff --git a/app/src/main/res/layout/layout_details_compatibility.xml b/app/src/main/res/layout/layout_details_compatibility.xml new file mode 100644 index 000000000..738d7838f --- /dev/null +++ b/app/src/main/res/layout/layout_details_compatibility.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 55255c0a3..feb7fd9f9 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -474,4 +474,17 @@ Unarchive Unable to open app %1$s (%2$d) ➔ %3$s (%4$d) + Powered by Plexus + Checking compatibility… + Compatibility + Requires Google Play Services + You may need to install Google’s proprietary library or a FOSS reimplementation such as microG. + Works without Google Play Services + This app works without Google’s proprietary library. However, it may still require other third-party libraries to work properly. + With microG Project + Without Google Play Services + Compatible: Works without any issues + Limited: Works with limited features + Unsupported: Not functional + Unknown: Not checked yet