2023-12-23 13:55:02 +01:00
package com.jens.automation2.receivers ;
2023-12-30 23:26:27 +01:00
import android.app.AlarmManager ;
import android.app.PendingIntent ;
2023-12-23 13:55:02 +01:00
import android.content.BroadcastReceiver ;
import android.content.Context ;
import android.content.Intent ;
import android.content.IntentFilter ;
import android.database.Cursor ;
import android.net.Uri ;
2023-12-30 23:26:27 +01:00
import android.os.Build ;
2023-12-23 13:55:02 +01:00
2023-12-27 14:48:27 +01:00
import androidx.annotation.NonNull ;
2023-12-23 13:55:02 +01:00
import com.jens.automation2.AutomationService ;
import com.jens.automation2.Miscellaneous ;
import com.jens.automation2.Rule ;
import com.jens.automation2.Trigger ;
import java.util.ArrayList ;
import java.util.Calendar ;
2024-01-01 13:35:21 +01:00
import java.util.Collections ;
2023-12-23 13:55:02 +01:00
import java.util.List ;
2023-12-29 19:43:50 +01:00
import java.util.Timer ;
import java.util.TimerTask ;
2023-12-23 13:55:02 +01:00
public class CalendarReceiver extends BroadcastReceiver implements AutomationListenerInterface
{
2023-12-25 11:57:55 +01:00
static CalendarReceiver calendarReceiverInstance = null ;
2023-12-23 13:55:02 +01:00
static boolean calendarReceiverActive = false ;
static IntentFilter calendarIntentFilter = null ;
2023-12-25 11:57:55 +01:00
private static AutomationService automationServiceRef ;
private static Intent calendarIntent = null ;
2023-12-27 13:33:09 +01:00
public static final int AVAILABILITY_OUT_OF_OFFICE = 4 ;
public static final int AVAILABILITY_WORKING_ELSEWHERE = 5 ;
2023-12-31 15:54:24 +01:00
public static final String calendarAlarmAction = " ALARM_FOR_CALENDAR " ;
2023-12-27 13:33:09 +01:00
static List < AndroidCalendar > calendarsCache = null ;
2023-12-28 17:17:08 +01:00
static List < CalendarEvent > calendarEventsCache = null ;
2023-12-27 13:33:09 +01:00
2023-12-29 19:43:50 +01:00
static Timer timer = null ;
static TimerTask timerTask = null ;
static Calendar nextWakeup = null ;
2023-12-30 23:26:27 +01:00
static AlarmManager alarmManager = null ;
2024-01-01 13:35:21 +01:00
static boolean wakeupNeedsToBeScheduled = false ;
2023-12-29 19:43:50 +01:00
2023-12-25 11:57:55 +01:00
public static CalendarReceiver getInstance ( )
{
if ( calendarReceiverInstance = = null )
calendarReceiverInstance = new CalendarReceiver ( ) ;
return calendarReceiverInstance ;
}
2023-12-23 13:55:02 +01:00
@Override
public void onReceive ( Context context , Intent intent )
{
if ( intent . getAction ( ) . equalsIgnoreCase ( Intent . ACTION_PROVIDER_CHANGED ) )
{
2023-12-29 19:43:50 +01:00
Miscellaneous . logEvent ( " i " , " CalendarReceiver " , " Received " + intent . getAction ( ) , 5 ) ;
2023-12-28 17:17:08 +01:00
calendarsCache = null ;
calendarEventsCache = null ;
2023-12-29 19:43:50 +01:00
checkForRules ( context ) ;
armOrRearmTimer ( ) ;
}
2023-12-31 15:54:24 +01:00
else if ( intent . getAction ( ) . equalsIgnoreCase ( calendarAlarmAction ) )
{
2024-01-01 13:35:21 +01:00
Miscellaneous . logEvent ( " i " , " CalendarReceiver " , " Received alarm for calendar receiver. " , 5 ) ;
2023-12-31 15:54:24 +01:00
routineAtAlarm ( ) ;
}
2023-12-29 19:43:50 +01:00
}
private static void checkForRules ( Context context )
{
ArrayList < Rule > ruleCandidates = Rule . findRuleCandidates ( Trigger . Trigger_Enum . calendarEvent ) ;
for ( int i = 0 ; i < ruleCandidates . size ( ) ; i + + )
{
if ( ruleCandidates . get ( i ) . getsGreenLight ( context ) )
ruleCandidates . get ( i ) . activate ( AutomationService . getInstance ( ) , false ) ;
2023-12-23 13:55:02 +01:00
}
}
@Override
public void startListener ( AutomationService automationService )
{
if ( ! calendarReceiverActive )
{
if ( calendarReceiverInstance = = null )
2023-12-25 11:57:55 +01:00
calendarReceiverInstance = new CalendarReceiver ( ) ;
2023-12-23 13:55:02 +01:00
if ( calendarIntentFilter = = null )
{
calendarIntentFilter = new IntentFilter ( ) ;
calendarIntentFilter . addAction ( Intent . ACTION_PROVIDER_CHANGED ) ;
2023-12-28 17:17:08 +01:00
// calendarIntentFilter.addDataScheme("content");
2023-12-23 13:55:02 +01:00
}
AutomationService . getInstance ( ) . registerReceiver ( calendarReceiverInstance , calendarIntentFilter ) ;
calendarReceiverActive = true ;
}
}
@Override
public void stopListener ( AutomationService automationService )
{
if ( calendarReceiverActive )
{
if ( calendarReceiverInstance ! = null )
{
AutomationService . getInstance ( ) . unregisterReceiver ( calendarReceiverInstance ) ;
calendarReceiverInstance = null ;
}
calendarReceiverActive = false ;
}
}
@Override
public boolean isListenerRunning ( )
{
return calendarReceiverActive ;
}
@Override
public Trigger . Trigger_Enum [ ] getMonitoredTrigger ( )
{
return new Trigger . Trigger_Enum [ ] { Trigger . Trigger_Enum . calendarEvent } ;
}
2023-12-27 13:33:09 +01:00
public static class AndroidCalendar
{
public int calendarId ;
public String displayName ;
2023-12-28 17:17:08 +01:00
public String accountString ;
@NonNull
@Override
public String toString ( )
{
return displayName + " ( " + accountString + " ) " ;
}
2023-12-27 13:33:09 +01:00
}
2023-12-23 13:55:02 +01:00
public static class CalendarEvent
{
2023-12-27 13:33:09 +01:00
public AndroidCalendar calendar ;
public int calendarId ;
public String eventId ;
public String title ;
public String description ;
public String location ;
public String availability ;
2023-12-23 13:55:02 +01:00
public Calendar start , end ;
2023-12-25 14:28:48 +01:00
public boolean allDay ;
2023-12-23 13:55:02 +01:00
2023-12-27 14:48:27 +01:00
public boolean isCurrentlyActive ( )
2023-12-23 13:55:02 +01:00
{
Calendar now = Calendar . getInstance ( ) ;
return now . getTimeInMillis ( ) > = start . getTimeInMillis ( ) & & now . getTimeInMillis ( ) < end . getTimeInMillis ( ) ;
}
2023-12-27 14:48:27 +01:00
@NonNull
@Override
public String toString ( )
{
return title ;
}
2023-12-23 13:55:02 +01:00
}
2023-12-27 13:33:09 +01:00
public static List < AndroidCalendar > readCalendars ( Context context )
{
if ( calendarsCache = = null )
{
calendarsCache = new ArrayList < > ( ) ;
Cursor cursor ;
cursor = context . getContentResolver ( ) . query (
Uri . parse ( " content://com.android.calendar/calendars " ) ,
2023-12-28 17:17:08 +01:00
new String [ ] { " _id " , " calendar_displayName " , " ownerAccount " , } ,
2023-12-27 13:33:09 +01:00
null , null , null ) ;
cursor . moveToFirst ( ) ;
// fetching calendars name
String CNames [ ] = new String [ cursor . getCount ( ) ] ;
List < AndroidCalendar > calendarlist = new ArrayList < > ( ) ;
for ( int i = 0 ; i < CNames . length ; i + + )
{
try
{
AndroidCalendar calendar = new AndroidCalendar ( ) ;
calendar . calendarId = Integer . parseInt ( cursor . getString ( 0 ) ) ;
calendar . displayName = cursor . getString ( 1 ) ;
2023-12-28 17:17:08 +01:00
calendar . accountString = cursor . getString ( 2 ) ;
2023-12-27 13:33:09 +01:00
calendarsCache . add ( calendar ) ;
}
catch ( Exception e )
{
}
cursor . moveToNext ( ) ;
}
if ( cursor ! = null )
cursor . close ( ) ;
}
return calendarsCache ;
}
2023-12-26 11:56:02 +01:00
public static List < CalendarEvent > readCalendarEvents ( Context context , boolean includePastEvents )
2023-12-23 13:55:02 +01:00
{
2023-12-28 17:17:08 +01:00
if ( calendarEventsCache = = null )
{
calendarEventsCache = new ArrayList < > ( ) ;
2023-12-25 14:28:48 +01:00
2023-12-28 17:17:08 +01:00
Cursor cursor ;
2023-12-23 13:55:02 +01:00
2023-12-28 17:17:08 +01:00
cursor = context . getContentResolver ( ) . query (
Uri . parse ( " content://com.android.calendar/events " ) ,
new String [ ] { " calendar_id " , " _id " , " title " , " description " , " allDay " , " dtstart " , " dtend " , " eventLocation " , " availability " } ,
null , null , null ) ;
2023-12-23 13:55:02 +01:00
2023-12-28 17:17:08 +01:00
cursor . moveToFirst ( ) ;
// fetching calendars name
String CNames [ ] = new String [ cursor . getCount ( ) ] ;
2023-12-23 13:55:02 +01:00
2023-12-28 17:17:08 +01:00
Calendar now = Calendar . getInstance ( ) ;
2023-12-26 11:56:02 +01:00
2023-12-28 17:17:08 +01:00
for ( int i = 0 ; i < CNames . length ; i + + )
2023-12-25 11:57:55 +01:00
{
2023-12-28 17:17:08 +01:00
try
2023-12-27 13:33:09 +01:00
{
2023-12-28 17:17:08 +01:00
CalendarEvent event = new CalendarEvent ( ) ;
event . calendarId = Integer . parseInt ( cursor . getString ( 0 ) ) ;
for ( AndroidCalendar cal : readCalendars ( context ) )
2023-12-27 13:33:09 +01:00
{
2023-12-28 17:17:08 +01:00
if ( cal . calendarId = = event . calendarId )
{
event . calendar = cal ;
break ;
}
2023-12-27 13:33:09 +01:00
}
2023-12-28 17:17:08 +01:00
event . eventId = cursor . getString ( 1 ) ;
event . title = cursor . getString ( 2 ) ;
event . description = cursor . getString ( 3 ) ;
event . allDay = cursor . getString ( 4 ) . equals ( " 1 " ) ;
event . start = Miscellaneous . calendarFromLong ( Long . parseLong ( cursor . getString ( 5 ) ) ) ;
event . end = Miscellaneous . calendarFromLong ( Long . parseLong ( cursor . getString ( 6 ) ) ) ;
event . location = cursor . getString ( 7 ) ;
event . availability = cursor . getString ( 8 ) ;
if ( includePastEvents | | event . end . getTimeInMillis ( ) > now . getTimeInMillis ( ) )
calendarEventsCache . add ( event ) ;
}
catch ( Exception e )
{ }
cursor . moveToNext ( ) ;
2023-12-25 11:57:55 +01:00
}
2023-12-23 13:55:02 +01:00
2023-12-28 17:17:08 +01:00
if ( cursor ! = null )
cursor . close ( ) ;
2023-12-29 19:43:50 +01:00
2023-12-28 17:17:08 +01:00
}
2023-12-25 14:28:48 +01:00
2023-12-28 17:17:08 +01:00
return calendarEventsCache ;
2023-12-23 13:55:02 +01:00
}
2023-12-25 11:57:55 +01:00
2023-12-30 23:26:27 +01:00
protected static void routineAtAlarm ( )
{
checkForRules ( Miscellaneous . getAnyContext ( ) ) ;
// Set next timer
calculateNextWakeup ( ) ;
armOrRearmTimer ( ) ;
}
2023-12-29 19:43:50 +01:00
private static void armOrRearmTimer ( )
{
2023-12-30 23:26:27 +01:00
PendingIntent pi = null ;
if ( Build . VERSION . SDK_INT > = Build . VERSION_CODES . M )
2023-12-29 19:43:50 +01:00
{
2023-12-30 23:26:27 +01:00
if ( alarmManager = = null )
2023-12-29 19:43:50 +01:00
{
2023-12-30 23:26:27 +01:00
alarmManager = ( AlarmManager ) Miscellaneous . getAnyContext ( ) . getSystemService ( Context . ALARM_SERVICE ) ;
2023-12-31 16:53:59 +01:00
}
2023-12-29 19:43:50 +01:00
2023-12-31 16:53:59 +01:00
if ( pi = = null )
{
2023-12-31 15:54:24 +01:00
Intent intent = new Intent ( Miscellaneous . getAnyContext ( ) , CalendarReceiver . class ) ;
intent . setAction ( calendarAlarmAction ) ;
pi = PendingIntent . getBroadcast ( AutomationService . getInstance ( ) , 0 , intent , 0 ) ;
2023-12-29 19:43:50 +01:00
}
2023-12-30 23:26:27 +01:00
}
else
2023-12-29 19:43:50 +01:00
{
2023-12-30 23:26:27 +01:00
timerTask = new TimerTask ( )
{
@Override
public void run ( )
{
routineAtAlarm ( ) ;
}
} ;
if ( timer ! = null )
{
timer . cancel ( ) ;
timer . purge ( ) ;
}
timer = new Timer ( ) ;
2023-12-29 19:43:50 +01:00
}
if ( nextWakeup = = null )
{
readCalendarEvents ( Miscellaneous . getAnyContext ( ) , false ) ;
calculateNextWakeup ( ) ;
}
// If it's now filled, go on
2024-01-01 13:35:21 +01:00
if ( nextWakeup ! = null & & wakeupNeedsToBeScheduled )
2023-12-30 23:26:27 +01:00
{
if ( Build . VERSION . SDK_INT > = Build . VERSION_CODES . M )
{
if ( Build . VERSION . SDK_INT < Build . VERSION_CODES . S | | ( Build . VERSION . SDK_INT > = Build . VERSION_CODES . S & & alarmManager . canScheduleExactAlarms ( ) ) )
{
try
{
alarmManager . cancel ( pi ) ;
}
catch ( Exception e )
{
}
2024-01-01 13:35:21 +01:00
Miscellaneous . logEvent ( " i " , " armOrRearmTimer() " , " Scheduling wakeup for calendar at " + Miscellaneous . formatDate ( nextWakeup . getTime ( ) ) , 5 ) ;
2023-12-31 15:54:24 +01:00
alarmManager . setExactAndAllowWhileIdle ( AlarmManager . RTC_WAKEUP , nextWakeup . getTimeInMillis ( ) , pi ) ;
2024-01-01 13:35:21 +01:00
wakeupNeedsToBeScheduled = false ;
2023-12-30 23:26:27 +01:00
}
}
else
timer . schedule ( timerTask , nextWakeup . getTimeInMillis ( ) ) ;
}
2023-12-29 19:43:50 +01:00
}
private static void calculateNextWakeup ( )
{
Calendar now = Calendar . getInstance ( ) ;
List < CalendarEvent > events = readCalendarEvents ( Miscellaneous . getAnyContext ( ) , false ) ;
2024-01-01 13:35:21 +01:00
if ( events . size ( ) = = 0 )
2023-12-29 19:43:50 +01:00
{
2024-01-01 13:35:21 +01:00
Miscellaneous . logEvent ( " i " , " calculateNextWakeup() " , " No future events, nothing to schedule. " , 5 ) ;
}
else
{
List < Rule > ruleCandidates = Rule . findRuleCandidates ( Trigger . Trigger_Enum . calendarEvent ) ;
List < Long > wakeUpCandidatesList = new ArrayList < > ( ) ;
2023-12-29 19:43:50 +01:00
for ( CalendarEvent event : events )
{
2024-01-01 13:35:21 +01:00
for ( Rule r : ruleCandidates )
2023-12-29 19:43:50 +01:00
{
2024-01-01 13:35:21 +01:00
for ( Trigger t : r . getTriggerSet ( ) )
2023-12-29 19:43:50 +01:00
{
2024-01-01 13:35:21 +01:00
if ( t . getTriggerType ( ) . equals ( Trigger . Trigger_Enum . calendarEvent ) & & t . checkCalendarEvent ( event , true ) )
{
/ *
Device needs to wakeup at start AND end of events , no matter what is specified in triggers .
This is because we also need to know when a trigger doesn ' t apply anymore to make it
count for hasStateNotAppliedSinceLastRuleExecution ( ) .
Otherwise the same rule would not get executed again even after calendar events have come and gone .
* /
if ( event . start . getTimeInMillis ( ) > now . getTimeInMillis ( ) )
wakeUpCandidatesList . add ( event . start . getTimeInMillis ( ) ) ;
if ( event . end . getTimeInMillis ( ) > now . getTimeInMillis ( ) )
wakeUpCandidatesList . add ( event . end . getTimeInMillis ( ) ) ;
}
2023-12-29 19:43:50 +01:00
}
}
2024-01-01 13:35:21 +01:00
}
Collections . sort ( wakeUpCandidatesList ) ;
if ( nextWakeup = = null | | nextWakeup . getTimeInMillis ( ) ! = wakeUpCandidatesList . get ( 0 ) )
{
Calendar newAlarm = Miscellaneous . calendarFromLong ( wakeUpCandidatesList . get ( 0 ) ) ;
if ( nextWakeup = = null )
Miscellaneous . logEvent ( " i " , " calculateNextWakeupForCalendar() " , " Chose " + Miscellaneous . formatDate ( newAlarm . getTime ( ) ) + " as next wakeup for calendar triggers. Old was null. " , 5 ) ;
2023-12-29 19:43:50 +01:00
else
2024-01-01 13:35:21 +01:00
Miscellaneous . logEvent ( " i " , " calculateNextWakeupForCalendar() " , " Chose " + Miscellaneous . formatDate ( newAlarm . getTime ( ) ) + " as next wakeup for calendar triggers. Old was " + Miscellaneous . formatDate ( nextWakeup . getTime ( ) ) , 5 ) ;
nextWakeup = newAlarm ;
if ( ! wakeupNeedsToBeScheduled )
wakeupNeedsToBeScheduled = true ;
2023-12-29 19:43:50 +01:00
}
2024-01-01 13:35:21 +01:00
else
Miscellaneous . logEvent ( " i " , " calculateNextWakeupForCalendar() " , " Alarm " + Miscellaneous . formatDate ( nextWakeup . getTime ( ) ) + " has been selected as next wakeup, but not rescheduling since this was not a change. " , 5 ) ;
2023-12-29 19:43:50 +01:00
}
}
2023-12-25 11:57:55 +01:00
public static void startCalendarReceiver ( final AutomationService automationServiceRef )
{
if ( ! calendarReceiverActive )
{
CalendarReceiver . automationServiceRef = automationServiceRef ;
if ( calendarReceiverInstance = = null )
calendarReceiverInstance = new CalendarReceiver ( ) ;
if ( calendarIntentFilter = = null )
{
calendarIntentFilter = new IntentFilter ( ) ;
calendarIntentFilter . addAction ( Intent . ACTION_PROVIDER_CHANGED ) ;
2023-12-29 19:43:50 +01:00
calendarIntentFilter . addDataScheme ( " content " ) ;
calendarIntentFilter . addDataAuthority ( " com.android.calendar " , null ) ;
2023-12-25 11:57:55 +01:00
}
calendarIntent = automationServiceRef . registerReceiver ( calendarReceiverInstance , calendarIntentFilter ) ;
calendarReceiverActive = true ;
2023-12-29 19:43:50 +01:00
armOrRearmTimer ( ) ;
2023-12-25 11:57:55 +01:00
}
}
2023-12-23 13:55:02 +01:00
}