fix: remove duplicate extension code (#322)

* fix: remove duplicate extension code

* fix: allow Material You icons to load

* fix: remove unused preference item

* fix: load mono on square setups
This commit is contained in:
TwistedUmbrellaX 2024-04-05 17:50:40 -04:00 committed by GitHub
parent 72c69e7c79
commit f6c7b09d9b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 149 additions and 321 deletions

View file

@ -30,7 +30,6 @@ enum class PrefName(val data: Pref) { //TODO: Split this into multiple files
), ),
AnimeExtensionRepos(Pref(Location.General, Set::class, setOf<String>())), AnimeExtensionRepos(Pref(Location.General, Set::class, setOf<String>())),
MangaExtensionRepos(Pref(Location.General, Set::class, setOf<String>())), MangaExtensionRepos(Pref(Location.General, Set::class, setOf<String>())),
SharedRepositories(Pref(Location.General, Boolean::class, false)),
AnimeSourcesOrder(Pref(Location.General, List::class, listOf<String>())), AnimeSourcesOrder(Pref(Location.General, List::class, listOf<String>())),
AnimeSearchHistory(Pref(Location.General, Set::class, setOf<String>())), AnimeSearchHistory(Pref(Location.General, Set::class, setOf<String>())),
MangaSourcesOrder(Pref(Location.General, List::class, listOf<String>())), MangaSourcesOrder(Pref(Location.General, List::class, listOf<String>())),

View file

