Optimize background Workers, remove redundant JobServices
1. Adapt the location callback in WeatherWorker to coroutines 2. Merge EventListenerJob into UpdateCalendarWorker 3. Remove BatteryListenerJob, as it's impossible to use Jobs to detect battery status
This commit is contained in:
parent
388653f62b
commit
d8e204c5d9
@ -108,10 +108,6 @@
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<service android:name=".services.EventListenerJob" android:permission="android.permission.BIND_JOB_SERVICE" />
|
||||
|
||||
<service android:name=".services.BatteryListenerJob" android:permission="android.permission.BIND_JOB_SERVICE" />
|
||||
|
||||
<service android:name=".receivers.NotificationListener" android:exported="true"
|
||||
android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
|
||||
<intent-filter>
|
||||
|
@ -4,7 +4,6 @@ import android.Manifest
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.provider.CalendarContract
|
||||
import com.tommasoberlose.anotherwidget.services.EventListenerJob
|
||||
import com.tommasoberlose.anotherwidget.models.Event
|
||||
import com.tommasoberlose.anotherwidget.global.Preferences
|
||||
import com.tommasoberlose.anotherwidget.services.UpdateCalendarWorker
|
||||
@ -50,11 +49,11 @@ object CalendarHelper {
|
||||
}
|
||||
|
||||
fun setEventUpdatesAndroidN(context: Context) {
|
||||
EventListenerJob.schedule(context)
|
||||
UpdateCalendarWorker.enqueueTrigger(context)
|
||||
}
|
||||
|
||||
fun removeEventUpdatesAndroidN(context: Context) {
|
||||
EventListenerJob.remove(context)
|
||||
UpdateCalendarWorker.cancelTrigger(context)
|
||||
}
|
||||
|
||||
fun List<Event>.applyFilters() : List<Event> {
|
||||
|
@ -324,7 +324,9 @@ object WeatherHelper {
|
||||
}
|
||||
}
|
||||
|
||||
fun getWeatherGovIcon(iconString: String, isDaytime: Boolean): String = when (iconString.substringBefore('?').substringAfterLast('/')) {
|
||||
fun getWeatherGovIcon(iconString: String, isDaytime: Boolean): String = when (
|
||||
iconString.substringBefore('?').substringAfterLast('/').substringBefore(',')
|
||||
) {
|
||||
"skc" -> "01"
|
||||
"few" -> "02"
|
||||
"sct" -> "02"
|
||||
|
@ -42,12 +42,12 @@ class WeatherReceiver : BroadcastReceiver() {
|
||||
5 -> 60L * 24
|
||||
else -> 60
|
||||
}
|
||||
WeatherWorker.enqueue(context, interval, TimeUnit.MINUTES)
|
||||
WeatherWorker.enqueuePeriodic(context, interval, TimeUnit.MINUTES)
|
||||
}
|
||||
}
|
||||
|
||||
fun removeUpdates(context: Context) {
|
||||
WeatherWorker.cancel(context)
|
||||
WeatherWorker.cancelPeriodic(context)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,61 +0,0 @@
|
||||
package com.tommasoberlose.anotherwidget.services
|
||||
|
||||
import android.app.job.JobInfo
|
||||
import android.app.job.JobParameters
|
||||
import android.app.job.JobScheduler
|
||||
import android.app.job.JobService
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import android.provider.CalendarContract
|
||||
import com.tommasoberlose.anotherwidget.helpers.CalendarHelper
|
||||
import com.tommasoberlose.anotherwidget.ui.widgets.MainWidget
|
||||
|
||||
class BatteryListenerJob : JobService() {
|
||||
override fun onStartJob(params: JobParameters): Boolean {
|
||||
MainWidget.updateWidget(this)
|
||||
schedule(
|
||||
this
|
||||
)
|
||||
return false
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
override fun onStopJob(params: JobParameters): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val chargingJobId = 1006
|
||||
private const val notChargingJobId = 1007
|
||||
fun schedule(context: Context) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
remove(context)
|
||||
val componentName = ComponentName(
|
||||
context,
|
||||
EventListenerJob::class.java
|
||||
)
|
||||
with(context.getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler) {
|
||||
schedule(
|
||||
JobInfo.Builder(chargingJobId, componentName)
|
||||
.setRequiresCharging(true)
|
||||
.setPersisted(true)
|
||||
.build()
|
||||
)
|
||||
schedule(
|
||||
JobInfo.Builder(notChargingJobId, componentName)
|
||||
.setRequiresCharging(false)
|
||||
.setPersisted(true)
|
||||
.build()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun remove(context: Context) {
|
||||
val js = context.getSystemService(JobScheduler::class.java)
|
||||
js?.cancel(chargingJobId)
|
||||
js?.cancel(notChargingJobId)
|
||||
}
|
||||
}
|
||||
}
|
@ -1,55 +0,0 @@
|
||||
package com.tommasoberlose.anotherwidget.services
|
||||
|
||||
import android.app.job.JobInfo
|
||||
import android.app.job.JobInfo.TriggerContentUri
|
||||
import android.app.job.JobParameters
|
||||
import android.app.job.JobScheduler
|
||||
import android.app.job.JobService
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import android.provider.CalendarContract
|
||||
import com.tommasoberlose.anotherwidget.helpers.CalendarHelper
|
||||
|
||||
|
||||
class EventListenerJob : JobService() {
|
||||
override fun onStartJob(params: JobParameters): Boolean {
|
||||
CalendarHelper.updateEventList(this)
|
||||
schedule(
|
||||
this
|
||||
)
|
||||
return false
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
override fun onStopJob(params: JobParameters): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val jobId = 1005
|
||||
fun schedule(context: Context) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
val componentName = ComponentName(
|
||||
context,
|
||||
EventListenerJob::class.java
|
||||
)
|
||||
with(context.getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler) {
|
||||
schedule(
|
||||
JobInfo.Builder(jobId, componentName)
|
||||
.addTriggerContentUri(TriggerContentUri(
|
||||
CalendarContract.CONTENT_URI,
|
||||
TriggerContentUri.FLAG_NOTIFY_FOR_DESCENDANTS
|
||||
))
|
||||
.build()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun remove(context: Context) {
|
||||
val js = context.getSystemService(JobScheduler::class.java)
|
||||
js?.cancel(jobId)
|
||||
}
|
||||
}
|
||||
}
|
@ -2,6 +2,9 @@ package com.tommasoberlose.anotherwidget.services
|
||||
|
||||
import android.Manifest
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import android.provider.CalendarContract
|
||||
import androidx.work.Constraints
|
||||
import androidx.work.CoroutineWorker
|
||||
import androidx.work.ExistingWorkPolicy
|
||||
import androidx.work.OneTimeWorkRequestBuilder
|
||||
@ -17,12 +20,12 @@ import com.tommasoberlose.anotherwidget.receivers.UpdatesReceiver
|
||||
import com.tommasoberlose.anotherwidget.ui.fragments.MainFragment
|
||||
import com.tommasoberlose.anotherwidget.ui.widgets.MainWidget
|
||||
import com.tommasoberlose.anotherwidget.utils.checkGrantedPermission
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import me.everything.providers.android.calendar.CalendarProvider
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import java.util.*
|
||||
import kotlin.collections.ArrayList
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
class UpdateCalendarWorker(private val context: Context, params: WorkerParameters) :
|
||||
CoroutineWorker(context, params) {
|
||||
@ -36,7 +39,6 @@ class UpdateCalendarWorker(private val context: Context, params: WorkerParameter
|
||||
if (!context.checkGrantedPermission(Manifest.permission.READ_CALENDAR)) {
|
||||
eventRepository.resetNextEventData()
|
||||
eventRepository.clearEvents()
|
||||
Preferences.showEvents = false
|
||||
} else {
|
||||
// fetch all events from now to next ACTION_CALENDAR_UPDATE + limit
|
||||
val now = Calendar.getInstance()
|
||||
@ -139,6 +141,7 @@ class UpdateCalendarWorker(private val context: Context, params: WorkerParameter
|
||||
} catch (ignored: java.lang.Exception) {
|
||||
}
|
||||
}
|
||||
enqueueTrigger(context)
|
||||
} else {
|
||||
eventRepository.resetNextEventData()
|
||||
eventRepository.clearEvents()
|
||||
@ -156,11 +159,34 @@ class UpdateCalendarWorker(private val context: Context, params: WorkerParameter
|
||||
|
||||
companion object {
|
||||
fun enqueue(context: Context) {
|
||||
WorkManager.getInstance(context.applicationContext).enqueueUniqueWork(
|
||||
"UpdateCalendarWorker",
|
||||
ExistingWorkPolicy.REPLACE,
|
||||
WorkManager.getInstance(context).enqueueUniqueWork(
|
||||
"updateEventList",
|
||||
ExistingWorkPolicy.KEEP,
|
||||
OneTimeWorkRequestBuilder<UpdateCalendarWorker>().build()
|
||||
)
|
||||
}
|
||||
|
||||
fun enqueueTrigger(context: Context) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
WorkManager.getInstance(context).enqueueUniqueWork(
|
||||
"updateEventListByTrigger",
|
||||
ExistingWorkPolicy.KEEP,
|
||||
OneTimeWorkRequestBuilder<UpdateCalendarWorker>().setConstraints(
|
||||
Constraints.Builder().addContentUriTrigger(
|
||||
CalendarContract.CONTENT_URI,
|
||||
true
|
||||
).build()
|
||||
).build()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun cancelTrigger(context: Context) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
WorkManager.getInstance(context).cancelUniqueWork(
|
||||
"updateEventListByTrigger"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -14,19 +14,18 @@ import androidx.work.WorkerParameters
|
||||
import com.google.android.gms.common.ConnectionResult
|
||||
import com.google.android.gms.common.GoogleApiAvailability
|
||||
import com.google.android.gms.location.LocationServices
|
||||
import com.google.android.gms.tasks.Tasks
|
||||
import com.tommasoberlose.anotherwidget.R
|
||||
import com.tommasoberlose.anotherwidget.global.Preferences
|
||||
import com.tommasoberlose.anotherwidget.helpers.WeatherHelper
|
||||
import com.tommasoberlose.anotherwidget.network.WeatherNetworkApi
|
||||
import com.tommasoberlose.anotherwidget.ui.fragments.MainFragment
|
||||
import com.tommasoberlose.anotherwidget.utils.checkGrantedPermission
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import java.util.concurrent.TimeUnit
|
||||
import kotlin.coroutines.resume
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
class WeatherWorker(private val context: Context, params: WorkerParameters) :
|
||||
CoroutineWorker(context, params) {
|
||||
@ -42,7 +41,11 @@ class WeatherWorker(private val context: Context, params: WorkerParameters) :
|
||||
if (GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(context)
|
||||
== ConnectionResult.SUCCESS
|
||||
) {
|
||||
LocationServices.getFusedLocationProviderClient(context).lastLocation
|
||||
suspendCancellableCoroutine { continuation ->
|
||||
LocationServices.getFusedLocationProviderClient(context).lastLocation.addOnCompleteListener {
|
||||
continuation.resume(if (it.isSuccessful) it.result else null)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
val lm = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
|
||||
var location: Location? = null
|
||||
@ -54,21 +57,15 @@ class WeatherWorker(private val context: Context, params: WorkerParameters) :
|
||||
location = it
|
||||
}
|
||||
}
|
||||
Tasks.forResult(location)
|
||||
}.addOnCompleteListener { task ->
|
||||
val networkApi = WeatherNetworkApi(context)
|
||||
if (task.isSuccessful) {
|
||||
val location = task.result
|
||||
if (location != null) {
|
||||
Preferences.customLocationLat = location.latitude.toString()
|
||||
Preferences.customLocationLon = location.longitude.toString()
|
||||
}
|
||||
location
|
||||
}.let { location ->
|
||||
if (location != null) {
|
||||
Preferences.customLocationLat = location.latitude.toString()
|
||||
Preferences.customLocationLon = location.longitude.toString()
|
||||
}
|
||||
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
networkApi.updateWeather()
|
||||
withContext(Dispatchers.IO) {
|
||||
WeatherNetworkApi(context).updateWeather()
|
||||
}
|
||||
EventBus.getDefault().post(MainFragment.UpdateUiMessageEvent())
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
@ -83,24 +80,24 @@ class WeatherWorker(private val context: Context, params: WorkerParameters) :
|
||||
|
||||
companion object {
|
||||
fun enqueue(context: Context) {
|
||||
WorkManager.getInstance(context.applicationContext).enqueueUniqueWork(
|
||||
"OneTimeWeatherWorker",
|
||||
WorkManager.getInstance(context).enqueueUniqueWork(
|
||||
"updateWeather",
|
||||
ExistingWorkPolicy.REPLACE,
|
||||
OneTimeWorkRequestBuilder<WeatherWorker>().build()
|
||||
)
|
||||
}
|
||||
|
||||
fun enqueue(context: Context, interval: Long, unit: TimeUnit) {
|
||||
WorkManager.getInstance(context.applicationContext).enqueueUniquePeriodicWork(
|
||||
"WeatherWorker",
|
||||
fun enqueuePeriodic(context: Context, interval: Long, unit: TimeUnit) {
|
||||
WorkManager.getInstance(context).enqueueUniquePeriodicWork(
|
||||
"updateWeatherPeriodically",
|
||||
ExistingPeriodicWorkPolicy.REPLACE,
|
||||
PeriodicWorkRequestBuilder<WeatherWorker>(interval, unit).build()
|
||||
)
|
||||
}
|
||||
|
||||
fun cancel(context: Context) {
|
||||
WorkManager.getInstance(context.applicationContext).cancelUniqueWork(
|
||||
"WeatherWorker"
|
||||
fun cancelPeriodic(context: Context) {
|
||||
WorkManager.getInstance(context).cancelUniqueWork(
|
||||
"updateWeatherPeriodically"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -141,7 +141,6 @@ class MainActivity : AppCompatActivity(), SharedPreferences.OnSharedPreferenceCh
|
||||
|
||||
if (Preferences.showEvents && !checkGrantedPermission(Manifest.permission.READ_CALENDAR)) {
|
||||
Preferences.showEvents = false
|
||||
com.tommasoberlose.anotherwidget.receivers.UpdatesReceiver.removeUpdates(this)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -88,6 +88,7 @@ class PreferencesFragment : Fragment() {
|
||||
CalendarHelper.setEventUpdatesAndroidN(requireContext())
|
||||
} else {
|
||||
CalendarHelper.removeEventUpdatesAndroidN(requireContext())
|
||||
UpdatesReceiver.removeUpdates(requireContext())
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -124,7 +125,6 @@ class PreferencesFragment : Fragment() {
|
||||
requireCalendarPermission()
|
||||
} else {
|
||||
Preferences.showEvents = enabled
|
||||
UpdatesReceiver.removeUpdates(requireContext())
|
||||
}
|
||||
}
|
||||
|
||||
@ -165,6 +165,7 @@ class PreferencesFragment : Fragment() {
|
||||
.withPermissions(
|
||||
Manifest.permission.READ_CALENDAR
|
||||
).withListener(object: MultiplePermissionsListener {
|
||||
private var shouldShowRationale = false
|
||||
override fun onPermissionsChecked(report: MultiplePermissionsReport?) {
|
||||
report?.let {
|
||||
val granted = report.areAllPermissionsGranted()
|
||||
@ -172,12 +173,33 @@ class PreferencesFragment : Fragment() {
|
||||
if (granted) {
|
||||
CalendarHelper.updateEventList(requireContext())
|
||||
}
|
||||
else if (!shouldShowRationale && report.isAnyPermissionPermanentlyDenied) {
|
||||
MaterialBottomSheetDialog(
|
||||
requireContext(),
|
||||
getString(R.string.title_permission_calendar),
|
||||
getString(R.string.description_permission_calendar)
|
||||
).setNegativeButton(getString(R.string.action_ignore))
|
||||
.setPositiveButton(getString(R.string.action_grant_permission)) {
|
||||
startActivity(
|
||||
android.content.Intent(
|
||||
android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS
|
||||
).apply {
|
||||
data = android.net.Uri.fromParts(
|
||||
"package",
|
||||
requireContext().packageName,
|
||||
null
|
||||
)
|
||||
}
|
||||
)
|
||||
}.show()
|
||||
}
|
||||
}
|
||||
}
|
||||
override fun onPermissionRationaleShouldBeShown(
|
||||
permissions: MutableList<PermissionRequest>?,
|
||||
token: PermissionToken?
|
||||
) {
|
||||
shouldShowRationale = true
|
||||
// Remember to invoke this method when the custom rationale is closed
|
||||
// or just by default if you don't want to use any custom rationale.
|
||||
token?.continuePermissionRequest()
|
||||
|
@ -221,18 +221,40 @@ class WeatherFragment : Fragment() {
|
||||
Manifest.permission.ACCESS_FINE_LOCATION,
|
||||
Manifest.permission.ACCESS_COARSE_LOCATION
|
||||
).withListener(object: MultiplePermissionsListener {
|
||||
private var shouldShowRationale = false
|
||||
override fun onPermissionsChecked(report: MultiplePermissionsReport?) {
|
||||
report?.let {
|
||||
if (report.grantedPermissionResponses.isNotEmpty()) {
|
||||
checkLocationPermission()
|
||||
WeatherHelper.updateWeather(requireContext())
|
||||
}
|
||||
else if (!shouldShowRationale && report.isAnyPermissionPermanentlyDenied) {
|
||||
MaterialBottomSheetDialog(
|
||||
requireContext(),
|
||||
getString(R.string.title_permission_location),
|
||||
getString(R.string.description_permission_location)
|
||||
).setNegativeButton(getString(R.string.action_ignore))
|
||||
.setPositiveButton(getString(R.string.action_grant_permission)) {
|
||||
startActivity(
|
||||
Intent(
|
||||
android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS
|
||||
).apply {
|
||||
data = android.net.Uri.fromParts(
|
||||
"package",
|
||||
requireContext().packageName,
|
||||
null
|
||||
)
|
||||
}
|
||||
)
|
||||
}.show()
|
||||
}
|
||||
}
|
||||
}
|
||||
override fun onPermissionRationaleShouldBeShown(
|
||||
permissions: MutableList<PermissionRequest>?,
|
||||
token: PermissionToken?
|
||||
) {
|
||||
shouldShowRationale = true
|
||||
// Remember to invoke this method when the custom rationale is closed
|
||||
// or just by default if you don't want to use any custom rationale.
|
||||
token?.continuePermissionRequest()
|
||||
@ -241,6 +263,12 @@ class WeatherFragment : Fragment() {
|
||||
.check()
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
checkWeatherProviderConfig()
|
||||
checkLocationPermission()
|
||||
}
|
||||
|
||||
private fun maintainScrollPosition(callback: () -> Unit) {
|
||||
binding.scrollView.isScrollable = false
|
||||
callback.invoke()
|
||||
|
@ -155,6 +155,7 @@ class MainViewModel(context: Application) : AndroidViewModel(context) {
|
||||
addSource(Preferences.asLiveData(Preferences::secondRowInformation)) { value = true }
|
||||
|
||||
addSource(Preferences.asLiveData(Preferences::showWeather)) { value = true }
|
||||
addSource(Preferences.asLiveData(Preferences::weatherProvider)) { value = true }
|
||||
addSource(Preferences.asLiveData(Preferences::weatherTempUnit)) { value = true }
|
||||
addSource(Preferences.asLiveData(Preferences::weatherIconPack)) { value = true }
|
||||
addSource(Preferences.asLiveData(Preferences::customLocationLat)) { value = true }
|
||||
|
@ -34,25 +34,26 @@ class MainWidget : AppWidgetProvider() {
|
||||
CalendarHelper.updateEventList(context)
|
||||
WeatherReceiver.setUpdates(context)
|
||||
MediaPlayerHelper.updatePlayingMediaInfo(context)
|
||||
|
||||
if (Preferences.showEvents) {
|
||||
CalendarHelper.setEventUpdatesAndroidN(context)
|
||||
} else {
|
||||
CalendarHelper.removeEventUpdatesAndroidN(context)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDisabled(context: Context) {
|
||||
if (getWidgetCount(context) == 0) {
|
||||
CalendarHelper.removeEventUpdatesAndroidN(context)
|
||||
UpdatesReceiver.removeUpdates(context)
|
||||
WeatherReceiver.removeUpdates(context)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val handler by lazy { android.os.Handler(android.os.Looper.getMainLooper()) }
|
||||
|
||||
fun updateWidget(context: Context) {
|
||||
context.sendBroadcast(IntentHelper.getWidgetUpdateIntent(context))
|
||||
handler.run {
|
||||
removeCallbacksAndMessages(null)
|
||||
postDelayed ({
|
||||
context.sendBroadcast(IntentHelper.getWidgetUpdateIntent(context))
|
||||
}, 100)
|
||||
}
|
||||
}
|
||||
|
||||
fun getWidgetCount(context: Context): Int {
|
||||
|
Loading…
x
Reference in New Issue
Block a user