Skip to content

Commit 8a35ca7

Browse files
committed
Implemented first version, with couple of FIXME to test
1 parent 8fa8e39 commit 8a35ca7

File tree

13 files changed

+310
-50
lines changed

13 files changed

+310
-50
lines changed

app/src/main/AndroidManifest.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
android:description="@string/plugin_host_permission_description"
1010
/>
1111

12+
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
1213
<uses-permission android:name="app.revanced.manager.permission.PLUGIN_HOST" />
1314
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"
1415
tools:ignore="QueryAllPackagesPermission" />

app/src/main/java/app/revanced/manager/ManagerApplication.kt

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,26 @@ import android.app.Activity
44
import android.app.Application
55
import android.os.Bundle
66
import android.util.Log
7+
import androidx.work.Configuration
78
import app.revanced.manager.data.platform.Filesystem
8-
import app.revanced.manager.di.*
9+
import app.revanced.manager.di.databaseModule
10+
import app.revanced.manager.di.httpModule
11+
import app.revanced.manager.di.managerModule
12+
import app.revanced.manager.di.preferencesModule
13+
import app.revanced.manager.di.repositoryModule
14+
import app.revanced.manager.di.rootModule
15+
import app.revanced.manager.di.serviceModule
16+
import app.revanced.manager.di.viewModelModule
17+
import app.revanced.manager.di.workerModule
918
import app.revanced.manager.domain.manager.PreferencesManager
1019
import app.revanced.manager.domain.repository.DownloaderPluginRepository
1120
import app.revanced.manager.domain.repository.PatchBundleRepository
1221
import app.revanced.manager.util.tag
13-
import kotlinx.coroutines.Dispatchers
1422
import coil.Coil
1523
import coil.ImageLoader
1624
import com.topjohnwu.superuser.Shell
1725
import com.topjohnwu.superuser.internal.BuilderImpl
26+
import kotlinx.coroutines.Dispatchers
1827
import kotlinx.coroutines.MainScope
1928
import kotlinx.coroutines.launch
2029
import me.zhanghai.android.appiconloader.coil.AppIconFetcher
@@ -25,13 +34,17 @@ import org.koin.android.ext.koin.androidLogger
2534
import org.koin.androidx.workmanager.koin.workManagerFactory
2635
import org.koin.core.context.startKoin
2736

