From d8e204c5d9698c66792eedb525c1d6f940493857 Mon Sep 17 00:00:00 2001 From: azuo Date: Thu, 7 Oct 2021 16:03:07 +0800 Subject: [PATCH] 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 --- app/src/main/AndroidManifest.xml | 4 -- .../anotherwidget/helpers/CalendarHelper.kt | 5 +- .../anotherwidget/helpers/WeatherHelper.kt | 4 +- .../receivers/WeatherReceiver.kt | 4 +- .../services/BatteryListenerJob.kt | 61 ------------------- .../services/EventListenerJob.kt | 55 ----------------- .../services/UpdateCalendarWorker.kt | 38 ++++++++++-- .../anotherwidget/services/WeatherWorker.kt | 51 ++++++++-------- .../ui/activities/MainActivity.kt | 1 - .../ui/fragments/tabs/PreferencesFragment.kt | 24 +++++++- .../ui/fragments/tabs/WeatherFragment.kt | 28 +++++++++ .../ui/viewmodels/MainViewModel.kt | 1 + .../anotherwidget/ui/widgets/MainWidget.kt | 15 ++--- 13 files changed, 123 insertions(+), 168 deletions(-) delete mode 100644 app/src/main/java/com/tommasoberlose/anotherwidget/services/BatteryListenerJob.kt delete mode 100644 app/src/main/java/com/tommasoberlose/anotherwidget/services/EventListenerJob.kt diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 292c24b..b6284b3 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -108,10 +108,6 @@ - - - - diff --git a/app/src/main/java/com/tommasoberlose/anotherwidget/helpers/CalendarHelper.kt b/app/src/main/java/com/tommasoberlose/anotherwidget/helpers/CalendarHelper.kt index 01bf676..62a6c5f 100644 --- a/app/src/main/java/com/tommasoberlose/anotherwidget/helpers/CalendarHelper.kt +++ b/app/src/main/java/com/tommasoberlose/anotherwidget/helpers/CalendarHelper.kt @@ -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.applyFilters() : List { diff --git a/app/src/main/java/com/tommasoberlose/anotherwidget/helpers/WeatherHelper.kt b/app/src/main/java/com/tommasoberlose/anotherwidget/helpers/WeatherHelper.kt index cf2152e..d37172e 100644 --- a/app/src/main/java/com/tommasoberlose/anotherwidget/helpers/WeatherHelper.kt +++ b/app/src/main/java/com/tommasoberlose/anotherwidget/helpers/WeatherHelper.kt @@ -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" diff --git a/app/src/main/java/com/tommasoberlose/anotherwidget/receivers/WeatherReceiver.kt b/app/src/main/java/com/tommasoberlose/anotherwidget/receivers/WeatherReceiver.kt index 0092180..49c1c7a 100644 --- a/app/src/main/java/com/tommasoberlose/anotherwidget/receivers/WeatherReceiver.kt +++ b/app/src/main/java/com/tommasoberlose/anotherwidget/receivers/WeatherReceiver.kt @@ -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) } } } diff --git a/app/src/main/java/com/tommasoberlose/anotherwidget/services/BatteryListenerJob.kt b/app/src/main/java/com/tommasoberlose/anotherwidget/services/BatteryListenerJob.kt deleted file mode 100644 index 19e6e0e..0000000 --- a/app/src/main/java/com/tommasoberlose/anotherwidget/services/BatteryListenerJob.kt +++ /dev/null @@ -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) - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tommasoberlose/anotherwidget/services/EventListenerJob.kt b/app/src/main/java/com/tommasoberlose/anotherwidget/services/EventListenerJob.kt deleted file mode 100644 index a8a811b..0000000 --- a/app/src/main/java/com/tommasoberlose/anotherwidget/services/EventListenerJob.kt +++ /dev/null @@ -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) - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tommasoberlose/anotherwidget/services/UpdateCalendarWorker.kt b/app/src/main/java/com/tommasoberlose/anotherwidget/services/UpdateCalendarWorker.kt index 6f610a9..a8a3ac4 100644 --- a/app/src/main/java/com/tommasoberlose/anotherwidget/services/UpdateCalendarWorker.kt +++ b/app/src/main/java/com/tommasoberlose/anotherwidget/services/UpdateCalendarWorker.kt @@ -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().build() ) } + + fun enqueueTrigger(context: Context) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + WorkManager.getInstance(context).enqueueUniqueWork( + "updateEventListByTrigger", + ExistingWorkPolicy.KEEP, + OneTimeWorkRequestBuilder().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" + ) + } + } } } diff --git a/app/src/main/java/com/tommasoberlose/anotherwidget/services/WeatherWorker.kt b/app/src/main/java/com/tommasoberlose/anotherwidget/services/WeatherWorker.kt index edf3c43..3f3edbb 100644 --- a/app/src/main/java/com/tommasoberlose/anotherwidget/services/WeatherWorker.kt +++ b/app/src/main/java/com/tommasoberlose/anotherwidget/services/WeatherWorker.kt @@ -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().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(interval, unit).build() ) } - fun cancel(context: Context) { - WorkManager.getInstance(context.applicationContext).cancelUniqueWork( - "WeatherWorker" + fun cancelPeriodic(context: Context) { + WorkManager.getInstance(context).cancelUniqueWork( + "updateWeatherPeriodically" ) } } diff --git a/app/src/main/java/com/tommasoberlose/anotherwidget/ui/activities/MainActivity.kt b/app/src/main/java/com/tommasoberlose/anotherwidget/ui/activities/MainActivity.kt index 4e01a0d..5bb464a 100644 --- a/app/src/main/java/com/tommasoberlose/anotherwidget/ui/activities/MainActivity.kt +++ b/app/src/main/java/com/tommasoberlose/anotherwidget/ui/activities/MainActivity.kt @@ -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) } } diff --git a/app/src/main/java/com/tommasoberlose/anotherwidget/ui/fragments/tabs/PreferencesFragment.kt b/app/src/main/java/com/tommasoberlose/anotherwidget/ui/fragments/tabs/PreferencesFragment.kt index c531a40..6295a55 100644 --- a/app/src/main/java/com/tommasoberlose/anotherwidget/ui/fragments/tabs/PreferencesFragment.kt +++ b/app/src/main/java/com/tommasoberlose/anotherwidget/ui/fragments/tabs/PreferencesFragment.kt @@ -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?, 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() diff --git a/app/src/main/java/com/tommasoberlose/anotherwidget/ui/fragments/tabs/WeatherFragment.kt b/app/src/main/java/com/tommasoberlose/anotherwidget/ui/fragments/tabs/WeatherFragment.kt index e93e1c0..d80ad89 100644 --- a/app/src/main/java/com/tommasoberlose/anotherwidget/ui/fragments/tabs/WeatherFragment.kt +++ b/app/src/main/java/com/tommasoberlose/anotherwidget/ui/fragments/tabs/WeatherFragment.kt @@ -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?, 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() diff --git a/app/src/main/java/com/tommasoberlose/anotherwidget/ui/viewmodels/MainViewModel.kt b/app/src/main/java/com/tommasoberlose/anotherwidget/ui/viewmodels/MainViewModel.kt index 52be37d..872fa15 100644 --- a/app/src/main/java/com/tommasoberlose/anotherwidget/ui/viewmodels/MainViewModel.kt +++ b/app/src/main/java/com/tommasoberlose/anotherwidget/ui/viewmodels/MainViewModel.kt @@ -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 } diff --git a/app/src/main/java/com/tommasoberlose/anotherwidget/ui/widgets/MainWidget.kt b/app/src/main/java/com/tommasoberlose/anotherwidget/ui/widgets/MainWidget.kt index 484fd54..cd70e38 100644 --- a/app/src/main/java/com/tommasoberlose/anotherwidget/ui/widgets/MainWidget.kt +++ b/app/src/main/java/com/tommasoberlose/anotherwidget/ui/widgets/MainWidget.kt @@ -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 {