Loading app/build.gradle +1 −1 Original line number Diff line number Diff line Loading @@ -42,7 +42,7 @@ def getSentryDsn = { -> } android { compileSdk 33 compileSdk 34 defaultConfig { applicationId "foundation.e.apps" Loading app/src/main/java/foundation/e/apps/data/gitlab/ReleaseInfoApi.kt +5 −3 Original line number Diff line number Diff line Loading @@ -17,11 +17,14 @@ package foundation.e.apps.data.gitlab import foundation.e.apps.data.gitlab.models.ReleaseInfo import foundation.e.apps.data.gitlab.models.GitLabReleaseInfo import retrofit2.Response import retrofit2.http.GET import retrofit2.http.Path /* Provides GitLab API client method (https://docs.gitlab.com/ee/api/releases/) */ interface ReleaseInfoApi { companion object { Loading @@ -32,6 +35,5 @@ interface ReleaseInfoApi { @GET("{projectId}/releases") suspend fun getReleases( @Path("projectId") projectId: Int, ): Response<List<ReleaseInfo>> ): Response<List<GitLabReleaseInfo>> } app/src/main/java/foundation/e/apps/data/gitlab/SystemAppsUpdatesRepository.kt +71 −22 Original line number Diff line number Diff line Loading @@ -22,6 +22,7 @@ import android.os.Build import dagger.hilt.android.qualifiers.ApplicationContext import foundation.e.apps.data.application.ApplicationDataManager import foundation.e.apps.data.application.data.Application import foundation.e.apps.data.gitlab.UpdatableSystemAppsApi.* import foundation.e.apps.data.gitlab.models.OsReleaseType import foundation.e.apps.data.gitlab.models.SystemAppInfo import foundation.e.apps.data.gitlab.models.SystemAppProject Loading @@ -43,6 +44,15 @@ class SystemAppsUpdatesRepository @Inject constructor( private val releaseInfoApi: ReleaseInfoApi, ) { private val androidVersionCode by lazy { try { getAndroidVersionCodeChar() } catch (exception: UnsupportedAndroidApiException) { Timber.w(exception.message, "Android API isn't in supported range to update some system apps") "UnsupportedAndroidAPI" } } private val systemAppProjectList = mutableListOf<SystemAppProject>() private fun getUpdatableSystemApps(): List<String> { Loading @@ -55,13 +65,7 @@ class SystemAppsUpdatesRepository @Inject constructor( return@handleNetworkResult } val systemName = getFullSystemName() val endPoint = if (isEligibleToFetchAppListFromTest(systemName)) { UpdatableSystemAppsApi.EndPoint.ENDPOINT_TEST } else { UpdatableSystemAppsApi.EndPoint.ENDPOINT_RELEASE } val endPoint = getUpdatableSystemAppEndPoint() val response = updatableSystemAppsApi.getUpdatableSystemApps(endPoint) if (response.isSuccessful && !response.body().isNullOrEmpty()) { Loading @@ -77,6 +81,15 @@ class SystemAppsUpdatesRepository @Inject constructor( } } private fun getUpdatableSystemAppEndPoint(): EndPoint { val systemName = getFullSystemName() return if (isEligibleToFetchAppListFromTest(systemName)) { EndPoint.ENDPOINT_TEST } else { EndPoint.ENDPOINT_RELEASE } } private fun isEligibleToFetchAppListFromTest(systemName: String) = systemName.isBlank() || systemName.contains("beta") || systemName.contains("rc") || Loading @@ -96,17 +109,23 @@ class SystemAppsUpdatesRepository @Inject constructor( } private suspend fun getReleaseDetailsUrl( projectId: Int, systemAppProject: SystemAppProject, releaseType: OsReleaseType, ): String? { val projectId = systemAppProject.projectId val releaseResponse = releaseInfoApi.getReleases(projectId) val releases = releaseResponse.body() var releases = releaseResponse.body() if (!releaseResponse.isSuccessful || releases == null) { Timber.e("Failed to fetch releases for project id - $projectId") return null } if (systemAppProject.dependsOnAndroidVersion) { val versionSuffix = "-$androidVersionCode" releases = releases.filter { isVersionedTag(it.tagName, versionSuffix) } } val sortedReleases = releases.sortedByDescending { it.releasedAt } Loading @@ -120,25 +139,23 @@ class SystemAppsUpdatesRepository @Inject constructor( return null } private suspend fun getSystemAppUpdateInfo( private fun isVersionedTag(tag: String, versionSuffix: String): Boolean { return tag.endsWith(suffix = versionSuffix, ignoreCase = true) } private suspend fun getApplication( packageName: String, releaseType: OsReleaseType, sdkLevel: Int, device: String, ): Application? { val projectId = systemAppProjectList.find { it.packageName == packageName }?.projectId ?: return null val detailsUrl = getReleaseDetailsUrl(projectId, releaseType) ?: return null val systemAppProject = systemAppProjectList.find { it.packageName == packageName } ?: return null val detailsUrl = getReleaseDetailsUrl(systemAppProject, releaseType) ?: return null val response = systemAppDefinitionApi.getSystemAppUpdateInfo(detailsUrl) val systemAppInfo = response.body() val systemAppInfo = getSystemAppInfo(packageName, detailsUrl) ?: return null return if (systemAppInfo == null) { Timber.e("Null app info for: $packageName, response: ${response.errorBody()?.string()}") null } else if (isSystemAppBlocked(systemAppInfo, sdkLevel, device)) { return if (isSystemAppBlocked(systemAppInfo, sdkLevel, device)) { Timber.e("Blocked system app: $packageName, details: $systemAppInfo") null } else { Loading @@ -146,6 +163,20 @@ class SystemAppsUpdatesRepository @Inject constructor( } } private suspend fun getSystemAppInfo( packageName: String, detailsUrl: String ): SystemAppInfo? { val response = systemAppDefinitionApi.getSystemAppUpdateInfo(detailsUrl) return if (response.isSuccessful) { response.body() } else { Timber.e("Can't get AppInfo for $packageName, response: ${response.errorBody()?.string()}") null } } private fun getFullSystemName(): String { return SystemInfoProvider.getSystemProperty(SystemInfoProvider.KEY_LINEAGE_VERSION) ?: "" } Loading @@ -164,6 +195,23 @@ class SystemAppsUpdatesRepository @Inject constructor( } } /** * This method must be updated when Murena or /e/ foundation support new Android version * or stop to support an old one */ private fun getAndroidVersionCodeChar(): String { /* TODO manually add new supported android version when they will be supported. * VANILLA_ICE_CREAM (A15) => https://gitlab.e.foundation/e/os/backlog/-/issues/2772 * BAKLAVA (A16) => https://gitlab.e.foundation/e/os/backlog/-/issues/2773 */ return when (val currentAPI = Build.VERSION.SDK_INT) { Build.VERSION_CODES.S, Build.VERSION_CODES.S_V2 -> "S" Build.VERSION_CODES.TIRAMISU -> "T" Build.VERSION_CODES.UPSIDE_DOWN_CAKE -> "U" else -> throw UnsupportedAndroidApiException("API level $currentAPI is not supported") } } suspend fun getSystemUpdates(): List<Application> { val updateList = mutableListOf<Application>() val releaseType = getSystemReleaseType() Loading @@ -179,7 +227,7 @@ class SystemAppsUpdatesRepository @Inject constructor( } val result = handleNetworkResult { getSystemAppUpdateInfo( getApplication( it, releaseType, sdkLevel, Loading @@ -200,5 +248,6 @@ class SystemAppsUpdatesRepository @Inject constructor( return updateList } } private class UnsupportedAndroidApiException(message: String) : RuntimeException(message) No newline at end of file app/src/main/java/foundation/e/apps/data/gitlab/models/ReleaseInfo.kt +3 −3 Original line number Diff line number Diff line Loading @@ -19,10 +19,10 @@ package foundation.e.apps.data.gitlab.models import com.squareup.moshi.Json data class ReleaseInfo( data class GitLabReleaseInfo( val name: String, @Json(name = "released_at") val releasedAt: String, @Json(name = "tag_name") val tagName: String, @Json(name = "released_at") val releasedAt: String, val assets: ReleaseAssets, ) { fun getAssetWebLink(assetName: String): String? { Loading app/src/main/java/foundation/e/apps/data/gitlab/models/SystemAppProject.kt +1 −0 Original line number Diff line number Diff line Loading @@ -20,4 +20,5 @@ package foundation.e.apps.data.gitlab.models data class SystemAppProject( val packageName: String, val projectId: Int, val dependsOnAndroidVersion: Boolean = false ) Loading
app/build.gradle +1 −1 Original line number Diff line number Diff line Loading @@ -42,7 +42,7 @@ def getSentryDsn = { -> } android { compileSdk 33 compileSdk 34 defaultConfig { applicationId "foundation.e.apps" Loading
app/src/main/java/foundation/e/apps/data/gitlab/ReleaseInfoApi.kt +5 −3 Original line number Diff line number Diff line Loading @@ -17,11 +17,14 @@ package foundation.e.apps.data.gitlab import foundation.e.apps.data.gitlab.models.ReleaseInfo import foundation.e.apps.data.gitlab.models.GitLabReleaseInfo import retrofit2.Response import retrofit2.http.GET import retrofit2.http.Path /* Provides GitLab API client method (https://docs.gitlab.com/ee/api/releases/) */ interface ReleaseInfoApi { companion object { Loading @@ -32,6 +35,5 @@ interface ReleaseInfoApi { @GET("{projectId}/releases") suspend fun getReleases( @Path("projectId") projectId: Int, ): Response<List<ReleaseInfo>> ): Response<List<GitLabReleaseInfo>> }
app/src/main/java/foundation/e/apps/data/gitlab/SystemAppsUpdatesRepository.kt +71 −22 Original line number Diff line number Diff line Loading @@ -22,6 +22,7 @@ import android.os.Build import dagger.hilt.android.qualifiers.ApplicationContext import foundation.e.apps.data.application.ApplicationDataManager import foundation.e.apps.data.application.data.Application import foundation.e.apps.data.gitlab.UpdatableSystemAppsApi.* import foundation.e.apps.data.gitlab.models.OsReleaseType import foundation.e.apps.data.gitlab.models.SystemAppInfo import foundation.e.apps.data.gitlab.models.SystemAppProject Loading @@ -43,6 +44,15 @@ class SystemAppsUpdatesRepository @Inject constructor( private val releaseInfoApi: ReleaseInfoApi, ) { private val androidVersionCode by lazy { try { getAndroidVersionCodeChar() } catch (exception: UnsupportedAndroidApiException) { Timber.w(exception.message, "Android API isn't in supported range to update some system apps") "UnsupportedAndroidAPI" } } private val systemAppProjectList = mutableListOf<SystemAppProject>() private fun getUpdatableSystemApps(): List<String> { Loading @@ -55,13 +65,7 @@ class SystemAppsUpdatesRepository @Inject constructor( return@handleNetworkResult } val systemName = getFullSystemName() val endPoint = if (isEligibleToFetchAppListFromTest(systemName)) { UpdatableSystemAppsApi.EndPoint.ENDPOINT_TEST } else { UpdatableSystemAppsApi.EndPoint.ENDPOINT_RELEASE } val endPoint = getUpdatableSystemAppEndPoint() val response = updatableSystemAppsApi.getUpdatableSystemApps(endPoint) if (response.isSuccessful && !response.body().isNullOrEmpty()) { Loading @@ -77,6 +81,15 @@ class SystemAppsUpdatesRepository @Inject constructor( } } private fun getUpdatableSystemAppEndPoint(): EndPoint { val systemName = getFullSystemName() return if (isEligibleToFetchAppListFromTest(systemName)) { EndPoint.ENDPOINT_TEST } else { EndPoint.ENDPOINT_RELEASE } } private fun isEligibleToFetchAppListFromTest(systemName: String) = systemName.isBlank() || systemName.contains("beta") || systemName.contains("rc") || Loading @@ -96,17 +109,23 @@ class SystemAppsUpdatesRepository @Inject constructor( } private suspend fun getReleaseDetailsUrl( projectId: Int, systemAppProject: SystemAppProject, releaseType: OsReleaseType, ): String? { val projectId = systemAppProject.projectId val releaseResponse = releaseInfoApi.getReleases(projectId) val releases = releaseResponse.body() var releases = releaseResponse.body() if (!releaseResponse.isSuccessful || releases == null) { Timber.e("Failed to fetch releases for project id - $projectId") return null } if (systemAppProject.dependsOnAndroidVersion) { val versionSuffix = "-$androidVersionCode" releases = releases.filter { isVersionedTag(it.tagName, versionSuffix) } } val sortedReleases = releases.sortedByDescending { it.releasedAt } Loading @@ -120,25 +139,23 @@ class SystemAppsUpdatesRepository @Inject constructor( return null } private suspend fun getSystemAppUpdateInfo( private fun isVersionedTag(tag: String, versionSuffix: String): Boolean { return tag.endsWith(suffix = versionSuffix, ignoreCase = true) } private suspend fun getApplication( packageName: String, releaseType: OsReleaseType, sdkLevel: Int, device: String, ): Application? { val projectId = systemAppProjectList.find { it.packageName == packageName }?.projectId ?: return null val detailsUrl = getReleaseDetailsUrl(projectId, releaseType) ?: return null val systemAppProject = systemAppProjectList.find { it.packageName == packageName } ?: return null val detailsUrl = getReleaseDetailsUrl(systemAppProject, releaseType) ?: return null val response = systemAppDefinitionApi.getSystemAppUpdateInfo(detailsUrl) val systemAppInfo = response.body() val systemAppInfo = getSystemAppInfo(packageName, detailsUrl) ?: return null return if (systemAppInfo == null) { Timber.e("Null app info for: $packageName, response: ${response.errorBody()?.string()}") null } else if (isSystemAppBlocked(systemAppInfo, sdkLevel, device)) { return if (isSystemAppBlocked(systemAppInfo, sdkLevel, device)) { Timber.e("Blocked system app: $packageName, details: $systemAppInfo") null } else { Loading @@ -146,6 +163,20 @@ class SystemAppsUpdatesRepository @Inject constructor( } } private suspend fun getSystemAppInfo( packageName: String, detailsUrl: String ): SystemAppInfo? { val response = systemAppDefinitionApi.getSystemAppUpdateInfo(detailsUrl) return if (response.isSuccessful) { response.body() } else { Timber.e("Can't get AppInfo for $packageName, response: ${response.errorBody()?.string()}") null } } private fun getFullSystemName(): String { return SystemInfoProvider.getSystemProperty(SystemInfoProvider.KEY_LINEAGE_VERSION) ?: "" } Loading @@ -164,6 +195,23 @@ class SystemAppsUpdatesRepository @Inject constructor( } } /** * This method must be updated when Murena or /e/ foundation support new Android version * or stop to support an old one */ private fun getAndroidVersionCodeChar(): String { /* TODO manually add new supported android version when they will be supported. * VANILLA_ICE_CREAM (A15) => https://gitlab.e.foundation/e/os/backlog/-/issues/2772 * BAKLAVA (A16) => https://gitlab.e.foundation/e/os/backlog/-/issues/2773 */ return when (val currentAPI = Build.VERSION.SDK_INT) { Build.VERSION_CODES.S, Build.VERSION_CODES.S_V2 -> "S" Build.VERSION_CODES.TIRAMISU -> "T" Build.VERSION_CODES.UPSIDE_DOWN_CAKE -> "U" else -> throw UnsupportedAndroidApiException("API level $currentAPI is not supported") } } suspend fun getSystemUpdates(): List<Application> { val updateList = mutableListOf<Application>() val releaseType = getSystemReleaseType() Loading @@ -179,7 +227,7 @@ class SystemAppsUpdatesRepository @Inject constructor( } val result = handleNetworkResult { getSystemAppUpdateInfo( getApplication( it, releaseType, sdkLevel, Loading @@ -200,5 +248,6 @@ class SystemAppsUpdatesRepository @Inject constructor( return updateList } } private class UnsupportedAndroidApiException(message: String) : RuntimeException(message) No newline at end of file
app/src/main/java/foundation/e/apps/data/gitlab/models/ReleaseInfo.kt +3 −3 Original line number Diff line number Diff line Loading @@ -19,10 +19,10 @@ package foundation.e.apps.data.gitlab.models import com.squareup.moshi.Json data class ReleaseInfo( data class GitLabReleaseInfo( val name: String, @Json(name = "released_at") val releasedAt: String, @Json(name = "tag_name") val tagName: String, @Json(name = "released_at") val releasedAt: String, val assets: ReleaseAssets, ) { fun getAssetWebLink(assetName: String): String? { Loading
app/src/main/java/foundation/e/apps/data/gitlab/models/SystemAppProject.kt +1 −0 Original line number Diff line number Diff line Loading @@ -20,4 +20,5 @@ package foundation.e.apps.data.gitlab.models data class SystemAppProject( val packageName: String, val projectId: Int, val dependsOnAndroidVersion: Boolean = false )