AppDetailsFragment: Implement compatibility section using plexus

Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
This commit is contained in:
Aayush Gupta
2024-12-24 13:43:23 +07:00
parent 42ea3a8af9
commit 516af19172
11 changed files with 207 additions and 12 deletions

View File

@@ -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"

View File

@@ -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
}
}

View File

@@ -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()
}

View File

@@ -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)
}
}

View File

@@ -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))
}
}

View File

@@ -205,6 +205,7 @@ class AppDetailsFragment : BaseFragment<FragmentDetailsBinding>() {
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<FragmentDetailsBinding>() {
}
}
// 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<FragmentDetailsBinding>() {
}
}
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)

View File

@@ -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<Report?>()
val exodusReport = _exodusReport.asSharedFlow()
private val plexusReportStash = mutableMapOf<String, PlexusReport?>()
private val _plexusReport = MutableSharedFlow<PlexusReport?>()
val plexusReport = _plexusReport.asSharedFlow()
private val testProgramStatusStash = mutableMapOf<String, TestingProgramStatus?>()
private val _testingProgramStatus = MutableSharedFlow<TestingProgramStatus?>()
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<Report> {
try {
val gson = GsonBuilder()
.excludeFieldsWithModifiers(Modifier.TRANSIENT, Modifier.STATIC)
.create()
val jsonObject = JSONObject(response)
val exodusObject = jsonObject.getJSONObject(packageName)
val exodusReport: ExodusReport = gson.fromJson(

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M40,720Q49,613 105.5,523Q162,433 256,380L182,252Q176,243 179,233Q182,223 192,218Q200,213 210,216Q220,219 226,228L300,356Q386,320 480,320Q574,320 660,356L734,228Q740,219 750,216Q760,213 768,218Q778,223 781,233Q784,243 778,252L704,380Q798,433 854.5,523Q911,613 920,720L40,720ZM280,610Q301,610 315.5,595.5Q330,581 330,560Q330,539 315.5,524.5Q301,510 280,510Q259,510 244.5,524.5Q230,539 230,560Q230,581 244.5,595.5Q259,610 280,610ZM680,610Q701,610 715.5,595.5Q730,581 730,560Q730,539 715.5,524.5Q701,510 680,510Q659,510 644.5,524.5Q630,539 630,560Q630,581 644.5,595.5Q659,610 680,610Z"/>
</vector>

View File

@@ -103,6 +103,10 @@
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:listitem="@layout/view_app" />
<include
android:id="@+id/layout_details_compatibility"
layout="@layout/layout_details_compatibility" />
<include
android:id="@+id/layout_details_permissions"
layout="@layout/layout_details_permissions" />

View File

@@ -0,0 +1,51 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:divider="@drawable/divider"
android:orientation="vertical"
android:paddingStart="@dimen/padding_small"
android:paddingEnd="@dimen/padding_small"
android:showDividers="middle">
<com.aurora.store.view.custom.layouts.ActionHeaderLayout
android:id="@+id/header_compatibility"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:headerSubtitle="@string/plexus_powered"
app:headerTitle="@string/details_compatibility_title" />
<com.aurora.store.view.custom.layouts.DevInfoLayout
android:id="@+id/txt_gms_dependency"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:imgIcon="@drawable/ic_menu_about"
tools:txtSubtitle="@string/details_compatibility_gms_required_subtitle"
tools:txtTitle="@string/details_compatibility_gms_required_title" />
<LinearLayout
android:id="@+id/compatibility_status_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:visibility="gone">
<com.aurora.store.view.custom.layouts.DevInfoLayout
android:id="@+id/txt_status_aosp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:imgIcon="@drawable/ic_android"
app:txtSubtitle="@string/plexus_progress"
app:txtTitle="@string/details_compatibility_no_gms" />
<com.aurora.store.view.custom.layouts.DevInfoLayout
android:id="@+id/txt_status_microG"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:imgIcon="@drawable/ic_android"
app:txtSubtitle="@string/plexus_progress"
app:txtTitle="@string/details_compatibility_microg" />
</LinearLayout>
</LinearLayout>

View File

@@ -474,4 +474,17 @@
<string name="action_unarchive">Unarchive</string>
<string name="unable_to_open">Unable to open app</string>
<string name="version_update" translatable="false"><xliff:g id="installed_version_name">%1$s</xliff:g> (<xliff:g id="installed_version_code">%2$d</xliff:g>) ➔ <xliff:g id="update_version_name">%3$s</xliff:g> (<xliff:g id="update_version_code">%4$d</xliff:g>)</string>
<string name="plexus_powered">Powered by Plexus</string>
<string name="plexus_progress">Checking compatibility…</string>
<string name="details_compatibility_title">Compatibility</string>
<string name="details_compatibility_gms_required_title">Requires Google Play Services</string>
<string name="details_compatibility_gms_required_subtitle">You may need to install Googles proprietary library or a FOSS reimplementation such as microG.</string>
<string name="details_compatibility_gms_not_required_title">Works without Google Play Services</string>
<string name="details_compatibility_gms_not_required_subtitle">This app works without Googles proprietary library. However, it may still require other third-party libraries to work properly.</string>
<string name="details_compatibility_microg">With microG Project</string>
<string name="details_compatibility_no_gms">Without Google Play Services</string>
<string name="details_compatibility_status_compatible">Compatible: Works without any issues</string>
<string name="details_compatibility_status_limited">Limited: Works with limited features</string>
<string name="details_compatibility_status_unsupported">Unsupported: Not functional</string>
<string name="details_compatibility_status_unknown">Unknown: Not checked yet</string>
</resources>