You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
MagiskModuleManager/app/src/main/kotlin/com/fox2code/mmm/AppUpdateManager.kt

143 lines
5.8 KiB
Kotlin

/*
* Copyright (c) 2023 to present Androidacy and contributors. Names, logos, icons, and the Androidacy name are all trademarks of Androidacy and may not be used without license. See LICENSE for more information.
*/
package com.fox2code.mmm
import com.fox2code.mmm.androidacy.AndroidacyRepoData
import com.fox2code.mmm.utils.io.Files.Companion.write
import com.fox2code.mmm.utils.io.net.Http.Companion.doHttpGet
import org.json.JSONObject
import timber.log.Timber
import java.io.File
import java.io.FileInputStream
import java.io.IOException
import java.io.InputStream
@Suppress("unused")
class AppUpdateManager private constructor() {
var changes: String? = null
private val compatDataId = HashMap<String, Int>()
private val updateLock = Any()
private val compatFile: File = File(MainApplication.INSTANCE!!.filesDir, "compat.txt")
private var latestRelease: Int?
private var lastChecked: Long
init {
latestRelease = MainApplication.bootSharedPreferences
?.getInt("latest_vcode", BuildConfig.VERSION_CODE)
lastChecked = 0
if (compatFile.isFile) {
try {
parseCompatibilityFlags(FileInputStream(compatFile))
} catch (ignored: IOException) {
}
}
}
// Return true if should show a notification
fun checkUpdate(force: Boolean): Boolean {
if (!BuildConfig.ENABLE_AUTO_UPDATER) return false
if (!force && peekShouldUpdate()) return true
val lastChecked = lastChecked
if (lastChecked != 0L && // Avoid spam calls by putting a 60 seconds timer
lastChecked < System.currentTimeMillis() - 60000L
) return force && peekShouldUpdate()
synchronized(updateLock) {
Timber.d("Checking for app updates")
if (lastChecked != this.lastChecked) return peekShouldUpdate()
// make a request to https://production-api.androidacy.com/amm/updates/check with appVersionCode and token/device_id/client_id
var token = AndroidacyRepoData.token
if (!AndroidacyRepoData.instance.isValidToken(token)) {
Timber.w("Invalid token, not checking for updates")
token = AndroidacyRepoData.instance.requestNewToken()
}
val deviceId = AndroidacyRepoData.generateDeviceId()
val clientId = BuildConfig.ANDROIDACY_CLIENT_ID
val url =
"https://production-api.androidacy.com/amm/updates/check?appVersionCode=${BuildConfig.VERSION_CODE}&token=$token&device_id=$deviceId&client_id=$clientId"
val response = doHttpGet(url, false)
// convert response to string
val responseString = String(response, Charsets.UTF_8)
Timber.d("Response: $responseString")
// json response has a boolean shouldUpdate and an int latestVersion
JSONObject(responseString).let {
if (it.getBoolean("shouldUpdate")) {
latestRelease = it.getInt("latestVersion")
MainApplication.bootSharedPreferences?.edit()
?.putInt("latest_vcode", latestRelease!!)?.apply()
}
this.changes = it.getString("changelog")
}
}
return peekShouldUpdate()
}
fun checkUpdateCompat() {
compatDataId.clear()
try {
write(compatFile, ByteArray(0))
} catch (e: IOException) {
Timber.e(e)
}
// There once lived an implementation that used a GitHub API to get the compatibility flags. It was removed because it was too slow and the API was rate limited.
Timber.w("Remote compatibility data flags are not implemented.")
}
fun peekShouldUpdate(): Boolean {
if (!BuildConfig.ENABLE_AUTO_UPDATER || BuildConfig.DEBUG) return false
// Convert both BuildConfig.VERSION_NAME and latestRelease to int
var currentVersion = 0
var latestVersion = 0
try {
currentVersion = BuildConfig.VERSION_CODE
latestVersion = latestRelease!!
} catch (ignored: NumberFormatException) {
}
return currentVersion < latestVersion
}
fun peekHasUpdate(): Boolean {
return if (!BuildConfig.ENABLE_AUTO_UPDATER || BuildConfig.DEBUG) false else peekShouldUpdate()
}
@Suppress("UNUSED_PARAMETER")
@Throws(IOException::class)
private fun parseCompatibilityFlags(inputStream: InputStream) {
compatDataId.clear()
Timber.d("Not implemented")
}
fun getCompatibilityFlags(moduleId: String): Int {
val compatFlags = compatDataId[moduleId]
return compatFlags ?: 0
}
@Suppress("MemberVisibilityCanBePrivate")
companion object {
const val FLAG_COMPAT_LOW_QUALITY = 0x0001
const val FLAG_COMPAT_NO_EXT = 0x0002
const val FLAG_COMPAT_MAGISK_CMD = 0x0004
const val FLAG_COMPAT_NEED_32BIT = 0x0008
const val FLAG_COMPAT_MALWARE = 0x0010
const val FLAG_COMPAT_NO_ANSI = 0x0020
const val FLAG_COMPAT_FORCE_ANSI = 0x0040
const val FLAG_COMPAT_FORCE_HIDE = 0x0080
const val FLAG_COMPAT_MMT_REBORN = 0x0100
const val FLAG_COMPAT_ZIP_WRAPPER = 0x0200
const val RELEASES_API_URL =
"https://api.github.com/repos/Androidacy/MagiskModuleManager/releases/latest"
val appUpdateManager = AppUpdateManager()
fun getFlagsForModule(moduleId: String): Int {
return appUpdateManager.getCompatibilityFlags(moduleId)
}
@JvmStatic
fun shouldForceHide(repoId: String): Boolean {
return if (BuildConfig.DEBUG || repoId.startsWith("repo_") || repoId == "magisk_alt_repo") false else !repoId.startsWith(
"repo_"
) && appUpdateManager.getCompatibilityFlags(repoId) and FLAG_COMPAT_FORCE_HIDE != 0
}
}
}