Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Timechange #271

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 8 additions & 6 deletions android/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,16 @@
android:name=".BootReceiver"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
<action android:name="android.intent.action.TIMEZONE_CHANGED" />
<action android:name="android.intent.action.TIME_SET" />
</intent-filter>
</receiver>
<service
android:name=".AlarmService"
android:exported="false"
android:foregroundServiceType="mediaPlayback">
android:name=".AlarmService"
android:exported="false"
android:foregroundServiceType="mediaPlayback">
</service>
</application>
</manifest>
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ class AlarmPlugin : FlutterPlugin, MethodChannel.MethodCallHandler {
if (alarm != null) {
val alarmIntent = createAlarmIntent(contextToUse, call, alarm.id)
val delayInSeconds = (alarm.dateTime.time - System.currentTimeMillis()) / 1000
android.util.Log.d("BootReceiver", "setAlarm() ID: ${alarm.id} - date=[${alarm.dateTime.time}] SCT=[${System.currentTimeMillis()}] - delayInSeconds=[$delayInSeconds]")

if (delayInSeconds <= 5) {
handleImmediateAlarm(contextToUse, alarmIntent, delayInSeconds.toInt())
Expand All @@ -135,30 +136,32 @@ class AlarmPlugin : FlutterPlugin, MethodChannel.MethodCallHandler {
}
}

fun stopAlarm(id: Int, result: MethodChannel.Result? = null) {
fun stopAlarm(id: Int, result: MethodChannel.Result? = null, customContext: Context? = null) {
val contextToUse = customContext ?: context

if (AlarmService.ringingAlarmIds.contains(id)) {
val stopIntent = Intent(context, AlarmService::class.java)
val stopIntent = Intent(contextToUse, AlarmService::class.java)
stopIntent.action = "STOP_ALARM"
stopIntent.putExtra("id", id)
context.stopService(stopIntent)
contextToUse.stopService(stopIntent)
}

// Intent to cancel the future alarm if it's set
val alarmIntent = Intent(context, AlarmReceiver::class.java)
val alarmIntent = Intent(contextToUse, AlarmReceiver::class.java)
val pendingIntent = PendingIntent.getBroadcast(
context,
contextToUse,
id,
alarmIntent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)

// Cancel the future alarm using AlarmManager
val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
val alarmManager = contextToUse.getSystemService(Context.ALARM_SERVICE) as AlarmManager
alarmManager.cancel(pendingIntent)

alarmIds.remove(id)
if (alarmIds.isEmpty() && notifOnKillEnabled) {
disableWarningNotificationOnKill(context)
disableWarningNotificationOnKill(contextToUse)
}

result?.success(true)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,25 +20,43 @@ class BootReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
if (intent.action == Intent.ACTION_BOOT_COMPLETED) {
Log.d("BootReceiver", "Device rebooted, rescheduling alarms")

rescheduleAlarms(context)
} else if (intent.action == Intent.ACTION_TIME_CHANGED || intent.action == Intent.ACTION_TIMEZONE_CHANGED) {
Log.d("BootReceiver", "Device change time, stop and rescheduling alarms")
stopAlarms(context)
rescheduleAlarms(context)
}
}

private fun stopAlarms(context: Context) {
val alarmStorage = AlarmStorage(context)
val storedAlarms = alarmStorage.getSavedAlarms()

for (alarm in storedAlarms) {
try {
Log.d("BootReceiver", "Stop alarm with ID: ${alarm.id}")
val alarmPlugin = AlarmPlugin()
alarmPlugin.stopAlarm(alarm.id, null, context)
} catch (e: Exception) {
Log.e("BootReceiver", "Exception while stop alarm with iid: ${alarm.id}", e)
}
}
}

private fun rescheduleAlarms(context: Context) {
val alarmStorage = AlarmStorage(context)
val storedAlarms = alarmStorage.getSavedAlarms()

Log.d("BootReceiver", "Rescheduling ${storedAlarms.size} alarms")

for (alarm in storedAlarms) {
if (alarm.notificationSettings == null) {
Log.d("BootReceiver", "Skipping alarm with ID: ${alarm.id} due to missing notificationSettings")
continue
}

var alarmArgs: Map<String, Any>? = null

try {
// Create the arguments for the MethodCall
alarmArgs = mapOf(
Expand All @@ -56,28 +74,28 @@ class BootReceiver : BroadcastReceiver() {
"icon" to alarm.notificationSettings.icon
)
).toMutableMap()

alarm.volume?.let {
(alarmArgs as MutableMap)[ "volume" ] = it
}

Log.d("BootReceiver", "Rescheduling alarm with ID: ${alarm.id}")
Log.d("BootReceiver", "Alarm arguments: $alarmArgs")

// Simulate the MethodCall
val methodCall = MethodCall("setAlarm", alarmArgs)

// Call the setAlarm method in AlarmPlugin with the custom context
val alarmPlugin = AlarmPlugin()
alarmPlugin.setAlarm(methodCall, object : MethodChannel.Result {
override fun success(result: Any?) {
Log.d("BootReceiver", "Alarm rescheduled successfully for ID: ${alarm.id}")
}

override fun error(errorCode: String, errorMessage: String?, errorDetails: Any?) {
Log.e("BootReceiver", "Failed to reschedule alarm for ID: ${alarm.id}, Error: $errorMessage")
}

override fun notImplemented() {
Log.e("BootReceiver", "Method not implemented")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ import com.google.gson.annotations.SerializedName
import java.util.Date
import io.flutter.Log
import java.lang.reflect.Type
import java.time.Instant
import java.util.Calendar
import java.util.TimeZone

data class AlarmSettings(
val id: Int,
Expand Down Expand Up @@ -44,7 +47,13 @@ class DateDeserializer : JsonDeserializer<Date> {
override fun deserialize(json: JsonElement?, typeOfT: Type?, context: JsonDeserializationContext?): Date {
val dateTimeMicroseconds = json?.asLong ?: 0L
val dateTimeMilliseconds = dateTimeMicroseconds / 1000
return Date(dateTimeMilliseconds)
return createUtcDate(dateTimeMilliseconds)
}

private fun createUtcDate(dateTimeMilliseconds: Long): Date {
val calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC"))
calendar.timeInMillis = dateTimeMilliseconds
return calendar.time
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import com.google.gson.GsonBuilder
import com.google.gson.JsonDeserializer
import java.util.Date
import io.flutter.Log
import java.util.Calendar
import java.util.TimeZone

class AlarmStorage(context: Context) {
companion object {
Expand All @@ -34,7 +36,9 @@ class AlarmStorage(context: Context) {

fun getSavedAlarms(): List<AlarmSettings> {
val gsonBuilder = GsonBuilder().registerTypeAdapter(Date::class.java, JsonDeserializer<Date> { json, _, _ ->
Date(json.asJsonPrimitive.asLong)
val calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC"))
calendar.timeInMillis = json.asJsonPrimitive.asLong
calendar.time
})
val gson: Gson = gsonBuilder.create()

Expand Down
2 changes: 2 additions & 0 deletions example/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@
*.swp
.DS_Store
.atom/
.build/
.buildlog/
.history
.svn/
.swiftpm/
migrate_working_dir/

# IntelliJ related
Expand Down
6 changes: 3 additions & 3 deletions example/ios/Runner.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -378,7 +378,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = 63LD84R3KS;
DEVELOPMENT_TEAM = 564UXMB3NN;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
Expand Down Expand Up @@ -514,7 +514,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = 63LD84R3KS;
DEVELOPMENT_TEAM = 564UXMB3NN;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
Expand Down Expand Up @@ -542,7 +542,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = 63LD84R3KS;
DEVELOPMENT_TEAM = 564UXMB3NN;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
Expand Down
32 changes: 22 additions & 10 deletions ios/Classes/SwiftAlarmPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -386,21 +386,23 @@ public class SwiftAlarmPlugin: NSObject, FlutterPlugin {
private func backgroundFetch() {
self.mixOtherAudios()

self.silentAudioPlayer?.pause()
self.silentAudioPlayer?.play()

let ids = Array(self.alarms.keys)

for id in ids {
NSLog("[SwiftAlarmPlugin] Background check alarm with id \(id)")
if let audioPlayer = self.alarms[id]?.audioPlayer, let dateTime = self.alarms[id]?.triggerTime {
let currentTime = audioPlayer.deviceCurrentTime
let time = currentTime + dateTime.timeIntervalSinceNow
audioPlayer.play(atTime: time)
}

if let alarm = self.alarms[id], let delayInSeconds = alarm.triggerTime?.timeIntervalSinceNow {
if (delayInSeconds < 0){
self.stopAlarm(id: id, cancelNotif: true, result: { _ in })
return
}
alarm.timer?.invalidate()//fix leak timer
alarm.timer = Timer.scheduledTimer(timeInterval: delayInSeconds, target: self, selector: #selector(self.executeTask(_:)), userInfo: id, repeats: false)

if let audioPlayer = self.alarms[id]?.audioPlayer {
let currentTime = audioPlayer.deviceCurrentTime
let time = currentTime + delayInSeconds
audioPlayer.stop()//without stop(), new play(atTime) will ignore
audioPlayer.play(atTime: time + 0.5)
}
}
}
}
Expand Down Expand Up @@ -465,6 +467,16 @@ public class SwiftAlarmPlugin: NSObject, FlutterPlugin {
} else {
NSLog("[SwiftAlarmPlugin] BGTaskScheduler not available for your version of iOS lower than 13.0")
}

//Listener Time change
NotificationCenter.default.addObserver(self, selector: #selector(timeZoneOrClockChanged), name: .NSSystemTimeZoneDidChange, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(timeZoneOrClockChanged), name: .NSSystemClockDidChange, object: nil)
}


@objc static func timeZoneOrClockChanged(notification: Notification) {
NSLog("[SwiftAlarmPlugin] Time zone or system clock changed: \(notification.name.rawValue)")
shared.backgroundFetch()
}

/// Enables background fetch
Expand Down