diff --git a/src/all/qtoon/AndroidManifest.xml b/src/all/qtoon/AndroidManifest.xml
new file mode 100644
index 000000000..d298433de
--- /dev/null
+++ b/src/all/qtoon/AndroidManifest.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/all/qtoon/build.gradle b/src/all/qtoon/build.gradle
new file mode 100644
index 000000000..9f87c368a
--- /dev/null
+++ b/src/all/qtoon/build.gradle
@@ -0,0 +1,8 @@
+ext {
+ extName = 'QToon'
+ extClass = '.QToonFactory'
+ extVersionCode = 1
+ isNsfw = true
+}
+
+apply from: "$rootDir/common.gradle"
diff --git a/src/all/qtoon/res/mipmap-hdpi/ic_launcher.png b/src/all/qtoon/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 000000000..6cbc2266b
Binary files /dev/null and b/src/all/qtoon/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/src/all/qtoon/res/mipmap-mdpi/ic_launcher.png b/src/all/qtoon/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 000000000..75d65d16a
Binary files /dev/null and b/src/all/qtoon/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/src/all/qtoon/res/mipmap-xhdpi/ic_launcher.png b/src/all/qtoon/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 000000000..7db118f24
Binary files /dev/null and b/src/all/qtoon/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/src/all/qtoon/res/mipmap-xxhdpi/ic_launcher.png b/src/all/qtoon/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 000000000..ab3b6eac8
Binary files /dev/null and b/src/all/qtoon/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/src/all/qtoon/res/mipmap-xxxhdpi/ic_launcher.png b/src/all/qtoon/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 000000000..298955737
Binary files /dev/null and b/src/all/qtoon/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/src/all/qtoon/src/eu/kanade/tachiyomi/extension/all/qtoon/Dto.kt b/src/all/qtoon/src/eu/kanade/tachiyomi/extension/all/qtoon/Dto.kt
new file mode 100644
index 000000000..6e811181b
--- /dev/null
+++ b/src/all/qtoon/src/eu/kanade/tachiyomi/extension/all/qtoon/Dto.kt
@@ -0,0 +1,125 @@
+package eu.kanade.tachiyomi.extension.all.qtoon
+
+import eu.kanade.tachiyomi.source.model.SManga
+import keiyoushi.utils.toJsonString
+import kotlinx.serialization.Serializable
+
+@Serializable
+class EncryptedResponse(
+ val ts: Long,
+ val data: String,
+)
+
+@Serializable
+class ComicsList(
+ val comics: List,
+ val more: Int,
+)
+
+@Serializable
+class ComicUrl(
+ val csid: String,
+ val webLinkId: String,
+)
+
+@Serializable
+class Image(
+ val thumb: Thumb,
+)
+
+@Serializable
+class Thumb(
+ val url: String,
+)
+
+@Serializable
+class ComicDetailsResponse(
+ val comic: Comic,
+)
+
+@Serializable
+class Comic(
+ val csid: String,
+ val webLinkId: String? = null,
+ val title: String,
+ val image: Image,
+ val tags: List,
+ val author: String? = null,
+ val serialStatus2: Int,
+ val updateMemo: String? = null,
+ val introduction: String,
+ val corners: Corner,
+) {
+ fun toSManga() = SManga.create().apply {
+ url = ComicUrl(csid, webLinkId.orEmpty()).toJsonString()
+ title = this@Comic.title
+ thumbnail_url = image.thumb.url
+ author = this@Comic.author
+ description = buildString {
+ append(introduction)
+ if (!updateMemo.isNullOrBlank()) {
+ append("\n\nUpdates: ", updateMemo)
+ }
+ }
+ genre = buildSet {
+ tags.mapTo(this) { it.name }
+ corners.cornerTags.mapTo(this) { it.name }
+ }.joinToString()
+ status = when (serialStatus2) {
+ 101 -> SManga.ONGOING
+ 103 -> SManga.COMPLETED
+ else -> SManga.UNKNOWN
+ }
+ initialized = true
+ }
+}
+
+@Serializable
+class Tag(
+ val name: String,
+)
+
+@Serializable
+class Corner(
+ val cornerTags: List,
+)
+
+@Serializable
+class ChapterEpisodes(
+ val episodes: List,
+)
+
+@Serializable
+class Episode(
+ val esid: String,
+ val title: String,
+ val serialNo: Int,
+)
+
+@Serializable
+class EpisodeUrl(
+ val esid: String,
+ val csid: String,
+)
+
+@Serializable
+class EpisodeResponse(
+ val definitions: List,
+)
+
+@Serializable
+class EpisodeDefinition(
+ val token: String,
+)
+
+@Serializable
+class EpisodeResources(
+ val resources: List,
+ val more: Int,
+)
+
+@Serializable
+class Resource(
+ val url: String,
+ val rgIdx: Int,
+)
diff --git a/src/all/qtoon/src/eu/kanade/tachiyomi/extension/all/qtoon/EncryptionUtils.kt b/src/all/qtoon/src/eu/kanade/tachiyomi/extension/all/qtoon/EncryptionUtils.kt
new file mode 100644
index 000000000..a3030dd0a
--- /dev/null
+++ b/src/all/qtoon/src/eu/kanade/tachiyomi/extension/all/qtoon/EncryptionUtils.kt
@@ -0,0 +1,71 @@
+package eu.kanade.tachiyomi.extension.all.qtoon
+
+import android.util.Base64
+import keiyoushi.utils.parseAs
+import okhttp3.Response
+import java.security.MessageDigest
+import javax.crypto.Cipher
+import javax.crypto.spec.IvParameterSpec
+import javax.crypto.spec.SecretKeySpec
+
+fun generateRandomString(length: Int): String {
+ val allowedChars = ('A'..'Z') + ('a'..'z') + ('2'..'8')
+ return (1..length)
+ .map { allowedChars.random() }
+ .joinToString("")
+}
+
+private fun md5(input: String): String {
+ val md = MessageDigest.getInstance("MD5")
+ val digest = md.digest(input.toByteArray(Charsets.UTF_8))
+ return digest.joinToString("") {
+ "%02x".format(it)
+ }
+}
+
+private fun aesDecrypt(data: String, key: ByteArray, iv: ByteArray): String {
+ val encryptedData = Base64.decode(data, Base64.DEFAULT)
+
+ val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding").apply {
+ val keySpec = SecretKeySpec(key, "AES")
+ val ivSpec = IvParameterSpec(iv)
+
+ init(Cipher.DECRYPT_MODE, keySpec, ivSpec)
+ }
+
+ val decryptedData = cipher.doFinal(encryptedData)
+
+ return String(decryptedData, Charsets.UTF_8)
+}
+
+fun decrypt(response: Response): String {
+ val res = response.parseAs()
+ val requestToken = response.request.header("did")!!
+
+ val inner = md5("$requestToken${res.ts}")
+ val outer = md5("${inner}OQlM9JBJgLWsgffb")
+
+ val key = outer.substring(0, 16).toByteArray(Charsets.UTF_8)
+ val iv = outer.substring(16, 32).toByteArray(Charsets.UTF_8)
+
+ return aesDecrypt(res.data, key, iv)
+}
+
+inline fun Response.decryptAs(): T {
+ return decrypt(this).parseAs()
+}
+
+fun decryptImageUrl(url: String, requestToken: String): String {
+ val inner = md5(requestToken)
+ val outer = md5("${inner}9tv86uBwmOYs7QZ0")
+
+ val key = outer.substring(0, 16).toByteArray(Charsets.UTF_8)
+ val iv = outer.substring(16, 32).toByteArray(Charsets.UTF_8)
+
+ return aesDecrypt(url, key, iv)
+}
+
+val mobileUserAgentRegex = Regex(
+ """android|avantgo|blackberry|iemobile|ipad|iphone|ipod|j2me|midp|mobile|opera mini|phone|palm|pocket|psp|symbian|up.browser|up.link|wap|windows ce|xda|xiino""",
+ RegexOption.IGNORE_CASE,
+)
diff --git a/src/all/qtoon/src/eu/kanade/tachiyomi/extension/all/qtoon/Filters.kt b/src/all/qtoon/src/eu/kanade/tachiyomi/extension/all/qtoon/Filters.kt
new file mode 100644
index 000000000..9451add3a
--- /dev/null
+++ b/src/all/qtoon/src/eu/kanade/tachiyomi/extension/all/qtoon/Filters.kt
@@ -0,0 +1,121 @@
+package eu.kanade.tachiyomi.extension.all.qtoon
+
+import eu.kanade.tachiyomi.source.model.Filter
+
+abstract class SelectFilter(
+ name: String,
+ private val options: List>,
+) : Filter.Select(
+ name,
+ options.map { it.first }.toTypedArray(),
+) {
+ val selected get() = options[state].second
+}
+
+class TagFilter : SelectFilter(
+ name = "Tags",
+ options = listOf(
+ "All" to "-1",
+ "Action" to "4",
+ "Adaptation" to "22",
+ "Adult" to "21",
+ "Adventure" to "38",
+ "Age Gap" to "41",
+ "BL" to "5",
+ "Bloody" to "53",
+ "Cheating/Infidelity" to "44",
+ "Childhood Friends" to "42",
+ "College life" to "29",
+ "Comedy" to "3",
+ "Crime" to "48",
+ "Doujinshi" to "43",
+ "Drama" to "6",
+ "Fantasy" to "2",
+ "GL" to "17",
+ "Harem" to "31",
+ "Hentai" to "34",
+ "Historical" to "16",
+ "Horror" to "12",
+ "Isekai" to "25",
+ "Josei(W)" to "23",
+ "Magic" to "28",
+ "Manga" to "26",
+ "Manhwa" to "19",
+ "Mature" to "18",
+ "Mystery" to "14",
+ "Office Workers" to "33",
+ "Omegaverse" to "35",
+ "Oneshot" to "50",
+ "Psychological" to "32",
+ "Reincarnation" to "30",
+ "Revenge" to "45",
+ "Reverse Harem" to "52",
+ "Romance" to "1",
+ "Royalty" to "49",
+ "School Life" to "27",
+ "Sci-fi" to "9",
+ "Seinen(M)" to "24",
+ "Shoujo" to "55",
+ "Shounen ai" to "36",
+ "Shounen(B)" to "40",
+ "Slice of life" to "10",
+ "Smut" to "20",
+ "Sports" to "15",
+ "Superhero" to "13",
+ "Supernatural" to "8",
+ "Thriller" to "7",
+ "Time Travel" to "39",
+ "Tragedy" to "56",
+ "Transmigration" to "51",
+ "Vampires" to "54",
+ "Villainess" to "46",
+ "Violence" to "37",
+ "Yakuzas" to "47",
+ ),
+)
+
+class GenderFilter : SelectFilter(
+ name = "Gender",
+ options = listOf(
+ "All" to "-1",
+ "Male" to "101",
+ "Female" to "103",
+ ),
+)
+
+class StatusFilter : SelectFilter(
+ name = "Status",
+ options = listOf(
+ "All" to "-1",
+ "Ongoing" to "101",
+ "Completed" to "103",
+ ),
+)
+
+class SortFilter : SelectFilter(
+ name = "Sort",
+ options = listOf(
+ "Hot" to "hot",
+ "New" to "new",
+ "Rate" to "rate",
+ ),
+)
+
+class HomePageFilter : SelectFilter(
+ name = "Home Page Section",
+ options = listOf(
+ "" to "",
+ "⨠Trending Updates â¨" to "as_l9zC15glGlkcS7yIamHQ",
+ "đĨĩ Hottest BL" to "as_8CgkZpYmgOr0aAYHsePs",
+ "â¤ī¸âđĨ Hot & Sweet Desire â¤ī¸âđĨ" to "as_DP6QM8o_pgvu4Q8uVNjt",
+ "đ Rebirth. Revenge. Reclaim. đĨ" to "as_16RPgJOVcNQ11N97pOe4B3",
+ "đ¯đĩ Manga Paradise âŠī¸" to "as_eF_lw9vKVUWpf0trKDk1",
+ "đĢ Campus Love, Teen Feels đ" to "as_RtRk4KegzUjsoEEUGWOK",
+ "đ Reborn in a Novel/Game đŽ" to "as_16IPE5so_KZ13zYzBRSf4O",
+ "âī¸ Level Up to a Top Hunter!" to "as_fQnbLm2ZSymVTHEWoxMf",
+ "âī¸ Must-Read Completed" to "as_fdZX3BgTPGRELzqlfg_A",
+ "đ¸ BL Vibes, Innocent Hearts đ" to "as_FPRnQVKG6qJ5poOo7FKE",
+ "đ
Reborn! A New Life Awaits đĨ" to "as_eth_Jc0XcLftyVnVJOnb",
+ "đ Beyond Friendship đ LGBT+" to "as_JW0c05O4zWPFSmDW0iCH",
+ ),
+)
diff --git a/src/all/qtoon/src/eu/kanade/tachiyomi/extension/all/qtoon/QToon.kt b/src/all/qtoon/src/eu/kanade/tachiyomi/extension/all/qtoon/QToon.kt
new file mode 100644
index 000000000..4921d2545
--- /dev/null
+++ b/src/all/qtoon/src/eu/kanade/tachiyomi/extension/all/qtoon/QToon.kt
@@ -0,0 +1,266 @@
+package eu.kanade.tachiyomi.extension.all.qtoon
+
+import eu.kanade.tachiyomi.network.GET
+import eu.kanade.tachiyomi.network.asObservableSuccess
+import eu.kanade.tachiyomi.source.model.Filter
+import eu.kanade.tachiyomi.source.model.FilterList
+import eu.kanade.tachiyomi.source.model.MangasPage
+import eu.kanade.tachiyomi.source.model.Page
+import eu.kanade.tachiyomi.source.model.SChapter
+import eu.kanade.tachiyomi.source.model.SManga
+import eu.kanade.tachiyomi.source.online.HttpSource
+import keiyoushi.utils.firstInstance
+import keiyoushi.utils.parseAs
+import keiyoushi.utils.toJsonString
+import okhttp3.HttpUrl
+import okhttp3.HttpUrl.Companion.toHttpUrl
+import okhttp3.Request
+import okhttp3.Response
+import rx.Observable
+
+class QToon(
+ override val lang: String,
+ private val siteLang: String,
+) : HttpSource() {
+ override val name = "QToon"
+
+ private val domain = "qtoon.com"
+ override val baseUrl = "https://$domain"
+ private val apiUrl = "https://api.$domain"
+
+ override val supportsLatest = true
+
+ override val client = network.cloudflareClient
+
+ override fun headersBuilder() = super.headersBuilder()
+ .set("Referer", "$baseUrl/")
+
+ override fun popularMangaRequest(page: Int) =
+ searchMangaRequest(page, "", getFilterList())
+
+ override fun popularMangaParse(response: Response) =
+ searchMangaParse(response)
+
+ override fun latestUpdatesRequest(page: Int): Request {
+ val url = apiUrl.toHttpUrl().newBuilder().apply {
+ addPathSegments("api/w/ranking/page/comics")
+ addQueryParameter("page", page.toString())
+ addQueryParameter("rsid", "daily_hot")
+ }.build()
+
+ return apiRequest(url)
+ }
+
+ override fun latestUpdatesParse(response: Response) =
+ searchMangaParse(response)
+
+ override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable {
+ if (query.startsWith("https://")) {
+ val urlPath = query.toHttpUrl().pathSegments
+ val csid = if (
+ urlPath.size == 2 &&
+ (urlPath[0] == "detail" || urlPath[0] == "reader") &&
+ siteLang == "en-US"
+ ) {
+ urlPath[1]
+ } else if (
+ urlPath.size == 3 &&
+ (urlPath[1] == "detail" || urlPath[1] == "reader") &&
+ urlPath[0] == siteLang.split("-", limit = 2)[0]
+ ) {
+ urlPath[2]
+ } else {
+ return Observable.just(MangasPage(emptyList(), false))
+ }
+
+ val url = apiUrl.toHttpUrl().newBuilder().apply {
+ addPathSegments("api/w/comic/detail")
+ addQueryParameter("csid", csid)
+ }.build()
+
+ return client.newCall(apiRequest(url))
+ .asObservableSuccess()
+ .map(::mangaDetailsParse)
+ .map { MangasPage(listOf(it), false) }
+ }
+
+ return super.fetchSearchManga(page, query, filters)
+ }
+
+ override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
+ if (query.isNotBlank()) {
+ val url = apiUrl.toHttpUrl().newBuilder().apply {
+ addPathSegments("api/w/search/comic/search")
+ addQueryParameter("title", query.trim())
+ addQueryParameter("page", page.toString())
+ }.build()
+
+ return apiRequest(url)
+ }
+
+ val homePageSection = filters.firstInstance().selected
+ if (homePageSection.isNotBlank()) {
+ val url = apiUrl.toHttpUrl().newBuilder().apply {
+ addPathSegments("api/w/album/page/comics")
+ addQueryParameter("page", page.toString())
+ addQueryParameter("asid", homePageSection)
+ }.build()
+
+ return apiRequest(url)
+ }
+
+ val url = apiUrl.toHttpUrl().newBuilder().apply {
+ addPathSegments("api/w/search/comic/gallery")
+ addQueryParameter("area", "-1")
+ addQueryParameter("tag", filters.firstInstance().selected)
+ addQueryParameter("gender", filters.firstInstance().selected)
+ addQueryParameter("serialStatus", filters.firstInstance().selected)
+ addQueryParameter("sortType", filters.firstInstance().selected)
+ addQueryParameter("page", page.toString())
+ }.build()
+
+ return apiRequest(url)
+ }
+
+ override fun getFilterList() = FilterList(
+ Filter.Header("Filters don't work with text search"),
+ TagFilter(),
+ StatusFilter(),
+ SortFilter(),
+ GenderFilter(),
+ Filter.Separator(),
+ Filter.Header("Home Page section don't work with other filters"),
+ HomePageFilter(),
+ )
+
+ override fun searchMangaParse(response: Response): MangasPage {
+ val data = response.decryptAs()
+
+ return MangasPage(
+ mangas = data.comics.map(Comic::toSManga),
+ hasNextPage = data.more == 1,
+ )
+ }
+
+ override fun mangaDetailsRequest(manga: SManga): Request {
+ val comicUrl = manga.url.parseAs()
+
+ val url = apiUrl.toHttpUrl().newBuilder().apply {
+ addPathSegments("api/w/comic/detail")
+ addQueryParameter("csid", comicUrl.webLinkId.ifBlank { comicUrl.csid })
+ }.build()
+
+ return apiRequest(url)
+ }
+
+ override fun getMangaUrl(manga: SManga): String {
+ val comicUrl = manga.url.parseAs()
+ val siteLangDir = siteLang.split("-", limit = 2).first()
+
+ return buildString {
+ append(baseUrl)
+ if (siteLangDir != "en") {
+ append("/")
+ append(siteLangDir)
+ }
+ append("/detail/")
+ append(comicUrl.webLinkId.ifBlank { comicUrl.csid })
+ }
+ }
+
+ override fun mangaDetailsParse(response: Response): SManga {
+ val comic = response.decryptAs().comic
+
+ return comic.toSManga()
+ }
+
+ override fun chapterListRequest(manga: SManga) =
+ mangaDetailsRequest(manga)
+
+ override fun chapterListParse(response: Response): List {
+ val episodes = response.decryptAs().episodes
+ val csid = response.request.url.queryParameter("csid")!!
+
+ return episodes.map { episode ->
+ SChapter.create().apply {
+ url = EpisodeUrl(episode.esid, csid).toJsonString()
+ name = episode.title
+ chapter_number = episode.serialNo.toFloat()
+ }
+ }.asReversed()
+ }
+
+ override fun pageListRequest(chapter: SChapter): Request {
+ val episodeUrl = chapter.url.parseAs()
+
+ val url = apiUrl.toHttpUrl().newBuilder().apply {
+ addPathSegments("api/w/comic/episode/detail")
+ addQueryParameter("esid", episodeUrl.esid)
+ }.build()
+
+ return apiRequest(url)
+ }
+
+ override fun getChapterUrl(chapter: SChapter): String {
+ val episodeUrl = chapter.url.parseAs()
+ val siteLangDir = siteLang.split("-", limit = 2).first()
+
+ return buildString {
+ append(baseUrl)
+ if (siteLangDir != "en") {
+ append(("/"))
+ append(siteLangDir)
+ }
+ append("/reader/")
+ append(episodeUrl.csid)
+ append("?chapter=")
+ append(episodeUrl.esid)
+ }
+ }
+
+ override fun pageListParse(response: Response): List {
+ val token = response.decryptAs().definitions[0].token
+
+ val urlBuilder = apiUrl.toHttpUrl().newBuilder().apply {
+ addPathSegments("api/w/resource/group/rslv")
+ addQueryParameter("token", token)
+ }
+ var page = 1
+ var hasNextPage = true
+ val resources = mutableListOf()
+
+ while (hasNextPage) {
+ val url = urlBuilder
+ .setQueryParameter("page", page.toString())
+ .build()
+
+ val data = client.newCall(apiRequest(url)).execute()
+ .decryptAs()
+
+ hasNextPage = data.more == 1
+ resources.addAll(data.resources)
+ page++
+ }
+
+ return resources.map {
+ Page(it.rgIdx, imageUrl = decryptImageUrl(it.url, requestToken))
+ }.sortedBy { it.index }
+ }
+
+ override fun imageUrlParse(response: Response): String {
+ throw UnsupportedOperationException()
+ }
+
+ private val requestToken = generateRandomString(24)
+
+ private fun apiRequest(url: HttpUrl): Request {
+ val headers = headersBuilder().apply {
+ val platform = mobileUserAgentRegex.containsMatchIn(headers["User-Agent"]!!)
+ add("platform", if (platform) "h5" else "pc")
+ add("lth", siteLang)
+ add("did", requestToken)
+ }.build()
+
+ return GET(url, headers)
+ }
+}
diff --git a/src/all/qtoon/src/eu/kanade/tachiyomi/extension/all/qtoon/QToonFactory.kt b/src/all/qtoon/src/eu/kanade/tachiyomi/extension/all/qtoon/QToonFactory.kt
new file mode 100644
index 000000000..5df2ecd10
--- /dev/null
+++ b/src/all/qtoon/src/eu/kanade/tachiyomi/extension/all/qtoon/QToonFactory.kt
@@ -0,0 +1,11 @@
+package eu.kanade.tachiyomi.extension.all.qtoon
+
+import eu.kanade.tachiyomi.source.SourceFactory
+
+class QToonFactory : SourceFactory {
+ override fun createSources() = listOf(
+ QToon("en", "en-US"),
+ QToon("es", "es-ES"),
+ QToon("pt-BR", "pt-PT"),
+ )
+}
diff --git a/src/all/qtoon/src/eu/kanade/tachiyomi/extension/all/qtoon/UrlActivity.kt b/src/all/qtoon/src/eu/kanade/tachiyomi/extension/all/qtoon/UrlActivity.kt
new file mode 100644
index 000000000..503a3e3b9
--- /dev/null
+++ b/src/all/qtoon/src/eu/kanade/tachiyomi/extension/all/qtoon/UrlActivity.kt
@@ -0,0 +1,29 @@
+package eu.kanade.tachiyomi.extension.all.qtoon
+
+import android.app.Activity
+import android.content.ActivityNotFoundException
+import android.content.Intent
+import android.os.Bundle
+import android.util.Log
+import kotlin.system.exitProcess
+
+class UrlActivity : Activity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ val mainIntent = Intent().apply {
+ action = "eu.kanade.tachiyomi.SEARCH"
+ putExtra("query", intent.data.toString())
+ putExtra("filter", packageName)
+ }
+
+ try {
+ startActivity(mainIntent)
+ } catch (e: ActivityNotFoundException) {
+ Log.e("QToon", "Unable to launch url activity", e)
+ }
+
+ finish()
+ exitProcess(0)
+ }
+}