package com.jens.automation2; import android.annotation.SuppressLint; import android.app.Notification; import android.bluetooth.BluetoothDevice; import android.content.Context; import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; import android.os.Looper; import android.os.Parcelable; import android.service.notification.StatusBarNotification; import android.telephony.TelephonyManager; import android.util.Log; import android.widget.Toast; import com.google.android.gms.location.DetectedActivity; import com.jens.automation2.location.LocationProvider; import com.jens.automation2.location.WifiBroadcastReceiver; import com.jens.automation2.receivers.ActivityDetectionReceiver; import com.jens.automation2.receivers.BatteryReceiver; import com.jens.automation2.receivers.BluetoothReceiver; import com.jens.automation2.receivers.ConnectivityReceiver; import com.jens.automation2.receivers.HeadphoneJackListener; import com.jens.automation2.receivers.NfcReceiver; import com.jens.automation2.receivers.NoiseListener; import com.jens.automation2.receivers.NotificationListener; import com.jens.automation2.receivers.PhoneStatusListener; import com.jens.automation2.receivers.ProcessListener; import java.sql.Time; import java.util.ArrayList; import java.util.Calendar; import java.util.Date; import static com.jens.automation2.Trigger.triggerParameter2Split; import androidx.core.app.NotificationCompat; import org.apache.commons.lang3.StringUtils; public class Rule implements Comparable { private static ArrayList ruleCollection = new ArrayList(); public static boolean isAnyRuleActive = false; private static ArrayList ruleRunHistory = new ArrayList(); public static ArrayList getRuleRunHistory() { return ruleRunHistory; } private ArrayList triggerSet; private ArrayList actionSet; private String name; private boolean ruleActive = true; // rules can be deactivated, so they won't fire if you don't want them temporarily private boolean ruleToggle = false; // rule will run again and do the opposite of its actions if applicable private Calendar lastExecution; private static Date lastActivatedRuleActivationTime; public Calendar getLastExecution() { return lastExecution; } public void setLastExecution(Calendar lastExecution) { this.lastExecution = lastExecution; } public boolean isRuleToggle() { return ruleToggle; } public void setRuleToggle(boolean ruleToggle) { this.ruleToggle = ruleToggle; } public static ArrayList getRuleCollection() { return ruleCollection; } public boolean isRuleActive() { return ruleActive; } public void setRuleActive(boolean ruleActive) { this.ruleActive = ruleActive; } public static void setRuleCollection(ArrayList ruleCollection) { Rule.ruleCollection = ruleCollection; } public static Date getLastActivatedRuleActivationTime() { return lastActivatedRuleActivationTime; } public static Rule getLastActivatedRule() { if(ruleRunHistory.size() > 0) return ruleRunHistory.get(0); else return null; } public ArrayList getTriggerSet() { return triggerSet; } public void setTriggerSet(ArrayList triggerSet) { this.triggerSet = triggerSet; } public ArrayList getActionSet() { return actionSet; } public void setActionSet(ArrayList actionSet) { this.actionSet = actionSet; } public String getName() { return name; } public void setName(String name) { this.name = name; } public static void readFromFile() { ruleCollection = XmlFileInterface.ruleCollection; } @Override public String toString() { return this.getName(); } @SuppressLint("NewApi") public String toStringLong() { String returnString = ""; if(isRuleActive()) returnString += "Active: "; else returnString += "Inactive: "; returnString += this.getName() + ": If "; for(int i=0; i { boolean wasActivated = false; @Override protected Void doInBackground(Object... params) { // Miscellaneous.logEvent("i", "Rule", ((Context) params[0]).getResources().getString(R.string.usingNewThreadForRuleExecution), 5); Thread.setDefaultUncaughtExceptionHandler(Miscellaneous.uncaughtExceptionHandler); // without this line debugger will - for some reason - skip all breakpoints in this class if(android.os.Debug.isDebuggerConnected()) android.os.Debug.waitForDebugger(); if (Looper.myLooper() == null) Looper.prepare(); wasActivated = activateInternally((AutomationService)params[0], (Boolean)params[1]); return null; } @Override protected void onProgressUpdate(String... messages) { AutomationService service = AutomationService.getInstance(); service.speak(messages[0], false); Toast.makeText(service, messages[0], Toast.LENGTH_LONG).show(); super.onProgressUpdate(messages); } @Override protected void onPostExecute(Void result) { /* Only update if the rules was actually executed. Became necessary for the notification trigger. If a user created a rule with a notification trigger and this app creates a notification itself this will otherwise end in an infinite loop. */ if(wasActivated) { setLastExecution(Calendar.getInstance()); AutomationService.updateNotification(); ActivityMainScreen.updateMainScreen(); super.onPostExecute(result); } } public boolean haveTriggersReallyChanged(Object triggeringObject) { boolean returnValue = false; try { for(int i=0; i < triggerSet.size(); i++) { Trigger t = (Trigger) triggerSet.get(i); if(t.hasStateRecentlyNotApplied(triggeringObject)) { Miscellaneous.logEvent("i", "Rule", "Rule \"" + getName() + "\" has trigger that flipped: " + t.toString(), 4); returnValue = true; // only 1 trigger needs to have flipped recently } } return returnValue; } catch(Exception e) { Miscellaneous.logEvent("e", "Rule", "Error while checking if rule \"" + getName() + "\" haveTriggersReallyChanged(): " + Log.getStackTraceString(e), 1); return false; } } /** * Will activate the rule. Should be called by a separate execution thread * @param automationService */ protected boolean activateInternally(AutomationService automationService, boolean force) { boolean isActuallyToggable = isActuallyToggable(); boolean notLastActive = getLastActivatedRule() == null || !getLastActivatedRule().equals(Rule.this); boolean doToggle = ruleToggle && isActuallyToggable; boolean triggersApplyAnew = haveTriggersReallyChanged(new Date()); if(notLastActive || force || doToggle || triggersApplyAnew) { String message; if(!doToggle) message = String.format(automationService.getResources().getString(R.string.ruleActivate), Rule.this.getName()); else message = String.format(automationService.getResources().getString(R.string.ruleActivateToggle), Rule.this.getName()); Miscellaneous.logEvent("i", "Rule", message, 2); // automationService.speak(message); // Toast.makeText(automationService, message, Toast.LENGTH_LONG).show(); if(Settings.startNewThreadForRuleActivation) publishProgress(message); for(int i = 0; i< Rule.this.getActionSet().size(); i++) { try { Rule.this.getActionSet().get(i).run(automationService, doToggle); } catch(Exception e) { Miscellaneous.logEvent("e", "RuleExecution", "Error running action of rule " + Rule.this.getName() + ": " + Log.getStackTraceString(e), 1); } } // Keep log of last x rule activations (Settings) try { Rule.ruleRunHistory.add(0, Rule.this); // add at beginning for better visualization Rule.lastActivatedRuleActivationTime = new Date(); while(ruleRunHistory.size() > Settings.rulesThatHaveBeenRanHistorySize) ruleRunHistory.remove(ruleRunHistory.size()-1); String history = ""; for(Rule rule : ruleRunHistory) history += rule.getName() + ", "; if(history.length() > 0) history = history.substring(0, history.length()-2); Miscellaneous.logEvent("i", "Rule history", "Most recent first: " + history, 4); } catch(Exception e) { Miscellaneous.logEvent("e", "Rule history error", Log.getStackTraceString(e), 3); } Miscellaneous.logEvent("i", "Rule", String.format(Miscellaneous.getAnyContext().getResources().getString(R.string.ruleActivationComplete), Rule.this.getName()), 2); } else { Miscellaneous.logEvent("i", "Rule", "Request to activate rule " + Rule.this.getName() + ", but it is the last one that was activated. Won't do it again.", 3); return false; } return true; } } public void activate(AutomationService automationService, boolean force) { ActivateRuleTask task = new ActivateRuleTask(); // if(Settings.startNewThreadForRuleActivation) task.execute(automationService, force); // else // { // task.activateInternally(automationService, force); // AutomationService.updateNotification(); // ActivityMainScreen.updateMainScreen(); // } } public static ArrayList findRuleCandidatesByPoi(PointOfInterest searchPoi, boolean triggerParameter) { Miscellaneous.logEvent("i", "RuleSearch", "Searching for rules referencing POI " + searchPoi.getName() + ". Total size of ruleset: " + String.valueOf(ruleCollection.size()), 4); ArrayList ruleCandidates = new ArrayList(); for(int i=0; i findRuleCandidatesByTimeFrame(TimeFrame searchTimeFrame, boolean triggerParameter) { ArrayList ruleCandidates = new ArrayList(); for(int i=0; i findRuleCandidatesByTime(Time searchTime) { Miscellaneous.logEvent("i", "RuleSearch", "Searching for rules with TimeFrame with time " + searchTime.toString() + ". RuleCollection-Size: " + String.valueOf(ruleCollection.size()), 3);; ArrayList ruleCandidates = new ArrayList(); for(Rule oneRule : ruleCollection) { innerloop: for(Trigger oneTrigger : oneRule.getTriggerSet()) { if(oneTrigger.getTriggerType() == Trigger.Trigger_Enum.timeFrame) { Miscellaneous.logEvent("i", "RuleSearch", "Searching interval: " + oneTrigger.getTimeFrame().getTriggerTimeStart().toString() + " to " + oneTrigger.getTimeFrame().getTriggerTimeStop().toString(), 5); Miscellaneous.logEvent("i", "RuleSearch", "interval start: " + String.valueOf(oneTrigger.getTimeFrame().getTriggerTimeStart().getTime()), 5); Miscellaneous.logEvent("i", "RuleSearch", "search time: " + String.valueOf(searchTime.getTime()), 5); Miscellaneous.logEvent("i", "RuleSearch", "interval stop: " + String.valueOf(oneTrigger.getTimeFrame().getTriggerTimeStop().getTime()), 5); if(oneTrigger.getTimeFrame().getTriggerTimeStart().getTime() > oneTrigger.getTimeFrame().getTriggerTimeStop().getTime()) { Miscellaneous.logEvent("i", "Timeframe search", "Rule goes over midnight.", 5); if(oneTrigger.getTimeFrame().getTriggerTimeStart().getTime() <= searchTime.getTime() || searchTime.getTime() <= oneTrigger.getTimeFrame().getTriggerTimeStop().getTime()+20000) //add 20 seconds because of delay { ruleCandidates.add(oneRule); break innerloop; //if the poi is found we don't need to search the other triggers in the same rule } } else if(oneTrigger.getTimeFrame().getTriggerTimeStart().getTime() <= searchTime.getTime() && searchTime.getTime() <= oneTrigger.getTimeFrame().getTriggerTimeStop().getTime()+20000) //add 20 seconds because of delay { Miscellaneous.logEvent("i", "RuleSearch", "Rule found with TimeFrame with time " + searchTime.toString(), 3); ruleCandidates.add(oneRule); break innerloop; //if the poi is found we don't need to search the other triggers in the same rule } } } } Miscellaneous.logEvent("i", "RuleSearch", String.valueOf(ruleCandidates.size()) + " Rule(s) found with TimeFrame with time " + searchTime.toString(), 3); return ruleCandidates; } public static ArrayList findRuleCandidatesByTimeFrame() { ArrayList ruleCandidates = new ArrayList(); for(Rule oneRule : ruleCollection) { innerloop: for(Trigger oneTrigger : oneRule.getTriggerSet()) { if(oneTrigger.getTriggerType() == Trigger.Trigger_Enum.timeFrame) { ruleCandidates.add(oneRule); break innerloop; //if the poi is found we don't need to search the other triggers in the same rule } } } return ruleCandidates; } public static ArrayList findRuleCandidatesByCharging(boolean triggerParameter) { ArrayList ruleCandidates = new ArrayList(); for(Rule oneRule : ruleCollection) { innerloop: for(Trigger oneTrigger : oneRule.getTriggerSet()) { if(oneTrigger.getTriggerType() == Trigger.Trigger_Enum.charging) { if(oneTrigger.getTriggerParameter() == triggerParameter) { ruleCandidates.add(oneRule); break innerloop; //if the poi is found we don't need to search the other triggers in the same rule } } } } return ruleCandidates; } public static ArrayList findRuleCandidatesByUsbHost(boolean triggerParameter) { ArrayList ruleCandidates = new ArrayList(); for(Rule oneRule : ruleCollection) { innerloop: for(Trigger oneTrigger : oneRule.getTriggerSet()) { if(oneTrigger.getTriggerType() == Trigger.Trigger_Enum.usb_host_connection) { if(oneTrigger.getTriggerParameter() == triggerParameter) { ruleCandidates.add(oneRule); break innerloop; //if the poi is found we don't need to search the other triggers in the same rule } } } } return ruleCandidates; } public static ArrayList findRuleCandidatesByBatteryLevel() { ArrayList ruleCandidates = new ArrayList(); for(Rule oneRule : ruleCollection) { innerloop: for(Trigger oneTrigger : oneRule.getTriggerSet()) { if(oneTrigger.getTriggerType() == Trigger.Trigger_Enum.batteryLevel) { // if(oneTrigger.getTriggerParameter() == triggerParameter) // { ruleCandidates.add(oneRule); break innerloop; //if the poi is found we don't need to search the other triggers in the same rule // } } } } return ruleCandidates; } public static ArrayList findRuleCandidatesBySpeed() { ArrayList ruleCandidates = new ArrayList(); for(Rule oneRule : ruleCollection) { innerloop: for(Trigger oneTrigger : oneRule.getTriggerSet()) { if(oneTrigger.getTriggerType() == Trigger.Trigger_Enum.speed) { // if(oneTrigger.getTriggerParameter() == triggerParameter) // { ruleCandidates.add(oneRule); break innerloop; //if the poi is found we don't need to search the other triggers in the same rule // } } } } return ruleCandidates; } public static ArrayList findRuleCandidatesByNoiseLevel() { ArrayList ruleCandidates = new ArrayList(); for(Rule oneRule : ruleCollection) { innerloop: for(Trigger oneTrigger : oneRule.getTriggerSet()) { if(oneTrigger.getTriggerType() == Trigger.Trigger_Enum.noiseLevel) { // if(oneTrigger.getTriggerParameter() == triggerParameter) // { ruleCandidates.add(oneRule); break innerloop; //if the poi is found we don't need to search the other triggers in the same rule // } } } } return ruleCandidates; } public static ArrayList findRuleCandidatesByWifiConnection() { ArrayList ruleCandidates = new ArrayList(); for(Rule oneRule : ruleCollection) { innerloop: for(Trigger oneTrigger : oneRule.getTriggerSet()) { if(oneTrigger.getTriggerType() == Trigger.Trigger_Enum.wifiConnection) { // if(oneTrigger.getTriggerParameter() == triggerParameter) // { ruleCandidates.add(oneRule); break innerloop; //we don't need to search the other triggers in the same rule // } } } } return ruleCandidates; } public static ArrayList findRuleCandidatesByBluetoothConnection() { ArrayList ruleCandidates = new ArrayList(); for(Rule oneRule : ruleCollection) { innerloop: for(Trigger oneTrigger : oneRule.getTriggerSet()) { if(oneTrigger.getTriggerType() == Trigger.Trigger_Enum.bluetoothConnection) { // if(oneTrigger.getTriggerParameter() == triggerParameter) // { ruleCandidates.add(oneRule); break innerloop; //we don't need to search the other triggers in the same rule // } } } } return ruleCandidates; } public static ArrayList findRuleCandidatesByProcess() { ArrayList ruleCandidates = new ArrayList(); for(Rule oneRule : ruleCollection) { innerloop: for(Trigger oneTrigger : oneRule.getTriggerSet()) { if(oneTrigger.getTriggerType() == Trigger.Trigger_Enum.process_started_stopped) { // if(oneTrigger.getTriggerParameter() == triggerParameter) // { ruleCandidates.add(oneRule); break innerloop; //we don't need to search the other triggers in the same rule // } } } } return ruleCandidates; } public static ArrayList findRuleCandidatesByAirplaneMode(boolean triggerParameter) { ArrayList ruleCandidates = new ArrayList(); for(Rule oneRule : ruleCollection) { innerloop: for(Trigger oneTrigger : oneRule.getTriggerSet()) { if(oneTrigger.getTriggerType() == Trigger.Trigger_Enum.airplaneMode) { if(oneTrigger.getTriggerParameter() == triggerParameter) { ruleCandidates.add(oneRule); break innerloop; //we don't need to search the other triggers in the same rule } } } } return ruleCandidates; } public static ArrayList findRuleCandidatesByRoaming(boolean triggerParameter) { ArrayList ruleCandidates = new ArrayList(); for(Rule oneRule : ruleCollection) { innerloop: for(Trigger oneTrigger : oneRule.getTriggerSet()) { if(oneTrigger.getTriggerType() == Trigger.Trigger_Enum.roaming) { if(oneTrigger.getTriggerParameter() == triggerParameter) { ruleCandidates.add(oneRule); break innerloop; //we don't need to search the other triggers in the same rule } } } } return ruleCandidates; } public static ArrayList findRuleCandidatesByPhoneCall(String direction) { ArrayList ruleCandidates = new ArrayList(); for(Rule oneRule : ruleCollection) { innerloop: for(Trigger oneTrigger : oneRule.getTriggerSet()) { if(oneTrigger.getTriggerType() == Trigger.Trigger_Enum.phoneCall) { String[] elements = oneTrigger.getTriggerParameter2().split(triggerParameter2Split); if(elements[1].equals(Trigger.triggerPhoneCallDirectionAny) || elements[1].equals(direction)) { ruleCandidates.add(oneRule); break innerloop; //we don't need to search the other triggers in the same rule } } } } return ruleCandidates; } public static ArrayList findRuleCandidatesByNfc() { ArrayList ruleCandidates = new ArrayList(); for(Rule oneRule : ruleCollection) { innerloop: for(Trigger oneTrigger : oneRule.getTriggerSet()) { if(oneTrigger.getTriggerType() == Trigger.Trigger_Enum.nfcTag) { // if(oneTrigger.getTriggerParameter() == triggerParameter) // { ruleCandidates.add(oneRule); break innerloop; //we don't need to search the other triggers in the same rule // } } } } return ruleCandidates; } public static ArrayList findRuleCandidates(Trigger.Trigger_Enum triggerType) { ArrayList ruleCandidates = new ArrayList(); for(Rule oneRule : ruleCollection) { innerloop: for(Trigger oneTrigger : oneRule.getTriggerSet()) { if(oneTrigger.getTriggerType() == triggerType) { ruleCandidates.add(oneRule); break innerloop; //we don't need to search the other triggers in the same rule } } } return ruleCandidates; } public static ArrayList findRuleCandidatesByActivityDetection() { ArrayList ruleCandidates = new ArrayList(); for(Rule oneRule : ruleCollection) { innerloop: for(Trigger oneTrigger : oneRule.getTriggerSet()) { if(oneTrigger.getTriggerType() == Trigger.Trigger_Enum.activityDetection) { // if(oneTrigger.getTriggerParameter() == triggerParameter) // { ruleCandidates.add(oneRule); break innerloop; //we don't need to search the other triggers in the same rule // } } } } return ruleCandidates; } public static ArrayList findRuleCandidatesByPoi(PointOfInterest searchPoi) { ArrayList ruleCandidates = new ArrayList(); for(Rule oneRule : ruleCollection) { innerloop: for(Trigger oneTrigger : oneRule.getTriggerSet()) { if(oneTrigger.getTriggerType() == Trigger.Trigger_Enum.pointOfInterest) { if(oneTrigger.getPointOfInterest() != null && oneTrigger.getPointOfInterest().equals(searchPoi)) // != null to exclude those who are referring all locations ("entering any location") { ruleCandidates.add(oneRule); break innerloop; //if the poi is found we don't need to search the other triggers in the same rule } } } } return ruleCandidates; } public static ArrayList findRuleCandidatesByHeadphoneJack(boolean triggerParameter) { ArrayList ruleCandidates = new ArrayList(); for(Rule oneRule : ruleCollection) { innerloop: for(Trigger oneTrigger : oneRule.getTriggerSet()) { if(oneTrigger.getTriggerType() == Trigger.Trigger_Enum.headsetPlugged) { if(oneTrigger.getTriggerParameter() == triggerParameter) { ruleCandidates.add(oneRule); break innerloop; //we don't need to search the other triggers in the same rule } } } } return ruleCandidates; } public static ArrayList findRuleCandidatesByProfile(Profile profile) { ArrayList ruleCandidates = new ArrayList(); for(Rule oneRule : ruleCollection) { innerloop: for(Action oneAction : oneRule.getActionSet()) { if(oneAction.getAction() == Action.Action_Enum.changeSoundProfile) { if(oneAction.getParameter2().equals(profile.getOldName())) // != null to exclude those who are referring all locations ("entering any location") { ruleCandidates.add(oneRule); break innerloop; //if the profile is found we don't need to search the other triggers in the same rule } } } } return ruleCandidates; } public static boolean isAnyRuleUsing(Trigger.Trigger_Enum triggerType) { for(Rule rule: ruleCollection) { if(rule.isRuleActive()) { for(Trigger trigger : rule.getTriggerSet()) { if(trigger.getTriggerType().equals(triggerType)) { Miscellaneous.logEvent("i", "Rule->isAnyRuleUsing()", String.format(Miscellaneous.getAnyContext().getString(R.string.atLeastRuleXisUsingY), rule.getName(), triggerType.getFullName(Miscellaneous.getAnyContext())), 5); return true; } } } } return false; } public static boolean isAnyRuleUsing(Action.Action_Enum actionType) { for(Rule rule: ruleCollection) { if(rule.isRuleActive()) { for(Action action : rule.getActionSet()) { if(action.getAction().equals(actionType)) { Miscellaneous.logEvent("i", "Rule->isAnyRuleUsing()", String.format(Miscellaneous.getAnyContext().getString(R.string.atLeastRuleXisUsingY), rule.getName(), actionType.getFullName(Miscellaneous.getAnyContext())), 5); return true; } } } } return false; } @Override public int compareTo(Rule another) { return this.getName().compareTo(another.getName()); } public boolean haveEnoughPermissions() { return ActivityPermissions.havePermissionsForRule(this, Miscellaneous.getAnyContext()); } }