From f24e9b2c168a348a6c5f45fb9a0942de5bc63128 Mon Sep 17 00:00:00 2001 From: Prateek Singh <76490368+prateek-singh-3212@users.noreply.github.com> Date: Fri, 3 Jun 2022 03:01:48 +0530 Subject: [PATCH] Implemented Work Manager --- AnkiDroid/build.gradle | 3 + AnkiDroid/src/main/AndroidManifest.xml | 17 ++- .../java/com/ichi2/anki/AnkiDroidApp.java | 40 +++++- .../com/ichi2/anki/DeckMetaDataPreference.kt | 24 +++- .../main/java/com/ichi2/anki/IntentHandler.kt | 12 -- .../anki/services/DeckMetaDataService.kt | 135 ------------------ .../ichi2/anki/worker/DeckMetaDataWorker.kt | 92 ++++++++++++ 7 files changed, 166 insertions(+), 157 deletions(-) delete mode 100644 AnkiDroid/src/main/java/com/ichi2/anki/services/DeckMetaDataService.kt create mode 100644 AnkiDroid/src/main/java/com/ichi2/anki/worker/DeckMetaDataWorker.kt diff --git a/AnkiDroid/build.gradle b/AnkiDroid/build.gradle index 0e670e535f44..b67e1c688906 100644 --- a/AnkiDroid/build.gradle +++ b/AnkiDroid/build.gradle @@ -331,5 +331,8 @@ dependencies { // GSON implementation 'com.google.code.gson:gson:2.9.0' + + // Work Manager + implementation "androidx.work:work-runtime-ktx:2.7.1" } apply from: "./kotlinMigration.gradle" diff --git a/AnkiDroid/src/main/AndroidManifest.xml b/AnkiDroid/src/main/AndroidManifest.xml index 62377325650b..60cff0a91f6c 100644 --- a/AnkiDroid/src/main/AndroidManifest.xml +++ b/AnkiDroid/src/main/AndroidManifest.xml @@ -377,11 +377,6 @@ - - + + + + + diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/AnkiDroidApp.java b/AnkiDroid/src/main/java/com/ichi2/anki/AnkiDroidApp.java index fcc10423aced..5d24ddce2ddc 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/AnkiDroidApp.java +++ b/AnkiDroid/src/main/java/com/ichi2/anki/AnkiDroidApp.java @@ -43,6 +43,7 @@ import com.ichi2.anki.exception.StorageAccessException; import com.ichi2.anki.services.BootService; import com.ichi2.anki.services.NotificationService; +import com.ichi2.anki.worker.DeckMetaDataWorker; import com.ichi2.compat.CompatHelper; import com.ichi2.utils.AdaptionUtil; import com.ichi2.utils.ExceptionUtil; @@ -52,9 +53,13 @@ import java.io.InputStream; import java.util.Locale; +import java.util.concurrent.TimeUnit; import java.util.regex.Matcher; import java.util.regex.Pattern; +import androidx.work.ExistingPeriodicWorkPolicy; +import androidx.work.PeriodicWorkRequest; +import androidx.work.WorkManager; import timber.log.Timber; import static com.ichi2.anki.CrashReportService.sendExceptionReport; @@ -63,7 +68,7 @@ /** * Application class. */ -public class AnkiDroidApp extends Application { +public class AnkiDroidApp extends Application implements androidx.work.Configuration.Provider { /** Running under instrumentation. a "/androidTest" directory will be created which contains a test collection */ public static boolean INSTRUMENTATION_TESTING = false; @@ -226,6 +231,9 @@ public void onCreate() { Timber.i("AnkiDroidApp: Starting Services"); new BootService().onReceive(this, new Intent(this, BootService.class)); + Timber.i("AnkiDroidApp: Starting Workers"); + setupDeckMetaDataWorker(); + // Register BroadcastReceiver NotificationService NotificationService ns = new NotificationService(); LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(this); @@ -411,6 +419,36 @@ public static String getWebViewErrorMessage() { return ExceptionUtil.getExceptionMessage(error); } + /** + * This function setups the work manager which run periodically. + **/ + private void setupDeckMetaDataWorker() { + Timber.tag("META").e("Setting up deck meta data worker..."); + + PeriodicWorkRequest deckMetaDataWorker = new PeriodicWorkRequest.Builder(DeckMetaDataWorker.class, 2, TimeUnit.MINUTES) + .addTag(DeckMetaDataWorker.DECK_META_WORKER) + .build(); + + WorkManager.getInstance(this) + .enqueueUniquePeriodicWork( + DeckMetaDataWorker.DECK_META_WORKER, + ExistingPeriodicWorkPolicy.REPLACE, + deckMetaDataWorker + ); + } + + + /** + * This Method sets the Work Manager Configuration. We are using Custom work manager Initialization. + * */ + @NonNull + @Override + public androidx.work.Configuration getWorkManagerConfiguration() { + return new androidx.work.Configuration.Builder() + .setMinimumLoggingLevel(android.util.Log.INFO) + .build(); + } + /** * A tree which logs necessary data for crash reporting. * diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/DeckMetaDataPreference.kt b/AnkiDroid/src/main/java/com/ichi2/anki/DeckMetaDataPreference.kt index 6821731327ac..b10cea04c7b5 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/DeckMetaDataPreference.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/DeckMetaDataPreference.kt @@ -1,9 +1,25 @@ +/* + * Copyright (c) 2022 Prateek Singh + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation; either version 3 of the License, or (at your option) any later + * version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + package com.ichi2.anki import android.content.Context import android.content.SharedPreferences import com.google.gson.Gson -import com.ichi2.anki.services.DeckMetaDataService +import com.ichi2.anki.worker.DeckMetaDataWorker import timber.log.Timber class DeckMetaDataPreference(context: Context) { @@ -21,7 +37,7 @@ class DeckMetaDataPreference(context: Context) { return sharedPreferences.getString(key, default)!! } - fun setMetaData(key: String, value: DeckMetaDataService.Meta) { + fun setMetaData(key: String, value: DeckMetaDataWorker.Meta) { val json: String = Gson().toJson(value) sharedPreferences.edit() .putString(key, json) @@ -29,14 +45,14 @@ class DeckMetaDataPreference(context: Context) { Timber.tag("META").e(json) } - fun getMetaData(key: String): DeckMetaDataService.Meta? { + fun getMetaData(key: String): DeckMetaDataWorker.Meta? { val jsonData = sharedPreferences .getString(key, null) return if (jsonData == null) { null } else { - Gson().fromJson(jsonData, DeckMetaDataService.Meta::class.java) + Gson().fromJson(jsonData, DeckMetaDataWorker.Meta::class.java) } } } diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/IntentHandler.kt b/AnkiDroid/src/main/java/com/ichi2/anki/IntentHandler.kt index f316a2efb320..9c1ddf7e78bd 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/IntentHandler.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/IntentHandler.kt @@ -27,7 +27,6 @@ import com.ichi2.anki.UIUtils.showThemedToast import com.ichi2.anki.dialogs.DialogHandler import com.ichi2.anki.dialogs.DialogHandler.Companion.storeMessage import com.ichi2.anki.servicelayer.ScopedStorageService -import com.ichi2.anki.services.DeckMetaDataService import com.ichi2.anki.services.ReminderService import com.ichi2.themes.Themes.disableXiaomiForceDarkMode import com.ichi2.utils.ImportUtils.handleFileImport @@ -49,7 +48,6 @@ class IntentHandler : Activity() { Timber.d("onCreate()") super.onCreate(savedInstanceState) disableXiaomiForceDarkMode(this) - launchDeckMetaDataService() setContentView(R.layout.progress_bar) val intent = intent Timber.v(intent.toString()) @@ -138,16 +136,6 @@ class IntentHandler : Activity() { finish() } - private fun launchDeckMetaDataService() { - Timber.tag("META").e("Launching deck meta data service...") - val intent = Intent(this, DeckMetaDataService::class.java) - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - startForegroundService(intent) - } else { - startService(intent) - } - } - // COULD_BE_BETTER: Also extract the parameters into here to reduce coupling @VisibleForTesting enum class LaunchType { diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/services/DeckMetaDataService.kt b/AnkiDroid/src/main/java/com/ichi2/anki/services/DeckMetaDataService.kt deleted file mode 100644 index fbf7717a3a9a..000000000000 --- a/AnkiDroid/src/main/java/com/ichi2/anki/services/DeckMetaDataService.kt +++ /dev/null @@ -1,135 +0,0 @@ -package com.ichi2.anki.services - -import android.annotation.SuppressLint -import android.app.AlarmManager -import android.app.Notification -import android.app.PendingIntent -import android.app.Service -import android.content.Intent -import android.os.IBinder -import androidx.core.app.NotificationCompat -import com.ichi2.anki.CollectionHelper -import com.ichi2.anki.DeckMetaDataPreference -import com.ichi2.anki.NotificationChannels -import com.ichi2.anki.R -import com.ichi2.libanki.sched.Counts -import com.ichi2.libanki.utils.Time -import timber.log.Timber - -/** - * This Service is responsible to Collect all the metadata and store it in share preference. - * Technically it refreshes the Deck Meta Data. Whenever this service is called - * then I will collect the new data of decks. - * */ -class DeckMetaDataService : Service() { - - private lateinit var colHelper: CollectionHelper - - override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { - - Timber.tag("META").e("On Service start command...") - - colHelper = CollectionHelper.getInstance() - - /** - * It sets the notification till the service is completed. - * */ - val notification = NotificationCompat.Builder(this, NotificationChannels.getId(NotificationChannels.Channel.GENERAL)) - .setOngoing(true) - .setContentTitle("Anki") - .setContentText("Ankidroid is syncing deck data.") - .setSmallIcon(R.mipmap.ic_launcher) - .setPriority(NotificationCompat.PRIORITY_LOW) - .setSilent(true) - .setCategory(Notification.CATEGORY_SERVICE) - .build() - - startForeground(FOREGROUND_SERVICE_ID, notification) - - // Update the data of Deck Metadata from HERE - val col = colHelper.getCol(this) - val deckList = col.sched.deckDueList() - - deckList.forEach { - val new = it?.newCount ?: -1 - val lrn = it?.lrnCount ?: -1 - val rev = it?.revCount ?: -1 - val data = Meta( - it?.did ?: -1, - it?.fullDeckName ?: "Deck Name", - new, - lrn, - rev, - col.sched.eta(Counts(new, lrn, rev), false) - ) - storeInPreference(data) - } - - updateLastFetch(colHelper.getTimeSafe(this).toString()) - - stopSelf() // Stops the current running service. - return START_NOT_STICKY - } - - private fun updateLastFetch(time: String) { - val metaPreference = DeckMetaDataPreference(this) - metaPreference.putString(LAST_FETCH_TIME, time) - } - - /** - * It is used to store the data in the DeckMetaData Shared Preference. - * */ - private fun storeInPreference(list: Meta) { - val metaPreference = DeckMetaDataPreference(this) - metaPreference.setMetaData(list.did.toString(), list) - } - - /** - * Does Nothing - * */ - override fun onBind(intent: Intent?): IBinder? { - // Kept Intentionally - return null - } - - override fun onDestroy() { - Timber.tag("META").e("Deck meta data Service on Destroy...") - scheduleNextAlarm(Long.MAX_VALUE) // Temp - } - - /** - * It is used to schedule the next alarm which helps in starting this service again. - **/ - @SuppressLint("UnspecifiedImmutableFlag") - private fun scheduleNextAlarm(interval: Long) { - val time = colHelper.getTimeSafe(this).intTimeMS() + interval - - val intent = Intent(this, DeckMetaDataService::class.java) - val pendingIntent = PendingIntent.getService( - this.applicationContext, SERVICE_PENDING_INTENT_ID, intent, 0 - ) - - val alarmManager = getSystemService(ALARM_SERVICE) as AlarmManager - alarmManager.set( - AlarmManager.RTC_WAKEUP, - time, - pendingIntent - ) - Timber.tag("META").e("Service will start again at %s", (Time.calendar(time).time)) - } - - data class Meta( - val did: Long, - val deckName: String, - val new: Int, - val lrn: Int, - val rev: Int, - val eta: Int - ) - - companion object { - const val FOREGROUND_SERVICE_ID = 10001 - const val SERVICE_PENDING_INTENT_ID = 10002 - const val LAST_FETCH_TIME = "LAST_FETCH" - } -} diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/worker/DeckMetaDataWorker.kt b/AnkiDroid/src/main/java/com/ichi2/anki/worker/DeckMetaDataWorker.kt new file mode 100644 index 000000000000..fb4a53bca815 --- /dev/null +++ b/AnkiDroid/src/main/java/com/ichi2/anki/worker/DeckMetaDataWorker.kt @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2022 Prateek Singh + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation; either version 3 of the License, or (at your option) any later + * version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +package com.ichi2.anki.worker + +import android.content.Context +import androidx.work.Worker +import androidx.work.WorkerParameters +import com.ichi2.anki.CollectionHelper +import com.ichi2.anki.DeckMetaDataPreference +import com.ichi2.libanki.sched.Counts +import timber.log.Timber + +/** + * This Worker is responsible to Collect all the metadata and store it in share preference. + * Technically it refreshes the Deck Meta Data. Whenever this service is called + * then I will collect the new data of decks. + * */ +class DeckMetaDataWorker(val context: Context, workerParameters: WorkerParameters) : Worker(context, workerParameters) { + + private lateinit var colHelper: CollectionHelper + + override fun doWork(): Result { + + colHelper = CollectionHelper.getInstance() + + Timber.tag("META").e("Deck Meta Data Worker started at: ${colHelper.getTimeSafe(context).currentDate}") + + // Update the data of Deck Metadata from HERE + val col = colHelper.getCol(context) + val deckList = col.sched.deckDueList() + + deckList.forEach { + val new = it?.newCount ?: -1 + val lrn = it?.lrnCount ?: -1 + val rev = it?.revCount ?: -1 + val data = Meta( + it?.did ?: -1, + it?.fullDeckName ?: "Deck Name", + new, + lrn, + rev, + col.sched.eta(Counts(new, lrn, rev), false) + ) + storeInPreference(data) + } + + updateLastFetch(colHelper.getTimeSafe(context).toString()) + + return Result.success() // Done work successfully... + } + + private fun updateLastFetch(time: String) { + val metaPreference = DeckMetaDataPreference(context) + metaPreference.putString(LAST_FETCH_TIME, time) + } + + /** + * It is used to store the data in the DeckMetaData Shared Preference. + * */ + private fun storeInPreference(list: Meta) { + val metaPreference = DeckMetaDataPreference(context) + metaPreference.setMetaData(list.did.toString(), list) + } + + data class Meta( + val did: Long, + val deckName: String, + val new: Int, + val lrn: Int, + val rev: Int, + val eta: Int + ) + + companion object { + const val DECK_META_WORKER = "DeckMetaData" + const val LAST_FETCH_TIME = "LAST_FETCH" + } +}