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

Verified Commit a8b414ea authored by Fahim M. Choudhury's avatar Fahim M. Choudhury
Browse files

perf: reduce updates load stalls

Add timing logs to attribute update latency and move update network/IO work off the main thread to keep the UI responsive.
parent 0eacbba0
Loading
Loading
Loading
Loading
+17 −7
Original line number Diff line number Diff line
@@ -29,6 +29,8 @@ import foundation.e.apps.data.handleNetworkResult
import foundation.e.apps.data.playstore.PlayStoreRepository
import foundation.e.apps.domain.model.install.Status
import foundation.e.apps.ui.applicationlist.ApplicationDiffUtil
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import javax.inject.Inject

class AppsApiImpl @Inject constructor(
@@ -38,7 +40,9 @@ class AppsApiImpl @Inject constructor(
    override suspend fun getCleanapkAppDetails(packageName: String): Pair<Application, ResultStatus> {
        var application = Application()
        val result = handleNetworkResult {
            application = stores.getStore(Source.OPEN_SOURCE)?.getAppDetails(packageName) ?: Application()
            application = withContext(Dispatchers.IO) {
                stores.getStore(Source.OPEN_SOURCE)?.getAppDetails(packageName) ?: Application()
            }
            application.source = Source.OPEN_SOURCE
            application.updateType()
            application.updateFilterLevel()
@@ -88,10 +92,12 @@ class AppsApiImpl @Inject constructor(
        packageNameList: List<String>,
    ): Pair<List<Application>, ResultStatus> {
        val status = ResultStatus.OK
        val applicationList = mutableListOf<Application>()

        val applicationList = withContext(Dispatchers.IO) {
            val list = mutableListOf<Application>()
            for (packageName in packageNameList) {
            applicationList.add(stores.getStore(Source.OPEN_SOURCE)?.getAppDetails(packageName) ?: Application())
                list.add(stores.getStore(Source.OPEN_SOURCE)?.getAppDetails(packageName) ?: Application())
            }
            list
        }

        return Pair(applicationList, status)
@@ -104,8 +110,10 @@ class AppsApiImpl @Inject constructor(

        val playStore = stores.getStore(Source.PLAY_STORE) as? PlayStoreRepository
        val response = handleNetworkResult {
            withContext(Dispatchers.IO) {
                playStore?.getAppsDetails(packageNameList).orEmpty()
            }
        }
        val apps = response.data ?: emptyList()
        for (app in apps) {
            handleFilteredApps(app, applicationList)
@@ -144,7 +152,9 @@ class AppsApiImpl @Inject constructor(
            val store = stores.getStore(source)
                ?: throw IllegalStateException("Could not get store")

            application = store.getAppDetails(packageName)
            application = withContext(Dispatchers.IO) {
                store.getAppDetails(packageName)
            }
            application.let {
                applicationDataManager.updateStatus(it)
                it.source = source
+6 −2
Original line number Diff line number Diff line
@@ -23,6 +23,8 @@ import foundation.e.apps.data.cleanapk.CleanApkDownloadInfoFetcher
import foundation.e.apps.data.enums.Source
import foundation.e.apps.data.handleNetworkResult
import foundation.e.apps.data.installation.model.AppInstall
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import javax.inject.Inject

class DownloadInfoApiImpl @Inject constructor(
@@ -127,6 +129,8 @@ class DownloadInfoApiImpl @Inject constructor(
    }

    override suspend fun getOSSDownloadInfo(id: String, version: String?) =
        withContext(Dispatchers.IO) {
            (appSources.cleanApkAppsRepo as CleanApkDownloadInfoFetcher)
                .getDownloadInfo(id, version)
        }
}
+5 −1
Original line number Diff line number Diff line
@@ -8,6 +8,8 @@ import foundation.e.apps.data.fdroid.models.BuildInfo
import foundation.e.apps.data.fdroid.models.FdroidApiModel
import foundation.e.apps.data.fdroid.models.FdroidEntity
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import retrofit2.Response
import timber.log.Timber
import java.io.IOException
@@ -42,7 +44,9 @@ class FDroidRepository @Inject constructor(
    }

    suspend fun getBuildVersionInfo(packageName: String): List<BuildInfo> {
        return getFdroidApiResponse(packageName)?.body()?.builds ?: emptyList()
        return withContext(Dispatchers.IO) {
            getFdroidApiResponse(packageName)?.body()?.builds ?: emptyList()
        }
    }

    override suspend fun getAuthorName(application: Application): String {
+91 −8
Original line number Diff line number Diff line
@@ -34,6 +34,7 @@ import foundation.e.apps.data.install.pkg.AppLoungePackageManager
import foundation.e.apps.data.system.SystemInfoProvider
import foundation.e.apps.domain.model.install.Status
import timber.log.Timber
import java.util.Locale
import javax.inject.Inject
import javax.inject.Singleton

@@ -60,6 +61,13 @@ class SystemAppsUpdatesRepository @Inject constructor(

    private val systemAppProjectList = mutableListOf<SystemAppProject>()

    private companion object {
        private const val NANOS_IN_MILLI = 1_000_000L
        private const val MILLIS_IN_SECOND = 1000L
        private const val SECONDS_IN_MINUTE = 60L
        private const val MINUTES_IN_HOUR = 60L
    }

    private fun getUpdatableSystemApps(): List<String> {
        return systemAppProjectList.map { it.packageName }
    }
@@ -160,17 +168,51 @@ class SystemAppsUpdatesRepository @Inject constructor(
        sdkLevel: Int,
        device: String,
    ): Application? {
        val systemAppProject = systemAppProjectList.find { it.packageName == packageName } ?: return null
        val detailsUrl = getReleaseDetailsUrl(systemAppProject, releaseType) ?: return null

        val systemAppInfo = getSystemAppInfo(packageName, detailsUrl) ?: return null
        val appStartMs = nowMs()
        val systemAppProject = systemAppProjectList.find { it.packageName == packageName }
        val releaseStartMs = nowMs()
        val detailsUrl = systemAppProject?.let {
            getReleaseDetailsUrl(it, releaseType)
        }
        Timber.tag("FAHIM").i(
            "System updates release lookup, package=%s, duration=%s, urlFound=%s",
            packageName,
            formatDuration(nowMs() - releaseStartMs),
            detailsUrl != null
        )

        return if (isSystemAppBlocked(systemAppInfo, sdkLevel, device)) {
        val infoStartMs = nowMs()
        val systemAppInfo = detailsUrl?.let {
            getSystemAppInfo(packageName, it)
        }
        Timber.tag("FAHIM").i(
            "System updates app info fetch, package=%s, duration=%s, hasInfo=%s",
            packageName,
            formatDuration(nowMs() - infoStartMs),
            systemAppInfo != null
        )
        val isBlocked = systemAppInfo?.let {
            isSystemAppBlocked(it, sdkLevel, device)
        } == true
        if (isBlocked) {
            Timber.e("Blocked system app: $packageName, details: $systemAppInfo")
            null
        } else {
        }

        val application = if (systemAppInfo != null && !isBlocked) {
            systemAppInfo.toApplication(context)
        } else {
            null
        }

        if (application != null) {
            Timber.tag("FAHIM").i(
                "System updates application built, package=%s, duration=%s",
                packageName,
                formatDuration(nowMs() - appStartMs)
            )
        }

        return application
    }

    private suspend fun getSystemAppInfo(
@@ -231,14 +273,24 @@ class SystemAppsUpdatesRepository @Inject constructor(

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

        val updatableApps = getUpdatableSystemApps()
        Timber.tag("FAHIM").i(
            "System updates start, totalApps=%d",
            updatableApps.size
        )
        updatableApps.forEach {
            val perAppStartMs = nowMs()
            if (!appLoungePackageManager.isInstalled(it)) {
                // Don't install for system apps which are removed (by root or otherwise)
                Timber.tag("FAHIM").i(
                    "System updates skip (not installed), package=%s",
                    it
                )
                return@forEach
            }

@@ -250,6 +302,12 @@ class SystemAppsUpdatesRepository @Inject constructor(
                    device,
                )
            }
            Timber.tag("FAHIM").i(
                "System updates fetch done, package=%s, duration=%s, success=%s",
                it,
                formatDuration(nowMs() - perAppStartMs),
                result.isSuccess()
            )

            if (!result.isSuccess()) {
                Timber.e("Failed to get system app info for $it - ${result.message}")
@@ -267,8 +325,33 @@ class SystemAppsUpdatesRepository @Inject constructor(
            }
        }

        Timber.tag("FAHIM").i(
            "System updates done, updatable=%d, duration=%s",
            updateList.size,
            formatDuration(nowMs() - overallStartMs)
        )

        return updateList
    }

    private fun nowMs(): Long {
        return System.nanoTime() / NANOS_IN_MILLI
    }

    private fun formatDuration(durationMs: Long): String {
        val totalSeconds = durationMs / MILLIS_IN_SECOND
        val milliseconds = durationMs % MILLIS_IN_SECOND
        val seconds = totalSeconds % SECONDS_IN_MINUTE
        val totalMinutes = totalSeconds / SECONDS_IN_MINUTE
        val minutes = totalMinutes % MINUTES_IN_HOUR
        val hours = totalMinutes / MINUTES_IN_HOUR

        return if (hours > 0) {
            String.format(Locale.US, "%d:%02d:%02d.%03d", hours, minutes, seconds, milliseconds)
        } else {
            String.format(Locale.US, "%02d:%02d.%03d", minutes, seconds, milliseconds)
        }
    }
}

private class UnsupportedAndroidApiException(message: String) : RuntimeException(message)
+265 −39

File changed.

Preview size limit exceeded, changes collapsed.

Loading