Skip to content

Commit

Permalink
[Push] Show notification on push notification (until sync is started) (
Browse files Browse the repository at this point in the history
…#1043)

* Added sync pending notification

Signed-off-by: Arnau Mora Gras <[email protected]>

* Moved notify function to PushNotificationManager

Signed-off-by: Arnau Mora Gras <[email protected]>

* Added ongoing and only-alert-once

Signed-off-by: Arnau Mora Gras <[email protected]>

* Added notification hiding

Signed-off-by: Arnau Mora Gras <[email protected]>

* Got rid of `cancel`

Signed-off-by: Arnau Mora Gras <[email protected]>

* Fixed comments

Signed-off-by: Arnau Mora Gras <[email protected]>

* Added content intent and sub text

Signed-off-by: Arnau Mora Gras <[email protected]>

* Updated usages

Signed-off-by: Arnau Mora Gras <[email protected]>

* Review changes

Signed-off-by: Arnau Mora Gras <[email protected]>

---------

Signed-off-by: Arnau Mora Gras <[email protected]>
  • Loading branch information
ArnyminerZ authored Oct 10, 2024
1 parent 26a670c commit c805e54
Show file tree
Hide file tree
Showing 6 changed files with 96 additions and 13 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package at.bitfire.davdroid.push

import android.accounts.Account
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import at.bitfire.davdroid.R
import at.bitfire.davdroid.ui.NotificationRegistry
import at.bitfire.davdroid.ui.account.AccountActivity
import dagger.hilt.android.qualifiers.ApplicationContext
import javax.inject.Inject

class PushNotificationManager @Inject constructor(
@ApplicationContext private val context: Context,
private val notificationRegistry: NotificationRegistry
) {

/**
* Generates the notification ID for a push notification.
*/
private fun notificationId(account: Account, authority: String): Int {
return account.name.hashCode() + account.type.hashCode() + authority.hashCode()
}

/**
* Sends a notification to inform the user that a push notification has been received, the
* sync has been scheduled, but it still has not run.
*/
fun notify(account: Account, authority: String) {
notificationRegistry.notifyIfPossible(notificationId(account, authority)) {
NotificationCompat.Builder(context, notificationRegistry.CHANNEL_STATUS)
.setSmallIcon(R.drawable.ic_sync)
.setContentTitle(context.getString(R.string.sync_notification_pending_push_title))
.setContentText(context.getString(R.string.sync_notification_pending_push_message))
.setSubText(account.name)
.setPriority(NotificationCompat.PRIORITY_LOW)
.setCategory(NotificationCompat.CATEGORY_STATUS)
.setAutoCancel(true)
.setOnlyAlertOnce(true)
.setContentIntent(
PendingIntent.getActivity(
context,
0,
Intent(context, AccountActivity::class.java).apply {
putExtra(AccountActivity.EXTRA_ACCOUNT, account)
},
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
)
)
.build()
}
}

/**
* Once the sync has been started, the notification is no longer needed and can be dismissed.
* It's safe to call this method even if the notification has not been shown.
*/
fun dismiss(account: Account, authority: String) {
NotificationManagerCompat.from(context)
.cancel(notificationId(account, authority))
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,13 @@ import at.bitfire.davdroid.repository.DavServiceRepository
import at.bitfire.davdroid.repository.PreferenceRepository
import at.bitfire.davdroid.sync.worker.SyncWorkerManager
import dagger.hilt.android.AndroidEntryPoint
import java.util.logging.Level
import java.util.logging.Logger
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.unifiedpush.android.connector.MessagingReceiver
import java.util.logging.Level
import java.util.logging.Logger
import javax.inject.Inject

@AndroidEntryPoint
class UnifiedPushReceiver: MessagingReceiver() {
Expand Down Expand Up @@ -74,14 +74,14 @@ class UnifiedPushReceiver: MessagingReceiver() {
collectionRepository.getSyncableByTopic(topic)?.let { collection ->
serviceRepository.get(collection.serviceId)?.let { service ->
val account = accountRepository.fromName(service.accountName)
syncWorkerManager.enqueueOneTimeAllAuthorities(account)
syncWorkerManager.enqueueOneTimeAllAuthorities(account, fromPush = true)
}
}

} else {
logger.warning("Got push message without topic, syncing all accounts")
for (account in accountRepository.getAll())
syncWorkerManager.enqueueOneTimeAllAuthorities(account)
syncWorkerManager.enqueueOneTimeAllAuthorities(account, fromPush = true)

}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import androidx.work.WorkQuery
import androidx.work.WorkerParameters
import at.bitfire.davdroid.InvalidAccountException
import at.bitfire.davdroid.R
import at.bitfire.davdroid.push.PushNotificationManager
import at.bitfire.davdroid.settings.AccountSettings
import at.bitfire.davdroid.sync.AddressBookSyncer
import at.bitfire.davdroid.sync.CalendarSyncer
Expand All @@ -31,15 +32,15 @@ import at.bitfire.davdroid.sync.Syncer
import at.bitfire.davdroid.sync.TaskSyncer
import at.bitfire.davdroid.ui.NotificationRegistry
import at.bitfire.ical4android.TaskProvider
import java.util.Collections
import java.util.logging.Logger
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.runInterruptible
import kotlinx.coroutines.withContext
import java.util.Collections
import java.util.logging.Logger
import javax.inject.Inject

abstract class BaseSyncWorker(
context: Context,
Expand Down Expand Up @@ -151,6 +152,9 @@ abstract class BaseSyncWorker(
@Inject
lateinit var notificationRegistry: NotificationRegistry

@Inject
lateinit var pushNotificationManager: PushNotificationManager

@Inject
lateinit var syncConditionsFactory: SyncConditions.Factory

Expand All @@ -174,6 +178,9 @@ abstract class BaseSyncWorker(
return Result.success()
}

// Dismiss any pending push notification
pushNotificationManager.dismiss(account, authority)

try {
val accountSettings = try {
accountSettingsFactory.create(account)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import androidx.work.PeriodicWorkRequest
import androidx.work.PeriodicWorkRequestBuilder
import androidx.work.WorkManager
import androidx.work.WorkRequest
import at.bitfire.davdroid.push.PushNotificationManager
import at.bitfire.davdroid.sync.SyncUtils
import at.bitfire.davdroid.sync.worker.BaseSyncWorker.Companion.INPUT_ACCOUNT_NAME
import at.bitfire.davdroid.sync.worker.BaseSyncWorker.Companion.INPUT_ACCOUNT_TYPE
Expand All @@ -45,7 +46,8 @@ import javax.inject.Inject
*/
class SyncWorkerManager @Inject constructor(
@ApplicationContext val context: Context,
val logger: Logger
val logger: Logger,
val pushNotificationManager: PushNotificationManager
) {

// one-time sync workers
Expand Down Expand Up @@ -106,6 +108,7 @@ class SyncWorkerManager @Inject constructor(
* @param manual user-initiated sync (ignores network checks)
* @param resync whether to request (full) re-synchronization or not
* @param upload see [ContentResolver.SYNC_EXTRAS_UPLOAD] – only used for contacts sync and Android 7 workaround
* @param fromPush whether this sync is initiated by a push notification
*
* @return existing or newly created worker name
*/
Expand All @@ -114,7 +117,8 @@ class SyncWorkerManager @Inject constructor(
authority: String,
manual: Boolean = false,
@InputResync resync: Int = NO_RESYNC,
upload: Boolean = false
upload: Boolean = false,
fromPush: Boolean = false
): String {
// enqueue and start syncing
val name = workerName(account, authority)
Expand All @@ -125,6 +129,10 @@ class SyncWorkerManager @Inject constructor(
resync = resync,
upload = upload
)
if (fromPush) {
logger.fine("Showing push sync pending notification for $name")
pushNotificationManager.notify(account, authority)
}
logger.info("Enqueueing unique worker: $name, tags = ${request.tags}")
WorkManager.getInstance(context).enqueueUniqueWork(
name,
Expand All @@ -147,15 +155,17 @@ class SyncWorkerManager @Inject constructor(
account: Account,
manual: Boolean = false,
@InputResync resync: Int = NO_RESYNC,
upload: Boolean = false
upload: Boolean = false,
fromPush: Boolean = false
) {
for (authority in SyncUtils.syncAuthorities(context))
enqueueOneTime(
account = account,
authority = authority,
manual = manual,
resync = resync,
upload = upload
upload = upload,
fromPush = fromPush
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,6 @@ class NotificationRegistry @Inject constructor(
logger.warning("Notifications disabled, not showing notification $id")
}


// specific common notifications

/**
Expand Down
2 changes: 2 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -490,6 +490,8 @@
<string name="sync_invalid_event">Received invalid event from server</string>
<string name="sync_invalid_task">Received invalid task from server</string>
<string name="sync_invalid_resources_ignoring">Ignoring one or more invalid resources</string>
<string name="sync_notification_pending_push_title">Sync pending</string>
<string name="sync_notification_pending_push_message">Remote data have changed</string>

<!-- widgets -->
<string name="widget_sync_all">Sync all</string>
Expand Down

0 comments on commit c805e54

Please sign in to comment.