Compare commits

...

8 Commits

Author SHA1 Message Date
fb3f28d035 Make weather updates more reliable 2021-10-15 14:42:38 +08:00
d8e204c5d9 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
2021-10-07 16:03:07 +08:00
388653f62b Meet some mandatory requirements for migrating to Android 12
1. Replace foreground services with Workers
2. Support approximate location
3. Fallback to inexact alarms if the SCHEDULE_EXACT_ALARM permission is revoked
4. Specify the mutability of each PendingIntent
5. Explicitly declare the android:exported attribute for app components that use intent filters
2021-10-03 16:34:56 +08:00
5763a18421 Optimize notification handling, fix activity detection and greetings. 2021-10-01 17:05:43 +08:00
5dcf0afe02 Correct merge mistake: viewModel.showPreview.observe invoked twice 2021-09-30 22:06:38 +08:00
94b1eec757 Revert: Add ACCESS_BACKGROUND_LOCATION permission. 2021-09-30 20:06:14 +08:00
c5b41d0886 Merge branch 'develop' into patch-develop 2021-09-30 16:56:29 +08:00
4e5bf62e23 UI updates 2021-09-29 14:35:15 +02:00
82 changed files with 723 additions and 877 deletions

3
.idea/gradle.xml generated
View File

@ -4,7 +4,7 @@
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="testRunner" value="PLATFORM" />
<option name="testRunner" value="GRADLE" />
<option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="modules">
@ -14,7 +14,6 @@
</set>
</option>
<option name="resolveModulePerSourceSet" value="false" />
<option name="useQualifiedModuleNames" value="true" />
</GradleProjectSettings>
</option>
</component>

2
.idea/misc.xml generated
View File

@ -61,7 +61,7 @@
</profile-state>
</entry>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">

View File

@ -1,13 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RunConfigurationProducerService">
<option name="ignoredProducers">
<set>
<option value="com.android.tools.idea.compose.preview.runconfiguration.ComposePreviewRunConfigurationProducer" />
<option value="org.jetbrains.plugins.gradle.execution.test.runner.AllInPackageGradleConfigurationProducer" />
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestClassGradleConfigurationProducer" />
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestMethodGradleConfigurationProducer" />
</set>
</option>
</component>
</project>

View File

