Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
2eba92a
Run ReadingListSyncAdapter as expedited work
Isira-Seneviratne Oct 26, 2024
78efb8a
Add permission
Isira-Seneviratne Oct 30, 2024
661b0f2
Reduce lines of code
Isira-Seneviratne Oct 30, 2024
f150815
Added QQ strings
Isira-Seneviratne Oct 30, 2024
21a9fd6
Merge branch 'main' into Reading-list-expedited
Isira-Seneviratne Nov 1, 2024
da6683b
Merge branch 'main' into Reading-list-expedited
Isira-Seneviratne Nov 1, 2024
463346f
Merge branch 'refs/heads/main' into Reading-list-expedited
Isira-Seneviratne Nov 3, 2024
317727a
Merge branch 'main' into Reading-list-expedited
Isira-Seneviratne Nov 10, 2024
3ef16cc
Merge branch 'main' into Reading-list-expedited
Isira-Seneviratne Nov 12, 2024
251ddc4
Merge branch 'main' into Reading-list-expedited
cooltey Nov 14, 2024
0ae4bb8
Display reading list notification immediately on Android 12 and later
Isira-Seneviratne Nov 16, 2024
093efd1
Merge branch 'main' into Reading-list-expedited
Isira-Seneviratne Nov 16, 2024
2c7a531
Merge branch 'main' into Reading-list-expedited
Isira-Seneviratne Nov 30, 2024
38980f5
Merge branch 'main' into Reading-list-expedited
cooltey Dec 2, 2024
26d27bd
Merge branch 'main' into Reading-list-expedited
Isira-Seneviratne Dec 4, 2024
66cacbe
Merge branch 'main' into Reading-list-expedited
Isira-Seneviratne Dec 13, 2024
6c185e5
Merge branch 'main' into Reading-list-expedited
Isira-Seneviratne Dec 31, 2024
42cc94c
Merge branch 'main' into Reading-list-expedited
Isira-Seneviratne Jan 29, 2025
6221f42
Merge branch 'main' into Reading-list-expedited
Isira-Seneviratne Feb 21, 2025
de1743b
Merge branch 'main' into Reading-list-expedited
Isira-Seneviratne Jul 5, 2025
a4e540e
Merge branch 'main' into Reading-list-expedited
Isira-Seneviratne Aug 17, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@
<!-- For being able to post notifications -->
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />

<!-- For syncing reading lists -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC"/>

<!--
List of hardware features that are implicitly used, but not required. These need to be
declared as optional, so that the app can be installed on devices that don't have them.
Expand Down Expand Up @@ -426,6 +430,11 @@
</intent-filter>
</receiver>

<service
android:name="androidx.work.impl.foreground.SystemForegroundService"
android:foregroundServiceType="dataSync"
tools:node="merge" />

<service
android:name=".auth.AuthenticatorService"
android:exported="false">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,27 @@
package org.wikipedia.readinglist.sync

import android.app.Notification
import android.content.*
import android.content.pm.ServiceInfo
import android.graphics.BitmapFactory
import android.os.Build
import android.os.Bundle
import androidx.core.app.NotificationCompat
import androidx.core.os.bundleOf
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.WorkManager
import androidx.work.WorkerParameters
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.wikipedia.R
import org.wikipedia.WikipediaApp
import org.wikipedia.auth.AccountUtil
import org.wikipedia.concurrency.FlowEventBus
Expand All @@ -23,6 +31,7 @@ import org.wikipedia.dataclient.WikiSite
import org.wikipedia.events.ReadingListsEnableDialogEvent
import org.wikipedia.events.ReadingListsEnabledStatusEvent
import org.wikipedia.events.ReadingListsNoLongerSyncedEvent
import org.wikipedia.notifications.NotificationCategory
import org.wikipedia.page.PageTitle
import org.wikipedia.readinglist.database.ReadingList
import org.wikipedia.readinglist.database.ReadingListPage
Expand All @@ -31,10 +40,19 @@ import org.wikipedia.readinglist.sync.SyncedReadingLists.RemoteReadingListEntry
import org.wikipedia.savedpages.SavedPageSyncService
import org.wikipedia.settings.Prefs
import org.wikipedia.settings.RemoteConfig
import org.wikipedia.util.MathUtil.percentage
import org.wikipedia.util.StringUtil
import org.wikipedia.util.log.L

