diff --git a/app/lint-baseline.xml b/app/lint-baseline.xml
index e01146f5..bf4a0466 100644
--- a/app/lint-baseline.xml
+++ b/app/lint-baseline.xml
@@ -80,7 +80,7 @@
errorLine2=" ~~~~~~~">
@@ -791,22 +791,22 @@
+ errorLine1=" <item quantity="one">برهمکنشی جدید</item>"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ errorLine1=" <item quantity="one">توصیف محتوا برای کمبینایان (کران ۱ نویسه)</item>"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
@@ -839,7 +839,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
@@ -850,7 +850,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
@@ -861,7 +861,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
@@ -1532,7 +1532,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~">
@@ -1543,7 +1543,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~">
@@ -1653,7 +1653,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
@@ -2500,7 +2500,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~">
@@ -2511,7 +2511,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
@@ -2522,7 +2522,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~">
@@ -2533,7 +2533,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~">
@@ -2544,7 +2544,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~">
@@ -2555,7 +2555,7 @@
errorLine2=" ~~~~~~~~~~~~">
@@ -2566,7 +2566,7 @@
errorLine2=" ~~~~~~~~~~~~~~">
@@ -2577,7 +2577,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~">
@@ -2588,7 +2588,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~">
@@ -2599,7 +2599,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~">
@@ -2610,7 +2610,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~">
@@ -2621,7 +2621,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~">
@@ -2632,7 +2632,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~">
@@ -2643,7 +2643,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
@@ -2654,7 +2654,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~">
@@ -2665,7 +2665,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
@@ -2676,7 +2676,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
@@ -2687,7 +2687,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
@@ -2698,7 +2698,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~">
@@ -2709,7 +2709,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
@@ -2720,7 +2720,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~">
@@ -2731,7 +2731,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
@@ -2742,7 +2742,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~">
@@ -2940,7 +2940,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~">
@@ -2951,7 +2951,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~">
@@ -3116,7 +3116,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~">
@@ -3127,7 +3127,7 @@
errorLine2=" ~~~~~~~~~">
@@ -3138,7 +3138,7 @@
errorLine2=" ~~~~~~~~~">
@@ -3226,7 +3226,7 @@
errorLine2=" ~~~~~~~~~">
@@ -3237,7 +3237,7 @@
errorLine2=" ~~~~~~~~~">
@@ -3248,7 +3248,7 @@
errorLine2=" ~~~~~~~~~">
@@ -3259,7 +3259,7 @@
errorLine2=" ~~~~~~~~~">
@@ -4425,7 +4425,7 @@
errorLine2=" ~~~~~~~~~">
@@ -4436,7 +4436,7 @@
errorLine2=" ~~~~~~~">
@@ -4447,7 +4447,7 @@
errorLine2=" ~~~~~~~">
@@ -5096,7 +5096,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
@@ -6071,7 +6071,7 @@
errorLine2=" ~~~~~~~~">
@@ -6082,7 +6082,7 @@
errorLine2=" ~~~~~~~~">
@@ -6115,7 +6115,7 @@
errorLine2=" ~~~~~~~~">
@@ -6126,7 +6126,7 @@
errorLine2=" ~~~~~~~~">
@@ -6225,7 +6225,7 @@
errorLine2=" ~~~~~~~~">
@@ -6236,7 +6236,7 @@
errorLine2=" ~~~~~~~~">
@@ -6247,7 +6247,7 @@
errorLine2=" ~~~~~~~~">
@@ -6258,7 +6258,7 @@
errorLine2=" ~~~~~~~~">
@@ -6896,7 +6896,7 @@
errorLine2=" ~~~~~~~~">
@@ -6907,7 +6907,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~">
@@ -6918,7 +6918,7 @@
errorLine2=" ~~~~~~~">
@@ -6929,7 +6929,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~">
@@ -6940,7 +6940,7 @@
errorLine2=" ~~~~~~~~~~~~">
@@ -6951,7 +6951,7 @@
errorLine2=" ~~~~~~~~~~~~~">
@@ -6962,7 +6962,7 @@
errorLine2=" ~~~~~~~">
@@ -6973,7 +6973,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~">
@@ -6984,7 +6984,7 @@
errorLine2=" ~~~~~~~~~~~~~">
@@ -6995,7 +6995,7 @@
errorLine2=" ~~~~~~~">
@@ -7006,7 +7006,7 @@
errorLine2=" ~~~~~~~">
@@ -7017,7 +7017,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~">
@@ -7028,7 +7028,7 @@
errorLine2=" ~~~~~~~~~~~~~">
diff --git a/app/src/main/java/com/keylesspalace/tusky/TuskyApplication.kt b/app/src/main/java/com/keylesspalace/tusky/TuskyApplication.kt
index 3107cea8..e7c64699 100644
--- a/app/src/main/java/com/keylesspalace/tusky/TuskyApplication.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/TuskyApplication.kt
@@ -23,6 +23,7 @@ import androidx.work.ExistingPeriodicWorkPolicy
import androidx.work.PeriodicWorkRequestBuilder
import androidx.work.WorkManager
import autodispose2.AutoDisposePlugins
+import com.keylesspalace.tusky.components.notifications.NotificationHelper
import com.keylesspalace.tusky.di.AppInjector
import com.keylesspalace.tusky.settings.PrefKeys
import com.keylesspalace.tusky.settings.SCHEMA_VERSION
@@ -95,6 +96,8 @@ class TuskyApplication : Application(), HasAndroidInjector {
Log.w("RxJava", "undeliverable exception", it)
}
+ NotificationHelper.createWorkerNotificationChannel(this)
+
WorkManager.initialize(
this,
androidx.work.Configuration.Builder()
diff --git a/app/src/main/java/com/keylesspalace/tusky/components/notifications/NotificationFetcher.kt b/app/src/main/java/com/keylesspalace/tusky/components/notifications/NotificationFetcher.kt
index 89f4222b..633ca08f 100644
--- a/app/src/main/java/com/keylesspalace/tusky/components/notifications/NotificationFetcher.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/components/notifications/NotificationFetcher.kt
@@ -11,9 +11,10 @@ import com.keylesspalace.tusky.entity.Marker
import com.keylesspalace.tusky.entity.Notification
import com.keylesspalace.tusky.network.MastodonApi
import com.keylesspalace.tusky.util.isLessThan
-import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.delay
import javax.inject.Inject
import kotlin.math.min
+import kotlin.time.Duration.Companion.milliseconds
/**
* Fetch Mastodon notifications and show Android notifications, with summaries, for them.
@@ -29,19 +30,17 @@ class NotificationFetcher @Inject constructor(
private val accountManager: AccountManager,
private val context: Context
) {
- fun fetchAndShow() {
+ suspend fun fetchAndShow() {
for (account in accountManager.getAllAccountsOrderedByActive()) {
if (account.notificationsEnabled) {
try {
val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
// Create sorted list of new notifications
- val notifications = runBlocking { // OK, because in a worker thread
- fetchNewNotifications(account)
- .filter { filterNotification(notificationManager, account, it) }
- .sortedWith(compareBy({ it.id.length }, { it.id })) // oldest notifications first
- .toMutableList()
- }
+ val notifications = fetchNewNotifications(account)
+ .filter { filterNotification(notificationManager, account, it) }
+ .sortedWith(compareBy({ it.id.length }, { it.id })) // oldest notifications first
+ .toMutableList()
// There's a maximum limit on the number of notifications an Android app
// can display. If the total number of notifications (current notifications,
@@ -82,7 +81,7 @@ class NotificationFetcher @Inject constructor(
// Android will rate limit / drop notifications if they're posted too
// quickly. There is no indication to the user that this happened.
// See https://github.com/tuskyapp/Tusky/pull/3626#discussion_r1192963664
- Thread.sleep(1000)
+ delay(1000.milliseconds)
}
NotificationHelper.updateSummaryNotifications(
diff --git a/app/src/main/java/com/keylesspalace/tusky/components/notifications/NotificationHelper.java b/app/src/main/java/com/keylesspalace/tusky/components/notifications/NotificationHelper.java
index 5e7f2ece..6a42e897 100644
--- a/app/src/main/java/com/keylesspalace/tusky/components/notifications/NotificationHelper.java
+++ b/app/src/main/java/com/keylesspalace/tusky/components/notifications/NotificationHelper.java
@@ -37,6 +37,7 @@ import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.annotation.StringRes;
import androidx.core.app.NotificationCompat;
import androidx.core.app.RemoteInput;
import androidx.core.app.TaskStackBuilder;
@@ -77,7 +78,12 @@ import java.util.concurrent.TimeUnit;
public class NotificationHelper {
- private static int notificationId = 0;
+ /** ID of notification shown when fetching notifications */
+ public static final int NOTIFICATION_ID_FETCH_NOTIFICATION = 0;
+ /** ID of notification shown when pruning the cache */
+ public static final int NOTIFICATION_ID_PRUNE_CACHE = 1;
+ /** Dynamic notification IDs start here */
+ private static int notificationId = NOTIFICATION_ID_PRUNE_CACHE + 1;
/**
* constants used in Intents
@@ -121,6 +127,7 @@ public class NotificationHelper {
public static final String CHANNEL_SIGN_UP = "CHANNEL_SIGN_UP";
public static final String CHANNEL_UPDATES = "CHANNEL_UPDATES";
public static final String CHANNEL_REPORT = "CHANNEL_REPORT";
+ public static final String CHANNEL_BACKGROUND_TASKS = "CHANNEL_BACKGROUND_TASKS";
/**
* WorkManager Tag
@@ -472,6 +479,49 @@ public class NotificationHelper {
pendingIntentFlags(false));
}
+ /**
+ * Creates a notification channel for notifications for background work that should not
+ * disturb the user.
+ *
+ * @param context context
+ */
+ public static void createWorkerNotificationChannel(@NonNull Context context) {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return;
+
+ NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
+
+ NotificationChannel channel = new NotificationChannel(
+ CHANNEL_BACKGROUND_TASKS,
+ context.getString(R.string.notification_listenable_worker_name),
+ NotificationManager.IMPORTANCE_NONE
+ );
+
+ channel.setDescription(context.getString(R.string.notification_listenable_worker_description));
+ channel.enableLights(false);
+ channel.enableVibration(false);
+ channel.setShowBadge(false);
+
+ notificationManager.createNotificationChannel(channel);
+ }
+
+ /**
+ * Creates a notification for a background worker.
+ *
+ * @param context context
+ * @param titleResource String resource to use as the notification's title
+ * @return the notification
+ */
+ @NonNull
+ public static android.app.Notification createWorkerNotification(@NonNull Context context, @StringRes int titleResource) {
+ String title = context.getString(titleResource);
+ return new NotificationCompat.Builder(context, CHANNEL_BACKGROUND_TASKS)
+ .setContentTitle(title)
+ .setTicker(title)
+ .setSmallIcon(R.drawable.ic_notify)
+ .setOngoing(true)
+ .build();
+ }
+
public static void createNotificationChannelsForAccount(@NonNull AccountEntity account, @NonNull Context context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
diff --git a/app/src/main/java/com/keylesspalace/tusky/worker/NotificationWorker.kt b/app/src/main/java/com/keylesspalace/tusky/worker/NotificationWorker.kt
index 84fabd4a..cc99b78b 100644
--- a/app/src/main/java/com/keylesspalace/tusky/worker/NotificationWorker.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/worker/NotificationWorker.kt
@@ -17,10 +17,15 @@
package com.keylesspalace.tusky.worker
+import android.app.Notification
import android.content.Context
-import androidx.work.Worker
+import androidx.work.CoroutineWorker
+import androidx.work.ForegroundInfo
import androidx.work.WorkerParameters
+import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.components.notifications.NotificationFetcher
+import com.keylesspalace.tusky.components.notifications.NotificationHelper
+import com.keylesspalace.tusky.components.notifications.NotificationHelper.NOTIFICATION_ID_FETCH_NOTIFICATION
import javax.inject.Inject
/** Fetch and show new notifications. */
@@ -28,16 +33,20 @@ class NotificationWorker(
appContext: Context,
params: WorkerParameters,
private val notificationsFetcher: NotificationFetcher
-) : Worker(appContext, params) {
- override fun doWork(): Result {
+) : CoroutineWorker(appContext, params) {
+ val notification: Notification = NotificationHelper.createWorkerNotification(applicationContext, R.string.notification_notification_worker)
+
+ override suspend fun doWork(): Result {
notificationsFetcher.fetchAndShow()
return Result.success()
}
+ override suspend fun getForegroundInfo() = ForegroundInfo(NOTIFICATION_ID_FETCH_NOTIFICATION, notification)
+
class Factory @Inject constructor(
private val notificationsFetcher: NotificationFetcher
) : ChildWorkerFactory {
- override fun createWorker(appContext: Context, params: WorkerParameters): Worker {
+ override fun createWorker(appContext: Context, params: WorkerParameters): CoroutineWorker {
return NotificationWorker(appContext, params, notificationsFetcher)
}
}
diff --git a/app/src/main/java/com/keylesspalace/tusky/worker/PruneCacheWorker.kt b/app/src/main/java/com/keylesspalace/tusky/worker/PruneCacheWorker.kt
index c0ebdb79..5a65a2ef 100644
--- a/app/src/main/java/com/keylesspalace/tusky/worker/PruneCacheWorker.kt
+++ b/app/src/main/java/com/keylesspalace/tusky/worker/PruneCacheWorker.kt
@@ -17,11 +17,16 @@
package com.keylesspalace.tusky.worker
+import android.app.Notification
import android.content.Context
import android.util.Log
import androidx.work.CoroutineWorker
+import androidx.work.ForegroundInfo
import androidx.work.ListenableWorker
import androidx.work.WorkerParameters
+import com.keylesspalace.tusky.R
+import com.keylesspalace.tusky.components.notifications.NotificationHelper
+import com.keylesspalace.tusky.components.notifications.NotificationHelper.NOTIFICATION_ID_PRUNE_CACHE
import com.keylesspalace.tusky.db.AccountManager
import com.keylesspalace.tusky.db.AppDatabase
import javax.inject.Inject
@@ -33,6 +38,8 @@ class PruneCacheWorker(
private val appDatabase: AppDatabase,
private val accountManager: AccountManager
) : CoroutineWorker(appContext, workerParams) {
+ val notification: Notification = NotificationHelper.createWorkerNotification(applicationContext, R.string.notification_prune_cache)
+
override suspend fun doWork(): Result {
for (account in accountManager.accounts) {
Log.d(TAG, "Pruning database using account ID: ${account.id}")
@@ -41,6 +48,8 @@ class PruneCacheWorker(
return Result.success()
}
+ override suspend fun getForegroundInfo() = ForegroundInfo(NOTIFICATION_ID_PRUNE_CACHE, notification)
+
companion object {
private const val TAG = "PruneCacheWorker"
private const val MAX_STATUSES_IN_CACHE = 1000
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 26af20b9..9fbb6fda 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -371,6 +371,8 @@
Notifications when posts you\'ve interacted with are edited
Reports
Notifications about moderation reports
+ Background activity
+ Notifications when Tusky is working in the background
Unknown
%s mentioned you
@@ -381,6 +383,8 @@
- %d new interaction
- %d new interactions
+ Fetching notifications…
+ Cache maintenance…
Locked Account