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

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

API for system apps

parent 02fd2c97
Loading
Loading
Loading
Loading
+8 −3
Original line number Diff line number Diff line
@@ -105,6 +105,8 @@ 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
@@ -112,9 +114,12 @@ data class Application(

    fun updateSource(context: Context) {
        this.apply {
            source = if (origin != Origin.CLEANAPK) ""
            else if (is_pwa) context.getString(R.string.pwa)
            else context.getString(R.string.open_source)
            source = when {
                origin == Origin.GITLAB -> context.getString(R.string.system_app)
                origin == Origin.GPLAY -> ""
                is_pwa -> context.getString(R.string.pwa)
                else -> context.getString(R.string.open_source)
            }
        }
    }
}
+34 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2019-2023  E FOUNDATION
 *
 * 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 EligibleSystemAppsApi {

    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 getAllEligibleApps(): Response<List<SystemAppProject>>

}
 No newline at end of file
+38 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2019-2023  E FOUNDATION
 *
 * 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>

}
 No newline at end of file
+136 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2019-2023  E FOUNDATION
 *
 * 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.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 eligibleSystemAppsApi: EligibleSystemAppsApi,
    private val systemAppDefinitionApi: SystemAppDefinitionApi,
    private val applicationDataManager: ApplicationDataManager,
    private val appLoungePackageManager: AppLoungePackageManager,
) {

    private var systemAppProjectList = mutableListOf<SystemAppProject>()

    suspend fun fetchAllEligibleApps() {
        val response = eligibleSystemAppsApi.getAllEligibleApps()
        if (response.isSuccessful && !response.body().isNullOrEmpty()) {
            response.body()?.let { systemAppProjectList.addAll(it) }
        }
    }

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

    private fun isSystemAppBlacklisted(
        systemAppInfo: SystemAppInfo,
        sdkLevel: Int,
        device: String,
    ): Boolean {
        return systemAppInfo.run {
            sdkLevel < minSdk ||
                    blacklistedAndroid?.contains(sdkLevel) == true ||
                    blacklistedDevices?.contains(device) == true ||
                    blacklistedDevices?.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)
        if (!response.isSuccessful) {
            Timber.e("Failed to fetch system app update definition for: $packageName, $releaseType")
            return null
        }

        val systemAppInfo = response.body() ?: return null

        if (isSystemAppBlacklisted(systemAppInfo, sdkLevel, device)) {
            Timber.e("Blacklisted system app: $packageName, $systemAppInfo")
            return null
        }

        return 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 eligibleApps = getAllEligibleApps()
        eligibleApps.forEach {

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

            getSystemAppUpdateInfo(
                it,
                releaseType,
                sdkLevel,
                device,
            )?.run {
                applicationDataManager.updateStatus(this)
                updateList.add(this)
                updateSource(context)
            }
        }

        return updateList
    }

}
 No newline at end of file
+55 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2019-2023  E FOUNDATION
 *
 * 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,
    val priority: Boolean?,
    @Json(name = "blacklisted_android") val blacklistedAndroid: List<Int>?,
    @Json(name = "blacklisted_devices") val blacklistedDevices: List<String>?,
)

fun SystemAppInfo.toApplication(): Application {
    return Application(
        _id = UUID.randomUUID().toString(),
        author = "Murena SAS",
        description = "",
        latest_version_code = versionCode,
        latest_version_number = versionName,
        name = name,
        package_name = packageName,
        origin = Origin.GITLAB,
        originalSize = 1, // so that the app is not filtered out,
        url = downloadUrl,
        isSystemApp = true,
        filterLevel = FilterLevel.NONE,
    )
}
 No newline at end of file
Loading