class ReadingListSyncAdapter(context: Context, params: WorkerParameters) : CoroutineWorker(context, params) {
// This is needed for API levels < 31.
override suspend fun getForegroundInfo(): ForegroundInfo {
val title = applicationContext.getString(R.string.notification_syncing_reading_list_initial_title)
val description = applicationContext.getString(R.string.notification_syncing_reading_list_initial_description)
val notification = getNotificationBuilder(title, description, 0, 0).build()

return createForegroundInfo(notification)
}

override suspend fun doWork(): Result {
return withContext(Dispatchers.IO) {
Expand All @@ -53,7 +71,6 @@ class ReadingListSyncAdapter(context: Context, params: WorkerParameters) : Corou
var allLocalLists: MutableList<ReadingList>? = null
val wiki = WikipediaApp.instance.wikiSite
val client = ReadingListClient(wiki)
val readingListSyncNotification = ReadingListSyncNotification.instance
val lastSyncTime = Prefs.readingListsLastSyncTime.orEmpty()
var shouldSendSyncEvent = extras.containsKey(SYNC_EXTRAS_REFRESHING)
var shouldRetry = false
Expand Down Expand Up @@ -149,7 +166,7 @@ class ReadingListSyncAdapter(context: Context, params: WorkerParameters) : Corou

// First, update our list hierarchy to match the remote hierarchy.
for ((remoteItemsSynced, remoteList) in remoteListsModified.withIndex()) {
readingListSyncNotification.setNotificationProgress(applicationContext, remoteItemsTotal, remoteItemsSynced)
updateForeground(remoteItemsTotal, remoteItemsSynced)
// Find the remote list in our local lists...
var localList: ReadingList? = null
var upsertNeeded = false
Expand Down Expand Up @@ -298,7 +315,7 @@ class ReadingListSyncAdapter(context: Context, params: WorkerParameters) : Corou

// Determine whether any remote lists need to be created or updated
for ((localItemsSynced, localList) in allLocalLists.withIndex()) {
readingListSyncNotification.setNotificationProgress(applicationContext, localItemsTotal, localItemsSynced)
updateForeground(localItemsTotal, localItemsSynced)
val remoteList = RemoteReadingList(name = localList.title, description = localList.description)
var upsertNeeded = false
if (localList.remoteId > 0) {
Expand Down Expand Up @@ -418,7 +435,6 @@ class ReadingListSyncAdapter(context: Context, params: WorkerParameters) : Corou
Prefs.readingListsLastSyncTime = client.lastDateHeader?.toString() ?: lastSyncTime
Prefs.readingListsDeletedIds = listIdsDeleted
Prefs.readingListPagesDeletedIds = pageIdsDeleted
readingListSyncNotification.cancelNotification(applicationContext)
if (shouldSendSyncEvent) {
SavedPageSyncService.sendSyncEvent(extras.containsKey(SYNC_EXTRAS_REFRESHING))
}
Expand All @@ -439,6 +455,44 @@ class ReadingListSyncAdapter(context: Context, params: WorkerParameters) : Corou
}
}

private suspend fun updateForeground(total: Int, progress: Int) {
val resources = applicationContext.resources
val title = resources.getQuantityString(R.plurals.notification_syncing_reading_list_title, total, total)
val description = resources.getQuantityString(R.plurals.notification_syncing_reading_list_description, total - progress, total - progress)
val contentInfo = "${percentage(progress.toFloat(), total.toFloat()).toInt()}%"
val notification = getNotificationBuilder(title, description, total, progress)
.setContentInfo(contentInfo)
.apply {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
setSubText(contentInfo)
}
}
.build()

setForeground(createForegroundInfo(notification))
}

private fun createForegroundInfo(notification: Notification): ForegroundInfo {
val type = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC else 0
return ForegroundInfo(NOTIFICATION_ID, notification, type)
}

private fun getNotificationBuilder(
title: String, description: String, total: Int, progress: Int
): NotificationCompat.Builder {
val icon = NotificationCategory.READING_LIST_SYNCING.iconResId
return NotificationCompat.Builder(applicationContext, NotificationCategory.READING_LIST_SYNCING.id)
.setSmallIcon(icon)
.setLargeIcon(BitmapFactory.decodeResource(applicationContext.resources, icon))
.setContentTitle(title)
.setContentText(description)
.setStyle(NotificationCompat.BigTextStyle().bigText(description))
.setSilent(true)
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setProgress(total, progress, progress == 0)
.setForegroundServiceBehavior(NotificationCompat.FOREGROUND_SERVICE_IMMEDIATE)
}

private fun getBooleanExtraFromData(inputData: Data): Bundle {
val extras = Bundle()
inputData.keyValueMap.forEach {
Expand Down Expand Up @@ -493,6 +547,8 @@ class ReadingListSyncAdapter(context: Context, params: WorkerParameters) : Corou
}

companion object {
private const val NOTIFICATION_ID = 1002

private const val WORK_NAME = "readingListSyncAdapter"
private const val SYNC_EXTRAS_FORCE_FULL_SYNC = "forceFullSync"
private const val SYNC_EXTRAS_REFRESHING = "refreshing"
Expand Down Expand Up @@ -549,20 +605,16 @@ class ReadingListSyncAdapter(context: Context, params: WorkerParameters) : Corou
}
return
}
extras.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true)
extras.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true)

// Convert the Bundle to a Data object
val dataBuilder = Data.Builder()
extras.keySet().forEach {
dataBuilder.putBoolean(it, extras.getBoolean(it))
}

val constraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build()
val workRequest = OneTimeWorkRequestBuilder<ReadingListSyncAdapter>()
.setConstraints(constraints)
.setConstraints(Constraints(NetworkType.CONNECTED))
.setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
.setInputData(dataBuilder.build())
.build()
WorkManager.getInstance(WikipediaApp.instance)
Expand Down

This file was deleted.

2 changes: 2 additions & 0 deletions app/src/main/res/values-qq/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -732,6 +732,8 @@
<item quantity="one">Name of the notification channel of syncing one reading list</item>
<item quantity="other">Name of the notification channel of syncing two or more reading lists</item>
</plurals>
<string name="notification_syncing_reading_list_initial_title">Title for notification when syncing reading lists begins.</string>
<string name="notification_syncing_reading_list_initial_description">Text of notification when syncing reading lists begins.</string>
<plurals name="notification_syncing_reading_list_title">
<item quantity="one">Title for notification when the syncing one reading list process begins. The \"%1$d\" symbol corresponds to the amount of lists.</item>
<item quantity="other">Title for notification when the syncing two or more reading lists process begins. The \"%1$d\" symbol corresponds to the amount of lists.</item>
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 @@ -723,6 +723,8 @@
<item quantity="one">Syncing Reading List</item>
<item quantity="other">Syncing Reading Lists</item>
</plurals>
<string name="notification_syncing_reading_list_initial_title">Syncing lists</string>
<string name="notification_syncing_reading_list_initial_description">Starting sync…</string>
<plurals name="notification_syncing_reading_list_title">
<item quantity="one">Syncing %1$d list&#8230;</item>
<item quantity="other">Syncing %1$d lists&#8230;</item>
Expand Down
Loading