diff --git a/app/src/apkFlavor/AndroidManifest.xml b/app/src/apkFlavor/AndroidManifest.xml
index 2357f46..84b71d8 100644
--- a/app/src/apkFlavor/AndroidManifest.xml
+++ b/app/src/apkFlavor/AndroidManifest.xml
@@ -71,9 +71,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
\ No newline at end of file
diff --git a/app/src/main/java/com/jens/automation2/Action.java b/app/src/main/java/com/jens/automation2/Action.java
index 718b8d9..b152f18 100644
--- a/app/src/main/java/com/jens/automation2/Action.java
+++ b/app/src/main/java/com/jens/automation2/Action.java
@@ -144,7 +144,7 @@ public class Action
case takeScreenshot:
return context.getResources().getString(R.string.takeScreenshot);
case setLocationService:
- return context.getResources().getString(R.string.setLocationService);
+ return context.getResources().getString(R.string.setLocationServiceCapital);
default:
return "Unknown";
}
diff --git a/app/src/main/java/com/jens/automation2/ActivityManageRule.java b/app/src/main/java/com/jens/automation2/ActivityManageRule.java
index f07971a..6111896 100644
--- a/app/src/main/java/com/jens/automation2/ActivityManageRule.java
+++ b/app/src/main/java/com/jens/automation2/ActivityManageRule.java
@@ -143,6 +143,8 @@ public class ActivityManageRule extends Activity
final static int requestCodeActionCopyTextToClipboardEdit = 830;
final static int requestCodeActionSetLocationServiceAdd = 831;
final static int requestCodeActionSetLocationServiceEdit = 832;
+ final static int requestCodeTriggerCalendarEventAdd = 833;
+ final static int requestCodeTriggerCalendarEventEdit = 834;
public static ActivityManageRule getInstance()
{
@@ -347,6 +349,12 @@ public class ActivityManageRule extends Activity
variableStateEditor.putExtra(intentNameTriggerParameter2, selectedTrigger.getTriggerParameter2());
startActivityForResult(variableStateEditor, requestCodeTriggerCheckVariableEdit);
break;
+ case calendarEvent:
+ Intent calendarStateEditor = new Intent(ActivityManageRule.this, ActivityManageTriggerCalendar.class);
+ calendarStateEditor.putExtra(intentNameTriggerParameter1, selectedTrigger.getTriggerParameter());
+ calendarStateEditor.putExtra(intentNameTriggerParameter2, selectedTrigger.getTriggerParameter2());
+ startActivityForResult(calendarStateEditor, requestCodeTriggerCalendarEventEdit);
+ break;
default:
break;
}
@@ -636,6 +644,10 @@ public class ActivityManageRule extends Activity
items.add(new Item(typesLong[i].toString(), R.drawable.router));
else if(types[i].toString().equals(Trigger_Enum.subSystemState.toString()))
items.add(new Item(typesLong[i].toString(), R.drawable.subsystemstate));
+ else if(types[i].toString().equals(Trigger_Enum.checkVariable.toString()))
+ items.add(new Item(typesLong[i].toString(), R.drawable.variable));
+ else if(types[i].toString().equals(Trigger_Enum.calendarEvent.toString()))
+ items.add(new Item(typesLong[i].toString(), R.drawable.calendar));
else
items.add(new Item(typesLong[i].toString(), R.drawable.placeholder));
}
@@ -644,15 +656,15 @@ public class ActivityManageRule extends Activity
{
public View getView(int position, View convertView, ViewGroup parent)
{
- //User super class to create the View
+ // User super class to create the View
View v = super.getView(position, convertView, parent);
TextView tv = (TextView)v.findViewById(android.R.id.text1);
- //Put the image on the TextView
+ // Put the image on the TextView
tv.setCompoundDrawablesWithIntrinsicBounds(items.get(position).icon, 0, 0, 0);
- //Add margin between image and text (support various screen densities)
+ // Add margin between image and text (support various screen densities)
int dp5 = (int) (5 * getResources().getDisplayMetrics().density + 0.5f);
tv.setCompoundDrawablePadding(dp5);
@@ -861,6 +873,13 @@ public class ActivityManageRule extends Activity
startActivityForResult(variableTriggerEditor, requestCodeTriggerCheckVariableAdd);
return;
}
+ else if(triggerType == Trigger_Enum.calendarEvent)
+ {
+ newTrigger.setTriggerType(Trigger_Enum.calendarEvent);
+ Intent calendarTriggerEditor = new Intent(myContext, ActivityManageTriggerCalendar.class);
+ startActivityForResult(calendarTriggerEditor, requestCodeTriggerCalendarEventAdd);
+ return;
+ }
else
getTriggerParameterDialog(context, booleanChoices).show();
@@ -1994,6 +2013,17 @@ public class ActivityManageRule extends Activity
this.refreshTriggerList();
}
}
+ else if(requestCode == requestCodeTriggerCalendarEventAdd)
+ {
+ if(resultCode == RESULT_OK)
+ {
+ newTrigger.setTriggerParameter(data.getBooleanExtra(intentNameTriggerParameter1, true));
+ newTrigger.setTriggerParameter2(data.getStringExtra(intentNameTriggerParameter2));
+ newTrigger.setParentRule(ruleToEdit);
+ ruleToEdit.getTriggerSet().add(newTrigger);
+ this.refreshTriggerList();
+ }
+ }
else if(requestCode == requestCodeTriggerTetheringEdit)
{
if(resultCode == RESULT_OK)
@@ -2033,6 +2063,19 @@ public class ActivityManageRule extends Activity
this.refreshTriggerList();
}
}
+ else if(requestCode == requestCodeTriggerCalendarEventEdit)
+ {
+ if(resultCode == RESULT_OK)
+ {
+ Trigger editedTrigger = new Trigger();
+ editedTrigger.setTriggerType(Trigger_Enum.calendarEvent);
+ editedTrigger.setTriggerParameter(data.getBooleanExtra(intentNameTriggerParameter1, true));
+ editedTrigger.setTriggerParameter2(data.getStringExtra(intentNameTriggerParameter2));
+ editedTrigger.setParentRule(ruleToEdit);
+ ruleToEdit.getTriggerSet().set(editIndex, editedTrigger);
+ this.refreshTriggerList();
+ }
+ }
else if(requestCode == requestCodeActionCopyTextToClipboardAdd)
{
if(resultCode == RESULT_OK)
@@ -2162,6 +2205,8 @@ public class ActivityManageRule extends Activity
items.add(new Item(typesLong[i].toString(), R.drawable.clipboard));
else if(types[i].toString().equals(Action_Enum.takeScreenshot.toString()))
items.add(new Item(typesLong[i].toString(), R.drawable.copier));
+ else if(types[i].toString().equals(Action_Enum.setVariable.toString()))
+ items.add(new Item(typesLong[i].toString(), R.drawable.variable));
else if(types[i].toString().equals(Action_Enum.setLocationService.toString()))
items.add(new Item(typesLong[i].toString(), R.drawable.compass_small));
else
diff --git a/app/src/main/java/com/jens/automation2/ActivityManageTriggerCalendar.java b/app/src/main/java/com/jens/automation2/ActivityManageTriggerCalendar.java
new file mode 100644
index 0000000..2539a9a
--- /dev/null
+++ b/app/src/main/java/com/jens/automation2/ActivityManageTriggerCalendar.java
@@ -0,0 +1,349 @@
+package com.jens.automation2;
+
+import android.Manifest;
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.Build;
+import android.os.Bundle;
+import android.provider.CalendarContract;
+import android.view.View;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.CheckBox;
+import android.widget.CompoundButton;
+import android.widget.EditText;
+import android.widget.LinearLayout;
+import android.widget.Spinner;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.jens.automation2.receivers.CalendarReceiver;
+
+import org.apache.commons.lang3.StringUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class ActivityManageTriggerCalendar extends Activity
+{
+ CheckBox chkCalendarEventActive, chkCalendarAvailabilityBusy, chkCalendarAvailabilityFree, chkCalendarAvailabilityTentative, chkCalendarAvailabilityOutOfOffice, chkCalendarAvailabilityWorkingElsewhere, chkCalendarAllDayEvent;
+ Spinner spinnerCalendarTitleDirection, spinnerCalendarLocationDirection, spinnerCalendarDescriptionDirection;
+ EditText etCalendarTitle, etCalendarLocation, etCalendarDescription;
+ LinearLayout llCalendarSelection;
+ Button bSaveTriggerCalendar;
+ List checkboxesCalendars = new ArrayList<>();
+ final static String separator = ",";
+ TextView tvMissingCalendarHint;
+
+ private static String[] directions;
+ ArrayAdapter directionSpinnerAdapter;
+ public static int requestCodePermissionReadCalendar = 815;
+
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState)
+ {
+ super.onCreate(savedInstanceState);
+ Miscellaneous.setDisplayLanguage(this);
+ setContentView(R.layout.activity_manage_trigger_calendar);
+
+ chkCalendarEventActive = (CheckBox) findViewById(R.id.chkCalendarEventActive);
+ spinnerCalendarTitleDirection = (Spinner)findViewById(R.id.spinnerCalendarTitleDirection);
+ spinnerCalendarLocationDirection = (Spinner)findViewById(R.id.spinnerCalendarLocationDirection);
+ spinnerCalendarDescriptionDirection = (Spinner)findViewById(R.id.spinnerCalendarDescriptionDirection);
+ chkCalendarAllDayEvent = (CheckBox)findViewById(R.id.chkCalendarAllDayEvent);
+ chkCalendarAvailabilityBusy = (CheckBox)findViewById(R.id.chkCalendarAvailabilityBusy);
+ chkCalendarAvailabilityFree = (CheckBox)findViewById(R.id.chkCalendarAvailabilityFree);
+ chkCalendarAvailabilityTentative = (CheckBox)findViewById(R.id.chkCalendarAvailabilityTentative);
+ chkCalendarAvailabilityOutOfOffice = (CheckBox)findViewById(R.id.chkCalendarAvailabilityOutOfOffice);
+ chkCalendarAvailabilityWorkingElsewhere = (CheckBox)findViewById(R.id.chkCalendarAvailabilityWorkingElsewhere);
+
+ tvMissingCalendarHint = (TextView) findViewById(R.id.tvMissingCalendarHint);
+
+ llCalendarSelection = (LinearLayout)findViewById(R.id.llCalendarSelection);
+
+ etCalendarTitle = (EditText)findViewById(R.id.etCalendarTitle);
+ etCalendarLocation = (EditText)findViewById(R.id.etCalendarLocation);
+ etCalendarDescription = (EditText)findViewById(R.id.etCalendarDescription);
+
+ bSaveTriggerCalendar = (Button)findViewById(R.id.bSaveTriggerCalendar);
+
+ directions = new String[] {
+ getResources().getString(R.string.directionStringEquals),
+ getResources().getString(R.string.directionStringContains),
+ getResources().getString(R.string.directionStringDoesNotContain),
+ getResources().getString(R.string.directionStringStartsWith),
+ getResources().getString(R.string.directionStringEndsWith),
+ getResources().getString(R.string.directionStringNotEquals)
+ };
+ directionSpinnerAdapter = new ArrayAdapter(this, R.layout.text_view_for_poi_listview_mediumtextsize, ActivityManageTriggerCalendar.directions);
+ spinnerCalendarTitleDirection.setAdapter(directionSpinnerAdapter);
+ spinnerCalendarLocationDirection.setAdapter(directionSpinnerAdapter);
+ spinnerCalendarDescriptionDirection.setAdapter(directionSpinnerAdapter);
+ directionSpinnerAdapter.notifyDataSetChanged();
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
+ {
+ if(ActivityPermissions.havePermission(Manifest.permission.READ_CALENDAR, ActivityManageTriggerCalendar.this) || ActivityPermissions.havePermission(Manifest.permission.WRITE_CALENDAR, ActivityManageTriggerCalendar.this))
+ populateCalenderCheckboxes();
+ else
+ {
+ AlertDialog.Builder builder = new AlertDialog.Builder(ActivityManageTriggerCalendar.this);
+ builder.setTitle(getResources().getString(R.string.info));
+ builder.setMessage(getResources().getString(R.string.permissionCalendarRequired));
+ builder.setNegativeButton(getResources().getString(R.string.cancel), new DialogInterface.OnClickListener()
+ {
+ @Override
+ public void onClick(DialogInterface dialogInterface, int i)
+ {
+ ActivityManageTriggerCalendar.this.finish();
+ }
+ });
+ builder.setPositiveButton(getResources().getString(R.string.ok), new DialogInterface.OnClickListener()
+ {
+ @Override
+ public void onClick(DialogInterface dialogInterface, int i)
+ {
+ requestPermissions(new String[]{ Manifest.permission.READ_CALENDAR } , requestCodePermissionReadCalendar);
+ }
+ });
+ builder.show();
+ }
+ }
+ else
+ populateCalenderCheckboxes();
+
+ chkCalendarEventActive.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener()
+ {
+ @Override
+ public void onCheckedChanged(CompoundButton compoundButton, boolean checked)
+ {
+ if(checked)
+ chkCalendarEventActive.setText(R.string.eventIsCurrentlyHappening);
+ else
+ chkCalendarEventActive.setText(R.string.eventIsCurrentlyNotHappening);
+ }
+ });
+
+ chkCalendarAllDayEvent.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener()
+ {
+ @Override
+ public void onCheckedChanged(CompoundButton compoundButton, boolean checked)
+ {
+ if(checked)
+ chkCalendarAllDayEvent.setText(getResources().getString(R.string.allDayEventTrue));
+ else
+ chkCalendarAllDayEvent.setText(getResources().getString(R.string.allDayEventFalse));
+ }
+ });
+
+ bSaveTriggerCalendar.setOnClickListener(new View.OnClickListener()
+ {
+ @Override
+ public void onClick(View view)
+ {
+ String titleDir = Trigger.getMatchCode(spinnerCalendarTitleDirection.getSelectedItem().toString());
+ String title = etCalendarTitle.getText().toString();
+ String descriptionDir = Trigger.getMatchCode(spinnerCalendarDescriptionDirection.getSelectedItem().toString());
+ String description = etCalendarDescription.getText().toString();
+ String locationDir = Trigger.getMatchCode(spinnerCalendarLocationDirection.getSelectedItem().toString());
+ String location = etCalendarLocation.getText().toString();
+
+ List availabilityList = new ArrayList<>();
+ if(chkCalendarAvailabilityBusy.isChecked())
+ availabilityList.add(String.valueOf(CalendarContract.Events.AVAILABILITY_BUSY));
+
+ if(chkCalendarAvailabilityFree.isChecked())
+ availabilityList.add(String.valueOf(CalendarContract.Events.AVAILABILITY_FREE));
+
+ if(chkCalendarAvailabilityTentative.isChecked())
+ availabilityList.add(String.valueOf(CalendarContract.Events.AVAILABILITY_TENTATIVE));
+
+ if(chkCalendarAvailabilityOutOfOffice.isChecked())
+ availabilityList.add(String.valueOf(CalendarReceiver.AVAILABILITY_OUT_OF_OFFICE));
+
+ if(chkCalendarAvailabilityWorkingElsewhere.isChecked())
+ availabilityList.add(String.valueOf(CalendarReceiver.AVAILABILITY_WORKING_ELSEWHERE));
+
+ List selectedCalendarsList = new ArrayList<>();
+ for(CheckBox calCheckbox : checkboxesCalendars)
+ {
+ if(calCheckbox.isChecked())
+ selectedCalendarsList.add((CalendarReceiver.AndroidCalendar) calCheckbox.getTag());
+ }
+ List selectedCalendarsIdArray = new ArrayList<>();
+ for(CalendarReceiver.AndroidCalendar cal : selectedCalendarsList)
+ selectedCalendarsIdArray.add(String.valueOf(cal.calendarId));
+
+ String returnString =
+ titleDir + Trigger.triggerParameter2Split + title + Trigger.triggerParameter2Split +
+ descriptionDir + Trigger.triggerParameter2Split + description + Trigger.triggerParameter2Split +
+ locationDir + Trigger.triggerParameter2Split + location + Trigger.triggerParameter2Split +
+ String.valueOf(chkCalendarAllDayEvent.isChecked()) + Trigger.triggerParameter2Split +
+ Miscellaneous.explode(separator, availabilityList.toArray(new String[availabilityList.size()])) + Trigger.triggerParameter2Split +
+ Miscellaneous.explode(separator, selectedCalendarsIdArray.toArray(new String[selectedCalendarsIdArray.size()]));
+
+ Intent data = new Intent();
+ data.putExtra(ActivityManageRule.intentNameTriggerParameter1, chkCalendarEventActive.isChecked());
+ data.putExtra(ActivityManageRule.intentNameTriggerParameter2, returnString);
+ ActivityManageTriggerCalendar.this.setResult(RESULT_OK, data);
+
+ finish();
+ }
+ });
+
+ Intent inputIntent = getIntent();
+ if(inputIntent.hasExtra(ActivityManageRule.intentNameTriggerParameter1))
+ loadValuesIntoGui(inputIntent);
+ }
+
+ private void populateCalenderCheckboxes()
+ {
+ List calList = CalendarReceiver.readCalendars(ActivityManageTriggerCalendar.this);
+
+ if(calList != null)
+ {
+ if(calList.size() > 0)
+ {
+ for (CalendarReceiver.AndroidCalendar cal : calList)
+ {
+ CheckBox oneCalCheckbox = new CheckBox(ActivityManageTriggerCalendar.this);
+ oneCalCheckbox.setText(cal.toString());
+ oneCalCheckbox.setTag(cal);
+ llCalendarSelection.addView(oneCalCheckbox);
+ checkboxesCalendars.add(oneCalCheckbox);
+ }
+ }
+ else
+ Miscellaneous.messageBox(getResources().getString(R.string.warning), getResources().getString(R.string.noCalendarsOnYourDevice), ActivityManageTriggerCalendar.this).show();
+ }
+ else
+ Miscellaneous.messageBox(getResources().getString(R.string.warning), getResources().getString(R.string.errorReadingCalendars), ActivityManageTriggerCalendar.this).show();
+ }
+
+ void loadValuesIntoGui(Intent data)
+ {
+ //TODO:try-catch
+
+ if(data.hasExtra(ActivityManageRule.intentNameTriggerParameter1))
+ chkCalendarEventActive.setChecked(data.getBooleanExtra(ActivityManageRule.intentNameTriggerParameter1, true));
+
+ if(data.hasExtra(ActivityManageRule.intentNameTriggerParameter2))
+ {
+ String input[] = data.getStringExtra(ActivityManageRule.intentNameTriggerParameter2).split(Trigger.triggerParameter2Split, -1);
+ /*
+ 0 = titleDir
+ 1 = title
+ 2 = descriptionDir
+ 3 = description
+ 4 = locationDir
+ 5 = location
+ 6 = all day event
+ 7 = availability list
+ 8 = calendars list
+ */
+
+ for(int i = 0; i < directions.length; i++)
+ {
+ if(Trigger.getMatchCode(directions[i]).equalsIgnoreCase(input[0]))
+ spinnerCalendarTitleDirection.setSelection(i);
+
+ if(Trigger.getMatchCode(directions[i]).equalsIgnoreCase(input[2]))
+ spinnerCalendarDescriptionDirection.setSelection(i);
+
+ if(Trigger.getMatchCode(directions[i]).equalsIgnoreCase(input[4]))
+ spinnerCalendarLocationDirection.setSelection(i);
+ }
+
+ etCalendarTitle.setText(input[1]);
+ etCalendarDescription.setText(input[3]);
+ etCalendarLocation.setText(input[5]);
+
+ chkCalendarAllDayEvent.setChecked(Boolean.parseBoolean(input[6]));
+
+ String[] availabilities = null;
+ if(!StringUtils.isEmpty(input[7]))
+ availabilities = input[7].split(separator);
+
+ if(availabilities != null)
+ {
+ for (String avail : availabilities)
+ {
+ if (Integer.parseInt(avail) == CalendarContract.Events.AVAILABILITY_BUSY)
+ chkCalendarAvailabilityBusy.setChecked(true);
+ else if (Integer.parseInt(avail) == CalendarContract.Events.AVAILABILITY_FREE)
+ chkCalendarAvailabilityFree.setChecked(true);
+ else if (Integer.parseInt(avail) == CalendarContract.Events.AVAILABILITY_TENTATIVE)
+ chkCalendarAvailabilityTentative.setChecked(true);
+ else if (Integer.parseInt(avail) == CalendarReceiver.AVAILABILITY_OUT_OF_OFFICE)
+ chkCalendarAvailabilityOutOfOffice.setChecked(true);
+ else if (Integer.parseInt(avail) == CalendarReceiver.AVAILABILITY_WORKING_ELSEWHERE)
+ chkCalendarAvailabilityWorkingElsewhere.setChecked(true);
+ }
+ }
+
+ String[] calendars = null;
+ if(!StringUtils.isEmpty(input[8]))
+ calendars = input[8].split(separator);
+
+ if(calendars != null)
+ {
+ List usedCalendarIDs = new ArrayList<>();
+ List unusedCalendarIDs = new ArrayList<>();
+ for (CheckBox checkbox : checkboxesCalendars)
+ {
+ int id = ((CalendarReceiver.AndroidCalendar) checkbox.getTag()).calendarId;
+ for (String calId : calendars)
+ {
+ if (calId.equals(String.valueOf(id)))
+ {
+ usedCalendarIDs.add(String.valueOf(id));
+ checkbox.setChecked(true);
+ break;
+ }
+ }
+ }
+ for (String calId : calendars)
+ {
+ if (!Miscellaneous.arraySearch((ArrayList) usedCalendarIDs, calId, false, true))
+ unusedCalendarIDs.add(calId);
+ }
+ if (unusedCalendarIDs.size() > 0)
+ {
+ /*
+ A calendar has been configured that has been deleted since. We cannot resolve it.
+ It will be removed with the next save, but we should inform this user
+ of these circumstances.
+ */
+
+ tvMissingCalendarHint.setText(String.format(getResources().getString(R.string.calendarsMissingHint), Miscellaneous.explode(", ", (ArrayList) unusedCalendarIDs)));
+ }
+ }
+ }
+ }
+
+ @Override
+ public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults)
+ {
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults);
+
+ if(requestCode == requestCodePermissionReadCalendar)
+ {
+ if(
+ permissions[0].equals(Manifest.permission.READ_CALENDAR)
+ ||
+ permissions[0].equals(Manifest.permission.WRITE_CALENDAR)
+ )
+ {
+ if(grantResults[0] == PackageManager.PERMISSION_GRANTED)
+ populateCalenderCheckboxes();
+ else
+ finish();
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/jens/automation2/ActivityPermissions.java b/app/src/main/java/com/jens/automation2/ActivityPermissions.java
index de627e3..221bca6 100644
--- a/app/src/main/java/com/jens/automation2/ActivityPermissions.java
+++ b/app/src/main/java/com/jens/automation2/ActivityPermissions.java
@@ -54,6 +54,7 @@ public class ActivityPermissions extends Activity
private static final int requestCodeForPermissionNotificationAccessAndroid13 = 12049;
private static final int requestCodeForPermissionsManageOverlay = 12050;
private static final int requestCodeForPermissionsAccessibility = 12051;
+ private static final int requestCodeForPermissionsScheduleExactAlarms = 12052;
protected String[] specificPermissionsToRequest = null;
public static String intentExtraName = "permissionsToBeRequested";
@@ -558,6 +559,8 @@ public class ActivityPermissions extends Activity
addToArrayListUnique(Manifest.permission.INTERNET, requiredPermissions);
break;
case timeFrame:
+ if(Build.VERSION.SDK_INT >= 31 && Miscellaneous.getTargetSDK(Miscellaneous.getAnyContext()) >= 31)
+ addToArrayListUnique(Manifest.permission.SCHEDULE_EXACT_ALARM, requiredPermissions);
break;
case usb_host_connection:
addToArrayListUnique(Manifest.permission.READ_PHONE_STATE, requiredPermissions);
@@ -580,6 +583,11 @@ public class ActivityPermissions extends Activity
case notification:
addToArrayListUnique(Manifest.permission.BIND_NOTIFICATION_LISTENER_SERVICE, requiredPermissions);
break;
+ case calendarEvent:
+ addToArrayListUnique(Manifest.permission.READ_CALENDAR, requiredPermissions);
+ if(Build.VERSION.SDK_INT >= 31 && Miscellaneous.getTargetSDK(Miscellaneous.getAnyContext()) >= 31)
+ addToArrayListUnique(Manifest.permission.SCHEDULE_EXACT_ALARM, requiredPermissions);
+ break;
default:
break;
}
@@ -828,6 +836,12 @@ public class ActivityPermissions extends Activity
case Manifest.permission.WRITE_EXTERNAL_STORAGE:
usingElements.add(getResources().getString(R.string.storeSettings));
break;
+ case Manifest.permission.SCHEDULE_EXACT_ALARM:
+ for(String ruleName : getRulesUsing(Trigger.Trigger_Enum.timeFrame))
+ usingElements.add(String.format(getResources().getString(R.string.ruleXrequiresThis), ruleName));
+ for(String ruleName : getRulesUsing(Trigger.Trigger_Enum.calendarEvent))
+ usingElements.add(String.format(getResources().getString(R.string.ruleXrequiresThis), ruleName));
+ break;
case Manifest.permission.BIND_NOTIFICATION_LISTENER_SERVICE:
for(String ruleName : getRulesUsing(Trigger.Trigger_Enum.notification))
usingElements.add(String.format(getResources().getString(R.string.ruleXrequiresThis), ruleName));
@@ -1023,6 +1037,10 @@ public class ActivityPermissions extends Activity
for(String ruleName : getRulesUsing(Action.Action_Enum.setLocationService))
usingElements.add(String.format(getResources().getString(R.string.ruleXrequiresThis), ruleName));
break;
+ case Manifest.permission.READ_CALENDAR:
+ for(String ruleName : getRulesUsing(Trigger.Trigger_Enum.calendarEvent))
+ usingElements.add(String.format(getResources().getString(R.string.ruleXrequiresThis), ruleName));
+ break;
}
return usingElements;
@@ -1091,6 +1109,10 @@ public class ActivityPermissions extends Activity
if (requestCode == requestCodeForPermissionsAccessibility)
if(havePermission(Manifest.permission.BIND_ACCESSIBILITY_SERVICE, ActivityPermissions.this))
requestPermissions(cachedPermissionsToRequest, true);
+
+ if (requestCode == requestCodeForPermissionsScheduleExactAlarms)
+ if(havePermission(Manifest.permission.SCHEDULE_EXACT_ALARM, ActivityPermissions.this))
+ requestPermissions(cachedPermissionsToRequest, true);
}
}
@@ -1218,6 +1240,22 @@ public class ActivityPermissions extends Activity
return;
}
+ else if (s.equalsIgnoreCase(Manifest.permission.SCHEDULE_EXACT_ALARM))
+ {
+ AlertDialog diag = Miscellaneous.messageBox(getResources().getString(R.string.info), getResources().getString(R.string.alarmsPermissionHint), ActivityPermissions.this);
+ diag.setOnDismissListener(new DialogInterface.OnDismissListener()
+ {
+ @Override
+ public void onDismiss(DialogInterface dialogInterface)
+ {
+ requiredPermissions.remove(s);
+ cachedPermissionsToRequest = requiredPermissions;
+ requestScheduleExactAlarms();
+ }
+ });
+ diag.show();
+ return;
+ }
else if(s.equals(Manifest.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS))
{
requiredPermissions.remove(s);
@@ -1303,6 +1341,14 @@ public class ActivityPermissions extends Activity
startActivityForResult(intent, requestCodeForPermissionsNotifications);
}
+
+ void requestScheduleExactAlarms()
+ {
+ Intent intent = new Intent(Settings.ACTION_REQUEST_SCHEDULE_EXACT_ALARM);
+ startActivityForResult(intent, requestCodeForPermissionsScheduleExactAlarms);
+ }
+
+
protected void applyChanges()
{
AutomationService service = AutomationService.getInstance();
diff --git a/app/src/main/java/com/jens/automation2/Miscellaneous.java b/app/src/main/java/com/jens/automation2/Miscellaneous.java
index 4da9c91..cc035db 100644
--- a/app/src/main/java/com/jens/automation2/Miscellaneous.java
+++ b/app/src/main/java/com/jens/automation2/Miscellaneous.java
@@ -806,7 +806,7 @@ public class Miscellaneous extends Service
alertDialog.setTitle(title);
alertDialog.setMessage(message);
- alertDialog.setPositiveButton("Ok", new DialogInterface.OnClickListener()
+ alertDialog.setPositiveButton(context.getResources().getString(R.string.ok), new DialogInterface.OnClickListener()
{
public void onClick(DialogInterface dialog, int whichButton)
{
diff --git a/app/src/main/java/com/jens/automation2/ReceiverCoordinator.java b/app/src/main/java/com/jens/automation2/ReceiverCoordinator.java
index 93b0339..65b144d 100644
--- a/app/src/main/java/com/jens/automation2/ReceiverCoordinator.java
+++ b/app/src/main/java/com/jens/automation2/ReceiverCoordinator.java
@@ -6,6 +6,7 @@ import android.util.Log;
import com.jens.automation2.location.CellLocationChangedReceiver;
import com.jens.automation2.location.WifiBroadcastReceiver;
import com.jens.automation2.receivers.BroadcastListener;
+import com.jens.automation2.receivers.CalendarReceiver;
import com.jens.automation2.receivers.DateTimeListener;
import com.jens.automation2.receivers.AutomationListenerInterface;
import com.jens.automation2.receivers.BatteryReceiver;
@@ -210,6 +211,9 @@ public class ReceiverCoordinator
if(Rule.isAnyRuleUsing(Trigger.Trigger_Enum.screenState))
ScreenStateReceiver.startScreenStateReceiver(AutomationService.getInstance());
+
+ if(Rule.isAnyRuleUsing(Trigger.Trigger_Enum.calendarEvent))
+ CalendarReceiver.startCalendarReceiver(AutomationService.getInstance());
}
public static void stopAllReceivers()
@@ -243,6 +247,7 @@ public class ReceiverCoordinator
BluetoothReceiver.stopBluetoothReceiver();
HeadphoneJackListener.getInstance().stopListener(AutomationService.getInstance());
DeviceOrientationListener.getInstance().stopListener(AutomationService.getInstance());
+ CalendarReceiver.getInstance().stopListener(AutomationService.getInstance());
}
catch(Exception e)
{
diff --git a/app/src/main/java/com/jens/automation2/Trigger.java b/app/src/main/java/com/jens/automation2/Trigger.java
index 687356a..1db9c20 100644
--- a/app/src/main/java/com/jens/automation2/Trigger.java
+++ b/app/src/main/java/com/jens/automation2/Trigger.java
@@ -14,6 +14,7 @@ import com.jens.automation2.location.WifiBroadcastReceiver;
import com.jens.automation2.receivers.BatteryReceiver;
import com.jens.automation2.receivers.BluetoothReceiver;
import com.jens.automation2.receivers.BroadcastListener;
+import com.jens.automation2.receivers.CalendarReceiver;
import com.jens.automation2.receivers.ConnectivityReceiver;
import com.jens.automation2.receivers.DeviceOrientationListener;
import com.jens.automation2.receivers.HeadphoneJackListener;
@@ -31,6 +32,7 @@ import org.apache.commons.lang3.StringUtils;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
+import java.util.List;
import java.util.Map;
public class Trigger
@@ -63,6 +65,7 @@ public class Trigger
tethering,
subSystemState,
checkVariable,
+ calendarEvent,
phoneCall; //phoneCall always needs to be at the very end because of Google's shitty so called privacy
public String getFullName(Context context)
@@ -123,6 +126,8 @@ public class Trigger
return context.getResources().getString(R.string.subSystemState);
case checkVariable:
return context.getResources().getString(R.string.checkVariable);
+ case calendarEvent:
+ return context.getResources().getString(R.string.calendarEventCapital);
default:
return "Unknown";
}
@@ -247,11 +252,14 @@ public class Trigger
case subSystemState:
if(!checkSubSystemState())
result = false;
- break;
case checkVariable:
if(!checkVariable())
result = false;
break;
+ case calendarEvent:
+ if(!checkCalendarEvent())
+ result = false;
+ break;
default:
break;
}
@@ -609,6 +617,117 @@ public class Trigger
return false;
}
+ boolean checkCalendarEvent()
+ {
+ try
+ {
+ String[] conditions = this.getTriggerParameter2().split(Trigger.triggerParameter2Split);
+ List calendarEvents = CalendarReceiver.readCalendarEvents(AutomationService.getInstance(), false);
+
+ /*
+ 0 = titleDirection
+ 1 = title;
+ 2 = descriptionDirection
+ 3 = description
+ 4 = eventLocationDirection
+ 5 = eventLocation
+ 6 = all day event
+ 7 = availabilityList
+ 8 = calendarList
+ */
+
+ for(CalendarReceiver.CalendarEvent event : calendarEvents)
+ {
+ boolean isActive = getTriggerParameter();
+ if(isActive != event.isCurrentlyActive())
+ {
+ Miscellaneous.logEvent("i", "CalendarCheck", "Event has to be currently active: " + String.valueOf(triggerParameter) + ", but is required otherwise.", 5);
+ continue;
+ }
+
+ if(!StringUtils.isEmpty(conditions[1]))
+ {
+ if (!Miscellaneous.compare(conditions[0], conditions[1], event.title))
+ {
+ Miscellaneous.logEvent("i", "CalendarCheck", "Title does not match.", 5);
+ continue;
+ }
+ }
+
+ if(!StringUtils.isEmpty(conditions[3]))
+ {
+ if (!Miscellaneous.compare(conditions[2], conditions[3], event.description))
+ {
+ Miscellaneous.logEvent("i", "CalendarCheck", "Description does not match.", 5);
+ continue;
+ }
+ }
+
+ if(!StringUtils.isEmpty(conditions[5]))
+ {
+ if (!Miscellaneous.compare(conditions[4], conditions[5], event.location))
+ {
+ Miscellaneous.logEvent("i", "CalendarCheck", "Location does not match.", 5);
+ continue;
+ }
+ }
+
+ if (Boolean.parseBoolean(conditions[6]) != event.allDay)
+ {
+ Miscellaneous.logEvent("i", "CalendarCheck", "All day setting does not match.", 5);
+ continue;
+ }
+
+ if(!StringUtils.isEmpty(conditions[7]))
+ {
+ String[] availabilities = conditions[7].split(ActivityManageTriggerCalendar.separator);
+ if (availabilities.length > 0)
+ {
+ if (!Miscellaneous.arraySearch(availabilities, event.availability, false, true))
+ {
+ Miscellaneous.logEvent("i", "CalendarCheck", "Availability does not match.", 5);
+ continue;
+ }
+ }
+ }
+
+ if(!StringUtils.isEmpty(conditions[8]))
+ {
+ String[] calendars = conditions[8].split(ActivityManageTriggerCalendar.separator);
+ if (calendars.length > 0)
+ {
+ if (!Miscellaneous.arraySearch(calendars, String.valueOf(event.calendarId), false, true))
+ {
+ Miscellaneous.logEvent("i", "CalendarCheck", "Calendar does not match.", 5);
+ continue;
+ }
+ }
+ }
+
+ // No contradictions found
+ Miscellaneous.logEvent("i", "CalendarCheck", "Event " + event + " matches.", 4);
+ return true;
+ }
+
+ // At this point none of the calendar items matches this trigger
+
+ // If trigger demands no calendar event and there is absolutely no future event, we'll need to check for that.
+ if(calendarEvents.size() == 0)
+ {
+ if(getTriggerParameter() == false)
+ return true;
+
+ // Further criteria don't matter if there are no events to check
+ }
+ }
+ catch(Exception e)
+ {
+ Miscellaneous.logEvent("e", "checkVariable()", Log.getStackTraceString(e), 1);
+ }
+
+ return false;
+ }
+
boolean checkBluetooth()
{
Miscellaneous.logEvent("i", Miscellaneous.getAnyContext().getResources().getString(R.string.ruleCheckOf), String.format("Checking for bluetooth...", this.getParentRule().getName()), 4);
@@ -1813,6 +1932,44 @@ public class Trigger
else
returnString.append(String.format(Miscellaneous.getAnyContext().getResources().getString(R.string.variableCheckStringDeleted), triggerParameter2));
break;
+ case calendarEvent:
+ returnString.append(Miscellaneous.getAnyContext().getResources().getString(R.string.calendarEvent));
+
+ returnString.append(" (");
+
+ if(triggerParameter)
+ returnString.append(Miscellaneous.getAnyContext().getResources().getString(R.string.eventIsCurrentlyHappening));
+ else
+ returnString.append(Miscellaneous.getAnyContext().getResources().getString(R.string.eventIsCurrentlyNotHappening));
+
+ returnString.append( ", ");
+
+ String[] conditions = triggerParameter2.split(triggerParameter2Split, -1);
+
+ if (!StringUtils.isEmpty(conditions[1]))
+ returnString.append(Miscellaneous.getAnyContext().getResources().getString(R.string.title) + " " + conditions[0] + " " + conditions[1] + ", ");
+ if (!StringUtils.isEmpty(conditions[3]))
+ returnString.append(Miscellaneous.getAnyContext().getResources().getString(R.string.location) + " " + conditions[2] + " " + conditions[3] + ", ");
+ if (!StringUtils.isEmpty(conditions[5]))
+ returnString.append(Miscellaneous.getAnyContext().getResources().getString(R.string.calendarDescription) + " " + conditions[4] + " " + conditions[5] + ", ");
+
+ if(Boolean.parseBoolean(conditions[6]))
+ returnString.append(Miscellaneous.getAnyContext().getResources().getString(R.string.allDayEventTrue) + ", ");
+ else
+ returnString.append(Miscellaneous.getAnyContext().getResources().getString(R.string.allDayEventFalse) + ", ");
+
+ if (!StringUtils.isEmpty(conditions[7]))
+ returnString.append(Miscellaneous.getAnyContext().getResources().getString(R.string.availabilities) + " " + conditions[7] + ", ");
+
+ if (!StringUtils.isEmpty(conditions[8]))
+ returnString.append(Miscellaneous.getAnyContext().getResources().getString(R.string.calendars) + " " + conditions[8]);
+
+ if (returnString.toString().endsWith(", "))
+ returnString.delete(returnString.length() - 2, returnString.length());
+
+ returnString.append(")");
+
+ break;
default:
returnString.append("error");
break;
diff --git a/app/src/main/java/com/jens/automation2/receivers/CalendarReceiver.java b/app/src/main/java/com/jens/automation2/receivers/CalendarReceiver.java
new file mode 100644
index 0000000..654de39
--- /dev/null
+++ b/app/src/main/java/com/jens/automation2/receivers/CalendarReceiver.java
@@ -0,0 +1,412 @@
+package com.jens.automation2.receivers;
+
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Build;
+import android.os.SystemClock;
+
+import androidx.annotation.NonNull;
+
+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;
+import java.util.List;
+import java.util.Timer;
+import java.util.TimerTask;
+
+public class CalendarReceiver extends BroadcastReceiver implements AutomationListenerInterface
+{
+ static CalendarReceiver calendarReceiverInstance = null;
+ static boolean calendarReceiverActive = false;
+ static IntentFilter calendarIntentFilter = null;
+ private static AutomationService automationServiceRef;
+ private static Intent calendarIntent = null;
+
+ public static final int AVAILABILITY_OUT_OF_OFFICE = 4;
+ public static final int AVAILABILITY_WORKING_ELSEWHERE = 5;
+ public static final String calendarAlarmAction = "ALARM_FOR_CALENDAR";
+
+ static List calendarsCache = null;
+ static List calendarEventsCache = null;
+
+ static Timer timer = null;
+ static TimerTask timerTask = null;
+ static Calendar nextWakeup = null;
+ static AlarmManager alarmManager = null;
+ static boolean alarmHasChanged = false;
+
+ public static CalendarReceiver getInstance()
+ {
+ if(calendarReceiverInstance == null)
+ calendarReceiverInstance = new CalendarReceiver();
+
+ return calendarReceiverInstance;
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent)
+ {
+ if(intent.getAction().equalsIgnoreCase(Intent.ACTION_PROVIDER_CHANGED))
+ {
+ Miscellaneous.logEvent("i", "CalendarReceiver", "Received " + intent.getAction(), 5);
+
+ calendarsCache = null;
+ calendarEventsCache = null;
+
+ checkForRules(context);
+ armOrRearmTimer();
+ }
+ else if(intent.getAction().equalsIgnoreCase(calendarAlarmAction))
+ {
+ Miscellaneous.logEvent("i", "AlarmReceiver", "Received alarm for calendar receiver.", 5);
+ routineAtAlarm();
+ }
+ }
+
+ private static void checkForRules(Context context)
+ {
+ ArrayList 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);
+ }
+ }
+
+ @Override
+ public void startListener(AutomationService automationService)
+ {
+ if(!calendarReceiverActive)
+ {
+ if(calendarReceiverInstance == null)
+ calendarReceiverInstance = new CalendarReceiver();
+
+ if(calendarIntentFilter == null)
+ {
+ calendarIntentFilter = new IntentFilter();
+ calendarIntentFilter.addAction(Intent.ACTION_PROVIDER_CHANGED);
+// calendarIntentFilter.addDataScheme("content");
+ }
+
+ 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};
+ }
+
+ public static class AndroidCalendar
+ {
+ public int calendarId;
+ public String displayName;
+ public String accountString;
+
+ @NonNull
+ @Override
+ public String toString()
+ {
+ return displayName + " (" + accountString + ")";
+ }
+ }
+
+ public static class CalendarEvent
+ {
+ public AndroidCalendar calendar;
+ public int calendarId;
+ public String eventId;
+ public String title;
+ public String description;
+ public String location;
+ public String availability;
+ public Calendar start, end;
+ public boolean allDay;
+
+ public boolean isCurrentlyActive()
+ {
+ Calendar now = Calendar.getInstance();
+ return now.getTimeInMillis() >= start.getTimeInMillis() && now.getTimeInMillis() < end.getTimeInMillis();
+ }
+
+ @NonNull
+ @Override
+ public String toString()
+ {
+ return title;
+ }
+ }
+
+ public static List readCalendars(Context context)
+ {
+ if(calendarsCache == null)
+ {
+ calendarsCache = new ArrayList<>();
+
+ Cursor cursor;
+
+ cursor = context.getContentResolver().query(
+ Uri.parse("content://com.android.calendar/calendars"),
+ new String[]{ "_id", "calendar_displayName", "ownerAccount", },
+ null, null, null);
+
+ cursor.moveToFirst();
+ // fetching calendars name
+ String CNames[] = new String[cursor.getCount()];
+
+ List 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);
+ calendar.accountString = cursor.getString(2);
+
+ calendarsCache.add(calendar);
+ }
+ catch (Exception e)
+ {
+ }
+ cursor.moveToNext();
+ }
+
+ if (cursor != null)
+ cursor.close();
+ }
+
+ return calendarsCache;
+ }
+
+ public static List readCalendarEvents(Context context, boolean includePastEvents)
+ {
+ if(calendarEventsCache == null)
+ {
+ calendarEventsCache = new ArrayList<>();
+
+ Cursor cursor;
+
+ 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);
+
+ cursor.moveToFirst();
+ // fetching calendars name
+ String CNames[] = new String[cursor.getCount()];
+
+ Calendar now = Calendar.getInstance();
+
+ for (int i = 0; i < CNames.length; i++)
+ {
+ try
+ {
+ CalendarEvent event = new CalendarEvent();
+ event.calendarId = Integer.parseInt(cursor.getString(0));
+
+ for(AndroidCalendar cal : readCalendars(context))
+ {
+ if(cal.calendarId == event.calendarId)
+ {
+ event.calendar = cal;
+ break;
+ }
+ }
+
+ 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();
+ }
+
+ if(cursor != null)
+ cursor.close();
+
+ }
+
+ return calendarEventsCache;
+ }
+
+ protected static void routineAtAlarm()
+ {
+ checkForRules(Miscellaneous.getAnyContext());
+
+ // Set next timer
+ calculateNextWakeup();
+ armOrRearmTimer();
+ }
+
+ private static void armOrRearmTimer()
+ {
+ PendingIntent pi = null;
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
+ {
+ if (alarmManager == null)
+ {
+ alarmManager = (AlarmManager) Miscellaneous.getAnyContext().getSystemService(Context.ALARM_SERVICE);
+
+ Intent intent = new Intent(Miscellaneous.getAnyContext(), CalendarReceiver.class);
+ intent.setAction(calendarAlarmAction);
+ pi = PendingIntent.getBroadcast(AutomationService.getInstance(), 0, intent, 0);
+ }
+ }
+ else
+ {
+ timerTask = new TimerTask()
+ {
+ @Override
+ public void run()
+ {
+ routineAtAlarm();
+ }
+ };
+
+ if(timer != null)
+ {
+ timer.cancel();
+ timer.purge();
+ }
+ timer = new Timer();
+ }
+
+ if(nextWakeup == null)
+ {
+ readCalendarEvents(Miscellaneous.getAnyContext(), false);
+ calculateNextWakeup();
+ }
+
+ // If it's now filled, go on
+ if(nextWakeup != null)
+ {
+ 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)
+ {
+
+ }
+ Miscellaneous.logEvent("i", "armOrRearmTimer()", "Setting calendar alarm for " + nextWakeup.toString(), 5);
+ alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, nextWakeup.getTimeInMillis(), pi);
+
+ //alarmManager.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP, nextWakeup.getTimeInMillis(), pi);
+ }
+ }
+ else
+ timer.schedule(timerTask, nextWakeup.getTimeInMillis());
+ }
+ }
+
+ private static void calculateNextWakeup()
+ {
+ Calendar now = Calendar.getInstance();
+ if (nextWakeup != null && nextWakeup.getTimeInMillis() < now.getTimeInMillis())
+ nextWakeup = null;
+
+ List events = readCalendarEvents(Miscellaneous.getAnyContext(), false);
+ if (events.size() > 0)
+ {
+ for (CalendarEvent event : events)
+ {
+ if (event.isCurrentlyActive())
+ {
+ if (nextWakeup == null || event.end.getTimeInMillis() < nextWakeup.getTimeInMillis())
+ {
+ nextWakeup = event.end;
+ Miscellaneous.logEvent("i", "calculateNextWakeupForCalendar()", "Choosing end of event " + event.title + " as next wakeup.", 5);
+ if(!alarmHasChanged)
+ alarmHasChanged = true;
+ }
+ }
+ else
+ {
+ if (nextWakeup == null || event.start.getTimeInMillis() < nextWakeup.getTimeInMillis())
+ {
+ nextWakeup = event.start;
+ Miscellaneous.logEvent("i", "calculateNextWakeupForCalendar()", "Choosing start of event " + event.title + " as next wakeup.", 5);
+ if(!alarmHasChanged)
+ alarmHasChanged = true;
+ }
+ }
+ }
+ }
+ //else
+ // we expect to be called byOnReceive() when new items exist
+ }
+
+ 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);
+ calendarIntentFilter.addDataScheme("content");
+ calendarIntentFilter.addDataAuthority("com.android.calendar", null);
+ }
+
+ calendarIntent = automationServiceRef.registerReceiver(calendarReceiverInstance, calendarIntentFilter);
+
+ calendarReceiverActive = true;
+
+ armOrRearmTimer();
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/res/drawable-hdpi/calendar.png b/app/src/main/res/drawable-hdpi/calendar.png
new file mode 100644
index 0000000..f529d02
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/calendar.png differ
diff --git a/app/src/main/res/drawable-hdpi/ear.png b/app/src/main/res/drawable-hdpi/ear.png
index 916b101..36cd243 100644
Binary files a/app/src/main/res/drawable-hdpi/ear.png and b/app/src/main/res/drawable-hdpi/ear.png differ
diff --git a/app/src/main/res/drawable-hdpi/variable.png b/app/src/main/res/drawable-hdpi/variable.png
new file mode 100644
index 0000000..31b83bb
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/variable.png differ
diff --git a/app/src/main/res/layout/activity_manage_trigger_calendar.xml b/app/src/main/res/layout/activity_manage_trigger_calendar.xml
new file mode 100644
index 0000000..dac9a54
--- /dev/null
+++ b/app/src/main/res/layout/activity_manage_trigger_calendar.xml
@@ -0,0 +1,323 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml
index bfa12ed..ca3363a 100644
--- a/app/src/main/res/values-es/strings.xml
+++ b/app/src/main/res/values-es/strings.xml
@@ -765,7 +765,7 @@
Llamar al número de teléfono
Llamar al número de teléfono
Aquí puede ingresar un número de teléfono al que se llamará sin más indicaciones. Puede usar esto para realizar configuraciones como realizar ajustes en el enrutamiento de llamadas, etc. Por favor, busque los códigos necesarios para esto por su cuenta.
- Terminar llamda de teléfono
+ Terminar llamada de teléfono
Terminar llamda de teléfono
La configuración y/o las reglas hacen referencia a funciones que no se pueden proporcionar en la versión de Google Play. Entre otras cosas que incluye todo lo relacionado con llamadas telefónicas y mensajes de texto.
Si la variable %1$s no está establecida
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 1c26769..45a91c9 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -899,10 +899,38 @@
Required for certain actions.
If you fail to grant one the following permission and a system message like \"Restricted permission\" you need to go to Android settings first, then applications, choose Automation. Now there should be 3 dots in the upper right corner. Click \"Allow restricted settings\". After that the necessary permission should be grantable. This should only apply to the APK version of the app, not the ones from F-Droid or Play Store.
set location service
+ Set location service
Unfortunately the permission WRITE_SECURE_SETTINGS cannot be given directly on your Android device. Instead you need to connect your device to a computer and grant the permission via ADB. Click here to find out how to grant it: https://server47.de/automation/adb_hack.php
Location service
SENSOR_ONLY
BATTERY_SAVING
HIGH_ACCURACY
The result of this request will be stored in the variable LAST_TRIGGERURL_RESULT if you wish to check it from another rule. In case of HTTP errors like 404 the value will be \"HTTP_ERROR\".
+ calendar event
+ Event is currently active
+ Calendar event
+ Location
+ Description/notes
+ Availability
+ Event is currently not happening/has ended
+ busy
+ free
+ tentative
+ out of office
+ working elsewhere
+ If you select no item, any one will be ok.
+ Calendars
+ It may be that only the first 3 types are actually working because the other types are not part of Google Calendar.
+ any calender
+ availabilities
+ In this trigger calendars with IDs %1$s have been previously configured, but have been deleted since. With the next save those will be removed from this trigger. Until then this trigger/condition will never be met.
+ account
+ All day event
+ event is an all day event
+ event is not an all day event
+ The permission to read your calendar will be required for a calendar trigger. It will already be required to populate the calendar fields in this window.
+ It appears like no calendars have been set up on your device. You can save this trigger, but it will never return true.
+ There was an error reading the calendars on your device.
+ Schedule exact alarms
+ After clicking OK a window will open. Please select Automation there and allow the app to schedule exact alarms.
\ No newline at end of file