28-
class ManagerApplication : Application() {
37+
class ManagerApplication : Application(), Configuration.Provider {
2938
private val scope = MainScope()
3039
private val prefs: PreferencesManager by inject()
3140
private val patchBundleRepository: PatchBundleRepository by inject()
3241
private val downloaderPluginRepository: DownloaderPluginRepository by inject()
3342
private val fs: Filesystem by inject()
3443

44+
override val workManagerConfiguration: Configuration
45+
get() = Configuration.Builder()
46+
.build()
47+
3548
override fun onCreate() {
3649
super.onCreate()
3750

@@ -51,7 +64,6 @@ class ManagerApplication : Application() {
5164
rootModule
5265
)
5366
}
54-
5567
val pixels = 512
5668
Coil.setImageLoader(
5769
ImageLoader.Builder(this)
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
package app.revanced.manager.di
22

3+
import app.revanced.manager.patcher.worker.BundleUpdateNotificationWorker
34
import app.revanced.manager.patcher.worker.PatcherWorker
45
import org.koin.androidx.workmanager.dsl.workerOf
56
import org.koin.dsl.module
67

78
val workerModule = module {
89
workerOf(::PatcherWorker)
10+
workerOf(::BundleUpdateNotificationWorker)
911
}

app/src/main/java/app/revanced/manager/domain/bundles/RemotePatchBundle.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ sealed class RemotePatchBundle(name: String, id: Int, directory: File, val endpo
3535

3636
suspend fun update(): Boolean = withContext(Dispatchers.IO) {
3737
val info = getLatestInfo()
38-
if (hasInstalled() && info.version == currentVersion())
38+
if (hasInstalled() && info.version == currentVersion()+"r") //FIXME
39+
//Log.i()
3940
return@withContext false
4041

4142
download(info)

app/src/main/java/app/revanced/manager/domain/manager/PreferencesManager.kt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,19 @@
11
package app.revanced.manager.domain.manager
22

33
import android.content.Context
4+
import app.revanced.manager.R
45
import app.revanced.manager.domain.manager.base.BasePreferencesManager
56
import app.revanced.manager.ui.theme.Theme
67
import app.revanced.manager.util.isDebuggable
78

9+
10+
enum class BackgroundBundleUpdateTime(val displayName: Int, val value: Long) {
11+
NEVER(R.string.never, 0),
12+
MIN15(R.string.minutes_15, 15),
13+
HOUR(R.string.hourly, 60),
14+
DAY(R.string.daily, 60 * 24)
15+
}
16+
817
class PreferencesManager(
918
context: Context
1019
) : BasePreferencesManager(context, "settings") {
@@ -22,6 +31,7 @@ class PreferencesManager(
2231
val firstLaunch = booleanPreference("first_launch", true)
2332
val managerAutoUpdates = booleanPreference("manager_auto_updates", false)
2433
val showManagerUpdateDialogOnLaunch = booleanPreference("show_manager_update_dialog_on_launch", true)
34+
val backgroundBundleUpdateTime = enumPreference("background_bundle_update_time", BackgroundBundleUpdateTime.NEVER)
2535

2636
val disablePatchVersionCompatCheck = booleanPreference("disable_patch_version_compatibility_check", false)
2737
val disableSelectionWarning = booleanPreference("disable_selection_warning", false)

app/src/main/java/app/revanced/manager/domain/repository/PatchBundleRepository.kt

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,24 +9,25 @@ import app.revanced.manager.data.platform.NetworkInfo
99
import app.revanced.manager.data.room.bundles.PatchBundleEntity
1010
import app.revanced.manager.domain.bundles.APIPatchBundle
1111
import app.revanced.manager.domain.bundles.JsonPatchBundle
12-
import app.revanced.manager.data.room.bundles.Source as SourceInfo
1312
import app.revanced.manager.domain.bundles.LocalPatchBundle
14-
import app.revanced.manager.domain.bundles.RemotePatchBundle
1513
import app.revanced.manager.domain.bundles.PatchBundleSource
14+
import app.revanced.manager.domain.bundles.RemotePatchBundle
1615
import app.revanced.manager.domain.manager.PreferencesManager
1716
import app.revanced.manager.patcher.patch.PatchInfo
1817
import app.revanced.manager.util.flatMapLatestAndCombine
1918
import app.revanced.manager.util.tag
2019
import app.revanced.manager.util.uiSafe
2120
import kotlinx.coroutines.Dispatchers
21+
import kotlinx.coroutines.async
22+
import kotlinx.coroutines.awaitAll
2223
import kotlinx.coroutines.coroutineScope
2324
import kotlinx.coroutines.flow.MutableStateFlow
2425
import kotlinx.coroutines.flow.first
2526
import kotlinx.coroutines.flow.map
2627
import kotlinx.coroutines.flow.update
27-
import kotlinx.coroutines.launch
2828
import kotlinx.coroutines.withContext
2929
import java.io.InputStream
30+
import app.revanced.manager.data.room.bundles.Source as SourceInfo
3031

3132
class PatchBundleRepository(
3233
private val app: Application,
@@ -164,21 +165,34 @@ class PatchBundleRepository(
164165
suspend fun redownloadRemoteBundles() =
165166
getBundlesByType<RemotePatchBundle>().forEach { it.downloadLatest() }
166167

167-
suspend fun updateCheck() =
168+
suspend fun updateCheck(): List<Result<RemotePatchBundle>> {
169+
var updateResult: List<Result<RemotePatchBundle>> = emptyList()
168170
uiSafe(app, R.string.source_download_fail, "Failed to update bundles") {
169171
coroutineScope {
170172
if (!networkInfo.isSafe()) {
171-
Log.d(tag, "Skipping update check because the network is down or metered.")
172173
return@coroutineScope
173174
}
175+
}
174176

175-
getBundlesByType<RemotePatchBundle>().forEach {
176-
launch {
177-
if (!it.getProps().autoUpdate) return@launch
178-
Log.d(tag, "Updating patch bundle: ${it.getName()}")
179-
it.update()
177+
updateResult = coroutineScope {
178+
getBundlesByType<RemotePatchBundle>()
179+
.filter { it.getProps().autoUpdate }
180+
.map { bundle ->
181+
async {
182+
try {
183+
if (bundle.update())
184+
return@async Result.success(bundle)
185+
else
186+
return@async null
187+
} catch (e: Exception) {
188+
Result.failure(e)
189+
}
190+
}
180191
}
181-
}
192+
.awaitAll()
193+
.filterNotNull()
182194
}
183195
}
196+
return updateResult
197+
}
184198
}

app/src/main/java/app/revanced/manager/domain/worker/WorkerRepository.kt

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,25 @@
11
package app.revanced.manager.domain.worker
22

33
import android.app.Application
4+
import android.app.Notification
5+
import android.app.NotificationChannel
6+
import android.app.NotificationManager
7+
import android.app.PendingIntent
8+
import android.content.Context
9+
import android.content.Intent
10+
import android.graphics.drawable.Icon
11+
import android.util.Log
12+
import androidx.work.ExistingPeriodicWorkPolicy
413
import androidx.work.ExistingWorkPolicy
514
import androidx.work.OneTimeWorkRequest
615
import androidx.work.OutOfQuotaPolicy
16+
import androidx.work.PeriodicWorkRequestBuilder
717
import androidx.work.WorkManager
18+
import app.revanced.manager.R
19+
import app.revanced.manager.domain.manager.BackgroundBundleUpdateTime
20+
import app.revanced.manager.patcher.worker.BundleUpdateNotificationWorker
821
import java.util.UUID
22+
import java.util.concurrent.TimeUnit
923

1024
class WorkerRepository(app: Application) {
1125
val workManager = WorkManager.getInstance(app)
@@ -33,4 +47,50 @@ class WorkerRepository(app: Application) {
3347
workManager.enqueueUniqueWork(name, ExistingWorkPolicy.REPLACE, request)
3448
return request.id
3549
}
50+
51+
inline fun <reified T> createNotification(
52+
context: Context,
53+
notificationChannel: NotificationChannel,
54+
title: String,
55+
description: String
56+
): Pair<Notification, NotificationManager> {
57+
val notificationIntent = Intent(context, T::class.java)
58+
val pendingIntent: PendingIntent = PendingIntent.getActivity(
59+
context, 0, notificationIntent, PendingIntent.FLAG_IMMUTABLE
60+
)
61+
62+
val notificationManager = context
63+
.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
64+
notificationManager.createNotificationChannel(notificationChannel)
65+
66+
return Pair(
67+
Notification.Builder(context, notificationChannel.id)
68+
.setContentTitle(title)
69+
.setContentText(description)
70+
.setLargeIcon(Icon.createWithResource(context, R.drawable.ic_notification))
71+
.setSmallIcon(Icon.createWithResource(context, R.drawable.ic_notification))
72+
.setContentIntent(pendingIntent).build(),
73+
notificationManager
74+
)
75+
}
76+
77+
fun scheduleBundleUpdateNotificationWork(bundleUpdateTime: BackgroundBundleUpdateTime) {
78+
val workId = "BundleUpdateNotificationWork"
79+
if(bundleUpdateTime == BackgroundBundleUpdateTime.NEVER) {
80+
workManager.cancelUniqueWork(workId)
81+
Log.d("WorkManager","Cancelled job with workId $workId.")
82+
} else {
83+
val workRequest =
84+
PeriodicWorkRequestBuilder<BundleUpdateNotificationWorker>(bundleUpdateTime.value, TimeUnit.MINUTES)
85+
.build()
86+
87+
workManager
88+
.enqueueUniquePeriodicWork(
89+
workId,
90+
ExistingPeriodicWorkPolicy.CANCEL_AND_REENQUEUE,
91+
workRequest
92+
)
93+
Log.d("WorkManager", "Periodic work $workId updated with time ${bundleUpdateTime.value}.")
94+
}
95+
}
3696
}
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
package app.revanced.manager.patcher.worker
2+
3+
import android.Manifest.permission.POST_NOTIFICATIONS
4+
import android.app.NotificationChannel
5+
import android.app.NotificationManager
6+
import android.content.Context
7+
import android.content.pm.PackageManager.PERMISSION_GRANTED
8+
import android.os.Build
9+
import android.util.Log
10+
import androidx.core.content.ContextCompat
11+
import androidx.work.WorkerParameters
12+
import app.revanced.manager.MainActivity
13+
import app.revanced.manager.R
14+
import app.revanced.manager.domain.repository.PatchBundleRepository
15+
import app.revanced.manager.domain.worker.Worker
16+
import app.revanced.manager.domain.worker.WorkerRepository
17+
import app.revanced.manager.plugin.downloader.PluginHostApi
18+
import org.koin.core.component.KoinComponent
19+
import org.koin.core.component.inject
20+
21+
@OptIn(PluginHostApi::class)
22+
class BundleUpdateNotificationWorker(
23+
context: Context,
24+
parameters: WorkerParameters
25+
) : Worker<BundleUpdateNotificationWorker.Args>(context, parameters), KoinComponent {
26+
private val patchBundleRepository: PatchBundleRepository by inject()
27+
private val workerRepository: WorkerRepository by inject()
28+
29+
class Args()
30+
31+
val notificationChannel = NotificationChannel(
32+
"background-bundle-update-channel", "Background Check Notifications", NotificationManager.IMPORTANCE_HIGH
33+
)
34+
companion object {
35+
const val LOG_TAG = "BundleAutoUpdateWorker"
36+
}
37+
38+
override suspend fun doWork(): Result {
39+
Log.d(LOG_TAG, "Searching for updates.")
40+
return try {
41+
val shouldSendNotification = patchBundleRepository.updateCheck()
42+
Log.d(LOG_TAG, "Found ${shouldSendNotification.size} new updates.")
43+
shouldSendNotification.forEach {
44+
it.getOrNull()?.let { bundle ->
45+
sendNotification(bundle.getName(), bundle.currentVersion()!!)
46+
}
47+
}
48+
Result.success()
49+
} catch (e: Exception) {
50+
Log.d(LOG_TAG, "Error during work: ${e.message}")
51+
Result.failure()
52+
}
53+
}
54+
55+
private fun sendNotification(bundleName: String, bundleVersion: String) {
56+
workerRepository.createNotification<MainActivity>(
57+
applicationContext,
58+
notificationChannel,
59+
applicationContext.getString(R.string.bundle_update),
60+
applicationContext.getString(
61+
R.string.bundle_update_description,
62+
bundleName,
63+
bundleVersion
64+
)
65+
).also { (notification, notificationManager) ->
66+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
67+
if (ContextCompat.checkSelfPermission(
68+
applicationContext,
69+
POST_NOTIFICATIONS
70+
) == PERMISSION_GRANTED
71+
) {
72+
notificationManager.notify("$bundleName-$bundleVersion".hashCode(), notification)
73+
Log.d(LOG_TAG, "Notification sent.")
74+
} else {
75+
Log.d(
76+
LOG_TAG,
77+
"POST_NOTIFICATIONS permission not granted. Cannot send notification."
78+
)
79+
}
80+
} else {
81+
notificationManager.notify("$bundleName-$bundleVersion".hashCode(), notification)
82+
Log.d(LOG_TAG, "Notification sent (pre-Android 13).")
83+
}
84+
}
85+
}
86+
}

0 commit comments

Comments
 (0)