Date and time change listener in Android?
In my application, there's a alarm service, and I find that if user change it's date or time to a passed time. My alarm will not be triggered at the time I expect.
So, I may have to reset all the alarms again. Is there an date and time change listener in android?
Create an intent filter :
static {
s_intentFilter = new IntentFilter();
s_intentFilter.addAction(Intent.ACTION_TIME_TICK);
s_intentFilter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
s_intentFilter.addAction(Intent.ACTION_TIME_CHANGED);
}
and a broadcast receiver:
private final BroadcastReceiver m_timeChangedReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
if (action.equals(Intent.ACTION_TIME_CHANGED) ||
action.equals(Intent.ACTION_TIMEZONE_CHANGED)) {
doWorkSon();
}
}
};
register the receiver:
public void onCreate() {
super.onCreate();
registerReceiver(m_timeChangedReceiver, s_intentFilter);
}
EDIT:
and unregister it:
public void onDestroy() {
super.onDestroy();
unregisterReceiver(m_timeChangedReceiver);
}
In addition to the accepted answer
If you want to listen to time changes while your app is not running I would register in the manifest:
<receiver android:name="com.your.pacakge.TimeChangeBroadcastReceiver">
<intent-filter>
<action android:name="android.intent.action.TIME_SET"/>
<action android:name="android.intent.action.TIMEZONE_CHANGED"/>
</intent-filter>
</receiver>
If you do this, do not explicitly register the receiver in the code with registerReceiver
and unregisterReceiver
.
Again, this is just an addition to the accepted answer.
In order to detect a change in date, you need to register to these actions:
- Intent.ACTION_TIME_CHANGED - "The time was set."
- Intent.ACTION_DATE_CHANGED - "The date has changed."
- Intent.ACTION_TIMEZONE_CHANGED - "The timezone has changed"
Here's one solution that I wrote, so all you have to do is to extend the class, and register&unregister from it on the Activity/Fragment :
abstract class DateChangedBroadcastReceiver : BroadcastReceiver() {
private var curDate = LocalDate.now()
/**called when the receiver detected the date has changed. You should still check it yourself, because you might already be synced with the new date*/
abstract fun onDateChanged(previousDate: LocalDate, newDate: LocalDate)
@Suppress("MemberVisibilityCanBePrivate")
fun register(context: Context, date: LocalDate) {
curDate = date
val filter = IntentFilter()
filter.addAction(Intent.ACTION_TIME_CHANGED)
filter.addAction(Intent.ACTION_DATE_CHANGED)
filter.addAction(Intent.ACTION_TIMEZONE_CHANGED)
context.registerReceiver(this, filter)
val newDate = LocalDate.now()
if (newDate != curDate) {
curDate = newDate
onDateChanged(date, newDate)
}
}
/**a convenient way to auto-unregister when activity/fragment has stopped. This should be called on the onResume method of the fragment/activity*/
fun registerOnResume(activity: AppCompatActivity, date: LocalDate, fragment: androidx.fragment.app.Fragment? = null) {
register(activity, date)
val lifecycle = fragment?.lifecycle ?: activity.lifecycle
lifecycle.addObserver(object : LifecycleObserver {
@Suppress("unused")
@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
fun onPause() {
// Log.d("AppLog", "onPause, so unregistering")
lifecycle.removeObserver(this)
activity.unregisterReceiver(this@DateChangedBroadcastReceiver)
}
})
}
override fun onReceive(context: Context, intent: Intent) {
val newDate = LocalDate.now()
// Log.d("AppLog", "got intent:" + intent.action + " curDate:" + curDate + " newDate:" + newDate)
if (newDate != curDate) {
// Log.d("AppLog", "cur date is different, so posting event")
val previousDate = curDate
curDate = newDate
onDateChanged(previousDate, newDate)
}
}
}
If you can't use LocalDate (as it uses relatively new API : 26 , which is currently used on about 21% of devices), you can use this instead:
abstract class DateChangedBroadcastReceiver : BroadcastReceiver() {
private var curDate = Calendar.getInstance()
/**called when the receiver detected the date has changed. You should still check it yourself, because you might already be synced with the new date*/
abstract fun onDateChanged(previousDate: Calendar, newDate: Calendar)
companion object {
fun toString(cal: Calendar): String {
return "${cal.get(Calendar.YEAR)}-${cal.get(Calendar.MONTH)}-${cal.get(Calendar.DAY_OF_MONTH)}"
}
fun resetDate(date: Calendar) {
date.set(Calendar.HOUR_OF_DAY, 0)
date.set(Calendar.MINUTE, 0)
date.set(Calendar.SECOND, 0)
date.set(Calendar.MILLISECOND, 0)
}
fun areOfSameDate(date: Calendar, otherDate: Calendar) =
date.get(Calendar.DAY_OF_YEAR) == otherDate.get(Calendar.DAY_OF_YEAR) &&
date.get(Calendar.YEAR) == otherDate.get(Calendar.YEAR)
}
@Suppress("MemberVisibilityCanBePrivate")
fun register(context: Context, date: Calendar) {
curDate = date.clone() as Calendar
resetDate(curDate)
val filter = IntentFilter()
filter.addAction(Intent.ACTION_TIME_CHANGED)
filter.addAction(Intent.ACTION_DATE_CHANGED)
filter.addAction(Intent.ACTION_TIMEZONE_CHANGED)
context.registerReceiver(this, filter)
val newDate = Calendar.getInstance()
resetDate(newDate)
if (!areOfSameDate(newDate, curDate)) {
val previousDate = curDate.clone() as Calendar
curDate = newDate
onDateChanged(previousDate, curDate)
}
}
/**a convenient way to auto-unregister when activity/fragment has stopped. This should be called on the onResume method of the fragment/activity*/
fun registerOnResume(activity: AppCompatActivity, date: Calendar, fragment: Fragment? = null) {
register(activity as Context, date)
val lifecycle = fragment?.lifecycle ?: activity.lifecycle
lifecycle.addObserver(object : LifecycleObserver {
@Suppress("unused")
@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
fun onPause() {
// Log.d("AppLog", "onPause, so unregistering")
lifecycle.removeObserver(this)
activity.unregisterReceiver(this@DateChangedBroadcastReceiver)
}
})
}
override fun onReceive(context: Context, intent: Intent) {
val newDate = Calendar.getInstance()
resetDate(newDate)
// Log.d("AppLog", "got intent:${intent.action} curDate:${toString(curDate)} newDate:${toString(newDate)}")
if (!areOfSameDate(newDate, curDate)) {
// Log.d("AppLog", "cur date is different, so posting event")
val previousDate = curDate.clone() as Calendar
curDate = newDate
onDateChanged(previousDate, newDate)
}
}
}
Example usage:
class MainActivity : AppCompatActivity() {
var curDate = Calendar.getInstance()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
override fun onResume() {
super.onResume()
object : DateChangedBroadcastReceiver() {
override fun onDateChanged(previousDate: Calendar, newDate: Calendar) {
Log.d("AppLog", "MainActivity: ${DateChangedBroadcastReceiver.toString(previousDate)} -> ${DateChangedBroadcastReceiver.toString(newDate)}")
curDate = newDate.clone() as Calendar
//TODO handle date change
}
}.registerOnResume(this, curDate)
}
}