LeOS-Droid/app/src/main/kotlin/com/leos/droidify/MainApplication.kt

277 lines
9.7 KiB
Kotlin

package com.leos.droidify
import android.annotation.SuppressLint
import android.app.Application
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import androidx.appcompat.app.AppCompatDelegate
import androidx.hilt.work.HiltWorkerFactory
import androidx.work.Configuration
import androidx.work.NetworkType
import coil.ImageLoader
import coil.ImageLoaderFactory
import coil.disk.DiskCache
import coil.memory.MemoryCache
import com.leos.core.common.Constants
import com.leos.core.common.cache.Cache
import com.leos.core.common.extension.getInstalledPackagesCompat
import com.leos.core.common.extension.jobScheduler
import com.leos.core.common.log
import com.leos.core.datastore.SettingsRepository
import com.leos.core.datastore.get
import com.leos.core.datastore.model.AutoSync
import com.leos.core.datastore.model.InstallerType
import com.leos.core.datastore.model.ProxyPreference
import com.leos.core.datastore.model.ProxyType
import com.leos.droidify.content.ProductPreferences
import com.leos.droidify.database.Database
import com.leos.droidify.index.RepositoryUpdater
import com.leos.droidify.receivers.InstalledAppReceiver
import com.leos.droidify.service.Connection
import com.leos.droidify.service.SyncService
import com.leos.droidify.sync.SyncPreference
import com.leos.droidify.sync.toJobNetworkType
import com.leos.droidify.utility.extension.toInstalledItem
import com.leos.droidify.work.CleanUpWorker
import com.leos.installer.InstallManager
import com.leos.installer.installers.root.RootPermissionHandler
import com.leos.installer.installers.shizuku.ShizukuPermissionHandler
import com.leos.network.Downloader
import dagger.hilt.android.HiltAndroidApp
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.collectIndexed
import kotlinx.coroutines.flow.drop
import kotlinx.coroutines.launch
import java.net.InetSocketAddress
import java.net.Proxy
import javax.inject.Inject
import kotlin.time.Duration.Companion.INFINITE
import kotlin.time.Duration.Companion.hours
import com.leos.core.common.R as CommonR
@HiltAndroidApp
class MainApplication : Application(), ImageLoaderFactory, Configuration.Provider {
private val parentJob = SupervisorJob()
private val appScope = CoroutineScope(Dispatchers.Default + parentJob)
@Inject
lateinit var settingsRepository: SettingsRepository
@Inject
lateinit var installer: InstallManager
@Inject
lateinit var downloader: Downloader
@Inject
lateinit var shizukuPermissionHandler: ShizukuPermissionHandler
@Inject
lateinit var rootPermissionHandler: RootPermissionHandler
@Inject
lateinit var workerFactory: HiltWorkerFactory
override fun onCreate() {
super.onCreate()
val databaseUpdated = Database.init(this)
ProductPreferences.init(this, appScope)
RepositoryUpdater.init(appScope, downloader)
listenApplications()
checkLanguage()
updatePreference()
setupInstaller()
if (databaseUpdated) forceSyncAll()
}
override fun onTerminate() {
super.onTerminate()
appScope.cancel("Application Terminated")
installer.close()
}
private fun setupInstaller() {
appScope.launch {
launch {
settingsRepository.get { installerType }.collect {
if (it == InstallerType.SHIZUKU) handleShizukuInstaller()
if (it == InstallerType.ROOT) {
if (!rootPermissionHandler.isGranted) {
settingsRepository.setInstallerType(InstallerType.Default)
}
}
}
}
installer()
}
}
private fun CoroutineScope.handleShizukuInstaller() = launch {
shizukuPermissionHandler.state.collect { (isGranted, isAlive, _) ->
if (isAlive && isGranted) {
settingsRepository.setInstallerType(InstallerType.SHIZUKU)
return@collect
}
if (isAlive) {
settingsRepository.setInstallerType(InstallerType.Default)
shizukuPermissionHandler.requestPermission()
return@collect
}
settingsRepository.setInstallerType(InstallerType.Default)
}
}
private fun listenApplications() {
registerReceiver(
InstalledAppReceiver(packageManager),
IntentFilter().apply {
addAction(Intent.ACTION_PACKAGE_ADDED)
addAction(Intent.ACTION_PACKAGE_REMOVED)
addDataScheme("package")
}
)
val installedItems =
packageManager.getInstalledPackagesCompat()
?.map { it.toInstalledItem() }
?: return
Database.InstalledAdapter.putAll(installedItems)
}
private fun checkLanguage() {
appScope.launch {
val lastSetLanguage = settingsRepository.getInitial().language
val systemSetLanguage = AppCompatDelegate.getApplicationLocales().toLanguageTags()
if (systemSetLanguage != lastSetLanguage && lastSetLanguage != "system") {
settingsRepository.setLanguage(systemSetLanguage)
}
}
}
private fun updatePreference() {
appScope.launch {
launch {
settingsRepository.get { unstableUpdate }.drop(1).collect {
forceSyncAll()
}
}
launch {
settingsRepository.get { autoSync }.collectIndexed { index, syncMode ->
// Don't update sync job on initial collect
updateSyncJob(index > 0, syncMode)
}
}
launch {
settingsRepository.get { cleanUpInterval }.drop(1).collect {
if (it == INFINITE) {
CleanUpWorker.removeAllSchedules(applicationContext)
} else {
CleanUpWorker.scheduleCleanup(applicationContext, it)
}
}
}
launch {
settingsRepository.get { proxy }.collect(::updateProxy)
}
}
}
private fun updateProxy(proxyPreference: ProxyPreference) {
val type = proxyPreference.type
val host = proxyPreference.host
val port = proxyPreference.port
val socketAddress = when (type) {
ProxyType.DIRECT -> null
ProxyType.HTTP, ProxyType.SOCKS -> {
try {
InetSocketAddress.createUnresolved(host, port)
} catch (e: IllegalArgumentException) {
log(e)
null
}
}
}
val androidProxyType = when (type) {
ProxyType.DIRECT -> Proxy.Type.DIRECT
ProxyType.HTTP -> Proxy.Type.HTTP
ProxyType.SOCKS -> Proxy.Type.SOCKS
}
val determinedProxy = socketAddress?.let { Proxy(androidProxyType, it) } ?: Proxy.NO_PROXY
downloader.setProxy(determinedProxy)
}
private fun updateSyncJob(force: Boolean, autoSync: AutoSync) {
if (autoSync == AutoSync.NEVER) {
jobScheduler?.cancel(Constants.JOB_ID_SYNC)
return
}
val jobScheduler = jobScheduler
val syncConditions = when (autoSync) {
AutoSync.ALWAYS -> SyncPreference(NetworkType.CONNECTED)
AutoSync.WIFI_ONLY -> SyncPreference(NetworkType.UNMETERED)
AutoSync.WIFI_PLUGGED_IN -> SyncPreference(NetworkType.UNMETERED, pluggedIn = true)
else -> null
}
val isPreviousJobPending = jobScheduler?.allPendingJobs
?.any { it.id == Constants.JOB_ID_SYNC } == false
if ((force || !isPreviousJobPending) && syncConditions != null) {
val period = 12.hours.inWholeMilliseconds
val job = SyncService.Job.create(
context = this,
periodMillis = period,
networkType = syncConditions.toJobNetworkType(),
isCharging = syncConditions.pluggedIn,
isBatteryLow = syncConditions.batteryNotLow
)
jobScheduler?.schedule(job)
}
}
private fun forceSyncAll() {
Database.RepositoryAdapter.getAll().forEach {
if (it.lastModified.isNotEmpty() || it.entityTag.isNotEmpty()) {
Database.RepositoryAdapter.put(it.copy(lastModified = "", entityTag = ""))
}
}
Connection(SyncService::class.java, onBind = { connection, binder ->
binder.sync(SyncService.SyncRequest.FORCE)
connection.unbind(this)
}).bind(this)
}
class BootReceiver : BroadcastReceiver() {
@SuppressLint("UnsafeProtectedBroadcastReceiver")
override fun onReceive(context: Context, intent: Intent) = Unit
}
override fun newImageLoader(): ImageLoader {
val memoryCache = MemoryCache.Builder(this)
.maxSizePercent(0.25)
.build()
val diskCache = DiskCache.Builder()
.directory(Cache.getImagesDir(this))
.maxSizePercent(0.05)
.build()
return ImageLoader.Builder(this)
.memoryCache(memoryCache)
.diskCache(diskCache)
.error(CommonR.drawable.ic_cannot_load)
.crossfade(350)
.build()
}
override val workManagerConfiguration: Configuration
get() = Configuration.Builder()
.setWorkerFactory(workerFactory)
.build()
}