mirror of
https://github.com/tachiyomiorg/tachiyomi.git
synced 2024-11-20 00:39:14 +01:00
[A12] Automatically update extensions that can be silently updated
This commit is contained in:
parent
51ec6d9bf6
commit
9f31529870
@ -47,11 +47,14 @@ object Notifications {
|
||||
/**
|
||||
* Notification channel and ids used by the library updater.
|
||||
*/
|
||||
private const val GROUP_EXTENSION_UPDATES = "group_extension_updates"
|
||||
const val CHANNEL_UPDATES_TO_EXTS = "updates_ext_channel"
|
||||
const val ID_UPDATES_TO_EXTS = -401
|
||||
|
||||
const val CHANNEL_EXT_PROGRESS = "ext_update_progress_channel"
|
||||
const val ID_EXTENSION_PROGRESS = -402
|
||||
const val CHANNEL_EXT_UPDATED = "ext_updated_channel"
|
||||
const val ID_UPDATED_EXTS = -403
|
||||
|
||||
private const val GROUP_BACKUP_RESTORE = "group_backup_restore"
|
||||
const val CHANNEL_BACKUP_RESTORE_PROGRESS = "backup_restore_progress_channel"
|
||||
@ -84,6 +87,7 @@ object Notifications {
|
||||
|
||||
listOf(
|
||||
NotificationChannelGroup(GROUP_BACKUP_RESTORE, context.getString(R.string.backup_and_restore)),
|
||||
NotificationChannelGroup(GROUP_EXTENSION_UPDATES, context.getString(R.string.extension_updates)),
|
||||
).forEach(context.notificationManager::createNotificationChannelGroup)
|
||||
|
||||
val channels = listOf(
|
||||
@ -108,9 +112,11 @@ object Notifications {
|
||||
},
|
||||
NotificationChannel(
|
||||
CHANNEL_UPDATES_TO_EXTS,
|
||||
context.getString(R.string.extension_updates),
|
||||
context.getString(R.string.extension_updates_pending),
|
||||
NotificationManager.IMPORTANCE_DEFAULT
|
||||
),
|
||||
).apply {
|
||||
group = GROUP_EXTENSION_UPDATES
|
||||
},
|
||||
NotificationChannel(
|
||||
CHANNEL_NEW_CHAPTERS,
|
||||
context.getString(R.string.new_chapters),
|
||||
@ -147,9 +153,17 @@ object Notifications {
|
||||
context.getString(R.string.updating_extensions),
|
||||
NotificationManager.IMPORTANCE_LOW
|
||||
).apply {
|
||||
group = GROUP_EXTENSION_UPDATES
|
||||
setShowBadge(false)
|
||||
setSound(null, null)
|
||||
},
|
||||
NotificationChannel(
|
||||
CHANNEL_EXT_UPDATED,
|
||||
context.getString(R.string.extensions_updated),
|
||||
NotificationManager.IMPORTANCE_DEFAULT
|
||||
).apply {
|
||||
group = GROUP_EXTENSION_UPDATES
|
||||
},
|
||||
NotificationChannel(
|
||||
CHANNEL_UPDATED,
|
||||
context.getString(R.string.update_completed),
|
||||
|
@ -231,6 +231,8 @@ object PreferenceKeys {
|
||||
|
||||
const val shouldAutoUpdate = "should_auto_update"
|
||||
|
||||
const val autoUpdateExtensions = "auto_update_extensions"
|
||||
|
||||
const val defaultChapterFilterByRead = "default_chapter_filter_by_read"
|
||||
|
||||
const val defaultChapterFilterByDownloaded = "default_chapter_filter_by_downloaded"
|
||||
|
@ -431,6 +431,8 @@ class PreferencesHelper(val context: Context) {
|
||||
|
||||
fun shouldAutoUpdate() = prefs.getInt(Keys.shouldAutoUpdate, AutoUpdaterJob.ONLY_ON_UNMETERED)
|
||||
|
||||
fun autoUpdateExtensions() = prefs.getInt(Keys.autoUpdateExtensions, AutoUpdaterJob.ONLY_ON_UNMETERED)
|
||||
|
||||
fun filterChapterByRead() = flowPrefs.getInt(Keys.defaultChapterFilterByRead, Manga.SHOW_ALL)
|
||||
|
||||
fun filterChapterByDownloaded() = flowPrefs.getInt(Keys.defaultChapterFilterByDownloaded, Manga.SHOW_ALL)
|
||||
|
@ -29,7 +29,7 @@ class ExtensionInstallNotifier(private val context: Context) {
|
||||
* Cached progress notification to avoid creating a lot.
|
||||
*/
|
||||
val progressNotificationBuilder by lazy {
|
||||
context.notificationBuilder(Notifications.CHANNEL_UPDATES_TO_EXTS) {
|
||||
context.notificationBuilder(Notifications.CHANNEL_EXT_PROGRESS) {
|
||||
setContentTitle(context.getString(R.string.app_name))
|
||||
setSmallIcon(android.R.drawable.stat_sys_download)
|
||||
setLargeIcon(notificationBitmap)
|
||||
@ -54,9 +54,35 @@ class ExtensionInstallNotifier(private val context: Context) {
|
||||
context.notificationManager.notify(
|
||||
Notifications.ID_EXTENSION_PROGRESS,
|
||||
progressNotificationBuilder
|
||||
.setChannelId(Notifications.CHANNEL_EXT_PROGRESS)
|
||||
.setContentTitle(context.getString(R.string.updating_extensions))
|
||||
.setProgress(max, progress, progress == 0)
|
||||
.build()
|
||||
)
|
||||
}
|
||||
|
||||
fun showUpdatedNotification(extensions: List<ExtensionManager.ExtensionInfo>, hideContent: Boolean) {
|
||||
context.notificationManager.notify(
|
||||
Notifications.ID_UPDATED_EXTS,
|
||||
progressNotificationBuilder
|
||||
.setChannelId(Notifications.CHANNEL_EXT_UPDATED)
|
||||
.setContentTitle(
|
||||
context.resources.getQuantityString(
|
||||
R.plurals.extensions_updated_plural,
|
||||
extensions.size,
|
||||
extensions.size
|
||||
)
|
||||
)
|
||||
.setSmallIcon(R.drawable.ic_extension_updated_24dp)
|
||||
.setOngoing(false)
|
||||
.setContentIntent(NotificationReceiver.openExtensionsPendingActivity(context))
|
||||
.clearActions()
|
||||
.setProgress(0, 0, false).apply {
|
||||
if (!hideContent) {
|
||||
setContentText(extensions.joinToString(", ") { it.name })
|
||||
}
|
||||
}
|
||||
.build()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -5,9 +5,11 @@ import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.IBinder
|
||||
import android.os.PowerManager
|
||||
import androidx.work.NetworkType
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.notification.Notifications
|
||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||
import eu.kanade.tachiyomi.data.preference.getOrDefault
|
||||
import eu.kanade.tachiyomi.extension.ExtensionManager.ExtensionInfo
|
||||
import eu.kanade.tachiyomi.extension.model.Extension
|
||||
import eu.kanade.tachiyomi.util.system.notificationManager
|
||||
@ -26,6 +28,7 @@ import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.util.ArrayList
|
||||
import java.util.concurrent.TimeUnit
|
||||
import kotlin.math.max
|
||||
|
||||
class ExtensionInstallService(
|
||||
val extensionManager: ExtensionManager = Injekt.get(),
|
||||
@ -63,7 +66,11 @@ class ExtensionInstallService(
|
||||
*/
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
if (intent == null) return START_NOT_STICKY
|
||||
if (!preferences.hasPromptedBeforeUpdateAll().get()) {
|
||||
val showUpdated = intent.getIntExtra(KEY_SHOW_UPDATED, 0)
|
||||
val showUpdatedNotification = showUpdated > 0
|
||||
val reRunUpdateCheck = showUpdated > 1
|
||||
|
||||
if (!showUpdatedNotification && !preferences.hasPromptedBeforeUpdateAll().get()) {
|
||||
toast(R.string.some_extensions_may_prompt)
|
||||
preferences.hasPromptedBeforeUpdateAll().set(true)
|
||||
}
|
||||
@ -79,14 +86,19 @@ class ExtensionInstallService(
|
||||
}
|
||||
?: return START_NOT_STICKY
|
||||
var installed = 0
|
||||
val installedExtensions = mutableListOf<ExtensionInfo>()
|
||||
job = serviceScope.launch {
|
||||
val results = list.map {
|
||||
val results = list.map { extension ->
|
||||
async {
|
||||
requestSemaphore.withPermit {
|
||||
extensionManager.installExtension(it, serviceScope)
|
||||
extensionManager.installExtension(extension, serviceScope)
|
||||
.collect {
|
||||
if (it.first.isCompleted()) {
|
||||
installedExtensions.add(extension)
|
||||
installed++
|
||||
val prefCount =
|
||||
preferences.extensionUpdatesCount().getOrDefault()
|
||||
preferences.extensionUpdatesCount().set(max(prefCount - 1, 0))
|
||||
}
|
||||
notifier.showProgressNotification(installed, list.size)
|
||||
}
|
||||
@ -95,9 +107,18 @@ class ExtensionInstallService(
|
||||
}
|
||||
results.awaitAll()
|
||||
}
|
||||
job?.invokeOnCompletion { stopSelf(startId) }
|
||||
|
||||
return START_REDELIVER_INTENT
|
||||
job?.invokeOnCompletion {
|
||||
if (showUpdatedNotification) {
|
||||
notifier.showUpdatedNotification(installedExtensions, preferences.hideNotificationContent())
|
||||
}
|
||||
if (reRunUpdateCheck || installedExtensions.size != list.size) {
|
||||
ExtensionUpdateJob.runJobAgain(this, NetworkType.CONNECTED)
|
||||
}
|
||||
stopSelf(startId)
|
||||
}
|
||||
|
||||
return START_NOT_STICKY
|
||||
}
|
||||
|
||||
/**
|
||||
@ -149,11 +170,13 @@ class ExtensionInstallService(
|
||||
* Key that defines what should be updated.
|
||||
*/
|
||||
private const val KEY_EXTENSION = "extension"
|
||||
private const val KEY_SHOW_UPDATED = "show_updated"
|
||||
|
||||
fun jobIntent(context: Context, extensions: List<Extension.Available>): Intent {
|
||||
fun jobIntent(context: Context, extensions: List<Extension.Available>, showUpdatedExtension: Int = 0): Intent {
|
||||
return Intent(context, ExtensionInstallService::class.java).apply {
|
||||
val info = extensions.map(::ExtensionInfo)
|
||||
putParcelableArrayListExtra(KEY_EXTENSION, ArrayList(info))
|
||||
putExtra(KEY_SHOW_UPDATED, showUpdatedExtension)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -182,6 +182,10 @@ class ExtensionManager(
|
||||
return untrustedExtensionsRelay.asObservable()
|
||||
}
|
||||
|
||||
fun isInstalledByApp(extension: Extension.Available): Boolean {
|
||||
return ExtensionLoader.isExtensionInstalledByApp(context, extension.pkgName)
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the available extensions in the [api] and updates [availableExtensions].
|
||||
*/
|
||||
|
@ -9,7 +9,9 @@ import androidx.core.content.ContextCompat
|
||||
import androidx.work.Constraints
|
||||
import androidx.work.CoroutineWorker
|
||||
import androidx.work.ExistingPeriodicWorkPolicy
|
||||
import androidx.work.ExistingWorkPolicy
|
||||
import androidx.work.NetworkType
|
||||
import androidx.work.OneTimeWorkRequestBuilder
|
||||
import androidx.work.PeriodicWorkRequestBuilder
|
||||
import androidx.work.WorkManager
|
||||
import androidx.work.WorkerParameters
|
||||
@ -18,8 +20,10 @@ import eu.kanade.tachiyomi.data.notification.NotificationReceiver
|
||||
import eu.kanade.tachiyomi.data.notification.Notifications
|
||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||
import eu.kanade.tachiyomi.data.preference.getOrDefault
|
||||
import eu.kanade.tachiyomi.data.updater.AutoUpdaterJob
|
||||
import eu.kanade.tachiyomi.extension.api.ExtensionGithubApi
|
||||
import eu.kanade.tachiyomi.extension.model.Extension
|
||||
import eu.kanade.tachiyomi.util.system.connectivityManager
|
||||
import eu.kanade.tachiyomi.util.system.notification
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import uy.kohesive.injekt.Injekt
|
||||
@ -44,15 +48,40 @@ class ExtensionUpdateJob(private val context: Context, workerParams: WorkerParam
|
||||
Result.success()
|
||||
}
|
||||
|
||||
private fun createUpdateNotification(extensions: List<Extension.Available>) {
|
||||
private fun createUpdateNotification(extensionsList: List<Extension.Available>) {
|
||||
val extensions = extensionsList.toMutableList()
|
||||
val preferences: PreferencesHelper by injectLazy()
|
||||
preferences.extensionUpdatesCount().set(extensions.size)
|
||||
// Not doing this yet since users will get prompted while device is idle
|
||||
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && preferences.autoUpdateExtensions()) {
|
||||
// val intent = ExtensionInstallService.jobIntent(context, extensions)
|
||||
// context.startForegroundService(intent)
|
||||
// return
|
||||
// }
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && preferences.autoUpdateExtensions() != AutoUpdaterJob.NEVER) {
|
||||
val cm = context.connectivityManager
|
||||
if (
|
||||
preferences.autoUpdateExtensions() == AutoUpdaterJob.ALWAYS ||
|
||||
!cm.isActiveNetworkMetered
|
||||
) {
|
||||
val extensionManager = Injekt.get<ExtensionManager>()
|
||||
val extensionsInstalledByApp =
|
||||
extensions.filter { extensionManager.isInstalledByApp(it) }
|
||||
val intent =
|
||||
ExtensionInstallService.jobIntent(
|
||||
context,
|
||||
extensionsInstalledByApp,
|
||||
// Re reun this job if not all the extensions can be auto updated
|
||||
if (extensionsInstalledByApp.size == extensions.size) {
|
||||
1
|
||||
} else {
|
||||
2
|
||||
}
|
||||
)
|
||||
context.startForegroundService(intent)
|
||||
if (extensionsInstalledByApp.size == extensions.size) {
|
||||
return
|
||||
} else {
|
||||
extensions.removeAll(extensionsInstalledByApp)
|
||||
}
|
||||
} else {
|
||||
runJobAgain(context, NetworkType.UNMETERED)
|
||||
}
|
||||
}
|
||||
NotificationManagerCompat.from(context).apply {
|
||||
notify(
|
||||
Notifications.ID_UPDATES_TO_EXTS,
|
||||
@ -74,7 +103,9 @@ class ExtensionUpdateJob(private val context: Context, workerParams: WorkerParam
|
||||
context
|
||||
)
|
||||
)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S &&
|
||||
extensions.size == extensionsList.size
|
||||
) {
|
||||
val intent = ExtensionInstallService.jobIntent(context, extensions)
|
||||
val pendingIntent =
|
||||
PendingIntent.getForegroundService(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
|
||||
@ -92,6 +123,20 @@ class ExtensionUpdateJob(private val context: Context, workerParams: WorkerParam
|
||||
|
||||
companion object {
|
||||
private const val TAG = "ExtensionUpdate"
|
||||
private const val AUTO_TAG = "AutoExtensionUpdate"
|
||||
|
||||
fun runJobAgain(context: Context, networkType: NetworkType) {
|
||||
val constraints = Constraints.Builder()
|
||||
.setRequiredNetworkType(networkType)
|
||||
.build()
|
||||
val request = OneTimeWorkRequestBuilder<ExtensionUpdateJob>()
|
||||
.setConstraints(constraints)
|
||||
.addTag(AUTO_TAG)
|
||||
.build()
|
||||
|
||||
WorkManager.getInstance(context)
|
||||
.enqueueUniqueWork(AUTO_TAG, ExistingWorkPolicy.REPLACE, request)
|
||||
}
|
||||
|
||||
fun setupTask(context: Context, forceAutoUpdateJob: Boolean? = null) {
|
||||
val preferences = Injekt.get<PreferencesHelper>()
|
||||
|
@ -4,7 +4,9 @@ import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.content.pm.PackageInfo
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Build
|
||||
import dalvik.system.PathClassLoader
|
||||
import eu.kanade.tachiyomi.BuildConfig
|
||||
import eu.kanade.tachiyomi.annotations.Nsfw
|
||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||
import eu.kanade.tachiyomi.data.preference.getOrDefault
|
||||
@ -85,6 +87,14 @@ internal object ExtensionLoader {
|
||||
return loadExtension(context, pkgName, pkgInfo)
|
||||
}
|
||||
|
||||
fun isExtensionInstalledByApp(context: Context, pkgName: String): Boolean {
|
||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
context.packageManager.getInstallSourceInfo(pkgName).installingPackageName
|
||||
} else {
|
||||
context.packageManager.getInstallerPackageName(pkgName)
|
||||
} == BuildConfig.APPLICATION_ID
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads an extension given its package name.
|
||||
*
|
||||
|
@ -1,10 +1,12 @@
|
||||
package eu.kanade.tachiyomi.ui.setting
|
||||
|
||||
import android.os.Build
|
||||
import androidx.preference.PreferenceScreen
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.preference.PreferenceKeys
|
||||
import eu.kanade.tachiyomi.data.preference.asImmediateFlow
|
||||
import eu.kanade.tachiyomi.data.preference.getOrDefault
|
||||
import eu.kanade.tachiyomi.data.updater.AutoUpdaterJob
|
||||
import eu.kanade.tachiyomi.extension.ExtensionUpdateJob
|
||||
import eu.kanade.tachiyomi.source.SourceManager
|
||||
import eu.kanade.tachiyomi.ui.main.MainActivity
|
||||
@ -34,6 +36,20 @@ class SettingsBrowseController : SettingsController() {
|
||||
true
|
||||
}
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
intListPreference(activity) {
|
||||
key = PreferenceKeys.autoUpdateExtensions
|
||||
titleRes = R.string.auto_update_extensions
|
||||
entryRange = 0..2
|
||||
entriesRes = arrayOf(
|
||||
R.string.over_any_network,
|
||||
R.string.over_wifi_only,
|
||||
R.string.dont_auto_update
|
||||
)
|
||||
defaultValue = AutoUpdaterJob.ONLY_ON_UNMETERED
|
||||
}
|
||||
infoPreference(R.string.some_extensions_may_not_update)
|
||||
}
|
||||
}
|
||||
|
||||
preferenceCategory {
|
||||
|
10
app/src/main/res/drawable/ic_extension_updated_24dp.xml
Normal file
10
app/src/main/res/drawable/ic_extension_updated_24dp.xml
Normal file
@ -0,0 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="96dp"
|
||||
android:height="96dp"
|
||||
android:viewportWidth="96"
|
||||
android:viewportHeight="96">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M50.9,85H25c-3.9,0 -7,-3.1 -7,-7V18c0,-3.9 3.1,-7 7,-7h46c3.9,0 7,3.1 7,7v37c-2.4,-0.9 -5,-1.4 -7.7,-1.4c-1.6,0 -3.1,0.2 -4.6,0.5C65.4,54 65.2,54 65,54h-3v-9c0,-2.8 -2.2,-5 -5,-5h-8v-2c0,-2.8 -2.2,-5 -5,-5s-5,2.2 -5,5v2h-8c-2.8,0 -5,2.2 -5,5v9h4c2.8,0 5,2.2 5,5s-2.2,5 -5,5h-4v8c0,2.8 2.2,5 5,5h8v-4c0,-2.8 2.2,-5 5,-5c2.3,0 4.3,1.6 4.8,3.8c-0.2,1.1 -0.3,2.3 -0.3,3.5C48.6,78.8 49.4,82.1 50.9,85zM81.7,62.3L66.9,77.1l-6.5,-6.5l-4.8,4.8l11.3,12.3l19.5,-19.5L81.7,62.3z"
|
||||
android:fillType="evenOdd"/>
|
||||
</vector>
|
@ -292,6 +292,7 @@
|
||||
<!-- Extensions -->
|
||||
<string name="extensions">Extensions</string>
|
||||
<string name="extension_updates">Extension Updates</string>
|
||||
<string name="extension_updates_pending">Extension Updates pending</string>
|
||||
<string name="extension_info">Extension info</string>
|
||||
<string name="filter_languages">Filter Languages</string>
|
||||
<string name="obsolete">Obsolete</string>
|
||||
@ -321,6 +322,11 @@
|
||||
<item quantity="one">%d update pending</item>
|
||||
<item quantity="other">%d updates pending</item>
|
||||
</plurals>
|
||||
<string name="extensions_updated">Extensions updated</string>
|
||||
<plurals name="extensions_updated_plural">
|
||||
<item quantity="one">Extension updated</item>
|
||||
<item quantity="other">%d extensions updated</item>
|
||||
</plurals>
|
||||
<plurals name="extension_updates_available">
|
||||
<item quantity="one">Extension update available</item>
|
||||
<item quantity="other">%d extension updates available</item>
|
||||
@ -770,6 +776,8 @@
|
||||
<!-- Browse Settings -->
|
||||
<string name="pref_global_search">Global search</string>
|
||||
<string name="check_for_extension_updates">Check for extension updates</string>
|
||||
<string name="auto_update_extensions">Auto-update extensions</string>
|
||||
<string name="some_extensions_may_not_update">Some extensions may not be auto-updated if they were installed outside this app</string>
|
||||
<string name="only_search_pinned_when">Only search pinned sources</string>
|
||||
<string name="match_pinned_sources">Match pinned sources</string>
|
||||
<string name="match_enabled_sources">Match enabled sources</string>
|
||||
|
Loading…
Reference in New Issue
Block a user