|
|
|
@file:Suppress("NAME_SHADOWING", "MemberVisibilityCanBePrivate")
|
|
|
|
|
|
|
|
package com.fox2code.mmm.androidacy
|
|
|
|
|
|
|
|
import android.annotation.SuppressLint
|
|
|
|
import android.content.DialogInterface
|
|
|
|
import android.graphics.Color
|
|
|
|
import android.net.Uri
|
|
|
|
import android.os.Build
|
|
|
|
import android.util.TypedValue
|
|
|
|
import android.webkit.JavascriptInterface
|
|
|
|
import android.widget.Toast
|
|
|
|
import androidx.annotation.Keep
|
|
|
|
import androidx.core.content.ContextCompat
|
|
|
|
import com.fox2code.foxcompat.view.FoxDisplay
|
|
|
|
import com.fox2code.mmm.BuildConfig
|
|
|
|
import com.fox2code.mmm.MainApplication
|
|
|
|
import com.fox2code.mmm.R
|
|
|
|
import com.fox2code.mmm.androidacy.AndroidacyUtil.Companion.getModuleId
|
|
|
|
import com.fox2code.mmm.androidacy.AndroidacyUtil.Companion.getModuleTitle
|
|
|
|
import com.fox2code.mmm.androidacy.AndroidacyUtil.Companion.hideToken
|
|
|
|
import com.fox2code.mmm.androidacy.AndroidacyUtil.Companion.isAndroidacyFileUrl
|
|
|
|
import com.fox2code.mmm.androidacy.AndroidacyUtil.Companion.isAndroidacyLink
|
|
|
|
import com.fox2code.mmm.installer.InstallerInitializer.Companion.peekMagiskPath
|
|
|
|
import com.fox2code.mmm.installer.InstallerInitializer.Companion.peekMagiskVersion
|
|
|
|
import com.fox2code.mmm.manager.ModuleInfo
|
|
|
|
import com.fox2code.mmm.manager.ModuleManager.Companion.instance
|
|
|
|
import com.fox2code.mmm.utils.ExternalHelper
|
|
|
|
import com.fox2code.mmm.utils.IntentHelper.Companion.openCustomTab
|
|
|
|
import com.fox2code.mmm.utils.IntentHelper.Companion.openInstaller
|
|
|
|
import com.fox2code.mmm.utils.IntentHelper.Companion.openUrl
|
|
|
|
import com.fox2code.mmm.utils.io.Files.Companion.readSU
|
|
|
|
import com.fox2code.mmm.utils.io.Files.Companion.writeSU
|
|
|
|
import com.fox2code.mmm.utils.io.Hashes.Companion.checkSumFormat
|
|
|
|
import com.fox2code.mmm.utils.io.Hashes.Companion.checkSumName
|
|
|
|
import com.fox2code.mmm.utils.io.Hashes.Companion.checkSumValid
|
|
|
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
|
|
|
import timber.log.Timber
|
|
|
|
import java.io.File
|
|
|
|
import java.io.IOException
|
|
|
|
import java.nio.charset.StandardCharsets
|
|
|
|
import java.util.Objects
|
|
|
|
|
|
|
|
@Keep
|
|
|
|
class AndroidacyWebAPI(
|
|
|
|
private val activity: AndroidacyActivity,
|
|
|
|
private val allowInstall: Boolean
|
|
|
|
) {
|
|
|
|
var consumedAction = false
|
|
|
|
var downloadMode = false
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Allow Androidacy backend to notify compat mode
|
|
|
|
* return current effective compat mode
|
|
|
|
*/
|
|
|
|
@get:JavascriptInterface
|
|
|
|
var effectiveCompatMode = 0
|
|
|
|
var notifiedCompatMode = 0
|
|
|
|
fun forceQuitRaw(error: String?) {
|
|
|
|
Toast.makeText(activity, error, Toast.LENGTH_LONG).show()
|
|
|
|
activity.runOnUiThread { activity.forceBackPressed() }
|
|
|
|
activity.backOnResume = true // Set backOnResume just in case
|
|
|
|
downloadMode = false
|
|
|
|
}
|
|
|
|
|
|
|
|
fun openNativeModuleDialogRaw(
|
|
|
|
moduleUrl: String?,
|
|
|
|
moduleId: String?,
|
|
|
|
installTitle: String?,
|
|
|
|
checksum: String?,
|
|
|
|
canInstall: Boolean
|
|
|
|
) {
|
|
|
|
if (BuildConfig.DEBUG) Timber.d(
|
|
|
|
"ModuleDialog, downloadUrl: " + hideToken(
|
|
|
|
moduleUrl!!
|
|
|
|
) + ", moduleId: " + moduleId + ", installTitle: " + installTitle + ", checksum: " + checksum + ", canInstall: " + canInstall
|
|
|
|
)
|
|
|
|
// moduleUrl should be a valid URL, i.e. in the androidacy.com domain
|
|
|
|
// if it is not, do not proceed
|
|
|
|
if (!isAndroidacyFileUrl(moduleUrl)) {
|
|
|
|
Timber.e("ModuleDialog, invalid URL: %s", moduleUrl)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
downloadMode = false
|
|
|
|
val repoModule = AndroidacyRepoData.getInstance().moduleHashMap[installTitle]
|
|
|
|
val title: String?
|
|
|
|
var description: String?
|
|
|
|
var mmtReborn = false
|
|
|
|
if (repoModule != null) {
|
|
|
|
title = repoModule.moduleInfo.name
|
|
|
|
description = repoModule.moduleInfo.description
|
|
|
|
mmtReborn = repoModule.moduleInfo.mmtReborn
|
|
|
|
if (description.isNullOrEmpty()) {
|
|
|
|
description = activity.getString(R.string.no_desc_found)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// URL Decode installTitle
|
|
|
|
title = installTitle
|
|
|
|
val checkSumType = checkSumName(checksum)
|
|
|
|
description = if (checkSumType == null) {
|
|
|
|
"Checksum: ${if (checksum.isNullOrEmpty()) "null" else checksum}"
|
|
|
|
} else {
|
|
|
|
"$checkSumType: $checksum"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
val builder = MaterialAlertDialogBuilder(activity)
|
|
|
|
builder.setTitle(title).setMessage(description).setCancelable(true)
|
|
|
|
.setIcon(R.drawable.ic_baseline_extension_24)
|
|
|
|
builder.setNegativeButton(R.string.download_module) { _: DialogInterface?, _: Int ->
|
|
|
|
downloadMode = true
|
|
|
|
openCustomTab(activity, moduleUrl)
|
|
|
|
}
|
|
|
|
if (canInstall) {
|
|
|
|
var hasUpdate = false
|
|
|
|
var config: String? = null
|
|
|
|
if (repoModule != null) {
|
|
|
|
config = repoModule.moduleInfo.config
|
|
|
|
val localModuleInfo = instance?.modules?.get(repoModule.id)
|
|
|
|
hasUpdate =
|
|
|
|
localModuleInfo != null && repoModule.moduleInfo.versionCode > localModuleInfo.versionCode
|
|
|
|
}
|
|
|
|
val fConfig = config
|
|
|
|
val fMMTReborn = mmtReborn
|
|
|
|
builder.setPositiveButton(if (hasUpdate) R.string.update_module else R.string.install_module) { _: DialogInterface?, _: Int ->
|
|
|
|
openInstaller(
|
|
|
|
activity,
|
|
|
|
moduleUrl,
|
|
|
|
title,
|
|
|
|
fConfig,
|
|
|
|
checksum,
|
|
|
|
fMMTReborn
|
|
|
|
)
|
|
|
|
// close activity
|
|
|
|
activity.runOnUiThread { activity.finishAndRemoveTask() }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
builder.setOnCancelListener { _: DialogInterface? ->
|
|
|
|
if (!activity.backOnResume) consumedAction = false
|
|
|
|
}
|
|
|
|
ExternalHelper.INSTANCE.injectButton(builder, {
|
|
|
|
downloadMode = true
|
|
|
|
try {
|
|
|
|
return@injectButton activity.downloadFileAsync(moduleUrl)
|
|
|
|
} catch (e: IOException) {
|
|
|
|
Timber.e(e, "Failed to download module")
|
|
|
|
activity.runOnUiThread {
|
|
|
|
Toast.makeText(
|
|
|
|
activity,
|
|
|
|
R.string.failed_download,
|
|
|
|
Toast.LENGTH_SHORT
|
|
|
|
).show()
|
|
|
|
}
|
|
|
|
return@injectButton null
|
|
|
|
}
|
|
|
|
}, "androidacy_repo")
|
|
|
|
val dim5dp = FoxDisplay.dpToPixel(5f)
|
|
|
|
builder.setBackgroundInsetStart(dim5dp).setBackgroundInsetEnd(dim5dp)
|
|
|
|
activity.runOnUiThread {
|
|
|
|
val alertDialog = builder.show()
|
|
|
|
for (i in -3..-1) {
|
|
|
|
val alertButton = alertDialog.getButton(i)
|
|
|
|
if (alertButton != null && alertButton.paddingStart > dim5dp) {
|
|
|
|
alertButton.setPadding(dim5dp, dim5dp, dim5dp, dim5dp)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Suppress("NAME_SHADOWING")
|
|
|
|
fun notifyCompatModeRaw(value: Int) {
|
|
|
|
var value = value
|
|
|
|
if (consumedAction) return
|
|
|
|
if (BuildConfig.DEBUG) Timber.d("Androidacy Compat mode: %s", value)
|
|
|
|
notifiedCompatMode = value
|
|
|
|
if (value < 0) {
|
|
|
|
value = 0
|
|
|
|
} else if (value > MAX_COMPAT_MODE) {
|
|
|
|
value = MAX_COMPAT_MODE
|
|
|
|
}
|
|
|
|
effectiveCompatMode = value
|
|
|
|
}
|
|
|
|
|
|
|
|
@JavascriptInterface
|
|
|
|
fun forceQuit(error: String?) {
|
|
|
|
// Allow forceQuit and cancel in downloadMode
|
|
|
|
if (consumedAction && !downloadMode) return
|
|
|
|
consumedAction = true
|
|
|
|
forceQuitRaw(error)
|
|
|
|
}
|
|
|
|
|
|
|
|
@JavascriptInterface
|
|
|
|
fun cancel() {
|
|
|
|
// Allow forceQuit and cancel in downloadMode
|
|
|
|
if (consumedAction && !downloadMode) return
|
|
|
|
consumedAction = true
|
|
|
|
activity.runOnUiThread { activity.forceBackPressed() }
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Open an url always in an external page or browser.
|
|
|
|
*/
|
|
|
|
@JavascriptInterface
|
|
|
|
fun openUrl(url: String?) {
|
|
|
|
if (consumedAction) return
|
|
|
|
consumedAction = true
|
|
|
|
downloadMode = false
|
|
|
|
if (BuildConfig.DEBUG) Timber.d("Received openUrl request: %s", url)
|
|
|
|
if (Uri.parse(url).scheme == "https") {
|
|
|
|
openUrl(activity, url)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Open an url in a custom tab if possible.
|
|
|
|
*/
|
|
|
|
@JavascriptInterface
|
|
|
|
fun openCustomTab(url: String?) {
|
|
|
|
if (consumedAction) return
|
|
|
|
consumedAction = true
|
|
|
|
downloadMode = false
|
|
|
|
if (BuildConfig.DEBUG) Timber.d("Received openCustomTab request: %s", url)
|
|
|
|
if (Uri.parse(url).scheme == "https") {
|
|
|
|
openCustomTab(activity, url)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Return if current theme is a light theme.
|
|
|
|
*/
|
|
|
|
@get:JavascriptInterface
|
|
|
|
val isLightTheme: Boolean
|
|
|
|
get() = MainApplication.getINSTANCE().isLightTheme
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Check if the manager has received root access
|
|
|
|
* (Note: hasRoot only return true on Magisk rooted phones)
|
|
|
|
*/
|
|
|
|
@JavascriptInterface
|
|
|
|
fun hasRoot(): Boolean {
|
|
|
|
return peekMagiskPath() != null
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Check if the install API can be used
|
|
|
|
*/
|
|
|
|
@JavascriptInterface
|
|
|
|
fun canInstall(): Boolean {
|
|
|
|
// With lockdown mode enabled or lack of root, install should not have any effect
|
|
|
|
return allowInstall && hasRoot() && !MainApplication.isShowcaseMode()
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* install a module via url, with the file checked with the md5 checksum value.
|
|
|
|
*/
|
|
|
|
@JavascriptInterface
|
|
|
|
fun install(moduleUrl: String, installTitle: String?, checksum: String?) {
|
|
|
|
// If compat mode is 0, this means Androidacy didn't implemented a download mode yet
|
|
|
|
var installTitle = installTitle
|
|
|
|
var checksum = checksum
|
|
|
|
if (consumedAction || effectiveCompatMode >= 1 && !canInstall()) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
consumedAction = true
|
|
|
|
downloadMode = false
|
|
|
|
if (BuildConfig.DEBUG) Timber.d("Received install request: $moduleUrl $installTitle $checksum")
|
|
|
|
if (!isAndroidacyLink(moduleUrl)) {
|
|
|
|
forceQuitRaw("Non Androidacy module link used on Androidacy")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
checksum = checkSumFormat(checksum)
|
|
|
|
if (checksum.isNullOrEmpty()) {
|
|
|
|
Timber.w("Androidacy didn't provided a checksum!")
|
|
|
|
} else if (!checkSumValid(checksum)) {
|
|
|
|
forceQuitRaw("Androidacy didn't provided a valid checksum")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
// moduleId is the module parameter in the url
|
|
|
|
val moduleId = getModuleId(moduleUrl)
|
|
|
|
// Let's handle download mode ourself if not implemented
|
|
|
|
if (effectiveCompatMode < 1) {
|
|
|
|
if (!canInstall()) {
|
|
|
|
downloadMode = true
|
|
|
|
activity.runOnUiThread {
|
|
|
|
if (activity.webView != null) {
|
|
|
|
activity.webView!!.loadUrl(moduleUrl)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
openNativeModuleDialogRaw(moduleUrl, moduleId, installTitle, checksum, true)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
val repoModule = AndroidacyRepoData.getInstance().moduleHashMap[installTitle]
|
|
|
|
var config: String? = null
|
|
|
|
var mmtReborn = false
|
|
|
|
if (repoModule != null && Objects.requireNonNull<String?>(repoModule.moduleInfo.name).length >= 3) {
|
|
|
|
installTitle = repoModule.moduleInfo.name // Set title to module name
|
|
|
|
config = repoModule.moduleInfo.config
|
|
|
|
mmtReborn = repoModule.moduleInfo.mmtReborn
|
|
|
|
}
|
|
|
|
activity.backOnResume = true
|
|
|
|
openInstaller(activity, moduleUrl, installTitle, config, checksum, mmtReborn)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* install a module via url, with the file checked with the md5 checksum value.
|
|
|
|
*/
|
|
|
|
@JavascriptInterface
|
|
|
|
fun openNativeModuleDialog(moduleUrl: String?, moduleId: String?, checksum: String?) {
|
|
|
|
var checksum = checksum
|
|
|
|
if (consumedAction) return
|
|
|
|
consumedAction = true
|
|
|
|
downloadMode = false
|
|
|
|
if (!isAndroidacyLink(moduleUrl)) {
|
|
|
|
forceQuitRaw("Non Androidacy module link used on Androidacy")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
checksum = checkSumFormat(checksum)
|
|
|
|
if (checksum.isNullOrEmpty()) {
|
|
|
|
Timber.w("Androidacy WebView didn't provided a checksum!")
|
|
|
|
} else if (!checkSumValid(checksum)) {
|
|
|
|
forceQuitRaw("Androidacy didn't provided a valid checksum")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
// Get moduleTitle from url
|
|
|
|
val moduleTitle = getModuleTitle(moduleUrl!!)
|
|
|
|
openNativeModuleDialogRaw(moduleUrl, moduleId, moduleTitle, checksum, canInstall())
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Tell if the moduleId is installed on the device
|
|
|
|
*/
|
|
|
|
@JavascriptInterface
|
|
|
|
fun isModuleInstalled(moduleId: String?): Boolean {
|
|
|
|
return instance?.modules?.get(moduleId) != null
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Tell if the moduleId is updating and waiting a reboot to update
|
|
|
|
*/
|
|
|
|
@JavascriptInterface
|
|
|
|
fun isModuleUpdating(moduleId: String?): Boolean {
|
|
|
|
val localModuleInfo = instance?.modules?.get(moduleId)
|
|
|
|
return localModuleInfo != null && localModuleInfo.hasFlag(ModuleInfo.FLAG_MODULE_UPDATING)
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Return the module version name or null if not installed.
|
|
|
|
*/
|
|
|
|
@JavascriptInterface
|
|
|
|
fun getModuleVersion(moduleId: String?): String? {
|
|
|
|
val localModuleInfo = instance?.modules?.get(moduleId)
|
|
|
|
return localModuleInfo?.version
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Return the module version code or -1 if not installed.
|
|
|
|
*/
|
|
|
|
@JavascriptInterface
|
|
|
|
fun getModuleVersionCode(moduleId: String?): Long {
|
|
|
|
val localModuleInfo = instance?.modules?.get(moduleId)
|
|
|
|
return localModuleInfo?.versionCode ?: -1L
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Hide action bar if visible, the action bar is only visible by default on notes.
|
|
|
|
*/
|
|
|
|
@JavascriptInterface
|
|
|
|
fun hideActionBar() {
|
|
|
|
if (consumedAction) return
|
|
|
|
consumedAction = true
|
|
|
|
activity.runOnUiThread {
|
|
|
|
activity.hideActionBar()
|
|
|
|
consumedAction = false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Show action bar if not visible, the action bar is only visible by default on notes.
|
|
|
|
* Optional title param to set action bar title.
|
|
|
|
*/
|
|
|
|
@JavascriptInterface
|
|
|
|
fun showActionBar(title: String?) {
|
|
|
|
if (consumedAction) return
|
|
|
|
consumedAction = true
|
|
|
|
activity.runOnUiThread {
|
|
|
|
activity.showActionBar()
|
|
|
|
if (!title.isNullOrEmpty()) {
|
|
|
|
activity.title = title
|
|
|
|
}
|
|
|
|
consumedAction = false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Return true if the module is an Androidacy module.
|
|
|
|
*/
|
|
|
|
@JavascriptInterface
|
|
|
|
fun isAndroidacyModule(moduleId: String?): Boolean {
|
|
|
|
val localModuleInfo = instance?.modules?.get(moduleId)
|
|
|
|
return localModuleInfo != null && ("Androidacy" == localModuleInfo.author || isAndroidacyLink(
|
|
|
|
localModuleInfo.config
|
|
|
|
))
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* get a module file, return an empty string if not
|
|
|
|
* an Androidacy module or if file doesn't exists.
|
|
|
|
*/
|
|
|
|
@JavascriptInterface
|
|
|
|
fun getAndroidacyModuleFile(moduleId: String, moduleFile: String?): String {
|
|
|
|
var moduleId = moduleId
|
|
|
|
var moduleFile = moduleFile
|
|
|
|
moduleId = moduleId.replace("\\.".toRegex(), "").replace("/".toRegex(), "")
|
|
|
|
if (moduleFile == null || consumedAction || !isAndroidacyModule(moduleId)) return ""
|
|
|
|
moduleFile = moduleFile.replace("\\.".toRegex(), "").replace("/".toRegex(), "")
|
|
|
|
val moduleFolder = File("/data/adb/modules/$moduleId")
|
|
|
|
val absModuleFile = File(moduleFolder, moduleFile).absoluteFile
|
|
|
|
return if (!absModuleFile.path.startsWith(moduleFolder.path)) "" else try {
|
|
|
|
String(readSU(absModuleFile.absoluteFile), StandardCharsets.UTF_8)
|
|
|
|
} catch (e: IOException) {
|
|
|
|
""
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Create an ".androidacy" file with {@param content} as content
|
|
|
|
* Return true if action succeeded
|
|
|
|
*/
|
|
|
|
@JavascriptInterface
|
|
|
|
fun setAndroidacyModuleMeta(moduleId: String, content: String?): Boolean {
|
|
|
|
var moduleId = moduleId
|
|
|
|
moduleId = moduleId.replace("\\.".toRegex(), "").replace("/".toRegex(), "")
|
|
|
|
if (content == null || consumedAction || !isAndroidacyModule(moduleId)) return false
|
|
|
|
val androidacyMetaFile = File("/data/adb/modules/$moduleId/.androidacy")
|
|
|
|
return try {
|
|
|
|
writeSU(androidacyMetaFile, content.toByteArray(StandardCharsets.UTF_8))
|
|
|
|
true
|
|
|
|
} catch (e: IOException) {
|
|
|
|
false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Return current app version code
|
|
|
|
*/
|
|
|
|
@get:JavascriptInterface
|
|
|
|
val appVersionCode: Int
|
|
|
|
get() = BuildConfig.VERSION_CODE
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Return current app version name
|
|
|
|
*/
|
|
|
|
@get:JavascriptInterface
|
|
|
|
val appVersionName: String
|
|
|
|
get() = BuildConfig.VERSION_NAME
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Return current magisk version code or 0 if not applicable
|
|
|
|
*/
|
|
|
|
@get:JavascriptInterface
|
|
|
|
val magiskVersionCode: Int
|
|
|
|
get() = if (peekMagiskPath() == null) 0 else peekMagiskVersion()
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Return current android sdk-int version code, see:
|
|
|
|
* [right here](https://source.android.com/setup/start/build-numbers)
|
|
|
|
*/
|
|
|
|
@get:JavascriptInterface
|
|
|
|
val androidVersionCode: Int
|
|
|
|
get() = Build.VERSION.SDK_INT
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Return current navigation bar height or 0 if not visible
|
|
|
|
*/
|
|
|
|
@get:JavascriptInterface
|
|
|
|
val navigationBarHeight: Int
|
|
|
|
get() = activity.navigationBarHeight
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Return current theme accent color
|
|
|
|
*/
|
|
|
|
@get:JavascriptInterface
|
|
|
|
val accentColor: Int
|
|
|
|
get() {
|
|
|
|
val theme = activity.theme
|
|
|
|
val typedValue = TypedValue()
|
|
|
|
theme.resolveAttribute(androidx.appcompat.R.attr.colorPrimary, typedValue, true)
|
|
|
|
if (typedValue.type >= TypedValue.TYPE_FIRST_COLOR_INT && typedValue.type <= TypedValue.TYPE_LAST_COLOR_INT) {
|
|
|
|
return typedValue.data
|
|
|
|
}
|
|
|
|
theme.resolveAttribute(android.R.attr.colorAccent, typedValue, true)
|
|
|
|
return typedValue.data
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Return current theme foreground color
|
|
|
|
*/
|
|
|
|
@get:JavascriptInterface
|
|
|
|
val foregroundColor: Int
|
|
|
|
get() = if (activity.isLightTheme) Color.BLACK else Color.WHITE
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Return current theme background color
|
|
|
|
*/
|
|
|
|
@get:JavascriptInterface
|
|
|
|
val backgroundColor: Int
|
|
|
|
get() {
|
|
|
|
val theme = activity.theme
|
|
|
|
val typedValue = TypedValue()
|
|
|
|
theme.resolveAttribute(
|
|
|
|
com.google.android.material.R.attr.backgroundColor,
|
|
|
|
typedValue,
|
|
|
|
true
|
|
|
|
)
|
|
|
|
if (typedValue.type >= TypedValue.TYPE_FIRST_COLOR_INT && typedValue.type <= TypedValue.TYPE_LAST_COLOR_INT) {
|
|
|
|
return typedValue.data
|
|
|
|
}
|
|
|
|
theme.resolveAttribute(android.R.attr.background, typedValue, true)
|
|
|
|
return typedValue.data
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Return current hex string of monet theme
|
|
|
|
*/
|
|
|
|
@JavascriptInterface
|
|
|
|
fun getMonetColor(id: String): String {
|
|
|
|
@SuppressLint("DiscouragedApi") val nameResourceID = activity.resources.getIdentifier(
|
|
|
|
"@android:color/$id", "color", activity.applicationInfo.packageName
|
|
|
|
)
|
|
|
|
return if (nameResourceID == 0) {
|
|
|
|
throw IllegalArgumentException("No resource string found with name $id")
|
|
|
|
} else {
|
|
|
|
val color = ContextCompat.getColor(activity, nameResourceID)
|
|
|
|
val red = Color.red(color)
|
|
|
|
val blue = Color.blue(color)
|
|
|
|
val green = Color.green(color)
|
|
|
|
String.format("#%02x%02x%02x", red, green, blue)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@JavascriptInterface
|
|
|
|
fun setAndroidacyToken(token: String?) {
|
|
|
|
AndroidacyRepoData.getInstance().setToken(token)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Androidacy feature level declaration method
|
|
|
|
@JavascriptInterface
|
|
|
|
fun notifyCompatUnsupported() {
|
|
|
|
notifyCompatModeRaw(COMPAT_UNSUPPORTED)
|
|
|
|
}
|
|
|
|
|
|
|
|
@JavascriptInterface
|
|
|
|
fun notifyCompatDownloadButton() {
|
|
|
|
notifyCompatModeRaw(COMPAT_DOWNLOAD)
|
|
|
|
}
|
|
|
|
|
|
|
|
companion object {
|
|
|
|
const val COMPAT_UNSUPPORTED = 0
|
|
|
|
const val COMPAT_DOWNLOAD = 1
|
|
|
|
private const val MAX_COMPAT_MODE = 1
|
|
|
|
}
|
|
|
|
}
|