Auto install app update (#561)
* migrating * - Fix Broadcaster - Include Updater for Debug - json to emulate Github call * - resetup AppUpdateJob when changing settings - Fix ACTION_MY_PACKAGE_REPLACED * improve notification * update icon * app update to high important * - Run auto install on AppUpdateJob - Add release link to job * idle state & network aware * fix duplicated job start * don't show notification & start job for peek-into-preview * allow cancel auto update job when set to NEVER * always notify on install complete * rearm AppUpdateJob each time MainActivity already checked for update * MainActivity checks every 2 days, rearm AutoUpdateJob in 3 days * - fix install error notification ID - add timeout for manual installation if package manager crash * settings & spotless * restriction settings & auto run download-install if not in restrictions * clean up
This commit is contained in:
parent
5a893927a3
commit
a8c587829c
24 changed files with 11120 additions and 32 deletions
|
|
@ -71,6 +71,7 @@ android {
|
|||
versionNameSuffix = "-${getCommitCount()}"
|
||||
applicationIdSuffix = ".debug"
|
||||
isPseudoLocalesEnabled = true
|
||||
buildConfigField("boolean", "INCLUDE_UPDATER", "true")
|
||||
}
|
||||
create("releaseTest") {
|
||||
applicationIdSuffix = ".rt"
|
||||
|
|
|
|||
229
app/src/debug/res/raw/preview_latest.json
Normal file
229
app/src/debug/res/raw/preview_latest.json
Normal file
|
|
@ -0,0 +1,229 @@
|
|||
{
|
||||
"url": "https://api.github.com/repos/komikku-app/komikku-preview/releases/184548882",
|
||||
"assets_url": "https://api.github.com/repos/komikku-app/komikku-preview/releases/184548882/assets",
|
||||
"upload_url": "https://uploads.github.com/repos/komikku-app/komikku-preview/releases/184548882/assets{?name,label}",
|
||||
"html_url": "https://github.com/komikku-app/komikku-preview/releases/tag/r9576",
|
||||
"id": 184548882,
|
||||
"author": {
|
||||
"login": "github-actions[bot]",
|
||||
"id": 41898282,
|
||||
"node_id": "MDM6Qm90NDE4OTgyODI=",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4",
|
||||
"gravatar_id": "",
|
||||
"url": "https://api.github.com/users/github-actions%5Bbot%5D",
|
||||
"html_url": "https://github.com/apps/github-actions",
|
||||
"followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers",
|
||||
"following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}",
|
||||
"gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}",
|
||||
"starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}",
|
||||
"subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions",
|
||||
"organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs",
|
||||
"repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos",
|
||||
"events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}",
|
||||
"received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events",
|
||||
"type": "Bot",
|
||||
"user_view_type": "public",
|
||||
"site_admin": false
|
||||
},
|
||||
"node_id": "RE_kwDOL4q0GM4K__4S",
|
||||
"tag_name": "r9576",
|
||||
"target_commitish": "main",
|
||||
"name": "Komikku Preview r9576",
|
||||
"draft": false,
|
||||
"prerelease": false,
|
||||
"created_at": "2024-10-31T21:22:20Z",
|
||||
"published_at": "2024-11-10T07:50:48Z",
|
||||
"assets": [
|
||||
{
|
||||
"url": "https://api.github.com/repos/komikku-app/komikku-preview/releases/assets/205437757",
|
||||
"id": 205437757,
|
||||
"node_id": "RA_kwDOL4q0GM4MPrs9",
|
||||
"name": "Komikku-arm64-v8a-r9576.apk",
|
||||
"label": "",
|
||||
"uploader": {
|
||||
"login": "github-actions[bot]",
|
||||
"id": 41898282,
|
||||
"node_id": "MDM6Qm90NDE4OTgyODI=",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4",
|
||||
"gravatar_id": "",
|
||||
"url": "https://api.github.com/users/github-actions%5Bbot%5D",
|
||||
"html_url": "https://github.com/apps/github-actions",
|
||||
"followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers",
|
||||
"following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}",
|
||||
"gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}",
|
||||
"starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}",
|
||||
"subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions",
|
||||
"organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs",
|
||||
"repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos",
|
||||
"events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}",
|
||||
"received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events",
|
||||
"type": "Bot",
|
||||
"user_view_type": "public",
|
||||
"site_admin": false
|
||||
},
|
||||
"content_type": "application/vnd.android.package-archive",
|
||||
"state": "uploaded",
|
||||
"size": 36655297,
|
||||
"download_count": 731,
|
||||
"created_at": "2024-11-10T07:42:42Z",
|
||||
"updated_at": "2024-11-10T07:42:43Z",
|
||||
"browser_download_url": "https://github.com/komikku-app/komikku-preview/releases/download/r9576/Komikku-arm64-v8a-r9576.apk"
|
||||
},
|
||||
{
|
||||
"url": "https://api.github.com/repos/komikku-app/komikku-preview/releases/assets/205437760",
|
||||
"id": 205437760,
|
||||
"node_id": "RA_kwDOL4q0GM4MPrtA",
|
||||
"name": "Komikku-armeabi-v7a-r9576.apk",
|
||||
"label": "",
|
||||
"uploader": {
|
||||
"login": "github-actions[bot]",
|
||||
"id": 41898282,
|
||||
"node_id": "MDM6Qm90NDE4OTgyODI=",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4",
|
||||
"gravatar_id": "",
|
||||
"url": "https://api.github.com/users/github-actions%5Bbot%5D",
|
||||
"html_url": "https://github.com/apps/github-actions",
|
||||
"followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers",
|
||||
"following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}",
|
||||
"gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}",
|
||||
"starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}",
|
||||
"subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions",
|
||||
"organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs",
|
||||
"repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos",
|
||||
"events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}",
|
||||
"received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events",
|
||||
"type": "Bot",
|
||||
"user_view_type": "public",
|
||||
"site_admin": false
|
||||
},
|
||||
"content_type": "application/vnd.android.package-archive",
|
||||
"state": "uploaded",
|
||||
"size": 31707343,
|
||||
"download_count": 72,
|
||||
"created_at": "2024-11-10T07:42:42Z",
|
||||
"updated_at": "2024-11-10T07:42:43Z",
|
||||
"browser_download_url": "https://github.com/komikku-app/komikku-preview/releases/download/r9576/Komikku-armeabi-v7a-r9576.apk"
|
||||
},
|
||||
{
|
||||
"url": "https://api.github.com/repos/komikku-app/komikku-preview/releases/assets/205437758",
|
||||
"id": 205437758,
|
||||
"node_id": "RA_kwDOL4q0GM4MPrs-",
|
||||
"name": "Komikku-r9576.apk",
|
||||
"label": "",
|
||||
"uploader": {
|
||||
"login": "github-actions[bot]",
|
||||
"id": 41898282,
|
||||
"node_id": "MDM6Qm90NDE4OTgyODI=",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4",
|
||||
"gravatar_id": "",
|
||||
"url": "https://api.github.com/users/github-actions%5Bbot%5D",
|
||||
"html_url": "https://github.com/apps/github-actions",
|
||||
"followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers",
|
||||
"following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}",
|
||||
"gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}",
|
||||
"starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}",
|
||||
"subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions",
|
||||
"organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs",
|
||||
"repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos",
|
||||
"events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}",
|
||||
"received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events",
|
||||
"type": "Bot",
|
||||
"user_view_type": "public",
|
||||
"site_admin": false
|
||||
},
|
||||
"content_type": "application/vnd.android.package-archive",
|
||||
"state": "uploaded",
|
||||
"size": 88282864,
|
||||
"download_count": 67,
|
||||
"created_at": "2024-11-10T07:42:42Z",
|
||||
"updated_at": "2024-11-10T07:42:44Z",
|
||||
"browser_download_url": "https://github.com/komikku-app/komikku-preview/releases/download/r9576/Komikku-r9576.apk"
|
||||
},
|
||||
{
|
||||
"url": "https://api.github.com/repos/komikku-app/komikku-preview/releases/assets/205437759",
|
||||
"id": 205437759,
|
||||
"node_id": "RA_kwDOL4q0GM4MPrs_",
|
||||
"name": "Komikku-x86-r9576.apk",
|
||||
"label": "",
|
||||
"uploader": {
|
||||
"login": "github-actions[bot]",
|
||||
"id": 41898282,
|
||||
"node_id": "MDM6Qm90NDE4OTgyODI=",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4",
|
||||
"gravatar_id": "",
|
||||
"url": "https://api.github.com/users/github-actions%5Bbot%5D",
|
||||
"html_url": "https://github.com/apps/github-actions",
|
||||
"followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers",
|
||||
"following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}",
|
||||
"gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}",
|
||||
"starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}",
|
||||
"subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions",
|
||||
"organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs",
|
||||
"repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos",
|
||||
"events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}",
|
||||
"received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events",
|
||||
"type": "Bot",
|
||||
"user_view_type": "public",
|
||||
"site_admin": false
|
||||
},
|
||||
"content_type": "application/vnd.android.package-archive",
|
||||
"state": "uploaded",
|
||||
"size": 37822615,
|
||||
"download_count": 3,
|
||||
"created_at": "2024-11-10T07:42:42Z",
|
||||
"updated_at": "2024-11-10T07:42:43Z",
|
||||
"browser_download_url": "https://github.com/komikku-app/komikku-preview/releases/download/r9576/Komikku-x86-r9576.apk"
|
||||
},
|
||||
{
|
||||
"url": "https://api.github.com/repos/komikku-app/komikku-preview/releases/assets/205437761",
|
||||
"id": 205437761,
|
||||
"node_id": "RA_kwDOL4q0GM4MPrtB",
|
||||
"name": "Komikku-x86_64-r9576.apk",
|
||||
"label": "",
|
||||
"uploader": {
|
||||
"login": "github-actions[bot]",
|
||||
"id": 41898282,
|
||||
"node_id": "MDM6Qm90NDE4OTgyODI=",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4",
|
||||
"gravatar_id": "",
|
||||
"url": "https://api.github.com/users/github-actions%5Bbot%5D",
|
||||
"html_url": "https://github.com/apps/github-actions",
|
||||
"followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers",
|
||||
"following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}",
|
||||
"gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}",
|
||||
"starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}",
|
||||
"subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions",
|
||||
"organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs",
|
||||
"repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos",
|
||||
"events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}",
|
||||
"received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events",
|
||||
"type": "Bot",
|
||||
"user_view_type": "public",
|
||||
"site_admin": false
|
||||
},
|
||||
"content_type": "application/vnd.android.package-archive",
|
||||
"state": "uploaded",
|
||||
"size": 39817388,
|
||||
"download_count": 14,
|
||||
"created_at": "2024-11-10T07:42:42Z",
|
||||
"updated_at": "2024-11-10T07:42:43Z",
|
||||
"browser_download_url": "https://github.com/komikku-app/komikku-preview/releases/download/r9576/Komikku-x86_64-r9576.apk"
|
||||
}
|
||||
],
|
||||
"tarball_url": "https://api.github.com/repos/komikku-app/komikku-preview/tarball/r9576",
|
||||
"zipball_url": "https://api.github.com/repos/komikku-app/komikku-preview/zipball/r9576",
|
||||
"body": "#### What's Changed\r\n##### New\r\n- Filter only Obsolete sources in Migration screen (@cuong-tran)\r\n- Add option to always use SSIV for image decoding (@AntsyLich)\r\n\r\n##### Improve\r\n- Trying to load saved-search as much as possible even though filterList might changed (@cuong-tran)\r\n- Improve saved-search usage (@cuong-tran)\r\n- Allow reset filters on SourceFeedScreen (@cuong-tran)\r\n- Allow using saved-search while migration source search (@cuong-tran)\r\n- Change bulk-favorite icon to Heart (@cuong-tran)\r\n- Bump default user agent (@AntsyLich)\r\n- Support comma (,) delimiter when searching library (@cuong-tran)\r\n- Auto format extension repo URLs (@AntsyLich)\r\n- Some improvements to Bangumi tracker search (@MajorTanya)\r\n- Do not sync automatically when not connected to a network. (@NGB-Was-Taken)\r\n\r\n##### Fix\r\n- Fix a rare crash when invoking \"Mark previous as read\" action (@AntsyLich)\r\n- Fix long strip images not loading in some old devices (@AntsyLich)\r\n- Fix InterceptActivity crash (@jobobby04)\r\n- Fix crashes from Exh Updater (@jobobby04)\r\n- Fix app onStart sync (@jobobby04)\r\n- Fix crash on MigrationListScreen with null accessing a mutableState when it's being put in the background (@cuong-tran)\r\n- Fix Reader's dialog font size (@cuong-tran)\r\n- Fix multiple issues with the E-Hentai updater (@jobobby04)\r\n- Fix a possible crash with auto-zoom (@jobobby04)\r\n- Fix source-search clear causing disappearance of search box (@cuong-tran)\r\n\r\n**Full Changelog**: https://github.com/komikku-app/komikku/compare/r9523...r9576\r\n\r\n---\r\n\r\n### Checksums\r\n\r\n| Variant | SHA-256 |\r\n| ------- | ------- |\r\n| Universal | d4810c40e54cfb0834dc1253b83c502ba0307a89114b9842f1a01f270d854979 |\r\n| arm64-v8a | 329720d8616359ee311c8bb4f1b151d8318e4afbf996c7d9202c01778ae99128 |\r\n| armeabi-v7a | cd500e73fd8397b07994a3d4de2028af35d2cc91c1c054ce55ad0321347498ca |\r\n| x86 | cc45fb1109cc08127d084fd780cba15d1d011aae6fae31a05690bc0a3ae027e4 |\r\n| x86_64 | a2da81a678d626e0a80b2fe393f987f8059e3924d67f6e1a6e145972e7373e04 |\r\n\r\n### If you are unsure which apk to download then go with `Komikku-r9576.apk`\r\n",
|
||||
"reactions": {
|
||||
"url": "https://api.github.com/repos/komikku-app/komikku-preview/releases/184548882/reactions",
|
||||
"total_count": 6,
|
||||
"+1": 0,
|
||||
"-1": 0,
|
||||
"laugh": 0,
|
||||
"hooray": 0,
|
||||
"confused": 0,
|
||||
"heart": 6,
|
||||
"rocket": 0,
|
||||
"eyes": 0
|
||||
},
|
||||
"mentions_count": 5
|
||||
}
|
||||
5846
app/src/debug/res/raw/previews.json
Normal file
5846
app/src/debug/res/raw/previews.json
Normal file
File diff suppressed because it is too large
Load diff
229
app/src/debug/res/raw/release_latest.json
Normal file
229
app/src/debug/res/raw/release_latest.json
Normal file
File diff suppressed because one or more lines are too long
4265
app/src/debug/res/raw/releases.json
Normal file
4265
app/src/debug/res/raw/releases.json
Normal file
File diff suppressed because one or more lines are too long
|
|
@ -217,6 +217,17 @@
|
|||
android:name=".data.notification.NotificationReceiver"
|
||||
android:exported="false" />
|
||||
|
||||
<receiver
|
||||
android:name=".data.updater.AppUpdateBroadcast"
|
||||
android:exported="false">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MY_PACKAGE_REPLACED" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.ACTION_MY_PACKAGE_REPLACED" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<service
|
||||
android:name=".extension.util.ExtensionInstallService"
|
||||
android:exported="false"
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import androidx.compose.runtime.toMutableStateList
|
|||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalUriHandler
|
||||
import androidx.compose.ui.window.DialogProperties
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.net.toUri
|
||||
import androidx.core.text.HtmlCompat
|
||||
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||
|
|
@ -42,6 +43,7 @@ import eu.kanade.tachiyomi.core.security.SecurityPreferences
|
|||
import eu.kanade.tachiyomi.data.download.DownloadCache
|
||||
import eu.kanade.tachiyomi.data.download.DownloadManager
|
||||
import eu.kanade.tachiyomi.data.library.LibraryUpdateJob
|
||||
import eu.kanade.tachiyomi.data.updater.AppUpdateJob
|
||||
import eu.kanade.tachiyomi.network.NetworkHelper
|
||||
import eu.kanade.tachiyomi.network.NetworkPreferences
|
||||
import eu.kanade.tachiyomi.network.PREF_DOH_360
|
||||
|
|
@ -90,6 +92,7 @@ import tachiyomi.domain.UnsortedPreferences
|
|||
import tachiyomi.domain.chapter.interactor.GetChaptersByMangaId
|
||||
import tachiyomi.domain.manga.interactor.GetAllManga
|
||||
import tachiyomi.domain.manga.interactor.ResetViewerFlags
|
||||
import tachiyomi.domain.release.service.AppUpdatePolicy
|
||||
import tachiyomi.domain.source.service.SourceManager
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.i18n.kmk.KMR
|
||||
|
|
@ -116,6 +119,7 @@ object SettingsAdvancedScreen : SearchableSettings {
|
|||
|
||||
val basePreferences = remember { Injekt.get<BasePreferences>() }
|
||||
val networkPreferences = remember { Injekt.get<NetworkPreferences>() }
|
||||
val unsortedPreferences = remember { Injekt.get<UnsortedPreferences>() }
|
||||
|
||||
return listOf(
|
||||
Preference.PreferenceItem.TextPreference(
|
||||
|
|
@ -153,6 +157,23 @@ object SettingsAdvancedScreen : SearchableSettings {
|
|||
context.startActivity(intent)
|
||||
},
|
||||
),
|
||||
// KMK -->
|
||||
Preference.PreferenceItem.MultiSelectListPreference(
|
||||
pref = unsortedPreferences.appShouldAutoUpdate(),
|
||||
title = stringResource(KMR.strings.auto_update_app),
|
||||
subtitle = stringResource(MR.strings.restrictions),
|
||||
entries = persistentMapOf(
|
||||
AppUpdatePolicy.DEVICE_ONLY_ON_WIFI to stringResource(MR.strings.connected_to_wifi),
|
||||
AppUpdatePolicy.DEVICE_NETWORK_NOT_METERED to stringResource(MR.strings.network_not_metered),
|
||||
AppUpdatePolicy.DEVICE_CHARGING to stringResource(MR.strings.charging),
|
||||
),
|
||||
onValueChanged = {
|
||||
// Post to event looper to allow the preference to be updated.
|
||||
ContextCompat.getMainExecutor(context).execute { AppUpdateJob.setupTask(context) }
|
||||
true
|
||||
},
|
||||
),
|
||||
// KMK <--
|
||||
getBackgroundActivityGroup(),
|
||||
getDataGroup(),
|
||||
getNetworkGroup(networkPreferences = networkPreferences),
|
||||
|
|
|
|||
|
|
@ -311,7 +311,11 @@ object AboutScreen : Screen() {
|
|||
peekIntoPreview: Boolean = false,
|
||||
// KMK <--
|
||||
) {
|
||||
val updateChecker = AppUpdateChecker(peekIntoPreview)
|
||||
val updateChecker = AppUpdateChecker(
|
||||
// KMK -->
|
||||
peekIntoPreview = peekIntoPreview,
|
||||
// KMK <--
|
||||
)
|
||||
withUIContext {
|
||||
try {
|
||||
when (val result = withIOContext { updateChecker.checkForUpdate(context, forceCheck = true) }) {
|
||||
|
|
|
|||
|
|
@ -75,6 +75,12 @@ object Notifications {
|
|||
const val ID_APP_UPDATER = 1
|
||||
const val ID_APP_UPDATE_PROMPT = 2
|
||||
const val ID_APP_UPDATE_ERROR = 3
|
||||
|
||||
// KMK -->
|
||||
const val ID_APP_INSTALL = 4
|
||||
const val ID_APP_INSTALLED = 5
|
||||
// KMK <--
|
||||
|
||||
const val CHANNEL_EXTENSIONS_UPDATE = "ext_apk_update_channel"
|
||||
const val ID_UPDATES_TO_EXTS = -401
|
||||
const val ID_EXTENSION_INSTALLER = -402
|
||||
|
|
@ -162,7 +168,7 @@ object Notifications {
|
|||
buildNotificationChannel(CHANNEL_INCOGNITO_MODE, IMPORTANCE_LOW) {
|
||||
setName(context.stringResource(MR.strings.pref_incognito_mode))
|
||||
},
|
||||
buildNotificationChannel(CHANNEL_APP_UPDATE, IMPORTANCE_DEFAULT) {
|
||||
buildNotificationChannel(CHANNEL_APP_UPDATE, IMPORTANCE_HIGH) {
|
||||
setGroup(GROUP_APK_UPDATES)
|
||||
setName(context.stringResource(MR.strings.channel_app_updates))
|
||||
},
|
||||
|
|
|
|||
|
|
@ -0,0 +1,63 @@
|
|||
package eu.kanade.tachiyomi.data.updater
|
||||
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageInstaller
|
||||
import androidx.core.net.toUri
|
||||
import eu.kanade.tachiyomi.util.system.getParcelableExtraCompat
|
||||
import eu.kanade.tachiyomi.util.system.toast
|
||||
import tachiyomi.i18n.kmk.KMR
|
||||
|
||||
class AppUpdateBroadcast : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
val appUpdateNotifier = AppUpdateNotifier(context)
|
||||
|
||||
if (intent.action == AppUpdateDownloadJob.PACKAGE_INSTALLED_ACTION) {
|
||||
/*
|
||||
* Callback on PackageInstaller status
|
||||
*/
|
||||
when (val status = intent.getIntExtra(PackageInstaller.EXTRA_STATUS, PackageInstaller.STATUS_FAILURE)) {
|
||||
PackageInstaller.STATUS_PENDING_USER_ACTION -> {
|
||||
val confirmIntent = intent.getParcelableExtraCompat<Intent>(Intent.EXTRA_INTENT)
|
||||
context.startActivity(confirmIntent?.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK))
|
||||
|
||||
appUpdateNotifier.cancelInstallNotification()
|
||||
val uri = intent.getStringExtra(AppUpdateDownloadJob.EXTRA_FILE_URI) ?: return
|
||||
val title = intent.getStringExtra(AppUpdateDownloadJob.EXTRA_DOWNLOAD_TITLE)
|
||||
appUpdateNotifier.promptInstall(uri.toUri(), title)
|
||||
}
|
||||
PackageInstaller.STATUS_SUCCESS -> {
|
||||
try {
|
||||
appUpdateNotifier.onInstallFinished()
|
||||
} finally {
|
||||
AppUpdateDownloadJob.stop(context)
|
||||
appUpdateNotifier.cancelInstallNotification()
|
||||
}
|
||||
}
|
||||
PackageInstaller.STATUS_FAILURE,
|
||||
PackageInstaller.STATUS_FAILURE_ABORTED,
|
||||
PackageInstaller.STATUS_FAILURE_BLOCKED,
|
||||
PackageInstaller.STATUS_FAILURE_CONFLICT,
|
||||
PackageInstaller.STATUS_FAILURE_INCOMPATIBLE,
|
||||
PackageInstaller.STATUS_FAILURE_INVALID,
|
||||
PackageInstaller.STATUS_FAILURE_STORAGE,
|
||||
-> {
|
||||
if (status != PackageInstaller.STATUS_FAILURE_ABORTED) {
|
||||
context.toast(KMR.strings.could_not_install_update)
|
||||
val uri = intent.getStringExtra(AppUpdateDownloadJob.EXTRA_FILE_URI) ?: return
|
||||
val title = intent.getStringExtra(AppUpdateDownloadJob.EXTRA_DOWNLOAD_TITLE)
|
||||
appUpdateNotifier.cancelInstallNotification()
|
||||
appUpdateNotifier.onInstallError(uri.toUri(), title)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (intent.action == Intent.ACTION_MY_PACKAGE_REPLACED) {
|
||||
/*
|
||||
* System broadcast that is sent when the current application package has been replaced with a new version,
|
||||
* to perform actions when app is updated or reinstalled.
|
||||
*/
|
||||
appUpdateNotifier.onInstallFinished()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
package eu.kanade.tachiyomi.data.updater
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import eu.kanade.tachiyomi.BuildConfig
|
||||
import eu.kanade.tachiyomi.util.system.isInstalledFromFDroid
|
||||
import tachiyomi.core.common.util.lang.withIOContext
|
||||
|
|
@ -15,7 +16,13 @@ class AppUpdateChecker(
|
|||
|
||||
private val getApplicationRelease: GetApplicationRelease by injectLazy()
|
||||
|
||||
suspend fun checkForUpdate(context: Context, forceCheck: Boolean = false): GetApplicationRelease.Result {
|
||||
suspend fun checkForUpdate(
|
||||
context: Context,
|
||||
forceCheck: Boolean = false,
|
||||
// KMK -->
|
||||
pendingAutoUpdate: Boolean = true,
|
||||
// KMK <--
|
||||
): GetApplicationRelease.Result {
|
||||
return withIOContext {
|
||||
val result = getApplicationRelease.await(
|
||||
GetApplicationRelease.Arguments(
|
||||
|
|
@ -28,12 +35,36 @@ class AppUpdateChecker(
|
|||
),
|
||||
)
|
||||
|
||||
when (result) {
|
||||
is GetApplicationRelease.Result.NewUpdate -> AppUpdateNotifier(context).promptUpdate(result.release)
|
||||
is GetApplicationRelease.Result.ThirdPartyInstallation -> AppUpdateNotifier(
|
||||
context,
|
||||
).promptFdroidUpdate()
|
||||
else -> {}
|
||||
// KMK -->
|
||||
if (!peekIntoPreview) {
|
||||
// KMK <--
|
||||
when (result) {
|
||||
is GetApplicationRelease.Result.NewUpdate -> {
|
||||
// KMK -->
|
||||
AppUpdateNotifier.releasePageUrl = result.release.releaseLink
|
||||
// KMK <--
|
||||
AppUpdateNotifier(context).promptUpdate(result.release)
|
||||
}
|
||||
|
||||
is GetApplicationRelease.Result.ThirdPartyInstallation -> AppUpdateNotifier(
|
||||
context,
|
||||
).promptFdroidUpdate()
|
||||
|
||||
else -> {}
|
||||
}
|
||||
|
||||
// KMK -->
|
||||
if (pendingAutoUpdate && result is GetApplicationRelease.Result.NewUpdate) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
AppUpdateDownloadJob.start(
|
||||
context = context,
|
||||
url = result.release.getDownloadLink(),
|
||||
title = result.release.version,
|
||||
scheduled = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
// KMK <--
|
||||
}
|
||||
|
||||
result
|
||||
|
|
|
|||
|
|
@ -1,31 +1,53 @@
|
|||
package eu.kanade.tachiyomi.data.updater
|
||||
|
||||
import android.app.PendingIntent
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageInstaller
|
||||
import android.content.pm.ServiceInfo
|
||||
import android.os.Build
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.work.BackoffPolicy
|
||||
import androidx.work.Constraints
|
||||
import androidx.work.CoroutineWorker
|
||||
import androidx.work.Data
|
||||
import androidx.work.ExistingWorkPolicy
|
||||
import androidx.work.ForegroundInfo
|
||||
import androidx.work.NetworkType
|
||||
import androidx.work.OneTimeWorkRequestBuilder
|
||||
import androidx.work.OutOfQuotaPolicy
|
||||
import androidx.work.WorkerParameters
|
||||
import androidx.work.workDataOf
|
||||
import eu.kanade.tachiyomi.BuildConfig
|
||||
import eu.kanade.tachiyomi.data.notification.Notifications
|
||||
import eu.kanade.tachiyomi.network.NetworkHelper
|
||||
import eu.kanade.tachiyomi.network.ProgressListener
|
||||
import eu.kanade.tachiyomi.util.storage.getUriCompat
|
||||
import eu.kanade.tachiyomi.util.system.connectivityManager
|
||||
import eu.kanade.tachiyomi.util.system.isConnectedToWifi
|
||||
import eu.kanade.tachiyomi.util.system.notificationManager
|
||||
import eu.kanade.tachiyomi.util.system.setForegroundSafely
|
||||
import eu.kanade.tachiyomi.util.system.toast
|
||||
import eu.kanade.tachiyomi.util.system.workManager
|
||||
import logcat.LogPriority
|
||||
import exh.log.xLogE
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.withContext
|
||||
import okhttp3.internal.http2.ErrorCode
|
||||
import okhttp3.internal.http2.StreamResetException
|
||||
import tachiyomi.core.common.i18n.stringResource
|
||||
import tachiyomi.core.common.util.lang.launchUI
|
||||
import tachiyomi.core.common.util.lang.withIOContext
|
||||
import tachiyomi.core.common.util.system.logcat
|
||||
import tachiyomi.domain.UnsortedPreferences
|
||||
import tachiyomi.domain.release.service.AppUpdatePolicy
|
||||
import tachiyomi.i18n.MR
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import java.io.File
|
||||
import java.lang.ref.WeakReference
|
||||
import java.util.concurrent.TimeUnit
|
||||
import kotlin.coroutines.cancellation.CancellationException
|
||||
|
||||
class AppUpdateDownloadJob(private val context: Context, workerParams: WorkerParameters) :
|
||||
|
|
@ -34,7 +56,28 @@ class AppUpdateDownloadJob(private val context: Context, workerParams: WorkerPar
|
|||
private val notifier = AppUpdateNotifier(context)
|
||||
private val network: NetworkHelper by injectLazy()
|
||||
|
||||
// KMK -->
|
||||
private val preferences = Injekt.get<UnsortedPreferences>()
|
||||
// KMK <--
|
||||
|
||||
override suspend fun doWork(): Result {
|
||||
// KMK -->
|
||||
val idleRun = inputData.getBoolean(SCHEDULED_RUN, false)
|
||||
if (idleRun) {
|
||||
if (!context.packageManager.canRequestPackageInstalls()) {
|
||||
return Result.failure()
|
||||
}
|
||||
val restrictions = preferences.appShouldAutoUpdate().get()
|
||||
if ((AppUpdatePolicy.DEVICE_ONLY_ON_WIFI in restrictions) &&
|
||||
!context.isConnectedToWifi() ||
|
||||
(AppUpdatePolicy.DEVICE_NETWORK_NOT_METERED in restrictions) &&
|
||||
context.connectivityManager.isActiveNetworkMetered
|
||||
) {
|
||||
return Result.retry()
|
||||
}
|
||||
}
|
||||
// KMK <--
|
||||
|
||||
val url = inputData.getString(EXTRA_DOWNLOAD_URL)
|
||||
val title = inputData.getString(EXTRA_DOWNLOAD_TITLE) ?: context.stringResource(MR.strings.app_name)
|
||||
|
||||
|
|
@ -43,11 +86,18 @@ class AppUpdateDownloadJob(private val context: Context, workerParams: WorkerPar
|
|||
}
|
||||
|
||||
setForegroundSafely()
|
||||
// KMK -->
|
||||
instance = WeakReference(this)
|
||||
// KMK <--
|
||||
|
||||
withIOContext {
|
||||
downloadApk(title, url)
|
||||
}
|
||||
|
||||
// KMK -->
|
||||
instance = null
|
||||
// KMK <--
|
||||
|
||||
return Result.success()
|
||||
}
|
||||
|
||||
|
|
@ -68,7 +118,7 @@ class AppUpdateDownloadJob(private val context: Context, workerParams: WorkerPar
|
|||
*
|
||||
* @param url url location of file
|
||||
*/
|
||||
private fun downloadApk(title: String, url: String) {
|
||||
private suspend fun downloadApk(title: String, url: String) = coroutineScope {
|
||||
// Show notification download starting.
|
||||
notifier.onDownloadStarted(title)
|
||||
|
||||
|
|
@ -107,14 +157,27 @@ class AppUpdateDownloadJob(private val context: Context, workerParams: WorkerPar
|
|||
try {
|
||||
// File where the apk will be saved.
|
||||
val apkFile = File(context.externalCacheDir, "update.apk")
|
||||
|
||||
// KMK -->
|
||||
network.downloadFileWithResume(url, apkFile, progressListener)
|
||||
if (isStopped) {
|
||||
cancel()
|
||||
return@coroutineScope
|
||||
}
|
||||
// KMK <--
|
||||
|
||||
notifier.cancel()
|
||||
notifier.promptInstall(apkFile.getUriCompat(context))
|
||||
// KMK -->
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
startInstalling(apkFile, title)
|
||||
} else {
|
||||
// KMK <--
|
||||
notifier.promptInstall(apkFile.getUriCompat(context))
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
logcat(LogPriority.ERROR) { e.toString() }
|
||||
xLogE("App update stopped:", e)
|
||||
val shouldCancel = e is CancellationException ||
|
||||
isStopped ||
|
||||
(e is StreamResetException && e.errorCode == ErrorCode.CANCEL)
|
||||
if (shouldCancel) {
|
||||
notifier.cancel()
|
||||
|
|
@ -129,26 +192,121 @@ class AppUpdateDownloadJob(private val context: Context, workerParams: WorkerPar
|
|||
}
|
||||
}
|
||||
|
||||
// KMK -->
|
||||
@RequiresApi(31)
|
||||
private suspend fun startInstalling(file: File, title: String) {
|
||||
try {
|
||||
val packageInstaller = context.packageManager.packageInstaller
|
||||
val data = file.inputStream()
|
||||
|
||||
val installParams = PackageInstaller.SessionParams(
|
||||
PackageInstaller.SessionParams.MODE_FULL_INSTALL,
|
||||
)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
installParams.setRequireUserAction(PackageInstaller.SessionParams.USER_ACTION_NOT_REQUIRED)
|
||||
}
|
||||
val sessionId = packageInstaller.createSession(installParams)
|
||||
val session = packageInstaller.openSession(sessionId)
|
||||
session.openWrite("package", 0, -1).use { packageInSession ->
|
||||
data.copyTo(packageInSession)
|
||||
}
|
||||
|
||||
val newIntent = Intent(context, AppUpdateBroadcast::class.java)
|
||||
.setAction(PACKAGE_INSTALLED_ACTION)
|
||||
.putExtra(EXTRA_FILE_URI, file.getUriCompat(context).toString())
|
||||
.putExtra(EXTRA_DOWNLOAD_TITLE, title)
|
||||
|
||||
val pendingIntent = PendingIntent.getBroadcast(
|
||||
context,
|
||||
sessionId,
|
||||
newIntent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE,
|
||||
)
|
||||
val statusReceiver = pendingIntent.intentSender
|
||||
session.commit(statusReceiver)
|
||||
notifier.onInstalling()
|
||||
withContext(Dispatchers.IO) {
|
||||
data.close()
|
||||
}
|
||||
launchUI {
|
||||
delay(1000)
|
||||
val hasNotification = context.notificationManager
|
||||
.activeNotifications.any { it.id == Notifications.ID_APP_INSTALL }
|
||||
// If the package manager crashes for whatever reason (china phone)
|
||||
// set a timeout and let the user manually install
|
||||
if (packageInstaller.getSessionInfo(sessionId) == null && !hasNotification) {
|
||||
notifier.cancelInstallNotification()
|
||||
notifier.promptInstall(file.getUriCompat(context))
|
||||
}
|
||||
}
|
||||
} catch (error: Exception) {
|
||||
// Either install package can't be found (probably bots) or there's a security exception
|
||||
// with the download manager. Nothing we can workaround.
|
||||
context.toast(error.message)
|
||||
notifier.cancelInstallNotification()
|
||||
notifier.promptInstall(file.getUriCompat(context))
|
||||
}
|
||||
}
|
||||
// KMK <--
|
||||
|
||||
companion object {
|
||||
private const val TAG = "AppUpdateDownload"
|
||||
|
||||
// KMK -->
|
||||
const val PACKAGE_INSTALLED_ACTION =
|
||||
"${BuildConfig.APPLICATION_ID}.SESSION_SELF_API_PACKAGE_INSTALLED"
|
||||
internal const val EXTRA_FILE_URI = "${BuildConfig.APPLICATION_ID}.AppInstaller.FILE_URI"
|
||||
private const val SCHEDULED_RUN = "scheduled_run"
|
||||
// KMK <--
|
||||
|
||||
const val EXTRA_DOWNLOAD_URL = "DOWNLOAD_URL"
|
||||
const val EXTRA_DOWNLOAD_TITLE = "DOWNLOAD_TITLE"
|
||||
|
||||
fun start(context: Context, url: String, title: String? = null) {
|
||||
val constraints = Constraints(
|
||||
requiredNetworkType = NetworkType.CONNECTED,
|
||||
)
|
||||
// KMK -->
|
||||
private var instance: WeakReference<AppUpdateDownloadJob>? = null
|
||||
// KMK <--
|
||||
|
||||
fun start(
|
||||
context: Context,
|
||||
url: String,
|
||||
title: String? = null,
|
||||
// KMK -->
|
||||
scheduled: Boolean = false,
|
||||
// KMK <--
|
||||
) {
|
||||
val data = Data.Builder()
|
||||
data.putString(EXTRA_DOWNLOAD_URL, url)
|
||||
data.putString(EXTRA_DOWNLOAD_TITLE, title)
|
||||
val request = OneTimeWorkRequestBuilder<AppUpdateDownloadJob>()
|
||||
.setConstraints(constraints)
|
||||
.addTag(TAG)
|
||||
.setInputData(
|
||||
workDataOf(
|
||||
EXTRA_DOWNLOAD_URL to url,
|
||||
EXTRA_DOWNLOAD_TITLE to title,
|
||||
),
|
||||
)
|
||||
.apply {
|
||||
// KMK -->
|
||||
if (scheduled) {
|
||||
data.putBoolean(SCHEDULED_RUN, true)
|
||||
val restrictions = Injekt.get<UnsortedPreferences>().appShouldAutoUpdate().get()
|
||||
val constraints = Constraints(
|
||||
requiredNetworkType = if (AppUpdatePolicy.DEVICE_NETWORK_NOT_METERED in restrictions) {
|
||||
NetworkType.UNMETERED
|
||||
} else {
|
||||
NetworkType.CONNECTED
|
||||
},
|
||||
requiresCharging = AppUpdatePolicy.DEVICE_CHARGING in restrictions,
|
||||
requiresBatteryNotLow = true,
|
||||
)
|
||||
setConstraints(constraints)
|
||||
setInitialDelay(10, TimeUnit.MINUTES)
|
||||
setBackoffCriteria(BackoffPolicy.LINEAR, 10, TimeUnit.MINUTES)
|
||||
} else {
|
||||
setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
|
||||
// KMK <--
|
||||
setConstraints(
|
||||
Constraints(
|
||||
requiredNetworkType = NetworkType.CONNECTED,
|
||||
),
|
||||
)
|
||||
}
|
||||
setInputData(data.build())
|
||||
}
|
||||
.build()
|
||||
|
||||
context.workManager.enqueueUniqueWork(TAG, ExistingWorkPolicy.REPLACE, request)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,61 @@
|
|||
package eu.kanade.tachiyomi.data.updater
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.work.Constraints
|
||||
import androidx.work.CoroutineWorker
|
||||
import androidx.work.ExistingPeriodicWorkPolicy
|
||||
import androidx.work.NetworkType
|
||||
import androidx.work.PeriodicWorkRequestBuilder
|
||||
import androidx.work.WorkManager
|
||||
import androidx.work.WorkerParameters
|
||||
import eu.kanade.tachiyomi.data.notification.Notifications
|
||||
import eu.kanade.tachiyomi.util.system.notificationManager
|
||||
import exh.log.xLogE
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class AppUpdateJob(private val context: Context, workerParams: WorkerParameters) :
|
||||
CoroutineWorker(context, workerParams) {
|
||||
|
||||
override suspend fun doWork(): Result = coroutineScope {
|
||||
try {
|
||||
AppUpdateChecker().checkForUpdate(context)
|
||||
Result.success()
|
||||
} catch (e: Exception) {
|
||||
xLogE("Unable to check for update", e)
|
||||
Result.failure()
|
||||
}
|
||||
}
|
||||
|
||||
fun NotificationCompat.Builder.update(block: NotificationCompat.Builder.() -> Unit) {
|
||||
block()
|
||||
context.notificationManager.notify(Notifications.ID_APP_UPDATER, build())
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "AppUpdateChecker"
|
||||
|
||||
fun setupTask(context: Context) {
|
||||
val constraints = Constraints.Builder()
|
||||
.setRequiredNetworkType(NetworkType.CONNECTED)
|
||||
.build()
|
||||
|
||||
val request = PeriodicWorkRequestBuilder<AppUpdateJob>(
|
||||
3,
|
||||
TimeUnit.DAYS,
|
||||
3,
|
||||
TimeUnit.HOURS,
|
||||
)
|
||||
.addTag(TAG)
|
||||
.setConstraints(constraints)
|
||||
.build()
|
||||
|
||||
WorkManager.getInstance(context).enqueueUniquePeriodicWork(TAG, ExistingPeriodicWorkPolicy.UPDATE, request)
|
||||
}
|
||||
|
||||
fun cancelTask(context: Context) {
|
||||
WorkManager.getInstance(context).cancelAllWorkByTag(TAG)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +1,12 @@
|
|||
package eu.kanade.tachiyomi.data.updater
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.PendingIntent
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.net.toUri
|
||||
import eu.kanade.tachiyomi.BuildConfig
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.notification.NotificationHandler
|
||||
import eu.kanade.tachiyomi.data.notification.NotificationReceiver
|
||||
|
|
@ -35,7 +35,11 @@ internal class AppUpdateNotifier(private val context: Context) {
|
|||
NotificationReceiver.dismissNotification(context, Notifications.ID_APP_UPDATER)
|
||||
}
|
||||
|
||||
@SuppressLint("LaunchActivityFromNotification")
|
||||
/**
|
||||
* Create a notification to prompt user there is new update, with action to:
|
||||
* - Download update [AppUpdateDownloadJob.start]
|
||||
* - Show Github's release notes
|
||||
*/
|
||||
fun promptUpdate(release: Release) {
|
||||
val updateIntent = NotificationReceiver.downloadAppUpdatePendingBroadcast(
|
||||
context,
|
||||
|
|
@ -115,9 +119,12 @@ internal class AppUpdateNotifier(private val context: Context) {
|
|||
*
|
||||
* @param uri path location of apk.
|
||||
*/
|
||||
fun promptInstall(uri: Uri) {
|
||||
fun promptInstall(uri: Uri, title: String? = null) {
|
||||
val installIntent = NotificationHandler.installApkPendingActivity(context, uri)
|
||||
with(notificationBuilder) {
|
||||
// KMK -->
|
||||
title?.let { setContentTitle(title) }
|
||||
// KMK <--
|
||||
setContentText(context.stringResource(MR.strings.update_check_notification_download_complete))
|
||||
setSmallIcon(android.R.drawable.stat_sys_download_done)
|
||||
setOnlyAlertOnce(false)
|
||||
|
|
@ -207,4 +214,97 @@ internal class AppUpdateNotifier(private val context: Context) {
|
|||
}
|
||||
notificationBuilder.show(Notifications.ID_APP_UPDATE_ERROR)
|
||||
}
|
||||
|
||||
// KMK -->
|
||||
fun onInstalling() {
|
||||
with(notificationBuilder) {
|
||||
setContentText(context.stringResource(MR.strings.ext_installing))
|
||||
setSmallIcon(android.R.drawable.stat_sys_download)
|
||||
setProgress(0, 0, true)
|
||||
setOnlyAlertOnce(true)
|
||||
clearActions()
|
||||
show(Notifications.ID_APP_INSTALL)
|
||||
}
|
||||
}
|
||||
|
||||
/** Call when apk download is finished. */
|
||||
fun onInstallFinished() {
|
||||
with(notificationBuilder) {
|
||||
setContentTitle(context.stringResource(KMR.strings.update_completed))
|
||||
setContentText(context.stringResource(MR.strings.updated_version, BuildConfig.VERSION_NAME))
|
||||
setSmallIcon(R.drawable.ic_komikku)
|
||||
setAutoCancel(true)
|
||||
setOngoing(false)
|
||||
setProgress(0, 0, false)
|
||||
val pendingIntent = PendingIntent.getActivity(
|
||||
context,
|
||||
0,
|
||||
context.packageManager.getLaunchIntentForPackage(BuildConfig.APPLICATION_ID),
|
||||
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE,
|
||||
)
|
||||
setContentIntent(pendingIntent)
|
||||
clearActions()
|
||||
addAction(
|
||||
R.drawable.ic_launch,
|
||||
context.stringResource(KMR.strings.open),
|
||||
pendingIntent,
|
||||
)
|
||||
addReleasePageAction()
|
||||
show(Notifications.ID_APP_INSTALLED)
|
||||
}
|
||||
}
|
||||
|
||||
private fun NotificationCompat.Builder.addReleasePageAction() {
|
||||
releasePageUrl?.let { releaseUrl ->
|
||||
val releaseIntent = Intent(Intent.ACTION_VIEW, releaseUrl.toUri()).apply {
|
||||
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
|
||||
}
|
||||
addAction(
|
||||
R.drawable.ic_info_24dp,
|
||||
context.stringResource(KMR.strings.release_page),
|
||||
PendingIntent.getActivity(
|
||||
context,
|
||||
releaseUrl.hashCode(),
|
||||
releaseIntent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun onInstallError(uri: Uri, title: String?) {
|
||||
with(notificationBuilder) {
|
||||
title?.let { setContentTitle(title) }
|
||||
setContentText(context.stringResource(KMR.strings.could_not_install_update))
|
||||
setSmallIcon(R.drawable.ic_warning_white_24dp)
|
||||
setOnlyAlertOnce(false)
|
||||
setAutoCancel(false)
|
||||
setProgress(0, 0, false)
|
||||
clearActions()
|
||||
// Retry action
|
||||
addAction(
|
||||
R.drawable.ic_refresh_24dp,
|
||||
context.stringResource(MR.strings.action_retry),
|
||||
NotificationHandler.installApkPendingActivity(context, uri),
|
||||
)
|
||||
// Cancel action
|
||||
addAction(
|
||||
R.drawable.ic_close_24dp,
|
||||
context.stringResource(MR.strings.action_cancel),
|
||||
NotificationReceiver.dismissNotificationPendingBroadcast(context, Notifications.ID_APP_UPDATE_ERROR),
|
||||
)
|
||||
addReleasePageAction()
|
||||
}
|
||||
notificationBuilder.show(Notifications.ID_APP_UPDATE_ERROR)
|
||||
}
|
||||
|
||||
fun cancelInstallNotification() {
|
||||
NotificationReceiver.dismissNotification(context, Notifications.ID_APP_INSTALL)
|
||||
NotificationReceiver.dismissNotification(context, Notifications.ID_APP_UPDATE_PROMPT)
|
||||
}
|
||||
|
||||
companion object {
|
||||
var releasePageUrl: String? = null
|
||||
}
|
||||
// KMK <--
|
||||
}
|
||||
|
|
|
|||
|
|
@ -75,6 +75,7 @@ import eu.kanade.tachiyomi.data.coil.MangaCoverMetadata
|
|||
import eu.kanade.tachiyomi.data.download.DownloadCache
|
||||
import eu.kanade.tachiyomi.data.notification.NotificationReceiver
|
||||
import eu.kanade.tachiyomi.data.updater.AppUpdateChecker
|
||||
import eu.kanade.tachiyomi.data.updater.AppUpdateJob
|
||||
import eu.kanade.tachiyomi.extension.api.ExtensionApi
|
||||
import eu.kanade.tachiyomi.ui.base.activity.BaseActivity
|
||||
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceScreen
|
||||
|
|
@ -438,6 +439,9 @@ class MainActivity : BaseActivity() {
|
|||
LaunchedEffect(Unit) {
|
||||
if (BuildConfig.INCLUDE_UPDATER) {
|
||||
try {
|
||||
// KMK -->
|
||||
AppUpdateJob.setupTask(context)
|
||||
// KMK <--
|
||||
val result = AppUpdateChecker().checkForUpdate(context)
|
||||
if (result is GetApplicationRelease.Result.NewUpdate) {
|
||||
val updateScreen = NewUpdateScreen(
|
||||
|
|
|
|||
|
|
@ -48,5 +48,6 @@ val migrations: List<Migration>
|
|||
// KMK -->
|
||||
OfficialExtensionRepositoryMigration(),
|
||||
IntegratedHentaiMigration(),
|
||||
SetupAppUpdateMigration(),
|
||||
// KMK <--
|
||||
)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
package mihon.core.migration.migrations
|
||||
|
||||
import android.app.Application
|
||||
import eu.kanade.tachiyomi.BuildConfig
|
||||
import eu.kanade.tachiyomi.data.updater.AppUpdateJob
|
||||
import mihon.core.migration.Migration
|
||||
import mihon.core.migration.MigrationContext
|
||||
|
||||
class SetupAppUpdateMigration : Migration {
|
||||
override val version: Float = Migration.ALWAYS
|
||||
|
||||
override suspend fun invoke(migrationContext: MigrationContext): Boolean {
|
||||
if (!BuildConfig.INCLUDE_UPDATER) return false
|
||||
|
||||
val context = migrationContext.get<Application>() ?: return false
|
||||
AppUpdateJob.setupTask(context)
|
||||
return true
|
||||
}
|
||||
}
|
||||
10
app/src/main/res/drawable/ic_launch.xml
Normal file
10
app/src/main/res/drawable/ic_launch.xml
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
<!-- drawable/launch.xml -->
|
||||
<!-- https://pictogrammers.com/library/mdi/ -->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:height="24dp"
|
||||
android:width="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path android:fillColor="#000000"
|
||||
android:pathData="M14,3V5H17.59L7.76,14.83L9.17,16.24L19,6.41V10H21V3M19,19H5V5H12V3H5C3.89,3 3,3.89 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V12H19V19Z" />
|
||||
</vector>
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
package tachiyomi.data.release
|
||||
|
||||
import dev.icerock.moko.graphics.BuildConfig
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.network.NetworkHelper
|
||||
import eu.kanade.tachiyomi.network.awaitSuccess
|
||||
|
|
@ -25,9 +26,14 @@ class ReleaseServiceImpl(
|
|||
|
||||
// KMK -->
|
||||
override suspend fun releaseNotes(repository: String): List<Release> {
|
||||
val releases = if (BuildConfig.DEBUG) {
|
||||
"https://raw.githubusercontent.com/$repository/refs/heads/master/app/src/debug/res/raw/releases.json"
|
||||
} else {
|
||||
"https://api.github.com/repos/$repository/releases"
|
||||
}
|
||||
return with(json) {
|
||||
networkService.client
|
||||
.newCall(GET("https://api.github.com/repos/$repository/releases"))
|
||||
.newCall(GET(releases))
|
||||
.awaitSuccess()
|
||||
.parseAs<List<GithubRelease>>()
|
||||
.map(releaseMapper)
|
||||
|
|
|
|||
|
|
@ -2,10 +2,19 @@ package tachiyomi.domain
|
|||
|
||||
import tachiyomi.core.common.preference.Preference
|
||||
import tachiyomi.core.common.preference.PreferenceStore
|
||||
import tachiyomi.domain.release.service.AppUpdatePolicy
|
||||
|
||||
class UnsortedPreferences(
|
||||
private val preferenceStore: PreferenceStore,
|
||||
) {
|
||||
// KMK -->
|
||||
fun appShouldAutoUpdate() = preferenceStore.getStringSet(
|
||||
"should_auto_update",
|
||||
setOf(
|
||||
AppUpdatePolicy.DEVICE_ONLY_ON_WIFI,
|
||||
),
|
||||
)
|
||||
// KMK <--
|
||||
|
||||
// SY -->
|
||||
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ class GetApplicationRelease(
|
|||
val now = Instant.now()
|
||||
|
||||
// Limit checks to once every 3 days at most
|
||||
val nextCheckTime = Instant.ofEpochMilli(lastChecked.get()).plus(3, ChronoUnit.DAYS)
|
||||
val nextCheckTime = Instant.ofEpochMilli(lastChecked.get()).plus(2, ChronoUnit.DAYS)
|
||||
if (!arguments.forceCheck && now.isBefore(nextCheckTime)) {
|
||||
return Result.NoNewUpdate
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
package tachiyomi.domain.release.service
|
||||
|
||||
class AppUpdatePolicy {
|
||||
companion object {
|
||||
const val DEVICE_ONLY_ON_WIFI = "wifi"
|
||||
const val DEVICE_NETWORK_NOT_METERED = "network_not_metered"
|
||||
const val DEVICE_CHARGING = "ac"
|
||||
}
|
||||
}
|
||||
|
|
@ -150,7 +150,7 @@ class GetApplicationReleaseTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `When now is before three days expect no new update`() = runTest {
|
||||
fun `When now is before two days expect no new update`() = runTest {
|
||||
every { preference.get() } returns Instant.now().toEpochMilli()
|
||||
every { preference.set(any()) }.answers { }
|
||||
|
||||
|
|
|
|||
|
|
@ -102,6 +102,7 @@
|
|||
<string name="pref_private_installer_warning">Avoid using Private Installer because it\'s not stable yet, will lead to really slow start/resume App.</string>
|
||||
<string name="saved_searches_feeds">Saved Searches & Feeds</string>
|
||||
<string name="preload_library_cover_color">Preload library's cover color</string>
|
||||
<string name="auto_update_app">Auto-update app</string>
|
||||
|
||||
<!-- Onboarding section -->
|
||||
<string name="onboarding_permission_external_storage">Access media files permission</string>
|
||||
|
|
@ -120,6 +121,10 @@
|
|||
|
||||
<!--UpdateCheck-->
|
||||
<string name="update_check_try_now">Try now</string>
|
||||
<string name="update_completed">Update completed</string>
|
||||
<string name="open">Open</string>
|
||||
<string name="release_page">Release page</string>
|
||||
<string name="could_not_install_update">Could not install update</string>
|
||||
|
||||
<!--UpdateCheck Notifications-->
|
||||
<string name="update_check_notification_preview_available">Coming (soon) updates!</string>
|
||||
|
|
|
|||
Loading…
Reference in a new issue