From a4c79a93950125448ae54e5769d59df8566d5d9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Rebelo?= Date: Sat, 2 Sep 2023 10:13:14 +0100 Subject: [PATCH] Attempt to fix DST changes --- app/src/main/AndroidManifest.xml | 3 + .../gadgetbridge/GBApplication.java | 2 + .../externalevents/TimeChangeReceiver.java | 102 ++++++++++++++++-- .../service/DeviceCommunicationService.java | 1 + 4 files changed, 101 insertions(+), 7 deletions(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index ada0ce462..662d7a435 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -33,6 +33,9 @@ + + + diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/GBApplication.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/GBApplication.java index 2494f447c..0812bad74 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/GBApplication.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/GBApplication.java @@ -68,6 +68,7 @@ import nodomain.freeyourgadget.gadgetbridge.entities.DaoMaster; import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession; import nodomain.freeyourgadget.gadgetbridge.entities.Device; import nodomain.freeyourgadget.gadgetbridge.externalevents.BluetoothStateChangeReceiver; +import nodomain.freeyourgadget.gadgetbridge.externalevents.TimeChangeReceiver; import nodomain.freeyourgadget.gadgetbridge.externalevents.opentracks.OpenTracksContentObserver; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceService; @@ -224,6 +225,7 @@ public class GBApplication extends Application { loadAppsPebbleBlackList(); PeriodicExporter.enablePeriodicExport(context); + TimeChangeReceiver.scheduleNextDstChange(context); if (isRunningMarshmallowOrLater()) { notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/TimeChangeReceiver.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/TimeChangeReceiver.java index dffc3f79b..9727de716 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/TimeChangeReceiver.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/TimeChangeReceiver.java @@ -16,34 +16,122 @@ along with this program. If not, see . */ package nodomain.freeyourgadget.gadgetbridge.externalevents; +import android.app.AlarmManager; +import android.app.PendingIntent; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; +import android.os.Build; +import android.os.SystemClock; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.threeten.bp.Instant; +import org.threeten.bp.ZoneId; +import org.threeten.bp.zone.ZoneOffsetTransition; +import org.threeten.bp.zone.ZoneRules; import java.util.Date; import java.util.GregorianCalendar; import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils; +import nodomain.freeyourgadget.gadgetbridge.util.GB; +import nodomain.freeyourgadget.gadgetbridge.util.PendingIntentUtils; import nodomain.freeyourgadget.gadgetbridge.util.Prefs; public class TimeChangeReceiver extends BroadcastReceiver { - private static final Logger LOG = LoggerFactory.getLogger(TimeChangeReceiver.class); + public static final String ACTION_DST_CHANGED = "nodomain.freeyourgadget.gadgetbridge.DST_CHANGED"; + @Override public void onReceive(Context context, Intent intent) { - Prefs prefs = GBApplication.getPrefs(); + final Prefs prefs = GBApplication.getPrefs(); final String action = intent.getAction(); + if (action == null) { + LOG.warn("Null action"); + return; + } - if (prefs.getBoolean("datetime_synconconnect", true) && (action.equals(Intent.ACTION_TIME_CHANGED) || action.equals(Intent.ACTION_TIMEZONE_CHANGED))) { - Date newTime = GregorianCalendar.getInstance().getTime(); - LOG.info("Time or Timezone changed, syncing with device: " + DateTimeUtils.formatDate(newTime) + " (" + newTime.toGMTString() + "), " + intent.getAction()); - GBApplication.deviceService().onSetTime(); + if (!prefs.getBoolean("datetime_synconconnect", true)) { + LOG.warn("Ignoring time change for {}, time sync is disabled", action); + return; + } + + switch (action) { + case Intent.ACTION_TIME_CHANGED: + case Intent.ACTION_TIMEZONE_CHANGED: + case ACTION_DST_CHANGED: + // Continue after the switch + break; + default: + LOG.warn("Unknown action {}", action); + return; + } + + final Date newTime = GregorianCalendar.getInstance().getTime(); + LOG.info("Time or Timezone changed, syncing with device: {} ({}), {}", DateTimeUtils.formatDate(newTime), newTime.toGMTString(), intent.getAction()); + GBApplication.deviceService().onSetTime(); + + // Reschedule the next DST change, since the timezone may have changed + scheduleNextDstChange(context); + } + + /** + * Schedule an alarm to trigger on the next DST change, since ACTION_TIMEZONE_CHANGED is not broadcast otherwise. + * + * @param context the context + */ + public static void scheduleNextDstChange(final Context context) { + final ZoneId zoneId = ZoneId.systemDefault(); + final ZoneRules zoneRules = zoneId.getRules(); + final Instant now = Instant.now(); + final ZoneOffsetTransition transition = zoneRules.nextTransition(now); + final long nextDstMillis = transition.getInstant().toEpochMilli(); + final long delayMillis = nextDstMillis - now.toEpochMilli() + 5000L; + + final Intent i = new Intent(context, TimeChangeReceiver.class); + i.setAction(ACTION_DST_CHANGED); + final PendingIntent pi = PendingIntentUtils.getBroadcast(context, 0, i, 0, false); + + final AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); + final boolean exactAlarm = canScheduleExactAlarms(context, am); + + LOG.info("Scheduling next DST change: {} (in {} millis) (exact = {})", nextDstMillis, delayMillis, exactAlarm); + + am.cancel(pi); + + boolean scheduledExact = false; + if (exactAlarm) { + try { + am.setExact(AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime() + delayMillis, pi); + scheduledExact = true; + } catch (final Exception e) { + LOG.error("Failed to schedule exact alarm for next DST change", e); + } + } + + // Fallback to inexact alarm if the exact one failed + if (!scheduledExact) { + try { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + am.setAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime() + delayMillis, pi); + } else { + am.set(AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime() + delayMillis, pi); + } + } catch (final Exception e) { + LOG.error("Failed to schedule inexact alarm next DST change", e); + } } } -} \ No newline at end of file + + private static boolean canScheduleExactAlarms(final Context context, final AlarmManager am) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + return am.canScheduleExactAlarms(); + } else { + return GB.checkPermission(context, "android.permission.SCHEDULE_EXACT_ALARM"); + } + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceCommunicationService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceCommunicationService.java index d17fae8f7..360579dcd 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceCommunicationService.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceCommunicationService.java @@ -1247,6 +1247,7 @@ public class DeviceCommunicationService extends Service implements SharedPrefere IntentFilter filter = new IntentFilter(); filter.addAction("android.intent.action.TIME_SET"); filter.addAction("android.intent.action.TIMEZONE_CHANGED"); + filter.addAction(TimeChangeReceiver.ACTION_DST_CHANGED); registerReceiver(mTimeChangeReceiver, filter); } if (mBlueToothPairingRequestReceiver == null) {