Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit f4f70836 authored by Sayantan Roychowdhury's avatar Sayantan Roychowdhury
Browse files

Merge branch '8124-update_system_apps_p2' into '8124-update_system_apps_p1'

feat: (Issue 8124) GitLab API to get system app info

See merge request !476
parents 94f51de5 bf2988c1
Loading
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -105,6 +105,7 @@ data class Application(
    var contentRating: ContentRating = ContentRating(),
    @SerializedName(value = "antifeatures")
    val antiFeatures: List<Map<String, String>> = emptyList(),
    var isSystemApp: Boolean = false,
) {
    fun updateType() {
        this.type = if (this.is_pwa) PWA else NATIVE
+38 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021-2024 MURENA SAS
 *
 * 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.gitlab

import foundation.e.apps.data.gitlab.models.SystemAppInfo
import retrofit2.Response
import retrofit2.http.GET
import retrofit2.http.Path

interface SystemAppDefinitionApi {

    companion object {
        const val BASE_URL =
            "https://gitlab.e.foundation/api/v4/projects/"
    }

    @GET("{projectId}/releases/permalink/latest/downloads/json/{releaseType}.json")
    suspend fun getSystemAppUpdateInfo(
        @Path("projectId") projectId: Int,
        @Path("releaseType") releaseType: String,
    ): Response<SystemAppInfo>

}
+151 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021-2024 MURENA SAS
 *
 * 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.gitlab

import android.content.Context
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.models.SystemAppInfo
import foundation.e.apps.data.gitlab.models.SystemAppProject
import foundation.e.apps.data.gitlab.models.toApplication
import foundation.e.apps.data.handleNetworkResult
import foundation.e.apps.install.pkg.AppLoungePackageManager
import foundation.e.apps.utils.SystemInfoProvider
import javax.inject.Inject
import javax.inject.Singleton
import timber.log.Timber

@Singleton
class SystemAppsUpdatesRepository @Inject constructor(
    @ApplicationContext private val context: Context,
    private val updatableSystemAppsApi: UpdatableSystemAppsApi,
    private val systemAppDefinitionApi: SystemAppDefinitionApi,
    private val applicationDataManager: ApplicationDataManager,
    private val appLoungePackageManager: AppLoungePackageManager,
) {

    private var systemAppProjectList = mutableListOf<SystemAppProject>()

    suspend fun fetchUpdatableSystemApps() {
        val result = handleNetworkResult {
            val response = updatableSystemAppsApi.getUpdatableSystemApps()
            if (response.isSuccessful && !response.body().isNullOrEmpty()) {
                response.body()?.let { systemAppProjectList.addAll(it) }
            } else {
                Timber.e("Failed to fetch updatable apps: ${response.errorBody()?.string()}")
            }
        }

        if (!result.isSuccess()) {
            Timber.e("Network error when fetching updatable apps - ${result.message}")
        }
    }

    fun getUpdatableSystemApps(): List<String> {
        return systemAppProjectList.map { it.packageName }
    }

    private fun isSystemAppBlocked(
        systemAppInfo: SystemAppInfo,
        sdkLevel: Int,
        device: String,
    ): Boolean {
        return systemAppInfo.run {
            sdkLevel < minSdk ||
                    blockedAndroid?.contains(sdkLevel) == true ||
                    blockedDevices?.contains(device) == true ||
                    blockedDevices?.contains("${device}@${sdkLevel}") == true
        }
    }

    private suspend fun getSystemAppUpdateInfo(
        packageName: String,
        releaseType: String,
        sdkLevel: Int,
        device: String,
    ): Application? {

        val projectId =
            systemAppProjectList.find { it.packageName == packageName }?.projectId ?: return null

        val response = systemAppDefinitionApi.getSystemAppUpdateInfo(projectId, releaseType)
        val systemAppInfo = response.body()

        return if (systemAppInfo == null) {
            Timber.e("Null app info for: $packageName, response: ${response.errorBody()?.string()}")
            null
        } else if (isSystemAppBlocked(systemAppInfo, sdkLevel, device)) {
            Timber.e("Blocked system app: $packageName, details: $systemAppInfo")
            null
        } else {
            systemAppInfo.toApplication()
        }
    }

    private fun getSdkLevel(): Int {
        return Build.VERSION.SDK_INT
    }

    private fun getDevice(): String {
        return SystemInfoProvider.getSystemProperty(SystemInfoProvider.KEY_LINEAGE_DEVICE) ?: ""
    }

    private fun getSystemReleaseType(): String {
        return SystemInfoProvider.getSystemProperty(SystemInfoProvider.KEY_LINEAGE_RELEASE_TYPE) ?: ""
    }

    suspend fun getSystemUpdates(): List<Application> {
        val updateList = mutableListOf<Application>()
        val releaseType = getSystemReleaseType()
        val sdkLevel = getSdkLevel()
        val device = getDevice()

        val updatableApps = getUpdatableSystemApps()
        updatableApps.forEach {

            if (!appLoungePackageManager.isInstalled(it)) {
                // Don't install for system apps which are removed (by root or otherwise)
                return@forEach
            }

            val result = handleNetworkResult {
                getSystemAppUpdateInfo(
                    it,
                    releaseType,
                    sdkLevel,
                    device,
                )
            }

            result.data?.run {
                applicationDataManager.updateStatus(this)
                updateList.add(this)
                updateSource(context)
            }

            if (!result.isSuccess()) {
                Timber.e("Failed to get system app info for $it - ${result.message}")
            }
        }

        return updateList
    }

}
+34 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021-2024 MURENA SAS
 *
 * 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.gitlab

import foundation.e.apps.data.gitlab.models.SystemAppProject
import retrofit2.Response
import retrofit2.http.GET

interface UpdatableSystemAppsApi {

    companion object {
        const val BASE_URL =
            "https://gitlab.e.foundation/e/os/system-apps-update-info/-/raw/main/"
    }

    @GET("updatable_system_apps.json?inline=false")
    suspend fun getUpdatableSystemApps(): Response<List<SystemAppProject>>

}
+59 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021-2024 MURENA SAS
 *
 * 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.gitlab.models

import com.fasterxml.jackson.annotation.JsonIgnoreProperties
import com.squareup.moshi.Json
import foundation.e.apps.data.application.data.Application
import foundation.e.apps.data.enums.FilterLevel
import foundation.e.apps.data.enums.Origin
import java.util.UUID

@JsonIgnoreProperties(ignoreUnknown = true)
data class SystemAppInfo(
    val name: String,
    @Json(name = "package_name") val packageName: String,
    @Json(name = "version_code") val versionCode: Int,
    @Json(name = "min_sdk") val minSdk: Int,
    @Json(name = "version_name") val versionName: String,
    @Json(name = "url") val downloadUrl: String,
    @Json(name = "size") val size: Long?,
    @Json(name = "author_name") val authorName: String?,
    val priority: Boolean?,
    @Json(name = "blocked_android") val blockedAndroid: List<Int>?,
    @Json(name = "blocked_devices") val blockedDevices: List<String>?,
)

private const val RANDOM_SIZE = 1L

fun SystemAppInfo.toApplication(): Application {
    return Application(
        _id = UUID.randomUUID().toString(),
        author = authorName ?: "eFoundation",
        description = "",
        latest_version_code = versionCode,
        latest_version_number = versionName,
        name = name,
        package_name = packageName,
        origin = Origin.GITLAB_RELEASES,
        originalSize = size ?: RANDOM_SIZE,
        url = downloadUrl,
        isSystemApp = true,
        filterLevel = FilterLevel.NONE,
    )
}
Loading