513 lines
18 KiB
Java
Raw Normal View History

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;
2024-01-06 17:25:27 +01:00
import android.util.Log;
2023-12-23 13:55:02 +01:00
2023-12-27 14:48:27 +01:00
import androidx.annotation.NonNull;
2024-01-06 17:25:27 +01:00
import androidx.annotation.Nullable;
2023-12-27 14:48:27 +01:00
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 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
2024-01-06 17:25:27 +01:00
public static class RuleEventPair
{
Rule rule;
CalendarEvent event;
public RuleEventPair(Rule rule, CalendarEvent event)
{
this.rule = rule;
this.event = event;
}
}
static List<RuleEventPair> calendarEventsUsed = new ArrayList<>(); // To determine for which events which rules have been executed
public static void addUsedPair(RuleEventPair pair)
{
// Add pair only if it's not in the list already.
for(RuleEventPair usedPair : calendarEventsUsed)
{
if(usedPair.rule.equals(pair.rule) && usedPair.event.equals(pair.event))
return;
}
calendarEventsUsed.add(pair);
}
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
}
2024-01-06 17:25:27 +01:00
static void checkForRules(Context context)
2023-12-29 19:43:50 +01:00
{
2024-01-06 17:25:27 +01:00
/*
Kann die selbe Regel mehrfach pro Termin ausgeführt werden? Nein, eh nicht, ne?
Am nächsten Tag ist es wieder ein anderer Termin.
Wenn zwei zeitgleiche Termine mit gleichen Inhalten in verschiedenen Kalendern sind,
würde die Regel so 2x ausgeführt werden.
*/
2024-01-06 11:49:49 +01:00
//TODO: Second appointment directly one after another or overlapping won't get executed
2023-12-29 19:43:50 +01:00
ArrayList<Rule> ruleCandidates = Rule.findRuleCandidates(Trigger.Trigger_Enum.calendarEvent);
2024-01-06 11:49:49 +01:00
for (int i = 0; i < ruleCandidates.size(); i++)
2023-12-29 19:43:50 +01:00
{
2024-01-06 11:49:49 +01:00
if (ruleCandidates.get(i).getsGreenLight(context))
2023-12-29 19:43:50 +01:00
ruleCandidates.get(i).activate(AutomationService.getInstance(), false);
2023-12-23 13:55:02 +01:00
}
}
@Override
2024-01-06 17:25:27 +01:00
public void startListener(AutomationService automationServiceRef)
2023-12-23 13:55:02 +01:00
{
2024-01-06 17:25:27 +01:00
startCalendarReceiver(automationServiceRef);
2023-12-23 13:55:02 +01:00
}
@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;
}
2024-01-06 17:25:27 +01:00
@Override
public boolean equals(@Nullable Object obj)
{
try
{
CalendarEvent compareEvent = (CalendarEvent) obj;
return calendarId == compareEvent.calendarId
&&
eventId.equals(compareEvent.eventId)
&&
title.equals(compareEvent.title)
&&
description.equals(compareEvent.description)
&&
location.equals(compareEvent.location)
&&
availability.equals(compareEvent.availability)
&&
start.getTimeInMillis() == compareEvent.start.getTimeInMillis()
&&
end.getTimeInMillis() == compareEvent.end.getTimeInMillis()
&&
allDay == compareEvent.allDay;
}
catch (Exception e)
{
Miscellaneous.logEvent("e", "CalendarReceiver compare()", Log.getStackTraceString(e), 5);
return false;
}
}
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();
}
2024-01-02 16:36:10 +01:00
public static void armOrRearmTimer()
2023-12-29 19:43:50 +01:00
{
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-06 11:49:49 +01:00
Miscellaneous.logEvent("i", "armOrRearmTimer()", "Scheduling wakeup for calendar at " + Miscellaneous.formatDate(nextWakeup.getTime()), 4);
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);
2024-01-02 16:36:10 +01:00
if(wakeUpCandidatesList.size() == 0)
2024-01-06 17:25:27 +01:00
Miscellaneous.logEvent("i", "calculateNextWakeupForCalendar()", "Not scheduling any calendar related wakeup as there are no future events that might match a configured trigger.", 4);
2024-01-02 16:36:10 +01:00
else
2024-01-01 13:35:21 +01:00
{
2024-01-02 16:36:10 +01:00
if (nextWakeup == null || nextWakeup.getTimeInMillis() != wakeUpCandidatesList.get(0))
{
Calendar newAlarm = Miscellaneous.calendarFromLong(wakeUpCandidatesList.get(0));
if (nextWakeup == null)
2024-01-06 17:25:27 +01:00
Miscellaneous.logEvent("i", "calculateNextWakeupForCalendar()", "Chose " + Miscellaneous.formatDate(newAlarm.getTime()) + " as next wakeup for calendar triggers. Old was null.", 4);
2024-01-02 16:36:10 +01:00
else
2024-01-06 17:25:27 +01:00
Miscellaneous.logEvent("i", "calculateNextWakeupForCalendar()", "Chose " + Miscellaneous.formatDate(newAlarm.getTime()) + " as next wakeup for calendar triggers. Old was " + Miscellaneous.formatDate(nextWakeup.getTime()), 4);
2024-01-02 16:36:10 +01:00
nextWakeup = newAlarm;
if (!wakeupNeedsToBeScheduled)
wakeupNeedsToBeScheduled = true;
}
2023-12-29 19:43:50 +01:00
else
2024-01-06 17:25:27 +01:00
Miscellaneous.logEvent("i", "calculateNextWakeupForCalendar()", "Alarm " + Miscellaneous.formatDate(nextWakeup.getTime()) + " has been selected as next wakeup, but not rescheduling since this was not a change.", 4);
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)
{
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
}
}
2024-01-06 17:25:27 +01:00
public static boolean mayRuleStillBeActivatedForPendingCalendarEvents(Rule rule)
{
for(CalendarEvent event : readCalendarEvents(Miscellaneous.getAnyContext(), false))
{
for(Trigger t : rule.getTriggerSet())
{
if(t.getTriggerType().equals(Trigger.Trigger_Enum.calendarEvent) && t.checkCalendarEvent(event, false))
{
if (!hasEventBeenUsedInRule(rule, event))
return true;
}
}
}
return false;
}
static boolean hasEventBeenUsedInRule(Rule rule, CalendarEvent event)
{
for (RuleEventPair executedPair : calendarEventsUsed)
{
if (executedPair.rule.equals(rule) && executedPair.event.equals(event))
return true;
}
return false;
}
2023-12-23 13:55:02 +01:00
}