From ce843abec83486cd330f3ed5a5b53526bcf16e71 Mon Sep 17 00:00:00 2001 From: tom5079 Date: Mon, 13 Jan 2020 14:06:47 +0900 Subject: [PATCH] Changed logic to update app from utilizing DownloadManager to manual download --- app/src/main/java/xyz/quaver/pupil/Pupil.kt | 20 +-- .../java/xyz/quaver/pupil/ui/MainActivity.kt | 113 +------------- .../quaver/pupil/util/GalleryDownloader.kt | 2 - .../main/java/xyz/quaver/pupil/util/file.kt | 25 ++++ .../main/java/xyz/quaver/pupil/util/update.kt | 139 +++++++++++++++++- app/src/main/res/values-ja/strings.xml | 2 + app/src/main/res/values-ko/strings.xml | 2 + app/src/main/res/values/strings.xml | 4 +- .../java/xyz/quaver/pupil/ExampleUnitTest.kt | 9 +- 9 files changed, 183 insertions(+), 133 deletions(-) diff --git a/app/src/main/java/xyz/quaver/pupil/Pupil.kt b/app/src/main/java/xyz/quaver/pupil/Pupil.kt index 52b9a947..efc52b3b 100644 --- a/app/src/main/java/xyz/quaver/pupil/Pupil.kt +++ b/app/src/main/java/xyz/quaver/pupil/Pupil.kt @@ -62,19 +62,15 @@ class Pupil : MultiDexApplication() { e.printStackTrace() } - if (!preference.getBoolean("channel_created", false)) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager - val channel = NotificationChannel("download", getString(R.string.channel_download), NotificationManager.IMPORTANCE_LOW).apply { - description = getString(R.string.channel_download_description) - enableLights(false) - enableVibration(false) - lockscreenVisibility = Notification.VISIBILITY_SECRET - } - manager.createNotificationChannel(channel) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + val channel = NotificationChannel("download", getString(R.string.channel_download), NotificationManager.IMPORTANCE_LOW).apply { + description = getString(R.string.channel_download_description) + enableLights(false) + enableVibration(false) + lockscreenVisibility = Notification.VISIBILITY_SECRET } - - preference.edit().putBoolean("channel_created", true).apply() + manager.createNotificationChannel(channel) } AppCompatDelegate.setDefaultNightMode(when (preference.getBoolean("dark_mode", false)) { diff --git a/app/src/main/java/xyz/quaver/pupil/ui/MainActivity.kt b/app/src/main/java/xyz/quaver/pupil/ui/MainActivity.kt index 307a557b..a536dd36 100644 --- a/app/src/main/java/xyz/quaver/pupil/ui/MainActivity.kt +++ b/app/src/main/java/xyz/quaver/pupil/ui/MainActivity.kt @@ -19,11 +19,7 @@ package xyz.quaver.pupil.ui import android.app.Activity -import android.app.DownloadManager -import android.content.BroadcastReceiver -import android.content.Context import android.content.Intent -import android.content.IntentFilter import android.graphics.drawable.Animatable import android.net.Uri import android.os.Bundle @@ -57,11 +53,8 @@ import kotlinx.coroutines.* import kotlinx.serialization.ImplicitReflectionSerializer import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonConfiguration -import kotlinx.serialization.json.JsonObject -import kotlinx.serialization.json.content import kotlinx.serialization.list import kotlinx.serialization.stringify -import ru.noties.markwon.Markwon import xyz.quaver.hitomi.* import xyz.quaver.pupil.Pupil import xyz.quaver.pupil.R @@ -163,7 +156,7 @@ class MainActivity : AppCompatActivity() { setContentView(R.layout.activity_main) - checkUpdate() + checkUpdate(this) initView() } @@ -252,110 +245,6 @@ class MainActivity : AppCompatActivity() { } } - private fun checkUpdate() { - - val preferences = PreferenceManager.getDefaultSharedPreferences(this) - val ignoreUpdateUntil = preferences.getLong("ignore_update_until", 0) - - if (ignoreUpdateUntil > System.currentTimeMillis()) - return - - fun extractReleaseNote(update: JsonObject, locale: String) : String { - val markdown = update["body"]!!.content - - val target = when(locale) { - "ko" -> "한국어" - "ja" -> "日本語" - else -> "English" - } - - val releaseNote = Regex("^# Release Note.+$") - val language = Regex("^## $target$") - val end = Regex("^#.+$") - - var releaseNoteFlag = false - var languageFlag = false - - val result = StringBuilder() - - for(line in markdown.lines()) { - if (releaseNote.matches(line)) { - releaseNoteFlag = true - continue - } - - if (releaseNoteFlag) { - if (language.matches(line)) { - languageFlag = true - continue - } - } - - if (languageFlag) { - if (end.matches(line)) - break - - result.append(line+"\n") - } - } - - return getString(R.string.update_release_note, update["tag_name"]?.content, result.toString()) - } - - CoroutineScope(Dispatchers.Default).launch { - val update = - checkUpdate(getString(R.string.release_url)) ?: return@launch - - val (url, fileName) = getApkUrl(update) ?: return@launch - fileName ?: return@launch - - val dialog = AlertDialog.Builder(this@MainActivity).apply { - setTitle(R.string.update_title) - val msg = extractReleaseNote(update, Locale.getDefault().language) - setMessage(Markwon.create(context).toMarkdown(msg)) - setPositiveButton(android.R.string.yes) { _, _ -> - val request = DownloadManager.Request(Uri.parse(url)).apply { - setDescription(getString(R.string.update_notification_description)) - setTitle(getString(R.string.app_name)) - setDestinationInExternalFilesDir(context, null, "") - } - - val manager = getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager - val id = manager.enqueue(request) - - registerReceiver(object: BroadcastReceiver() { - override fun onReceive(context: Context?, intent: Intent?) { - try { - val install = Intent(Intent.ACTION_VIEW).apply { - flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_GRANT_READ_URI_PERMISSION - setDataAndType(manager.getUriForDownloadedFile(id), manager.getMimeTypeForDownloadedFile(id)) - } - - startActivity(install) - unregisterReceiver(this) - } catch (e: Exception) { - AlertDialog.Builder(this@MainActivity).apply { - setTitle(R.string.update_failed) - setMessage(R.string.update_failed_message) - setPositiveButton(android.R.string.ok) { _, _ -> } - }.show() - } - } - }, IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE)) - } - setNegativeButton(R.string.ignore_update) { _, _ -> - preferences.edit() - .putLong("ignore_update_until", System.currentTimeMillis() + 604800000) - .apply() - } - } - - launch(Dispatchers.Main) { - dialog.show() - } - } - } - private fun initView() { var prevP1 = 0 main_appbar_layout.addOnOffsetChangedListener( diff --git a/app/src/main/java/xyz/quaver/pupil/util/GalleryDownloader.kt b/app/src/main/java/xyz/quaver/pupil/util/GalleryDownloader.kt index 07538401..2eb58b08 100644 --- a/app/src/main/java/xyz/quaver/pupil/util/GalleryDownloader.kt +++ b/app/src/main/java/xyz/quaver/pupil/util/GalleryDownloader.kt @@ -163,8 +163,6 @@ class GalleryDownloader( } } - private fun webpUrlFromUrl(url: String) = url.replace("/galleries/", "/webp/") + ".webp" - fun start() { downloadJob = CoroutineScope(Dispatchers.Default).launch { val reader = reader!!.await() ?: return@launch diff --git a/app/src/main/java/xyz/quaver/pupil/util/file.kt b/app/src/main/java/xyz/quaver/pupil/util/file.kt index 746687db..dd38730b 100644 --- a/app/src/main/java/xyz/quaver/pupil/util/file.kt +++ b/app/src/main/java/xyz/quaver/pupil/util/file.kt @@ -22,6 +22,7 @@ import android.content.Context import androidx.core.content.ContextCompat import androidx.preference.PreferenceManager import java.io.File +import java.net.URL fun getCachedGallery(context: Context, galleryID: Int): File { return File(getDownloadDirectory(context), galleryID.toString()).let { @@ -36,4 +37,28 @@ fun getDownloadDirectory(context: Context): File { val dlLocation = PreferenceManager.getDefaultSharedPreferences(context).getInt("dl_location", 0) return ContextCompat.getExternalFilesDirs(context, null)[dlLocation] +} + +fun URL.download(to: File, onDownloadProgress: ((Long, Long) -> Unit)? = null) { + to.outputStream().use { out -> + + with(openConnection()) { + val fileSize = contentLength.toLong() + + getInputStream().use { + + var bytesCopied: Long = 0 + val buffer = ByteArray(DEFAULT_BUFFER_SIZE) + var bytes = it.read(buffer) + while (bytes >= 0) { + out.write(buffer, 0, bytes) + bytesCopied += bytes + onDownloadProgress?.invoke(bytesCopied, fileSize) + bytes = it.read(buffer) + } + + } + } + + } } \ No newline at end of file diff --git a/app/src/main/java/xyz/quaver/pupil/util/update.kt b/app/src/main/java/xyz/quaver/pupil/util/update.kt index c42c13d8..13266ae6 100644 --- a/app/src/main/java/xyz/quaver/pupil/util/update.kt +++ b/app/src/main/java/xyz/quaver/pupil/util/update.kt @@ -18,15 +18,31 @@ package xyz.quaver.pupil.util +import android.app.PendingIntent import android.content.Context +import android.content.Intent +import android.webkit.MimeTypeMap +import androidx.appcompat.app.AlertDialog +import androidx.appcompat.app.AppCompatActivity +import androidx.core.app.NotificationCompat +import androidx.core.app.NotificationManagerCompat +import androidx.core.content.FileProvider +import androidx.lifecycle.Lifecycle +import androidx.preference.PreferenceManager +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch import kotlinx.serialization.InternalSerializationApi import kotlinx.serialization.internal.EnumSerializer import kotlinx.serialization.json.* +import ru.noties.markwon.Markwon import xyz.quaver.availableInHiyobi import xyz.quaver.hitomi.Reader import xyz.quaver.pupil.BuildConfig +import xyz.quaver.pupil.R import java.io.File import java.net.URL +import java.util.* fun getReleases(url: String) : JsonArray { return try { @@ -58,14 +74,127 @@ fun checkUpdate(url: String) : JsonObject? { } } -fun getApkUrl(releases: JsonObject) : Pair? { +fun getApkUrl(releases: JsonObject) : String? { return releases["assets"]?.jsonArray?.firstOrNull { Regex("Pupil-v.+\\.apk").matches(it.jsonObject["name"]?.content ?: "") }.let { - if (it == null) - null - else - Pair(it.jsonObject["browser_download_url"]?.content, it.jsonObject["name"]?.content) + it?.jsonObject?.get("browser_download_url")?.content + } +} + +val UPDATE_NOTIFICATION_ID = 384823 +fun checkUpdate(context: AppCompatActivity) { + + val preferences = PreferenceManager.getDefaultSharedPreferences(context) + val ignoreUpdateUntil = preferences.getLong("ignore_update_until", 0) + + if (ignoreUpdateUntil > System.currentTimeMillis()) + return + + fun extractReleaseNote(update: JsonObject, locale: Locale) : String { + val markdown = update["body"]!!.content + + val target = when(locale) { + Locale.KOREAN -> "한국어" + Locale.JAPANESE -> "日本語" + else -> "English" + } + + val releaseNote = Regex("^# Release Note.+$") + val language = Regex("^## $target$") + val end = Regex("^#.+$") + + var releaseNoteFlag = false + var languageFlag = false + + val result = StringBuilder() + + for(line in markdown.lines()) { + if (releaseNote.matches(line)) { + releaseNoteFlag = true + continue + } + + if (releaseNoteFlag) { + if (language.matches(line)) { + languageFlag = true + continue + } + } + + if (languageFlag) { + if (end.matches(line)) + break + + result.append(line+"\n") + } + } + + return context.getString(R.string.update_release_note, update["tag_name"]?.content, result.toString()) + } + + CoroutineScope(Dispatchers.Default).launch { + val update = + checkUpdate(context.getString(R.string.release_url)) ?: return@launch + + val url = getApkUrl(update) ?: return@launch + + val dialog = AlertDialog.Builder(context).apply { + setTitle(R.string.update_title) + val msg = extractReleaseNote(update, Locale.getDefault()) + setMessage(Markwon.create(context).toMarkdown(msg)) + setPositiveButton(android.R.string.yes) { _, _ -> + + val notificationManager = NotificationManagerCompat.from(context) + val builder = NotificationCompat.Builder(context, "download").apply { + setContentTitle(context.getString(R.string.update_notification_description)) + setSmallIcon(android.R.drawable.stat_sys_download) + priority = NotificationCompat.PRIORITY_LOW + } + + CoroutineScope(Dispatchers.IO).launch { + val target = File(getDownloadDirectory(context), "Pupil.apk") + + URL(url).download(target) { progress, fileSize -> + builder.setProgress(fileSize.toInt(), progress.toInt(), false) + notificationManager.notify(UPDATE_NOTIFICATION_ID, builder.build()) + } + + val install = Intent(Intent.ACTION_VIEW).apply { + flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_GRANT_READ_URI_PERMISSION + setDataAndType(FileProvider.getUriForFile( + context, + context.applicationContext.packageName + ".fileprovider", + target + ), MimeTypeMap.getSingleton().getExtensionFromMimeType(".apk")) + } + + builder.apply { + setContentIntent(PendingIntent.getActivity(context, 0, install, 0)) + setProgress(0, 0, false) + setSmallIcon(android.R.drawable.stat_sys_download_done) + setContentTitle(context.getString(R.string.update_download_completed)) + setContentText(context.getString(R.string.update_download_completed_description)) + } + + notificationManager.cancel(UPDATE_NOTIFICATION_ID) + + if (context.lifecycle.currentState.isAtLeast(Lifecycle.State.RESUMED)) + context.startActivity(install) + else + notificationManager.notify(UPDATE_NOTIFICATION_ID, builder.build()) + } + } + setNegativeButton(R.string.ignore_update) { _, _ -> + preferences.edit() + .putLong("ignore_update_until", System.currentTimeMillis() + 604800000) + .apply() + } + } + + launch(Dispatchers.Main) { + dialog.show() + } } } diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 066bfaa1..625fcd2b 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -114,4 +114,6 @@ 内部ストレージ 外部SDカード %s 使用可能 + ダウンロードが完了しました + ここをクリックしてアップデートを行えます \ No newline at end of file diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index 7085b22c..5c2ecdde 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -114,4 +114,6 @@ 내부 저장공간 외부 SD카드 %s 사용 가능 + 다운로드가 완료되었습니다 + 여기를 클릭해서 업데이트를 진행할 수 있습니다 \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 305bcc6d..afd7d1fd 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -76,7 +76,9 @@ Update available Download started - Downloading apk… + Download Completed + Click here to update + Downloading update… # Release Note(v%1$s)\n%2$s Search galleries diff --git a/app/src/test/java/xyz/quaver/pupil/ExampleUnitTest.kt b/app/src/test/java/xyz/quaver/pupil/ExampleUnitTest.kt index 2aef6c6b..795f7983 100644 --- a/app/src/test/java/xyz/quaver/pupil/ExampleUnitTest.kt +++ b/app/src/test/java/xyz/quaver/pupil/ExampleUnitTest.kt @@ -27,12 +27,19 @@ package xyz.quaver.pupil */ import org.junit.Test +import xyz.quaver.pupil.util.download +import java.io.File +import java.net.URL class ExampleUnitTest { @Test fun test() { - + URL("https://github.om/tom5079/Pupil/releases/download/4.2-beta2-hotfix2/Pupil-v4.2-beta2-hotfix2.apk").download( + File(System.getenv("USERPROFILE"), "Pupil.apk") + ) { downloaded, fileSize -> + println("%.1f%%".format(downloaded*100.0/fileSize)) + } } }