From 49a67bc7c6d0ea38c88d8b424a2f188941dc609e Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Tue, 25 Dec 2018 22:14:56 -0500 Subject: Update imageactivity and add tests, resolves #1107 --- .../pitchedapps/frost/services/BaseJobService.kt | 59 ++++++++++++++ .../frost/services/FrostNotifications.kt | 4 - .../frost/services/FrostRequestService.kt | 3 +- .../com/pitchedapps/frost/services/LocalService.kt | 90 ++++++++++++++++++++++ .../frost/services/NotificationService.kt | 32 +++----- 5 files changed, 162 insertions(+), 26 deletions(-) create mode 100644 app/src/main/kotlin/com/pitchedapps/frost/services/BaseJobService.kt create mode 100644 app/src/main/kotlin/com/pitchedapps/frost/services/LocalService.kt (limited to 'app/src/main/kotlin/com/pitchedapps/frost/services') diff --git a/app/src/main/kotlin/com/pitchedapps/frost/services/BaseJobService.kt b/app/src/main/kotlin/com/pitchedapps/frost/services/BaseJobService.kt new file mode 100644 index 00000000..3cc7deaf --- /dev/null +++ b/app/src/main/kotlin/com/pitchedapps/frost/services/BaseJobService.kt @@ -0,0 +1,59 @@ +/* + * Copyright 2018 Allan Wang + * + * 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.pitchedapps.frost.services + +import android.app.job.JobParameters +import android.app.job.JobService +import androidx.annotation.CallSuper +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlin.coroutines.CoroutineContext + +abstract class BaseJobService : JobService(), CoroutineScope { + + private lateinit var job: Job + override val coroutineContext: CoroutineContext + get() = Dispatchers.Main + job + + protected val startTime = System.currentTimeMillis() + + /** + * Note that if a job plans on running asynchronously, it should return true + */ + @CallSuper + override fun onStartJob(params: JobParameters?): Boolean { + job = Job() + return false + } + + @CallSuper + override fun onStopJob(params: JobParameters?): Boolean { + job.cancel() + return false + } +} + +/* + * Collection of ids for job services. + * These should all be unique + */ + +const val NOTIFICATION_JOB_NOW = 6 +const val NOTIFICATION_PERIODIC_JOB = 7 +const val LOCAL_SERVICE_BASE = 110 +const val REQUEST_SERVICE_BASE = 220 diff --git a/app/src/main/kotlin/com/pitchedapps/frost/services/FrostNotifications.kt b/app/src/main/kotlin/com/pitchedapps/frost/services/FrostNotifications.kt index ee515a55..d036d3a8 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/services/FrostNotifications.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/services/FrostNotifications.kt @@ -274,12 +274,8 @@ data class FrostNotification( NotificationManagerCompat.from(context).notify(tag, id, notif.build()) } -const val NOTIFICATION_PERIODIC_JOB = 7 - fun Context.scheduleNotifications(minutes: Long): Boolean = scheduleJob(NOTIFICATION_PERIODIC_JOB, minutes) -const val NOTIFICATION_JOB_NOW = 6 - fun Context.fetchNotifications(): Boolean = fetchJob(NOTIFICATION_JOB_NOW) diff --git a/app/src/main/kotlin/com/pitchedapps/frost/services/FrostRequestService.kt b/app/src/main/kotlin/com/pitchedapps/frost/services/FrostRequestService.kt index 22acc9fb..d41f0b3c 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/services/FrostRequestService.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/services/FrostRequestService.kt @@ -82,7 +82,6 @@ private const val ARG_0 = "frost_request_arg_0" private const val ARG_1 = "frost_request_arg_1" private const val ARG_2 = "frost_request_arg_2" private const val ARG_3 = "frost_request_arg_3" -private const val JOB_REQUEST_BASE = 928 private fun BaseBundle.getCookie() = getString(ARG_COOKIE) private fun BaseBundle.putCookie(cookie: String) = putString(ARG_COOKIE, cookie) @@ -145,7 +144,7 @@ object FrostRunnable { return false } - val builder = JobInfo.Builder(JOB_REQUEST_BASE + command.ordinal, serviceComponent) + val builder = JobInfo.Builder(REQUEST_SERVICE_BASE + command.ordinal, serviceComponent) .setMinimumLatency(0L) .setExtras(bundle) .setOverrideDeadline(2000L) diff --git a/app/src/main/kotlin/com/pitchedapps/frost/services/LocalService.kt b/app/src/main/kotlin/com/pitchedapps/frost/services/LocalService.kt new file mode 100644 index 00000000..3d66f1ee --- /dev/null +++ b/app/src/main/kotlin/com/pitchedapps/frost/services/LocalService.kt @@ -0,0 +1,90 @@ +/* + * Copyright 2018 Allan Wang + * + * 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.pitchedapps.frost.services + +import android.app.job.JobInfo +import android.app.job.JobParameters +import android.app.job.JobScheduler +import android.content.ComponentName +import android.content.Context +import android.os.PersistableBundle +import com.pitchedapps.frost.activities.ImageActivity +import com.pitchedapps.frost.utils.L +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import java.io.FileFilter + +class LocalService : BaseJobService() { + + enum class Flag { + PURGE_IMAGE + } + + companion object { + private const val FLAG = "extra_local_flag" + + /** + * Launches a local service with the provided flag + */ + fun schedule(context: Context, flag: Flag): Boolean { + val scheduler = context.getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler + val serviceComponent = ComponentName(context, LocalService::class.java) + val bundle = PersistableBundle() + bundle.putString(FLAG, flag.name) + + val builder = JobInfo.Builder(LOCAL_SERVICE_BASE + flag.ordinal, serviceComponent) + .setMinimumLatency(0L) + .setExtras(bundle) + .setOverrideDeadline(2000L) + + val result = scheduler.schedule(builder.build()) + if (result <= 0) { + L.eThrow("FrostRequestService scheduler failed for ${flag.name}") + return false + } + L.d { "Scheduled ${flag.name}" } + return true + } + } + + override fun onStartJob(params: JobParameters?): Boolean { + super.onStartJob(params) + val flagString = params?.extras?.getString(FLAG) + val flag: Flag = try { + Flag.valueOf(flagString!!) + } catch (e: Exception) { + L.e { "Local service with invalid flag $flagString" } + return true + } + launch { + when (flag) { + Flag.PURGE_IMAGE -> purgeImages() + } + } + return false + } + + private suspend fun purgeImages() { + withContext(Dispatchers.IO) { + val purge = System.currentTimeMillis() - ImageActivity.PURGE_TIME + ImageActivity.cacheDir(this@LocalService) + .listFiles(FileFilter { it.isFile && it.lastModified() < purge }) + ?.forEach { it.delete() } + } + } +} diff --git a/app/src/main/kotlin/com/pitchedapps/frost/services/NotificationService.kt b/app/src/main/kotlin/com/pitchedapps/frost/services/NotificationService.kt index 3470ca07..7360c191 100644 --- a/app/src/main/kotlin/com/pitchedapps/frost/services/NotificationService.kt +++ b/app/src/main/kotlin/com/pitchedapps/frost/services/NotificationService.kt @@ -17,7 +17,6 @@ package com.pitchedapps.frost.services import android.app.job.JobParameters -import android.app.job.JobService import androidx.core.app.NotificationManagerCompat import ca.allanwang.kau.utils.string import com.pitchedapps.frost.BuildConfig @@ -27,14 +26,10 @@ import com.pitchedapps.frost.dbflow.loadFbCookiesSync import com.pitchedapps.frost.utils.L import com.pitchedapps.frost.utils.Prefs import com.pitchedapps.frost.utils.frostEvent -import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job -import kotlinx.coroutines.async import kotlinx.coroutines.isActive import kotlinx.coroutines.launch -import kotlinx.coroutines.suspendCancellableCoroutine -import kotlin.coroutines.CoroutineContext +import kotlinx.coroutines.withContext /** * Created by Allan Wang on 2017-06-14. @@ -44,22 +39,20 @@ import kotlin.coroutines.CoroutineContext * * All fetching is done through parsers */ -class NotificationService : JobService(), CoroutineScope { - - private lateinit var job: Job - override val coroutineContext: CoroutineContext - get() = Dispatchers.Main + job - - private val startTime = System.currentTimeMillis() +class NotificationService : BaseJobService() { override fun onStopJob(params: JobParameters?): Boolean { + super.onStopJob(params) prepareFinish(true) return false } + private var preparedFinish = false + private fun prepareFinish(abrupt: Boolean) { - if (job.isCancelled) + if (preparedFinish) return + preparedFinish = true val time = System.currentTimeMillis() - startTime L.i { "Notification service has ${if (abrupt) "finished abruptly" else "finished"} in $time ms" } frostEvent( @@ -68,15 +61,14 @@ class NotificationService : JobService(), CoroutineScope { "IM Included" to Prefs.notificationsInstantMessages, "Duration" to time ) - job.cancel() } override fun onStartJob(params: JobParameters?): Boolean { + super.onStartJob(params) L.i { "Fetching notifications" } - job = Job() launch { try { - async { sendNotifications(params) }.await() + sendNotifications(params) } finally { if (!isActive) prepareFinish(false) @@ -86,14 +78,14 @@ class NotificationService : JobService(), CoroutineScope { return true } - private suspend fun sendNotifications(params: JobParameters?): Unit = suspendCancellableCoroutine { + private suspend fun sendNotifications(params: JobParameters?): Unit = withContext(Dispatchers.Default) { val currentId = Prefs.userId val cookies = loadFbCookiesSync() - if (it.isCancelled) return@suspendCancellableCoroutine + if (!isActive) return@withContext val jobId = params?.extras?.getInt(NOTIFICATION_PARAM_ID, -1) ?: -1 var notifCount = 0 for (cookie in cookies) { - if (it.isCancelled) break + if (!isActive) break val current = cookie.id == currentId if (Prefs.notificationsGeneral && (current || Prefs.notificationAllAccounts) -- cgit v1.2.3