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 {