diff --git a/app/build.gradle b/app/build.gradle index 8ef949d..797b6ae 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -7,7 +7,7 @@ android { defaultConfig { applicationId "com.jens.automation2" - minSdkVersion 14 + minSdkVersion 16 compileSdkVersion 29 buildToolsVersion '29.0.2' useLibrary 'org.apache.http.legacy' @@ -64,16 +64,16 @@ android { } dependencies { - googlePlayFlavorImplementation 'com.google.firebase:firebase-appindexing:16.0.1' - googlePlayFlavorImplementation 'com.google.android.gms:play-services-location:15.0.1' + googlePlayFlavorImplementation 'com.google.firebase:firebase-appindexing:19.2.0' + googlePlayFlavorImplementation 'com.google.android.gms:play-services-location:17.1.0' - apkFlavorImplementation 'com.google.firebase:firebase-appindexing:16.0.1' - apkFlavorImplementation 'com.google.android.gms:play-services-location:15.0.1' + apkFlavorImplementation 'com.google.firebase:firebase-appindexing:19.2.0' + apkFlavorImplementation 'com.google.android.gms:play-services-location:17.1.0' implementation 'androidx.appcompat:appcompat:1.2.0' - implementation 'com.google.android.material:material:1.1.0' + implementation 'com.google.android.material:material:1.3.0' testImplementation 'junit:junit:4.+' - androidTestImplementation 'androidx.test.ext:junit:1.1.1' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' + androidTestImplementation 'androidx.test.ext:junit:1.1.2' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' } \ No newline at end of file diff --git a/app/src/main/java/com/jens/automation2/ActivityMainScreen.java b/app/src/apkFlavor/java/com/jens/automation2/ActivityMainScreen.java similarity index 100% rename from app/src/main/java/com/jens/automation2/ActivityMainScreen.java rename to app/src/apkFlavor/java/com/jens/automation2/ActivityMainScreen.java diff --git a/app/src/apkFlavor/java/com/jens/automation2/MyGoogleServicesInterface.java b/app/src/apkFlavor/java/com/jens/automation2/MyGoogleServicesInterface.java new file mode 100644 index 0000000..3382ce6 --- /dev/null +++ b/app/src/apkFlavor/java/com/jens/automation2/MyGoogleServicesInterface.java @@ -0,0 +1,16 @@ +package com.jens.automation2; + +import com.google.android.gms.common.ConnectionResult; +import com.google.android.gms.common.GoogleApiAvailability; + +public class MiscellaneousWithGoogleServices +{ + public static boolean isPlayServiceAvailable() + { + GoogleApiAvailability aa = new GoogleApiAvailability(); + if(aa.isGooglePlayServicesAvailable(Miscellaneous.getAnyContext()) == ConnectionResult.SUCCESS) + return true; + else + return false; + } +} diff --git a/app/src/main/java/com/jens/automation2/Rule.java b/app/src/apkFlavor/java/com/jens/automation2/Rule.java similarity index 100% rename from app/src/main/java/com/jens/automation2/Rule.java rename to app/src/apkFlavor/java/com/jens/automation2/Rule.java diff --git a/app/src/main/java/com/jens/automation2/location/GeofenceBroadcastReceiver.java b/app/src/apkFlavor/java/com/jens/automation2/location/GeofenceBroadcastReceiver.java similarity index 100% rename from app/src/main/java/com/jens/automation2/location/GeofenceBroadcastReceiver.java rename to app/src/apkFlavor/java/com/jens/automation2/location/GeofenceBroadcastReceiver.java diff --git a/app/src/main/java/com/jens/automation2/location/GeofenceIntentService.java b/app/src/apkFlavor/java/com/jens/automation2/location/GeofenceIntentService.java similarity index 100% rename from app/src/main/java/com/jens/automation2/location/GeofenceIntentService.java rename to app/src/apkFlavor/java/com/jens/automation2/location/GeofenceIntentService.java diff --git a/app/src/apkFlavor/java/com/jens/automation2/receivers/ActivityDetectionReceiver.java b/app/src/apkFlavor/java/com/jens/automation2/receivers/ActivityDetectionReceiver.java new file mode 100644 index 0000000..93a0347 --- /dev/null +++ b/app/src/apkFlavor/java/com/jens/automation2/receivers/ActivityDetectionReceiver.java @@ -0,0 +1,391 @@ +package com.jens.automation2.receivers; + +import android.app.IntentService; +import android.app.PendingIntent; +import android.content.Intent; +import android.os.Bundle; +import android.util.Log; + +import com.google.android.gms.common.ConnectionResult; +import com.google.android.gms.common.GooglePlayServicesUtil; +import com.google.android.gms.common.api.GoogleApiClient; +import com.google.android.gms.common.api.GoogleApiClient.OnConnectionFailedListener; +import com.google.android.gms.location.ActivityRecognition; +import com.google.android.gms.location.ActivityRecognitionApi; +import com.google.android.gms.location.ActivityRecognitionResult; +import com.google.android.gms.location.DetectedActivity; +import com.jens.automation2.ActivityPermissions; +import com.jens.automation2.AutomationService; +import com.jens.automation2.Miscellaneous; +import com.jens.automation2.R; +import com.jens.automation2.Rule; +import com.jens.automation2.Settings; +import com.jens.automation2.Trigger.Trigger_Enum; + +import java.util.ArrayList; +import java.util.Date; + +// See also: http://developer.android.com/reference/com/google/android/gms/location/ActivityRecognitionClient.html +// https://www.sitepoint.com/google-play-services-location-activity-recognition/ + +public class ActivityDetectionReceiver extends IntentService implements AutomationListenerInterface, GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener +{ + protected static ActivityRecognitionApi activityRecognitionClient = null; + protected static boolean connected = false; + protected static enum LastRequestEnum { start, stop, restart }; + protected static LastRequestEnum lastRequest = null; + protected static GoogleApiClient googleApiClient = null; + protected static ActivityRecognitionResult activityDetectionLastResult = null; + protected static long lastUpdate = 0; + protected static Date currentTime; + protected static ActivityDetectionReceiver instance = null; + + protected static ActivityDetectionReceiver getInstance() + { + if(instance == null) + instance = new ActivityDetectionReceiver(); + + return instance; + } + + protected static boolean activityDetectionReceiverRunning = false; + protected static ActivityDetectionReceiver activityDetectionReceiverInstance = null; + + public static boolean isActivityDetectionReceiverRunning() + { + return activityDetectionReceiverRunning; + } + + public static ActivityRecognitionResult getActivityDetectionLastResult() + { + return activityDetectionLastResult; + } + + public static GoogleApiClient getApiClient() + { + if(googleApiClient == null) + { + googleApiClient = new GoogleApiClient.Builder(AutomationService.getInstance()) + .addConnectionCallbacks(getInstance()) + .addOnConnectionFailedListener(getInstance()) + .addApi(ActivityRecognition.API) + .build(); + } + + return googleApiClient; + } + + private static void requestUpdates() + { + long frequency = Settings.activityDetectionFrequency * 1000; + Miscellaneous.logEvent("i", "ActivityDetectionReceiver", "Requesting ActivityDetection updates with frequency " + String.valueOf(frequency) + " milliseconds.", 4); + + + ActivityRecognition.ActivityRecognitionApi.requestActivityUpdates(getApiClient(), 1000, getInstance().getActivityDetectionPendingIntent()); + } + private void reloadUpdates() + { + long frequency = Settings.activityDetectionFrequency * 1000; + Miscellaneous.logEvent("i", "ActivityDetectionReceiver", "Re-requesting ActivityDetection updates with frequency " + String.valueOf(frequency) + " milliseconds.", 4); + + activityRecognitionClient.removeActivityUpdates(getApiClient(), getInstance().getActivityDetectionPendingIntent()); + try + { + Thread.sleep(1000); + } + catch (InterruptedException e) + { + Miscellaneous.logEvent("i", "ActivityDetectionReceiver", "Error reloading updates for ActivityDetectionReceiver: " + Log.getStackTraceString(e), 5); + } + + ActivityRecognition.ActivityRecognitionApi.requestActivityUpdates(getApiClient(), frequency, getInstance().getActivityDetectionPendingIntent()); + } + + private static void stopUpdates() + { + Miscellaneous.logEvent("i", "ActivityDetectionReceiver", "Unsubscribing from ActivityDetection-updates.", 4); + + ActivityRecognition.ActivityRecognitionApi.removeActivityUpdates(getApiClient(), getInstance().getActivityDetectionPendingIntent()); +// activityRecognitionClient.removeActivityUpdates(getApiClient(), getInstance().getActivityDetectionPendingIntent()); +// activityRecognitionClient.disconnect(); + } + public static void startActivityDetectionReceiver() + { + try + { + Miscellaneous.logEvent("i", "ActivityDetectionReceiver", "Starting ActivityDetectionReceiver", 3); + + if(activityDetectionReceiverInstance == null) + activityDetectionReceiverInstance = new ActivityDetectionReceiver(); + + if(!activityDetectionReceiverRunning && Rule.isAnyRuleUsing(Trigger_Enum.activityDetection)) + { + if(isPlayServiceAvailable()) + { + /*if(activityRecognitionClient == null) + activityRecognitionClient = new ActivityRecognitionClient(Miscellaneous.getAnyContext(), activityDetectionReceiverInstance, activityDetectionReceiverInstance);*/ + + lastRequest = LastRequestEnum.start; + + if(!connected) + getApiClient().connect(); + else + requestUpdates(); + + activityDetectionReceiverRunning = true; + } + } + } + catch(Exception ex) + { + Miscellaneous.logEvent("e", "ActivityDetectionReceiver", "Error starting ActivityDetectionReceiver: " + Log.getStackTraceString(ex), 3); + } + } + + public static void restartActivityDetectionReceiver() + { + try + { + if(!activityDetectionReceiverRunning && Rule.isAnyRuleUsing(Trigger_Enum.activityDetection)) + { + Miscellaneous.logEvent("i", "ActivityDetectionReceiver", "Restarting ActivityDetectionReceiver", 3); + + if(activityDetectionReceiverInstance == null) + activityDetectionReceiverInstance = new ActivityDetectionReceiver(); + + if(isPlayServiceAvailable()) + { +// if(activityRecognitionClient == null) +// activityRecognitionClient = new ActivityRecognitionClient(Miscellaneous.getAnyContext(), activityDetectionReceiverInstance, activityDetectionReceiverInstance); + + lastRequest = LastRequestEnum.restart; + + if(!connected) + getApiClient().connect(); + else + requestUpdates(); + } + } + } + catch(Exception ex) + { + Miscellaneous.logEvent("e", "ActivityDetectionReceiver", "Error starting ActivityDetectionReceiver: " + Log.getStackTraceString(ex), 3); + } + + } + + public static void stopActivityDetectionReceiver() + { + try + { + if(activityDetectionReceiverRunning) + { + Miscellaneous.logEvent("i", "ActivityDetectionReceiver", "Stopping ActivityDetectionReceiver", 3); + + if(isPlayServiceAvailable()) + { + lastRequest = LastRequestEnum.stop; + + if(!connected) + getApiClient().connect(); + else + stopUpdates(); + + activityDetectionReceiverRunning = false; + } + } + } + catch(Exception ex) + { + Miscellaneous.logEvent("e", "ActivityDetectionReceiver", "Error stopping ActivityDetectionReceiver: " + Log.getStackTraceString(ex), 3); + } + + + @Override + public void onConnectionFailed(ConnectionResult arg0) + { + Miscellaneous.logEvent("i", "ActivityDetectionReceiver", "Connection to Play Services failed.", 4); + if(connected && getApiClient().isConnected()) + { + connected = false; + } + } + + @Override + public void onConnected(Bundle arg0) + { + Miscellaneous.logEvent("i", "ActivityDetectionReceiver", "Connected to Play Services.", 4); + + connected = true; + + if(lastRequest == null) + { + Miscellaneous.logEvent("w", "ActivityDetectionReceiver", "Request type not specified. Start or stop listening to activity detection updates?", 4); + return; + } + + if(lastRequest.equals(LastRequestEnum.start)) + requestUpdates(); + else if(lastRequest.equals(LastRequestEnum.stop)) + stopUpdates(); + else //reload, e.g. to set a new update time + reloadUpdates(); + } + + @Override + public void onConnectionSuspended(int arg0) + { + Miscellaneous.logEvent("i", "ActivityDetectionReceiver", "Connection to Play Services suspended.", 4); +// activityRecognitionClient.disconnect(); + connected = false; + } + + public ActivityDetectionReceiver() + { + super("ActivityDetectionIntentService"); + if(instance == null) + instance = this; + } + + @Override + protected void onHandleIntent(Intent intent) + { + Miscellaneous.logEvent("i", "ActivityDetectionReceiver", "onHandleIntent(): Received some status.", 5); + + try + { + if(isActivityDetectionReceiverRunning()) + { + currentTime = new Date(); + + if(lastUpdate == 0 | currentTime.getTime() >= lastUpdate + Settings.activityDetectionFrequency * 1000 - 1000) // -1000 to include updates only marginaly below the threshold + { + lastUpdate = currentTime.getTime(); + + if(ActivityRecognitionResult.hasResult(intent)) + { + activityDetectionLastResult = ActivityRecognitionResult.extractResult(intent); + + for(DetectedActivity activity : activityDetectionLastResult.getProbableActivities()) + { + int loglevel = 3; + if(activity.getConfidence() < Settings.activityDetectionRequiredProbability) + loglevel = 4; + + Miscellaneous.logEvent("i", "ActivityDetectionReceiver", "Detected activity (probability " + String.valueOf(activity.getConfidence()) + "%): " + getDescription(activity.getType()), loglevel); + } + + /* + * Returns the list of activities that where detected with the confidence value associated with each activity. + * The activities are sorted by most probable activity first. + * The sum of the confidences of all detected activities this method returns does not have to be <= 100 + * since some activities are not mutually exclusive (for example, you can be walking while in a bus) + * and some activities are hierarchical (ON_FOOT is a generalization of WALKING and RUNNING). + */ + + ArrayList allRulesWithActivityDetection = Rule.findRuleCandidatesByActivityDetection(); + for(int i=0; i types = new ArrayList(); + + for(int type : getAllTypes()) + types.add(getDescription(type)); + + return types.toArray(new String[types.size()]); + } + + @Override + public void startListener(AutomationService automationService) + { + ActivityDetectionReceiver.startActivityDetectionReceiver(); + } + + @Override + public void stopListener(AutomationService automationService) + { + ActivityDetectionReceiver.stopActivityDetectionReceiver(); + } + + public static boolean haveAllPermission() + { + return ActivityPermissions.havePermission("com.google.android.gms.permission.ACTIVITY_RECOGNITION", Miscellaneous.getAnyContext()); + } + + @Override + public boolean isListenerRunning() + { + return ActivityDetectionReceiver.isActivityDetectionReceiverRunning(); + } + + @Override + public Trigger_Enum[] getMonitoredTrigger() + { + return new Trigger_Enum[] { Trigger_Enum.activityDetection }; + } + + private PendingIntent getActivityDetectionPendingIntent() + { + Intent intent = new Intent(AutomationService.getInstance(), ActivityDetectionReceiver.class); + PendingIntent returnValue = PendingIntent.getService(AutomationService.getInstance(), 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); + return returnValue; + } +} \ No newline at end of file diff --git a/app/src/fdroidFlavor/java/com/jens/automation2/ActivityMainScreen.java b/app/src/fdroidFlavor/java/com/jens/automation2/ActivityMainScreen.java new file mode 100644 index 0000000..249f928 --- /dev/null +++ b/app/src/fdroidFlavor/java/com/jens/automation2/ActivityMainScreen.java @@ -0,0 +1,557 @@ +package com.jens.automation2; + +import android.annotation.SuppressLint; +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.view.MotionEvent; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.View.OnTouchListener; +import android.widget.ArrayAdapter; +import android.widget.Button; +import android.widget.CompoundButton; +import android.widget.CompoundButton.OnCheckedChangeListener; +import android.widget.ListView; +import android.widget.TextView; +import android.widget.Toast; +import android.widget.ToggleButton; + +import com.jens.automation2.AutomationService.serviceCommands; +import com.jens.automation2.Trigger.Trigger_Enum; +import com.jens.automation2.location.LocationProvider; + +import java.util.Calendar; + +@SuppressLint("NewApi") +public class ActivityMainScreen extends ActivityGeneric +{ + private static boolean guiChangeInProgress = false; + + private static ActivityMainScreen activityMainScreenInstance = null; + private ToggleButton toggleService, tbLockSound; + private Button bShowHelp, bPrivacy, bSettingsErase, bSettingsSetToDefault, bVolumeTest, bAddSoundLockTIme; + private TextView tvActivePoi, tvClosestPoi, tvLastRule, tvMainScreenNote, tvlockSoundDuration; + + private ListView lvRuleHistory; + private ArrayAdapter ruleHistoryListViewAdapter; + + private static boolean uiUpdateRunning = false; + + @Override + public void onCreate(Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + setContentView(R.layout.main_overview_layout); + + activityMainScreenInstance = this; + + if(ActivityPermissions.needMorePermissions(ActivityMainScreen.this)) + { + Intent permissionsIntent = new Intent(ActivityMainScreen.this, ActivityPermissions.class); + startActivityForResult(permissionsIntent, 7000); + } + + Settings.readFromPersistentStorage(this); + + guiChangeInProgress = true; + + tvActivePoi = (TextView) findViewById(R.id.tvActivePoi); + tvClosestPoi = (TextView) findViewById(R.id.tvClosestPoi); + lvRuleHistory = (ListView) findViewById(R.id.lvRuleHistory); + tvLastRule = (TextView) findViewById(R.id.tvTimeFrameHelpText); + tvMainScreenNote = (TextView) findViewById(R.id.tvMainScreenNote); + tvlockSoundDuration = (TextView)findViewById(R.id.tvlockSoundDuration); + tbLockSound = (ToggleButton) findViewById(R.id.tbLockSound); + toggleService = (ToggleButton) findViewById(R.id.tbArmMastListener); + toggleService.setChecked(AutomationService.isMyServiceRunning(this)); + toggleService.setOnCheckedChangeListener(new OnCheckedChangeListener() + { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) + { + if (!ActivityMainScreen.this.uiUpdateRunning) + { + if (toggleService.isChecked()) + { + startAutomationService(getBaseContext(), false); + } else + { + stopAutomationService(); + } + } + } + }); + + tvMainScreenNote.setOnClickListener(new OnClickListener() + { + @Override + public void onClick(View v) + { + Intent intent = new Intent(ActivityMainScreen.this, ActivityPermissions.class); + startActivityForResult(intent, ActivityPermissions.requestCodeForPermissions); + } + }); + + tbLockSound.setOnCheckedChangeListener(new OnCheckedChangeListener() + { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) + { + Settings.lockSoundChanges = isChecked; + + if(!isChecked) + { + AutomationService.getInstance().nullLockSoundChangesEnd(); + updateMainScreen(); + } + + if (!guiChangeInProgress) + Settings.writeSettings(ActivityMainScreen.this); + } + }); + + Button bSettings = (Button) findViewById(R.id.bSettings); + bSettings.setOnClickListener(new OnClickListener() + { + @Override + public void onClick(View v) + { + Intent myIntent = new Intent(ActivityMainScreen.this, ActivitySettings.class); + startActivityForResult(myIntent, 6000); + } + }); + + Button bVolumeTest = (Button) findViewById(R.id.bVolumeTest); + bVolumeTest.setOnClickListener(new OnClickListener() + { + @Override + public void onClick(View v) + { + Intent intent = new Intent(ActivityMainScreen.this, ActivityVolumeTest.class); + startActivity(intent); + } + }); + + bShowHelp = (Button) findViewById(R.id.bShowHelp); + bShowHelp.setOnClickListener(new OnClickListener() + { + + @Override + public void onClick(View v) + { + Intent showHelpIntent = new Intent(ActivityMainScreen.this, ActivityHelp.class); + startActivity(showHelpIntent); + } + }); + + bPrivacy = (Button) findViewById(R.id.bPrivacy); + bPrivacy.setOnClickListener(new OnClickListener() + { + @Override + public void onClick(View v) + { + AlertDialog.Builder builder = new AlertDialog.Builder(ActivityMainScreen.this); + builder.setMessage(getResources().getString(R.string.privacyConfirmationText)); + builder.setPositiveButton(getResources().getString(R.string.yes), new DialogInterface.OnClickListener() + { + @Override + public void onClick(DialogInterface dialog, int which) + { + openPrivacyPolicy(); + } + }); + builder.setNegativeButton(getResources().getString(R.string.no), null); + builder.create().show(); + } + }); + + /*bSettingsErase = (Button)findViewById(R.id.bSettingsErase); + bSettingsErase.setOnClickListener(new OnClickListener() + { + @Override + public void onClick(View v) + { + getEraseSettingsDialog(ActivityMainScreen.this).show(); + } + });*/ + bSettingsSetToDefault = (Button) findViewById(R.id.bSettingsSetToDefault); + bSettingsSetToDefault.setOnClickListener(new OnClickListener() + { + @Override + public void onClick(View v) + { + getDefaultSettingsDialog(ActivityMainScreen.this).show(); + } + }); + + lvRuleHistory.setOnTouchListener(new OnTouchListener() + { + @Override + public boolean onTouch(View v, MotionEvent event) + { + v.getParent().requestDisallowInterceptTouchEvent(true); + return false; + } + }); + + bAddSoundLockTIme = (Button)findViewById(R.id.bAddSoundLockTIme); + bAddSoundLockTIme.setText("+" + Settings.lockSoundChangesInterval + " min"); + bAddSoundLockTIme.setOnClickListener(new OnClickListener() + { + @Override + public void onClick(View view) + { + if(AutomationService.isMyServiceRunning(ActivityMainScreen.this)) + { + AutomationService.getInstance().lockSoundChangesEndAddTime(); + ActivityMainScreen.updateMainScreen(); + } + else + Toast.makeText(ActivityMainScreen.this, getResources().getString(R.string.serviceNotRunning), Toast.LENGTH_LONG).show(); + } + }); + + ruleHistoryListViewAdapter = new ArrayAdapter(this, R.layout.text_view_for_poi_listview_mediumtextsize, Rule.getRuleRunHistory()); + + if (PointOfInterest.getPointOfInterestCollection() == null | PointOfInterest.getPointOfInterestCollection().size() == 0) + PointOfInterest.loadPoisFromFile(); + if (Rule.getRuleCollection() == null | Rule.getRuleCollection().size() == 0) + Rule.readFromFile(); + + ActivityMainScreen.updateMainScreen(); + + this.storeServiceReferenceInVariable(); + + guiChangeInProgress = false; + } + + private static AlertDialog getEraseSettingsDialog(final Context context) + { + AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(context); + alertDialogBuilder.setTitle(context.getResources().getString(R.string.areYouSure)); + alertDialogBuilder.setPositiveButton(context.getResources().getString(R.string.yes), new DialogInterface.OnClickListener() + { + @Override + public void onClick(DialogInterface dialog, int which) + { + if (Settings.eraseSettings(context)) + Toast.makeText(context, context.getResources().getString(R.string.settingsErased), Toast.LENGTH_LONG).show(); + } + }); + alertDialogBuilder.setNegativeButton(context.getResources().getString(R.string.no), null); + AlertDialog alertDialog = alertDialogBuilder.create(); + + return alertDialog; + } + + private static AlertDialog getDefaultSettingsDialog(final Context context) + { + AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(context); + alertDialogBuilder.setTitle(context.getResources().getString(R.string.areYouSure)); + alertDialogBuilder.setPositiveButton(context.getResources().getString(R.string.yes), new DialogInterface.OnClickListener() + { + @Override + public void onClick(DialogInterface dialog, int which) + { + if (Settings.initializeSettings(context, true)) + Toast.makeText(context, context.getResources().getString(R.string.settingsSetToDefault), Toast.LENGTH_LONG).show(); + } + }); + alertDialogBuilder.setNegativeButton(context.getResources().getString(R.string.no), null); + AlertDialog alertDialog = alertDialogBuilder.create(); + + return alertDialog; + } + + public static ActivityMainScreen getActivityMainScreenInstance() + { + return activityMainScreenInstance; + } + + public static void updateMainScreen() + { + Miscellaneous.logEvent("i", "MainScreen", "Request to update notification.", 5); + + if (activityMainScreenInstance != null) + { + if(ActivityPermissions.needMorePermissions(activityMainScreenInstance)) + { + activityMainScreenInstance.tvMainScreenNote.setText(R.string.mainScreenPermissionNote); + activityMainScreenInstance.tvMainScreenNote.setVisibility(View.VISIBLE); + } + else + { + activityMainScreenInstance.tvMainScreenNote.setText(""); + activityMainScreenInstance.tvMainScreenNote.setVisibility(View.GONE); + } + + if (AutomationService.isMyServiceRunning(activityMainScreenInstance)) + { + Miscellaneous.logEvent("i", "MainScreen", "Service is running. Updating mainscreen with this info.", 5); + uiUpdateRunning = true; + activityMainScreenInstance.toggleService.setChecked(true); + uiUpdateRunning = false; + // if(activityMainScreenInstance.hasWindowFocus()) + // { + try + { + PointOfInterest activePoi = PointOfInterest.getActivePoi(); + if (activePoi == null) + { + PointOfInterest closestPoi = PointOfInterest.getClosestPOI(LocationProvider.getInstance().getCurrentLocation()); + activityMainScreenInstance.tvActivePoi.setText("none"); + activityMainScreenInstance.tvClosestPoi.setText(closestPoi.getName()); + } + else + { + activityMainScreenInstance.tvActivePoi.setText(activePoi.getName()); + activityMainScreenInstance.tvClosestPoi.setText("n./a."); + } + } + catch (NullPointerException e) + { + if (PointOfInterest.getPointOfInterestCollection().size() > 0) + { + if( + Rule.isAnyRuleUsing(Trigger_Enum.pointOfInterest) + && + ActivityPermissions.havePermission(ActivityPermissions.permissionNameLocationCoarse, AutomationService.getInstance()) + && + ActivityPermissions.havePermission(ActivityPermissions.permissionNameLocationFine, AutomationService.getInstance()) + ) + activityMainScreenInstance.tvActivePoi.setText(activityMainScreenInstance.getResources().getString(R.string.stillGettingPosition)); + else + activityMainScreenInstance.tvActivePoi.setText(activityMainScreenInstance.getResources().getString(R.string.locationEngineNotActive)); + + activityMainScreenInstance.tvClosestPoi.setText("n./a."); + } + else + { + activityMainScreenInstance.tvActivePoi.setText(activityMainScreenInstance.getResources().getString(R.string.noPoisDefinedShort)); + activityMainScreenInstance.tvClosestPoi.setText("n./a."); + } + } + + try + { + activityMainScreenInstance.tvLastRule.setText(Rule.getLastActivatedRule().getName() + " " + activityMainScreenInstance.getResources().getString(R.string.at) + " " + Rule.getLastActivatedRuleActivationTime().toLocaleString()); + activityMainScreenInstance.updateListView(); + } + catch (Exception e) + { + activityMainScreenInstance.tvLastRule.setText("n./a."); + } + } + else + { + Miscellaneous.logEvent("i", "MainScreen", "Service not running. Updating mainscreen with this info.", 5); + activityMainScreenInstance.toggleService.setChecked(false); + activityMainScreenInstance.tvActivePoi.setText(activityMainScreenInstance.getResources().getString(R.string.serviceNotRunning)); + activityMainScreenInstance.tvClosestPoi.setText(""); + activityMainScreenInstance.tvLastRule.setText(""); + } + +// uiUpdateRunning = true; + if(AutomationService.isMyServiceRunning(ActivityMainScreen.getActivityMainScreenInstance()) && AutomationService.getInstance() != null) + { + AutomationService.getInstance().checkLockSoundChangesTimeElapsed(); + + Calendar end = AutomationService.getInstance().getLockSoundChangesEnd(); + activityMainScreenInstance.tbLockSound.setChecked(end != null); + activityMainScreenInstance.tbLockSound.setEnabled(end != null); + + if(end != null) + { + Calendar now = Calendar.getInstance(); + long millis = end.getTimeInMillis() - now.getTimeInMillis(); + long minutes = millis/1000/60; + if(minutes < 60) + activityMainScreenInstance.tvlockSoundDuration.setText(String.valueOf(minutes + " min...")); + else + { + double hours = (double)minutes / 60.0; + activityMainScreenInstance.tvlockSoundDuration.setText(String.valueOf(Math.round(hours * 100.0) / 100.0) + " h..."); + } + } + else + activityMainScreenInstance.tvlockSoundDuration.setText(String.valueOf("")); + } + else + { + activityMainScreenInstance.tbLockSound.setChecked(false); + activityMainScreenInstance.tbLockSound.setEnabled(false); + activityMainScreenInstance.tvlockSoundDuration.setText(""); + } + Settings.writeSettings(activityMainScreenInstance); +// uiUpdateRunning = false; +// } +// else +// Miscellaneous.logEvent("i", "ActivityMainScreen", "Window doesn't have focus. We're not updating anything.", 5); + } + else + Miscellaneous.logEvent("i", "ActivityMainScreen", "Activity not running. No need to update.", 5); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) + { + super.onActivityResult(requestCode, resultCode, data); + +// Miscellaneous.logEvent("i", "ListView", "Notifying ListViewAdapter", 4); + + if (AutomationService.isMyServiceRunning(this)) + bindToService(); + + switch (requestCode) + { + case ActivityPermissions.requestCodeForPermissions: + updateMainScreen(); + break; + case 6000: //settings + Settings.readFromPersistentStorage(this); + + if (boundToService && AutomationService.isMyServiceRunning(this)) + myAutomationService.serviceInterface(serviceCommands.reloadSettings); + + if(AutomationService.isMyServiceRunning(ActivityMainScreen.this)) + Toast.makeText(this, getResources().getString(R.string.settingsWillTakeTime), Toast.LENGTH_LONG).show(); + + break; + } + + if (AutomationService.isMyServiceRunning(this)) + { + // Let service reload via binding interface. + if (boundToService) + { + myAutomationService.serviceInterface(serviceCommands.updateNotification); //in case names got changed. + unBindFromService(); + } + } + else + { + // Let service reload classically. + AutomationService service = AutomationService.getInstance(); + if (service != null) + service.applySettingsAndRules(); + } + } + + public static void startAutomationService(Context context, boolean startAtBoot) + { + try + { + if (Rule.getRuleCollection().size() > 0) + { + if (!AutomationService.isMyServiceRunning(context)) + { +// if(myServiceIntent == null) //do we need that line????? + myServiceIntent = new Intent(context, AutomationService.class); + myServiceIntent.putExtra("startAtBoot", startAtBoot); + context.startService(myServiceIntent); + } else + Miscellaneous.logEvent("w", "Service", context.getResources().getString(R.string.logServiceAlreadyRunning), 3); + } else + { + Toast.makeText(context, context.getResources().getString(R.string.serviceWontStart), Toast.LENGTH_LONG).show(); + activityMainScreenInstance.toggleService.setChecked(false); + } + } + catch (NullPointerException ne) + { + Toast.makeText(context, context.getResources().getString(R.string.serviceWontStart), Toast.LENGTH_LONG).show(); + activityMainScreenInstance.toggleService.setChecked(false); + } + catch (Exception e) + { + Toast.makeText(context, "Error: " + e.getMessage(), Toast.LENGTH_LONG).show(); + activityMainScreenInstance.toggleService.setChecked(false); + } + } + + private void stopAutomationService() + { + if (myServiceIntent == null) + myServiceIntent = new Intent(this, AutomationService.class); + stopService(myServiceIntent); + } + + @Override + protected void onRestart() + { + super.onRestart(); + toggleService.setChecked(AutomationService.isMyServiceRunning(this)); + ActivityMainScreen.updateMainScreen(); + } + + @Override + protected void onStart() + { + toggleService.setChecked(AutomationService.isMyServiceRunning(this)); + ActivityMainScreen.updateMainScreen(); + super.onStart(); + } + + @Override + protected void onResume() + { + super.onResume(); + toggleService.setChecked(AutomationService.isMyServiceRunning(this)); + ActivityMainScreen.updateMainScreen(); + + if(Build.VERSION.SDK_INT >= 28 && !Settings.noticeAndroid9MicrophoneShown && Rule.isAnyRuleUsing(Trigger_Enum.noiseLevel)) + { + Settings.noticeAndroid9MicrophoneShown = true; + Settings.writeSettings(ActivityMainScreen.this); + Miscellaneous.messageBox(getResources().getString(R.string.app_name), getResources().getString(R.string.android9RecordAudioNotice) + " " + getResources().getString(R.string.messageNotShownAgain), ActivityMainScreen.this).show(); + } + + if(Build.VERSION.SDK_INT >= 29 && !Settings.noticeAndroid10WifiShown && Rule.isAnyRuleUsing(Action.Action_Enum.setWifi)) + { + Settings.noticeAndroid10WifiShown = true; + Settings.writeSettings(ActivityMainScreen.this); + Miscellaneous.messageBox(getResources().getString(R.string.app_name), getResources().getString(R.string.android10WifiToggleNotice) + " " + getResources().getString(R.string.messageNotShownAgain), ActivityMainScreen.this).show(); + } + } + + @Override + protected void onDestroy() + { + super.onDestroy(); + activityMainScreenInstance = null; + } + + private void openPrivacyPolicy() + { + String privacyPolicyUrl = "http://server47.de/automation/privacy.html"; + + Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(privacyPolicyUrl)); + startActivity(browserIntent); + } + + private void updateListView() + { + Miscellaneous.logEvent("i", "ListView", "Attempting to update lvRuleHistory", 4); + try + { + if (lvRuleHistory.getAdapter() == null) + lvRuleHistory.setAdapter(ruleHistoryListViewAdapter); + + ruleHistoryListViewAdapter.notifyDataSetChanged(); + } + catch (NullPointerException e) + { + } + } + + public static void showMessageBox(String title, String text) + { + Miscellaneous.messageBox(title, text, ActivityMainScreen.getActivityMainScreenInstance()); + } + +} \ No newline at end of file diff --git a/app/src/fdroidFlavor/java/com/jens/automation2/Rule.java b/app/src/fdroidFlavor/java/com/jens/automation2/Rule.java new file mode 100644 index 0000000..d2b7b0a --- /dev/null +++ b/app/src/fdroidFlavor/java/com/jens/automation2/Rule.java @@ -0,0 +1,1394 @@ +package com.jens.automation2; + +import android.bluetooth.BluetoothDevice; +import android.content.Context; +import android.os.AsyncTask; +import android.os.Looper; +import android.util.Log; +import android.widget.Toast; + +import com.jens.automation2.location.WifiBroadcastReceiver; +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.PhoneStatusListener; +import com.jens.automation2.receivers.ProcessListener; + +import java.sql.Time; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Date; + + +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 static Date lastActivatedRuleActivationTime; + + 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(); + } + public String toStringLong() + { + String returnString = ""; + + if(isRuleActive()) + returnString += "Active: "; + else + returnString += "Inactive: "; + + returnString += this.getName() + ": If "; + + for(int i=0; i= 0 + && + Miscellaneous.compareTimes(nowTime, oneTrigger.getTimeFrame().getTriggerTimeStop()) > 0 + ) + | + // Other case, start time higher than end time, timeframe goes over midnight + ( + Miscellaneous.compareTimes(oneTrigger.getTimeFrame().getTriggerTimeStart(), oneTrigger.getTimeFrame().getTriggerTimeStop()) < 0 + && + (Miscellaneous.compareTimes(oneTrigger.getTimeFrame().getTriggerTimeStart(), nowTime) >= 0 + | + Miscellaneous.compareTimes(nowTime, oneTrigger.getTimeFrame().getTriggerTimeStop()) > 0) + ) + + ) + { + // We are in the timeframe + Miscellaneous.logEvent("i", "TimeFrame", "We're currently (" + calNow.getTime().toString() + ") in the specified TimeFrame (" + oneTrigger.getTimeFrame().toString() + "). Trigger of Rule " + this.getName() + " applies.", 3); + if(oneTrigger.getTriggerParameter()) + { + Miscellaneous.logEvent("i", "TimeFrame", "That's what's specified. Trigger of Rule " + this.getName() + " applies.", 3); + //return true; + } + else + { + Miscellaneous.logEvent("i", "TimeFrame", "That's not what's specified. Trigger of Rule " + this.getName() + " doesn't apply.", 3); + return false; + } + } + else + { + Miscellaneous.logEvent("i", "TimeFrame", "We're currently (" + calNow.getTime().toString() + ", Day: " + String.valueOf(calNow.get(Calendar.DAY_OF_WEEK)) + ") not in the specified TimeFrame (" + oneTrigger.getTimeFrame().toString() + ") because of the time. Trigger of Rule " + this.getName() + " doesn\'t apply..", 5); + if(!oneTrigger.getTriggerParameter()) + { + Miscellaneous.logEvent("i", "TimeFrame", "That's what's specified. Trigger of Rule " + this.getName() + " applies.", 5); + //return true; + } + else + { + Miscellaneous.logEvent("i", "TimeFrame", "That's not what's specified. Trigger of Rule " + this.getName() + " doesn't apply.", 5); + return false; + } + // return false; + } + } + else + { + Miscellaneous.logEvent("i", "TimeFrame", "We're currently (" + calNow.getTime().toString() + ", Day: " + String.valueOf(calNow.get(Calendar.DAY_OF_WEEK)) + ") not in the specified TimeFrame (" + oneTrigger.getTimeFrame().toString() + ") because of the day. Trigger of Rule " + this.getName() + " doesn\'t apply.", 5); + return false; + } + } + else if(oneTrigger.getTriggerType().equals(Trigger.Trigger_Enum.charging)) + { + if(BatteryReceiver.isDeviceCharging(context) == 0) + { + return false; // unknown charging state, can't activate rule under these conditions + } + else if(BatteryReceiver.isDeviceCharging(context) == 1) + { + if(oneTrigger.getTriggerParameter()) //rule says when charging, but we're currently discharging + return false; + } + else if(BatteryReceiver.isDeviceCharging(context) == 2) + { + if(!oneTrigger.getTriggerParameter()) //rule says when discharging, but we're currently charging + return false; + } + } + else if(oneTrigger.getTriggerType().equals(Trigger.Trigger_Enum.usb_host_connection)) + { + if(BatteryReceiver.isUsbHostConnected() != oneTrigger.getTriggerParameter()) + { + return false; + } + } + else if(oneTrigger.getTriggerType().equals(Trigger.Trigger_Enum.batteryLevel)) + { + if(oneTrigger.getTriggerParameter()) + { + if(BatteryReceiver.getBatteryLevel() < oneTrigger.getBatteryLevel()) + { + Miscellaneous.logEvent("i", String.format(context.getResources().getString(R.string.ruleCheckOf), this.getName()), context.getResources().getString(R.string.ruleDoesntApplyBatteryLowerThan) + " " + String.valueOf(oneTrigger.getBatteryLevel()), 3); + return false; + } + } + else + { + if(BatteryReceiver.getBatteryLevel() > oneTrigger.getBatteryLevel()) + { + Miscellaneous.logEvent("i", String.format(context.getResources().getString(R.string.ruleCheckOf), this.getName()), context.getResources().getString(R.string.ruleDoesntApplyBatteryHigherThan) + " " + String.valueOf(oneTrigger.getBatteryLevel()), 3); + return false; + } + } + } + else if(oneTrigger.getTriggerType().equals(Trigger.Trigger_Enum.speed)) + { + if(oneTrigger.getTriggerParameter()) + { + if(com.jens.automation2.location.LocationProvider.getSpeed() < oneTrigger.getSpeed()) + { + Miscellaneous.logEvent("i", String.format(context.getResources().getString(R.string.ruleCheckOf), this.getName()), context.getResources().getString(R.string.ruleDoesntApplyWeAreSlowerThan) + " " + String.valueOf(oneTrigger.getSpeed()), 3); + return false; + } + } + else + { + if(com.jens.automation2.location.LocationProvider.getSpeed() > oneTrigger.getSpeed()) + { + Miscellaneous.logEvent("i", String.format(context.getResources().getString(R.string.ruleCheckOf), this.getName()), context.getResources().getString(R.string.ruleDoesntApplyWeAreFasterThan) + " " + String.valueOf(oneTrigger.getSpeed()), 3); + return false; + } + } + } + else if(oneTrigger.getTriggerType().equals(Trigger.Trigger_Enum.noiseLevel)) + { + if(oneTrigger.getTriggerParameter()) + { + if(NoiseListener.getNoiseLevelDb() < oneTrigger.getNoiseLevelDb()) + { + Miscellaneous.logEvent("i", String.format(context.getResources().getString(R.string.ruleCheckOf), this.getName()), context.getResources().getString(R.string.ruleDoesntApplyItsQuieterThan) + " " + String.valueOf(oneTrigger.getNoiseLevelDb()), 3); + return false; + } + } + else + { + if(NoiseListener.getNoiseLevelDb() > oneTrigger.getNoiseLevelDb()) + { + Miscellaneous.logEvent("i", String.format(context.getResources().getString(R.string.ruleCheckOf), this.getName()), context.getResources().getString(R.string.ruleDoesntApplyItsLouderThan) + " " + String.valueOf(oneTrigger.getNoiseLevelDb()), 3); + return false; + } + } + } + else if(oneTrigger.getTriggerType().equals(Trigger.Trigger_Enum.wifiConnection)) + { + Miscellaneous.logEvent("i", String.format(context.getResources().getString(R.string.ruleCheckOf), this.getName()), "Checking for wifi state", 4); + if(oneTrigger.getTriggerParameter() == WifiBroadcastReceiver.lastConnectedState) // connected / disconnected + { + if(oneTrigger.getWifiName().length() > 0) // only check if any wifi name specified, otherwise any wifi will do + { + Miscellaneous.logEvent("i", String.format(context.getResources().getString(R.string.ruleCheckOf), this.getName()), "Wifi name specified, checking that.", 4); + if(!WifiBroadcastReceiver.getLastWifiSsid().equals(oneTrigger.getWifiName())) + { + Miscellaneous.logEvent("i", String.format(context.getResources().getString(R.string.ruleCheckOf), this.getName()), String.format(context.getResources().getString(R.string.ruleDoesntApplyNotTheCorrectSsid), oneTrigger.getWifiName(), WifiBroadcastReceiver.getLastWifiSsid()), 3); + return false; + } + else + Miscellaneous.logEvent("i", String.format(context.getResources().getString(R.string.ruleCheckOf), this.getName()), "Wifi name matches. Rule will apply.", 4); + } + else + Miscellaneous.logEvent("i", String.format(context.getResources().getString(R.string.ruleCheckOf), this.getName()), "No wifi name specified, any will do.", 4); + } + else + { + Miscellaneous.logEvent("i", String.format(context.getResources().getString(R.string.ruleCheckOf), this.getName()), "Wifi state not correct, demanded " + String.valueOf(oneTrigger.getTriggerParameter() + ", got " + String.valueOf(WifiBroadcastReceiver.lastConnectedState)), 4); + return false; + } + } + else if(oneTrigger.getTriggerType().equals(Trigger.Trigger_Enum.process_started_stopped)) + { + boolean running = ProcessListener.getRunningApps().contains(oneTrigger.getProcessName()); + + if(running) + Miscellaneous.logEvent("i", "ProcessMonitoring", "App " + oneTrigger.getProcessName() + " is currently running.", 4); + else + Miscellaneous.logEvent("i", "ProcessMonitoring", "App " + oneTrigger.getProcessName() + " is not running.", 4); + + if(running != oneTrigger.getTriggerParameter()) + { + Miscellaneous.logEvent("i", "ProcessMonitoring", "Trigger doesn't apply.", 4); + return false; + } + + Miscellaneous.logEvent("i", "ProcessMonitoring", "Trigger applies.", 4); + } + else if(oneTrigger.getTriggerType().equals(Trigger.Trigger_Enum.airplaneMode)) + { + if(ConnectivityReceiver.isAirplaneMode(context) != oneTrigger.getTriggerParameter()) + { + return false; + } + } + else if(oneTrigger.getTriggerType().equals(Trigger.Trigger_Enum.roaming)) + { + if(ConnectivityReceiver.isRoaming(context) != oneTrigger.getTriggerParameter()) + { + return false; + } + } + else if(oneTrigger.getTriggerType().equals(Trigger.Trigger_Enum.phoneCall)) + { + if(oneTrigger.getPhoneNumber().equals("any") | oneTrigger.getPhoneNumber().equals(PhoneStatusListener.getLastPhoneNumber())) + { + if(PhoneStatusListener.isInACall() == oneTrigger.getTriggerParameter()) + { + if(oneTrigger.getPhoneDirection() == 0 | (oneTrigger.getPhoneDirection() == PhoneStatusListener.getLastPhoneDirection())) + { + // Everything's allright + } + else + { + Miscellaneous.logEvent("i", "Rule", "Rule doesn't apply. Wrong direction. Demanded: " + String.valueOf(oneTrigger.getPhoneDirection()) + ", got: " + String.valueOf(PhoneStatusListener.getLastPhoneDirection()), 4); + return false; + } + } + else + { + Miscellaneous.logEvent("i", "Rule", "Rule doesn't apply. Wrong call status. Demanded: " + String.valueOf(oneTrigger.getTriggerParameter()) + ", got: " + String.valueOf(PhoneStatusListener.isInACall()), 4); + return false; + } + } + else + Miscellaneous.logEvent("i", "Rule", "Rule doesn't apply. Wrong phone number. Demanded: " + oneTrigger.getPhoneNumber() + ", got: " + PhoneStatusListener.getLastPhoneNumber(), 4); + } + else if(oneTrigger.getTriggerType().equals(Trigger.Trigger_Enum.nfcTag)) + { + if(NfcReceiver.lastReadLabel == null) + { + Miscellaneous.logEvent("i", String.format(context.getResources().getString(R.string.ruleCheckOf), this.getName()), context.getResources().getString(R.string.ruleDoesntApplyNoTagLabel), 3); + return false; + } + else if(!NfcReceiver.lastReadLabel.equals(oneTrigger.getNfcTagId())) + { + Miscellaneous.logEvent("i", String.format(context.getResources().getString(R.string.ruleCheckOf), this.getName()), context.getResources().getString(R.string.ruleDoesntApplyWrongTagLabel) + " " + NfcReceiver.lastReadLabel + " / " + oneTrigger.getNfcTagId(), 3); + return false; + } + } + else if(oneTrigger.getTriggerType().equals(Trigger.Trigger_Enum.bluetoothConnection)) + { + Miscellaneous.logEvent("i", String.format(context.getResources().getString(R.string.ruleCheckOf), this.getName()), "Checking for bluetooth...", 4); + +// if( // connected / disconnected +// (oneTrigger.getTriggerParameter() && (BluetoothReceiver.getLastAction().equals(android.bluetooth.BluetoothDevice.ACTION_ACL_CONNECTED) | BluetoothReceiver.getLastAction().equals("android.bluetooth.device.action.ACL_CONNECTED"))) +// | +// (!oneTrigger.getTriggerParameter() && (BluetoothReceiver.getLastAction().equals(android.bluetooth.BluetoothDevice.ACTION_ACL_DISCONNECT_REQUESTED) | BluetoothReceiver.getLastAction().equals(android.bluetooth.BluetoothDevice.ACTION_ACL_DISCONNECTED) | BluetoothReceiver.getLastAction().equals("android.bluetooth.device.action.ACTION_ACL_DISCONNECT_REQUESTED") | BluetoothReceiver.getLastAction().equals("android.bluetooth.device.action.ACTION_ACL_DISCONNECTED"))) +// ) +// { +// if(oneTrigger.getBluetoothDeviceAddress() != null) +// { +// if(oneTrigger.getBluetoothDeviceAddress().equals("")) +// { +// Miscellaneous.logEvent("i", String.format(context.getResources().getString(R.string.ruleCheckOf), this.getName()), "No bluetooth address specified, any will do.", 4); +// } +// else if(oneTrigger.getBluetoothDeviceAddress().equals("")) +// { +// // ??? +// } +// else +// { +// Miscellaneous.logEvent("i", String.format(context.getResources().getString(R.string.ruleCheckOf), this.getName()), "Bluetooth address specified, checking that.", 4); +// if(!BluetoothReceiver.getLastAffectedDevice().getAddress().equals(oneTrigger.getBluetoothDeviceAddress())) +// { +// Miscellaneous.logEvent("i", String.format(context.getResources().getString(R.string.ruleCheckOf), this.getName()), context.getResources().getString(R.string.ruleDoesntApplyNotTheCorrectDeviceAddress), 3); +// return false; +// } +// else +// Miscellaneous.logEvent("i", String.format(context.getResources().getString(R.string.ruleCheckOf), this.getName()), "Bluetooth address matches. Rule will apply.", 4); +// } +// } +// } +// else if(BluetoothReceiver.getLastAction().equals(android.bluetooth.BluetoothDevice.ACTION_FOUND) | BluetoothReceiver.getLastAction().equals(android.bluetooth.BluetoothDevice.ACTION_FOUND)) +// { +// if(!oneTrigger.getTriggerParameter()) +// { +// Miscellaneous.logEvent("i", String.format(context.getResources().getString(R.string.ruleCheckOf), this.getName()), context.getResources().getString(R.string.ruleDoesntApplyDeviceInRangeButShouldNotBe), 3); +// return false; +// } +// } +// else // above only checks for last action, this checks for things in the past + { + if(oneTrigger.getBluetoothDeviceAddress().equals("")) + { + if(oneTrigger.getBluetoothEvent().equals(BluetoothDevice.ACTION_ACL_CONNECTED)) + { + if(BluetoothReceiver.isAnyDeviceConnected() != oneTrigger.getTriggerParameter()) + return false; + } + else if((oneTrigger.getBluetoothEvent().equals(BluetoothDevice.ACTION_ACL_DISCONNECTED))) + { + if(BluetoothReceiver.isAnyDeviceConnected() != oneTrigger.getTriggerParameter()) + return false; + } + else + { + // range + if(BluetoothReceiver.isAnyDeviceInRange() != oneTrigger.getTriggerParameter()) + return false; + } + } + else if(oneTrigger.getBluetoothDeviceAddress().equals("")) + { + if(oneTrigger.getBluetoothEvent().equals(BluetoothDevice.ACTION_ACL_CONNECTED)) + { + if(BluetoothReceiver.isAnyDeviceConnected() == oneTrigger.getTriggerParameter()) + return false; + } + else if((oneTrigger.getBluetoothEvent().equals(BluetoothDevice.ACTION_ACL_DISCONNECTED))) + { + if(BluetoothReceiver.isAnyDeviceConnected() == oneTrigger.getTriggerParameter()) + return false; + } + else + { + // range + if(BluetoothReceiver.isAnyDeviceInRange() == oneTrigger.getTriggerParameter()) + return false; + } + } + else if(oneTrigger.getBluetoothDeviceAddress().length() > 0) + { + if(oneTrigger.getBluetoothEvent().equals(BluetoothDevice.ACTION_ACL_CONNECTED)) + { + if(BluetoothReceiver.isDeviceCurrentlyConnected(BluetoothReceiver.getDeviceByAddress(oneTrigger.getBluetoothDeviceAddress())) != oneTrigger.getTriggerParameter()) + return false; + } + else if((oneTrigger.getBluetoothEvent().equals(BluetoothDevice.ACTION_ACL_DISCONNECTED))) + { + if(BluetoothReceiver.isDeviceCurrentlyConnected(BluetoothReceiver.getDeviceByAddress(oneTrigger.getBluetoothDeviceAddress())) != oneTrigger.getTriggerParameter()) + return false; + } + else + { + // range + if(BluetoothReceiver.isDeviceInRange(BluetoothReceiver.getDeviceByAddress(oneTrigger.getBluetoothDeviceAddress())) != oneTrigger.getTriggerParameter()) + return false; + } + } + else + { + Miscellaneous.logEvent("i", String.format(context.getResources().getString(R.string.ruleCheckOf), this.getName()), context.getResources().getString(R.string.ruleDoesntApplyStateNotCorrect), 3); + return false; + } + } + } + else if(oneTrigger.getTriggerType().equals(Trigger.Trigger_Enum.headsetPlugged)) + { + if(HeadphoneJackListener.isHeadsetConnected() != oneTrigger.getTriggerParameter()) + return false; + else + if(oneTrigger.getHeadphoneType() != 2 && oneTrigger.getHeadphoneType() != HeadphoneJackListener.getHeadphoneType()) + { + Miscellaneous.logEvent("i", String.format(context.getResources().getString(R.string.ruleCheckOf), this.getName()), context.getResources().getString(R.string.ruleDoesntApplyWrongHeadphoneType), 3); + return false; + } + } + } + + return true; + } + + Miscellaneous.logEvent("i", String.format(context.getResources().getString(R.string.ruleCheckOf), this.getName()), String.format(context.getResources().getString(R.string.ruleIsDeactivatedCantApply), this.getName()), 3); + return false; + } + + private class ActivateRuleTask extends AsyncTask + { + @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(); + + 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) + { + AutomationService.updateNotification(); + ActivityMainScreen.updateMainScreen(); + super.onPostExecute(result); + } + + /** + * Will activate the rule. Should be called by a separate execution thread + * @param automationService + */ + protected void activateInternally(AutomationService automationService, boolean force) + { + boolean isActuallyToggable = isActuallyToggable(); + + boolean notLastActive = getLastActivatedRule() == null || !getLastActivatedRule().equals(Rule.this); + boolean doToggle = ruleToggle && isActuallyToggable; + + if(notLastActive | force | doToggle) + { + 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++) + Rule.this.getActionSet().get(i).run(automationService, doToggle); + + // 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); + } + } + } + + 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(boolean triggerParameter) + { + ArrayList ruleCandidates = new ArrayList(); + + for(Rule oneRule : ruleCollection) + { + innerloop: + for(Trigger oneTrigger : oneRule.getTriggerSet()) + { + if(oneTrigger.getTriggerType() == Trigger.Trigger_Enum.phoneCall) + { + 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 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 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()); + } +} diff --git a/app/src/googlePlayFlavor/java/com/jens/automation2/ActivityMainScreen.java b/app/src/googlePlayFlavor/java/com/jens/automation2/ActivityMainScreen.java new file mode 100644 index 0000000..543f141 --- /dev/null +++ b/app/src/googlePlayFlavor/java/com/jens/automation2/ActivityMainScreen.java @@ -0,0 +1,597 @@ +package com.jens.automation2; + +import android.annotation.SuppressLint; +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.view.MotionEvent; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.View.OnTouchListener; +import android.widget.ArrayAdapter; +import android.widget.Button; +import android.widget.CompoundButton; +import android.widget.CompoundButton.OnCheckedChangeListener; +import android.widget.ListView; +import android.widget.TextView; +import android.widget.Toast; +import android.widget.ToggleButton; + +import com.google.android.gms.appindexing.AppIndex; +import com.google.android.gms.appindexing.Thing; +import com.google.android.gms.common.api.GoogleApiClient; +import com.jens.automation2.AutomationService.serviceCommands; +import com.jens.automation2.Trigger.Trigger_Enum; +import com.jens.automation2.location.LocationProvider; + +import java.util.Calendar; + +@SuppressLint("NewApi") +public class ActivityMainScreen extends ActivityGeneric +{ + private static boolean guiChangeInProgress = false; + + private static ActivityMainScreen activityMainScreenInstance = null; + private ToggleButton toggleService, tbLockSound; + private Button bShowHelp, bPrivacy, bSettingsErase, bSettingsSetToDefault, bVolumeTest, bAddSoundLockTIme; + private TextView tvActivePoi, tvClosestPoi, tvLastRule, tvMainScreenNote, tvlockSoundDuration; + + private ListView lvRuleHistory; + private ArrayAdapter ruleHistoryListViewAdapter; + + private static boolean uiUpdateRunning = false; + /** + * ATTENTION: This was auto-generated to implement the App Indexing API. + * See https://g.co/AppIndexing/AndroidStudio for more information. + */ + private GoogleApiClient client; + + @Override + public void onCreate(Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + setContentView(R.layout.main_overview_layout); + + activityMainScreenInstance = this; + + if(ActivityPermissions.needMorePermissions(ActivityMainScreen.this)) + { + Intent permissionsIntent = new Intent(ActivityMainScreen.this, ActivityPermissions.class); + startActivityForResult(permissionsIntent, 7000); + } + + Settings.readFromPersistentStorage(this); + + guiChangeInProgress = true; + + tvActivePoi = (TextView) findViewById(R.id.tvActivePoi); + tvClosestPoi = (TextView) findViewById(R.id.tvClosestPoi); + lvRuleHistory = (ListView) findViewById(R.id.lvRuleHistory); + tvLastRule = (TextView) findViewById(R.id.tvTimeFrameHelpText); + tvMainScreenNote = (TextView) findViewById(R.id.tvMainScreenNote); + tvlockSoundDuration = (TextView)findViewById(R.id.tvlockSoundDuration); + tbLockSound = (ToggleButton) findViewById(R.id.tbLockSound); + toggleService = (ToggleButton) findViewById(R.id.tbArmMastListener); + toggleService.setChecked(AutomationService.isMyServiceRunning(this)); + toggleService.setOnCheckedChangeListener(new OnCheckedChangeListener() + { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) + { + if (!ActivityMainScreen.this.uiUpdateRunning) + { + if (toggleService.isChecked()) + { + startAutomationService(getBaseContext(), false); + } else + { + stopAutomationService(); + } + } + } + }); + + tvMainScreenNote.setOnClickListener(new OnClickListener() + { + @Override + public void onClick(View v) + { + Intent intent = new Intent(ActivityMainScreen.this, ActivityPermissions.class); + startActivityForResult(intent, ActivityPermissions.requestCodeForPermissions); + } + }); + + tbLockSound.setOnCheckedChangeListener(new OnCheckedChangeListener() + { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) + { + Settings.lockSoundChanges = isChecked; + + if(!isChecked) + { + AutomationService.getInstance().nullLockSoundChangesEnd(); + updateMainScreen(); + } + + if (!guiChangeInProgress) + Settings.writeSettings(ActivityMainScreen.this); + } + }); + + Button bSettings = (Button) findViewById(R.id.bSettings); + bSettings.setOnClickListener(new OnClickListener() + { + @Override + public void onClick(View v) + { + Intent myIntent = new Intent(ActivityMainScreen.this, ActivitySettings.class); + startActivityForResult(myIntent, 6000); + } + }); + + Button bVolumeTest = (Button) findViewById(R.id.bVolumeTest); + bVolumeTest.setOnClickListener(new OnClickListener() + { + @Override + public void onClick(View v) + { + Intent intent = new Intent(ActivityMainScreen.this, ActivityVolumeTest.class); + startActivity(intent); + } + }); + + bShowHelp = (Button) findViewById(R.id.bShowHelp); + bShowHelp.setOnClickListener(new OnClickListener() + { + + @Override + public void onClick(View v) + { + Intent showHelpIntent = new Intent(ActivityMainScreen.this, ActivityHelp.class); + startActivity(showHelpIntent); + } + }); + + bPrivacy = (Button) findViewById(R.id.bPrivacy); + bPrivacy.setOnClickListener(new OnClickListener() + { + @Override + public void onClick(View v) + { + AlertDialog.Builder builder = new AlertDialog.Builder(ActivityMainScreen.this); + builder.setMessage(getResources().getString(R.string.privacyConfirmationText)); + builder.setPositiveButton(getResources().getString(R.string.yes), new DialogInterface.OnClickListener() + { + @Override + public void onClick(DialogInterface dialog, int which) + { + openPrivacyPolicy(); + } + }); + builder.setNegativeButton(getResources().getString(R.string.no), null); + builder.create().show(); + } + }); + + /*bSettingsErase = (Button)findViewById(R.id.bSettingsErase); + bSettingsErase.setOnClickListener(new OnClickListener() + { + @Override + public void onClick(View v) + { + getEraseSettingsDialog(ActivityMainScreen.this).show(); + } + });*/ + bSettingsSetToDefault = (Button) findViewById(R.id.bSettingsSetToDefault); + bSettingsSetToDefault.setOnClickListener(new OnClickListener() + { + @Override + public void onClick(View v) + { + getDefaultSettingsDialog(ActivityMainScreen.this).show(); + } + }); + + lvRuleHistory.setOnTouchListener(new OnTouchListener() + { + @Override + public boolean onTouch(View v, MotionEvent event) + { + v.getParent().requestDisallowInterceptTouchEvent(true); + return false; + } + }); + + bAddSoundLockTIme = (Button)findViewById(R.id.bAddSoundLockTIme); + bAddSoundLockTIme.setText("+" + Settings.lockSoundChangesInterval + " min"); + bAddSoundLockTIme.setOnClickListener(new OnClickListener() + { + @Override + public void onClick(View view) + { + if(AutomationService.isMyServiceRunning(ActivityMainScreen.this)) + { + AutomationService.getInstance().lockSoundChangesEndAddTime(); + ActivityMainScreen.updateMainScreen(); + } + else + Toast.makeText(ActivityMainScreen.this, getResources().getString(R.string.serviceNotRunning), Toast.LENGTH_LONG).show(); + } + }); + + ruleHistoryListViewAdapter = new ArrayAdapter(this, R.layout.text_view_for_poi_listview_mediumtextsize, Rule.getRuleRunHistory()); + + if (PointOfInterest.getPointOfInterestCollection() == null | PointOfInterest.getPointOfInterestCollection().size() == 0) + PointOfInterest.loadPoisFromFile(); + if (Rule.getRuleCollection() == null | Rule.getRuleCollection().size() == 0) + Rule.readFromFile(); + + ActivityMainScreen.updateMainScreen(); + + this.storeServiceReferenceInVariable(); + + guiChangeInProgress = false; + // ATTENTION: This was auto-generated to implement the App Indexing API. + // See https://g.co/AppIndexing/AndroidStudio for more information. + client = new GoogleApiClient.Builder(this).addApi(AppIndex.API).build(); + } + + private static AlertDialog getEraseSettingsDialog(final Context context) + { + AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(context); + alertDialogBuilder.setTitle(context.getResources().getString(R.string.areYouSure)); + alertDialogBuilder.setPositiveButton(context.getResources().getString(R.string.yes), new DialogInterface.OnClickListener() + { + @Override + public void onClick(DialogInterface dialog, int which) + { + if (Settings.eraseSettings(context)) + Toast.makeText(context, context.getResources().getString(R.string.settingsErased), Toast.LENGTH_LONG).show(); + } + }); + alertDialogBuilder.setNegativeButton(context.getResources().getString(R.string.no), null); + AlertDialog alertDialog = alertDialogBuilder.create(); + + return alertDialog; + } + + private static AlertDialog getDefaultSettingsDialog(final Context context) + { + AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(context); + alertDialogBuilder.setTitle(context.getResources().getString(R.string.areYouSure)); + alertDialogBuilder.setPositiveButton(context.getResources().getString(R.string.yes), new DialogInterface.OnClickListener() + { + @Override + public void onClick(DialogInterface dialog, int which) + { + if (Settings.initializeSettings(context, true)) + Toast.makeText(context, context.getResources().getString(R.string.settingsSetToDefault), Toast.LENGTH_LONG).show(); + } + }); + alertDialogBuilder.setNegativeButton(context.getResources().getString(R.string.no), null); + AlertDialog alertDialog = alertDialogBuilder.create(); + + return alertDialog; + } + + public static ActivityMainScreen getActivityMainScreenInstance() + { + return activityMainScreenInstance; + } + + public static void updateMainScreen() + { + Miscellaneous.logEvent("i", "MainScreen", "Request to update notification.", 5); + + if (activityMainScreenInstance != null) + { + if(ActivityPermissions.needMorePermissions(activityMainScreenInstance)) + { + activityMainScreenInstance.tvMainScreenNote.setText(R.string.mainScreenPermissionNote); + activityMainScreenInstance.tvMainScreenNote.setVisibility(View.VISIBLE); + } + else + { + activityMainScreenInstance.tvMainScreenNote.setText(""); + activityMainScreenInstance.tvMainScreenNote.setVisibility(View.GONE); + } + + if (AutomationService.isMyServiceRunning(activityMainScreenInstance)) + { + Miscellaneous.logEvent("i", "MainScreen", "Service is running. Updating mainscreen with this info.", 5); + uiUpdateRunning = true; + activityMainScreenInstance.toggleService.setChecked(true); + uiUpdateRunning = false; + // if(activityMainScreenInstance.hasWindowFocus()) + // { + try + { + PointOfInterest activePoi = PointOfInterest.getActivePoi(); + if (activePoi == null) + { + PointOfInterest closestPoi = PointOfInterest.getClosestPOI(LocationProvider.getInstance().getCurrentLocation()); + activityMainScreenInstance.tvActivePoi.setText("none"); + activityMainScreenInstance.tvClosestPoi.setText(closestPoi.getName()); + } + else + { + activityMainScreenInstance.tvActivePoi.setText(activePoi.getName()); + activityMainScreenInstance.tvClosestPoi.setText("n./a."); + } + } + catch (NullPointerException e) + { + if (PointOfInterest.getPointOfInterestCollection().size() > 0) + { + if( + Rule.isAnyRuleUsing(Trigger_Enum.pointOfInterest) + && + ActivityPermissions.havePermission(ActivityPermissions.permissionNameLocationCoarse, AutomationService.getInstance()) + && + ActivityPermissions.havePermission(ActivityPermissions.permissionNameLocationFine, AutomationService.getInstance()) + ) + activityMainScreenInstance.tvActivePoi.setText(activityMainScreenInstance.getResources().getString(R.string.stillGettingPosition)); + else + activityMainScreenInstance.tvActivePoi.setText(activityMainScreenInstance.getResources().getString(R.string.locationEngineNotActive)); + + activityMainScreenInstance.tvClosestPoi.setText("n./a."); + } + else + { + activityMainScreenInstance.tvActivePoi.setText(activityMainScreenInstance.getResources().getString(R.string.noPoisDefinedShort)); + activityMainScreenInstance.tvClosestPoi.setText("n./a."); + } + } + + try + { + activityMainScreenInstance.tvLastRule.setText(Rule.getLastActivatedRule().getName() + " " + activityMainScreenInstance.getResources().getString(R.string.at) + " " + Rule.getLastActivatedRuleActivationTime().toLocaleString()); + activityMainScreenInstance.updateListView(); + } + catch (Exception e) + { + activityMainScreenInstance.tvLastRule.setText("n./a."); + } + } + else + { + Miscellaneous.logEvent("i", "MainScreen", "Service not running. Updating mainscreen with this info.", 5); + activityMainScreenInstance.toggleService.setChecked(false); + activityMainScreenInstance.tvActivePoi.setText(activityMainScreenInstance.getResources().getString(R.string.serviceNotRunning)); + activityMainScreenInstance.tvClosestPoi.setText(""); + activityMainScreenInstance.tvLastRule.setText(""); + } + +// uiUpdateRunning = true; + if(AutomationService.isMyServiceRunning(ActivityMainScreen.getActivityMainScreenInstance()) && AutomationService.getInstance() != null) + { + AutomationService.getInstance().checkLockSoundChangesTimeElapsed(); + + Calendar end = AutomationService.getInstance().getLockSoundChangesEnd(); + activityMainScreenInstance.tbLockSound.setChecked(end != null); + activityMainScreenInstance.tbLockSound.setEnabled(end != null); + + if(end != null) + { + Calendar now = Calendar.getInstance(); + long millis = end.getTimeInMillis() - now.getTimeInMillis(); + long minutes = millis/1000/60; + if(minutes < 60) + activityMainScreenInstance.tvlockSoundDuration.setText(String.valueOf(minutes + " min...")); + else + { + double hours = (double)minutes / 60.0; + activityMainScreenInstance.tvlockSoundDuration.setText(String.valueOf(Math.round(hours * 100.0) / 100.0) + " h..."); + } + } + else + activityMainScreenInstance.tvlockSoundDuration.setText(String.valueOf("")); + } + else + { + activityMainScreenInstance.tbLockSound.setChecked(false); + activityMainScreenInstance.tbLockSound.setEnabled(false); + activityMainScreenInstance.tvlockSoundDuration.setText(""); + } + Settings.writeSettings(activityMainScreenInstance); +// uiUpdateRunning = false; +// } +// else +// Miscellaneous.logEvent("i", "ActivityMainScreen", "Window doesn't have focus. We're not updating anything.", 5); + } + else + Miscellaneous.logEvent("i", "ActivityMainScreen", "Activity not running. No need to update.", 5); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) + { + super.onActivityResult(requestCode, resultCode, data); + +// Miscellaneous.logEvent("i", "ListView", "Notifying ListViewAdapter", 4); + + if (AutomationService.isMyServiceRunning(this)) + bindToService(); + + switch (requestCode) + { + case ActivityPermissions.requestCodeForPermissions: + updateMainScreen(); + break; + case 6000: //settings + Settings.readFromPersistentStorage(this); + + if (boundToService && AutomationService.isMyServiceRunning(this)) + myAutomationService.serviceInterface(serviceCommands.reloadSettings); + + if(AutomationService.isMyServiceRunning(ActivityMainScreen.this)) + Toast.makeText(this, getResources().getString(R.string.settingsWillTakeTime), Toast.LENGTH_LONG).show(); + + break; + } + + if (AutomationService.isMyServiceRunning(this)) + { + // Let service reload via binding interface. + if (boundToService) + { + myAutomationService.serviceInterface(serviceCommands.updateNotification); //in case names got changed. + unBindFromService(); + } + } + else + { + // Let service reload classically. + AutomationService service = AutomationService.getInstance(); + if (service != null) + service.applySettingsAndRules(); + } + } + + public static void startAutomationService(Context context, boolean startAtBoot) + { + try + { + if (Rule.getRuleCollection().size() > 0) + { + if (!AutomationService.isMyServiceRunning(context)) + { +// if(myServiceIntent == null) //do we need that line????? + myServiceIntent = new Intent(context, AutomationService.class); + myServiceIntent.putExtra("startAtBoot", startAtBoot); + context.startService(myServiceIntent); + } else + Miscellaneous.logEvent("w", "Service", context.getResources().getString(R.string.logServiceAlreadyRunning), 3); + } else + { + Toast.makeText(context, context.getResources().getString(R.string.serviceWontStart), Toast.LENGTH_LONG).show(); + activityMainScreenInstance.toggleService.setChecked(false); + } + } + catch (NullPointerException ne) + { + Toast.makeText(context, context.getResources().getString(R.string.serviceWontStart), Toast.LENGTH_LONG).show(); + activityMainScreenInstance.toggleService.setChecked(false); + } + catch (Exception e) + { + Toast.makeText(context, "Error: " + e.getMessage(), Toast.LENGTH_LONG).show(); + activityMainScreenInstance.toggleService.setChecked(false); + } + } + + private void stopAutomationService() + { + if (myServiceIntent == null) + myServiceIntent = new Intent(this, AutomationService.class); + stopService(myServiceIntent); + } + + @Override + protected void onRestart() + { + super.onRestart(); + toggleService.setChecked(AutomationService.isMyServiceRunning(this)); + ActivityMainScreen.updateMainScreen(); + } + + @Override + protected void onStart() + { + super.onStart(); + client.connect(); + toggleService.setChecked(AutomationService.isMyServiceRunning(this)); + ActivityMainScreen.updateMainScreen(); + AppIndex.AppIndexApi.start(client, getIndexApiAction()); + } + + @Override + protected void onResume() + { + super.onResume(); + toggleService.setChecked(AutomationService.isMyServiceRunning(this)); + ActivityMainScreen.updateMainScreen(); + + if(Build.VERSION.SDK_INT >= 28 && !Settings.noticeAndroid9MicrophoneShown && Rule.isAnyRuleUsing(Trigger_Enum.noiseLevel)) + { + Settings.noticeAndroid9MicrophoneShown = true; + Settings.writeSettings(ActivityMainScreen.this); + Miscellaneous.messageBox(getResources().getString(R.string.app_name), getResources().getString(R.string.android9RecordAudioNotice) + " " + getResources().getString(R.string.messageNotShownAgain), ActivityMainScreen.this).show(); + } + + if(Build.VERSION.SDK_INT >= 29 && !Settings.noticeAndroid10WifiShown && Rule.isAnyRuleUsing(Action.Action_Enum.setWifi)) + { + Settings.noticeAndroid10WifiShown = true; + Settings.writeSettings(ActivityMainScreen.this); + Miscellaneous.messageBox(getResources().getString(R.string.app_name), getResources().getString(R.string.android10WifiToggleNotice) + " " + getResources().getString(R.string.messageNotShownAgain), ActivityMainScreen.this).show(); + } + } + + @Override + protected void onDestroy() + { + super.onDestroy(); + activityMainScreenInstance = null; + } + + private void openPrivacyPolicy() + { + String privacyPolicyUrl = "http://server47.de/automation/privacy.html"; + + Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(privacyPolicyUrl)); + startActivity(browserIntent); + } + + private void updateListView() + { + Miscellaneous.logEvent("i", "ListView", "Attempting to update lvRuleHistory", 4); + try + { + if (lvRuleHistory.getAdapter() == null) + lvRuleHistory.setAdapter(ruleHistoryListViewAdapter); + + ruleHistoryListViewAdapter.notifyDataSetChanged(); + } catch (NullPointerException e) + { + } + } + + /** + * ATTENTION: This was auto-generated to implement the App Indexing API. + * See https://g.co/AppIndexing/AndroidStudio for more information. + */ + public com.google.android.gms.appindexing.Action getIndexApiAction() + { + Thing object = new Thing.Builder() + .setName("ActivityMainScreen Page") // TODO: Define a title for the content shown. + // TODO: Make sure this auto-generated URL is correct. + .setUrl(Uri.parse("http://[ENTER-YOUR-URL-HERE]")) + .build(); + return new com.google.android.gms.appindexing.Action.Builder(com.google.android.gms.appindexing.Action.TYPE_VIEW) + .setObject(object) + .setActionStatus(com.google.android.gms.appindexing.Action.STATUS_TYPE_COMPLETED) + .build(); + } + + @Override + public void onStop() + { + super.onStop(); + + // ATTENTION: This was auto-generated to implement the App Indexing API. + // See https://g.co/AppIndexing/AndroidStudio for more information. + AppIndex.AppIndexApi.end(client, getIndexApiAction()); + client.disconnect(); + } + + public static void showMessageBox(String title, String text) + { + Miscellaneous.messageBox(title, text, ActivityMainScreen.getActivityMainScreenInstance()); + } + +} \ No newline at end of file diff --git a/app/src/googlePlayFlavor/java/com/jens/automation2/MyGoogleServicesInterface.java b/app/src/googlePlayFlavor/java/com/jens/automation2/MyGoogleServicesInterface.java new file mode 100644 index 0000000..1d983ec --- /dev/null +++ b/app/src/googlePlayFlavor/java/com/jens/automation2/MyGoogleServicesInterface.java @@ -0,0 +1,16 @@ +package com.jens.automation2; + +import com.google.android.gms.common.ConnectionResult; +import com.google.android.gms.common.GoogleApiAvailability; + +public class MiscellaneousWithGoogleServices +{ + public static boolean isPlayServiceAvailable() + { + GoogleApiAvailability aa = new GoogleApiAvailability(); + if(aa.isGooglePlayServicesAvailable(Miscellaneous.getAnyContext()) == ConnectionResult.SUCCESS) + return true; + else + return false; + } +} \ No newline at end of file diff --git a/app/src/googlePlayFlavor/java/com/jens/automation2/Rule.java b/app/src/googlePlayFlavor/java/com/jens/automation2/Rule.java new file mode 100644 index 0000000..e6e5801 --- /dev/null +++ b/app/src/googlePlayFlavor/java/com/jens/automation2/Rule.java @@ -0,0 +1,1425 @@ +package com.jens.automation2; + +import android.bluetooth.BluetoothDevice; +import android.content.Context; +import android.os.AsyncTask; +import android.os.Looper; +import android.util.Log; +import android.widget.Toast; + +import com.google.android.gms.location.DetectedActivity; +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.PhoneStatusListener; +import com.jens.automation2.receivers.ProcessListener; + +import java.sql.Time; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Date; + + +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 static Date lastActivatedRuleActivationTime; + + 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(); + } + public String toStringLong() + { + String returnString = ""; + + if(isRuleActive()) + returnString += "Active: "; + else + returnString += "Inactive: "; + + returnString += this.getName() + ": If "; + + for(int i=0; i= 0 + && + Miscellaneous.compareTimes(nowTime, oneTrigger.getTimeFrame().getTriggerTimeStop()) > 0 + ) + | + // Other case, start time higher than end time, timeframe goes over midnight + ( + Miscellaneous.compareTimes(oneTrigger.getTimeFrame().getTriggerTimeStart(), oneTrigger.getTimeFrame().getTriggerTimeStop()) < 0 + && + (Miscellaneous.compareTimes(oneTrigger.getTimeFrame().getTriggerTimeStart(), nowTime) >= 0 + | + Miscellaneous.compareTimes(nowTime, oneTrigger.getTimeFrame().getTriggerTimeStop()) > 0) + ) + + ) + { + // We are in the timeframe + Miscellaneous.logEvent("i", "TimeFrame", "We're currently (" + calNow.getTime().toString() + ") in the specified TimeFrame (" + oneTrigger.getTimeFrame().toString() + "). Trigger of Rule " + this.getName() + " applies.", 3); + if(oneTrigger.getTriggerParameter()) + { + Miscellaneous.logEvent("i", "TimeFrame", "That's what's specified. Trigger of Rule " + this.getName() + " applies.", 3); + //return true; + } + else + { + Miscellaneous.logEvent("i", "TimeFrame", "That's not what's specified. Trigger of Rule " + this.getName() + " doesn't apply.", 3); + return false; + } + } + else + { + Miscellaneous.logEvent("i", "TimeFrame", "We're currently (" + calNow.getTime().toString() + ", Day: " + String.valueOf(calNow.get(Calendar.DAY_OF_WEEK)) + ") not in the specified TimeFrame (" + oneTrigger.getTimeFrame().toString() + ") because of the time. Trigger of Rule " + this.getName() + " doesn\'t apply..", 5); + if(!oneTrigger.getTriggerParameter()) + { + Miscellaneous.logEvent("i", "TimeFrame", "That's what's specified. Trigger of Rule " + this.getName() + " applies.", 5); + //return true; + } + else + { + Miscellaneous.logEvent("i", "TimeFrame", "That's not what's specified. Trigger of Rule " + this.getName() + " doesn't apply.", 5); + return false; + } + // return false; + } + } + else + { + Miscellaneous.logEvent("i", "TimeFrame", "We're currently (" + calNow.getTime().toString() + ", Day: " + String.valueOf(calNow.get(Calendar.DAY_OF_WEEK)) + ") not in the specified TimeFrame (" + oneTrigger.getTimeFrame().toString() + ") because of the day. Trigger of Rule " + this.getName() + " doesn\'t apply.", 5); + return false; + } + } + else if(oneTrigger.getTriggerType().equals(Trigger.Trigger_Enum.charging)) + { + if(BatteryReceiver.isDeviceCharging(context) == 0) + { + return false; // unknown charging state, can't activate rule under these conditions + } + else if(BatteryReceiver.isDeviceCharging(context) == 1) + { + if(oneTrigger.getTriggerParameter()) //rule says when charging, but we're currently discharging + return false; + } + else if(BatteryReceiver.isDeviceCharging(context) == 2) + { + if(!oneTrigger.getTriggerParameter()) //rule says when discharging, but we're currently charging + return false; + } + } + else if(oneTrigger.getTriggerType().equals(Trigger.Trigger_Enum.usb_host_connection)) + { + if(BatteryReceiver.isUsbHostConnected() != oneTrigger.getTriggerParameter()) + { + return false; + } + } + else if(oneTrigger.getTriggerType().equals(Trigger.Trigger_Enum.batteryLevel)) + { + if(oneTrigger.getTriggerParameter()) + { + if(BatteryReceiver.getBatteryLevel() < oneTrigger.getBatteryLevel()) + { + Miscellaneous.logEvent("i", String.format(context.getResources().getString(R.string.ruleCheckOf), this.getName()), context.getResources().getString(R.string.ruleDoesntApplyBatteryLowerThan) + " " + String.valueOf(oneTrigger.getBatteryLevel()), 3); + return false; + } + } + else + { + if(BatteryReceiver.getBatteryLevel() > oneTrigger.getBatteryLevel()) + { + Miscellaneous.logEvent("i", String.format(context.getResources().getString(R.string.ruleCheckOf), this.getName()), context.getResources().getString(R.string.ruleDoesntApplyBatteryHigherThan) + " " + String.valueOf(oneTrigger.getBatteryLevel()), 3); + return false; + } + } + } + else if(oneTrigger.getTriggerType().equals(Trigger.Trigger_Enum.speed)) + { + if(oneTrigger.getTriggerParameter()) + { + if(com.jens.automation2.location.LocationProvider.getSpeed() < oneTrigger.getSpeed()) + { + Miscellaneous.logEvent("i", String.format(context.getResources().getString(R.string.ruleCheckOf), this.getName()), context.getResources().getString(R.string.ruleDoesntApplyWeAreSlowerThan) + " " + String.valueOf(oneTrigger.getSpeed()), 3); + return false; + } + } + else + { + if(com.jens.automation2.location.LocationProvider.getSpeed() > oneTrigger.getSpeed()) + { + Miscellaneous.logEvent("i", String.format(context.getResources().getString(R.string.ruleCheckOf), this.getName()), context.getResources().getString(R.string.ruleDoesntApplyWeAreFasterThan) + " " + String.valueOf(oneTrigger.getSpeed()), 3); + return false; + } + } + } + else if(oneTrigger.getTriggerType().equals(Trigger.Trigger_Enum.noiseLevel)) + { + if(oneTrigger.getTriggerParameter()) + { + if(NoiseListener.getNoiseLevelDb() < oneTrigger.getNoiseLevelDb()) + { + Miscellaneous.logEvent("i", String.format(context.getResources().getString(R.string.ruleCheckOf), this.getName()), context.getResources().getString(R.string.ruleDoesntApplyItsQuieterThan) + " " + String.valueOf(oneTrigger.getNoiseLevelDb()), 3); + return false; + } + } + else + { + if(NoiseListener.getNoiseLevelDb() > oneTrigger.getNoiseLevelDb()) + { + Miscellaneous.logEvent("i", String.format(context.getResources().getString(R.string.ruleCheckOf), this.getName()), context.getResources().getString(R.string.ruleDoesntApplyItsLouderThan) + " " + String.valueOf(oneTrigger.getNoiseLevelDb()), 3); + return false; + } + } + } + else if(oneTrigger.getTriggerType().equals(Trigger.Trigger_Enum.wifiConnection)) + { + Miscellaneous.logEvent("i", String.format(context.getResources().getString(R.string.ruleCheckOf), this.getName()), "Checking for wifi state", 4); + if(oneTrigger.getTriggerParameter() == WifiBroadcastReceiver.lastConnectedState) // connected / disconnected + { + if(oneTrigger.getWifiName().length() > 0) // only check if any wifi name specified, otherwise any wifi will do + { + Miscellaneous.logEvent("i", String.format(context.getResources().getString(R.string.ruleCheckOf), this.getName()), "Wifi name specified, checking that.", 4); + if(!WifiBroadcastReceiver.getLastWifiSsid().equals(oneTrigger.getWifiName())) + { + Miscellaneous.logEvent("i", String.format(context.getResources().getString(R.string.ruleCheckOf), this.getName()), String.format(context.getResources().getString(R.string.ruleDoesntApplyNotTheCorrectSsid), oneTrigger.getWifiName(), WifiBroadcastReceiver.getLastWifiSsid()), 3); + return false; + } + else + Miscellaneous.logEvent("i", String.format(context.getResources().getString(R.string.ruleCheckOf), this.getName()), "Wifi name matches. Rule will apply.", 4); + } + else + Miscellaneous.logEvent("i", String.format(context.getResources().getString(R.string.ruleCheckOf), this.getName()), "No wifi name specified, any will do.", 4); + } + else + { + Miscellaneous.logEvent("i", String.format(context.getResources().getString(R.string.ruleCheckOf), this.getName()), "Wifi state not correct, demanded " + String.valueOf(oneTrigger.getTriggerParameter() + ", got " + String.valueOf(WifiBroadcastReceiver.lastConnectedState)), 4); + return false; + } + } + else if(oneTrigger.getTriggerType().equals(Trigger.Trigger_Enum.process_started_stopped)) + { + boolean running = ProcessListener.getRunningApps().contains(oneTrigger.getProcessName()); + + if(running) + Miscellaneous.logEvent("i", "ProcessMonitoring", "App " + oneTrigger.getProcessName() + " is currently running.", 4); + else + Miscellaneous.logEvent("i", "ProcessMonitoring", "App " + oneTrigger.getProcessName() + " is not running.", 4); + + if(running != oneTrigger.getTriggerParameter()) + { + Miscellaneous.logEvent("i", "ProcessMonitoring", "Trigger doesn't apply.", 4); + return false; + } + + Miscellaneous.logEvent("i", "ProcessMonitoring", "Trigger applies.", 4); + } + else if(oneTrigger.getTriggerType().equals(Trigger.Trigger_Enum.airplaneMode)) + { + if(ConnectivityReceiver.isAirplaneMode(context) != oneTrigger.getTriggerParameter()) + { + return false; + } + } + else if(oneTrigger.getTriggerType().equals(Trigger.Trigger_Enum.roaming)) + { + if(ConnectivityReceiver.isRoaming(context) != oneTrigger.getTriggerParameter()) + { + return false; + } + } + else if(oneTrigger.getTriggerType().equals(Trigger.Trigger_Enum.phoneCall)) + { + if(oneTrigger.getPhoneNumber().equals("any") | oneTrigger.getPhoneNumber().equals(PhoneStatusListener.getLastPhoneNumber())) + { + if(PhoneStatusListener.isInACall() == oneTrigger.getTriggerParameter()) + { + if(oneTrigger.getPhoneDirection() == 0 | (oneTrigger.getPhoneDirection() == PhoneStatusListener.getLastPhoneDirection())) + { + // Everything's allright + } + else + { + Miscellaneous.logEvent("i", "Rule", "Rule doesn't apply. Wrong direction. Demanded: " + String.valueOf(oneTrigger.getPhoneDirection()) + ", got: " + String.valueOf(PhoneStatusListener.getLastPhoneDirection()), 4); + return false; + } + } + else + { + Miscellaneous.logEvent("i", "Rule", "Rule doesn't apply. Wrong call status. Demanded: " + String.valueOf(oneTrigger.getTriggerParameter()) + ", got: " + String.valueOf(PhoneStatusListener.isInACall()), 4); + return false; + } + } + else + Miscellaneous.logEvent("i", "Rule", "Rule doesn't apply. Wrong phone number. Demanded: " + oneTrigger.getPhoneNumber() + ", got: " + PhoneStatusListener.getLastPhoneNumber(), 4); + } + else if(oneTrigger.getTriggerType().equals(Trigger.Trigger_Enum.nfcTag)) + { + if(NfcReceiver.lastReadLabel == null) + { + Miscellaneous.logEvent("i", String.format(context.getResources().getString(R.string.ruleCheckOf), this.getName()), context.getResources().getString(R.string.ruleDoesntApplyNoTagLabel), 3); + return false; + } + else if(!NfcReceiver.lastReadLabel.equals(oneTrigger.getNfcTagId())) + { + Miscellaneous.logEvent("i", String.format(context.getResources().getString(R.string.ruleCheckOf), this.getName()), context.getResources().getString(R.string.ruleDoesntApplyWrongTagLabel) + " " + NfcReceiver.lastReadLabel + " / " + oneTrigger.getNfcTagId(), 3); + return false; + } + } + else if(oneTrigger.getTriggerType().equals(Trigger.Trigger_Enum.activityDetection)) + { + if(ActivityDetectionReceiver.getActivityDetectionLastResult() != null) + { + boolean found = false; + for(DetectedActivity oneDetectedActivity : ActivityDetectionReceiver.getActivityDetectionLastResult().getProbableActivities()) + { + if(oneDetectedActivity.getType() == oneTrigger.getActivityDetectionType()) + found = true; + } + + if(!found) + { + Miscellaneous.logEvent("i", String.format(context.getResources().getString(R.string.ruleCheckOf), this.getName()), String.format(context.getResources().getString(R.string.ruleDoesntApplyActivityNotPresent), ActivityDetectionReceiver.getDescription(oneTrigger.getActivityDetectionType())), 3); + return false; + } + else + { + for(DetectedActivity oneDetectedActivity : ActivityDetectionReceiver.getActivityDetectionLastResult().getProbableActivities()) + { + if(oneDetectedActivity.getType() == oneTrigger.getActivityDetectionType() && oneDetectedActivity.getConfidence() < Settings.activityDetectionRequiredProbability) + { + Miscellaneous.logEvent("i", String.format(context.getResources().getString(R.string.ruleCheckOf), this.getName()), String.format(context.getResources().getString(R.string.ruleDoesntApplyActivityGivenButTooLowProbability), ActivityDetectionReceiver.getDescription(oneDetectedActivity.getType()), String.valueOf(oneDetectedActivity.getConfidence()), String.valueOf(Settings.activityDetectionRequiredProbability)), 3); + return false; + } + } + } + } + } + else if(oneTrigger.getTriggerType().equals(Trigger.Trigger_Enum.bluetoothConnection)) + { + Miscellaneous.logEvent("i", String.format(context.getResources().getString(R.string.ruleCheckOf), this.getName()), "Checking for bluetooth...", 4); + +// if( // connected / disconnected +// (oneTrigger.getTriggerParameter() && (BluetoothReceiver.getLastAction().equals(android.bluetooth.BluetoothDevice.ACTION_ACL_CONNECTED) | BluetoothReceiver.getLastAction().equals("android.bluetooth.device.action.ACL_CONNECTED"))) +// | +// (!oneTrigger.getTriggerParameter() && (BluetoothReceiver.getLastAction().equals(android.bluetooth.BluetoothDevice.ACTION_ACL_DISCONNECT_REQUESTED) | BluetoothReceiver.getLastAction().equals(android.bluetooth.BluetoothDevice.ACTION_ACL_DISCONNECTED) | BluetoothReceiver.getLastAction().equals("android.bluetooth.device.action.ACTION_ACL_DISCONNECT_REQUESTED") | BluetoothReceiver.getLastAction().equals("android.bluetooth.device.action.ACTION_ACL_DISCONNECTED"))) +// ) +// { +// if(oneTrigger.getBluetoothDeviceAddress() != null) +// { +// if(oneTrigger.getBluetoothDeviceAddress().equals("")) +// { +// Miscellaneous.logEvent("i", String.format(context.getResources().getString(R.string.ruleCheckOf), this.getName()), "No bluetooth address specified, any will do.", 4); +// } +// else if(oneTrigger.getBluetoothDeviceAddress().equals("")) +// { +// // ??? +// } +// else +// { +// Miscellaneous.logEvent("i", String.format(context.getResources().getString(R.string.ruleCheckOf), this.getName()), "Bluetooth address specified, checking that.", 4); +// if(!BluetoothReceiver.getLastAffectedDevice().getAddress().equals(oneTrigger.getBluetoothDeviceAddress())) +// { +// Miscellaneous.logEvent("i", String.format(context.getResources().getString(R.string.ruleCheckOf), this.getName()), context.getResources().getString(R.string.ruleDoesntApplyNotTheCorrectDeviceAddress), 3); +// return false; +// } +// else +// Miscellaneous.logEvent("i", String.format(context.getResources().getString(R.string.ruleCheckOf), this.getName()), "Bluetooth address matches. Rule will apply.", 4); +// } +// } +// } +// else if(BluetoothReceiver.getLastAction().equals(android.bluetooth.BluetoothDevice.ACTION_FOUND) | BluetoothReceiver.getLastAction().equals(android.bluetooth.BluetoothDevice.ACTION_FOUND)) +// { +// if(!oneTrigger.getTriggerParameter()) +// { +// Miscellaneous.logEvent("i", String.format(context.getResources().getString(R.string.ruleCheckOf), this.getName()), context.getResources().getString(R.string.ruleDoesntApplyDeviceInRangeButShouldNotBe), 3); +// return false; +// } +// } +// else // above only checks for last action, this checks for things in the past + { + if(oneTrigger.getBluetoothDeviceAddress().equals("")) + { + if(oneTrigger.getBluetoothEvent().equals(BluetoothDevice.ACTION_ACL_CONNECTED)) + { + if(BluetoothReceiver.isAnyDeviceConnected() != oneTrigger.getTriggerParameter()) + return false; + } + else if((oneTrigger.getBluetoothEvent().equals(BluetoothDevice.ACTION_ACL_DISCONNECTED))) + { + if(BluetoothReceiver.isAnyDeviceConnected() != oneTrigger.getTriggerParameter()) + return false; + } + else + { + // range + if(BluetoothReceiver.isAnyDeviceInRange() != oneTrigger.getTriggerParameter()) + return false; + } + } + else if(oneTrigger.getBluetoothDeviceAddress().equals("")) + { + if(oneTrigger.getBluetoothEvent().equals(BluetoothDevice.ACTION_ACL_CONNECTED)) + { + if(BluetoothReceiver.isAnyDeviceConnected() == oneTrigger.getTriggerParameter()) + return false; + } + else if((oneTrigger.getBluetoothEvent().equals(BluetoothDevice.ACTION_ACL_DISCONNECTED))) + { + if(BluetoothReceiver.isAnyDeviceConnected() == oneTrigger.getTriggerParameter()) + return false; + } + else + { + // range + if(BluetoothReceiver.isAnyDeviceInRange() == oneTrigger.getTriggerParameter()) + return false; + } + } + else if(oneTrigger.getBluetoothDeviceAddress().length() > 0) + { + if(oneTrigger.getBluetoothEvent().equals(BluetoothDevice.ACTION_ACL_CONNECTED)) + { + if(BluetoothReceiver.isDeviceCurrentlyConnected(BluetoothReceiver.getDeviceByAddress(oneTrigger.getBluetoothDeviceAddress())) != oneTrigger.getTriggerParameter()) + return false; + } + else if((oneTrigger.getBluetoothEvent().equals(BluetoothDevice.ACTION_ACL_DISCONNECTED))) + { + if(BluetoothReceiver.isDeviceCurrentlyConnected(BluetoothReceiver.getDeviceByAddress(oneTrigger.getBluetoothDeviceAddress())) != oneTrigger.getTriggerParameter()) + return false; + } + else + { + // range + if(BluetoothReceiver.isDeviceInRange(BluetoothReceiver.getDeviceByAddress(oneTrigger.getBluetoothDeviceAddress())) != oneTrigger.getTriggerParameter()) + return false; + } + } + else + { + Miscellaneous.logEvent("i", String.format(context.getResources().getString(R.string.ruleCheckOf), this.getName()), context.getResources().getString(R.string.ruleDoesntApplyStateNotCorrect), 3); + return false; + } + } + } + else if(oneTrigger.getTriggerType().equals(Trigger.Trigger_Enum.headsetPlugged)) + { + if(HeadphoneJackListener.isHeadsetConnected() != oneTrigger.getTriggerParameter()) + return false; + else + if(oneTrigger.getHeadphoneType() != 2 && oneTrigger.getHeadphoneType() != HeadphoneJackListener.getHeadphoneType()) + { + Miscellaneous.logEvent("i", String.format(context.getResources().getString(R.string.ruleCheckOf), this.getName()), context.getResources().getString(R.string.ruleDoesntApplyWrongHeadphoneType), 3); + return false; + } + } + } + + return true; + } + + Miscellaneous.logEvent("i", String.format(context.getResources().getString(R.string.ruleCheckOf), this.getName()), String.format(context.getResources().getString(R.string.ruleIsDeactivatedCantApply), this.getName()), 3); + return false; + } + + private class ActivateRuleTask extends AsyncTask + { + @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(); + + 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) + { + AutomationService.updateNotification(); + ActivityMainScreen.updateMainScreen(); + super.onPostExecute(result); + } + + /** + * Will activate the rule. Should be called by a separate execution thread + * @param automationService + */ + protected void activateInternally(AutomationService automationService, boolean force) + { + boolean isActuallyToggable = isActuallyToggable(); + + boolean notLastActive = getLastActivatedRule() == null || !getLastActivatedRule().equals(Rule.this); + boolean doToggle = ruleToggle && isActuallyToggable; + + if(notLastActive | force | doToggle) + { + 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++) + Rule.this.getActionSet().get(i).run(automationService, doToggle); + + // 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); + } + } + } + + 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(boolean triggerParameter) + { + ArrayList ruleCandidates = new ArrayList(); + + for(Rule oneRule : ruleCollection) + { + innerloop: + for(Trigger oneTrigger : oneRule.getTriggerSet()) + { + if(oneTrigger.getTriggerType() == Trigger.Trigger_Enum.phoneCall) + { + 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 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 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()); + } +} diff --git a/app/src/googlePlayFlavor/java/com/jens/automation2/location/GeofenceBroadcastReceiver.java b/app/src/googlePlayFlavor/java/com/jens/automation2/location/GeofenceBroadcastReceiver.java new file mode 100644 index 0000000..d2c78e6 --- /dev/null +++ b/app/src/googlePlayFlavor/java/com/jens/automation2/location/GeofenceBroadcastReceiver.java @@ -0,0 +1,54 @@ +package com.jens.automation2.location; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.util.Log; + +import com.google.android.gms.location.Geofence; +import com.google.android.gms.location.GeofencingEvent; +import com.jens.automation2.Miscellaneous; + +import java.util.List; + +import static eu.chainfire.libsuperuser.Debug.TAG; + +public class GeofenceBroadcastReceiver extends BroadcastReceiver +{ + @Override + public void onReceive(Context context, Intent intent) + { + GeofencingEvent geofencingEvent = GeofencingEvent.fromIntent(intent); + if (geofencingEvent.hasError()) + { +// Miscellaneous.logEvent("i", "Geofence", geofenceTransitionDetails, 2); +// String errorMessage = GeofenceStatusCodes.getErrorString(geofencingEvent.getErrorCode()); + Log.e(TAG, "Geofence error"); + return; + } + + // Get the transition type. + int geofenceTransition = geofencingEvent.getGeofenceTransition(); + + // Test that the reported transition was of interest. + if (geofenceTransition == Geofence.GEOFENCE_TRANSITION_ENTER || geofenceTransition == Geofence.GEOFENCE_TRANSITION_EXIT) + { + // Get the geofences that were triggered. A single event can trigger + // multiple geofences. + List triggeringGeofences = geofencingEvent.getTriggeringGeofences(); + + // Get the transition details as a String. + String geofenceTransitionDetails = "something happened";//getGeofenceTransitionDetails(this, geofenceTransition, triggeringGeofences); + + // Send notification and log the transition details. + Miscellaneous.logEvent("i", "Geofence", geofenceTransitionDetails, 2); + Log.i(TAG, geofenceTransitionDetails); + } + else + { + // Log the error. +// Log.e(TAG, getString(R.string.geofence_transition_invalid_type, geofenceTransition)); + Log.e("Geofence", String.valueOf(geofenceTransition)); + } + } +} diff --git a/app/src/googlePlayFlavor/java/com/jens/automation2/location/GeofenceIntentService.java b/app/src/googlePlayFlavor/java/com/jens/automation2/location/GeofenceIntentService.java new file mode 100644 index 0000000..3b20edb --- /dev/null +++ b/app/src/googlePlayFlavor/java/com/jens/automation2/location/GeofenceIntentService.java @@ -0,0 +1,109 @@ +package com.jens.automation2.location; + +import android.app.IntentService; +import android.app.PendingIntent; +import android.content.Intent; + +import androidx.annotation.Nullable; + +import com.google.android.gms.common.api.GoogleApiClient; +import com.google.android.gms.location.GeofencingClient; +import com.google.android.gms.location.GeofencingRequest; +import com.google.android.gms.location.LocationServices; +import com.jens.automation2.PointOfInterest; + +import java.util.ArrayList; +import java.util.List; + +public class GeofenceIntentService extends IntentService +{ + private GeofencingClient mGeofencingClient; + protected static GoogleApiClient googleApiClient = null; + PendingIntent geofencePendingIntent; + + static GeofenceIntentService instance; + + List geoFenceList = new ArrayList<>(); + + public static GeofenceIntentService getInstance() + { + if (instance == null) + instance = new GeofenceIntentService("Automation"); + + return instance; + } + + public GeofenceIntentService(String name) + { + super(name); + } + + @Override + protected void onHandleIntent(@Nullable Intent intent) + { + + } + + @Override + public void onCreate() + { + mGeofencingClient = LocationServices.getGeofencingClient(this); + + } + + public void addFence(PointOfInterest poi) + { + com.google.android.gms.location.Geofence geofence = new com.google.android.gms.location.Geofence.Builder() + .setRequestId(poi.getName()) // Geofence ID + .setCircularRegion(poi.getLocation().getLatitude(), poi.getLocation().getLongitude(), (float) poi.getRadius()) // defining fence region + .setExpirationDuration(com.google.android.gms.location.Geofence.NEVER_EXPIRE) // expiring date + // Transition types that it should look for + .setTransitionTypes(com.google.android.gms.location.Geofence.GEOFENCE_TRANSITION_ENTER | com.google.android.gms.location.Geofence.GEOFENCE_TRANSITION_EXIT) + .build(); + + geoFenceList.add(geofence); + + GeofencingRequest request = new GeofencingRequest.Builder() + // Notification to trigger when the Geofence is created + .setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_ENTER) + .addGeofence(geofence) // add a Geofence + .build(); + + mGeofencingClient.removeGeofences(getGeofencePendingIntent()); + } + + public static void startService() + { + for (PointOfInterest poi : PointOfInterest.getPointOfInterestCollection()) + getInstance().addFence(poi); + } + + public static void stopService() + { + for (PointOfInterest poi : PointOfInterest.getPointOfInterestCollection()) + getInstance().addFence(poi); + } + + /** + * Gets a PendingIntent to send with the request to add or remove Geofences. Location Services + * issues the Intent inside this PendingIntent whenever a geofence transition occurs for the + * current list of geofences. + * + * @return A PendingIntent for the IntentService that handles geofence transitions. + */ + + private PendingIntent getGeofencePendingIntent() + { + // Reuse the PendingIntent if we already have it. + if (geofencePendingIntent != null) + { + return geofencePendingIntent; + } + Intent intent = new Intent(this, GeofenceBroadcastReceiver.class); + // We use FLAG_UPDATE_CURRENT so that we get the same pending intent back when + // calling addGeofences() and removeGeofences(). + geofencePendingIntent = PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); + return geofencePendingIntent; + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/jens/automation2/receivers/ActivityDetectionReceiver.java b/app/src/googlePlayFlavor/java/com/jens/automation2/receivers/ActivityDetectionReceiver.java similarity index 99% rename from app/src/main/java/com/jens/automation2/receivers/ActivityDetectionReceiver.java rename to app/src/googlePlayFlavor/java/com/jens/automation2/receivers/ActivityDetectionReceiver.java index d4a5158..90ca7e6 100644 --- a/app/src/main/java/com/jens/automation2/receivers/ActivityDetectionReceiver.java +++ b/app/src/googlePlayFlavor/java/com/jens/automation2/receivers/ActivityDetectionReceiver.java @@ -9,7 +9,6 @@ import android.util.Log; import com.google.android.gms.common.ConnectionResult; import com.google.android.gms.common.GooglePlayServicesUtil; import com.google.android.gms.common.api.GoogleApiClient; -import com.google.android.gms.common.api.GoogleApiClient.OnConnectionFailedListener; import com.google.android.gms.location.ActivityRecognition; import com.google.android.gms.location.ActivityRecognitionApi; import com.google.android.gms.location.ActivityRecognitionResult; diff --git a/app/src/main/java/com/jens/automation2/ActivityManageSpecificRule.java b/app/src/main/java/com/jens/automation2/ActivityManageSpecificRule.java index d9c4c42..dac191b 100644 --- a/app/src/main/java/com/jens/automation2/ActivityManageSpecificRule.java +++ b/app/src/main/java/com/jens/automation2/ActivityManageSpecificRule.java @@ -34,11 +34,14 @@ import android.widget.ListView; import android.widget.TextView; import android.widget.Toast; +import androidx.annotation.RequiresApi; + import com.jens.automation2.Action.Action_Enum; import com.jens.automation2.Trigger.Trigger_Enum; -import com.jens.automation2.receivers.ActivityDetectionReceiver; import com.jens.automation2.receivers.NfcReceiver; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collections; @@ -489,7 +492,8 @@ public class ActivityManageSpecificRule extends Activity .setTitle(getResources().getString(R.string.selectTypeOfTrigger)) .setAdapter(adapter, new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) + @RequiresApi(api = Build.VERSION_CODES.KITKAT) + public void onClick(DialogInterface dialog, int which) { triggerType = Trigger_Enum.values()[which]; @@ -530,13 +534,25 @@ public class ActivityManageSpecificRule extends Activity booleanChoices = new String[]{getResources().getString(R.string.started), getResources().getString(R.string.stopped)}; else if(triggerType == Trigger_Enum.activityDetection) { - if(ActivityDetectionReceiver.isPlayServiceAvailable()) + try { - newTrigger.setTriggerType(Trigger_Enum.activityDetection); - getTriggerActivityDetectionDialog().show(); + Method m = Miscellaneous.getClassMethodReflective("ActivityDetectionReceiver", "isPlayServiceAvailable"); + if(m != null) + { + boolean available = (Boolean)m.invoke(null); + if(available) + { + newTrigger.setTriggerType(Trigger_Enum.activityDetection); + getTriggerActivityDetectionDialog().show(); + } + else + Toast.makeText(myContext, getResources().getString(R.string.triggerOnlyAvailableIfPlayServicesInstalled), Toast.LENGTH_LONG).show(); + } + } + catch (IllegalAccessException | InvocationTargetException e) + { + e.printStackTrace(); } - else - Toast.makeText(myContext, getResources().getString(R.string.triggerOnlyAvailableIfPlayServicesInstalled), Toast.LENGTH_LONG).show(); return; } else if(triggerType == Trigger_Enum.nfcTag) @@ -898,25 +914,52 @@ public class ActivityManageSpecificRule extends Activity return alertDialog; } + @RequiresApi(api = Build.VERSION_CODES.KITKAT) private AlertDialog getTriggerActivityDetectionDialog() { AlertDialog.Builder alertDialog = new AlertDialog.Builder(this); alertDialog.setTitle(Miscellaneous.getAnyContext().getResources().getString(R.string.selectTypeOfActivity)); - String[] choices = ActivityDetectionReceiver.getAllDescriptions(); - alertDialog.setItems(choices, new DialogInterface.OnClickListener() - { - @Override - public void onClick(DialogInterface dialog, int which) + + Method m = Miscellaneous.getClassMethodReflective("ActivityDetectionReceiver", "getAllDescriptions"); + if(m != null) + { + String[] choices = new String[0]; + try { - newTrigger.setActivityDetectionType(ActivityDetectionReceiver.getAllTypes()[which]); - ruleToEdit.getTriggerSet().add(newTrigger); - refreshTriggerList(); + choices = (String[])m.invoke(null); + alertDialog.setItems(choices, new DialogInterface.OnClickListener() + { + @Override + public void onClick(DialogInterface dialog, int which) + { + Method m = Miscellaneous.getClassMethodReflective("ActivityDetectionReceiver", "getAllTypes"); + if(m != null) + { + try + { + Integer[] choices = (Integer[])m.invoke(null); + + newTrigger.setActivityDetectionType(choices[which]); + ruleToEdit.getTriggerSet().add(newTrigger); + refreshTriggerList(); + } + catch (IllegalAccessException | InvocationTargetException e) + { + e.printStackTrace(); + } + } + } + }); } - }); + catch (IllegalAccessException | InvocationTargetException e) + { + e.printStackTrace(); + } + } return alertDialog.create(); - } + } private static class GenerateApplicationSelectionsDialogTask extends AsyncTask { diff --git a/app/src/main/java/com/jens/automation2/Miscellaneous.java b/app/src/main/java/com/jens/automation2/Miscellaneous.java index 1eadfc7..29c60de 100644 --- a/app/src/main/java/com/jens/automation2/Miscellaneous.java +++ b/app/src/main/java/com/jens/automation2/Miscellaneous.java @@ -48,6 +48,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.lang.Thread.UncaughtExceptionHandler; +import java.lang.reflect.Method; import java.math.BigDecimal; import java.math.RoundingMode; import java.net.HttpURLConnection; @@ -72,12 +73,9 @@ import javax.net.ssl.X509TrustManager; import static com.jens.automation2.AutomationService.NOTIFICATION_CHANNEL_ID; import static com.jens.automation2.AutomationService.channelName; -//import android.R.string; -//import android.util.Log; - public class Miscellaneous extends Service { - private static String writeableFolderStringCache = null; + protected static String writeableFolderStringCache = null; public static final String lineSeparator = System.getProperty("line.separator"); public static String downloadURL(String url, String username, String password) @@ -884,4 +882,26 @@ public class Miscellaneous extends Service } } } + + public static Method getClassMethodReflective(String className, String methodName) + { + Class atRecClass = null; + try + { + atRecClass = Class.forName("ActivityDetectionReceiver"); + for(Method m : atRecClass.getMethods()) + { + if(m.getName().equalsIgnoreCase("isPlayServiceAvailable")) + { + return m; + } + } + } + catch (ClassNotFoundException e) + { + e.printStackTrace(); + } + + return null; + } } \ No newline at end of file diff --git a/app/src/main/java/com/jens/automation2/ReceiverCoordinator.java b/app/src/main/java/com/jens/automation2/ReceiverCoordinator.java index f831ce9..46e5003 100644 --- a/app/src/main/java/com/jens/automation2/ReceiverCoordinator.java +++ b/app/src/main/java/com/jens/automation2/ReceiverCoordinator.java @@ -4,7 +4,6 @@ import android.util.Log; import com.jens.automation2.location.CellLocationChangedReceiver; import com.jens.automation2.location.WifiBroadcastReceiver; -import com.jens.automation2.receivers.ActivityDetectionReceiver; import com.jens.automation2.receivers.AlarmListener; import com.jens.automation2.receivers.AutomationListenerInterface; import com.jens.automation2.receivers.BatteryReceiver; diff --git a/app/src/main/java/com/jens/automation2/Trigger.java b/app/src/main/java/com/jens/automation2/Trigger.java index e6e43a8..243ddd7 100644 --- a/app/src/main/java/com/jens/automation2/Trigger.java +++ b/app/src/main/java/com/jens/automation2/Trigger.java @@ -2,10 +2,14 @@ package com.jens.automation2; import android.bluetooth.BluetoothDevice; import android.content.Context; +import android.os.Build; + +import androidx.annotation.RequiresApi; -import com.jens.automation2.receivers.ActivityDetectionReceiver; import com.jens.automation2.receivers.BluetoothReceiver; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.util.ArrayList; @@ -215,22 +219,13 @@ public class Trigger } + @RequiresApi(api = Build.VERSION_CODES.KITKAT) @SuppressWarnings("unused") @Override public String toString() { StringBuilder returnString = new StringBuilder(); - /* - * public enum TriggerType_Enum { pointOfInterest, timeFrame, event }; - public enum Event_Enum { charging_started, charging_stopped, usb_connected, usb_disconnected }; - - private TriggerType_Enum triggerType; - private PointOfInterest pointOfInterest; - private Event_Enum event; - private TimeFrame timeFrame; - */ - switch(this.getTriggerType()) { case charging: @@ -358,8 +353,27 @@ public class Trigger returnString.append(Miscellaneous.getAnyContext().getResources().getString(R.string.closeTo) + " " + Miscellaneous.getAnyContext().getResources().getString(R.string.nfcTag) + " " + Miscellaneous.getAnyContext().getResources().getString(R.string.withLabel) + " " + this.getNfcTagId()); break; case activityDetection: - // This type doesn't have an activate/deactivate equivalent, at least not yet. - returnString.append(Miscellaneous.getAnyContext().getResources().getString(R.string.detectedActivity) + " " + ActivityDetectionReceiver.getDescription(getActivityDetectionType())); + if(ActivityPermissions.isPermissionDeclaratedInManifest(Miscellaneous.getAnyContext(), "com.google.android.gms.permission.ACTIVITY_RECOGNITION")) + { + // This type doesn't have an activate/deactivate equivalent, at least not yet. + try + { + Class activityDetection = Class.forName("com.jens.automation2.receivers.ActivityDetectionReceiver"); + for(Method method : activityDetection.getMethods()) + { + if(method.getName().equalsIgnoreCase("getDescription")) + returnString.append(method.invoke(getActivityDetectionType())); +// returnString.append(Miscellaneous.getAnyContext().getResources().getString(R.string.detectedActivity) + " " + activityDetection.getDescription(getActivityDetectionType())); + } + } + catch (ClassNotFoundException | IllegalAccessException | InvocationTargetException e) + { + e.printStackTrace(); + } + + } + else + returnString.append("Invalid trigger. This application version cannot handle ActivityDetection."); break; case bluetoothConnection: String device = Miscellaneous.getAnyContext().getResources().getString(R.string.anyDevice); diff --git a/app/src/main/java/com/jens/automation2/location/LocationProvider.java b/app/src/main/java/com/jens/automation2/location/LocationProvider.java index 76ec703..21c4bca 100644 --- a/app/src/main/java/com/jens/automation2/location/LocationProvider.java +++ b/app/src/main/java/com/jens/automation2/location/LocationProvider.java @@ -237,8 +237,8 @@ public class LocationProvider } else { - if(Rule.isAnyRuleUsing(Trigger_Enum.pointOfInterest)) - GeofenceIntentService.startService(); +// if(Rule.isAnyRuleUsing(Trigger_Enum.pointOfInterest)) +// GeofenceIntentService.startService(); } }