@ -14,7 +14,6 @@ apikeyProperties.load(new FileInputStream(apikeyPropertiesFile))
android {
compileSdkVersion 30
buildToolsVersion "29.0.3"
defaultConfig {
applicationId "com.tommasoberlose.anotherwidget"
@ -68,7 +67,7 @@ dependencies {
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
// UI
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'androidx.appcompat:appcompat:1.3.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation 'com.google.android.material:material:1.3.0'
implementation 'androidx.browser:browser:1.3.0'
@ -117,10 +116,10 @@ dependencies {
implementation 'com.android.billingclient:billing-ktx:3.0.3'
// KTX
implementation "androidx.core:core-ktx:1.3.2"
implementation "androidx.core:core-ktx:1.5.0"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.1"
implementation "androidx.palette:palette-ktx:1.0.0"
implementation 'androidx.core:core-ktx:1.3.2'
implementation 'androidx.core:core-ktx:1.5.0'
//Retrofit
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
@ -133,7 +132,7 @@ dependencies {
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.1'
// Add the Firebase SDK for Crashlytics.
implementation 'com.google.firebase:firebase-crashlytics:17.4.1'
implementation 'com.google.firebase:firebase-crashlytics:18.0.0'
// Preferences
implementation 'com.chibatching.kotpref:kotpref:2.13.1'

View File

@ -6,15 +6,15 @@
<uses-permission android:name="android.permission.READ_CALENDAR" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="com.android.vending.BILLING" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.ACTIVITY_RECOGNITION" />
<uses-permission android:name="android.gms.permission.ACTIVITY_RECOGNITION"/>
<uses-permission android:name="com.google.android.gms.permission.ACTIVITY_RECOGNITION" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="com.android.alarm.permission.SET_ALARM" />
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
<application
android:allowBackup="true"
@ -26,13 +26,13 @@
android:usesCleartextTraffic="true"
android:theme="@style/AppTheme"
tools:ignore="LockedOrientationActivity">
<activity android:name=".ui.activities.MainActivity" android:theme="@style/AppTheme.Main" android:screenOrientation="portrait">
<activity android:name=".ui.activities.SplashActivity" android:exported="true" android:theme="@style/AppTheme.Main" android:screenOrientation="portrait">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".ui.activities.MainActivity" android:theme="@style/AppTheme" android:screenOrientation="portrait" />
<activity android:name=".ui.activities.tabs.ChooseApplicationActivity" android:screenOrientation="portrait" />
<activity android:name=".ui.activities.tabs.CustomFontActivity" android:screenOrientation="portrait" />
<activity android:name=".ui.activities.tabs.CustomLocationActivity" android:screenOrientation="portrait" />
@ -45,11 +45,10 @@
<activity android:name=".ui.activities.tabs.MediaInfoFormatActivity" android:screenOrientation="portrait" />
<activity android:name=".ui.activities.tabs.TimeZoneSelectorActivity" android:screenOrientation="portrait" />
<receiver android:name=".ui.widgets.MainWidget">
<receiver android:name=".ui.widgets.MainWidget" android:exported="true">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/the_widget_info" />
@ -75,8 +74,8 @@
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
<action android:name="com.tommasoberlose.anotherwidget.action.ACTION_CALENDAR_UPDATE" />
<action android:name="com.tommasoberlose.anotherwidget.action.ACTION_TIME_UPDATE" />
<action android:name="com.tommasoberlose.anotherwidget.action.CALENDAR_UPDATE" />
<action android:name="com.tommasoberlose.anotherwidget.action.TIME_UPDATE" />
<action android:name="com.sec.android.widgetapp.APPWIDGET_RESIZE" />
<action android:name="android.intent.action.MY_PACKAGE_REPLACED" />
<action android:name="android.app.action.NEXT_ALARM_CLOCK_CHANGED" />
@ -91,8 +90,7 @@
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="com.tommasoberlose.anotherwidget.action.ACTION_WEATHER_UPDATE" />
<category android:name="android.intent.category.DEFAULT" />
<action android:name="com.tommasoberlose.anotherwidget.action.WEATHER_UPDATE" />
<action android:name="android.intent.action.MY_PACKAGE_REPLACED" />
<action android:name="android.intent.action.BOOT_COMPLETED" />
<action android:name="android.intent.action.TIME_SET" />
@ -104,26 +102,13 @@
<receiver
android:name=".receivers.WidgetClickListenerReceiver"
android:enabled="true"
android:exported="false">
android:exported="true">
<intent-filter>
<action android:name="com.tommasoberlose.anotherwidget.action.ACTION_OPEN_WEATHER_INTENT" />
<action android:name="com.tommasoberlose.anotherwidget.action.OPEN_WEATHER_INTENT" />
</intent-filter>
</receiver>
<receiver
android:name=".receivers.CrashlyticsReceiver"
android:enabled="true"
android:exported="false">
<intent-filter>
<action android:name="com.tommasoberlose.anotherwidget.action.ACTION_REPORT_CRASH" />
</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"
<service android:name=".receivers.NotificationListener" android:exported="true"
android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
<intent-filter>
<action android:name="android.service.notification.NotificationListenerService" />
@ -143,7 +128,7 @@
</receiver>
<receiver android:name=".receivers.ActivityDetectionReceiver"
android:exported="false"
android:exported="true"
android:permission="com.google.android.gms.permission.ACTIVITY_RECOGNITION">
<intent-filter>
<action android:name="com.mypackage.ACTION_PROCESS_ACTIVITY_TRANSITIONS" />
@ -151,17 +136,6 @@
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
<service
android:name=".services.UpdateCalendarService"
android:enabled="true"
android:exported="false"
android:foregroundServiceType="dataSync" />
<service
android:name=".services.LocationService"
android:enabled="true"
android:exported="false"
android:foregroundServiceType="location" />
</application>
<queries>

View File

@ -177,6 +177,8 @@ class GlanceSettingsDialog(val context: Activity, val provider: Constants.Glance
Constants.GlanceProviderId.NEXT_CLOCK_ALARM -> {
Preferences.showNextAlarm = isChecked
checkNextAlarm()
if (!isChecked)
AlarmHelper.clearTimeout(context)
}
Constants.GlanceProviderId.BATTERY_LEVEL_LOW -> {
Preferences.showBatteryCharging = isChecked
@ -184,6 +186,8 @@ class GlanceSettingsDialog(val context: Activity, val provider: Constants.Glance
Constants.GlanceProviderId.NOTIFICATIONS -> {
Preferences.showNotifications = isChecked
checkLastNotificationsPermission()
if (!isChecked)
ActiveNotificationsHelper.clearLastNotification(context)
}
Constants.GlanceProviderId.GREETINGS -> {
Preferences.showGreetings = isChecked
@ -238,8 +242,6 @@ class GlanceSettingsDialog(val context: Activity, val provider: Constants.Glance
}
private fun checkNextAlarm() {
if (!Preferences.showNextAlarm)
AlarmHelper.clearTimeout(context)
with(context.getSystemService(Context.ALARM_SERVICE) as AlarmManager) {
val alarm = nextAlarmClock
if (alarm != null && alarm.showIntent != null) {

View File

@ -85,7 +85,7 @@ object Preferences : KotprefModel() {
var weatherIconPack by intPref(default = Constants.WeatherIconPack.DEFAULT.rawValue)
// UI
var widgetMargin by floatPref(default = Constants.Dimension.SMALL.rawValue)
var widgetMargin by floatPref(default = Constants.Dimension.NONE.rawValue)
var widgetPadding by floatPref(default = Constants.Dimension.SMALL.rawValue)
// Clock

View File

@ -25,6 +25,7 @@ object ActiveNotificationsHelper {
remove(Preferences::lastNotificationIcon)
}
MainWidget.updateWidget(context)
NotificationListener.clearTimeout(context)
}
fun checkNotificationAccess(context: Context): Boolean {

View File

@ -9,6 +9,7 @@ import android.util.Log
import com.tommasoberlose.anotherwidget.global.Actions
import com.tommasoberlose.anotherwidget.receivers.ActivityDetectionReceiver
import com.tommasoberlose.anotherwidget.receivers.UpdatesReceiver
import com.tommasoberlose.anotherwidget.utils.setExactIfCanSchedule
import java.text.SimpleDateFormat
import java.util.*
@ -44,14 +45,14 @@ object AlarmHelper {
val intent = Intent(context, UpdatesReceiver::class.java).apply {
action = Actions.ACTION_ALARM_UPDATE
}
setExact(
setExactIfCanSchedule(
AlarmManager.RTC,
trigger,
PendingIntent.getBroadcast(
context,
ALARM_UPDATE_ID,
intent,
0
PendingIntent.FLAG_IMMUTABLE
)
)
}
@ -62,7 +63,7 @@ object AlarmHelper {
val intent = Intent(context, UpdatesReceiver::class.java).apply {
action = Actions.ACTION_ALARM_UPDATE
}
cancel(PendingIntent.getBroadcast(context, ALARM_UPDATE_ID, intent, 0))
cancel(PendingIntent.getBroadcast(context, ALARM_UPDATE_ID, intent, PendingIntent.FLAG_IMMUTABLE))
}
}

View File

@ -4,10 +4,9 @@ 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.UpdateCalendarService
import com.tommasoberlose.anotherwidget.services.UpdateCalendarWorker
import com.tommasoberlose.anotherwidget.utils.checkGrantedPermission
import me.everything.providers.android.calendar.CalendarProvider
import java.util.*
@ -19,7 +18,7 @@ import kotlin.collections.ArrayList
object CalendarHelper {
fun updateEventList(context: Context) {
UpdateCalendarService.enqueueWork(context)
UpdateCalendarWorker.enqueue(context)
}
fun getCalendarList(context: Context): List<me.everything.providers.android.calendar.Calendar> {
@ -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> {

View File

@ -37,7 +37,7 @@ object GreetingsHelper {
Intent(context, UpdatesReceiver::class.java).apply {
action = Actions.ACTION_UPDATE_GREETINGS
},
0)
PendingIntent.FLAG_IMMUTABLE)
)
setRepeating(
@ -51,7 +51,7 @@ object GreetingsHelper {
Intent(context, UpdatesReceiver::class.java).apply {
action = Actions.ACTION_UPDATE_GREETINGS
},
0)
PendingIntent.FLAG_IMMUTABLE)
)
setRepeating(
@ -65,7 +65,7 @@ object GreetingsHelper {
Intent(context, UpdatesReceiver::class.java).apply {
action = Actions.ACTION_UPDATE_GREETINGS
},
0)
PendingIntent.FLAG_IMMUTABLE)
)
setRepeating(
@ -79,14 +79,14 @@ object GreetingsHelper {
Intent(context, UpdatesReceiver::class.java).apply {
action = Actions.ACTION_UPDATE_GREETINGS
},
0)
PendingIntent.FLAG_IMMUTABLE)
)
} else {
listOf(MORNING_TIME, MORNING_TIME_END, EVENING_TIME, NIGHT_TIME).forEach {
cancel(PendingIntent.getBroadcast(context, it, Intent(context,
UpdatesReceiver::class.java).apply {
action = Actions.ACTION_UPDATE_GREETINGS
}, 0))
}, PendingIntent.FLAG_IMMUTABLE))
}
}
}
@ -102,7 +102,7 @@ object GreetingsHelper {
val array = when {
hour in 5..8 -> context.resources.getStringArray(R.array.morning_greetings)
hour in 19..21 -> context.resources.getStringArray(R.array.evening_greetings)
hour >= 22 && hour < 5 -> context.resources.getStringArray(R.array.night_greetings)
hour >= 22 || hour < 5 -> context.resources.getStringArray(R.array.night_greetings)
else -> emptyArray()
}
return if (array.isNotEmpty()) array[Random().nextInt(array.size)] else ""

View File

@ -32,7 +32,7 @@ object IntentHelper {
return if (intent.flags and Intent.FLAG_ACTIVITY_NEW_TASK == Intent.FLAG_ACTIVITY_NEW_TASK)
PendingIntent.getActivity(context, requestCode, intent, flags)
else
PendingIntent.getBroadcast(context, requestCode, intent, 0)
PendingIntent.getBroadcast(context, requestCode, intent, flags)
}
fun getWidgetUpdateIntent(context: Context): Intent {

View File

@ -7,7 +7,7 @@ import com.tommasoberlose.anotherwidget.R
import com.tommasoberlose.anotherwidget.global.Constants
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.network.WeatherNetworkApi
import com.tommasoberlose.anotherwidget.services.LocationService
import com.tommasoberlose.anotherwidget.services.WeatherWorker
import com.tommasoberlose.anotherwidget.ui.widgets.MainWidget
import com.tommasoberlose.anotherwidget.utils.checkGrantedPermission
import com.tommasoberlose.anotherwidget.utils.isDarkTheme
@ -19,18 +19,10 @@ import com.tommasoberlose.anotherwidget.utils.isDarkTheme
object WeatherHelper {
suspend fun updateWeather(context: Context) {
Kotpref.init(context)
if (Preferences.customLocationAdd != "") {
WeatherNetworkApi(context).updateWeather()
} else if (context.checkGrantedPermission(Manifest.permission.ACCESS_FINE_LOCATION) &&
(android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.R ||
context.checkGrantedPermission(Manifest.permission.ACCESS_BACKGROUND_LOCATION))
) {
LocationService.requestNewLocation(context)
} else {
Preferences.weatherProviderLocationError = context.getString(R.string.weather_provider_error_missing_location)
Preferences.weatherProviderError = ""
fun updateWeather(context: Context, force: Boolean = false) {
if (Preferences.showWeather || force)
WeatherWorker.enqueue(context, replace = force)
else {
removeWeather(context)
org.greenrobot.eventbus.EventBus.getDefault().post(
com.tommasoberlose.anotherwidget.ui.fragments.MainFragment.UpdateUiMessageEvent()
@ -339,7 +331,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"

View File

@ -17,13 +17,11 @@ import com.tommasoberlose.anotherwidget.helpers.WeatherHelper
import com.tommasoberlose.anotherwidget.network.repository.*
import com.tommasoberlose.anotherwidget.ui.fragments.MainFragment
import com.tommasoberlose.anotherwidget.ui.widgets.MainWidget
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.greenrobot.eventbus.EventBus
import java.lang.Exception
import java.text.SimpleDateFormat
import java.util.*
import kotlin.coroutines.resume
import kotlinx.coroutines.suspendCancellableCoroutine
import org.greenrobot.eventbus.EventBus
class WeatherNetworkApi(val context: Context) {
suspend fun updateWeather() {
@ -31,7 +29,7 @@ class WeatherNetworkApi(val context: Context) {
Preferences.weatherProviderError = "-"
Preferences.weatherProviderLocationError = ""
if (Preferences.showWeather && Preferences.customLocationLat != "" && Preferences.customLocationLon != "") {
if (Preferences.customLocationLat != "" && Preferences.customLocationLon != "") {
when (Constants.WeatherProvider.fromInt(Preferences.weatherProvider)) {
Constants.WeatherProvider.OPEN_WEATHER -> useOpenWeatherMap(context)
Constants.WeatherProvider.WEATHER_GOV -> useWeatherGov(context)
@ -42,46 +40,67 @@ class WeatherNetworkApi(val context: Context) {
Constants.WeatherProvider.YR -> useYrProvider(context)
}
} else {
if (!Preferences.showWeather)
Preferences.weatherProviderError = context.getString(R.string.show_weather_not_visible)
else {
Preferences.weatherProviderLocationError = context.getString(R.string.weather_provider_error_missing_location)
Preferences.weatherProviderError = ""
}
WeatherHelper.removeWeather(
context
)
EventBus.getDefault().post(MainFragment.UpdateUiMessageEvent())
}
}
private fun useOpenWeatherMap(context: Context) {
private suspend fun useOpenWeatherMap(context: Context) {
if (Preferences.weatherProviderApiOpen != "") {
val helper = OpenWeatherMapHelper(Preferences.weatherProviderApiOpen)
helper.setUnits(if (Preferences.weatherTempUnit == "F") Units.IMPERIAL else Units.METRIC)
when (val response = suspendCancellableCoroutine<Any?> { continuation ->
helper.getCurrentWeatherByGeoCoordinates(Preferences.customLocationLat.toDouble(), Preferences.customLocationLon.toDouble(), object :
CurrentWeatherCallback {
override fun onSuccess(currentWeather: CurrentWeather?) {
currentWeather?.let {
Preferences.weatherTemp = currentWeather.main.temp.toFloat()
Preferences.weatherIcon = currentWeather.weather[0].icon
Preferences.weatherRealTempUnit = Preferences.weatherTempUnit
MainWidget.updateWidget(context)
}
Preferences.weatherProviderError = ""
Preferences.weatherProviderLocationError = ""
EventBus.getDefault().post(MainFragment.UpdateUiMessageEvent())
continuation.resume(currentWeather)
}
override fun onFailure(throwable: Throwable?) {
Preferences.weatherProviderError = context.getString(R.string.weather_provider_error_generic)
Preferences.weatherProviderLocationError = ""
EventBus.getDefault().post(MainFragment.UpdateUiMessageEvent())
continuation.resume(throwable)
}
})
}) {
is CurrentWeather -> {
Preferences.weatherTemp = response.main.temp.toFloat()
Preferences.weatherIcon = response.weather[0].icon
Preferences.weatherRealTempUnit = Preferences.weatherTempUnit
MainWidget.updateWidget(context)
Preferences.weatherProviderError = ""
Preferences.weatherProviderLocationError = ""
}
is Throwable -> {
if (response.javaClass == Throwable::class.java) {
// server error, see [OpenWeatherMapHelper.handleCurrentWeatherResponse]
if (response.message?.startsWith("UnAuthorized") == true) {
Preferences.weatherProviderError = context.getString(R.string.weather_provider_error_invalid_key)
Preferences.weatherProviderLocationError = ""
}
else {
Preferences.weatherProviderError = context.getString(R.string.weather_provider_error_generic)
Preferences.weatherProviderLocationError = ""
}
WeatherHelper.removeWeather(
context
)
}
else {
Preferences.weatherProviderError = context.getString(R.string.weather_provider_error_connection)
Preferences.weatherProviderLocationError = ""
}
}
else -> {
Preferences.weatherProviderError = context.getString(R.string.weather_provider_error_generic)
Preferences.weatherProviderLocationError = ""
}
}
EventBus.getDefault().post(MainFragment.UpdateUiMessageEvent())
} else {
Preferences.weatherProviderError = context.getString(R.string.weather_provider_error_missing_key)
Preferences.weatherProviderLocationError = ""

View File

@ -21,6 +21,7 @@ import com.google.android.gms.location.*
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.ui.widgets.MainWidget
import com.tommasoberlose.anotherwidget.utils.checkGrantedPermission
import com.tommasoberlose.anotherwidget.utils.setExactIfCanSchedule
import java.util.*
import java.util.concurrent.TimeUnit
@ -32,11 +33,11 @@ class ActivityDetectionReceiver : BroadcastReceiver() {
val result = ActivityTransitionResult.extractResult(intent)!!
val lastEvent = result.transitionEvents.last()
if (lastEvent.activityType == DetectedActivity.WALKING || lastEvent.activityType == DetectedActivity.RUNNING && lastEvent.transitionType == ActivityTransition.ACTIVITY_TRANSITION_EXIT) {
if ((lastEvent.activityType == DetectedActivity.WALKING || lastEvent.activityType == DetectedActivity.RUNNING) && lastEvent.transitionType == ActivityTransition.ACTIVITY_TRANSITION_EXIT) {
requestDailySteps(context)
}
} else {
if (intent.action == Intent.ACTION_BOOT_COMPLETED || intent.action == Intent.ACTION_MY_PACKAGE_REPLACED && Preferences.showDailySteps && Build.VERSION.SDK_INT < Build.VERSION_CODES.Q || context.checkGrantedPermission(Manifest.permission.ACTIVITY_RECOGNITION)) {
if ((intent.action == Intent.ACTION_BOOT_COMPLETED || intent.action == Intent.ACTION_MY_PACKAGE_REPLACED) && Preferences.showDailySteps && (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q || context.checkGrantedPermission(Manifest.permission.ACTIVITY_RECOGNITION))) {
resetDailySteps(context)
registerFence(context)
} else {
@ -45,13 +46,6 @@ class ActivityDetectionReceiver : BroadcastReceiver() {
}
}
private fun resetDailySteps(context: Context) {
Kotpref.init(context)
Preferences.blockingBulk {
remove(Preferences::googleFitSteps)
}
}
companion object {
val FITNESS_OPTIONS: FitnessOptions = FitnessOptions.builder()
.addDataType(DataType.TYPE_STEP_COUNT_DELTA, FitnessOptions.ACCESS_READ)
@ -86,7 +80,7 @@ class ActivityDetectionReceiver : BroadcastReceiver() {
context,
2,
Intent(context, ActivityDetectionReceiver::class.java),
0
PendingIntent.FLAG_IMMUTABLE
)
)
@ -105,7 +99,7 @@ class ActivityDetectionReceiver : BroadcastReceiver() {
context,
2,
Intent(context, ActivityDetectionReceiver::class.java),
0
PendingIntent.FLAG_IMMUTABLE
)
)
@ -115,10 +109,13 @@ class ActivityDetectionReceiver : BroadcastReceiver() {
context,
2,
Intent(context, ActivityDetectionReceiver::class.java),
0
PendingIntent.FLAG_IMMUTABLE
).cancel()
}
}
resetDailySteps(context)
clearTimeout(context)
}
fun requestDailySteps(context: Context) {
@ -162,17 +159,36 @@ class ActivityDetectionReceiver : BroadcastReceiver() {
}
}
private fun resetDailySteps(context: Context) {
Kotpref.init(context)
Preferences.blockingBulk {
remove(Preferences::googleFitSteps)
}
}
private fun setTimeout(context: Context) {
with(context.getSystemService(Context.ALARM_SERVICE) as AlarmManager) {
cancel(PendingIntent.getBroadcast(context, 5, Intent(context, ActivityDetectionReceiver::class.java), 0))
setExact(
setExactIfCanSchedule(
AlarmManager.RTC,
Calendar.getInstance().timeInMillis + 5 * 60 * 1000,
PendingIntent.getBroadcast(
context,
5,
Intent(context, ActivityDetectionReceiver::class.java),
0
PendingIntent.FLAG_IMMUTABLE
)
)
}
}
private fun clearTimeout(context: Context) {
with(context.getSystemService(Context.ALARM_SERVICE) as AlarmManager) {
cancel(
PendingIntent.getBroadcast(
context,
5,
Intent(context, ActivityDetectionReceiver::class.java),
PendingIntent.FLAG_IMMUTABLE
)
)
}

View File

@ -1,32 +0,0 @@
package com.tommasoberlose.anotherwidget.receivers
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.util.Log
import com.google.firebase.crashlytics.FirebaseCrashlytics
import com.tommasoberlose.anotherwidget.global.Actions
import java.lang.Exception
class CrashlyticsReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
if (intent.action == Actions.ACTION_REPORT_CRASH) {
val exception: Exception = intent.getSerializableExtra(EXCEPTION) as Exception
FirebaseCrashlytics.getInstance().recordException(exception)
FirebaseCrashlytics.getInstance().sendUnsentReports()
}
}
companion object {
private const val EXCEPTION = "EXCEPTION"
fun sendCrash(context: Context, exception: Exception) {
context.sendBroadcast(Intent(context, CrashlyticsReceiver::class.java).apply {
action = Actions.ACTION_REPORT_CRASH
putExtra(EXCEPTION, exception)
})
}
}
}

View File

@ -16,6 +16,7 @@ import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.helpers.ActiveNotificationsHelper
import com.tommasoberlose.anotherwidget.helpers.MediaPlayerHelper
import com.tommasoberlose.anotherwidget.ui.widgets.MainWidget
import com.tommasoberlose.anotherwidget.utils.setExactIfCanSchedule
import java.lang.Exception
import java.util.*
@ -23,19 +24,26 @@ import java.util.*
class NotificationListener : NotificationListenerService() {
override fun onListenerConnected() {
MediaPlayerHelper.updatePlayingMediaInfo(this)
MainWidget.updateWidget(this)
ActiveNotificationsHelper.clearLastNotification(this)
super.onListenerConnected()
}
override fun onListenerDisconnected() {
MediaPlayerHelper.updatePlayingMediaInfo(this)
ActiveNotificationsHelper.clearLastNotification(this)
super.onListenerDisconnected()
}
override fun onNotificationPosted(sbn: StatusBarNotification?) {
sbn?.notification?.extras?.let { bundle ->
bundle.getParcelable<MediaSession.Token>(Notification.EXTRA_MEDIA_SESSION)?.let {
if (Preferences.showMusic)
MediaPlayerHelper.updatePlayingMediaInfo(this)
} ?: run {
val isGroupHeader = sbn.notification.flags and Notification.FLAG_GROUP_SUMMARY != 0
val isOngoing = sbn.notification.flags and Notification.FLAG_ONGOING_EVENT != 0
if (bundle.containsKey(Notification.EXTRA_TITLE) && !isGroupHeader && !isOngoing && ActiveNotificationsHelper.isAppAccepted(sbn.packageName) && !sbn.packageName.contains("com.android.systemui")) {
if (Preferences.showNotifications && bundle.containsKey(Notification.EXTRA_TITLE) && !isGroupHeader && !isOngoing && ActiveNotificationsHelper.isAppAccepted(sbn.packageName) && !sbn.packageName.contains("com.android.systemui")) {
Preferences.lastNotificationId = sbn.id
Preferences.lastNotificationTitle = bundle.getString(Notification.EXTRA_TITLE) ?: ""
try {
@ -59,15 +67,13 @@ class NotificationListener : NotificationListenerService() {
}
override fun onNotificationRemoved(sbn: StatusBarNotification?) {
if (Preferences.showMusic)
MediaPlayerHelper.updatePlayingMediaInfo(this)
sbn?.let {
if (sbn.id == Preferences.lastNotificationId && sbn.packageName == Preferences.lastNotificationPackage) {
if (Preferences.showNotifications && sbn.id == Preferences.lastNotificationId && sbn.packageName == Preferences.lastNotificationPackage) {
ActiveNotificationsHelper.clearLastNotification(this)
}
}
MainWidget.updateWidget(this)
super.onNotificationRemoved(sbn)
}
@ -76,10 +82,9 @@ class NotificationListener : NotificationListenerService() {
val intent = Intent(context, UpdatesReceiver::class.java).apply {
action = Actions.ACTION_CLEAR_NOTIFICATION
}
cancel(PendingIntent.getBroadcast(context, 28943, intent, 0))
val timeoutPref = Constants.GlanceNotificationTimer.fromInt(Preferences.hideNotificationAfter)
if (timeoutPref != Constants.GlanceNotificationTimer.WHEN_DISMISSED) {
setExact(
setExactIfCanSchedule(
AlarmManager.RTC,
Calendar.getInstance().timeInMillis + when (timeoutPref) {
Constants.GlanceNotificationTimer.HALF_MINUTE -> 30 * 1000
@ -87,16 +92,27 @@ class NotificationListener : NotificationListenerService() {
Constants.GlanceNotificationTimer.FIVE_MINUTES -> 5 * 60 * 1000
Constants.GlanceNotificationTimer.TEN_MINUTES -> 10 * 60 * 1000
Constants.GlanceNotificationTimer.FIFTEEN_MINUTES -> 15 * 60 * 1000
else -> 0
else -> 60 * 1000
},
PendingIntent.getBroadcast(
context,
5,
intent,
0
PendingIntent.FLAG_IMMUTABLE
)
)
}
}
}
companion object {
fun clearTimeout(context: Context) {
with(context.getSystemService(Context.ALARM_SERVICE) as AlarmManager) {
val intent = Intent(context, UpdatesReceiver::class.java).apply {
action = Actions.ACTION_CLEAR_NOTIFICATION
}
cancel(PendingIntent.getBroadcast(context, 5, intent, PendingIntent.FLAG_IMMUTABLE))
}
}
}
}

View File

@ -12,11 +12,9 @@ import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.helpers.*
import com.tommasoberlose.anotherwidget.models.Event
import com.tommasoberlose.anotherwidget.ui.widgets.MainWidget
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import org.joda.time.Period
import com.tommasoberlose.anotherwidget.utils.setExactIfCanSchedule
import java.util.*
import org.joda.time.Period
class UpdatesReceiver : BroadcastReceiver() {
@ -27,18 +25,18 @@ class UpdatesReceiver : BroadcastReceiver() {
Intent.ACTION_MY_PACKAGE_REPLACED,
Intent.ACTION_TIME_CHANGED,
Intent.ACTION_TIMEZONE_CHANGED,
Intent.ACTION_LOCALE_CHANGED,
Intent.ACTION_LOCALE_CHANGED -> {
CalendarHelper.updateEventList(context)
MediaPlayerHelper.updatePlayingMediaInfo(context)
ActiveNotificationsHelper.clearLastNotification(context)
GreetingsHelper.toggleGreetings(context)
}
Intent.ACTION_DATE_CHANGED,
Actions.ACTION_CALENDAR_UPDATE -> {
ActiveNotificationsHelper.clearLastNotification(context)
MediaPlayerHelper.updatePlayingMediaInfo(context)
CalendarHelper.updateEventList(context)
}
"com.sec.android.widgetapp.APPWIDGET_RESIZE",
AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED,
Actions.ACTION_ALARM_UPDATE,
Actions.ACTION_UPDATE_GREETINGS,
Actions.ACTION_TIME_UPDATE -> {
MainWidget.updateWidget(context)
if (intent.hasExtra(EVENT_ID)) {
@ -46,20 +44,24 @@ class UpdatesReceiver : BroadcastReceiver() {
}
}
Actions.ACTION_CLEAR_NOTIFICATION -> {
ActiveNotificationsHelper.clearLastNotification(context)
"com.sec.android.widgetapp.APPWIDGET_RESIZE",
AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED,
Actions.ACTION_ALARM_UPDATE,
Actions.ACTION_UPDATE_GREETINGS -> {
MainWidget.updateWidget(context)
}
Actions.ACTION_CLEAR_NOTIFICATION -> {
ActiveNotificationsHelper.clearLastNotification(context)
}
Actions.ACTION_REFRESH -> {
GlobalScope.launch(Dispatchers.IO) {
CalendarHelper.updateEventList(context)
MediaPlayerHelper.updatePlayingMediaInfo(context)
WeatherHelper.updateWeather(context)
}
}
}
}
companion object {
const val EVENT_ID = "EVENT_ID"
@ -71,7 +73,7 @@ class UpdatesReceiver : BroadcastReceiver() {
if (eventId == null) {
// schedule ACTION_CALENDAR_UPDATE at midnight (ACTION_DATE_CHANGED no longer works)
with(context.getSystemService(Context.ALARM_SERVICE) as AlarmManager) {
setExact(
setExactIfCanSchedule(
AlarmManager.RTC,
Calendar.getInstance().apply {
set(Calendar.MILLISECOND, 0)
@ -86,7 +88,7 @@ class UpdatesReceiver : BroadcastReceiver() {
Intent(context, UpdatesReceiver::class.java).apply {
action = Actions.ACTION_CALENDAR_UPDATE
},
0
PendingIntent.FLAG_IMMUTABLE
)
)
}
@ -161,7 +163,7 @@ class UpdatesReceiver : BroadcastReceiver() {
add(Calendar.DATE, 1)
}.timeInMillis <= fireTime) return
with(context.getSystemService(Context.ALARM_SERVICE) as AlarmManager) {
setExact(
setExactIfCanSchedule(
AlarmManager.RTC,
fireTime.coerceAtLeast(now.timeInMillis + 1000 * 60),
PendingIntent.getBroadcast(
@ -172,7 +174,7 @@ class UpdatesReceiver : BroadcastReceiver() {
if (event.startDate > now.timeInMillis)
putExtra(EVENT_ID, event.id)
},
PendingIntent.FLAG_UPDATE_CURRENT
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
)
}
@ -182,12 +184,12 @@ class UpdatesReceiver : BroadcastReceiver() {
with(context.getSystemService(Context.ALARM_SERVICE) as AlarmManager) {
cancel(PendingIntent.getBroadcast(context, 0, Intent(context, UpdatesReceiver::class.java).apply {
action = Actions.ACTION_CALENDAR_UPDATE
}, 0))
}, PendingIntent.FLAG_IMMUTABLE))
val eventRepository = EventRepository(context)
eventRepository.getFutureEvents().forEach {
cancel(PendingIntent.getBroadcast(context, it.id.toInt(), Intent(context, UpdatesReceiver::class.java).apply {
action = Actions.ACTION_TIME_UPDATE
}, 0))
}, PendingIntent.FLAG_IMMUTABLE))
}
eventRepository.close()
}

View File

@ -1,18 +1,12 @@
package com.tommasoberlose.anotherwidget.receivers
import android.app.AlarmManager
import android.app.PendingIntent
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import com.tommasoberlose.anotherwidget.global.Actions
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.helpers.WeatherHelper
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import java.util.*
import com.tommasoberlose.anotherwidget.services.WeatherWorker
class WeatherReceiver : BroadcastReceiver() {
@ -23,57 +17,21 @@ class WeatherReceiver : BroadcastReceiver() {
Intent.ACTION_TIMEZONE_CHANGED,
Intent.ACTION_LOCALE_CHANGED,
Intent.ACTION_TIME_CHANGED,
Actions.ACTION_WEATHER_UPDATE -> setUpdates(context)
}
}
companion object {
private const val MINUTE = 60 * 1000L
fun setUpdates(context: Context) {
if (Preferences.showWeather) {
val interval = MINUTE * when (Preferences.weatherRefreshPeriod) {
0 -> 30
1 -> 60
2 -> 60L * 3
3 -> 60L * 6
4 -> 60L * 12
5 -> 60L * 24
else -> 60
}
with(context.getSystemService(Context.ALARM_SERVICE) as AlarmManager) {
setExact(
AlarmManager.RTC,
System.currentTimeMillis() + interval,
PendingIntent.getBroadcast(context, 0, Intent(context, WeatherReceiver::class.java).apply { action = Actions.ACTION_WEATHER_UPDATE }, 0)
)
}
GlobalScope.launch(Dispatchers.IO) {
Actions.ACTION_WEATHER_UPDATE -> {
WeatherHelper.updateWeather(context)
}
}
}
fun setOneTimeUpdate(context: Context) {
companion object {
fun setUpdates(context: Context) {
if (Preferences.showWeather) {
listOf(10, 20, 30).forEach {
with(context.getSystemService(Context.ALARM_SERVICE) as AlarmManager) {
setExactAndAllowWhileIdle(
AlarmManager.RTC,
it * MINUTE,
PendingIntent.getBroadcast(context, it, Intent(context, WeatherReceiver::class.java).apply { action = Actions.ACTION_WEATHER_UPDATE }, 0)
)
}
}
WeatherWorker.enqueueTrigger(context)
}
}
fun removeUpdates(context: Context) {
with(context.getSystemService(Context.ALARM_SERVICE) as AlarmManager) {
cancel(PendingIntent.getBroadcast(context, 0, Intent(context, WeatherReceiver::class.java).apply { action = Actions.ACTION_WEATHER_UPDATE }, 0))
listOf(10, 20, 30).forEach {
cancel(PendingIntent.getBroadcast(context, it, Intent(context, WeatherReceiver::class.java).apply { action = Actions.ACTION_WEATHER_UPDATE }, 0))
}
}
WeatherWorker.cancelTrigger(context)
}
}
}

View File

@ -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)
}
}
}

View File

@ -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)
}
}
}

View File

@ -1,144 +0,0 @@
package com.tommasoberlose.anotherwidget.services
import android.Manifest
import android.app.*
import android.app.job.JobScheduler
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.location.Address
import android.location.Geocoder
import android.os.IBinder
import android.util.Log
import androidx.core.app.*
import androidx.core.content.ContextCompat
import com.google.android.gms.location.LocationServices
import com.tommasoberlose.anotherwidget.R
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.network.WeatherNetworkApi
import com.tommasoberlose.anotherwidget.ui.activities.MainActivity
import com.tommasoberlose.anotherwidget.ui.fragments.MainFragment
import com.tommasoberlose.anotherwidget.utils.checkGrantedPermission
import kotlinx.coroutines.*
import org.greenrobot.eventbus.EventBus
import java.lang.Exception
import java.util.*
import kotlin.collections.ArrayList
class LocationService : Service() {
private var job: Job? = null
override fun onCreate() {
super.onCreate()
startForeground(LOCATION_ACCESS_NOTIFICATION_ID, getLocationAccessNotification())
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
startForeground(LOCATION_ACCESS_NOTIFICATION_ID, getLocationAccessNotification())
job?.cancel()
job = GlobalScope.launch(Dispatchers.IO) {
if (checkGrantedPermission(Manifest.permission.ACCESS_FINE_LOCATION) &&
(android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.R ||
checkGrantedPermission(Manifest.permission.ACCESS_BACKGROUND_LOCATION))
) {
if (com.google.android.gms.common.GoogleApiAvailability.getInstance()
.isGooglePlayServicesAvailable(this@LocationService)
== com.google.android.gms.common.ConnectionResult.SUCCESS
) {
LocationServices.getFusedLocationProviderClient(this@LocationService).lastLocation
} else {
val lm = getSystemService(LOCATION_SERVICE) as android.location.LocationManager
var location: android.location.Location? = null
for (provider in arrayOf(
"fused", // LocationManager.FUSED_PROVIDER,
android.location.LocationManager.GPS_PROVIDER,
android.location.LocationManager.NETWORK_PROVIDER,
android.location.LocationManager.PASSIVE_PROVIDER
)) {
if (lm.isProviderEnabled(provider)) {
location = lm.getLastKnownLocation(provider)
if (location != null) break
}
}
com.google.android.gms.tasks.Tasks.forResult(location)
}.addOnCompleteListener { task ->
val networkApi = WeatherNetworkApi(this@LocationService)
if (task.isSuccessful) {
val location = task.result
if (location != null) {
Preferences.customLocationLat = location.latitude.toString()
Preferences.customLocationLon = location.longitude.toString()
}
CoroutineScope(Dispatchers.IO).launch {
networkApi.updateWeather()
withContext(Dispatchers.Main) {
stopSelf()
}
}
EventBus.getDefault().post(MainFragment.UpdateUiMessageEvent())
} else {
CoroutineScope(Dispatchers.IO).launch {
networkApi.updateWeather()
withContext(Dispatchers.Main) {
stopSelf()
}
}
EventBus.getDefault().post(MainFragment.UpdateUiMessageEvent())
}
}
} else {
stopSelf()
}
}
return START_STICKY
}
override fun onDestroy() {
super.onDestroy()
job?.cancel()
job = null
}
companion object {
const val LOCATION_ACCESS_NOTIFICATION_ID = 28465
@JvmStatic
fun requestNewLocation(context: Context) {
ContextCompat.startForegroundService(context, Intent(context, LocationService::class.java))
}
}
override fun onBind(intent: Intent?): IBinder? {
return null
}
private fun getLocationAccessNotification(): Notification {
with(NotificationManagerCompat.from(this)) {
// Create channel
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
createNotificationChannel(
NotificationChannel(
getString(R.string.location_access_notification_channel_id),
getString(R.string.location_access_notification_channel_name),
NotificationManager.IMPORTANCE_LOW
).apply {
description = getString(R.string.location_access_notification_channel_description)
}
)
}
val builder = NotificationCompat.Builder(this@LocationService, getString(R.string.location_access_notification_channel_id))
.setSmallIcon(R.drawable.ic_stat_notification)
.setContentTitle(getString(R.string.location_access_notification_title))
.setOngoing(true)
.setColor(ContextCompat.getColor(this@LocationService, R.color.colorAccent))
// Main intent that open the activity
builder.setContentIntent(PendingIntent.getActivity(this@LocationService, 0, Intent(this@LocationService, MainActivity::class.java), PendingIntent.FLAG_UPDATE_CURRENT))
return builder.build()
}
}
}

View File

@ -1,226 +0,0 @@
package com.tommasoberlose.anotherwidget.services
import android.Manifest
import android.app.*
import android.content.Context
import android.content.Intent
import android.os.IBinder
import android.util.Log
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import androidx.core.content.ContextCompat
import com.tommasoberlose.anotherwidget.R
import com.tommasoberlose.anotherwidget.db.EventRepository
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.helpers.CalendarHelper
import com.tommasoberlose.anotherwidget.helpers.CalendarHelper.applyFilters
import com.tommasoberlose.anotherwidget.helpers.CalendarHelper.sortEvents
import com.tommasoberlose.anotherwidget.models.Event
import com.tommasoberlose.anotherwidget.receivers.UpdatesReceiver
import com.tommasoberlose.anotherwidget.ui.activities.MainActivity
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.GlobalScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import me.everything.providers.android.calendar.CalendarProvider
import org.greenrobot.eventbus.EventBus
import java.util.*
import kotlin.collections.ArrayList
class UpdateCalendarService : Service() {
companion object {
const val CALENDAR_SYNC_NOTIFICATION_ID = 28468
fun enqueueWork(context: Context) {
ContextCompat.startForegroundService(context, Intent(context, UpdateCalendarService::class.java))
}
}
override fun onCreate() {
super.onCreate()
startForeground(CALENDAR_SYNC_NOTIFICATION_ID, getCalendarSyncNotification())
}
private var job: Job? = null
override fun onBind(intent: Intent?): IBinder? {
return null
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
startForeground(CALENDAR_SYNC_NOTIFICATION_ID, getCalendarSyncNotification())
job?.cancel()
job = GlobalScope.launch(Dispatchers.IO) {
UpdatesReceiver.removeUpdates(this@UpdateCalendarService)
val eventRepository = EventRepository(this@UpdateCalendarService)
if (Preferences.showEvents) {
val eventList = ArrayList<Event>()
// fetch all events from now to next ACTION_CALENDAR_UPDATE + limit
val now = Calendar.getInstance()
val limit = Calendar.getInstance().apply {
set(Calendar.MILLISECOND, 0)
set(Calendar.SECOND, 0)
set(Calendar.MINUTE, 0)
set(Calendar.HOUR_OF_DAY, 0)
add(Calendar.DATE, 1)
when (Preferences.showUntil) {
0 -> add(Calendar.HOUR, 3)
1 -> add(Calendar.HOUR, 6)
2 -> add(Calendar.HOUR, 12)
3 -> add(Calendar.DAY_OF_MONTH, 1)
4 -> add(Calendar.DAY_OF_MONTH, 3)
5 -> add(Calendar.DAY_OF_MONTH, 7)
6 -> add(Calendar.MINUTE, 30)
7 -> add(Calendar.HOUR, 1)
else -> add(Calendar.HOUR, 6)
}
}
if (!checkGrantedPermission(
Manifest.permission.READ_CALENDAR
)
) {
eventRepository.resetNextEventData()
eventRepository.clearEvents()
Preferences.showEvents = false
} else {
try {
val provider = CalendarProvider(this@UpdateCalendarService)
// apply time zone offset to correctly fetch all-day events
val data = provider.getInstances(
now.timeInMillis + now.timeZone.getOffset(now.timeInMillis).coerceAtMost(0),
limit.timeInMillis + limit.timeZone.getOffset(limit.timeInMillis).coerceAtLeast(0)
)
if (data != null) {
val instances = data.list
for (instance in instances) {
try {
val e = provider.getEvent(instance.eventId)
if (e == null || e.deleted || CalendarHelper.getFilteredCalendarIdList().contains(e.calendarId))
continue
if (e.allDay) {
val start = Calendar.getInstance()
start.timeInMillis = instance.begin
val end = Calendar.getInstance()
end.timeInMillis = instance.end
instance.begin =
start.timeInMillis - start.timeZone.getOffset(start.timeInMillis)
instance.end =
end.timeInMillis - end.timeZone.getOffset(end.timeInMillis)
}
if (instance.begin <= limit.timeInMillis && now.timeInMillis < instance.end) {
/* Following check may result in "fake" all-day events with
* non-UTC start/end time, and therefore cannot be found by
* Calendar when tapped to open details.
// Check all day events
val startDate = Calendar.getInstance()
startDate.timeInMillis = instance.begin
val endDate = Calendar.getInstance()
endDate.timeInMillis = instance.end
val isAllDay = e.allDay || (
startDate.get(Calendar.MILLISECOND) == 0
&& startDate.get(Calendar.SECOND) == 0
&& startDate.get(Calendar.MINUTE) == 0
&& startDate.get(Calendar.HOUR_OF_DAY) == 0
&& endDate.get(Calendar.MILLISECOND) == 0
&& endDate.get(Calendar.SECOND) == 0
&& endDate.get(Calendar.MINUTE) == 0
&& endDate.get(Calendar.HOUR_OF_DAY) == 0
)
*/
eventList.add(
Event(
id = instance.id,
eventID = e.id,
title = e.title ?: "",
startDate = instance.begin,
endDate = instance.end,
calendarID = e.calendarId,
allDay = e.allDay,
address = e.eventLocation ?: "",
selfAttendeeStatus = e.selfAttendeeStatus.toInt(),
availability = e.availability
)
)
}
} catch (ignored: Exception) {
}
}
}
val sortedEvents = eventList.sortEvents()
val filteredEventList = sortedEvents
.applyFilters()
if (filteredEventList.isEmpty()) {
eventRepository.resetNextEventData()
eventRepository.clearEvents()
} else {
eventRepository.saveEvents(
sortedEvents
)
eventRepository.saveNextEventData(filteredEventList.first())
}
} catch (ignored: java.lang.Exception) {
}
}
} else {
eventRepository.resetNextEventData()
eventRepository.clearEvents()
}
eventRepository.close()
UpdatesReceiver.setUpdates(this@UpdateCalendarService)
MainWidget.updateWidget(this@UpdateCalendarService)
EventBus.getDefault().post(MainFragment.UpdateUiMessageEvent())
stopSelf()
}
return START_STICKY
}
override fun onDestroy() {
super.onDestroy()
job?.cancel()
job = null
}
private fun getCalendarSyncNotification(): Notification {
with(NotificationManagerCompat.from(this)) {
// Create channel
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
createNotificationChannel(
NotificationChannel(
getString(R.string.calendar_sync_notification_channel_id),
getString(R.string.calendar_sync_notification_channel_name),
NotificationManager.IMPORTANCE_LOW
).apply {
description = getString(R.string.calendar_sync_notification_channel_description)
}
)
}
val builder = NotificationCompat.Builder(this@UpdateCalendarService, getString(R.string.calendar_sync_notification_channel_id))
.setSmallIcon(R.drawable.ic_stat_notification)
.setContentTitle(getString(R.string.calendar_sync_notification_title))
.setOngoing(true)
.setColor(ContextCompat.getColor(this@UpdateCalendarService, R.color.colorAccent))
// Main intent that open the activity
builder.setContentIntent(PendingIntent.getActivity(this@UpdateCalendarService, 0, Intent(this@UpdateCalendarService, MainActivity::class.java), PendingIntent.FLAG_UPDATE_CURRENT))
return builder.build()
}
}
}

View File

@ -0,0 +1,195 @@
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.ExistingWorkPolicy
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkManager
import androidx.work.Worker
import androidx.work.WorkerParameters
import com.tommasoberlose.anotherwidget.db.EventRepository
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.helpers.CalendarHelper
import com.tommasoberlose.anotherwidget.helpers.CalendarHelper.applyFilters
import com.tommasoberlose.anotherwidget.helpers.CalendarHelper.sortEvents
import com.tommasoberlose.anotherwidget.models.Event
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 java.util.*
import me.everything.providers.android.calendar.CalendarProvider
import org.greenrobot.eventbus.EventBus
class UpdateCalendarWorker(context: Context, params: WorkerParameters) : Worker(context, params) {
override fun doWork(): Result {
val context = applicationContext
UpdatesReceiver.removeUpdates(context)
val eventRepository = EventRepository(context)
if (Preferences.showEvents) {
if (!context.checkGrantedPermission(Manifest.permission.READ_CALENDAR)) {
eventRepository.resetNextEventData()
eventRepository.clearEvents()
} else {
// fetch all events from now to next ACTION_CALENDAR_UPDATE + limit
val now = Calendar.getInstance()
val limit = Calendar.getInstance().apply {
set(Calendar.MILLISECOND, 0)
set(Calendar.SECOND, 0)
set(Calendar.MINUTE, 0)
set(Calendar.HOUR_OF_DAY, 0)
add(Calendar.DATE, 1)
when (Preferences.showUntil) {
0 -> add(Calendar.HOUR, 3)
1 -> add(Calendar.HOUR, 6)
2 -> add(Calendar.HOUR, 12)
3 -> add(Calendar.DAY_OF_MONTH, 1)
4 -> add(Calendar.DAY_OF_MONTH, 3)
5 -> add(Calendar.DAY_OF_MONTH, 7)
6 -> add(Calendar.MINUTE, 30)
7 -> add(Calendar.HOUR, 1)
else -> add(Calendar.HOUR, 6)
}
}
try {
val eventList = ArrayList<Event>()
val provider = CalendarProvider(context)
// apply time zone offset to correctly fetch all-day events
val data = provider.getInstances(
now.timeInMillis + now.timeZone.getOffset(now.timeInMillis).coerceAtMost(0),
limit.timeInMillis + limit.timeZone.getOffset(limit.timeInMillis).coerceAtLeast(0)
)
if (data != null) {
val instances = data.list
for (instance in instances) {
try {
val e = provider.getEvent(instance.eventId)
if (e == null || e.deleted || CalendarHelper.getFilteredCalendarIdList().contains(e.calendarId))
continue
if (e.allDay) {
val start = Calendar.getInstance()
start.timeInMillis = instance.begin
val end = Calendar.getInstance()
end.timeInMillis = instance.end
instance.begin =
start.timeInMillis - start.timeZone.getOffset(start.timeInMillis)
instance.end =
end.timeInMillis - end.timeZone.getOffset(end.timeInMillis)
}
if (instance.begin <= limit.timeInMillis && now.timeInMillis < instance.end) {
/* Following check may result in "fake" all-day events with
* non-UTC start/end time, and therefore cannot be found by
* Calendar when tapped to open details.
// Check all day events
val startDate = Calendar.getInstance()
startDate.timeInMillis = instance.begin
val endDate = Calendar.getInstance()
endDate.timeInMillis = instance.end
val isAllDay = e.allDay || (
startDate.get(Calendar.MILLISECOND) == 0
&& startDate.get(Calendar.SECOND) == 0
&& startDate.get(Calendar.MINUTE) == 0
&& startDate.get(Calendar.HOUR_OF_DAY) == 0
&& endDate.get(Calendar.MILLISECOND) == 0
&& endDate.get(Calendar.SECOND) == 0
&& endDate.get(Calendar.MINUTE) == 0
&& endDate.get(Calendar.HOUR_OF_DAY) == 0
)
*/
eventList.add(
Event(
id = instance.id,
eventID = e.id,
title = e.title ?: "",
startDate = instance.begin,
endDate = instance.end,
calendarID = e.calendarId,
allDay = e.allDay,
address = e.eventLocation ?: "",
selfAttendeeStatus = e.selfAttendeeStatus.toInt(),
availability = e.availability
)
)
}
} catch (ignored: Exception) {
}
}
}
val sortedEvents = eventList.sortEvents()
val filteredEventList = sortedEvents.applyFilters()
if (filteredEventList.isEmpty()) {
eventRepository.resetNextEventData()
eventRepository.clearEvents()
} else {
eventRepository.saveEvents(sortedEvents)
eventRepository.saveNextEventData(filteredEventList.first())
}
} catch (ignored: java.lang.Exception) {
}
}
} else {
eventRepository.resetNextEventData()
eventRepository.clearEvents()
}
eventRepository.close()
UpdatesReceiver.setUpdates(context)
MainWidget.updateWidget(context)
EventBus.getDefault().post(MainFragment.UpdateUiMessageEvent())
if (Preferences.showEvents)
enqueueTrigger(context)
return Result.success()
}
companion object {
fun enqueue(context: Context) {
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(
"updateEventListTrigger",
ExistingWorkPolicy.KEEP,
OneTimeWorkRequestBuilder<Trigger>().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(
"updateEventListTrigger"
)
}
}
}
class Trigger(context: Context, params: WorkerParameters) : Worker(context, params) {
override fun doWork(): Result {
if (Preferences.showEvents && !isStopped)
enqueue(applicationContext)
return Result.success()
}
}
}

View File

@ -0,0 +1,108 @@
package com.tommasoberlose.anotherwidget.services
import android.Manifest
import android.content.Context
import android.location.Location
import android.location.LocationManager
import androidx.work.CoroutineWorker
import androidx.work.ExistingWorkPolicy
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkManager
import androidx.work.Worker
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.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.network.WeatherNetworkApi
import com.tommasoberlose.anotherwidget.utils.checkGrantedPermission
import java.util.concurrent.TimeUnit
import kotlin.coroutines.resume
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.withContext
class WeatherWorker(context: Context, params: WorkerParameters) : CoroutineWorker(context, params) {
override suspend fun doWork(): Result {
val context = applicationContext
if (Preferences.customLocationAdd == "" &&
context.checkGrantedPermission(Manifest.permission.ACCESS_COARSE_LOCATION)
) {
if (GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(context)
== ConnectionResult.SUCCESS
) {
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
for (provider in lm.getProviders(true)) {
lm.getLastKnownLocation(provider)?.let {
if (location == null ||
it.time - location!!.time > 2 * 60 * 1000 ||
(it.time - location!!.time > -2 * 60 * 1000 && it.accuracy < location!!.accuracy))
location = it
}
}
location
}?.let { location ->
Preferences.customLocationLat = location.latitude.toString()
Preferences.customLocationLon = location.longitude.toString()
}
}
withContext(Dispatchers.IO) {
WeatherNetworkApi(context).updateWeather()
}
if (Preferences.showWeather)
enqueueTrigger(context)
return Result.success()
}
companion object {
fun enqueue(context: Context, replace: Boolean = false) {
WorkManager.getInstance(context).enqueueUniqueWork(
"updateWeather",
if (replace) ExistingWorkPolicy.REPLACE else ExistingWorkPolicy.KEEP,
OneTimeWorkRequestBuilder<WeatherWorker>().build()
)
}
fun enqueueTrigger(context: Context) {
val interval = when (Preferences.weatherRefreshPeriod) {
0 -> 30
1 -> 60
2 -> 60L * 3
3 -> 60L * 6
4 -> 60L * 12
5 -> 60L * 24
else -> 60
}
WorkManager.getInstance(context).enqueueUniqueWork(
"updateWeatherTrigger",
ExistingWorkPolicy.REPLACE,
OneTimeWorkRequestBuilder<Trigger>().setInitialDelay(
interval, TimeUnit.MINUTES
).build()
)
}
fun cancelTrigger(context: Context) {
WorkManager.getInstance(context).cancelUniqueWork(
"updateWeatherTrigger"
)
}
}
class Trigger(context: Context, params: WorkerParameters) : Worker(context, params) {
override fun doWork(): Result {
if (Preferences.showWeather && !isStopped)
enqueue(applicationContext)
return Result.success()
}
}
}

View File

@ -46,6 +46,7 @@ class MainActivity : AppCompatActivity(), SharedPreferences.OnSharedPreferenceCh
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
overridePendingTransition(R.anim.nav_default_enter_anim, R.anim.nav_default_exit_anim)
viewModel = ViewModelProvider(this).get(MainViewModel::class.java)
binding = ActivityMainBinding.inflate(layoutInflater)
@ -140,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)
}
}

View File

@ -0,0 +1,28 @@
package com.tommasoberlose.anotherwidget.ui.activities
import android.content.Intent
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import com.tommasoberlose.anotherwidget.R
import com.tommasoberlose.anotherwidget.databinding.ActivityMainBinding
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.ui.viewmodels.MainViewModel
import kotlinx.coroutines.delay
class SplashActivity: AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
lifecycleScope.launchWhenResumed {
delay(1000)
if (!this@SplashActivity.isDestroyed) {
startActivity(Intent(this@SplashActivity, MainActivity::class.java))
finish()
}
}
}
}

View File

@ -169,6 +169,7 @@ class CustomDateActivity : AppCompatActivity() {
isDateCapitalize = viewModel.isDateCapitalize.value ?: true
isDateUppercase = viewModel.isDateUppercase.value ?: false
}
com.tommasoberlose.anotherwidget.ui.widgets.MainWidget.updateWidget(this)
super.onBackPressed()
}

View File

@ -57,9 +57,7 @@ class WeatherProviderActivity : AppCompatActivity() {
updateListItem()
binding.loader.isVisible = true
lifecycleScope.launch {
WeatherHelper.updateWeather(this@WeatherProviderActivity)
}
WeatherHelper.updateWeather(this@WeatherProviderActivity, true)
}
.clicked(R.id.radioButton) {
if (Preferences.weatherProvider != provider.rawValue) {
@ -72,9 +70,7 @@ class WeatherProviderActivity : AppCompatActivity() {
updateListItem()
binding.loader.isVisible = true
lifecycleScope.launch {
WeatherHelper.updateWeather(this@WeatherProviderActivity)
}
WeatherHelper.updateWeather(this@WeatherProviderActivity, true)
}
.checked(R.id.radioButton, provider.rawValue == Preferences.weatherProvider)
.with<TextView>(R.id.text2) {
@ -92,10 +88,8 @@ class WeatherProviderActivity : AppCompatActivity() {
}
.clicked(R.id.action_configure) {
BottomSheetWeatherProviderSettings(this) {
lifecycleScope.launch {
binding.loader.isVisible = true
WeatherHelper.updateWeather(this@WeatherProviderActivity)
}
WeatherHelper.updateWeather(this@WeatherProviderActivity, true)
}.show()
}
.visibility(R.id.action_configure, if (/*WeatherHelper.isKeyRequired(provider) && */provider.rawValue == Preferences.weatherProvider) View.VISIBLE else View.GONE)

View File

@ -12,6 +12,7 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.RemoteViews
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
@ -99,10 +100,6 @@ class MainFragment : Fragment() {
}
private fun subscribeUi(viewModel: MainViewModel) {
viewModel.showPreview.observe(viewLifecycleOwner) {
binding.preview.visibility = if (it) View.VISIBLE else View.GONE
}
viewModel.showWallpaper.observe(viewLifecycleOwner) {
if (it) {
val wallpaper = requireActivity().getCurrentWallpaper()
@ -146,6 +143,10 @@ class MainFragment : Fragment() {
binding.toolbar.cardElevation = if (it > 0) 32f else 0f
}
viewModel.showPreview.observe(viewLifecycleOwner) {
binding.preview.isVisible = it
}
viewModel.widgetPreferencesUpdate.observe(viewLifecycleOwner) {
onUpdateUiEvent(null)
}
@ -169,16 +170,13 @@ class MainFragment : Fragment() {
val view: View = generatedView.apply(requireActivity().applicationContext, binding.widget)
view.measure(0, 0)
binding.widgetLoader.animate().scaleX(1f).scaleY(1f).alpha(1f)
.setDuration(200L).start()
binding.widgetLoader.animate().alpha(0f).setDuration(200L).start()
binding.widget.animate().alpha(0f).setDuration(200L).withEndAction {
updatePreviewVisibility(view.measuredHeight)
binding.widget.removeAllViews()
binding.widget.addView(view)
updatePreviewVisibility(view.measuredHeight)
binding.widgetLoader.animate().scaleX(0f).scaleY(0f).alpha(0f)
.setDuration(200L).start()
binding.widget.animate().alpha(1f).start()
binding.widget.animate().setStartDelay(300L).alpha(1f).start()
}.start()
}
}
@ -188,6 +186,7 @@ class MainFragment : Fragment() {
}
private fun updatePreviewVisibility(widgetHeight: Int) {
if (isAdded) {
val newHeight = widgetHeight + 32f.convertDpToPixel(requireContext()).toInt()
if (binding.preview.layoutParams.height != newHeight) {
binding.preview.clearAnimation()
@ -195,7 +194,7 @@ class MainFragment : Fragment() {
binding.preview.height,
newHeight
).apply {
duration = 500L
duration = 300L
addUpdateListener {
val animatedValue = animatedValue as Int
val layoutParams = binding.preview.layoutParams
@ -205,11 +204,12 @@ class MainFragment : Fragment() {
}.start()
}
}
}
override fun onResume() {
super.onResume()
EventBus.getDefault().register(this)
updateUI()
// updateUI()
}
override fun onPause() {

View File

@ -206,7 +206,6 @@ class SettingsFragment : Fragment() {
.animate()
.rotation((binding.actionRefreshIcon.rotation - binding.actionRefreshIcon.rotation % 360f) + 360f)
.withEndAction {
viewLifecycleOwner.lifecycleScope.launch(Dispatchers.IO) {
try {
WeatherHelper.updateWeather(requireContext())
CalendarHelper.updateEventList(requireContext())
@ -216,7 +215,6 @@ class SettingsFragment : Fragment() {
ex.printStackTrace()
}
}
}
.start()
}
}

View File

@ -23,6 +23,7 @@ import com.tommasoberlose.anotherwidget.components.MaterialBottomSheetDialog
import com.tommasoberlose.anotherwidget.databinding.FragmentPreferencesBinding
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.helpers.CalendarHelper
import com.tommasoberlose.anotherwidget.helpers.WeatherHelper
import com.tommasoberlose.anotherwidget.receivers.UpdatesReceiver
import com.tommasoberlose.anotherwidget.receivers.WeatherReceiver
import com.tommasoberlose.anotherwidget.ui.activities.MainActivity
@ -88,6 +89,7 @@ class PreferencesFragment : Fragment() {
CalendarHelper.setEventUpdatesAndroidN(requireContext())
} else {
CalendarHelper.removeEventUpdatesAndroidN(requireContext())
UpdatesReceiver.removeUpdates(requireContext())
}
}
}
@ -124,7 +126,6 @@ class PreferencesFragment : Fragment() {
requireCalendarPermission()
} else {
Preferences.showEvents = enabled
UpdatesReceiver.removeUpdates(requireContext())
}
}
@ -137,7 +138,7 @@ class PreferencesFragment : Fragment() {
if (enabled) {
Preferences.weatherProviderError = ""
Preferences.weatherProviderLocationError = ""
WeatherReceiver.setUpdates(requireContext())
WeatherHelper.updateWeather(requireContext())
} else {
WeatherReceiver.removeUpdates(requireContext())
}
@ -165,6 +166,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 +174,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()

View File

@ -134,23 +134,13 @@ class WeatherFragment : Fragment() {
}
private fun checkLocationPermission() {
if (requireActivity().checkGrantedPermission(Manifest.permission.ACCESS_FINE_LOCATION) &&
(android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.R ||
requireActivity().checkGrantedPermission(Manifest.permission.ACCESS_BACKGROUND_LOCATION))
) {
if (requireActivity().checkGrantedPermission(Manifest.permission.ACCESS_COARSE_LOCATION)) {
binding.locationPermissionAlert.isVisible = false
} else if (Preferences.customLocationAdd == "") {
binding.locationPermissionAlert.isVisible = true
binding.locationPermissionAlert.setOnClickListener {
requirePermission()
}
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.R &&
requireActivity().checkGrantedPermission(Manifest.permission.ACCESS_FINE_LOCATION)
) {
val text = getString(R.string.action_grant_permission) + " - " +
requireContext().packageManager.backgroundPermissionOptionLabel
binding.locationPermissionAlert.text = text
}
binding.weatherProviderLocationError.isVisible = false
} else {
binding.locationPermissionAlert.isVisible = false
@ -187,10 +177,8 @@ class WeatherFragment : Fragment() {
.addOnSelectItemListener { value ->
if (value != Preferences.weatherTempUnit) {
Preferences.weatherTempUnit = value
viewLifecycleOwner.lifecycleScope.launch {
WeatherHelper.updateWeather(requireContext())
}
}
}.show()
}
@ -218,10 +206,8 @@ class WeatherFragment : Fragment() {
if (resultCode == Activity.RESULT_OK) {
when (requestCode) {
Constants.RESULT_CODE_CUSTOM_LOCATION -> {
viewLifecycleOwner.lifecycleScope.launch {
WeatherHelper.updateWeather(requireContext())
}
}
//RequestCode.WEATHER_PROVIDER_REQUEST_CODE.code -> {
//}
}
@ -232,19 +218,35 @@ class WeatherFragment : Fragment() {
private fun requirePermission() {
Dexter.withContext(requireContext())
.withPermissions(
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.R &&
requireActivity().checkGrantedPermission(Manifest.permission.ACCESS_FINE_LOCATION))
Manifest.permission.ACCESS_BACKGROUND_LOCATION
else
Manifest.permission.ACCESS_FINE_LOCATION
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.areAllPermissionsGranted()) {
if (report.grantedPermissionResponses.isNotEmpty()) {
checkLocationPermission()
viewLifecycleOwner.lifecycleScope.launch {
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()
}
}
}
@ -252,6 +254,7 @@ class WeatherFragment : Fragment() {
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()
@ -260,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()

View File

@ -135,7 +135,6 @@ class MainViewModel(context: Application) : AndroidViewModel(context) {
addSource(Preferences.asLiveData(Preferences::customFontFile)) { value = true }
addSource(Preferences.asLiveData(Preferences::customFontName)) { value = true }
addSource(Preferences.asLiveData(Preferences::customFontVariant)) { value = true }
addSource(Preferences.asLiveData(Preferences::secondRowInformation)) { value = true }
addSource(Preferences.asLiveData(Preferences::widgetAlign)) { value = true }
addSource(Preferences.asLiveData(Preferences::widgetMargin)) { value = true }
addSource(Preferences.asLiveData(Preferences::widgetPadding)) { value = true }
@ -156,7 +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::weatherTempUnit)) { value = true }
addSource(Preferences.asLiveData(Preferences::weatherProvider)) { value = true }
addSource(Preferences.asLiveData(Preferences::weatherIconPack)) { value = true }
addSource(Preferences.asLiveData(Preferences::customLocationLat)) { value = true }
addSource(Preferences.asLiveData(Preferences::customLocationLon)) { value = true }

View File

@ -7,7 +7,6 @@ import android.content.Intent
import android.graphics.Color
import android.graphics.Typeface
import android.text.format.DateUtils
import android.util.Log
import android.util.TypedValue
import android.view.Gravity
import android.view.LayoutInflater
@ -16,7 +15,6 @@ import android.view.ViewGroup
import android.widget.*
import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
import androidx.core.view.updateMargins
import com.tommasoberlose.anotherwidget.R
import com.tommasoberlose.anotherwidget.databinding.LeftAlignedWidgetBinding
import com.tommasoberlose.anotherwidget.db.EventRepository
@ -26,7 +24,6 @@ import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.helpers.*
import com.tommasoberlose.anotherwidget.helpers.ColorHelper.toIntValue
import com.tommasoberlose.anotherwidget.helpers.ImageHelper.applyShadow
import com.tommasoberlose.anotherwidget.receivers.CrashlyticsReceiver
import com.tommasoberlose.anotherwidget.receivers.NewCalendarEventReceiver
import com.tommasoberlose.anotherwidget.receivers.WidgetClickListenerReceiver
import com.tommasoberlose.anotherwidget.utils.checkGrantedPermission
@ -61,7 +58,7 @@ class AlignedWidget(val context: Context, val rightAligned: Boolean = false) {
context,
appWidgetId,
IntentHelper.getWidgetUpdateIntent(context),
PendingIntent.FLAG_UPDATE_CURRENT
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
views.setOnClickPendingIntent(R.id.widget_shape_background, refreshIntent)
@ -70,7 +67,6 @@ class AlignedWidget(val context: Context, val rightAligned: Boolean = false) {
views.setViewPadding(R.id.main_layout, padding, padding, padding, padding)
} catch (ex: Exception) {
ex.printStackTrace()
CrashlyticsReceiver.sendCrash(context, ex)
}
// Clock
@ -88,7 +84,6 @@ class AlignedWidget(val context: Context, val rightAligned: Boolean = false) {
views = updateGridView(generatedBinding, views, appWidgetId)
} catch (ex: Exception) {
ex.printStackTrace()
CrashlyticsReceiver.sendCrash(context, ex)
}
return views
@ -108,7 +103,7 @@ class AlignedWidget(val context: Context, val rightAligned: Boolean = false) {
val i = Intent(context, WidgetClickListenerReceiver::class.java)
i.action = Actions.ACTION_OPEN_WEATHER_INTENT
val weatherPIntent = PendingIntent.getBroadcast(context, widgetID, i, 0)
val weatherPIntent = PendingIntent.getBroadcast(context, widgetID, i, PendingIntent.FLAG_IMMUTABLE)
views.setOnClickPendingIntent(R.id.weather_rect, weatherPIntent)
views.setOnClickPendingIntent(R.id.weather_sub_line_rect, weatherPIntent)
@ -138,7 +133,7 @@ class AlignedWidget(val context: Context, val rightAligned: Boolean = false) {
context,
widgetID,
IntentHelper.getCalendarIntent(context),
PendingIntent.FLAG_UPDATE_CURRENT
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
views.setOnClickPendingIntent(R.id.date_rect, calPIntent)
views.setViewVisibility(R.id.first_line_rect, View.VISIBLE)
@ -175,7 +170,7 @@ class AlignedWidget(val context: Context, val rightAligned: Boolean = false) {
context,
NewCalendarEventReceiver::class.java
).apply { action = Actions.ACTION_GO_TO_NEXT_EVENT },
PendingIntent.FLAG_UPDATE_CURRENT
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
)
@ -189,7 +184,7 @@ class AlignedWidget(val context: Context, val rightAligned: Boolean = false) {
context,
widgetID,
IntentHelper.getEventIntent(context, nextEvent),
PendingIntent.FLAG_UPDATE_CURRENT
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
views.setOnClickPendingIntent(R.id.next_event_rect, eventIntent)
views.setImageViewBitmap(
@ -226,7 +221,7 @@ class AlignedWidget(val context: Context, val rightAligned: Boolean = false) {
context,
widgetID,
IntentHelper.getGoogleMapsIntentFromAddress(context, nextEvent.address),
PendingIntent.FLAG_UPDATE_CURRENT
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
views.setOnClickPendingIntent(R.id.sub_line_rect, mapIntent)
} else {
@ -234,7 +229,7 @@ class AlignedWidget(val context: Context, val rightAligned: Boolean = false) {
context,
widgetID,
IntentHelper.getCalendarIntent(context, nextEvent.startDate),
PendingIntent.FLAG_UPDATE_CURRENT
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
views.setOnClickPendingIntent(R.id.sub_line_rect, pIntentDetail)
}
@ -254,7 +249,7 @@ class AlignedWidget(val context: Context, val rightAligned: Boolean = false) {
context,
widgetID,
IntentHelper.getMusicIntent(context),
PendingIntent.FLAG_UPDATE_CURRENT
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
views.setOnClickPendingIntent(R.id.sub_line_rect, musicIntent)
showSomething = true
@ -269,7 +264,7 @@ class AlignedWidget(val context: Context, val rightAligned: Boolean = false) {
context,
widgetID,
IntentHelper.getClockIntent(context),
PendingIntent.FLAG_UPDATE_CURRENT
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
views.setOnClickPendingIntent(R.id.sub_line_rect, alarmIntent)
showSomething = true
@ -285,7 +280,7 @@ class AlignedWidget(val context: Context, val rightAligned: Boolean = false) {
context,
widgetID,
IntentHelper.getBatteryIntent(),
PendingIntent.FLAG_UPDATE_CURRENT
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
views.setOnClickPendingIntent(R.id.sub_line_rect, batteryIntent)
showSomething = true
@ -305,7 +300,7 @@ class AlignedWidget(val context: Context, val rightAligned: Boolean = false) {
context,
widgetID,
IntentHelper.getFitIntent(context),
PendingIntent.FLAG_UPDATE_CURRENT
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
views.setOnClickPendingIntent(R.id.sub_line_rect, fitIntent)
showSomething = true
@ -326,7 +321,7 @@ class AlignedWidget(val context: Context, val rightAligned: Boolean = false) {
context,
widgetID,
IntentHelper.getNotificationIntent(context),
PendingIntent.FLAG_UPDATE_CURRENT
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
views.setOnClickPendingIntent(
R.id.sub_line_rect,
@ -354,7 +349,7 @@ class AlignedWidget(val context: Context, val rightAligned: Boolean = false) {
nextEvent,
forceEventDetails = true
),
PendingIntent.FLAG_UPDATE_CURRENT
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
views.setOnClickPendingIntent(
R.id.sub_line_rect,
@ -368,7 +363,7 @@ class AlignedWidget(val context: Context, val rightAligned: Boolean = false) {
if (Preferences.showWeatherAsGlanceProvider && Preferences.showWeather && Preferences.weatherIcon != "") {
val i = Intent(context, WidgetClickListenerReceiver::class.java)
i.action = Actions.ACTION_OPEN_WEATHER_INTENT
val weatherPIntent = PendingIntent.getBroadcast(context, widgetID, i, 0)
val weatherPIntent = PendingIntent.getBroadcast(context, widgetID, i, PendingIntent.FLAG_IMMUTABLE)
views.setOnClickPendingIntent(
R.id.sub_line_rect,
@ -417,7 +412,6 @@ class AlignedWidget(val context: Context, val rightAligned: Boolean = false) {
)
} catch (ex: Exception) {
ex.printStackTrace()
CrashlyticsReceiver.sendCrash(context, ex)
}
return views
@ -996,7 +990,6 @@ class AlignedWidget(val context: Context, val rightAligned: Boolean = false) {
return bindingView
} catch (ex: Exception) {
ex.printStackTrace()
CrashlyticsReceiver.sendCrash(context, ex)
return null
}
}

View File

@ -3,7 +3,6 @@ package com.tommasoberlose.anotherwidget.ui.widgets
import android.app.PendingIntent
import android.content.Context
import android.util.TypedValue
import android.view.Gravity
import android.view.View
import android.widget.RemoteViews
import com.tommasoberlose.anotherwidget.R
@ -11,7 +10,6 @@ import com.tommasoberlose.anotherwidget.global.Constants
import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.helpers.ColorHelper
import com.tommasoberlose.anotherwidget.helpers.IntentHelper
import com.tommasoberlose.anotherwidget.receivers.CrashlyticsReceiver
import com.tommasoberlose.anotherwidget.utils.isDarkTheme
import com.tommasoberlose.anotherwidget.utils.toPixel
@ -43,7 +41,7 @@ class ClockWidget(val context: Context) {
context,
widgetID,
IntentHelper.getClockIntent(context),
0
PendingIntent.FLAG_IMMUTABLE
)
views.setOnClickPendingIntent(R.id.time, clockPIntent)
views.setOnClickPendingIntent(R.id.time_am_pm, clockPIntent)
@ -111,7 +109,6 @@ class ClockWidget(val context: Context) {
}
} catch (ex: Exception) {
ex.printStackTrace()
CrashlyticsReceiver.sendCrash(context, ex)
}
return views

View File

@ -32,27 +32,28 @@ class MainWidget : AppWidgetProvider() {
override fun onEnabled(context: Context) {
CalendarHelper.updateEventList(context)
WeatherReceiver.setUpdates(context)
WeatherHelper.updateWeather(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) {
handler.run {
removeCallbacksAndMessages(null)
postDelayed ({
context.sendBroadcast(IntentHelper.getWidgetUpdateIntent(context))
}, 100)
}
}
fun getWidgetCount(context: Context): Int {

View File

@ -2,13 +2,11 @@ package com.tommasoberlose.anotherwidget.ui.widgets
import android.Manifest
import android.app.PendingIntent
import android.appwidget.AppWidgetManager
import android.content.Context
import android.content.Intent
import android.graphics.Color
import android.graphics.Typeface
import android.text.format.DateUtils
import android.util.Log
import android.util.TypedValue
import android.view.Gravity
import android.view.LayoutInflater
@ -28,7 +26,6 @@ import com.tommasoberlose.anotherwidget.global.Preferences
import com.tommasoberlose.anotherwidget.helpers.*
import com.tommasoberlose.anotherwidget.helpers.ColorHelper.toIntValue
import com.tommasoberlose.anotherwidget.helpers.ImageHelper.applyShadow
import com.tommasoberlose.anotherwidget.receivers.CrashlyticsReceiver
import com.tommasoberlose.anotherwidget.receivers.NewCalendarEventReceiver
import com.tommasoberlose.anotherwidget.receivers.WidgetClickListenerReceiver
import com.tommasoberlose.anotherwidget.utils.checkGrantedPermission
@ -63,7 +60,7 @@ class StandardWidget(val context: Context) {
context,
appWidgetId,
IntentHelper.getWidgetUpdateIntent(context),
PendingIntent.FLAG_UPDATE_CURRENT
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
views.setOnClickPendingIntent(R.id.widget_shape_background, refreshIntent)
@ -72,7 +69,6 @@ class StandardWidget(val context: Context) {
views.setViewPadding(R.id.main_layout, padding, padding, padding, padding)
} catch (ex: Exception) {
ex.printStackTrace()
CrashlyticsReceiver.sendCrash(context, ex)
}
// Clock
@ -90,7 +86,6 @@ class StandardWidget(val context: Context) {
views = updateGridView(generatedBinding, views, appWidgetId)
} catch (ex: Exception) {
ex.printStackTrace()
CrashlyticsReceiver.sendCrash(context, ex)
}
return views
@ -110,7 +105,7 @@ class StandardWidget(val context: Context) {
val i = Intent(context, WidgetClickListenerReceiver::class.java)
i.action = Actions.ACTION_OPEN_WEATHER_INTENT
val weatherPIntent = PendingIntent.getBroadcast(context, widgetID, i, 0)
val weatherPIntent = PendingIntent.getBroadcast(context, widgetID, i, PendingIntent.FLAG_IMMUTABLE)
views.setOnClickPendingIntent(R.id.weather_rect, weatherPIntent)
views.setOnClickPendingIntent(R.id.weather_sub_line_rect, weatherPIntent)
@ -140,7 +135,7 @@ class StandardWidget(val context: Context) {
context,
widgetID,
IntentHelper.getCalendarIntent(context),
PendingIntent.FLAG_UPDATE_CURRENT
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
views.setOnClickPendingIntent(R.id.date_rect, calPIntent)
views.setViewVisibility(R.id.first_line_rect, View.VISIBLE)
@ -183,7 +178,7 @@ class StandardWidget(val context: Context) {
context,
NewCalendarEventReceiver::class.java
).apply { action = Actions.ACTION_GO_TO_NEXT_EVENT },
PendingIntent.FLAG_UPDATE_CURRENT
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
)
@ -202,7 +197,7 @@ class StandardWidget(val context: Context) {
context,
NewCalendarEventReceiver::class.java
).apply { action = Actions.ACTION_GO_TO_PREVIOUS_EVENT },
PendingIntent.FLAG_UPDATE_CURRENT
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
)
@ -218,7 +213,7 @@ class StandardWidget(val context: Context) {
context,
widgetID,
IntentHelper.getEventIntent(context, nextEvent),
PendingIntent.FLAG_UPDATE_CURRENT
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
views.setOnClickPendingIntent(R.id.next_event_rect, eventIntent)
views.setViewVisibility(R.id.next_event_rect, View.VISIBLE)
@ -251,7 +246,7 @@ class StandardWidget(val context: Context) {
context,
widgetID,
IntentHelper.getGoogleMapsIntentFromAddress(context, nextEvent.address),
PendingIntent.FLAG_UPDATE_CURRENT
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
views.setOnClickPendingIntent(R.id.sub_line_rect, mapIntent)
} else {
@ -259,7 +254,7 @@ class StandardWidget(val context: Context) {
context,
widgetID,
IntentHelper.getCalendarIntent(context, nextEvent.startDate),
PendingIntent.FLAG_UPDATE_CURRENT
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
views.setOnClickPendingIntent(R.id.sub_line_rect, pIntentDetail)
}
@ -285,7 +280,7 @@ class StandardWidget(val context: Context) {
context,
widgetID,
IntentHelper.getMusicIntent(context),
PendingIntent.FLAG_UPDATE_CURRENT
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
views.setOnClickPendingIntent(R.id.sub_line_rect, musicIntent)
showSomething = true
@ -300,7 +295,7 @@ class StandardWidget(val context: Context) {
context,
widgetID,
IntentHelper.getClockIntent(context),
PendingIntent.FLAG_UPDATE_CURRENT
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
views.setOnClickPendingIntent(R.id.sub_line_rect, alarmIntent)
showSomething = true
@ -316,7 +311,7 @@ class StandardWidget(val context: Context) {
context,
widgetID,
IntentHelper.getBatteryIntent(),
PendingIntent.FLAG_UPDATE_CURRENT
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
views.setOnClickPendingIntent(R.id.sub_line_rect, batteryIntent)
showSomething = true
@ -336,7 +331,7 @@ class StandardWidget(val context: Context) {
context,
widgetID,
IntentHelper.getFitIntent(context),
PendingIntent.FLAG_UPDATE_CURRENT
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
views.setOnClickPendingIntent(R.id.sub_line_rect, fitIntent)
showSomething = true
@ -357,7 +352,7 @@ class StandardWidget(val context: Context) {
context,
widgetID,
IntentHelper.getNotificationIntent(context),
PendingIntent.FLAG_UPDATE_CURRENT
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
views.setOnClickPendingIntent(
R.id.sub_line_rect,
@ -385,7 +380,7 @@ class StandardWidget(val context: Context) {
nextEvent,
forceEventDetails = true
),
PendingIntent.FLAG_UPDATE_CURRENT
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
views.setOnClickPendingIntent(
R.id.sub_line_rect,
@ -399,7 +394,7 @@ class StandardWidget(val context: Context) {
if (Preferences.showWeatherAsGlanceProvider && Preferences.showWeather && Preferences.weatherIcon != "") {
val i = Intent(context, WidgetClickListenerReceiver::class.java)
i.action = Actions.ACTION_OPEN_WEATHER_INTENT
val weatherPIntent = PendingIntent.getBroadcast(context, widgetID, i, 0)
val weatherPIntent = PendingIntent.getBroadcast(context, widgetID, i, PendingIntent.FLAG_IMMUTABLE)
views.setOnClickPendingIntent(
R.id.sub_line_rect,
@ -446,7 +441,6 @@ class StandardWidget(val context: Context) {
}
} catch (ex: Exception) {
ex.printStackTrace()
CrashlyticsReceiver.sendCrash(context, ex)
}
return views
@ -1027,7 +1021,6 @@ class StandardWidget(val context: Context) {
} catch (ex: Exception) {
ex.printStackTrace()
CrashlyticsReceiver.sendCrash(context, ex)
return null
}
}

View File

@ -214,6 +214,14 @@ fun Context.checkGrantedPermission(permission: String): Boolean {
return ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED
}
fun android.app.AlarmManager.setExactIfCanSchedule(type: Int, triggerAtMillis: Long, operation: android.app.PendingIntent) {
// uncomment the following check after bumping compileSdkVersion/targetSdkVersion to 31
//if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.S || canScheduleExactAlarms())
setExact(type, triggerAtMillis, operation)
//else
// set(type, triggerAtMillis, operation)
}
fun Context.getCurrentWallpaper(): Drawable? = try {
WallpaperManager.getInstance(this).drawable
} catch (e: Exception) {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 284 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 302 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 348 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 483 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 590 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.1 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 194 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 179 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 220 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 348 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 389 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 337 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 221 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 377 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 575 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 746 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 348 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 289 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 389 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 590 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 780 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 483 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 383 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 590 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 893 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 95 KiB

After

Width:  |  Height:  |  Size: 178 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 590 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 520 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 780 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -3,9 +3,9 @@
<item>
<shape android:shape="rectangle">
<corners
android:radius="9dp" />
android:radius="20dp" />
<solid
android:color="@android:color/white"/>
android:color="@color/colorPrimary"/>
</shape>
</item>
</selector>

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape android:shape="oval">
<stroke android:color="@android:color/white"
android:width="2dp" />
</shape>
</item>
</selector>

View File

@ -0,0 +1,19 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="20dp"
android:height="20dp"
android:viewportWidth="20"
android:viewportHeight="20"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M16.5,10h-3.04c-0.52,0 -1.02,0.27 -1.28,0.73C11.75,11.49 10.94,12 10,12s-1.75,-0.51 -2.18,-1.27C7.56,10.27 7.07,10 6.54,10H3.5C2.67,10 2,10.67 2,11.5v4C2,16.33 2.67,17 3.5,17h13c0.83,0 1.5,-0.67 1.5,-1.5v-4C18,10.67 17.33,10 16.5,10z"/>
<path
android:fillColor="@android:color/white"
android:pathData="M10,3L10,3C9.59,3 9.25,3.34 9.25,3.75v2.5C9.25,6.66 9.59,7 10,7h0c0.41,0 0.75,-0.34 0.75,-0.75v-2.5C10.75,3.34 10.41,3 10,3z"/>
<path
android:fillColor="@android:color/white"
android:pathData="M16.36,5.64L16.36,5.64c-0.29,-0.29 -0.77,-0.29 -1.06,0L13.54,7.4c-0.29,0.29 -0.29,0.77 0,1.06l0,0c0.29,0.29 0.77,0.29 1.06,0l1.77,-1.77C16.66,6.4 16.66,5.93 16.36,5.64z"/>
<path
android:fillColor="@android:color/white"
android:pathData="M6.46,7.4L4.7,5.64c-0.29,-0.29 -0.77,-0.29 -1.06,0l0,0c-0.29,0.29 -0.29,0.77 0,1.06L5.4,8.46c0.29,0.29 0.77,0.29 1.06,0l0,0C6.76,8.17 6.76,7.7 6.46,7.4z"/>
</vector>

View File

@ -0,0 +1,19 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M20.45,6.55L20.45,6.55c-0.38,-0.38 -1.01,-0.38 -1.39,0L16.89,8.7c-0.39,0.38 -0.39,1.01 0,1.39l0.01,0.01c0.39,0.39 1.01,0.39 1.4,0c0.62,-0.63 1.52,-1.54 2.15,-2.17C20.83,7.55 20.83,6.93 20.45,6.55z"/>
<path
android:fillColor="@android:color/white"
android:pathData="M12.02,3h-0.03C11.44,3 11,3.44 11,3.98v3.03C11,7.56 11.44,8 11.98,8h0.03C12.56,8 13,7.56 13,7.02V3.98C13,3.44 12.56,3 12.02,3z"/>
<path
android:fillColor="@android:color/white"
android:pathData="M7.1,10.11l0.01,-0.01c0.38,-0.38 0.38,-1.01 0,-1.39L4.96,6.54c-0.38,-0.39 -1.01,-0.39 -1.39,0L3.55,6.55c-0.39,0.39 -0.39,1.01 0,1.39c0.63,0.62 1.53,1.54 2.15,2.17C6.09,10.49 6.72,10.49 7.1,10.11z"/>
<path
android:fillColor="@android:color/white"
android:pathData="M12,15c-1.24,0 -2.31,-0.75 -2.76,-1.83C8.92,12.43 8.14,12 7.34,12L4,12c-1.1,0 -2,0.9 -2,2l0,5c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2v-5c0,-1.1 -0.9,-2 -2,-2l-3.34,0c-0.8,0 -1.58,0.43 -1.9,1.17C14.31,14.25 13.24,15 12,15"/>
</vector>

View File

@ -1,4 +1,6 @@
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:clipChildren="false"

View File

@ -19,6 +19,7 @@
<color name="colorPrimaryDark">#F8F8F8</color>
<color name="colorAccent">#0092ca</color>
<color name="colorAccent_op10">#1A1089FF</color>
<color name="colorAccent_op20">#331089FF</color>
<color name="colorAccent_op30">#4D1089FF</color>
<color name="colorTitle">#1089FF</color>
<color name="black_op10">#1A000000</color>

View File

@ -9,12 +9,12 @@ buildscript {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:4.2.0'
classpath 'com.android.tools.build:gradle:7.0.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath 'com.google.gms:google-services:4.3.5'
classpath 'com.google.gms:google-services:4.3.8'
// Add the Crashlytics Gradle plugin.
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.5.2'
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.6.1'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files

View File

@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip