Changed logic to update app from utilizing DownloadManager to manual download

This commit is contained in:
tom5079
2020-01-13 14:06:47 +09:00
parent 6b43faa70e
commit ce843abec8
9 changed files with 183 additions and 133 deletions

View File

@@ -62,7 +62,6 @@ class Pupil : MultiDexApplication() {
e.printStackTrace() e.printStackTrace()
} }
if (!preference.getBoolean("channel_created", false)) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
val channel = NotificationChannel("download", getString(R.string.channel_download), NotificationManager.IMPORTANCE_LOW).apply { val channel = NotificationChannel("download", getString(R.string.channel_download), NotificationManager.IMPORTANCE_LOW).apply {
@@ -74,9 +73,6 @@ class Pupil : MultiDexApplication() {
manager.createNotificationChannel(channel) manager.createNotificationChannel(channel)
} }
preference.edit().putBoolean("channel_created", true).apply()
}
AppCompatDelegate.setDefaultNightMode(when (preference.getBoolean("dark_mode", false)) { AppCompatDelegate.setDefaultNightMode(when (preference.getBoolean("dark_mode", false)) {
true -> AppCompatDelegate.MODE_NIGHT_YES true -> AppCompatDelegate.MODE_NIGHT_YES
false -> AppCompatDelegate.MODE_NIGHT_NO false -> AppCompatDelegate.MODE_NIGHT_NO

View File

@@ -19,11 +19,7 @@
package xyz.quaver.pupil.ui package xyz.quaver.pupil.ui
import android.app.Activity import android.app.Activity
import android.app.DownloadManager
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.IntentFilter
import android.graphics.drawable.Animatable import android.graphics.drawable.Animatable
import android.net.Uri import android.net.Uri
import android.os.Bundle import android.os.Bundle
@@ -57,11 +53,8 @@ import kotlinx.coroutines.*
import kotlinx.serialization.ImplicitReflectionSerializer import kotlinx.serialization.ImplicitReflectionSerializer
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonConfiguration import kotlinx.serialization.json.JsonConfiguration
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.content
import kotlinx.serialization.list import kotlinx.serialization.list
import kotlinx.serialization.stringify import kotlinx.serialization.stringify
import ru.noties.markwon.Markwon
import xyz.quaver.hitomi.* import xyz.quaver.hitomi.*
import xyz.quaver.pupil.Pupil import xyz.quaver.pupil.Pupil
import xyz.quaver.pupil.R import xyz.quaver.pupil.R
@@ -163,7 +156,7 @@ class MainActivity : AppCompatActivity() {
setContentView(R.layout.activity_main) setContentView(R.layout.activity_main)
checkUpdate() checkUpdate(this)
initView() 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() { private fun initView() {
var prevP1 = 0 var prevP1 = 0
main_appbar_layout.addOnOffsetChangedListener( main_appbar_layout.addOnOffsetChangedListener(

View File

@@ -163,8 +163,6 @@ class GalleryDownloader(
} }
} }
private fun webpUrlFromUrl(url: String) = url.replace("/galleries/", "/webp/") + ".webp"
fun start() { fun start() {
downloadJob = CoroutineScope(Dispatchers.Default).launch { downloadJob = CoroutineScope(Dispatchers.Default).launch {
val reader = reader!!.await() ?: return@launch val reader = reader!!.await() ?: return@launch

View File

@@ -22,6 +22,7 @@ import android.content.Context
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import java.io.File import java.io.File
import java.net.URL
fun getCachedGallery(context: Context, galleryID: Int): File { fun getCachedGallery(context: Context, galleryID: Int): File {
return File(getDownloadDirectory(context), galleryID.toString()).let { return File(getDownloadDirectory(context), galleryID.toString()).let {
@@ -37,3 +38,27 @@ fun getDownloadDirectory(context: Context): File {
return ContextCompat.getExternalFilesDirs(context, null)[dlLocation] 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)
}
}
}
}
}

View File

@@ -18,15 +18,31 @@
package xyz.quaver.pupil.util package xyz.quaver.pupil.util
import android.app.PendingIntent
import android.content.Context 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.InternalSerializationApi
import kotlinx.serialization.internal.EnumSerializer import kotlinx.serialization.internal.EnumSerializer
import kotlinx.serialization.json.* import kotlinx.serialization.json.*
import ru.noties.markwon.Markwon
import xyz.quaver.availableInHiyobi import xyz.quaver.availableInHiyobi
import xyz.quaver.hitomi.Reader import xyz.quaver.hitomi.Reader
import xyz.quaver.pupil.BuildConfig import xyz.quaver.pupil.BuildConfig
import xyz.quaver.pupil.R
import java.io.File import java.io.File
import java.net.URL import java.net.URL
import java.util.*
fun getReleases(url: String) : JsonArray { fun getReleases(url: String) : JsonArray {
return try { return try {
@@ -58,14 +74,127 @@ fun checkUpdate(url: String) : JsonObject? {
} }
} }
fun getApkUrl(releases: JsonObject) : Pair<String?, String?>? { fun getApkUrl(releases: JsonObject) : String? {
return releases["assets"]?.jsonArray?.firstOrNull { return releases["assets"]?.jsonArray?.firstOrNull {
Regex("Pupil-v.+\\.apk").matches(it.jsonObject["name"]?.content ?: "") Regex("Pupil-v.+\\.apk").matches(it.jsonObject["name"]?.content ?: "")
}.let { }.let {
if (it == null) it?.jsonObject?.get("browser_download_url")?.content
null }
}
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 else
Pair(it.jsonObject["browser_download_url"]?.content, it.jsonObject["name"]?.content) 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()
}
} }
} }

View File

@@ -114,4 +114,6 @@
<string name="settings_dl_location_internal">内部ストレージ</string> <string name="settings_dl_location_internal">内部ストレージ</string>
<string name="settings_dl_location_removable">外部SDカード</string> <string name="settings_dl_location_removable">外部SDカード</string>
<string name="settings_dl_location_available">%s 使用可能</string> <string name="settings_dl_location_available">%s 使用可能</string>
<string name="update_download_completed">ダウンロードが完了しました</string>
<string name="update_download_completed_description">ここをクリックしてアップデートを行えます</string>
</resources> </resources>

View File

@@ -114,4 +114,6 @@
<string name="settings_dl_location_internal">내부 저장공간</string> <string name="settings_dl_location_internal">내부 저장공간</string>
<string name="settings_dl_location_removable">외부 SD카드</string> <string name="settings_dl_location_removable">외부 SD카드</string>
<string name="settings_dl_location_available">%s 사용 가능</string> <string name="settings_dl_location_available">%s 사용 가능</string>
<string name="update_download_completed">다운로드가 완료되었습니다</string>
<string name="update_download_completed_description">여기를 클릭해서 업데이트를 진행할 수 있습니다</string>
</resources> </resources>

View File

@@ -76,7 +76,9 @@
<string name="update_title">Update available</string> <string name="update_title">Update available</string>
<string name="update_download_started">Download started</string> <string name="update_download_started">Download started</string>
<string name="update_notification_description">Downloading apk&#8230;</string> <string name="update_download_completed">Download Completed</string>
<string name="update_download_completed_description">Click here to update</string>
<string name="update_notification_description">Downloading update&#8230;</string>
<string name="update_release_note"># Release Note(v%1$s)\n%2$s</string> <string name="update_release_note"># Release Note(v%1$s)\n%2$s</string>
<string name="search_hint">Search galleries</string> <string name="search_hint">Search galleries</string>

View File

@@ -27,12 +27,19 @@ package xyz.quaver.pupil
*/ */
import org.junit.Test import org.junit.Test
import xyz.quaver.pupil.util.download
import java.io.File
import java.net.URL
class ExampleUnitTest { class ExampleUnitTest {
@Test @Test
fun 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))
}
} }
} }