@ -6,10 +6,10 @@ import ani.dantotsu.snackString
import ani.dantotsu.util.Logger import ani.dantotsu.util.Logger
import eu.kanade.domain.source.service.SourcePreferences import eu.kanade.domain.source.service.SourcePreferences
import eu.kanade.tachiyomi.extension.InstallStep import eu.kanade.tachiyomi.extension.InstallStep
import eu.kanade.tachiyomi.extension.anime.api.AnimeExtensionGithubApi
import eu.kanade.tachiyomi.extension.anime.model.AnimeExtension import eu.kanade.tachiyomi.extension.anime.model.AnimeExtension
import eu.kanade.tachiyomi.extension.anime.model.AnimeLoadResult import eu.kanade.tachiyomi.extension.anime.model.AnimeLoadResult
import eu.kanade.tachiyomi.extension.anime.model.AvailableAnimeSources import eu.kanade.tachiyomi.extension.anime.model.AvailableAnimeSources
import eu.kanade.tachiyomi.extension.api.ExtensionGithubApi
import eu.kanade.tachiyomi.extension.util.ExtensionInstallReceiver import eu.kanade.tachiyomi.extension.util.ExtensionInstallReceiver
import eu.kanade.tachiyomi.extension.util.ExtensionInstaller import eu.kanade.tachiyomi.extension.util.ExtensionInstaller
import eu.kanade.tachiyomi.extension.util.ExtensionLoader import eu.kanade.tachiyomi.extension.util.ExtensionLoader
@ -47,7 +47,7 @@ class AnimeExtensionManager(
/** /**
* API where all the available anime extensions can be found. * API where all the available anime extensions can be found.
*/ */
private val api = AnimeExtensionGithubApi() private val api = ExtensionGithubApi()
/** /**
* The installer which installs, updates and uninstalls the anime extensions. * The installer which installs, updates and uninstalls the anime extensions.
@ -118,7 +118,7 @@ class AnimeExtensionManager(
*/ */
suspend fun findAvailableExtensions() { suspend fun findAvailableExtensions() {
val extensions: List<AnimeExtension.Available> = try { val extensions: List<AnimeExtension.Available> = try {
api.findExtensions() api.findAnimeExtensions()
} catch (e: Exception) { } catch (e: Exception) {
Logger.log(e) Logger.log(e)
withUIContext { snackString("Failed to get extensions list") } withUIContext { snackString("Failed to get extensions list") }
@ -206,7 +206,7 @@ class AnimeExtensionManager(
* @param extension The anime extension to be installed. * @param extension The anime extension to be installed.
*/ */
fun installExtension(extension: AnimeExtension.Available): Observable<InstallStep> { fun installExtension(extension: AnimeExtension.Available): Observable<InstallStep> {
return installer.downloadAndInstall(api.getApkUrl(extension), extension) return installer.downloadAndInstall(api.getAnimeApkUrl(extension), extension)
} }
/** /**

View file

@ -1,214 +0,0 @@
package eu.kanade.tachiyomi.extension.anime.api
import android.content.Context
import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName
import ani.dantotsu.util.Logger
import eu.kanade.tachiyomi.extension.ExtensionUpdateNotifier
import eu.kanade.tachiyomi.extension.anime.AnimeExtensionManager
import eu.kanade.tachiyomi.extension.anime.model.AnimeExtension
import eu.kanade.tachiyomi.extension.anime.model.AnimeLoadResult
import eu.kanade.tachiyomi.extension.anime.model.AvailableAnimeSources
import eu.kanade.tachiyomi.extension.util.ExtensionLoader
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.NetworkHelper
import eu.kanade.tachiyomi.network.awaitSuccess
import eu.kanade.tachiyomi.network.parseAs
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import tachiyomi.core.preference.Preference
import tachiyomi.core.preference.PreferenceStore
import tachiyomi.core.util.lang.withIOContext
import uy.kohesive.injekt.injectLazy
import java.util.Date
import kotlin.time.Duration.Companion.days
internal class AnimeExtensionGithubApi {
private val networkService: NetworkHelper by injectLazy()
private val preferenceStore: PreferenceStore by injectLazy()
private val animeExtensionManager: AnimeExtensionManager by injectLazy()
private val json: Json by injectLazy()
private val lastExtCheck: Preference<Long> by lazy {
preferenceStore.getLong("last_ext_check", 0)
}
suspend fun findExtensions(): List<AnimeExtension.Available> {
return withIOContext {
val extensions: ArrayList<AnimeExtension.Available> = arrayListOf()
val repos =
PrefManager.getVal<Set<String>>(PrefName.AnimeExtensionRepos).toMutableList()
if (repos.isEmpty()) {
repos.add("https://raw.githubusercontent.com/aniyomiorg/aniyomi-extensions/repo")
PrefManager.setVal(PrefName.AnimeExtensionRepos, repos.toSet())
}
repos.forEach {
try {
val githubResponse = try {
networkService.client
.newCall(GET("${it}/index.min.json"))
.awaitSuccess()
} catch (e: Throwable) {
Logger.log("Failed to get repo: $it")
Logger.log(e)
null
}
val response = githubResponse ?: run {
networkService.client
.newCall(GET(fallbackRepoUrl(it) + "/index.min.json"))
.awaitSuccess()
}
val repoExtensions = with(json) {
response
.parseAs<List<AnimeExtensionJsonObject>>()
.toExtensions(it)
}
// Sanity check - a small number of extensions probably means something broke
// with the repo generator
if (repoExtensions.size < 10) {
throw Exception()
}
extensions.addAll(repoExtensions)
} catch (e: Throwable) {
Logger.log("Failed to get extensions from GitHub")
Logger.log(e)
}
}
extensions
}
}
suspend fun checkForUpdates(
context: Context,
fromAvailableExtensionList: Boolean = false
): List<AnimeExtension.Installed>? {
// Limit checks to once a day at most
if (fromAvailableExtensionList && Date().time < lastExtCheck.get() + 1.days.inWholeMilliseconds) {
return null
}
val extensions = if (fromAvailableExtensionList) {
animeExtensionManager.availableExtensionsFlow.value
} else {
findExtensions().also { lastExtCheck.set(Date().time) }
}
val installedExtensions = ExtensionLoader.loadAnimeExtensions(context)
.filterIsInstance<AnimeLoadResult.Success>()
.map { it.extension }
val extensionsWithUpdate = mutableListOf<AnimeExtension.Installed>()
for (installedExt in installedExtensions) {
val pkgName = installedExt.pkgName
val availableExt = extensions.find { it.pkgName == pkgName } ?: continue
val hasUpdatedVer = availableExt.versionCode > installedExt.versionCode
val hasUpdatedLib = availableExt.libVersion > installedExt.libVersion
val hasUpdate = installedExt.isUnofficial.not() && (hasUpdatedVer || hasUpdatedLib)
if (hasUpdate) {
extensionsWithUpdate.add(installedExt)
}
}
if (extensionsWithUpdate.isNotEmpty()) {
ExtensionUpdateNotifier(context).promptUpdates(extensionsWithUpdate.map { it.name })
}
return extensionsWithUpdate
}
private fun List<AnimeExtensionJsonObject>.toExtensions(repository: String): List<AnimeExtension.Available> {
return this
.filter {
val libVersion = it.extractLibVersion()
libVersion >= ExtensionLoader.ANIME_LIB_VERSION_MIN && libVersion <= ExtensionLoader.ANIME_LIB_VERSION_MAX
}
.map {
AnimeExtension.Available(
name = it.name.substringAfter("Aniyomi: "),
pkgName = it.pkg,
versionName = it.version,
versionCode = it.code,
libVersion = it.extractLibVersion(),
lang = it.lang,
isNsfw = it.nsfw == 1,
hasReadme = it.hasReadme == 1,
hasChangelog = it.hasChangelog == 1,
sources = it.sources?.toAnimeExtensionSources().orEmpty(),
apkName = it.apk,
repository = repository,
iconUrl = "${repository}/icon/${it.pkg}.png",
)
}
}
private fun List<AnimeExtensionSourceJsonObject>.toAnimeExtensionSources(): List<AvailableAnimeSources> {
return this.map {
AvailableAnimeSources(
id = it.id,
lang = it.lang,
name = it.name,
baseUrl = it.baseUrl,
)
}
}
fun getApkUrl(extension: AnimeExtension.Available): String {
return "${extension.repository}/apk/${extension.apkName}"
}
private fun fallbackRepoUrl(repoUrl: String): String? {
var fallbackRepoUrl = "https://gcore.jsdelivr.net/gh/"
val strippedRepoUrl =
repoUrl.removePrefix("https://").removePrefix("http://").removeSuffix("/")
val repoUrlParts = strippedRepoUrl.split("/")
if (repoUrlParts.size < 3) {
return null
}
val repoOwner = repoUrlParts[1]
val repoName = repoUrlParts[2]
fallbackRepoUrl += "$repoOwner/$repoName"
val repoBranch = if (repoUrlParts.size > 3) {
repoUrlParts[3]
} else {
"main"
}
fallbackRepoUrl += "@$repoBranch"
return fallbackRepoUrl
}
}
private fun AnimeExtensionJsonObject.extractLibVersion(): Double {
return version.substringBeforeLast('.').toDouble()
}
@Serializable
private data class AnimeExtensionJsonObject(
val name: String,
val pkg: String,
val apk: String,
val lang: String,
val code: Long,
val version: String,
val nsfw: Int,
val hasReadme: Int = 0,
val hasChangelog: Int = 0,
val sources: List<AnimeExtensionSourceJsonObject>?,
)
@Serializable
private data class AnimeExtensionSourceJsonObject(
val id: Long,
val lang: String,
val name: String,
val baseUrl: String,
)

View file

@ -1,14 +1,12 @@
package eu.kanade.tachiyomi.extension.manga.api package eu.kanade.tachiyomi.extension.api
import android.content.Context
import ani.dantotsu.settings.saving.PrefManager import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.settings.saving.PrefName
import ani.dantotsu.util.Logger import ani.dantotsu.util.Logger
import eu.kanade.tachiyomi.extension.ExtensionUpdateNotifier import eu.kanade.tachiyomi.extension.anime.model.AnimeExtension
import eu.kanade.tachiyomi.extension.manga.MangaExtensionManager import eu.kanade.tachiyomi.extension.anime.model.AvailableAnimeSources
import eu.kanade.tachiyomi.extension.manga.model.AvailableMangaSources import eu.kanade.tachiyomi.extension.manga.model.AvailableMangaSources
import eu.kanade.tachiyomi.extension.manga.model.MangaExtension import eu.kanade.tachiyomi.extension.manga.model.MangaExtension
import eu.kanade.tachiyomi.extension.manga.model.MangaLoadResult
import eu.kanade.tachiyomi.extension.util.ExtensionLoader import eu.kanade.tachiyomi.extension.util.ExtensionLoader
import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.NetworkHelper import eu.kanade.tachiyomi.network.NetworkHelper
@ -16,25 +14,143 @@ import eu.kanade.tachiyomi.network.awaitSuccess
import eu.kanade.tachiyomi.network.parseAs import eu.kanade.tachiyomi.network.parseAs
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import tachiyomi.core.preference.Preference
import tachiyomi.core.preference.PreferenceStore
import tachiyomi.core.util.lang.withIOContext import tachiyomi.core.util.lang.withIOContext
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import java.util.Date
import kotlin.time.Duration.Companion.days
internal class MangaExtensionGithubApi {
internal class ExtensionGithubApi {
private val networkService: NetworkHelper by injectLazy() private val networkService: NetworkHelper by injectLazy()
private val preferenceStore: PreferenceStore by injectLazy()
private val extensionManager: MangaExtensionManager by injectLazy()
private val json: Json by injectLazy() private val json: Json by injectLazy()
private val lastExtCheck: Preference<Long> by lazy { private fun List<ExtensionSourceJsonObject>.toAnimeExtensionSources(): List<AvailableAnimeSources> {
preferenceStore.getLong("last_ext_check", 0) return this.map {
AvailableAnimeSources(
id = it.id,
lang = it.lang,
name = it.name,
baseUrl = it.baseUrl,
)
}
} }
suspend fun findExtensions(): List<MangaExtension.Available> { private fun List<ExtensionJsonObject>.toAnimeExtensions(repository: String): List<AnimeExtension.Available> {
return this
.filter {
val libVersion = it.extractLibVersion()
libVersion >= ExtensionLoader.ANIME_LIB_VERSION_MIN && libVersion <= ExtensionLoader.ANIME_LIB_VERSION_MAX
}
.map {
AnimeExtension.Available(
name = it.name.substringAfter("Aniyomi: "),
pkgName = it.pkg,
versionName = it.version,
versionCode = it.code,
libVersion = it.extractLibVersion(),
lang = it.lang,
isNsfw = it.nsfw == 1,
hasReadme = it.hasReadme == 1,
hasChangelog = it.hasChangelog == 1,
sources = it.sources?.toAnimeExtensionSources().orEmpty(),
apkName = it.apk,
repository = repository,
iconUrl = "${repository}/icon/${it.pkg}.png",
)
}
}
suspend fun findAnimeExtensions(): List<AnimeExtension.Available> {
return withIOContext {
val extensions: ArrayList<AnimeExtension.Available> = arrayListOf()
val repos =
PrefManager.getVal<Set<String>>(PrefName.MangaExtensionRepos).toMutableList()
if (repos.isEmpty()) {
repos.add("https://raw.githubusercontent.com/keiyoushi/extensions/main")
PrefManager.setVal(PrefName.MangaExtensionRepos, repos.toSet())
}
repos.forEach {
try {
val githubResponse = try {
networkService.client
.newCall(GET("${it}/index.min.json"))
.awaitSuccess()
} catch (e: Throwable) {
Logger.log("Failed to get repo: $it")
Logger.log(e)
null
}
val response = githubResponse ?: run {
networkService.client
.newCall(GET(fallbackRepoUrl(it) + "/index.min.json"))
.awaitSuccess()
}
val repoExtensions = with(json) {
response
.parseAs<List<ExtensionJsonObject>>()
.toAnimeExtensions(it)
}
// Sanity check - a small number of extensions probably means something broke
// with the repo generator
if (repoExtensions.size < 10) {
throw Exception()
}
extensions.addAll(repoExtensions)
} catch (e: Throwable) {
Logger.log("Failed to get extensions from GitHub")
Logger.log(e)
}
}
extensions
}
}
fun getAnimeApkUrl(extension: AnimeExtension.Available): String {
return "${extension.repository}/apk/${extension.apkName}"
}
private fun List<ExtensionSourceJsonObject>.toMangaExtensionSources(): List<AvailableMangaSources> {
return this.map {
AvailableMangaSources(
id = it.id,
lang = it.lang,
name = it.name,
baseUrl = it.baseUrl,
)
}
}
private fun List<ExtensionJsonObject>.toMangaExtensions(repository: String): List<MangaExtension.Available> {
return this
.filter {
val libVersion = it.extractLibVersion()
libVersion >= ExtensionLoader.MANGA_LIB_VERSION_MIN && libVersion <= ExtensionLoader.MANGA_LIB_VERSION_MAX
}
.map {
MangaExtension.Available(
name = it.name.substringAfter("Tachiyomi: "),
pkgName = it.pkg,
versionName = it.version,
versionCode = it.code,
libVersion = it.extractLibVersion(),
lang = it.lang,
isNsfw = it.nsfw == 1,
hasReadme = it.hasReadme == 1,
hasChangelog = it.hasChangelog == 1,
sources = it.sources?.toMangaExtensionSources().orEmpty(),
apkName = it.apk,
repository = repository,
iconUrl = "${repository}/icon/${it.pkg}.png",
)
}
}
suspend fun findMangaExtensions(): List<MangaExtension.Available> {
return withIOContext { return withIOContext {
val extensions: ArrayList<MangaExtension.Available> = arrayListOf() val extensions: ArrayList<MangaExtension.Available> = arrayListOf()
@ -67,7 +183,7 @@ internal class MangaExtensionGithubApi {
val repoExtensions = with(json) { val repoExtensions = with(json) {
response response
.parseAs<List<ExtensionJsonObject>>() .parseAs<List<ExtensionJsonObject>>()
.toExtensions(it) .toMangaExtensions(it)
} }
// Sanity check - a small number of extensions probably means something broke // Sanity check - a small number of extensions probably means something broke
@ -87,88 +203,10 @@ internal class MangaExtensionGithubApi {
} }
} }
suspend fun checkForUpdates( fun getMangaApkUrl(extension: MangaExtension.Available): String {
context: Context,
fromAvailableExtensionList: Boolean = false
): List<MangaExtension.Installed>? {
// Limit checks to once a day at most
if (fromAvailableExtensionList && Date().time < lastExtCheck.get() + 1.days.inWholeMilliseconds) {
return null
}
val extensions = if (fromAvailableExtensionList) {
extensionManager.availableExtensionsFlow.value
} else {
findExtensions().also { lastExtCheck.set(Date().time) }
}
val installedExtensions = ExtensionLoader.loadMangaExtensions(context)
.filterIsInstance<MangaLoadResult.Success>()
.map { it.extension }
val extensionsWithUpdate = mutableListOf<MangaExtension.Installed>()
for (installedExt in installedExtensions) {
val pkgName = installedExt.pkgName
val availableExt = extensions.find { it.pkgName == pkgName } ?: continue
val hasUpdatedVer = availableExt.versionCode > installedExt.versionCode
val hasUpdatedLib = availableExt.libVersion > installedExt.libVersion
val hasUpdate = installedExt.isUnofficial.not() && (hasUpdatedVer || hasUpdatedLib)
if (hasUpdate) {
extensionsWithUpdate.add(installedExt)
}
}
if (extensionsWithUpdate.isNotEmpty()) {
ExtensionUpdateNotifier(context).promptUpdates(extensionsWithUpdate.map { it.name })
}
return extensionsWithUpdate
}
private fun List<ExtensionJsonObject>.toExtensions(repository: String): List<MangaExtension.Available> {
return this
.filter {
val libVersion = it.extractLibVersion()
libVersion >= ExtensionLoader.MANGA_LIB_VERSION_MIN && libVersion <= ExtensionLoader.MANGA_LIB_VERSION_MAX
}
.map {
MangaExtension.Available(
name = it.name.substringAfter("Tachiyomi: "),
pkgName = it.pkg,
versionName = it.version,
versionCode = it.code,
libVersion = it.extractLibVersion(),
lang = it.lang,
isNsfw = it.nsfw == 1,
hasReadme = it.hasReadme == 1,
hasChangelog = it.hasChangelog == 1,
sources = it.sources?.toExtensionSources().orEmpty(),
apkName = it.apk,
repository = repository,
iconUrl = "${repository}/icon/${it.pkg}.png",
)
}
}
private fun List<ExtensionSourceJsonObject>.toExtensionSources(): List<AvailableMangaSources> {
return this.map {
AvailableMangaSources(
id = it.id,
lang = it.lang,
name = it.name,
baseUrl = it.baseUrl,
)
}
}
fun getApkUrl(extension: MangaExtension.Available): String {
return "${extension.repository}/apk/${extension.apkName}" return "${extension.repository}/apk/${extension.apkName}"
} }
private fun ExtensionJsonObject.extractLibVersion(): Double {
return version.substringBeforeLast('.').toDouble()
}
private fun fallbackRepoUrl(repoUrl: String): String? { private fun fallbackRepoUrl(repoUrl: String): String? {
var fallbackRepoUrl = "https://gcore.jsdelivr.net/gh/" var fallbackRepoUrl = "https://gcore.jsdelivr.net/gh/"
val strippedRepoUrl = val strippedRepoUrl =
@ -211,3 +249,7 @@ private data class ExtensionSourceJsonObject(
val name: String, val name: String,
val baseUrl: String, val baseUrl: String,
) )
private fun ExtensionJsonObject.extractLibVersion(): Double {
return version.substringBeforeLast('.').toDouble()
}

View file

@ -6,7 +6,7 @@ import ani.dantotsu.snackString
import ani.dantotsu.util.Logger import ani.dantotsu.util.Logger
import eu.kanade.domain.source.service.SourcePreferences import eu.kanade.domain.source.service.SourcePreferences
import eu.kanade.tachiyomi.extension.InstallStep import eu.kanade.tachiyomi.extension.InstallStep
import eu.kanade.tachiyomi.extension.manga.api.MangaExtensionGithubApi import eu.kanade.tachiyomi.extension.api.ExtensionGithubApi
import eu.kanade.tachiyomi.extension.manga.model.AvailableMangaSources import eu.kanade.tachiyomi.extension.manga.model.AvailableMangaSources
import eu.kanade.tachiyomi.extension.manga.model.MangaExtension import eu.kanade.tachiyomi.extension.manga.model.MangaExtension
import eu.kanade.tachiyomi.extension.manga.model.MangaLoadResult import eu.kanade.tachiyomi.extension.manga.model.MangaLoadResult
@ -47,7 +47,7 @@ class MangaExtensionManager(
/** /**
* API where all the available extensions can be found. * API where all the available extensions can be found.
*/ */
private val api = MangaExtensionGithubApi() private val api = ExtensionGithubApi()
/** /**
* The installer which installs, updates and uninstalls the extensions. * The installer which installs, updates and uninstalls the extensions.
@ -115,7 +115,7 @@ class MangaExtensionManager(
*/ */
suspend fun findAvailableExtensions() { suspend fun findAvailableExtensions() {
val extensions: List<MangaExtension.Available> = try { val extensions: List<MangaExtension.Available> = try {
api.findExtensions() api.findMangaExtensions()
} catch (e: Exception) { } catch (e: Exception) {
Logger.log(e) Logger.log(e)
withUIContext { snackString("Failed to get manga extensions") } withUIContext { snackString("Failed to get manga extensions") }
@ -203,7 +203,7 @@ class MangaExtensionManager(
* @param extension The extension to be installed. * @param extension The extension to be installed.
*/ */
fun installExtension(extension: MangaExtension.Available): Observable<InstallStep> { fun installExtension(extension: MangaExtension.Available): Observable<InstallStep> {
return installer.downloadAndInstall(api.getApkUrl(extension), extension) return installer.downloadAndInstall(api.getMangaApkUrl(extension), extension)
} }
/** /**

View file

@ -1,6 +1,6 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" <vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="768dp" android:width="200dp"
android:height="768dp" android:height="200dp"
android:viewportWidth="768" android:viewportWidth="768"
android:viewportHeight="768"> android:viewportHeight="768">
<group> <group>

View file

@ -2,4 +2,5 @@
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_alpha_background" /> <background android:drawable="@drawable/ic_launcher_alpha_background" />
<foreground android:drawable="@drawable/ic_launcher_alpha_foreground" /> <foreground android:drawable="@drawable/ic_launcher_alpha_foreground" />
<monochrome android:drawable="@drawable/mono" />
</adaptive-icon> </adaptive-icon>