commit a8950597d021cbdfaa38e899afde33ada6edcc10 Author: jens Date: Tue Feb 16 13:42:49 2021 +0100 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..74284c4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,150 @@ + +# Created by https://www.toptal.com/developers/gitignore/api/androidstudio +# Edit at https://www.toptal.com/developers/gitignore?templates=androidstudio + +### AndroidStudio ### +# Covers files to be ignored for android development using Android Studio. + +# Built application files +*.apk +*.ap_ +*.aab + +# Files for the ART/Dalvik VM +*.dex + +# Java class files +*.class + +# Generated files +bin/ +gen/ +out/ + +# Gradle files +.gradle +.gradle/ +build/ + +# Signing files +.signing/ + +# Local configuration file (sdk path, etc) +local.properties + +# Proguard folder generated by Eclipse +proguard/ + +# Log Files +*.log + +# Android Studio +/*/build/ +/*/local.properties +/*/out +/*/*/build +/*/*/production +captures/ +.navigation/ +*.ipr +*~ +*.swp + +# Keystore files +*.jks +*.keystore + +# Google Services (e.g. APIs or Firebase) +# google-services.json + +# Android Patch +gen-external-apklibs + +# External native build folder generated in Android Studio 2.2 and later +.externalNativeBuild + +# NDK +obj/ + +# IntelliJ IDEA +*.iml +*.iws +/out/ + +# User-specific configurations +.idea/caches/ +.idea/libraries/ +.idea/shelf/ +.idea/workspace.xml +.idea/tasks.xml +.idea/.name +.idea/compiler.xml +.idea/copyright/profiles_settings.xml +.idea/encodings.xml +.idea/misc.xml +.idea/modules.xml +.idea/scopes/scope_settings.xml +.idea/dictionaries +.idea/vcs.xml +.idea/jsLibraryMappings.xml +.idea/datasources.xml +.idea/dataSources.ids +.idea/sqlDataSources.xml +.idea/dynamic.xml +.idea/uiDesigner.xml +.idea/assetWizardSettings.xml +.idea/gradle.xml +.idea/jarRepositories.xml +.idea/navEditor.xml + +# OS-specific files +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db + +# Legacy Eclipse project files +.classpath +.project +.cproject +.settings/ + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.war +*.ear + +# virtual machine crash logs (Reference: http://www.java.com/en/download/help/error_hotspot.xml) +hs_err_pid* + +## Plugin-specific files: + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Mongo Explorer plugin +.idea/mongoSettings.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +### AndroidStudio Patch ### + +!/gradle/wrapper/gradle-wrapper.jar + +# End of https://www.toptal.com/developers/gitignore/api/androidstudio + + +/app/app-release.apk +Automation_settings.xml diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..74284c4 --- /dev/null +++ b/app/.gitignore @@ -0,0 +1,150 @@ + +# Created by https://www.toptal.com/developers/gitignore/api/androidstudio +# Edit at https://www.toptal.com/developers/gitignore?templates=androidstudio + +### AndroidStudio ### +# Covers files to be ignored for android development using Android Studio. + +# Built application files +*.apk +*.ap_ +*.aab + +# Files for the ART/Dalvik VM +*.dex + +# Java class files +*.class + +# Generated files +bin/ +gen/ +out/ + +# Gradle files +.gradle +.gradle/ +build/ + +# Signing files +.signing/ + +# Local configuration file (sdk path, etc) +local.properties + +# Proguard folder generated by Eclipse +proguard/ + +# Log Files +*.log + +# Android Studio +/*/build/ +/*/local.properties +/*/out +/*/*/build +/*/*/production +captures/ +.navigation/ +*.ipr +*~ +*.swp + +# Keystore files +*.jks +*.keystore + +# Google Services (e.g. APIs or Firebase) +# google-services.json + +# Android Patch +gen-external-apklibs + +# External native build folder generated in Android Studio 2.2 and later +.externalNativeBuild + +# NDK +obj/ + +# IntelliJ IDEA +*.iml +*.iws +/out/ + +# User-specific configurations +.idea/caches/ +.idea/libraries/ +.idea/shelf/ +.idea/workspace.xml +.idea/tasks.xml +.idea/.name +.idea/compiler.xml +.idea/copyright/profiles_settings.xml +.idea/encodings.xml +.idea/misc.xml +.idea/modules.xml +.idea/scopes/scope_settings.xml +.idea/dictionaries +.idea/vcs.xml +.idea/jsLibraryMappings.xml +.idea/datasources.xml +.idea/dataSources.ids +.idea/sqlDataSources.xml +.idea/dynamic.xml +.idea/uiDesigner.xml +.idea/assetWizardSettings.xml +.idea/gradle.xml +.idea/jarRepositories.xml +.idea/navEditor.xml + +# OS-specific files +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db + +# Legacy Eclipse project files +.classpath +.project +.cproject +.settings/ + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.war +*.ear + +# virtual machine crash logs (Reference: http://www.java.com/en/download/help/error_hotspot.xml) +hs_err_pid* + +## Plugin-specific files: + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Mongo Explorer plugin +.idea/mongoSettings.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +### AndroidStudio Patch ### + +!/gradle/wrapper/gradle-wrapper.jar + +# End of https://www.toptal.com/developers/gitignore/api/androidstudio + + +/app/app-release.apk +Automation_settings.xml diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..8ef949d --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,79 @@ +plugins { + id 'com.android.application' +} + +android { + compileSdkVersion 29 + + defaultConfig { + applicationId "com.jens.automation2" + minSdkVersion 14 + compileSdkVersion 29 + buildToolsVersion '29.0.2' + useLibrary 'org.apache.http.legacy' + versionCode 96 + versionName "1.6.21" + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + lintOptions { + checkReleaseBuilds false + abortOnError false + } + + flavorDimensions "version" + + productFlavors + { + googlePlayFlavor + { + dimension "version" + applicationIdSuffix ".googlePlay" + versionNameSuffix "-googlePlay" + targetSdkVersion 29 + } + + fdroidFlavor + { + dimension "version" + applicationIdSuffix ".fdroid" + versionNameSuffix "-fdroid" + targetSdkVersion 28 + } + + apkFlavor + { + dimension "version" + applicationIdSuffix ".apk" + versionNameSuffix "-apk" + targetSdkVersion 28 + } + } +} + +dependencies { + googlePlayFlavorImplementation 'com.google.firebase:firebase-appindexing:16.0.1' + googlePlayFlavorImplementation 'com.google.android.gms:play-services-location:15.0.1' + + apkFlavorImplementation 'com.google.firebase:firebase-appindexing:16.0.1' + apkFlavorImplementation 'com.google.android.gms:play-services-location:15.0.1' + + implementation 'androidx.appcompat:appcompat:1.2.0' + + implementation 'com.google.android.material:material:1.1.0' + testImplementation 'junit:junit:4.+' + androidTestImplementation 'androidx.test.ext:junit:1.1.1' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/app/src/androidTest/java/com/jens/automation2/ExampleInstrumentedTest.java b/app/src/androidTest/java/com/jens/automation2/ExampleInstrumentedTest.java new file mode 100644 index 0000000..e7403b4 --- /dev/null +++ b/app/src/androidTest/java/com/jens/automation2/ExampleInstrumentedTest.java @@ -0,0 +1,28 @@ +package com.jens.automation2; + +import android.content.Context; + +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.*; + +/** + * Instrumented test, which will execute on an Android device. + * + * @see Testing documentation + */ +@RunWith(AndroidJUnit4.class) +public class ExampleInstrumentedTest +{ + @Test + public void useAppContext() + { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); + assertEquals("com.jens.automation2", appContext.getPackageName()); + } +} \ No newline at end of file diff --git a/app/src/apkFlavor/AndroidManifest.xml b/app/src/apkFlavor/AndroidManifest.xml new file mode 100644 index 0000000..398fd94 --- /dev/null +++ b/app/src/apkFlavor/AndroidManifest.xml @@ -0,0 +1,203 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/fdroidFlavor/AndroidManifest.xml b/app/src/fdroidFlavor/AndroidManifest.xml new file mode 100644 index 0000000..b6f9730 --- /dev/null +++ b/app/src/fdroidFlavor/AndroidManifest.xml @@ -0,0 +1,187 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/google-services.json b/app/src/google-services.json new file mode 100644 index 0000000..35b249a --- /dev/null +++ b/app/src/google-services.json @@ -0,0 +1,48 @@ +{ + "project_info": { + "project_number": "977361397436", + "project_id": "automation-9bed6" + }, + "client": [ + { + "client_info": { + "mobilesdk_app_id": "1:977361397436:android:ee5ad5243b40934a", + "android_client_info": { + "package_name": "com.jens.automation2" + } + }, + "oauth_client": [ + { + "client_id": "977361397436-86ebgevdi3ttfhcki5l1ldnquocos854.apps.googleusercontent.com", + "client_type": 1, + "android_info": { + "package_name": "com.jens.automation2", + "certificate_hash": "86ec434e30ed99ec9ee1f4e5c33166558a8442a9" + } + }, + { + "client_id": "977361397436-1n1769oadmm31pckln04pjdkela9p3e4.apps.googleusercontent.com", + "client_type": 3 + } + ], + "api_key": [ + { + "current_key": "AIzaSyCeDEkuDmAr7b03auJKDrL1YAB-KlY7088" + } + ], + "services": { + "analytics_service": { + "status": 1 + }, + "appinvite_service": { + "status": 1, + "other_platform_oauth_client": [] + }, + "ads_service": { + "status": 1 + } + } + } + ], + "configuration_version": "1" +} \ No newline at end of file diff --git a/app/src/googlePlayFlavor/AndroidManifest.xml b/app/src/googlePlayFlavor/AndroidManifest.xml new file mode 100644 index 0000000..3198100 --- /dev/null +++ b/app/src/googlePlayFlavor/AndroidManifest.xml @@ -0,0 +1,203 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..a3e1fb2 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,192 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/jens/automation2/AESCrypt.java b/app/src/main/java/com/jens/automation2/AESCrypt.java new file mode 100644 index 0000000..8223a4b --- /dev/null +++ b/app/src/main/java/com/jens/automation2/AESCrypt.java @@ -0,0 +1,200 @@ +package com.jens.automation2; + +import android.util.Base64; +import android.util.Log; + +import java.io.UnsupportedEncodingException; +import java.security.GeneralSecurityException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +import javax.crypto.Cipher; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; + +/** + * Encrypt and decrypt messages using AES 256 bit encryption that are compatible with AESCrypt-ObjC and AESCrypt Ruby. + *

+ * Created by scottab on 04/10/2014. + */ +public final class AESCrypt +{ + private static final String TAG = "AESCrypt"; + + //AESCrypt-ObjC uses CBC and PKCS7Padding + private static final String AES_MODE = "AES/CBC/PKCS7Padding"; +// private static final String AES_MODE = "AES/ECB/NoPadding"; + private static final String CHARSET = "UTF-8"; + + //AESCrypt-ObjC uses SHA-256 (and so a 256-bit key) + private static final String HASH_ALGORITHM = "SHA-256"; + + //AESCrypt-ObjC uses blank IV (not the best security, but the aim here is compatibility) + private static final byte[] ivBytes = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + + //togglable log option (please turn off in live!) + public static boolean DEBUG_LOG_ENABLED = false; + + + /** + * Generates SHA256 hash of the password which is used as key + * + * @param password used to generated key + * @return SHA256 of the password + */ + private static SecretKeySpec generateKey(final String password) throws NoSuchAlgorithmException, UnsupportedEncodingException { + final MessageDigest digest = MessageDigest.getInstance(HASH_ALGORITHM); + byte[] bytes = password.getBytes("UTF-8"); + digest.update(bytes, 0, bytes.length); + byte[] key = digest.digest(); + + log("SHA-256 key ", key); + + SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES"); + return secretKeySpec; + } + + + /** + * Encrypt and encode message using 256-bit AES with key generated from password. + * + * + * @param password used to generated key + * @param message the thing you want to encrypt assumed String UTF-8 + * @return Base64 encoded CipherText + * @throws GeneralSecurityException if problems occur during encryption + */ + public static String encrypt(final String password, String message) + throws GeneralSecurityException { + + try { + final SecretKeySpec key = generateKey(password); + + log("message", message); + + byte[] cipherText = encrypt(key, ivBytes, message.getBytes(CHARSET)); + + //NO_WRAP is important as was getting \n at the end + String encoded = Base64.encodeToString(cipherText, Base64.NO_WRAP); + log("Base64.NO_WRAP", encoded); + return encoded; + } catch (UnsupportedEncodingException e) { + if (DEBUG_LOG_ENABLED) + Log.e(TAG, "UnsupportedEncodingException ", e); + throw new GeneralSecurityException(e); + } + } + + + /** + * More flexible AES encrypt that doesn't encode + * @param key AES key typically 128, 192 or 256 bit + * @param iv Initiation Vector + * @param message in bytes (assumed it's already been decoded) + * @return Encrypted cipher text (not encoded) + * @throws GeneralSecurityException if something goes wrong during encryption + */ + public static byte[] encrypt(final SecretKeySpec key, final byte[] iv, final byte[] message) + throws GeneralSecurityException { + final Cipher cipher = Cipher.getInstance(AES_MODE); + IvParameterSpec ivSpec = new IvParameterSpec(iv); + cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec); + byte[] cipherText = cipher.doFinal(message); + + log("cipherText", cipherText); + + return cipherText; + } + + + /** + * Decrypt and decode ciphertext using 256-bit AES with key generated from password + * + * @param password used to generated key + * @param base64EncodedCipherText the encrpyted message encoded with base64 + * @return message in Plain text (String UTF-8) + * @throws GeneralSecurityException if there's an issue decrypting + */ + public static String decrypt(final String password, String base64EncodedCipherText) + throws GeneralSecurityException { + + try { + final SecretKeySpec key = generateKey(password); + + log("base64EncodedCipherText", base64EncodedCipherText); + byte[] decodedCipherText = Base64.decode(base64EncodedCipherText, Base64.NO_WRAP); + log("decodedCipherText", decodedCipherText); + + byte[] decryptedBytes = decrypt(key, ivBytes, decodedCipherText); + + log("decryptedBytes", decryptedBytes); + String message = new String(decryptedBytes, CHARSET); + log("message", message); + + + return message; + } catch (UnsupportedEncodingException e) { + if (DEBUG_LOG_ENABLED) + Log.e(TAG, "UnsupportedEncodingException ", e); + + throw new GeneralSecurityException(e); + } + } + + + /** + * More flexible AES decrypt that doesn't encode + * + * @param key AES key typically 128, 192 or 256 bit + * @param iv Initiation Vector + * @param decodedCipherText in bytes (assumed it's already been decoded) + * @return Decrypted message cipher text (not encoded) + * @throws GeneralSecurityException if something goes wrong during encryption + */ + public static byte[] decrypt(final SecretKeySpec key, final byte[] iv, final byte[] decodedCipherText) + throws GeneralSecurityException { + final Cipher cipher = Cipher.getInstance(AES_MODE); + IvParameterSpec ivSpec = new IvParameterSpec(iv); + cipher.init(Cipher.DECRYPT_MODE, key, ivSpec); + byte[] decryptedBytes = cipher.doFinal(decodedCipherText); + + log("decryptedBytes", decryptedBytes); + + return decryptedBytes; + } + + + + + private static void log(String what, byte[] bytes) { + if (DEBUG_LOG_ENABLED) + Log.d(TAG, what + "[" + bytes.length + "] [" + bytesToHex(bytes) + "]"); + } + + private static void log(String what, String value) { + if (DEBUG_LOG_ENABLED) + Log.d(TAG, what + "[" + value.length() + "] [" + value + "]"); + } + + + /** + * Converts byte array to hexidecimal useful for logging and fault finding + * @param bytes + * @return + */ + private static String bytesToHex(byte[] bytes) { + final char[] hexArray = {'0', '1', '2', '3', '4', '5', '6', '7', '8', + '9', 'A', 'B', 'C', 'D', 'E', 'F'}; + char[] hexChars = new char[bytes.length * 2]; + int v; + for (int j = 0; j < bytes.length; j++) { + v = bytes[j] & 0xFF; + hexChars[j * 2] = hexArray[v >>> 4]; + hexChars[j * 2 + 1] = hexArray[v & 0x0F]; + } + return new String(hexChars); + } + + private AESCrypt() { + } +} \ No newline at end of file diff --git a/app/src/main/java/com/jens/automation2/Action.java b/app/src/main/java/com/jens/automation2/Action.java new file mode 100644 index 0000000..576cfe3 --- /dev/null +++ b/app/src/main/java/com/jens/automation2/Action.java @@ -0,0 +1,537 @@ +package com.jens.automation2; + +import android.content.Context; +import android.os.AsyncTask; +import android.util.Log; + +import org.apache.http.client.methods.HttpGet; + +import java.util.ArrayList; +import java.util.Locale; + + +public class Action +{ + public enum Action_Enum { + setWifi, + setBluetooth, + setUsbTethering, + setWifiTethering, + setDisplayRotation, + turnWifiOn,turnWifiOff, + turnBluetoothOn,turnBluetoothOff, + triggerUrl, + changeSoundProfile, + turnUsbTetheringOn,turnUsbTetheringOff, + turnWifiTetheringOn,turnWifiTetheringOff, + enableScreenRotation, disableScreenRotation, + startOtherActivity, + waitBeforeNextAction, + wakeupDevice, + setAirplaneMode, + setDataConnection, + speakText, + playMusic, + setScreenBrightness, + sendTextMessage; + + public String getFullName(Context context) + { + switch(this) + { + case setWifi: + return context.getResources().getString(R.string.actionSetWifi); + case setBluetooth: + return context.getResources().getString(R.string.actionSetBluetooth); + case setWifiTethering: + return context.getResources().getString(R.string.actionSetWifiTethering); + case setUsbTethering: + return context.getResources().getString(R.string.actionSetUsbTethering); + case setDisplayRotation: + return context.getResources().getString(R.string.actionSetDisplayRotation); + case turnWifiOn: + return context.getResources().getString(R.string.actionTurnWifiOn); + case turnWifiOff: + return context.getResources().getString(R.string.actionTurnWifiOff); + case turnBluetoothOn: + return context.getResources().getString(R.string.actionTurnBluetoothOn); + case turnBluetoothOff: + return context.getResources().getString(R.string.actionTurnBluetoothOff); + case triggerUrl: + return context.getResources().getString(R.string.actionTriggerUrl); + case changeSoundProfile: + return context.getResources().getString(R.string.actionChangeSoundProfile); + case turnUsbTetheringOn: + return context.getResources().getString(R.string.actionTurnUsbTetheringOn); + case turnUsbTetheringOff: + return context.getResources().getString(R.string.actionTurnUsbTetheringOff); + case turnWifiTetheringOn: + return context.getResources().getString(R.string.actionTurnWifiTetheringOn); + case turnWifiTetheringOff: + return context.getResources().getString(R.string.actionTurnWifiTetheringOff); + case enableScreenRotation: + return context.getResources().getString(R.string.actionEnableScreenRotation); + case disableScreenRotation: + return context.getResources().getString(R.string.actionDisableScreenRotation); + case startOtherActivity: + return context.getResources().getString(R.string.startOtherActivity); + case waitBeforeNextAction: + return context.getResources().getString(R.string.waitBeforeNextAction); + case wakeupDevice: + return context.getResources().getString(R.string.wakeupDevice); + case setAirplaneMode: + return context.getResources().getString(R.string.airplaneMode); + case setDataConnection: + return context.getResources().getString(R.string.actionDataConnection); + case speakText: + return context.getResources().getString(R.string.actionSpeakText); + case playMusic: + return context.getResources().getString(R.string.actionPlayMusic); + case sendTextMessage: + return context.getResources().getString(R.string.sendTextMessage); + case setScreenBrightness: + return context.getResources().getString(R.string.setScreenBrightness); + default: + return "Unknown"; + } + } + }; + + private Action_Enum action; + private boolean parameter1 = false; + private String parameter2 = ""; + + public Action_Enum getAction() + { + return action; + } + public void setAction(Action_Enum action) + { + this.action = action; + } + + public boolean getParameter1() + { + return parameter1; + } + public void setParameter1(boolean parameter1) + { + this.parameter1 = parameter1; + } + public String getParameter2() + { + return parameter2; + } + public void setParameter2(String parameter) + { + this.parameter2 = parameter; + } + public String toStringShort() + { + String returnString = action.toString(); + + return returnString; + } + @Override + public String toString() + { + StringBuilder returnString = new StringBuilder(); + + if(this.getAction().equals(Action_Enum.setWifi)) + { + if(this.getParameter1()) + returnString.append(Miscellaneous.getAnyContext().getResources().getString(R.string.actionTurnWifiOn)); + else + returnString.append(Miscellaneous.getAnyContext().getResources().getString(R.string.actionTurnWifiOff)); + } + else if(this.getAction().equals(Action_Enum.setBluetooth)) + { + if(this.getParameter1()) + returnString.append(Miscellaneous.getAnyContext().getResources().getString(R.string.actionTurnBluetoothOn)); + else + returnString.append(Miscellaneous.getAnyContext().getResources().getString(R.string.actionTurnBluetoothOff)); + } + else if(this.getAction().equals(Action_Enum.setUsbTethering)) + { + if(this.getParameter1()) + returnString.append(Miscellaneous.getAnyContext().getResources().getString(R.string.actionTurnUsbTetheringOn)); + else + returnString.append(Miscellaneous.getAnyContext().getResources().getString(R.string.actionTurnUsbTetheringOff)); + } + else if(this.getAction().equals(Action_Enum.setWifiTethering)) + { + if(this.getParameter1()) + returnString.append(Miscellaneous.getAnyContext().getResources().getString(R.string.actionTurnWifiTetheringOn)); + else + returnString.append(Miscellaneous.getAnyContext().getResources().getString(R.string.actionTurnWifiTetheringOff)); + } + else if(this.getAction().equals(Action_Enum.setDisplayRotation)) + { + if(this.getParameter1()) + returnString.append(Miscellaneous.getAnyContext().getResources().getString(R.string.actionEnableScreenRotation)); + else + returnString.append(Miscellaneous.getAnyContext().getResources().getString(R.string.actionDisableScreenRotation)); + } + else if(this.getAction().equals(Action_Enum.setAirplaneMode)) + { + if(this.getParameter1()) + returnString.append(Miscellaneous.getAnyContext().getResources().getString(R.string.actionTurnAirplaneModeOn)); + else + returnString.append(Miscellaneous.getAnyContext().getResources().getString(R.string.actionTurnAirplaneModeOff)); + } + else if(this.getAction().equals(Action_Enum.setDataConnection)) + { + if(this.getParameter1()) + returnString.append(Miscellaneous.getAnyContext().getResources().getString(R.string.actionSetDataConnectionOn)); + else + returnString.append(Miscellaneous.getAnyContext().getResources().getString(R.string.actionSetDataConnectionOff)); + } + else if(this.getAction().equals(Action_Enum.startOtherActivity)) + { + returnString.append(Miscellaneous.getAnyContext().getResources().getString(R.string.startOtherActivity)); + } + else if(this.getAction().equals(Action_Enum.triggerUrl)) + { + returnString.append(Miscellaneous.getAnyContext().getResources().getString(R.string.actionTriggerUrl)); + } + else if(this.getAction().equals(Action_Enum.speakText)) + { + returnString.append(Miscellaneous.getAnyContext().getResources().getString(R.string.actionSpeakText)); + } + else if(this.getAction().equals(Action_Enum.playMusic)) + { + returnString.append(Miscellaneous.getAnyContext().getResources().getString(R.string.actionPlayMusic)); + } + else if(this.getAction().equals(Action_Enum.sendTextMessage)) + { + returnString.append(Miscellaneous.getAnyContext().getResources().getString(R.string.sendTextMessage)); + } + else if(this.getAction().equals(Action_Enum.wakeupDevice)) + { + returnString.append(Miscellaneous.getAnyContext().getResources().getString(R.string.wakeupDevice)); + } + else + returnString.append(action.toString()); + + if(this.getAction().equals(Action_Enum.triggerUrl)) + { + String[] components = parameter2.split(";"); + if(components.length >= 3) + { + returnString.append(": " + components[2]); + + if(parameter1) + returnString.append(" using authentication."); + } + else + returnString.append(": " + components[0]); + } + else if(this.getAction().equals(Action_Enum.sendTextMessage)) + { + String[] components = parameter2.split(Actions.smsSeparator); + if(components.length >= 2) + { + returnString.append(" to number " + components[0]); + + returnString.append(". Message: " + components[1]); + } + } + else if(this.getAction().equals(Action_Enum.setScreenBrightness)) + { + returnString.append(" to "); + + if(parameter1) + returnString.append(Miscellaneous.getAnyContext().getResources().getString(R.string.brightnessAuto)); + else + returnString.append(Miscellaneous.getAnyContext().getResources().getString(R.string.brightnessManual)); + + returnString.append(" / " + Integer.parseInt(parameter2) + "%"); + } + else + if (parameter2 != null && parameter2.length() > 0) + returnString.append(": " + parameter2); + + return returnString.toString(); + } + + public static CharSequence[] getActionTypesAsArray() + { + ArrayList actionTypesList = new ArrayList(); + + for(Action_Enum action : Action_Enum.values()) + { + if( // exclusion for deprecated types + !action.toString().equals("turnWifiOn") + && + !action.toString().equals("turnWifiOff") + && + !action.toString().equals("turnBluetoothOn") + && + !action.toString().equals("turnBluetoothOff") + && + !action.toString().equals("turnUsbTetheringOn") + && + !action.toString().equals("turnUsbTetheringOff") + && + !action.toString().equals("turnWifiTetheringOn") + && + !action.toString().equals("turnWifiTetheringOff") + && + !action.toString().equals("enableScreenRotation") + && + !action.toString().equals("disableScreenRotation") + ) // exclusion for deprecated types + actionTypesList.add(action.toString()); + } + + return (String[])actionTypesList.toArray(new String[actionTypesList.size()]); + } + public static CharSequence[] getActionTypesFullNameStringAsArray(Context context) + { + ArrayList actionTypesList = new ArrayList(); + + for(Action_Enum action : Action_Enum.values()) + { + if( // exclusion for deprecated types + !action.toString().equals("turnWifiOn") + && + !action.toString().equals("turnWifiOff") + && + !action.toString().equals("turnBluetoothOn") + && + !action.toString().equals("turnBluetoothOff") + && + !action.toString().equals("turnUsbTetheringOn") + && + !action.toString().equals("turnUsbTetheringOff") + && + !action.toString().equals("turnWifiTetheringOn") + && + !action.toString().equals("turnWifiTetheringOff") + && + !action.toString().equals("enableScreenRotation") + && + !action.toString().equals("disableScreenRotation") + ) // exclusion for deprecated types + actionTypesList.add(action.getFullName(context)); + } + + return (String[])actionTypesList.toArray(new String[actionTypesList.size()]); + } + + public void run(Context context, boolean toggleActionIfPossible) + { + switch(this.getAction()) + { + case changeSoundProfile: + /* + * Old version. Those checks should not be necessary anymore. Also they didn't work + * because profiles were created with names like silent, vibrate and normal. + */ +// if(this.getParameter2().equals("silent")) +// Actions.setSound(context, AudioManager.RINGER_MODE_SILENT); +// else if(this.getParameter2().equals("vibrate")) +// Actions.setSound(context, AudioManager.RINGER_MODE_VIBRATE); +// else if(this.getParameter2().equals("normal")) +// Actions.setSound(context, AudioManager.RINGER_MODE_NORMAL); +// else +// { + Profile p = Profile.getByName(this.getParameter2()); + if(p != null) + p.activate(context); +// } + break; + case triggerUrl: + triggerUrl(context); + break; + case setBluetooth: + Actions.setBluetooth(context, getParameter1(), toggleActionIfPossible); + break; + case setUsbTethering: + Actions.setUsbTethering(context, getParameter1(), toggleActionIfPossible); + break; + case setWifi: + Actions.setWifi(context, getParameter1(), toggleActionIfPossible); + break; + case setWifiTethering: + Actions.setWifiTethering(context, getParameter1(), toggleActionIfPossible); + break; + case setDisplayRotation: + Actions.setDisplayRotation(context, getParameter1(), toggleActionIfPossible); + break; + case startOtherActivity: + Actions.startOtherActivity(getParameter2()); + break; + case waitBeforeNextAction: + Actions.waitBeforeNextAction(Long.parseLong(this.getParameter2())); + break; + case wakeupDevice: + Actions.wakeupDevice(Long.parseLong(this.getParameter2())); + // wakeupDevice() will create a seperate thread. That'll take some time, we wait 100ms. + try + { + Thread.sleep(100); + } + catch (InterruptedException e) + { + e.printStackTrace(); + } + break; + case setAirplaneMode: + Actions.setAirplaneMode(this.getParameter1(), toggleActionIfPossible); + break; + case setDataConnection: + Actions.MobileDataStuff.setDataConnection(this.getParameter1(), toggleActionIfPossible); + break; + case speakText: + Actions.speakText(this.getParameter2()); + break; + case playMusic: + Actions.playMusic(this.getParameter1(), toggleActionIfPossible); + break; + case sendTextMessage: + Actions.sendTextMessage(context, this.getParameter2().split(Actions.smsSeparator)); + break; + case setScreenBrightness: + Actions.setScreenBrightness(getParameter1(), Integer.parseInt(getParameter2())); + break; + default: + Miscellaneous.logEvent("w", "Action", context.getResources().getString(R.string.unknownActionSpecified), 3); + break; + } + } + + private void triggerUrl(Context context) + { + String username = null; + String password = null; + String url; + + String[] components = getParameter2().split(";"); + + if(components.length >= 3) + { + username = components[0]; + password = components[1]; + url = components[2]; + } + else + url = components[0]; + + try + { + url = Miscellaneous.replaceVariablesInText(url, context); + + Actions myAction = new Actions(); + + Miscellaneous.logEvent("i", "HTTP", "Attempting download of " + url, 4); //getResources().getString("attemptingDownloadOf"); + + if(this.getParameter1()) // use authentication + new DownloadTask().execute(url, username, password); + else + new DownloadTask().execute(url, null, null); + } + catch(Exception e) + { + Miscellaneous.logEvent("e", "triggerUrl", context.getResources().getString(R.string.errorTriggeringUrl) + ": " + e.getMessage() + ", detailed: " + Log.getStackTraceString(e), 2); + } + } + + public class DownloadTask extends AsyncTask + { + @Override + public String doInBackground(String... parameters) + { + Thread.setDefaultUncaughtExceptionHandler(Miscellaneous.uncaughtExceptionHandler); + + int attempts=1; + String urlString=parameters[0]; + + String urlUsername = null; + String urlPassword = null; + if(parameters.length >= 3) + { + urlUsername=parameters[1]; + urlPassword=parameters[2]; + } + + String response = "httpError"; + HttpGet post; + + if(Settings.httpAttempts < 1) + Miscellaneous.logEvent("w", "HTTP Request", Miscellaneous.getAnyContext().getResources().getString(R.string.cantDownloadTooFewRequestsInSettings), 3); + + while(attempts <= Settings.httpAttempts && response.equals("httpError")) + { + Miscellaneous.logEvent("i", "HTTP Request", "Attempt " + String.valueOf(attempts++) + " of " + String.valueOf(Settings.httpAttempts), 3); + +// try +// { + // Either thorough checking or no encryption + if(!Settings.httpAcceptAllCertificates | !urlString.toLowerCase(Locale.getDefault()).contains("https")) +// { +// URL url = new URL(urlString); +// URLConnection urlConnection = url.openConnection(); +// urlConnection.setReadTimeout(Settings.httpAttemptsTimeout * 1000); +// InputStream in = urlConnection.getInputStream(); +// response = Miscellaneous.convertStreamToString(in); + + response = Miscellaneous.downloadURL(urlString, urlUsername, urlPassword); +// } + else +// { + response = Miscellaneous.downloadURLwithoutCertificateChecking(urlString, urlUsername, urlPassword); +// post = new HttpGet(new URI(urlString)); +// final HttpParams httpParams = new BasicHttpParams(); +// HttpConnectionParams.setConnectionTimeout(httpParams, Settings.httpAttemptsTimeout * 1000); +// HttpClient client = new DefaultHttpClient(httpParams); +// +// client = sslClient(client); +// +// // Execute HTTP Post Request +// HttpResponse result = client.execute(post); +// response = EntityUtils.toString(result.getEntity()); +// } +// } +// catch (URISyntaxException e) +// { +// Miscellaneous.logEvent("w", "HTTP RESULT", Log.getStackTraceString(e), 3); +// } +// catch (ClientProtocolException e) +// { +// Miscellaneous.logEvent("w", "HTTP RESULT", Log.getStackTraceString(e), 3); +// } +// catch (IOException e) +// { +// Miscellaneous.logEvent("w", "HTTP RESULT", Log.getStackTraceString(e), 3); +// e.printStackTrace(); +// } +// finally +// { + try + { + Thread.sleep(Settings.httpAttemptGap * 1000); + } + catch (InterruptedException e1) + { + Miscellaneous.logEvent("w", "HTTP RESULT", "Failed to pause between HTTP requests.", 5); + } +// } + } + +// Miscellaneous.logEvent("i", "HTTPS RESULT", response, 3); + + return response; + } + + @Override + public void onPostExecute(String result) + { + //Do something with result + //Toast.makeText(context, text, duration) result; + Miscellaneous.logEvent("i", "HTTP RESULT", result, 3); + Actions myAction=new Actions(); + myAction.useDownloadedWebpage(result); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/jens/automation2/Actions.java b/app/src/main/java/com/jens/automation2/Actions.java new file mode 100644 index 0000000..fa6cf3a --- /dev/null +++ b/app/src/main/java/com/jens/automation2/Actions.java @@ -0,0 +1,1192 @@ +package com.jens.automation2; + +import android.annotation.SuppressLint; +import android.annotation.TargetApi; +import android.app.PendingIntent; +import android.bluetooth.BluetoothAdapter; +import android.content.ActivityNotFoundException; +import android.content.Context; +import android.content.Intent; +import android.media.AudioManager; +import android.net.ConnectivityManager; +import android.net.wifi.WifiManager; +import android.os.Build; +import android.os.PowerManager; +import android.os.PowerManager.WakeLock; +import android.provider.MediaStore; +import android.telephony.SmsManager; +import android.telephony.SubscriptionManager; +import android.telephony.TelephonyManager; +import android.util.Log; +import android.view.WindowManager; +import android.widget.Toast; + +import com.jens.automation2.location.WifiBroadcastReceiver; +import com.jens.automation2.receivers.ConnectivityReceiver; + +import org.apache.http.client.HttpClient; +import org.apache.http.conn.ClientConnectionManager; +import org.apache.http.conn.scheme.Scheme; +import org.apache.http.conn.scheme.SchemeRegistry; +import org.apache.http.conn.ssl.SSLSocketFactory; +import org.apache.http.conn.util.InetAddressUtils; +import org.apache.http.impl.client.DefaultHttpClient; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.net.InetAddress; +import java.net.NetworkInterface; +import java.security.KeyStore; +import java.util.Collections; +import java.util.List; + +import javax.net.ssl.SSLContext; + +import eu.chainfire.libsuperuser.Shell; + +public class Actions +{ + public static AutomationService autoMationServerRef; + public static Context context; + private static Intent playMusicIntent; + private static boolean suAvailable = false; + private static String suVersion = null; + private static String suVersionInternal = null; + private static List suResult = null; + final static String smsSeparator = "&sms&"; + + public static Boolean setWifi(Context context, Boolean desiredState, boolean toggleActionIfPossible) + { + Miscellaneous.logEvent("i", "Wifi", "Changing Wifi to " + String.valueOf(desiredState), 4); + + if(desiredState && Settings.useWifiForPositioning) + WifiBroadcastReceiver.startWifiReceiver(autoMationServerRef.getLocationProvider()); + + WifiManager myWifi = (WifiManager)context.getSystemService(Context.WIFI_SERVICE); + + // toggle + if(toggleActionIfPossible) + { + Toast.makeText(context, context.getResources().getString(R.string.toggling) + " " + context.getResources().getString(R.string.wifi), Toast.LENGTH_LONG).show(); + desiredState = !myWifi.isWifiEnabled(); + } + + // Only perform action if necessary + if((!myWifi.isWifiEnabled() && desiredState) | (myWifi.isWifiEnabled() && !desiredState)) + { + String wifiString = ""; + + if(desiredState) + { + wifiString = context.getResources().getString(R.string.activating) + " " + context.getResources().getString(R.string.wifi); + } + else + { + wifiString = context.getResources().getString(R.string.deactivating) + " " + context.getResources().getString(R.string.wifi); + } + + Toast.makeText(context, wifiString, Toast.LENGTH_LONG).show(); + + boolean returnValue = myWifi.setWifiEnabled(desiredState); + if(!returnValue) + Miscellaneous.logEvent("i", "Wifi", "Error changing Wifi to " + String.valueOf(desiredState), 2); + else + Miscellaneous.logEvent("i", "Wifi", "Wifi changed to " + String.valueOf(desiredState), 2); + + return returnValue; + } + + return true; + } + + public static void setDisplayRotation(Context myContext, Boolean desiredState, boolean toggleActionIfPossible) + { + Miscellaneous.logEvent("i", "ScreenRotation", "Changing ScreenRotation to " + String.valueOf(desiredState), 4); + try + { + if(toggleActionIfPossible) + { + Miscellaneous.logEvent("i", "setScreenRotation", myContext.getResources().getString(R.string.toggling), 2); + boolean currentStatus = android.provider.Settings.System.getInt(myContext.getContentResolver(),android.provider.Settings.System.ACCELEROMETER_ROTATION, 0) == 0; + if(currentStatus) + desiredState = !currentStatus; + } + + if(desiredState) + { + if(android.provider.Settings.System.getInt(myContext.getContentResolver(),android.provider.Settings.System.ACCELEROMETER_ROTATION, 0) == 0) + { + android.provider.Settings.System.putInt(myContext.getContentResolver(),android.provider.Settings.System.ACCELEROMETER_ROTATION, 1); + Miscellaneous.logEvent("i", "setScreenRotation", myContext.getResources().getString(R.string.screenRotationEnabled), 2); + } + else + Miscellaneous.logEvent("i", "setScreenRotation", myContext.getResources().getString(R.string.screenRotationAlreadyEnabled), 2); + } + else + { + if(android.provider.Settings.System.getInt(myContext.getContentResolver(),android.provider.Settings.System.ACCELEROMETER_ROTATION, 0) == 1) + { + android.provider.Settings.System.putInt(myContext.getContentResolver(),android.provider.Settings.System.ACCELEROMETER_ROTATION, 0); + Miscellaneous.logEvent("i", "setScreenRotation", myContext.getResources().getString(R.string.screenRotationDisabled), 2); + } + else + Miscellaneous.logEvent("i", "setScreenRotation", myContext.getResources().getString(R.string.screenRotationAlreadyDisabled), 2); + } + } + catch(Exception e) + { + Miscellaneous.logEvent("e", "setScreenRotation", myContext.getResources().getString(R.string.errorChangingScreenRotation) + ": " + Log.getStackTraceString(e), 2); + } + } + + private static boolean isWifiApEnabled(Context context) + { + WifiManager wifiManager = (WifiManager)context.getSystemService(Context.WIFI_SERVICE); + boolean currentlyEnabled = false; + + Method[] methods = wifiManager.getClass().getDeclaredMethods(); + for(Method method : methods) + { + if(method.getName().equals("isWifiApEnabled")) + { + try + { + Object returnObject = method.invoke(wifiManager); + currentlyEnabled = Boolean.valueOf(returnObject.toString()); + + if(currentlyEnabled) + Miscellaneous.logEvent("i", "isWifiApEnabled", "true", 5); + else + Miscellaneous.logEvent("i", "isWifiApEnabled", "false", 5); + } + catch(Exception e) + { + Miscellaneous.logEvent("i", "isWifiApEnabled", context.getResources().getString(R.string.errorDeterminingWifiApState) + ": " + e.getMessage(), 4); + } + } + } + return currentlyEnabled; + } + public static Boolean setWifiTethering(Context context, Boolean desiredState, boolean toggleActionIfPossible) + { + Miscellaneous.logEvent("i", "WifiTethering", "Changing WifiTethering to " + String.valueOf(desiredState), 4); + + boolean state = Actions.isWifiApEnabled(context); + + if(toggleActionIfPossible) + { + Miscellaneous.logEvent("i", "WifiAp", context.getResources().getString(R.string.toggling), 2); + desiredState = !state; + } + + if(((state && !desiredState) || (!state && desiredState))) + { + WifiManager wifiManager = (WifiManager)context.getSystemService(Context.WIFI_SERVICE); + Method[] methods = wifiManager.getClass().getDeclaredMethods(); + for(Method method : methods) + { +// Miscellaneous.logEvent("i", "WifiAp", "Trying to find appropriate method... " + method.getName()); + if(method.getName().equals("setWifiApEnabled")) + { + try + { + String desiredString = ""; + if(desiredState) + desiredString = "activate"; + else + desiredString = "deactivate"; + + if(!toggleActionIfPossible) + { + Miscellaneous.logEvent("i", "WifiAp", "Trying to " + desiredString + " wifi ap...", 2); + if(!method.isAccessible()) + method.setAccessible(true); + method.invoke(wifiManager, null, desiredState); + } + else + { + Miscellaneous.logEvent("i", "WifiAp", "Trying to " + context.getResources().getString(R.string.toggle) + " wifi ap...", 2); + if(!method.isAccessible()) + method.setAccessible(true); + method.invoke(wifiManager, null, !state); + } + + Miscellaneous.logEvent("i", "WifiAp", "Wifi ap " + desiredString + "d.", 2); + } + catch(Exception e) + { + Miscellaneous.logEvent("i", "WifiAp", context.getResources().getString(R.string.errorActivatingWifiAp) + ". " + e.getMessage(), 2); + } + } + } + } + return true; + } + + public static boolean setUsbTethering(Context context2, Boolean desiredState, boolean toggleActionIfPossible) + { + //TODO:toggle not really implemented, yet + + Miscellaneous.logEvent("i", "UsbTethering", "Changing UsbTethering to " + String.valueOf(desiredState), 4); + + boolean state = false; //Actions.isWifiApEnabled(context); + Object connectivityServiceObject = null; + ConnectivityManager connMgr = null; + + try + { + connectivityServiceObject = context.getSystemService(Context.CONNECTIVITY_SERVICE); + connMgr = (ConnectivityManager)connectivityServiceObject; + } + catch(Exception e) + { + Miscellaneous.logEvent("e", "UsbTethering", context2.getResources().getString(R.string.logErrorGettingConnectionManagerService), 2); + return false; + } + + try + { + if((state && !desiredState) || (!state && desiredState)) + { + try + { + Method method = connectivityServiceObject.getClass().getDeclaredMethod("getTetheredIfaces"); + if(!method.isAccessible()) + method.setAccessible(true); + String[] tetheredInterfaces = (String[]) method.invoke(connectivityServiceObject); + if(tetheredInterfaces.length > 0) + state = true; + } + catch(NoSuchMethodException e) + { + // System doesn't have that method, try another way + + String ipAddr = getIPAddressUsb(true); + if (ipAddr.length() == 0) + state = false; // tethering not enabled + else + state = true; // tethering enabled + } + } + } + catch(Exception e) + { + Miscellaneous.logEvent("w", "UsbTethering", context2.getResources().getString(R.string.logErrorDeterminingCurrentUsbTetheringState), 3); + } + + if(toggleActionIfPossible) + { + Miscellaneous.logEvent("w", "UsbTethering", context2.getResources().getString(R.string.toggling), 3); + desiredState = !state; + } + + + if((state && !desiredState) || (!state && desiredState)) + { + String desiredString = ""; + if(desiredState) + desiredString = "activate"; + else + desiredString = "deactivate"; + + try + { + Method method = null; + + for(Method m : connectivityServiceObject.getClass().getDeclaredMethods()) + { + if(desiredState && m.getName().equals("tether")) + { + method = m; + break; + } + + if(!desiredState && m.getName().equals("untether")) + { + method = m; + break; + } + } + + if(method == null) + throw new NoSuchMethodException(); + + + /* + * For some reason this doesn't work, throws NoSuchMethodExpection even if the method is present. + */ +// if(desiredState) +// method = connectivityServiceObject.getClass().getDeclaredMethod("tether"); +// else +// method = connectivityServiceObject.getClass().getDeclaredMethod("untether"); + + // DETECT INTERFACE NAME + Miscellaneous.logEvent("i", "UsbTethering", context2.getResources().getString(R.string.logDetectingTetherableUsbInterface), 4); + String[] available = null; + Method[] wmMethods = connMgr.getClass().getDeclaredMethods(); + for(Method getMethod: wmMethods) + { + if(getMethod.getName().equals("getTetherableUsbRegexs")) + { + try + { + if(!method.isAccessible()) + method.setAccessible(true); + available = (String[]) getMethod.invoke(connMgr); +// break; + } + catch (Exception e) + { + e.printStackTrace(); + } + } + } + // DETECT INTERFACE NAME + + + if(available.length > 0) + { + for(String interfaceName : available) + { + Miscellaneous.logEvent("i", "UsbTethering", "Detected " + String.valueOf(available.length) + " tetherable usb interfaces.", 5); + Miscellaneous.logEvent("i", "UsbTethering", "Trying to " + desiredString + " UsbTethering on interface " + interfaceName + "...", 5); + if(!method.isAccessible()) + method.setAccessible(true); + Integer returnCode = (Integer)method.invoke(connectivityServiceObject, interfaceName); + if(returnCode == 0) + { + Miscellaneous.logEvent("i", "UsbTethering", "UsbTethering " + desiredString + "d.", 5); + return true; + } + else + { + Miscellaneous.logEvent("w", "UsbTethering", "Failed to " + desiredString + "Usb Tethering. ReturnCode of method " + method.getName() + ": " + String.valueOf(returnCode), 5); + } + } + } + } + catch (NoSuchMethodException e) + { + Miscellaneous.logEvent("w", "UsbTethering", "Error while trying to " + desiredString + " UsbTethering. This kind of error may indicate we are above Android 2.3: " + Log.getStackTraceString(e), 3); + } + catch(Exception e) + { + Miscellaneous.logEvent("w", "UsbTethering", "Error while trying to " + desiredString + " UsbTethering. " + Log.getStackTraceString(e), 3); + } + } + return false; + } + + public static Boolean setBluetooth(Context context, Boolean desiredState, boolean toggleActionIfPossible) + { + Miscellaneous.logEvent("i", "Bluetooth", "Changing bluetooth to " + String.valueOf(desiredState), 4); + + try + { + BluetoothAdapter myBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); + + // toggle + if(toggleActionIfPossible) + { + Miscellaneous.logEvent("e", "SetBluetooth", context.getResources().getString(R.string.toggling), 2); + desiredState = !myBluetoothAdapter.isEnabled(); + } + + // activate + if(!myBluetoothAdapter.isEnabled() && desiredState) + { + Toast.makeText(context, context.getResources().getString(R.string.activating) + " Bluetooth.", Toast.LENGTH_LONG).show(); + myBluetoothAdapter.enable(); + return true; + } + + // deactivate + if(myBluetoothAdapter.isEnabled() && !desiredState) + { + Toast.makeText(context, context.getResources().getString(R.string.deactivating) + " Bluetooth.", Toast.LENGTH_LONG).show(); + myBluetoothAdapter.disable(); + return true; + } + } + catch(NullPointerException e) + { + Miscellaneous.logEvent("e", "SetBluetooth", context.getResources().getString(R.string.failedToTriggerBluetooth), 2); + Toast.makeText(context, context.getResources().getString(R.string.bluetoothFailed), Toast.LENGTH_LONG).show(); + } + + return false; + } + + public static void setSound(Context context, int soundSetting) + { + Miscellaneous.logEvent("i", context.getResources().getString(R.string.soundSettings), "Changing sound to " + String.valueOf(soundSetting), 4); + + AudioManager myAudioManager = (AudioManager)context.getSystemService(Context.AUDIO_SERVICE); + myAudioManager.setRingerMode(soundSetting); + } + + private static String getIPAddressUsb(final boolean useIPv4) + { + try + { + final List interfaces = Collections.list(NetworkInterface.getNetworkInterfaces()); + for (final NetworkInterface intf : interfaces) + { + if (intf.getDisplayName().startsWith("usb")) // ro "rndis0" + { + final List addrs = Collections.list(intf.getInetAddresses()); + for (final InetAddress addr : addrs) + { + final String sAddr = addr.getHostAddress().toUpperCase(); + final boolean isIPv4 = InetAddressUtils.isIPv4Address(sAddr); + if (useIPv4) + { + if (isIPv4) + { + return sAddr; + } + } + else + { + if (!isIPv4) + { + final int delim = sAddr.indexOf('%'); + return delim < 0 ? sAddr : sAddr.substring(0, delim); + } + } + } + } + } + } + catch (final Exception ex) + { + // for now eat exceptions + } + return ""; + } + + public void useDownloadedWebpage(String result) + { +// Toast.makeText(context, "Result: " + result, Toast.LENGTH_LONG).show(); + } + + public static HttpClient getInsecureSslClient(HttpClient client) + { + try + { + SSLContext ctx = SSLContext.getInstance("TLS"); + SSLSocketFactory ssf = null; +// MySSLSocketFactoryInsecure ssfI = null; + +// if(!Settings.httpAcceptAllCertificates) +// { +// ssf = new MySSLSocketFactory(ctx); +// ssf.setHostnameVerifier(SSLSocketFactory.STRICT_HOSTNAME_VERIFIER); +// +// char[] keystorePass="insecure".toCharArray(); //passphrase for keystore +// KeyStore keyStore=KeyStore.getInstance("BKS"); +// +//// String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm(); +// TrustManagerFactory tmf = TrustManagerFactory.getInstance("X509"); +// tmf.init(keyStore); +// +// Miscellaneous.logEvent("i", "SSL Keystore", context.getCacheDir().toString(), 4); +// InputStream is = context.getResources().openRawResource(R.raw.keystore); +// keyStore.load(is,keystorePass); +// +// ctx.init(null, tmf.getTrustManagers(), null); +// } +// else +// { + KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType()); + trustStore.load(null, null); + ssf = new MySSLSocketFactoryInsecure(trustStore); + ssf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER); + ctx.init(null, null, null); +// } + + ClientConnectionManager ccm = client.getConnectionManager(); + SchemeRegistry sr = ccm.getSchemeRegistry(); + +// if(!Settings.httpAcceptAllCertificates) + sr.register(new Scheme("https", ssf, 443)); +// else +// sr.register(new Scheme("https", ssfI, 443)); + + return new DefaultHttpClient(ccm, client.getParams()); + } + catch (Exception ex) + { + ex.printStackTrace(); + return null; + } + } + + public static void startOtherActivity(String param) + { + Miscellaneous.logEvent("i", "StartOtherActivity", "Starting other Activity...", 4); + + String packageName, className; + String params[] = param.split(";"); + packageName = params[0]; + className = params[1]; + + try + { + Miscellaneous.logEvent("i", "StartOtherApp", "Starting " + packageName + " " + className, 3); + Intent externalActivityIntent = new Intent(Intent.ACTION_MAIN); + externalActivityIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + externalActivityIntent.addCategory(Intent.CATEGORY_LAUNCHER); + externalActivityIntent.setClassName(packageName, className); + + // has intent values to deliver + for(int i=2; i 0) + duration = String.valueOf(awakeTime) + " milliseconds"; + + Miscellaneous.logEvent("i", "wakeupDevice", "wakeupDevice for " + String.valueOf(duration) + ".", 4); + + if(awakeTime > 0) + { + /* + * This action needs to be performed in a separate thread. If it ran in the same one + * the screen would turn on, the specified amount of time would pass and the screen + * would turn off again before any other action in the rule would be ran. + */ + Thread t = new Thread(new WakeUpDeviceClass(awakeTime)); + t.start(); + } + } + + public static void sendTextMessage(Context context, String[] parametersArray) + { + String phoneNumber, message; + + phoneNumber = parametersArray[0]; + message = parametersArray[1]; + + try + { + String textToSend = Miscellaneous.replaceVariablesInText(message, context); + + /* + Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("sms:" + phoneNumber)); + intent.putExtra("sms_body", message); + AutomationService.getInstance().startActivity(intent); + */ + + PendingIntent pi = PendingIntent.getActivity(context, 0, new Intent(context, Actions.class), 0); + SmsManager sms = SmsManager.getDefault(); + sms.sendTextMessage(phoneNumber, null, message, pi, null); + } + catch(Exception e) + { + Miscellaneous.logEvent("e", Miscellaneous.getAnyContext().getString(R.string.sendTextMessage), "Error in sendTextMessage: " + Log.getStackTraceString(e), 3); + } + } + + private static class WakeUpDeviceClass implements Runnable + { + private long awakeTime; + + public WakeUpDeviceClass(long awakeTime) + { + super(); + this.awakeTime = awakeTime; + } + + @Override + public void run() + { + PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE); + WakeLock wakeLock = pm.newWakeLock((WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON | PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP), "Automation:Wakelock"); + wakeLock.acquire(); + + try + { + Thread.sleep(awakeTime); + } + catch (InterruptedException e) + { + Miscellaneous.logEvent("w", context.getResources().getString(R.string.wakeupDevice), "Error keeping device awake: " + Log.getStackTraceString(e), 4); + } + + wakeLock.release(); + } + } + + @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) + @SuppressLint("NewApi") + public static boolean setAirplaneMode(boolean desiredState, boolean toggleActionIfPossible) + { + // Beginning from SDK Version 17 this may not work anymore. + // Setting airplane_mode_on has moved from android.provider.Settings.System to android.provider.Settings.Global, value is unchanged. + + boolean returnValue = false; + + try + { + boolean isEnabled = ConnectivityReceiver.isAirplaneMode(autoMationServerRef); + + if(isEnabled) + Miscellaneous.logEvent("i", "Airplane mode", "Current status is enabled.", 4); + else + Miscellaneous.logEvent("i", "Airplane mode", "Current status is disabled.", 4); + + if(toggleActionIfPossible) + { + Miscellaneous.logEvent("i", "Airplane mode", context.getResources().getString(R.string.toggling), 4); + desiredState = !isEnabled; + } + + if(isEnabled != desiredState) + { + int desiredValueInt = 0; + + if(desiredState) + desiredValueInt = 1; + + if(Build.VERSION.SDK_INT < 17) + { + returnValue = android.provider.Settings.System.putInt(context.getContentResolver(), android.provider.Settings.System.AIRPLANE_MODE_ON, desiredValueInt); + } + else + { + if(desiredState) + { + String[] commands = new String[] + { + "settings put global airplane_mode_on 1", + "am broadcast -a android.intent.action.AIRPLANE_MODE --ez state true" + }; + returnValue = executeCommandViaSu(commands); + } + else + { + + String[] commands = new String[] + { + "settings put global airplane_mode_on 0", + "am broadcast -a android.intent.action.AIRPLANE_MODE --ez state false" + }; + returnValue = executeCommandViaSu(commands); + } + +// returnValue = android.provider.Settings.Global.putString(context.getContentResolver(), "airplane_mode_on", String.valueOf(desiredValueInt)); + } + + // Post an intent to reload + Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED); + intent.putExtra("state", !isEnabled); + context.sendBroadcast(intent); + } + else + Miscellaneous.logEvent("i", "Airplane mode", "Airplane mode is already in status " + String.valueOf(desiredState) + ". Nothing to do.", 3); + } + catch(Exception e) + { + Miscellaneous.logEvent("e", "Airplane mode", Log.getStackTraceString(e), 2); + } + + return returnValue; + } + + /** + * Toggles the device between the different types of networks. + * Might not work. It seems only system apps are allowed to do this. + */ + public static boolean setNetworkType(int desiredType) + { + Miscellaneous.logEvent("i", "setNetworkType", "Asked to set network type to: " + String.valueOf(desiredType), 3); + if(desiredType > 0) + { + try + { + ConnectivityManager connManager = (ConnectivityManager) Miscellaneous.getAnyContext().getSystemService(context.CONNECTIVITY_SERVICE); + + // TelephonyManager.NETWORK_TYPE_EDGE + + if(connManager.getNetworkPreference() == desiredType) + { + Miscellaneous.logEvent("i", "setNetworkType", "Desired networkType already set. Not doing anything.", 4); + } + else + { + Miscellaneous.logEvent("i", "setNetworkType", "Setting network type to: " + String.valueOf(desiredType), 3); + connManager.setNetworkPreference(desiredType); + } + return true; + } + catch(Exception e) + { + Miscellaneous.logEvent("e", "setNetworkType", "Error changing network type: " + Log.getStackTraceString(e), 2); + return false; + } + } + else + { + Miscellaneous.logEvent("w", "setNetworkType", "Invalid type of network specified: " + String.valueOf(desiredType), 4); + return false; + } + } + + public static void speakText(String parameter2) + { + try + { + String textToSpeak = Miscellaneous.replaceVariablesInText(parameter2, context); + autoMationServerRef.speak(textToSpeak, true); + } + catch(Exception e) + { + Miscellaneous.logEvent("e", "Speak text", "Error in speak text: " + Log.getStackTraceString(e), 3); + } + } + + @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) + public static boolean playMusic(boolean desiredState, boolean toggleActionIfPossible) + { + try + { + //TODO:toggle + // if(desiredState) + // { + Miscellaneous.logEvent("e", "Play music", "Starting music player...", 3); + + String deviceName = Build.MODEL; + /* + * Fix for Samsung devices: http://stackoverflow.com/questions/12532207/open-music-player-on-galaxy-s3 + */ + if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1 | deviceName.contains("SM-")) + playMusicIntent = new Intent(Intent.CATEGORY_APP_MUSIC); + else + playMusicIntent = new Intent(MediaStore.INTENT_ACTION_MUSIC_PLAYER); + + playMusicIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + context.startActivity(playMusicIntent); + + // playMusicIntent = new Intent(); + // playMusicIntent.setAction(android.content.Intent.ACTION_VIEW); + // File file = new File(YOUR_SONG_URI); + // playMusicIntent.setDataAndType(Uri.fromFile(file), "audio/*"); + // context.startActivity(playMusicIntent); + + return true; + // } + // else + // { + // if(playMusicIntent != null) + // { + // context.stopService(playMusicIntent); + // } + // } + + // return false; + } + catch(ActivityNotFoundException e) + { + Toast.makeText(context, "Error: No music player found.", Toast.LENGTH_LONG).show(); + Miscellaneous.logEvent("e", "Play music", "Error in playerMusic(): No music player found. " + Log.getStackTraceString(e), 3); + return false; + } + catch(Exception e) + { + Toast.makeText(context, "Error starting music player.", Toast.LENGTH_LONG).show(); + Miscellaneous.logEvent("e", "Play music", "Error in playerMusic(): " + Log.getStackTraceString(e), 3); + return false; + } + } + + private String getTransactionCode() + { + try + { + TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); + Class telephonyManagerClass = Class.forName(telephonyManager.getClass().getName()); + Method getITelephonyMethod = telephonyManagerClass.getDeclaredMethod("getITelephony"); + getITelephonyMethod.setAccessible(true); + Object ITelephonyStub = getITelephonyMethod.invoke(telephonyManager); + Class ITelephonyClass = Class.forName(ITelephonyStub.getClass().getName()); + + Class stub = ITelephonyClass.getDeclaringClass(); + Field field = stub.getDeclaredField("TRANSACTION_setDataEnabled"); + field.setAccessible(true); + return String.valueOf(field.getInt(null)); + } + catch (Exception e) + { + if (Build.VERSION.SDK_INT >= 22) + return "86"; + else if (Build.VERSION.SDK_INT == 21) + return "83"; + } + return ""; + } + + private static String getTransactionCodeFromApi20(Context context) throws Exception + { + try + { + final TelephonyManager mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); + final Class mTelephonyClass = Class.forName(mTelephonyManager.getClass().getName()); + final Method mTelephonyMethod = mTelephonyClass.getDeclaredMethod("getITelephony"); + mTelephonyMethod.setAccessible(true); + final Object mTelephonyStub = mTelephonyMethod.invoke(mTelephonyManager); + final Class mTelephonyStubClass = Class.forName(mTelephonyStub.getClass().getName()); + final Class mClass = mTelephonyStubClass.getDeclaringClass(); + final Field field = mClass.getDeclaredField("TRANSACTION_setDataEnabled"); + field.setAccessible(true); + return String.valueOf(field.getInt(null)); + } + catch (Exception e) + { + // The "TRANSACTION_setDataEnabled" field is not available, + // or named differently in the current API level, so we throw + // an exception and inform users that the method is not available. + throw e; + } + } + + public static class MobileDataStuff + { + /** + * Turns data on and off. + * Requires root permissions from lollipop on. + * @param toggleActionIfPossible + */ + public static boolean setDataConnection(boolean desiredState, boolean toggleActionIfPossible) + { + Miscellaneous.logEvent("i", "setData", "Asked to turn data to: " + String.valueOf(desiredState), 3); + + try + { + ConnectivityManager connManager = (ConnectivityManager) Miscellaneous.getAnyContext().getSystemService(Miscellaneous.getAnyContext().CONNECTIVITY_SERVICE); + final Class conmanClass = Class.forName(connManager.getClass().getName()); + final Field iConnectivityManagerField = conmanClass.getDeclaredField("mService"); + iConnectivityManagerField.setAccessible(true); + final Object iConnectivityManager = iConnectivityManagerField.get(connManager); + final Class iConnectivityManagerClass = Class.forName(iConnectivityManager.getClass().getName()); + + Boolean isEnabled = isMobileDataEnabled(); + + if(toggleActionIfPossible) + { + context.getResources().getString(R.string.toggling); + desiredState = !isEnabled; + } + +// if(isEnabled != desiredState) +// { + if(Build.VERSION.SDK_INT <= 20) + { + for(Method m : iConnectivityManagerClass.getDeclaredMethods()) + { + Miscellaneous.logEvent("i", "method", m.getName(), 5); + } + + final Method setMobileDataEnabledMethod = iConnectivityManagerClass.getDeclaredMethod("setMobileDataEnabled", Boolean.TYPE); + setMobileDataEnabledMethod.setAccessible(true); + + setMobileDataEnabledMethod.invoke(iConnectivityManager, desiredState); + } + else + { + return setDataConnectionWithRoot(desiredState); + } +// } +// else +// { +// Miscellaneous.logEvent("i", "setData", "Data already set to " + String.valueOf(desiredState) + ". Not doing anything.", 4); +// } + + return true; + } + catch(Exception e) + { + Miscellaneous.logEvent("e", "setData", "Error changing network type: " + Log.getStackTraceString(e), 2); + return false; + } + } + + protected static boolean setDataConnectionWithRoot(boolean enable) + { + try + { + int desiredState = 0; + if(enable) + desiredState = 1; + + if(MobileDataStuff.setMobileNetworkfromLollipop(desiredState, autoMationServerRef)) + { + Miscellaneous.logEvent("i", "setDataConnectionWithRoot()", Miscellaneous.getAnyContext().getResources().getString(R.string.dataConWithRootSuccess), 2); + return true; + } + else + { + Miscellaneous.logEvent("e", "setDataConnectionWithRoot()", Miscellaneous.getAnyContext().getResources().getString(R.string.dataConWithRootFail), 2); + return false; + } + } + catch(Exception e) + { + String rootString; + if(Miscellaneous.isPhoneRooted()) + rootString = Miscellaneous.getAnyContext().getResources().getString(R.string.phoneIsRooted); + else + rootString = Miscellaneous.getAnyContext().getResources().getString(R.string.phoneIsNotRooted); + + Miscellaneous.logEvent("e", "setDataWithRoot()", "Error setting data setting with root. " + rootString + ": " + Log.getStackTraceString(e), 3); + return false; + } + } + + @SuppressLint("NewApi") + public static boolean setMobileNetworkfromLollipop(int desiredState, Context context) throws Exception + { + String command = null; + int state = 0; + + try + { + // Get the current state of the mobile network. + state = isMobileDataEnabled() ? 0 : 1; + // Get the value of the "TRANSACTION_setDataEnabled" field. + String transactionCode = getTransactionCode(context); + // Android 5.1+ (API 22) and later. + if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP) + { + SubscriptionManager mSubscriptionManager = (SubscriptionManager) context.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE); + // Loop through the subscription list i.e. SIM list. + for (int i = 0; i < mSubscriptionManager.getActiveSubscriptionInfoCountMax(); i++) + { + if (transactionCode != null && transactionCode.length() > 0) + { + // Get the active subscription ID for a given SIM card. + int subscriptionId = mSubscriptionManager.getActiveSubscriptionInfoList().get(i).getSubscriptionId(); + // Execute the command via `su` to turn off + // mobile network for a subscription service. + command = "service call phone " + transactionCode + " i32 " + subscriptionId + " i32 " + desiredState; + Miscellaneous.logEvent("i", "setDataConnectionWithRoot()", "Running command: " + command.toString(), 5); + return executeCommandViaSu(new String[] { command }); + } + } + } + else if (Build.VERSION.SDK_INT == Build.VERSION_CODES.LOLLIPOP) + { + // Android 5.0 (API 21) only. + if (transactionCode != null && transactionCode.length() > 0) + { + // Execute the command via `su` to turn off mobile network. + command = "service call phone " + transactionCode + " i32 " + desiredState; + return executeCommandViaSu(new String[] { command }); + } + } + } + catch(Exception e) + { + // Oops! Something went wrong, so we throw the exception here. + throw e; + } + + return false; + } + + @SuppressLint("NewApi") + public static boolean isMobileDataEnabled() + { + boolean isEnabled = false; + + if(Build.VERSION.SDK_INT <= 20) + { + try + { + ConnectivityManager connManager = (ConnectivityManager) Miscellaneous.getAnyContext().getSystemService(Miscellaneous.getAnyContext().CONNECTIVITY_SERVICE); + final Class conmanClass = Class.forName(connManager.getClass().getName()); + final Field iConnectivityManagerField = conmanClass.getDeclaredField("mService"); + iConnectivityManagerField.setAccessible(true); + final Object iConnectivityManager = iConnectivityManagerField.get(connManager); + final Class iConnectivityManagerClass = Class.forName(iConnectivityManager.getClass().getName()); + + Method getMobileDataEnabledMethod = null; + for(Method m : iConnectivityManagerClass.getDeclaredMethods()) + { + Miscellaneous.logEvent("i", "Methods", m.getName(), 5); + if(m.getName().equals("getMobileDataEnabled")) + { + getMobileDataEnabledMethod = m; + break; + } + } + // final Method getMobileDataEnabledMethod = iConnectivityManagerClass.getDeclaredMethod("getMobileDataEnabled", null); + + getMobileDataEnabledMethod.setAccessible(true); + + isEnabled = (Boolean) getMobileDataEnabledMethod.invoke(iConnectivityManager, (Object[]) null); + } + catch (Exception e) + { + Miscellaneous.logEvent("e", "isMobileDataEnabled()", "Error checking if mobile data is enabled: " + Log.getStackTraceString(e), 3); + } + } + else + { + isEnabled = android.provider.Settings.Global.getInt(context.getContentResolver(), "mobile_data", 0) == 1; + } + + return isEnabled; + } + + private static String getTransactionCode(Context context) throws Exception + { + try + { + final TelephonyManager mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); + final Class mTelephonyClass = Class.forName(mTelephonyManager.getClass().getName()); + final Method mTelephonyMethod = mTelephonyClass.getDeclaredMethod("getITelephony"); + mTelephonyMethod.setAccessible(true); + final Object mTelephonyStub = mTelephonyMethod.invoke(mTelephonyManager); + final Class mTelephonyStubClass = Class.forName(mTelephonyStub.getClass().getName()); + final Class mClass = mTelephonyStubClass.getDeclaringClass(); + final Field field = mClass.getDeclaredField("TRANSACTION_setDataEnabled"); + field.setAccessible(true); + return String.valueOf(field.getInt(null)); + } + catch (Exception e) + { + // The "TRANSACTION_setDataEnabled" field is not available, + // or named differently in the current API level, so we throw + // an exception and inform users that the method is not available. + throw e; + } + } + } + + protected static boolean executeCommandViaSu(String[] commands) + { + boolean success = false; + + try + { + suAvailable = Shell.SU.available(); + if (suAvailable) + { + suVersion = Shell.SU.version(false); + suVersionInternal = Shell.SU.version(true); + suResult = Shell.SU.run(commands); + + if(suResult != null) + success = true; + } + } + catch (Exception e) + { + success = false; + } + + return success; + } + + public static void setScreenBrightness(boolean autoBrightness, int brightnessValue) + { + if(autoBrightness) + android.provider.Settings.System.putInt(context.getContentResolver(), android.provider.Settings.System.SCREEN_BRIGHTNESS_MODE, android.provider.Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC); + else + android.provider.Settings.System.putInt(context.getContentResolver(), android.provider.Settings.System.SCREEN_BRIGHTNESS_MODE, android.provider.Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL); + + int actualBrightnessValue = (int)((float)brightnessValue / 100.0 * 255.0); + android.provider.Settings.System.putInt(context.getContentResolver(), android.provider.Settings.System.SCREEN_BRIGHTNESS, actualBrightnessValue); + } +} diff --git a/app/src/main/java/com/jens/automation2/ActivityEditSendTextMessage.java b/app/src/main/java/com/jens/automation2/ActivityEditSendTextMessage.java new file mode 100644 index 0000000..8c3307a --- /dev/null +++ b/app/src/main/java/com/jens/automation2/ActivityEditSendTextMessage.java @@ -0,0 +1,181 @@ +package com.jens.automation2; + +import android.app.Activity; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.database.Cursor; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.provider.ContactsContract; +import android.view.View; +import android.view.View.OnClickListener; +import android.widget.Button; +import android.widget.EditText; +import android.widget.Toast; + +import androidx.annotation.NonNull; + +import com.jens.automation2.Action.Action_Enum; + + +public class ActivityEditSendTextMessage extends Activity +{ + Button bSaveSendTextMessage, bImportNumberFromContacts; + EditText etPhoneNumber, etSendTextMessage; + + protected final static int requestCodeForContactsPermissions = 9876; + +// private String existingUrl = ""; + + public static boolean edit = false; + public static Action resultingAction = null; + + @Override + protected void onCreate(Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + this.setContentView(R.layout.send_textmessage_editor); + + etSendTextMessage = (EditText)findViewById(R.id.etSendTextMessage); + etPhoneNumber = (EditText)findViewById(R.id.etPhoneNumber); + bSaveSendTextMessage = (Button)findViewById(R.id.bSaveSendTextMessage); + bImportNumberFromContacts = (Button)findViewById(R.id.bImportNumberFromContacts); + + bSaveSendTextMessage.setOnClickListener(new OnClickListener() + { + @Override + public void onClick(View v) + { + if(etSendTextMessage.getText().toString().length() > 0 && etPhoneNumber.getText().toString().length() > 0) + { + if(resultingAction == null) + { + resultingAction = new Action(); + resultingAction.setAction(Action_Enum.sendTextMessage); + resultingAction.setParameter2(etPhoneNumber.getText().toString() + Actions.smsSeparator + etSendTextMessage.getText().toString()); + } + backToRuleManager(); + } + else + Toast.makeText(getBaseContext(), getResources().getString(R.string.textTooShort), Toast.LENGTH_LONG).show(); + } + }); + + bImportNumberFromContacts.setOnClickListener(new OnClickListener() + { + @Override + public void onClick(View view) + { + if(!ActivityPermissions.havePermission("android.permission.READ_CONTACTS", ActivityEditSendTextMessage.this)) + { + requestPermissions("android.permission.READ_CONTACTS"); + } + else + openContactsDialogue(); + } + }); + + ActivityEditSendTextMessage.edit = getIntent().getBooleanExtra("edit", false); + if(edit) + { + String[] parameters = ActivityEditSendTextMessage.resultingAction.getParameter2().split(Actions.smsSeparator); + etPhoneNumber.setText(parameters[0]); + etSendTextMessage.setText(parameters[1]); + } + + +// String url = getIntent().getStringExtra("urlToTrigger"); +// if(url != null) +// existingUrl = url; + } + + private void backToRuleManager() + { +// Intent returnIntent = new Intent(); +// returnIntent.putExtra("urlToTrigger", existingUrl); + +// setResult(RESULT_OK, returnIntent); + + if(edit && resultingAction != null) + { + ActivityEditSendTextMessage.resultingAction.setParameter2(etPhoneNumber.getText().toString() + Actions.smsSeparator + etSendTextMessage.getText().toString()); + } + + setResult(RESULT_OK); + + this.finish(); + } + protected void requestPermissions(String... requiredPermissions) + { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) + { + if(requiredPermissions.length > 0) + { + StringBuilder permissions = new StringBuilder(); + for (String perm : requiredPermissions) + permissions.append(perm + "; "); + if (permissions.length() > 0) + permissions.delete(permissions.length() - 2, permissions.length()); + + Miscellaneous.logEvent("i", "Permissions", "Requesting permissions: " + permissions, 2); + + requestPermissions(requiredPermissions, requestCodeForContactsPermissions); + } + } + } + + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) + { + if(requestCode == requestCodeForContactsPermissions) + { + for(int i=0; i0) + { + if(resultingAction == null) + { + resultingAction = new Action(); + resultingAction.setAction(Action_Enum.speakText); + resultingAction.setParameter2(etSpeakText.getText().toString()); + } + backToRuleManager(); + } + else + Toast.makeText(getBaseContext(), getResources().getString(R.string.textTooShort), Toast.LENGTH_LONG).show(); + } + }); + + ActivityEditSpeakText.edit = getIntent().getBooleanExtra("edit", false); + if(edit) + etSpeakText.setText(ActivityEditSpeakText.resultingAction.getParameter2()); + + +// String url = getIntent().getStringExtra("urlToTrigger"); +// if(url != null) +// existingUrl = url; + } + + private void backToRuleManager() + { +// Intent returnIntent = new Intent(); +// returnIntent.putExtra("urlToTrigger", existingUrl); + +// setResult(RESULT_OK, returnIntent); + + if(edit && resultingAction != null) + ActivityEditSpeakText.resultingAction.setParameter2(etSpeakText.getText().toString()); + + setResult(RESULT_OK); + + this.finish(); + } + +} diff --git a/app/src/main/java/com/jens/automation2/ActivityEditTriggerUrl.java b/app/src/main/java/com/jens/automation2/ActivityEditTriggerUrl.java new file mode 100644 index 0000000..fc3fc54 --- /dev/null +++ b/app/src/main/java/com/jens/automation2/ActivityEditTriggerUrl.java @@ -0,0 +1,171 @@ +package com.jens.automation2; + +import android.app.Activity; +import android.os.Bundle; +import android.view.View; +import android.view.View.OnClickListener; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemLongClickListener; +import android.widget.ArrayAdapter; +import android.widget.Button; +import android.widget.CheckBox; +import android.widget.CompoundButton; +import android.widget.CompoundButton.OnCheckedChangeListener; +import android.widget.EditText; +import android.widget.ListView; +import android.widget.TableLayout; +import android.widget.Toast; + +import com.jens.automation2.Action.Action_Enum; + +import java.util.Map; + +public class ActivityEditTriggerUrl extends Activity +{ + Button bSaveTriggerUrl; + EditText etTriggerUrl, etTriggerUrlUsername, etTriggerUrlPassword; + ListView lvTriggerUrlPostParameters; + CheckBox chkTriggerUrlUseAuthentication; + TableLayout tlTriggerUrlAuthentication; + + ArrayAdapter> lvTriggerUrlPostParametersAdapter; + +// private String existingUrl = ""; + + public static boolean edit = false; + public static Action resultingAction = null; + + @Override + protected void onCreate(Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + this.setContentView(R.layout.trigger_url_editor); + + etTriggerUrl = (EditText)findViewById(R.id.etTriggerUrl); + etTriggerUrlUsername = (EditText)findViewById(R.id.etTriggerUrlUsername); + etTriggerUrlPassword = (EditText)findViewById(R.id.etTriggerUrlPassword); + chkTriggerUrlUseAuthentication = (CheckBox)findViewById(R.id.chkTriggerUrlUseAuthentication); + lvTriggerUrlPostParameters = (ListView)findViewById(R.id.lvTriggerUrlPostParameters); + tlTriggerUrlAuthentication = (TableLayout)findViewById(R.id.tlTriggerUrlAuthentication); + bSaveTriggerUrl = (Button)findViewById(R.id.bSaveTriggerUrl); + bSaveTriggerUrl.setOnClickListener(new OnClickListener() + { + @Override + public void onClick(View v) + { + if(etTriggerUrl.getText().toString().length() > 0) + { + if(resultingAction == null) + { + resultingAction = new Action(); + resultingAction.setAction(Action_Enum.triggerUrl); + resultingAction.setParameter1(chkTriggerUrlUseAuthentication.isChecked()); + + String username = etTriggerUrlUsername.getText().toString(); + String password = etTriggerUrlPassword.getText().toString(); + + if(username == null) + username = ""; + + if(password == null) + password = ""; + + ActivityEditTriggerUrl.resultingAction.setParameter2( + username + ";" + + password + ";" + + etTriggerUrl.getText().toString().trim() + ); + } + backToRuleManager(); + } + else + Toast.makeText(getBaseContext(), getResources().getString(R.string.urlTooShort), Toast.LENGTH_LONG).show(); + } + }); + + + chkTriggerUrlUseAuthentication.setOnCheckedChangeListener(new OnCheckedChangeListener() + { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) + { + if(isChecked) + tlTriggerUrlAuthentication.setVisibility(View.VISIBLE); + else + tlTriggerUrlAuthentication.setVisibility(View.GONE); + + etTriggerUrlUsername.setEnabled(isChecked); + etTriggerUrlPassword.setEnabled(isChecked); + } + }); + + lvTriggerUrlPostParameters.setOnItemLongClickListener(new OnItemLongClickListener() + { + @Override + public boolean onItemLongClick(AdapterView arg0, View arg1, int arg2, long arg3) + { + return false; + } + }); + updateListView(); + + + ActivityEditTriggerUrl.edit = getIntent().getBooleanExtra("edit", false); + if(edit) + { + // username,password,URL + String[] components = ActivityEditTriggerUrl.resultingAction.getParameter2().split(";"); + + if(components.length >= 3) + { + etTriggerUrl.setText(components[2]); + chkTriggerUrlUseAuthentication.setChecked(ActivityEditTriggerUrl.resultingAction.getParameter1()); + etTriggerUrlUsername.setText(components[0]); + etTriggerUrlPassword.setText(components[1]); + } + else + etTriggerUrl.setText(components[0]); + } + } + + private void backToRuleManager() + { + if(edit && resultingAction != null) + { + String username = etTriggerUrlUsername.getText().toString(); + String password = etTriggerUrlPassword.getText().toString(); + + if(username == null) + username = ""; + + if(password == null) + password = ""; + + ActivityEditTriggerUrl.resultingAction.setParameter1(chkTriggerUrlUseAuthentication.isChecked()); + + ActivityEditTriggerUrl.resultingAction.setParameter2( + username + ";" + + password + ";" + + etTriggerUrl.getText().toString() + ); + } + + setResult(RESULT_OK); + + this.finish(); + } + + private void updateListView() + { + Miscellaneous.logEvent("i", "ListView", "Attempting to update lvTriggerUrlPostParameters", 4); + try + { + if(lvTriggerUrlPostParameters.getAdapter() == null) + lvTriggerUrlPostParameters.setAdapter(lvTriggerUrlPostParametersAdapter); + + lvTriggerUrlPostParametersAdapter.notifyDataSetChanged(); + } + catch(NullPointerException e) + {} + } +} diff --git a/app/src/main/java/com/jens/automation2/ActivityGeneric.java b/app/src/main/java/com/jens/automation2/ActivityGeneric.java new file mode 100644 index 0000000..ab1e3fc --- /dev/null +++ b/app/src/main/java/com/jens/automation2/ActivityGeneric.java @@ -0,0 +1,67 @@ +package com.jens.automation2; + +import android.app.Activity; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.IBinder; +import android.util.Log; + +import com.jens.automation2.AutomationService.LocalBinder; + +public class ActivityGeneric extends Activity +{ + public static Intent myServiceIntent = null; + AutomationService myAutomationService = null; + boolean boundToService = false; + + public void storeServiceReferenceInVariable() + { + if(AutomationService.isMyServiceRunning(getApplicationContext()) && myAutomationService == null) + { + bindToService(); + } + } + + public void bindToService() + { + Log.i("service", "binding to service"); + if(!boundToService) + { +// if(myServiceIntent == null) + myServiceIntent = new Intent(this, AutomationService.class); + + Miscellaneous.logEvent("i", "Service", getResources().getString(R.string.logAttemptingToBindToService) + String.valueOf(bindService(myServiceIntent, myServiceConnection, Context.BIND_AUTO_CREATE)), 5); + } + } + public void unBindFromService() + { + Miscellaneous.logEvent("i", "Service", getResources().getString(R.string.logAttemptingToUnbindFromService), 5); + if(boundToService) + { + unbindService(myServiceConnection); + boundToService = false; + Miscellaneous.logEvent("i", "Service", getResources().getString(R.string.logUnboundFromService), 5); + } + } + + private ServiceConnection myServiceConnection = new ServiceConnection() + { + @Override + public void onServiceConnected(ComponentName name, IBinder service) + { + Miscellaneous.logEvent("i", "Service", getResources().getString(R.string.logBoundToService), 5); + LocalBinder binder = (LocalBinder)service; + myAutomationService = binder.getService(); + boundToService = true; + } + + @Override + public void onServiceDisconnected(ComponentName name) + { + Miscellaneous.logEvent("i", "Service", getResources().getString(R.string.logUnboundFromService), 5); + boundToService = false; + } + }; +} diff --git a/app/src/main/java/com/jens/automation2/ActivityHelp.java b/app/src/main/java/com/jens/automation2/ActivityHelp.java new file mode 100644 index 0000000..9a4f849 --- /dev/null +++ b/app/src/main/java/com/jens/automation2/ActivityHelp.java @@ -0,0 +1,23 @@ +package com.jens.automation2; + +import android.app.Activity; +import android.os.Bundle; +import android.text.method.LinkMovementMethod; +import android.widget.TextView; + +import com.jens.automation2.R.layout; + +public class ActivityHelp extends Activity +{ + + @Override + protected void onCreate(Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + setContentView(layout.help_text); + + TextView tvHelpTextEnergySaving = (TextView) findViewById(R.id.tvHelpTextEnergySaving); + tvHelpTextEnergySaving.setMovementMethod(LinkMovementMethod.getInstance()); + } + +} diff --git a/app/src/main/java/com/jens/automation2/ActivityMainPoi.java b/app/src/main/java/com/jens/automation2/ActivityMainPoi.java new file mode 100644 index 0000000..d5ed26a --- /dev/null +++ b/app/src/main/java/com/jens/automation2/ActivityMainPoi.java @@ -0,0 +1,201 @@ +package com.jens.automation2; + +import android.app.AlertDialog; +import android.content.DialogInterface; +import android.content.Intent; +import android.os.Bundle; +import android.view.View; +import android.view.View.OnClickListener; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemLongClickListener; +import android.widget.ArrayAdapter; +import android.widget.Button; +import android.widget.ListView; +import android.widget.Toast; + +import com.jens.automation2.AutomationService.serviceCommands; + +public class ActivityMainPoi extends ActivityGeneric +{ + private Button bAddPoi; + ListView poiListView; + + ArrayAdapter poiListViewAdapter; + + AutomationService myAutomationService; + boolean boundToService = false; + + protected static ActivityMainPoi instance = null; + + public static PointOfInterest poiToEdit; + + protected final static int requestCodeForPermission = 1002; + + public static ActivityMainPoi getInstance() + { + if(instance == null) + instance = new ActivityMainPoi(); + + return instance; + } + + @Override + protected void onCreate(Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + setContentView(R.layout.main_poi_layout); + + instance = this; + + bAddPoi = (Button)findViewById(R.id.bAddPoi); + bAddPoi.setOnClickListener(new OnClickListener() + { + @Override + public void onClick(View v) + { + if(!ActivityPermissions.havePermission(ActivityPermissions.writeExternalStoragePermissionName, ActivityMainPoi.this)) + { + Toast.makeText(ActivityMainPoi.this, getResources().getString(R.string.appRequiresPermissiontoAccessExternalStorage), Toast.LENGTH_LONG).show(); + return; + } + + if(!ActivityPermissions.havePermission(ActivityPermissions.permissionNameLocationCoarse, ActivityMainPoi.this) || !ActivityPermissions.havePermission(ActivityPermissions.permissionNameLocationFine, ActivityMainPoi.this)) + { + Intent permissionIntent = new Intent(ActivityMainPoi.this, ActivityPermissions.class); + + permissionIntent.putExtra(ActivityPermissions.intentExtraName, new String[] { ActivityPermissions.permissionNameLocationCoarse, ActivityPermissions.permissionNameLocationFine }); + + startActivityForResult(permissionIntent, requestCodeForPermission); + } + else + { + buttonAddPoi(); + } + } + }); + + poiListView = (ListView)findViewById(R.id.lvPoiList); + + poiListViewAdapter = new ArrayAdapter(this, R.layout.text_view_for_poi_listview_mediumtextsize, PointOfInterest.getPointOfInterestCollection()); + poiListView.setClickable(true); + /*poiListView.setOnItemClickListener(new AdapterView.OnItemClickListener() + { + @Override + public void onItemClick(AdapterView arg0, View arg1, int arg2, long arg3) + { + poiToEdit = (PointOfInterest)poiListViewAdapter.getItem(arg2); + Intent manageSpecificPoiIntent = new Intent (ActivityMainPoi.this, ActivityManageSpecificPoi.class); + manageSpecificPoiIntent.putExtra("action", "change"); + startActivityForResult(manageSpecificPoiIntent, 2000); + } + });*/ + poiListView.setOnItemLongClickListener(new OnItemLongClickListener() + { + @Override + public boolean onItemLongClick(AdapterView arg0, View arg1, int arg2, long arg3) + { + getPoiOptionsDialog((PointOfInterest)poiListView.getItemAtPosition(arg2)).show(); + return false; + } + }); + + updateListView(); + + this.storeServiceReferenceInVariable(); + } + + private void buttonAddPoi() + { + poiToEdit = null; + Intent manageSpecificPoiIntent = new Intent(ActivityMainPoi.this, ActivityManageSpecificPoi.class); + manageSpecificPoiIntent.putExtra("action", "create"); + startActivityForResult(manageSpecificPoiIntent, 1000); + } + + public void updateListView() + { + Miscellaneous.logEvent("i", "ListView", "Attempting to update PoiListView", 5); + try + { + if(poiListView.getAdapter() == null) + poiListView.setAdapter(poiListViewAdapter); + + poiListViewAdapter.notifyDataSetChanged(); + } + catch(NullPointerException e) + {} + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) + { + super.onActivityResult(requestCode, resultCode, data); + + if(AutomationService.isMyServiceRunning(this)) + bindToService(); + + switch(requestCode) + { + case 1000: //add Poi +// poiToEdit = null; //clear cache + updateListView(); + break; + case 2000://edit Poi + poiToEdit = null; //clear cache + updateListView(); + break; + case requestCodeForPermission: + if(resultCode == RESULT_OK) + buttonAddPoi(); + break; + } + + if(boundToService && AutomationService.isMyServiceRunning(this)) + { + myAutomationService.serviceInterface(serviceCommands.updateNotification); //in case names got changed. + unBindFromService(); + } + } + + private AlertDialog getPoiOptionsDialog(final PointOfInterest pointOfInterest) + { + AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(this); + alertDialogBuilder.setTitle(getResources().getString(R.string.whatToDoWithPoi)); + alertDialogBuilder.setItems(new String[]{ getResources().getString(R.string.edit), getResources().getString(R.string.deleteCapital) }, new DialogInterface.OnClickListener() + { + @Override + public void onClick(DialogInterface dialog, int which) + { + switch(which) + { + /*case 0: + if(AutomationService.isMyServiceRunning(ActivityMainPoi.this)) + { + AutomationService runContext = AutomationService.getInstance(); + if(runContext != null) + { + pointOfInterest.activate(runContext); + break; + } + } + Toast.makeText(ActivityMainPoi.this, getResources().getString(R.string.serviceHasToRunForThat), Toast.LENGTH_LONG).show(); + break;*/ + case 0: + poiToEdit = pointOfInterest; + Intent manageSpecificPoiIntent = new Intent (ActivityMainPoi.this, ActivityManageSpecificPoi.class); + manageSpecificPoiIntent.putExtra("action", "change"); + startActivityForResult(manageSpecificPoiIntent, 2000); + break; + case 1: + if(pointOfInterest.delete(Miscellaneous.getAnyContext())) + updateListView(); + break; + } + } + }); + AlertDialog alertDialog = alertDialogBuilder.create(); + + return alertDialog; + } + +} diff --git a/app/src/main/java/com/jens/automation2/ActivityMainProfiles.java b/app/src/main/java/com/jens/automation2/ActivityMainProfiles.java new file mode 100644 index 0000000..44f4578 --- /dev/null +++ b/app/src/main/java/com/jens/automation2/ActivityMainProfiles.java @@ -0,0 +1,198 @@ +package com.jens.automation2; + +import android.app.AlertDialog; +import android.content.DialogInterface; +import android.content.Intent; +import android.os.Bundle; +import android.view.View; +import android.view.View.OnClickListener; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemLongClickListener; +import android.widget.ArrayAdapter; +import android.widget.Button; +import android.widget.ListView; +import android.widget.Toast; + +import com.jens.automation2.AutomationService.serviceCommands; + +public class ActivityMainProfiles extends ActivityGeneric +{ + private Button bAddProfile; + ListView profileListView; + + ArrayAdapter profileListViewAdapter; + + AutomationService myAutomationService; + + public static Profile profileToEdit; + + protected static ActivityMainProfiles instance = null; + + public static ActivityMainProfiles getInstance() + { + if(instance == null) + instance = new ActivityMainProfiles(); + + return instance; + } + + @Override + protected void onCreate(Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + setContentView(R.layout.main_profile_layout); + + instance = this; + + bAddProfile = (Button)findViewById(R.id.bAddProfile); + bAddProfile.setOnClickListener(new OnClickListener() + { + @Override + public void onClick(View v) + { + if(!ActivityPermissions.havePermission(ActivityPermissions.writeExternalStoragePermissionName, ActivityMainProfiles.this)) + { + Toast.makeText(ActivityMainProfiles.this, getResources().getString(R.string.appRequiresPermissiontoAccessExternalStorage), Toast.LENGTH_LONG).show(); + return; + } + + profileToEdit = null; + Intent manageSpecificProfileIntent = new Intent (ActivityMainProfiles.this, ActivityManageSpecificProfile.class); + manageSpecificProfileIntent.putExtra("action", "create"); + startActivityForResult(manageSpecificProfileIntent, 1000); + } + }); + + profileListView = (ListView)findViewById(R.id.lvProfilesList); + + profileListViewAdapter = new ArrayAdapter(this, R.layout.text_view_for_poi_listview_mediumtextsize, Profile.getProfileCollection()); + profileListView.setClickable(true); + /*profileListView.setOnItemClickListener(new AdapterView.OnItemClickListener() + { + @Override + public void onItemClick(AdapterView arg0, View arg1, int arg2, long arg3) + { + profileToEdit = (Profile)profileListViewAdapter.getItem(arg2); + Intent manageSpecificProfileIntent = new Intent (ActivityMainProfiles.this, ActivityManageSpecificProfile.class); + manageSpecificProfileIntent.putExtra("action", "change"); + startActivityForResult(manageSpecificProfileIntent, 2000); + } + });*/ + profileListView.setOnItemLongClickListener(new OnItemLongClickListener() + { + @Override + public boolean onItemLongClick(AdapterView arg0, View arg1, int arg2, long arg3) + { + getProfileDialog((Profile)profileListView.getItemAtPosition(arg2)).show(); + return false; + } + }); + + if(Settings.executeRulesAndProfilesWithSingleClick) + { + profileListView.setOnItemClickListener(new AdapterView.OnItemClickListener() + { + @Override + public void onItemClick(AdapterView parent, View view, int position, long id) + { + if(AutomationService.isMyServiceRunning(ActivityMainProfiles.this)) + { + AutomationService runContext = AutomationService.getInstance(); + if(runContext != null) + { + Profile profile = (Profile)profileListView.getItemAtPosition(position); + profile.activate(runContext); + } + } + } + }); + } + + updateListView(); + + this.storeServiceReferenceInVariable(); + } + + public void updateListView() + { + Miscellaneous.logEvent("i", "ListView", "Attempting to update ProfileListView", 5); + try + { + if(profileListView.getAdapter() == null) + profileListView.setAdapter(profileListViewAdapter); + + profileListViewAdapter.notifyDataSetChanged(); + } + catch(NullPointerException e) + {} + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) + { + super.onActivityResult(requestCode, resultCode, data); + + if(AutomationService.isMyServiceRunning(this)) + bindToService(); + + if(requestCode == 1000) //add Profile + { +// profileToEdit = null; //clear cache + updateListView(); + } + + if(requestCode == 2000) //edit Profile + { + profileToEdit = null; //clear cache + updateListView(); + } + + if(boundToService && AutomationService.isMyServiceRunning(this)) + { + myAutomationService.serviceInterface(serviceCommands.updateNotification); // in case names got changed. + unBindFromService(); + } + } + + private AlertDialog getProfileDialog(final Profile profile) + { + AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(this); + alertDialogBuilder.setTitle(getResources().getString(R.string.whatToDoWithProfile)); + alertDialogBuilder.setItems(new String[]{ getResources().getString(R.string.runManually), getResources().getString(R.string.edit), getResources().getString(R.string.deleteCapital) }, new DialogInterface.OnClickListener() + { + @Override + public void onClick(DialogInterface dialog, int which) + { + switch(which) + { + case 0: + if(AutomationService.isMyServiceRunning(ActivityMainProfiles.this)) + { + AutomationService runContext = AutomationService.getInstance(); + if(runContext != null) + { + profile.activate(runContext); + break; + } + } + Toast.makeText(ActivityMainProfiles.this, getResources().getString(R.string.serviceHasToRunForThat), Toast.LENGTH_LONG).show(); + break; + case 1: + profileToEdit = profile; + Intent manageSpecificProfileIntent = new Intent (ActivityMainProfiles.this, ActivityManageSpecificProfile.class); + manageSpecificProfileIntent.putExtra("action", "change"); + startActivityForResult(manageSpecificProfileIntent, 2000); + break; + case 2: + if(profile.delete(myAutomationService)) + updateListView(); + break; + } + } + }); + AlertDialog alertDialog = alertDialogBuilder.create(); + + return alertDialog; + } + +} diff --git a/app/src/main/java/com/jens/automation2/ActivityMainRules.java b/app/src/main/java/com/jens/automation2/ActivityMainRules.java new file mode 100644 index 0000000..1d61701 --- /dev/null +++ b/app/src/main/java/com/jens/automation2/ActivityMainRules.java @@ -0,0 +1,252 @@ +package com.jens.automation2; + +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemLongClickListener; +import android.widget.ArrayAdapter; +import android.widget.Button; +import android.widget.ImageView; +import android.widget.ListView; +import android.widget.TextView; +import android.widget.Toast; + +import com.jens.automation2.AutomationService.serviceCommands; +import com.jens.automation2.receivers.AlarmListener; + +import java.util.ArrayList; + +public class ActivityMainRules extends ActivityGeneric +{ + private ListView ruleListView; + private ArrayAdapter ruleListViewAdapter; + public static Rule ruleToEdit; + protected static ActivityMainRules instance = null; + + public static ActivityMainRules getInstance() + { + if(instance == null) + instance = new ActivityMainRules(); + + return instance; + } + + @Override + protected void onCreate(Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + setContentView(R.layout.main_rule_layout); + + instance = this; + + Button bAddRule = (Button)findViewById(R.id.bAddRule); + bAddRule.setOnClickListener(new OnClickListener() + { + @Override + public void onClick(View v) + { + if(!ActivityPermissions.havePermission(ActivityPermissions.writeExternalStoragePermissionName, ActivityMainRules.this)) + { + Toast.makeText(ActivityMainRules.this, getResources().getString(R.string.appRequiresPermissiontoAccessExternalStorage), Toast.LENGTH_LONG).show(); + return; + } + + ruleToEdit = null; + Intent startAddRuleIntent = new Intent(ActivityMainRules.this, ActivityManageSpecificRule.class); + startActivityForResult(startAddRuleIntent, 3000); + } + }); + + ruleListView = (ListView)findViewById(R.id.lvRuleList); + + ruleListViewAdapter = new RuleArrayAdapter(this, R.layout.view_for_rule_listview, Rule.getRuleCollection()); + ruleListView.setClickable(true); + + ruleListView.setOnItemLongClickListener(new OnItemLongClickListener() + { + @Override + public boolean onItemLongClick(AdapterView arg0, View arg1, int arg2, long arg3) + { + getRuleDialog((Rule)ruleListView.getItemAtPosition(arg2)).show(); + return false; + } + }); + + if(Settings.executeRulesAndProfilesWithSingleClick) + { + ruleListView.setOnItemClickListener(new AdapterView.OnItemClickListener() + { + @Override + public void onItemClick(AdapterView parent, View view, int position, long id) + { + if(AutomationService.isMyServiceRunning(ActivityMainRules.this)) + { + AutomationService runContext = AutomationService.getInstance(); + if(runContext != null) + { + Rule rule = (Rule)ruleListView.getItemAtPosition(position); + rule.activate(runContext, true); + } + } + } + }); + } + + updateListView(); + + this.storeServiceReferenceInVariable(); + } + + private static class RuleHolder + { + public ImageView ivActiveInactive; + public TextView tvRuleName; + } + + private static class RuleArrayAdapter extends ArrayAdapter + { + + public RuleArrayAdapter(Context context, int resource, ArrayList objects) + { + super(context, resource, objects); + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) + { + View v = convertView; + RuleHolder holder = new RuleHolder(); + // First let's verify the convertView is not null + if (convertView == null) + { + // This a new view we inflate the new layout + LayoutInflater inflater = (LayoutInflater) this.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE); + v = inflater.inflate(R.layout.view_for_rule_listview, null); + // Now we can fill the layout with the right values + TextView tv = (TextView) v.findViewById(R.id.tvRuleName); + ImageView img = (ImageView) v.findViewById(R.id.ivActiveInactive); + holder.tvRuleName = tv; + holder.ivActiveInactive = img; + v.setTag(holder); + } + else + holder = (RuleHolder) v.getTag(); + + System.out.println("Position ["+position+"]"); + Rule r = Rule.getRuleCollection().get(position); + holder.tvRuleName.setText(r.getName()); + if(r.isRuleActive()) + { + if (r.haveEnoughPermissions()) + holder.ivActiveInactive.setImageResource(R.drawable.status_active); + else + holder.ivActiveInactive.setImageResource(R.drawable.status_unable); + } + else + holder.ivActiveInactive.setImageResource(R.drawable.status_inactive); + + return v; + } + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) + { + super.onActivityResult(requestCode, resultCode, data); + if(AutomationService.isMyServiceRunning(this)) + bindToService(); + + if(requestCode == 3000) //add Rule + { + ruleToEdit = null; //clear cache + updateListView(); + } + + if(requestCode == 4000) //editRule + { + ruleToEdit = null; //clear cache + updateListView(); + } + + AutomationService service = AutomationService.getInstance(); + if(service != null) + service.applySettingsAndRules(); + + if(boundToService && AutomationService.isMyServiceRunning(this)) + { + myAutomationService.serviceInterface(serviceCommands.updateNotification); //in case names got changed. + unBindFromService(); + } + } + + private AlertDialog getRuleDialog(final Rule ruleThisIsAbout) + { + AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(this); + alertDialogBuilder.setTitle(getResources().getString(R.string.whatToDoWithRule)); + alertDialogBuilder.setItems(new String[]{ getResources().getString(R.string.runManually), getResources().getString(R.string.edit), getResources().getString(R.string.deleteCapital) }, new DialogInterface.OnClickListener() + { + @Override + public void onClick(DialogInterface dialog, int which) + { + switch(which) + { + case 0: + if(AutomationService.isMyServiceRunning(ActivityMainRules.this)) + { + AutomationService runContext = AutomationService.getInstance(); + if(runContext != null) + { + ruleThisIsAbout.activate(runContext, true); + break; + } + } + Toast.makeText(ActivityMainRules.this, getResources().getString(R.string.serviceHasToRunForThat), Toast.LENGTH_LONG).show(); + break; + case 1: + ruleToEdit = ruleThisIsAbout; + Intent manageSpecificRuleIntent = new Intent (ActivityMainRules.this, ActivityManageSpecificRule.class); + startActivityForResult(manageSpecificRuleIntent, 4000); + break; + case 2: + if(ruleThisIsAbout.delete()) + updateListView(); + break; + } + } + }); + AlertDialog alertDialog = alertDialogBuilder.create(); + + return alertDialog; + } + + public void updateListView() + { + Miscellaneous.logEvent("i", "ListView", "Attempting to update RuleListView", 4); + try + { + if(ruleListView.getAdapter() == null) + ruleListView.setAdapter(ruleListViewAdapter); + + ruleListViewAdapter.notifyDataSetChanged(); + } + catch(NullPointerException e) + {} + + try + { + if(AutomationService.isMyServiceRunning(this)) + AlarmListener.reloadAlarms(); + } + catch(NullPointerException e) + { + // AlarmManager instance not prepared, yet. + } + } +} diff --git a/app/src/main/java/com/jens/automation2/ActivityMainScreen.java b/app/src/main/java/com/jens/automation2/ActivityMainScreen.java new file mode 100644 index 0000000..8cbe196 --- /dev/null +++ b/app/src/main/java/com/jens/automation2/ActivityMainScreen.java @@ -0,0 +1,600 @@ +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();// ATTENTION: This was auto-generated to implement the App Indexing API. +// See https://g.co/AppIndexing/AndroidStudio for more information. + client.connect(); + toggleService.setChecked(AutomationService.isMyServiceRunning(this)); + ActivityMainScreen.updateMainScreen(); + // ATTENTION: This was auto-generated to implement the App Indexing API. + // See https://g.co/AppIndexing/AndroidStudio for more information. + 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/main/java/com/jens/automation2/ActivityMainTabLayout.java b/app/src/main/java/com/jens/automation2/ActivityMainTabLayout.java new file mode 100644 index 0000000..ff0195f --- /dev/null +++ b/app/src/main/java/com/jens/automation2/ActivityMainTabLayout.java @@ -0,0 +1,70 @@ +package com.jens.automation2; + +import android.annotation.SuppressLint; +import android.app.TabActivity; +import android.content.Intent; +import android.os.Bundle; +import android.widget.TabHost; +import android.widget.TabHost.TabSpec; + +import com.jens.automation2.receivers.NfcReceiver; + + +@SuppressLint("NewApi") +public class ActivityMainTabLayout extends TabActivity +{ + + @Override + protected void onCreate(Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + setContentView(R.layout.main_tab_layout); + + TabHost tabHost = getTabHost(); + + TabSpec specOverview = tabHost.newTabSpec("overview"); + specOverview.setIndicator(getResources().getString(R.string.overview), getResources().getDrawable(R.drawable.icon_overview_tab)); + Intent overviewIntent = new Intent(this, ActivityMainScreen.class); + specOverview.setContent(overviewIntent); + + TabSpec specPoi = tabHost.newTabSpec("pois"); + specPoi.setIndicator(getResources().getString(R.string.pois), getResources().getDrawable(R.drawable.map)); + Intent mainPoiIntent = new Intent(this, ActivityMainPoi.class); + specPoi.setContent(mainPoiIntent); + + TabSpec specRules = tabHost.newTabSpec("rules"); + specRules.setIndicator(getResources().getString(R.string.rules), getResources().getDrawable(R.drawable.gear)); + Intent mainRulesIntent = new Intent(this, ActivityMainRules.class); + specRules.setContent(mainRulesIntent); + + TabSpec specProfiles = tabHost.newTabSpec("profiles"); + specProfiles.setIndicator(getResources().getString(R.string.profiles), getResources().getDrawable(R.drawable.sound)); + Intent mainProfilesIntent = new Intent(this, ActivityMainProfiles.class); + specProfiles.setContent(mainProfilesIntent); + + tabHost.addTab(specOverview); + tabHost.addTab(specPoi); + tabHost.addTab(specRules); + tabHost.addTab(specProfiles); + + tabHost.setCurrentTab(Settings.startScreen); + } + + + @Override + protected void onResume() + { + super.onResume(); +// Miscellaneous.logEvent("i", "NFC", "ActivityMainTabLayout.onResume().", 5); + NfcReceiver.checkIntentForNFC(this, getIntent()); +// NfcReceiver.checkIntentForNFC(this, new Intent(this.getApplicationContext(), this.getClass())); + } + + @Override + protected void onNewIntent(Intent intent) + { +// Miscellaneous.logEvent("i", "NFC", "ActivityMainTabLayout.onNewIntent().", 5); +// setIntent(intent); + NfcReceiver.checkIntentForNFC(this, intent); + } +} diff --git a/app/src/main/java/com/jens/automation2/ActivityManageBluetoothTrigger.java b/app/src/main/java/com/jens/automation2/ActivityManageBluetoothTrigger.java new file mode 100644 index 0000000..6f6d5dc --- /dev/null +++ b/app/src/main/java/com/jens/automation2/ActivityManageBluetoothTrigger.java @@ -0,0 +1,187 @@ +package com.jens.automation2; + +import android.app.Activity; +import android.bluetooth.BluetoothDevice; +import android.os.Bundle; +import android.util.Log; +import android.view.View; +import android.view.View.OnClickListener; +import android.widget.ArrayAdapter; +import android.widget.Button; +import android.widget.CompoundButton; +import android.widget.CompoundButton.OnCheckedChangeListener; +import android.widget.RadioButton; +import android.widget.Spinner; +import android.widget.Toast; + +import com.jens.automation2.receivers.BluetoothReceiver; + +public class ActivityManageBluetoothTrigger extends Activity +{ + protected static Trigger editedBluetoothTrigger; + RadioButton radioAnyBluetoothDevice, radioNoDevice, radioDeviceFromList, radioBluetoothConnected, radioBluetoothDisconnected, radioBluetoothInRange, radioBluetoothOutRange; + Button bSaveBluetoothTrigger; + Spinner spinnerBluetoothDevices; + + ArrayAdapter bluetoothDevicesSpinnerAdapter; + + @Override + protected void onCreate(Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_bluetooth_trigger); + + radioAnyBluetoothDevice = (RadioButton)findViewById(R.id.radioAnyBluetoothDevice); + radioNoDevice = (RadioButton)findViewById(R.id.radioNoDevice); + radioDeviceFromList = (RadioButton)findViewById(R.id.radioDeviceFromList); + radioBluetoothConnected = (RadioButton)findViewById(R.id.radioBluetoothConnected); + radioBluetoothDisconnected = (RadioButton)findViewById(R.id.radioBluetoothDisconnected); + radioBluetoothInRange = (RadioButton)findViewById(R.id.radioBluetoothInRange); + radioBluetoothOutRange = (RadioButton)findViewById(R.id.radioBluetoothOutRange); + bSaveBluetoothTrigger = (Button)findViewById(R.id.bSaveBluetoothTrigger); + spinnerBluetoothDevices = (Spinner)findViewById(R.id.spinnerBluetoothDevices); + + bluetoothDevicesSpinnerAdapter = new ArrayAdapter(this, R.layout.text_view_for_poi_listview_mediumtextsize, BluetoothReceiver.getAllPairedBluetoothDevicesStrings()); + + radioDeviceFromList.setOnCheckedChangeListener(new OnCheckedChangeListener() + { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) + { + spinnerBluetoothDevices.setEnabled(isChecked); + } + }); + + bSaveBluetoothTrigger.setOnClickListener(new OnClickListener() + { + @Override + public void onClick(View v) + { + if(saveTrigger()) + { + setResult(RESULT_OK); + finish(); + } + } + }); + + refreshBluetoothDeviceSpinner(); + spinnerBluetoothDevices.setEnabled(false); + + if(editedBluetoothTrigger.getBluetoothDeviceAddress() != null && editedBluetoothTrigger.getBluetoothDeviceAddress().length() > 0) + loadVariableIntoGui(); + } + + protected void refreshBluetoothDeviceSpinner() + { + Miscellaneous.logEvent("i", "Spinner", "Attempting to update spinnerBluetoothDevices", 4); + if(spinnerBluetoothDevices.getAdapter() == null) + { + spinnerBluetoothDevices.setAdapter(bluetoothDevicesSpinnerAdapter); + } + + bluetoothDevicesSpinnerAdapter.notifyDataSetChanged(); + } + + protected boolean saveTrigger() + { + try + { + // DEVICE + + if(radioAnyBluetoothDevice.isChecked()) + { + editedBluetoothTrigger.setBluetoothDeviceAddress(""); + } + else if(radioNoDevice.isChecked()) + { + editedBluetoothTrigger.setBluetoothDeviceAddress(""); + } + else if(radioDeviceFromList.isChecked()) + { + BluetoothDevice selectedDevice = BluetoothReceiver.getAllPairedBluetoothDevices()[spinnerBluetoothDevices.getSelectedItemPosition()]; + if(selectedDevice != null) + { + editedBluetoothTrigger.setBluetoothDeviceAddress(selectedDevice.getAddress()); + } + else + Miscellaneous.logEvent("w", "ActivityManageBluetoothTrigger", "Device not found.", 3); + } + else + { + Toast.makeText(ActivityManageBluetoothTrigger.this, getResources().getString(R.string.selectDeviceOption), Toast.LENGTH_LONG).show(); + return false; + } + + + // EVENT + + if(radioBluetoothConnected.isChecked()) + { + editedBluetoothTrigger.setTriggerParameter(true); + editedBluetoothTrigger.setBluetoothEvent(BluetoothDevice.ACTION_ACL_CONNECTED); + } + else if(radioBluetoothDisconnected.isChecked()) + { + editedBluetoothTrigger.setTriggerParameter(false); + editedBluetoothTrigger.setBluetoothEvent(BluetoothDevice.ACTION_ACL_DISCONNECTED); + } + else if(radioBluetoothInRange.isChecked()) + { + editedBluetoothTrigger.setTriggerParameter(true); + editedBluetoothTrigger.setBluetoothEvent(BluetoothDevice.ACTION_FOUND); + } + else if(radioBluetoothOutRange.isChecked()) + { + editedBluetoothTrigger.setTriggerParameter(false); + editedBluetoothTrigger.setBluetoothEvent(BluetoothDevice.ACTION_FOUND); + } + else + { + Toast.makeText(ActivityManageBluetoothTrigger.this, getResources().getString(R.string.selectConnectionOption), Toast.LENGTH_LONG).show(); + return false; + } + + return true; + } + catch(Exception e) + { + Miscellaneous.logEvent("w", "ActivityManageBluetoothTrigger", "Error during trigger create/change: " + Log.getStackTraceString(e), 2); + } + + return false; + } + + protected void loadVariableIntoGui() + { + if(editedBluetoothTrigger != null) + { + if(editedBluetoothTrigger.getBluetoothDeviceAddress().equals("")) + { + radioAnyBluetoothDevice.setChecked(true); + } + else if(editedBluetoothTrigger.getBluetoothDeviceAddress().equals("")) + { + radioNoDevice.setChecked(true); + } + else + { + radioDeviceFromList.setChecked(true); + spinnerBluetoothDevices.setSelection(BluetoothReceiver.getDevicePositionByAddress(editedBluetoothTrigger.getBluetoothDeviceAddress())); + } + + if(editedBluetoothTrigger.getBluetoothEvent().equals(BluetoothDevice.ACTION_ACL_CONNECTED)) + { + radioBluetoothConnected.setChecked(true); + } + else if(editedBluetoothTrigger.getBluetoothEvent().equals(BluetoothDevice.ACTION_ACL_DISCONNECTED)) + { + radioBluetoothDisconnected.setChecked(true); + } + else + { + radioBluetoothInRange.setChecked(true); + } + } + } +} diff --git a/app/src/main/java/com/jens/automation2/ActivityManageBrightnessSetting.java b/app/src/main/java/com/jens/automation2/ActivityManageBrightnessSetting.java new file mode 100644 index 0000000..fc79853 --- /dev/null +++ b/app/src/main/java/com/jens/automation2/ActivityManageBrightnessSetting.java @@ -0,0 +1,64 @@ +package com.jens.automation2; + +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; +import android.view.View; +import android.widget.Button; +import android.widget.CheckBox; +import android.widget.CompoundButton; +import android.widget.SeekBar; +import android.widget.TextView; + +import androidx.annotation.Nullable; + +public class ActivityManageBrightnessSetting extends Activity +{ + CheckBox chkAutoBrightness; + SeekBar sbBrightness; + Button bApplyBrightness; + TextView tvAutoBrightnessNotice; + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) + { + setContentView(R.layout.activity_manage_brightness_setting); + super.onCreate(savedInstanceState); + + chkAutoBrightness = (CheckBox)findViewById(R.id.chkAutoBrightness); + sbBrightness = (SeekBar)findViewById(R.id.sbBrightness); + bApplyBrightness = (Button)findViewById(R.id.bApplyBrightness); + tvAutoBrightnessNotice = (TextView)findViewById(R.id.tvAutoBrightnessNotice); + + Intent input = getIntent(); + + if(input.hasExtra("autoBrightness")) + chkAutoBrightness.setChecked(input.getBooleanExtra("autoBrightness", false)); + + if(input.hasExtra("brightnessValue")) + sbBrightness.setProgress(input.getIntExtra("brightnessValue", 0)); + + bApplyBrightness.setOnClickListener(new View.OnClickListener() + { + @Override + public void onClick(View view) + { + Intent answer = new Intent(); + answer.putExtra("autoBrightness", chkAutoBrightness.isChecked()); + answer.putExtra("brightnessValue", sbBrightness.getProgress()); + setResult(RESULT_OK, answer); + finish(); + } + }); + + chkAutoBrightness.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() + { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) + { + if(isChecked) + tvAutoBrightnessNotice.setText(R.string.autoBrightnessNotice); + } + }); + } +} diff --git a/app/src/main/java/com/jens/automation2/ActivityManageNfc.java b/app/src/main/java/com/jens/automation2/ActivityManageNfc.java new file mode 100644 index 0000000..59fe242 --- /dev/null +++ b/app/src/main/java/com/jens/automation2/ActivityManageNfc.java @@ -0,0 +1,306 @@ +package com.jens.automation2; + +import android.annotation.SuppressLint; +import android.app.Activity; +import android.app.PendingIntent; +import android.app.ProgressDialog; +import android.content.DialogInterface; +import android.content.DialogInterface.OnCancelListener; +import android.content.Intent; +import android.content.IntentFilter; +import android.nfc.NfcAdapter; +import android.nfc.Tag; +import android.os.Bundle; +import android.view.View; +import android.view.View.OnClickListener; +import android.widget.Button; +import android.widget.EditText; +import android.widget.TextView; +import android.widget.Toast; + +import com.jens.automation2.receivers.NfcReceiver; + +@SuppressLint("NewApi") +public class ActivityManageNfc extends Activity +{ + public static String generatedId = null; + private static Tag discoveredTag = null; + + EditText etNewNfcIdValue; + Button bReadNfcTag, bUseValueCurrentlyStored, bWriteNfcTag; + TextView tvCurrentNfcId; + + private static int currentStatus = 0; + private static ProgressDialog progressDialog = null; + + // Check if NFC is activated + + @Override + protected void onCreate(Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + setContentView(R.layout.manage_nfc); + + etNewNfcIdValue = (EditText)findViewById(R.id.etNewNfcIdValue); + bReadNfcTag = (Button)findViewById(R.id.bReadNfcTag); + bUseValueCurrentlyStored = (Button)findViewById(R.id.bUseValueCurrentlyStored); + bWriteNfcTag = (Button)findViewById(R.id.bWriteNfcTag); + tvCurrentNfcId = (TextView)findViewById(R.id.tvCurrentNfcId); + + bReadNfcTag.setOnClickListener(new OnClickListener() + { + @Override + public void onClick(View v) + { + if(discoveredTag != null) + { + generatedId = NfcReceiver.readTag(discoveredTag); + tvCurrentNfcId.setText(generatedId); + } + else + { + progressDialog = ProgressDialog.show(ActivityManageNfc.this, null, getResources().getString(R.string.nfcBringTagIntoRange), false, true, new OnCancelListener() + { + @Override + public void onCancel(DialogInterface dialog) + { + progressDialog.dismiss(); + progressDialog = null; + currentStatus = 0; + } + }); + } + } + }); + + bUseValueCurrentlyStored.setOnClickListener(new OnClickListener() + { + @Override + public void onClick(View v) + { + if(discoveredTag != null) + { + if(checkEnteredText(false)) + { + setResult(RESULT_OK); + finish(); + } + } + else + { + progressDialog = ProgressDialog.show(ActivityManageNfc.this, null, getResources().getString(R.string.nfcBringTagIntoRange), false, true, new OnCancelListener() + { + @Override + public void onCancel(DialogInterface dialog) + { + progressDialog.dismiss(); + progressDialog = null; + currentStatus = 0; + } + }); + + currentStatus = 1; + } + } + }); + + bWriteNfcTag.setOnClickListener(new OnClickListener() + { + @Override + public void onClick(View v) + { + if(checkEnteredText(true)) + { +// ActivityPermissions.requestSpecificPermission("android.permission.NFC"); + if(discoveredTag != null) + { + tryWrite(); + } + else + { + progressDialog = ProgressDialog.show(ActivityManageNfc.this, null, getResources().getString(R.string.nfcBringTagIntoRange), false, true, new OnCancelListener() + { + @Override + public void onCancel(DialogInterface dialog) + { + progressDialog.dismiss(); + progressDialog = null; + currentStatus = 0; + } + }); + + currentStatus = 2; + +// Toast.makeText(ActivityManageNfc.this, "No tag.", Toast.LENGTH_LONG).show(); +// Miscellaneous.logEvent("w", "NFC", "No tag.", 2); + } + } + } + }); + + if(generatedId != null) + etNewNfcIdValue.setText(generatedId); + } + + public static void enableForegroundDispatch(final Activity activity) + { + NfcAdapter nfcAdapter = NfcReceiver.getNfcAdapter(activity); + + final Intent intent = new Intent(activity.getApplicationContext(), activity.getClass()); + intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); + + final PendingIntent pendingIntent = PendingIntent.getActivity(activity.getApplicationContext(), 0, intent, 0); + + IntentFilter[] filters = new IntentFilter[1]; + String[][] techList = new String[][]{}; + + // Notice that this is the same filter as in our manifest. + filters[0] = new IntentFilter(); + filters[0].addAction(NfcAdapter.ACTION_NDEF_DISCOVERED); + filters[0].addAction(NfcAdapter.ACTION_TAG_DISCOVERED); + filters[0].addAction(NfcAdapter.ACTION_TECH_DISCOVERED); + filters[0].addCategory(Intent.CATEGORY_DEFAULT); +// try +// { +// filters[0].addDataType(NfcReceiver.MIME_TEXT_PLAIN); +// } +// catch (MalformedMimeTypeException e) +// { +// throw new RuntimeException("Check your mime type."); +// } + + nfcAdapter.enableForegroundDispatch(activity, pendingIntent, filters, techList); + + Miscellaneous.logEvent("i", "NFC", "Enabled foreground dispatch.", 5); + } + + public static void disableForegroundDispatch(final Activity activity) + { + NfcAdapter nfcAdapter = NfcReceiver.getNfcAdapter(activity); + nfcAdapter.disableForegroundDispatch(activity); + Miscellaneous.logEvent("i", "NFC", "Disabled foreground dispatch.", 5); + } + + @Override + protected void onPause() + { + /** + * Call this before onPause, otherwise an IllegalArgumentException is thrown as well. + */ + disableForegroundDispatch(this); + + super.onPause(); + } + + @Override + protected void onResume() + { + super.onResume(); + /** + * It's important, that the activity is in the foreground (resumed). Otherwise + * an IllegalStateException is thrown. + */ + enableForegroundDispatch(this); + +// NfcReceiver.checkIntentForNFC(this, new Intent(this.getApplicationContext(), this.getClass())); + } + + @Override + protected void onNewIntent(Intent intent) + { + Miscellaneous.logEvent("i", "NFC", "ActivityManageNfc->onNewIntent().", 5); + + Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG); + + if(tag == null) + { + tvCurrentNfcId.setText(getResources().getString(R.string.nfcNoTag)); + } + else + { + Miscellaneous.logEvent("i", "NFC", getResources().getString(R.string.nfcTagDiscovered), 4); + Toast.makeText(this, getResources().getString(R.string.nfcTagDiscovered), Toast.LENGTH_LONG).show(); + discoveredTag = tag; + if(progressDialog != null) + { + progressDialog.dismiss(); + progressDialog = null; + } + + if(currentStatus == 0) + { + generatedId = NfcReceiver.readTag(discoveredTag); + if(generatedId != null && generatedId.length() > 0) + tvCurrentNfcId.setText(generatedId); + else + tvCurrentNfcId.setText(getResources().getString(R.string.nfcTagDataNotUsable)); + } + else if(currentStatus == 1) + { + tryRead(); + } + else if(currentStatus == 2) + if(checkEnteredText(true)) + tryWrite(); + } + +// NfcReceiver.checkIntentForNFC(this, intent); + } + + private boolean checkEnteredText(boolean checkGuiValue) + { + if(checkGuiValue) + generatedId = etNewNfcIdValue.getText().toString(); + + if(generatedId.length() == 0) + { + generatedId = null; + Toast.makeText(ActivityManageNfc.this, getResources().getString(R.string.nfcEnterValidIdentifier), Toast.LENGTH_LONG).show(); + return false; + } + else + return true; + } + + private void tryWrite() + { + if(NfcReceiver.writeTag(generatedId, discoveredTag)) + { + currentStatus = 0; + Toast.makeText(ActivityManageNfc.this, getResources().getString(R.string.nfcTagWrittenSuccessfully), Toast.LENGTH_LONG).show(); + setResult(RESULT_OK); + finish(); + } + else + { + currentStatus = 0; + Toast.makeText(ActivityManageNfc.this, getResources().getString(R.string.nfcTagWriteError), Toast.LENGTH_LONG).show(); + Miscellaneous.logEvent("e", "NFC", getResources().getString(R.string.nfcTagWriteError), 2); + } + } + + private void tryRead() + { + generatedId = NfcReceiver.readTag(discoveredTag); + if(checkEnteredText(false)) + { + currentStatus = 0; + Toast.makeText(ActivityManageNfc.this, getResources().getString(R.string.nfcTagReadSuccessfully), Toast.LENGTH_LONG).show(); + setResult(RESULT_OK); + finish(); + } + else + { + currentStatus = 0; + Toast.makeText(ActivityManageNfc.this, getResources().getString(R.string.nfcValueNotSuitable), Toast.LENGTH_LONG).show(); + generatedId = null; + } + } + + @Override + protected void onDestroy() + { + super.onDestroy(); + discoveredTag = null; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/jens/automation2/ActivityManageSpecificPoi.java b/app/src/main/java/com/jens/automation2/ActivityManageSpecificPoi.java new file mode 100644 index 0000000..b1b35fe --- /dev/null +++ b/app/src/main/java/com/jens/automation2/ActivityManageSpecificPoi.java @@ -0,0 +1,476 @@ +package com.jens.automation2; + +import android.app.Activity; +import android.app.AlertDialog; +import android.app.ProgressDialog; +import android.content.ActivityNotFoundException; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.location.Criteria; +import android.location.Location; +import android.location.LocationListener; +import android.location.LocationManager; +import android.net.Uri; +import android.os.Bundle; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.inputmethod.InputMethodManager; +import android.widget.Button; +import android.widget.EditText; +import android.widget.ImageButton; +import android.widget.Toast; + +public class ActivityManageSpecificPoi extends Activity +{ + public LocationManager myLocationManager; + MyLocationListenerGps myLocationListenerGps = new MyLocationListenerGps(); + Location locationGps = null, locationNetwork = null; +// Location locationWifi = null; + MyLocationListenerNetwork myLocationListenerNetwork = new MyLocationListenerNetwork(); + Button bGetPosition, bSavePoi; + ImageButton ibShowOnMap; + EditText guiPoiName, guiPoiLatitude, guiPoiLongitude, guiPoiRadius; + + private static ProgressDialog progressDialog; + + @Override + protected void onPause() + { + super.onPause(); + Miscellaneous.logEvent("i", "ActivityManageSpecificPoi", getResources().getString(R.string.logClearingBothLocationListeners) , 5); + myLocationManager.removeUpdates(myLocationListenerGps); + myLocationManager.removeUpdates(myLocationListenerNetwork); + } + + @Override + protected void onCreate(Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + this.setContentView(R.layout.manage_specific_poi); + + myLocationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE); + bGetPosition = (Button)findViewById(R.id.bGetPosition); + ibShowOnMap = (ImageButton)findViewById(R.id.ibShowOnMap); + + guiPoiName = (EditText)findViewById(R.id.etPoiName); + guiPoiLatitude = (EditText)findViewById(R.id.etPoiLatitude); + guiPoiLongitude = (EditText)findViewById(R.id.etPoiLongitude); + guiPoiRadius = (EditText)findViewById(R.id.etPoiRadius); + + bGetPosition.setOnClickListener(new OnClickListener() + { + @Override + public void onClick(View v) + { + hideKeyboard(); + getNotificationDialog(getResources().getString(R.string.positioningWindowNotice)).show(); + } + }); + + bSavePoi = (Button)findViewById(R.id.bSavePoi); + bSavePoi.setOnClickListener(new OnClickListener() + { + @Override + public void onClick(View v) + { + hideKeyboard(); + + if(ActivityMainPoi.poiToEdit == null) + createPoi(); + else + changePoi(); + } + }); + + ibShowOnMap.setOnClickListener(new OnClickListener() + { + @Override + public void onClick(View v) + { + hideKeyboard(); + showOnMap(); + } + }); + + if(ActivityMainPoi.poiToEdit != null) + editPoi(ActivityMainPoi.poiToEdit); + //else + // new Poi to be created + } + + private void createPoi() + { + myLocationManager.removeUpdates(myLocationListenerGps); + ActivityMainPoi.poiToEdit = new PointOfInterest(); + ActivityMainPoi.poiToEdit.setLocation(new Location("POINT_LOCATION")); + if(loadFormValuesToVariable()) + if(ActivityMainPoi.poiToEdit.create(this)) + { + this.setResult(RESULT_OK); + finish(); + } + } + private void changePoi() + { + myLocationManager.removeUpdates(myLocationListenerGps); + if(loadFormValuesToVariable()) + if(ActivityMainPoi.poiToEdit.change(this)) + { + this.setResult(RESULT_OK); + finish(); + } + } + + private void getLocation() + { + Criteria critNetwork = new Criteria(); + critNetwork.setPowerRequirement(Criteria.POWER_LOW); + critNetwork.setAltitudeRequired(false); + critNetwork.setSpeedRequired(false); + critNetwork.setBearingRequired(false); + critNetwork.setCostAllowed(false); + critNetwork.setAccuracy(Criteria.ACCURACY_COARSE); + + Criteria critGps = new Criteria(); + critGps.setAltitudeRequired(false); + critGps.setSpeedRequired(false); + critGps.setBearingRequired(false); + critGps.setCostAllowed(true); + critGps.setAccuracy(Criteria.ACCURACY_FINE); + + String provider1 = myLocationManager.getBestProvider(critNetwork, true); + String provider2 = myLocationManager.getBestProvider(critGps, true); +// String provider3 = myLocationManager.getProvider("wifi"); + + if(provider1 == null | provider2 == null) + { + Toast.makeText(this, getResources().getString(R.string.logNoSuitableProvider), Toast.LENGTH_LONG).show(); + return; + } + else + { + Miscellaneous.logEvent("i", "POI Manager", getResources().getString(R.string.logGettingPositionWithProvider) + " " + provider1, 3); + myLocationManager.requestLocationUpdates(provider1, 500, Settings.satisfactoryAccuracyNetwork, myLocationListenerNetwork); + + Miscellaneous.logEvent("i", "POI Manager", getResources().getString(R.string.logGettingPositionWithProvider) + " " + provider2, 3); + myLocationManager.requestLocationUpdates(provider2, 500, Settings.satisfactoryAccuracyGps, myLocationListenerGps); + } + + } + + private void compareLocations() + { + if(locationGps != null) + { + guiPoiLatitude.setText(String.valueOf(locationGps.getLatitude())); + guiPoiLongitude.setText(String.valueOf(locationGps.getLongitude())); + + if(locationNetwork != null) + { + Miscellaneous.logEvent("i", "POI Manager", getResources().getString(R.string.comparing), 4); + + double variance = locationGps.distanceTo(locationNetwork); + + String text = getResources().getString(R.string.distanceBetween) + " " + String.valueOf(Math.round(variance)) + " " + getResources().getString(R.string.radiusSuggestion); +// Toast.makeText(getBaseContext(), text, Toast.LENGTH_LONG).show(); + Miscellaneous.logEvent("i", "POI Manager", text, 4); +// if(variance > 50 && guiPoiRadius.getText().toString().length()>0 && Integer.parseInt(guiPoiRadius.getText().toString()) soundModeAdapter; + + public void setIncomingCallsRingtone(File incomingCallsRingtone) + { + this.incomingCallsRingtone = incomingCallsRingtone; + + if(incomingCallsRingtone != null) + tvIncomingCallsRingtone.setText(this.incomingCallsRingtone.getAbsolutePath()); + else + tvIncomingCallsRingtone.setText(getResources().getString(R.string.none)); + } + + public File getIncomingCallsRingtone() + { + return incomingCallsRingtone; + } + + public void setNotificationsRingtone(File notificationsRingtone) + { + this.notificationsRingtone = notificationsRingtone; + + if(this.notificationsRingtone != null) + tvNotificationsRingtone.setText(this.notificationsRingtone.getAbsolutePath()); + else + tvNotificationsRingtone.setText(getResources().getString(R.string.none)); + } + + public File getNotificationsRingtone() + { + return notificationsRingtone; + } + + @Override + protected void onCreate(Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + this.setContentView(R.layout.manage_specific_profile); + + checkBoxChangeSoundMode = (CheckBox)findViewById(R.id.checkBoxChangeSoundMode); + checkBoxChangeVolumeMusicVideoGameMedia = (CheckBox)findViewById(R.id.checkBoxChangeVolumeMusicVideoGameMedia); + checkBoxChangeVolumeNotifications = (CheckBox)findViewById(R.id.checkBoxChangeVolumeNotifications); + checkBoxChangeVolumeAlarms = (CheckBox)findViewById(R.id.checkBoxChangeVolumeAlarms); + checkBoxChangeIncomingCallsRingtone = (CheckBox)findViewById(R.id.checkBoxChangeIncomingCallsRingtone); + checkBoxChangeNotificationRingtone = (CheckBox)findViewById(R.id.checkBoxChangeNotificationRingtone); + checkBoxChangeAudibleSelection = (CheckBox)findViewById(R.id.checkBoxChangeAudibleSelection); + checkBoxChangeScreenLockUnlockSound = (CheckBox)findViewById(R.id.checkBoxChangeScreenLockUnlockSound); + checkBoxChangeHapticFeedback = (CheckBox)findViewById(R.id.checkBoxChangeHapticFeedback); + checkBoxChangeVibrateWhenRinging = (CheckBox)findViewById(R.id.checkBoxChangeVibrateWhenRinging); + checkBoxAudibleSelection = (CheckBox)findViewById(R.id.checkBoxAudibleSelection); + checkBoxScreenLockUnlockSound = (CheckBox)findViewById(R.id.checkBoxScreenLockUnlockSound); + checkBoxHapticFeedback = (CheckBox)findViewById(R.id.checkBoxHapticFeedback); + checkBoxVibrateWhenRinging = (CheckBox)findViewById(R.id.checkBoxVibrateWhenRinging); + spinnerSoundMode = (Spinner)findViewById(R.id.spinnerSoundMode); + seekBarVolumeMusic = (SeekBar)findViewById(R.id.seekBarVolumeMusic); + seekBarVolumeNotifications = (SeekBar)findViewById(R.id.seekBarVolumeNotifications); + seekBarVolumeAlarms = (SeekBar)findViewById(R.id.seekBarVolumeAlarms); + bChangeSoundIncomingCalls = (Button)findViewById(R.id.bChangeSoundIncomingCalls); + bChangeSoundNotifications = (Button)findViewById(R.id.bChangeSoundNotifications); + tvIncomingCallsRingtone = (TextView)findViewById(R.id.tvIncomingCallsRingtone); + tvNotificationsRingtone = (TextView)findViewById(R.id.tvNotificationsRingtone); + bSaveProfile = (Button)findViewById(R.id.bSaveProfile); + etName = (EditText)findViewById(R.id.etName); + + checkBoxVibrateWhenRinging.setEnabled(false); + checkBoxAudibleSelection.setEnabled(false); + checkBoxScreenLockUnlockSound.setEnabled(false); + checkBoxHapticFeedback.setEnabled(false); + spinnerSoundMode.setEnabled(false); + seekBarVolumeMusic.setEnabled(false); + seekBarVolumeNotifications.setEnabled(false); + seekBarVolumeAlarms.setEnabled(false); + bChangeSoundIncomingCalls.setEnabled(false); + bChangeSoundNotifications.setEnabled(false); + + spinnerSoundMode.setSelection(0); + + // Scale SeekBars to the system's maximum volume values + AudioManager am = (AudioManager) Miscellaneous.getAnyContext().getSystemService(Context.AUDIO_SERVICE); + seekBarVolumeMusic.setMax(am.getStreamMaxVolume(AudioManager.STREAM_MUSIC)); + seekBarVolumeNotifications.setMax(am.getStreamMaxVolume(AudioManager.STREAM_NOTIFICATION)); + seekBarVolumeAlarms.setMax(am.getStreamMaxVolume(AudioManager.STREAM_ALARM)); + + soundModeAdapter = new ArrayAdapter(this, R.layout.text_view_for_poi_listview_mediumtextsize, new String[] { getResources().getString(R.string.soundModeSilent), getResources().getString(R.string.soundModeVibrate), getResources().getString(R.string.soundModeNormal) }); + spinnerSoundMode.setAdapter(soundModeAdapter); + + checkBoxChangeSoundMode.setOnCheckedChangeListener(new OnCheckedChangeListener() + { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) + { + spinnerSoundMode.setEnabled(isChecked); + } + }); + checkBoxChangeVolumeMusicVideoGameMedia.setOnCheckedChangeListener(new OnCheckedChangeListener() + { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) + { + seekBarVolumeMusic.setEnabled(isChecked); + } + }); + checkBoxChangeVolumeNotifications.setOnCheckedChangeListener(new OnCheckedChangeListener() + { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) + { + seekBarVolumeNotifications.setEnabled(isChecked); + } + }); + checkBoxChangeVolumeAlarms.setOnCheckedChangeListener(new OnCheckedChangeListener() + { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) + { + seekBarVolumeAlarms.setEnabled(isChecked); + } + }); + checkBoxChangeIncomingCallsRingtone.setOnCheckedChangeListener(new OnCheckedChangeListener() + { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) + { + bChangeSoundIncomingCalls.setEnabled(isChecked); + } + }); + checkBoxChangeNotificationRingtone.setOnCheckedChangeListener(new OnCheckedChangeListener() + { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) + { + bChangeSoundNotifications.setEnabled(isChecked); + } + }); + checkBoxChangeAudibleSelection.setOnCheckedChangeListener(new OnCheckedChangeListener() + { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) + { + checkBoxAudibleSelection.setEnabled(isChecked); + } + }); + checkBoxChangeScreenLockUnlockSound.setOnCheckedChangeListener(new OnCheckedChangeListener() + { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) + { + checkBoxScreenLockUnlockSound.setEnabled(isChecked); + + if(isChecked && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) + Miscellaneous.messageBox("Info", getResources().getString(R.string.screenLockSoundNotice), ActivityManageSpecificProfile.this).show(); + } + }); + checkBoxChangeHapticFeedback.setOnCheckedChangeListener(new OnCheckedChangeListener() + { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) + { + checkBoxHapticFeedback.setEnabled(isChecked); + } + }); + checkBoxChangeVibrateWhenRinging.setOnCheckedChangeListener(new OnCheckedChangeListener() + { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) + { + checkBoxVibrateWhenRinging.setEnabled(isChecked); + } + }); + + bSaveProfile.setOnClickListener(new OnClickListener() + { + @Override + public void onClick(View v) + { + try + { + if(ActivityMainProfiles.profileToEdit == null) + createProfile(ActivityManageSpecificProfile.this); + else + changeProfile(); + } + catch(Exception ex) + { + Toast.makeText(ActivityManageSpecificProfile.this, getResources().getString(R.string.errorWritingFile) + " " + ex.getMessage(), Toast.LENGTH_LONG).show(); + } + } + }); + + bChangeSoundIncomingCalls.setOnClickListener(new OnClickListener() + { + @Override + public void onClick(View v) + { + try + { + Intent fileIntent = new Intent(Intent.ACTION_GET_CONTENT); + fileIntent.setType("audio/*"); + startActivityForResult(Intent.createChooser(fileIntent, "Select a ringtone"), intentCodeRingtonePickerCallsFile); + } + catch(ActivityNotFoundException e) + { + // Use media browser instead + Intent fileSelectionIntent = new Intent(RingtoneManager.ACTION_RINGTONE_PICKER); + + if(ActivityMainProfiles.profileToEdit != null) + { + Uri currenturi = Uri.parse(ActivityMainProfiles.profileToEdit.incomingCallsRingtone.getAbsolutePath()); + if(ActivityMainProfiles.profileToEdit.changeIncomingCallsRingtone) + fileSelectionIntent.putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, currenturi); + } + + startActivityForResult(fileSelectionIntent, intentCodeRingtonePickerCallsRingtone); + } + } + }); + bChangeSoundNotifications.setOnClickListener(new OnClickListener() + { + @Override + public void onClick(View v) + { + try + { + Intent fileIntent = new Intent(Intent.ACTION_GET_CONTENT); + fileIntent.setType("audio/*"); + startActivityForResult(Intent.createChooser(fileIntent, "Select a ringtone"), intentCodeRingtonePickerNotificationsFile); + } + catch(ActivityNotFoundException e) + { + // Use media browser instead + Intent fileSelectionIntent = new Intent(RingtoneManager.ACTION_RINGTONE_PICKER); + + if(ActivityMainProfiles.profileToEdit != null) + { + Uri currenturi = Uri.parse(ActivityMainProfiles.profileToEdit.notificationRingtone.getAbsolutePath()); + if(ActivityMainProfiles.profileToEdit.changeNotificationRingtone) + fileSelectionIntent.putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, currenturi); + } + + startActivityForResult(fileSelectionIntent, intentCodeRingtonePickerNotificationsRingtone); + } + } + }); + + if(ActivityMainProfiles.profileToEdit != null) + editProfile(ActivityMainProfiles.profileToEdit); + //else + // new Profile to be created + +// Toast.makeText(this, getResources().getString(R.string.someOptionsNotAvailableYet), Toast.LENGTH_LONG).show(); + } + + private void createProfile(Context context) + { + if(plausibilityCheck()) + { + if(loadFormValuesToVariable()) + if(ActivityMainProfiles.profileToEdit.create(context, true)) + { + this.setResult(RESULT_OK); + finish(); + } + } + } + private void changeProfile() + { + if(plausibilityCheck()) + { + loadFormValuesToVariable(); + + if(ActivityMainProfiles.profileToEdit.change(this)) + { + this.setResult(RESULT_OK); + finish(); + } + } + } + + public void editProfile(Profile profileToEdit) + { + etName.setText(ActivityMainProfiles.profileToEdit.getName()); + checkBoxChangeSoundMode.setChecked(ActivityMainProfiles.profileToEdit.getChangeSoundMode()); + checkBoxChangeVolumeMusicVideoGameMedia.setChecked(ActivityMainProfiles.profileToEdit.getChangeVolumeMusicVideoGameMedia()); + checkBoxChangeVolumeNotifications.setChecked(ActivityMainProfiles.profileToEdit.getChangeVolumeNotifications()); + checkBoxChangeVolumeAlarms.setChecked(ActivityMainProfiles.profileToEdit.getChangeVolumeAlarms()); + checkBoxChangeIncomingCallsRingtone.setChecked(ActivityMainProfiles.profileToEdit.getChangeIncomingCallsRingtone()); + checkBoxChangeNotificationRingtone.setChecked(ActivityMainProfiles.profileToEdit.getChangeNotificationRingtone()); + checkBoxChangeAudibleSelection.setChecked(ActivityMainProfiles.profileToEdit.getChangeAudibleSelection()); + checkBoxChangeScreenLockUnlockSound.setChecked(ActivityMainProfiles.profileToEdit.getChangeScreenLockUnlockSound()); + checkBoxChangeHapticFeedback.setChecked(ActivityMainProfiles.profileToEdit.getChangeHapticFeedback()); + checkBoxChangeVibrateWhenRinging.setChecked(ActivityMainProfiles.profileToEdit.getChangeVibrateWhenRinging()); + + spinnerSoundMode.setSelection(ActivityMainProfiles.profileToEdit.getSoundMode()); + seekBarVolumeMusic.setProgress(ActivityMainProfiles.profileToEdit.getVolumeMusic()); + seekBarVolumeNotifications.setProgress(ActivityMainProfiles.profileToEdit.getVolumeNotifications()); + seekBarVolumeAlarms.setProgress(ActivityMainProfiles.profileToEdit.getVolumeAlarms()); + checkBoxAudibleSelection.setChecked(ActivityMainProfiles.profileToEdit.audibleSelection); + checkBoxScreenLockUnlockSound.setChecked(ActivityMainProfiles.profileToEdit.screenLockUnlockSound); + checkBoxHapticFeedback.setChecked(ActivityMainProfiles.profileToEdit.hapticFeedback); + checkBoxVibrateWhenRinging.setChecked(ActivityMainProfiles.profileToEdit.vibrateWhenRinging); + + setIncomingCallsRingtone(ActivityMainProfiles.profileToEdit.getIncomingCallsRingtone()); + setNotificationsRingtone(ActivityMainProfiles.profileToEdit.getNotificationRingtone()); + } + + private boolean loadFormValuesToVariable() + { + if(plausibilityCheck()) + { + if(ActivityMainProfiles.profileToEdit == null) + ActivityMainProfiles.profileToEdit = new Profile(); + + ActivityMainProfiles.profileToEdit.setName(etName.getText().toString()); + ActivityMainProfiles.profileToEdit.setChangeSoundMode(checkBoxChangeSoundMode.isChecked()); + ActivityMainProfiles.profileToEdit.setChangeVolumeMusicVideoGameMedia(checkBoxChangeVolumeMusicVideoGameMedia.isChecked()); + ActivityMainProfiles.profileToEdit.setChangeVolumeNotifications(checkBoxChangeVolumeNotifications.isChecked()); + ActivityMainProfiles.profileToEdit.setChangeVolumeAlarms(checkBoxChangeVolumeAlarms.isChecked()); + ActivityMainProfiles.profileToEdit.setChangeIncomingCallsRingtone(checkBoxChangeIncomingCallsRingtone.isChecked()); + ActivityMainProfiles.profileToEdit.setChangeNotificationRingtone(checkBoxChangeNotificationRingtone.isChecked()); + ActivityMainProfiles.profileToEdit.setChangeAudibleSelection(checkBoxChangeAudibleSelection.isChecked()); + ActivityMainProfiles.profileToEdit.setChangeScreenLockUnlockSound(checkBoxChangeScreenLockUnlockSound.isChecked()); + ActivityMainProfiles.profileToEdit.setChangeHapticFeedback(checkBoxChangeHapticFeedback.isChecked()); + ActivityMainProfiles.profileToEdit.setChangeVibrateWhenRinging(checkBoxChangeVibrateWhenRinging.isChecked()); + + ActivityMainProfiles.profileToEdit.setAudibleSelection(checkBoxAudibleSelection.isChecked()); + ActivityMainProfiles.profileToEdit.setScreenLockUnlockSound(checkBoxScreenLockUnlockSound.isChecked()); + ActivityMainProfiles.profileToEdit.setHapticFeedback(checkBoxHapticFeedback.isChecked()); + ActivityMainProfiles.profileToEdit.setVibrateWhenRinging(checkBoxVibrateWhenRinging.isChecked()); + ActivityMainProfiles.profileToEdit.setSoundMode(spinnerSoundMode.getSelectedItemPosition()); + ActivityMainProfiles.profileToEdit.setVolumeMusic(seekBarVolumeMusic.getProgress()); + ActivityMainProfiles.profileToEdit.setVolumeNotifications(seekBarVolumeNotifications.getProgress()); + ActivityMainProfiles.profileToEdit.setVolumeAlarms(seekBarVolumeAlarms.getProgress()); + ActivityMainProfiles.profileToEdit.setIncomingCallsRingtone(incomingCallsRingtone); + ActivityMainProfiles.profileToEdit.setNotificationRingtone(notificationsRingtone); + + return true; + } + + return false; + } + + private boolean plausibilityCheck() + { + if(etName.getText().toString().length() > 0) + { +// Check for duplicates +// for(Profile.) +// if(etName.getText().toString() + } + else + { + Toast.makeText(this, getResources().getString(R.string.enterAname), Toast.LENGTH_LONG).show(); + return false; + } + + if(!checkBoxChangeSoundMode.isChecked() + & + !checkBoxChangeVolumeMusicVideoGameMedia.isChecked() + & + !checkBoxChangeVolumeNotifications.isChecked() + & + !checkBoxChangeVolumeAlarms.isChecked() + & + !checkBoxChangeIncomingCallsRingtone.isChecked() + & + !checkBoxChangeNotificationRingtone.isChecked() + & + !checkBoxChangeAudibleSelection.isChecked() + & + !checkBoxChangeScreenLockUnlockSound.isChecked() + & + !checkBoxChangeHapticFeedback.isChecked() + ) + { + Toast.makeText(this, getResources().getString(R.string.noChangeSelectedProfileDoesntMakeSense), Toast.LENGTH_LONG).show(); + return false; + } + + return true; + } + + @RequiresApi(api = Build.VERSION_CODES.KITKAT) + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) + { + if(resultCode == RESULT_OK) + { + switch (requestCode) + { + case intentCodeRingtonePickerCallsRingtone: // incoming calls + { + // Method for ringtone selection + Uri uri = data.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI); + if (uri != null) + { + String ringTonePath = CompensateCrappyAndroidPaths.getPath(ActivityManageSpecificProfile.this, uri); + setIncomingCallsRingtone(new File(ringTonePath)); + } + break; + } + case intentCodeRingtonePickerCallsFile: + { + String ringTonePath = CompensateCrappyAndroidPaths.getPath(ActivityManageSpecificProfile.this, data.getData()); + setIncomingCallsRingtone(new File(ringTonePath)); + break; + } + case intentCodeRingtonePickerNotificationsRingtone: // notifications + { + Uri uri = data.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI); + if (uri != null) + { + String ringTonePath = CompensateCrappyAndroidPaths.getPath(ActivityManageSpecificProfile.this, data.getData()); + setNotificationsRingtone(new File(ringTonePath)); + } + break; + } + case intentCodeRingtonePickerNotificationsFile: + { + String ringTonePath = CompensateCrappyAndroidPaths.getPath(ActivityManageSpecificProfile.this, data.getData()); + setNotificationsRingtone(new File(ringTonePath)); + break; + } + default: + ; + } + } + } + + public String getRealPathFromURI(Uri uri) + { + String[] projection = { MediaStore.Images.Media.DATA }; + Cursor cursor = getContentResolver().query(uri, projection, null, null, null); + if (cursor == null) return null; + int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA); + cursor.moveToFirst(); + String s=cursor.getString(column_index); + cursor.close(); + return s; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/jens/automation2/ActivityManageSpecificRule.java b/app/src/main/java/com/jens/automation2/ActivityManageSpecificRule.java new file mode 100644 index 0000000..d9c4c42 --- /dev/null +++ b/app/src/main/java/com/jens/automation2/ActivityManageSpecificRule.java @@ -0,0 +1,1674 @@ +package com.jens.automation2; + +import android.app.Activity; +import android.app.AlertDialog; +import android.app.Dialog; +import android.app.ProgressDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.os.AsyncTask; +import android.os.Build; +import android.os.Bundle; +import android.text.InputType; +import android.text.util.Linkify; +import android.view.MotionEvent; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.View.OnTouchListener; +import android.view.ViewGroup; +import android.view.inputmethod.InputMethodManager; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemClickListener; +import android.widget.AdapterView.OnItemLongClickListener; +import android.widget.ArrayAdapter; +import android.widget.Button; +import android.widget.CheckBox; +import android.widget.CompoundButton; +import android.widget.CompoundButton.OnCheckedChangeListener; +import android.widget.EditText; +import android.widget.ImageView; +import android.widget.ListAdapter; +import android.widget.ListView; +import android.widget.TextView; +import android.widget.Toast; + +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.util.ArrayList; +import java.util.Collections; + +public class ActivityManageSpecificRule extends Activity +{ + public Context context; + private Button cmdTriggerAdd, cmdActionAdd, cmdSaveRule; + private ListView triggerListView, actionListView; + private EditText etRuleName; + private CheckBox chkRuleActive, chkRuleToggle; + private static ActivityManageSpecificRule instance = null; + ImageView imageHelpButton; + + private static ProgressDialog progressDialog = null; + + private static Trigger_Enum triggerType; + private static boolean triggerParameter; + private static PointOfInterest triggerPoi; + private static String triggerProcess; + private static int triggerBattery; + private static double triggerSpeed; + private static double triggerNoise; + private static TimeFrame triggerTimeFrame; + private static String triggerWifiName; + + private static Rule ruleToEdit; + private static boolean newRule; + + private static Trigger newTrigger; + private static Action newAction; + + ArrayAdapter triggerListViewAdapter; + ArrayAdapter actionListViewAdapter; + + int editIndex = 0; + + final static int requestCodeActionTriggerUrlAdd = 1000; + final static int requestCodeActionTriggerUrlEdit = 1001; + final static int requestCodeTriggerTimeframeAdd = 2000; + final static int requestCodeTriggerTimeframeEdit = 2001; + final static int requestCodeActionStartActivityAdd = 3000; + final static int requestCodeActionStartActivityEdit = 3001; + final static int requestCodeTriggerNfcTagAdd = 4000; + final static int requestCodeTriggerNfcTagEdit = 4001; + final static int requestCodeActionSpeakTextAdd = 5000; + final static int requestCodeActionSpeakTextEdit = 1001; + final static int requestCodeTriggerBluetoothAdd = 6000; + final static int requestCodeTriggerBluetoothEdit = 6001; + final static int requestCodeActionScreenBrightnessAdd = 401; + final static int requestCodeActionScreenBrightnessEdit = 402; + final static int requestCodeActionSendTextMessage = 7001; + + public static ActivityManageSpecificRule getInstance() + { + if(instance == null) + instance = new ActivityManageSpecificRule(); + + return instance; + } + + @Override + protected void onCreate(Bundle savedInstanceState) + { + context = this; + super.onCreate(savedInstanceState); + setContentView(R.layout.manage_specific_rule); + + instance = this; + + cmdTriggerAdd = (Button)findViewById(R.id.cmdTriggerAdd); + cmdActionAdd = (Button)findViewById(R.id.cmdActionAdd); + triggerListView = (ListView)findViewById(R.id.lvTriggerListView); + actionListView = (ListView)findViewById(R.id.lvActionListView); + etRuleName = (EditText)findViewById(R.id.etRuleName); + cmdSaveRule = (Button)findViewById(R.id.cmdSaveRule); + chkRuleActive = (CheckBox)findViewById(R.id.chkRuleActive); + chkRuleToggle = (CheckBox)findViewById(R.id.chkRuleToggle); + imageHelpButton = (ImageView)findViewById(R.id.imageHelpButton); + + //decide if it will be created anew or loaded to edit an existing one + if(ActivityMainRules.ruleToEdit == null) + { + // new rule + Miscellaneous.logEvent("i", "Rule", "Cache empty, assuming create request.", 3); + newRule = true; + ruleToEdit = new Rule(); + ruleToEdit.setTriggerSet(new ArrayList()); + ruleToEdit.setActionSet(new ArrayList()); + triggerListViewAdapter = new ArrayAdapter(this, R.layout.text_view_for_poi_listview_mediumtextsize, ruleToEdit.getTriggerSet()); + actionListViewAdapter = new ArrayAdapter(this, R.layout.text_view_for_poi_listview_mediumtextsize, ruleToEdit.getActionSet()); + } + else + { + // change existing rule + Miscellaneous.logEvent("i", "Rule", "Cache not empty, assuming change request.", 3); + newRule = false; + ruleToEdit = ActivityMainRules.ruleToEdit; + triggerListViewAdapter = new ArrayAdapter(this, R.layout.text_view_for_poi_listview_mediumtextsize, ruleToEdit.getTriggerSet()); + actionListViewAdapter = new ArrayAdapter(this, R.layout.text_view_for_poi_listview_mediumtextsize, ruleToEdit.getActionSet()); + loadVariablesIntoGui(); + } + + cmdTriggerAdd.setOnClickListener(new OnClickListener() + { + @Override + public void onClick(View v) + { + hideKeyboard(); + newTrigger = new Trigger(); + getTriggerTypeDialog(context).show(); + } + }); + + cmdActionAdd.setOnClickListener(new OnClickListener() + { + @Override + public void onClick(View v) + { + hideKeyboard(); + getActionTypeDialog().show(); + } + }); + + cmdSaveRule.setOnClickListener(new OnClickListener() + { + @Override + public void onClick(View v) + { + if(newRule) + { + Miscellaneous.logEvent("i", "Rule", "Will create a new rule.", 3); + loadFormValuesToVariable(); + if(ruleToEdit.create(context)) + { + ActivityPermissions.getRequiredPermissions(false); + finish(); + } + else + Toast.makeText(ActivityManageSpecificRule.this, getResources().getString(R.string.errorWritingConfig), Toast.LENGTH_LONG).show(); + } + else + { + Miscellaneous.logEvent("i", "Rule", "Will change an existing rule.", 3); + loadFormValuesToVariable(); + if(ruleToEdit.change(context)) + { + ActivityPermissions.getRequiredPermissions(false); + finish(); + } + else + Toast.makeText(ActivityManageSpecificRule.this, getResources().getString(R.string.errorWritingConfig), Toast.LENGTH_LONG).show(); + } + } + }); + + triggerListView.setClickable(true); + actionListView.setClickable(true); + + triggerListView.setOnItemLongClickListener(new OnItemLongClickListener() + { + @Override + public boolean onItemLongClick(AdapterView arg0, View arg1, int arg2, long arg3) + { + getTriggerDeleteDialog(context, (Trigger)triggerListView.getItemAtPosition(arg2)).show(); + return false; + } + }); + triggerListView.setOnItemClickListener(new OnItemClickListener() + { + @Override + public void onItemClick(AdapterView arg0, View arg1, int arg2, long arg3) + { + editIndex = arg2; + Trigger selectedTrigger = (Trigger)triggerListView.getItemAtPosition(arg2); + switch(selectedTrigger.getTriggerType()) + { +// case batteryLevel: +// break; +// case charging: +// break; +// case noiseLevel: +// break; +// case pointOfInterest: +// break; +// case process_started_stopped: +// break; +// case speed: +// break; + case timeFrame: + ActivityManageTimeFrame.editedTimeFrameTrigger = selectedTrigger; + Intent timeFrameEditor = new Intent(ActivityManageSpecificRule.this, ActivityManageTimeFrame.class); + startActivityForResult(timeFrameEditor, requestCodeTriggerTimeframeEdit); + break; +// case usb_host_connection: +// break; +// case wifiConnection: +// break; + case bluetoothConnection: + ActivityManageBluetoothTrigger.editedBluetoothTrigger = selectedTrigger; + Intent bluetoothEditor = new Intent(ActivityManageSpecificRule.this, ActivityManageBluetoothTrigger.class); + startActivityForResult(bluetoothEditor, requestCodeTriggerBluetoothEdit); + break; + default: + break; + } + } + }); + triggerListView.setOnTouchListener(new OnTouchListener() + { + @Override + public boolean onTouch(View v, MotionEvent event) + { + v.getParent().requestDisallowInterceptTouchEvent(true); + return false; + } + }); + + actionListView.setOnItemLongClickListener(new OnItemLongClickListener() + { + public boolean onItemLongClick(AdapterView arg0, View arg1, int arg2, long arg3) + { + getActionDeleteDialog(context, (Action)actionListView.getItemAtPosition(arg2)).show(); + return false; + } + }); + actionListView.setOnItemClickListener(new OnItemClickListener() + { + // editing triggers + @Override + public void onItemClick(AdapterView arg0, View arg1, int arg2, long arg3) + { + editIndex = arg2; + Action a = (Action)actionListView.getItemAtPosition(arg2); + switch(a.getAction()) + { +// case changeSoundProfile: +// break; +// case disableScreenRotation: +// break; +// case enableScreenRotation: +// break; +// case setAirplaneMode: +// break; + case startOtherActivity: + Intent intent = new Intent(ActivityManageSpecificRule.this, ActivityManageStartActivity.class); + ActivityManageStartActivity.resultingAction = a; + intent.putExtra("edit", true); + startActivityForResult(intent, requestCodeActionStartActivityEdit); + break; + case triggerUrl: + Intent activityEditTriggerUrlIntent = new Intent(ActivityManageSpecificRule.this, ActivityEditTriggerUrl.class); +// activityEditTriggerUrlIntent.putExtra("urlToTrigger", a.getParameter2()); + ActivityEditTriggerUrl.resultingAction = a; + activityEditTriggerUrlIntent.putExtra("edit", true); + startActivityForResult(activityEditTriggerUrlIntent, requestCodeActionTriggerUrlEdit); + break; +// case turnBluetoothOff: +// break; +// case turnBluetoothOn: +// break; +// case turnUsbTetheringOff: +// break; +// case turnUsbTetheringOn: +// break; +// case turnWifiOff: +// break; +// case turnWifiOn: +// break; +// case turnWifiTetheringOff: +// break; +// case turnWifiTetheringOn: +// break; +// case waitBeforeNextAction: +// break; +// case wakeupDevice: +// break; + case speakText: + Intent activitySpeakTextIntent = new Intent(ActivityManageSpecificRule.this, ActivityEditSpeakText.class); + ActivityEditSpeakText.resultingAction = a; + activitySpeakTextIntent.putExtra("edit", true); + startActivityForResult(activitySpeakTextIntent, requestCodeActionSpeakTextEdit); + break; + case sendTextMessage: + Intent activitySendTextMessageIntent = new Intent(ActivityManageSpecificRule.this, ActivityEditSendTextMessage.class); + ActivityEditSendTextMessage.resultingAction = a; + activitySendTextMessageIntent.putExtra("edit", true); + startActivityForResult(activitySendTextMessageIntent, requestCodeActionSendTextMessage); + break; + case setScreenBrightness: + Intent activityEditScreenBrightnessIntent = new Intent(ActivityManageSpecificRule.this, ActivityManageBrightnessSetting.class); +// ActivityEditTriggerUrl.resultingAction = a; + activityEditScreenBrightnessIntent.putExtra("autoBrightness", a.getParameter1()); + activityEditScreenBrightnessIntent.putExtra("brightnessValue", Integer.parseInt(a.getParameter2())); + startActivityForResult(activityEditScreenBrightnessIntent, requestCodeActionScreenBrightnessEdit); + break; + default: + Miscellaneous.logEvent("w", "Edit action", "Editing of action type " + a.getAction().toString() + " not implemented, yet.", 4); + break; + } + } + }); + actionListView.setOnTouchListener(new OnTouchListener() + { + @Override + public boolean onTouch(View v, MotionEvent event) + { + v.getParent().requestDisallowInterceptTouchEvent(true); + return false; + } + }); + + chkRuleToggle.setOnCheckedChangeListener(new OnCheckedChangeListener() + { + private boolean guiEditing = false; + + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) + { + if(!guiEditing) + if(!plausibilityCheck()) + { + guiEditing = true; + chkRuleToggle.setChecked(false); + guiEditing = false; + Toast.makeText(ActivityManageSpecificRule.this, getResources().getString(R.string.toggleNotAllowed), Toast.LENGTH_LONG).show(); + } + } + }); + + imageHelpButton.setOnClickListener(new OnClickListener() + { + @Override + public void onClick(View v) + { + // Open help popup + Miscellaneous.messageBox(getResources().getString(R.string.whatsThis), getResources().getString(R.string.helpTextToggable), ActivityManageSpecificRule.this).show(); + } + }); + } + + protected boolean plausibilityCheck() + { + boolean nfcFound = false; + if(chkRuleToggle.isChecked()) + { + for(Trigger trigger : ruleToEdit.getTriggerSet()) + { + if(trigger.getTriggerType().equals(Trigger_Enum.nfcTag)) + nfcFound = true; + } + if(!nfcFound) + return false; + } + + return true; + } + + protected void loadFormValuesToVariable() + { + ruleToEdit.setName(etRuleName.getText().toString()); + ruleToEdit.setRuleActive(chkRuleActive.isChecked()); + ruleToEdit.setRuleToggle(chkRuleToggle.isChecked()); + } + + private void loadVariablesIntoGui() + { + // Set all gui fields to the values of the to-be-edited-object + + etRuleName.setText(ruleToEdit.getName()); + chkRuleActive.setChecked(ruleToEdit.isRuleActive()); + chkRuleToggle.setChecked(ruleToEdit.isRuleToggle()); + + refreshTriggerList(); + refreshActionList(); + } + + private AlertDialog getTriggerTypeDialog(final Context myContext) + { + final ArrayList items = new ArrayList(); + + CharSequence[] types = Trigger.getTriggerTypesAsArray(); + CharSequence[] typesLong = Trigger.getTriggerTypesStringAsArray(myContext); + + for(int i=0; i(this, android.R.layout.select_dialog_item, android.R.id.text1, items) + { + public View getView(int position, View convertView, ViewGroup parent) + { + //User super class to create the View + View v = super.getView(position, convertView, parent); + + TextView tv = (TextView)v.findViewById(android.R.id.text1); + + //Put the image on the TextView + tv.setCompoundDrawablesWithIntrinsicBounds(items.get(position).icon, 0, 0, 0); + + //Add margin between image and text (support various screen densities) + int dp5 = (int) (5 * getResources().getDisplayMetrics().density + 0.5f); + tv.setCompoundDrawablePadding(dp5); + + return v; + } + }; + + AlertDialog.Builder builder = new AlertDialog.Builder(this) + .setTitle(getResources().getString(R.string.selectTypeOfTrigger)) + .setAdapter(adapter, new DialogInterface.OnClickListener() + { + public void onClick(DialogInterface dialog, int which) + { + triggerType = Trigger_Enum.values()[which]; + + String[] booleanChoices = null; + if(triggerType == Trigger_Enum.pointOfInterest) + { + if(PointOfInterest.getPointOfInterestCollection() != null && PointOfInterest.getPointOfInterestCollection().size() > 0) + booleanChoices = new String[]{getResources().getString(R.string.entering), getResources().getString(R.string.leaving)}; + else + { + Toast.makeText(myContext, getResources().getString(R.string.noPoisSpecified), Toast.LENGTH_LONG).show(); + return; + } + } + else if(triggerType == Trigger_Enum.timeFrame) + { + newTrigger.setTriggerType(Trigger_Enum.timeFrame); + ActivityManageTimeFrame.editedTimeFrameTrigger = newTrigger; + Intent timeFrameEditor = new Intent(myContext, ActivityManageTimeFrame.class); + startActivityForResult(timeFrameEditor, requestCodeTriggerTimeframeAdd); + return; + } + else if(triggerType == Trigger_Enum.charging) + booleanChoices = new String[]{getResources().getString(R.string.started), getResources().getString(R.string.stopped)}; + else if(triggerType == Trigger_Enum.usb_host_connection) + booleanChoices = new String[]{getResources().getString(R.string.connected), getResources().getString(R.string.disconnected)}; + else if(triggerType == Trigger_Enum.speed | triggerType == Trigger_Enum.noiseLevel | triggerType == Trigger_Enum.batteryLevel) + booleanChoices = new String[]{getResources().getString(R.string.exceeds), getResources().getString(R.string.dropsBelow)}; + else if(triggerType == Trigger_Enum.wifiConnection) + booleanChoices = new String[]{getResources().getString(R.string.connected), getResources().getString(R.string.disconnected)}; + else if(triggerType == Trigger_Enum.process_started_stopped) + booleanChoices = new String[]{getResources().getString(R.string.started), getResources().getString(R.string.stopped)}; + else if(triggerType == Trigger_Enum.airplaneMode) + booleanChoices = new String[]{getResources().getString(R.string.activated), getResources().getString(R.string.deactivated)}; + else if(triggerType == Trigger_Enum.roaming) + booleanChoices = new String[]{getResources().getString(R.string.activated), getResources().getString(R.string.deactivated)}; + else if(triggerType == Trigger_Enum.phoneCall) + booleanChoices = new String[]{getResources().getString(R.string.started), getResources().getString(R.string.stopped)}; + else if(triggerType == Trigger_Enum.activityDetection) + { + if(ActivityDetectionReceiver.isPlayServiceAvailable()) + { + newTrigger.setTriggerType(Trigger_Enum.activityDetection); + getTriggerActivityDetectionDialog().show(); + } + else + Toast.makeText(myContext, getResources().getString(R.string.triggerOnlyAvailableIfPlayServicesInstalled), Toast.LENGTH_LONG).show(); + return; + } + else if(triggerType == Trigger_Enum.nfcTag) + { + if(NfcReceiver.checkNfcRequirements(ActivityManageSpecificRule.this, true)) + { + newTrigger.setTriggerType(Trigger_Enum.nfcTag); + Intent nfcEditor = new Intent(myContext, ActivityManageNfc.class); + startActivityForResult(nfcEditor, requestCodeTriggerNfcTagAdd); + return; + } + } + else if(triggerType == Trigger_Enum.bluetoothConnection) + { + if(!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH)) + Miscellaneous.messageBox("Bluetooth", getResources().getString(R.string.deviceDoesNotHaveBluetooth), ActivityManageSpecificRule.this).show();; + + newTrigger.setTriggerType(Trigger_Enum.bluetoothConnection); + ActivityManageBluetoothTrigger.editedBluetoothTrigger = newTrigger; + Intent bluetoothEditor = new Intent(myContext, ActivityManageBluetoothTrigger.class); + startActivityForResult(bluetoothEditor, requestCodeTriggerBluetoothAdd); + return; + } + else if(triggerType == Trigger_Enum.headsetPlugged) + booleanChoices = new String[]{getResources().getString(R.string.connected), getResources().getString(R.string.disconnected)}; + + if(triggerType == Trigger_Enum.nfcTag) + { + if (NfcReceiver.checkNfcRequirements(ActivityManageSpecificRule.this, true)) + getTriggerParamterDialog(context, booleanChoices).show(); + } + else + getTriggerParamterDialog(context, booleanChoices).show(); + } + }); + + return builder.create(); + } + private AlertDialog getTriggerParamterDialog(final Context myContext, final String[] choices) + { + AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(context); + alertDialogBuilder.setTitle(getResources().getString(R.string.selectTypeOfTrigger)); + alertDialogBuilder.setItems(choices, new DialogInterface.OnClickListener() + { + @Override + public void onClick(DialogInterface dialog, int which) + { + if(which==0) + newTrigger.setTriggerParameter(true); + else + newTrigger.setTriggerParameter(false); + + if(triggerType == Trigger_Enum.pointOfInterest) + { + ArrayList choices = new ArrayList(); + choices.add("< " + getResources().getString(R.string.none) + " >"); + for(String s : PointOfInterest.getNamesInArray()) + choices.add(s); + + getTriggerPoiDialog(myContext, choices.toArray(new String[choices.size()])).show(); + } + else if(triggerType == Trigger_Enum.batteryLevel) + { + ArrayList choicesList = new ArrayList(); + for(int i=1; i<=100; i+=1) + choicesList.add(String.valueOf(i) + " %"); + String[] choices = (String[]) choicesList.toArray(new String[choicesList.size()]); + getTriggerBatteryDialog(myContext, choices).show(); + } + else if(triggerType == Trigger_Enum.speed) + { + ArrayList choicesList = new ArrayList(); + for(int i=5; i<=150; i+=5) + choicesList.add(String.valueOf(i) + " km/h"); + String[] choices = (String[]) choicesList.toArray(new String[choicesList.size()]); + getTriggerSpeedDialog(myContext, choices).show(); + } + else if(triggerType == Trigger_Enum.noiseLevel) + { + ArrayList choicesList = new ArrayList(); + for(int i=5; i<=150; i+=5) + choicesList.add(String.valueOf(i) + " dB"); + String[] choices = (String[]) choicesList.toArray(new String[choicesList.size()]); + getTriggerNoiseDialog(myContext, choices).show(); + } +// else if(triggerType.equals(Trigger.Event_Enum.timeFrame)) +// { +// newTrigger.setTriggerType(Trigger.Event_Enum.timeFrame); +// ActivityManageTimeFrame.editedTimeFrameTrigger = null; +// Intent timeFrameEditor = new Intent(myContext, ActivityManageTimeFrame.class); +// startActivityForResult(timeFrameEditor, 2000); +// } + else if(triggerType.equals(Trigger_Enum.wifiConnection)) + { + newTrigger.setTriggerType(Trigger_Enum.wifiConnection); + getTriggerWifiDialog(myContext).show(); + } + else if(triggerType.equals(Trigger_Enum.process_started_stopped)) + { + progressDialog = ProgressDialog.show(myContext, null, getResources().getString(R.string.gettingListOfInstalledApplications), true, false); + newTrigger.setTriggerType(Trigger_Enum.process_started_stopped); + new GenerateApplicationSelectionsDialogTask().execute(ActivityManageSpecificRule.this); +// getTriggerRunningProcessDialog1(myContext).show(); + } + else if(triggerType.equals(Trigger_Enum.phoneCall)) + { + newTrigger.setTriggerType(Trigger_Enum.phoneCall); + getTriggerPhoneDirectionDialog(myContext).show(); + } + else if(triggerType.equals(Trigger_Enum.headsetPlugged)) + { + newTrigger.setTriggerType(Trigger_Enum.headsetPlugged); + getTriggerHeadphoneDialog(myContext).show(); + } + else + { + newTrigger.setTriggerType(triggerType); + ruleToEdit.getTriggerSet().add(newTrigger); + refreshTriggerList(); + } + } + }); + AlertDialog alertDialog = alertDialogBuilder.create(); + + return alertDialog; + } + private AlertDialog getTriggerBatteryDialog(final Context myContext, final String[] choices) + { + AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(context); + alertDialogBuilder.setTitle(getResources().getString(R.string.selectBattery)); + alertDialogBuilder.setItems(choices, new DialogInterface.OnClickListener() + { + @Override + public void onClick(DialogInterface dialog, int which) + { + try + { + triggerBattery = (which+1); + newTrigger.setTriggerType(Trigger_Enum.batteryLevel); + newTrigger.setBatteryLevel(triggerBattery); +// Log.i("test", newTrigger.toString()); +// Log.i("test", String.valueOf(newTrigger.getBatteryLevel())); + ruleToEdit.getTriggerSet().add(newTrigger); + refreshTriggerList(); + } + catch (Exception e) + { + e.printStackTrace(); + } + } + }); + AlertDialog alertDialog = alertDialogBuilder.create(); + + return alertDialog; + } + private AlertDialog getTriggerSpeedDialog(final Context myContext, final String[] choices) + { + AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(context); + alertDialogBuilder.setTitle(getResources().getString(R.string.selectSpeed)); + alertDialogBuilder.setItems(choices, new DialogInterface.OnClickListener() + { + @Override + public void onClick(DialogInterface dialog, int which) + { + try + { + triggerSpeed = (which+1)*5; + newTrigger.setTriggerType(Trigger_Enum.speed); + newTrigger.setSpeed(triggerSpeed); + ruleToEdit.getTriggerSet().add(newTrigger); + refreshTriggerList(); + } + catch (Exception e) + { + e.printStackTrace(); + } + } + }); + AlertDialog alertDialog = alertDialogBuilder.create(); + + return alertDialog; + } + private AlertDialog getTriggerNoiseDialog(final Context myContext, final String[] choices) + { + AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(context); + alertDialogBuilder.setTitle(getResources().getString(R.string.selectNoiseLevel)); + alertDialogBuilder.setItems(choices, new DialogInterface.OnClickListener() + { + @Override + public void onClick(DialogInterface dialog, int which) + { + try + { + triggerNoise = (which+1)*5; + newTrigger.setTriggerType(Trigger_Enum.noiseLevel); + newTrigger.setNoiseLevelDb(Math.round(triggerNoise)); + ruleToEdit.getTriggerSet().add(newTrigger); + refreshTriggerList(); + + /* + * Comment about the physical reference value. + */ + if(!Rule.isAnyRuleUsing(Trigger_Enum.noiseLevel)) + { + AlertDialog noiseHintDialog = Miscellaneous.messageBox(myContext.getResources().getString(R.string.hint), myContext.getResources().getString(R.string.noiseDetectionHint), myContext); + noiseHintDialog.show(); + Linkify.addLinks((TextView) noiseHintDialog.findViewById(android.R.id.message), Linkify.ALL); +// ((TextView)noiseHintDialog.findViewById(android.R.id.message)).setMovementMethod(LinkMovementMethod.getInstance()); + } + } + catch (Exception e) + { + e.printStackTrace(); + } + } + }); + AlertDialog alertDialog = alertDialogBuilder.create(); + + return alertDialog; + } + private AlertDialog getTriggerWifiDialog(final Context myContext) + { + AlertDialog.Builder alertDialog = new AlertDialog.Builder(this); + + alertDialog.setTitle(myContext.getResources().getString(R.string.wifiName)); + alertDialog.setMessage(myContext.getResources().getString(R.string.enterWifiName)); + + // Set an EditText view to get user input + final EditText input = new EditText(this); + alertDialog.setView(input); + + alertDialog.setPositiveButton("Ok", new DialogInterface.OnClickListener() + { + public void onClick(DialogInterface dialog, int whichButton) + { + newTrigger.setWifiName(input.getText().toString()); + ruleToEdit.getTriggerSet().add(newTrigger); + refreshTriggerList(); + } + }); + + alertDialog.setNegativeButton(myContext.getResources().getString(R.string.cancel), new DialogInterface.OnClickListener() + { + public void onClick(DialogInterface dialog, int whichButton) + { + // Canceled. + } + }); + + return alertDialog.create(); + } + private AlertDialog getTriggerPhoneDirectionDialog(final Context myContext) + { + AlertDialog.Builder alertDialog = new AlertDialog.Builder(this); + + alertDialog.setTitle(myContext.getResources().getString(R.string.phoneDirection)); + String[] choices = new String[] { myContext.getResources().getString(R.string.any), myContext.getResources().getString(R.string.incoming), myContext.getResources().getString(R.string.outgoing) }; + alertDialog.setItems(choices, new DialogInterface.OnClickListener() + { + @Override + public void onClick(DialogInterface dialog, int which) + { + newTrigger.setPhoneDirection(which); + getTriggerPhoneNumberDialog(ActivityManageSpecificRule.this).show(); + } + }); + + return alertDialog.create(); + } + private AlertDialog getTriggerPhoneNumberDialog(final Context myContext) + { + AlertDialog.Builder alertDialog = new AlertDialog.Builder(this); + + alertDialog.setTitle(myContext.getResources().getString(R.string.phoneNumber)); + alertDialog.setMessage(myContext.getResources().getString(R.string.enterPhoneNumber)); + + // Set an EditText view to get user input + final EditText input = new EditText(this); + input.setInputType(InputType.TYPE_CLASS_PHONE); + alertDialog.setView(input); + + alertDialog.setPositiveButton("Ok", new DialogInterface.OnClickListener() + { + public void onClick(DialogInterface dialog, int whichButton) + { + String number = input.getText().toString(); + if(number.length() == 0) + number = "any"; + + newTrigger.setPhoneNumber(number); + ruleToEdit.getTriggerSet().add(newTrigger); + refreshTriggerList(); + } + }); + + alertDialog.setNegativeButton(myContext.getResources().getString(R.string.cancel), new DialogInterface.OnClickListener() + { + public void onClick(DialogInterface dialog, int whichButton) + { + // Canceled. + } + }); + + return alertDialog.create(); + } + private AlertDialog getTriggerPoiDialog(final Context myContext, final String[] choices) + { + AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(context); + alertDialogBuilder.setTitle(getResources().getString(R.string.selectPoi)); + alertDialogBuilder.setItems(choices, new DialogInterface.OnClickListener() + { + @Override + public void onClick(DialogInterface dialog, int which) + { + try + { + if(which > 0) + triggerPoi = PointOfInterest.getByName(choices[which]); + else + triggerPoi = null; + + newTrigger.setTriggerType(Trigger_Enum.pointOfInterest); + newTrigger.setPointOfInterest(triggerPoi); + ruleToEdit.getTriggerSet().add(newTrigger); + refreshTriggerList(); + } + catch (Exception e) + { + e.printStackTrace(); + } + } + }); + AlertDialog alertDialog = alertDialogBuilder.create(); + + return alertDialog; + } + private AlertDialog getTriggerHeadphoneDialog(final Context myContext) + { + AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(context); + alertDialogBuilder.setTitle(getResources().getString(R.string.headphoneSelectType)); + alertDialogBuilder.setItems(new String[]{ myContext.getResources().getString(R.string.headphoneSimple), myContext.getResources().getString(R.string.headphoneMicrophone), myContext.getResources().getString(R.string.headphoneAny) }, new DialogInterface.OnClickListener() + { + @Override + public void onClick(DialogInterface dialog, int which) + { + try + { + newTrigger.setHeadphoneType(which); + ruleToEdit.getTriggerSet().add(newTrigger); + refreshTriggerList(); + } + catch (Exception e) + { + e.printStackTrace(); + } + } + }); + AlertDialog alertDialog = alertDialogBuilder.create(); + + return alertDialog; + } + 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) + { + newTrigger.setActivityDetectionType(ActivityDetectionReceiver.getAllTypes()[which]); + ruleToEdit.getTriggerSet().add(newTrigger); + refreshTriggerList(); + } + }); + + return alertDialog.create(); + } + + private static class GenerateApplicationSelectionsDialogTask extends AsyncTask + { + @Override + protected String[] doInBackground(ActivityManageSpecificRule... params) + { +// Looper.prepare(); + final String[] applicationArray = ActivityManageStartActivity.getApplicationNameListString(params[0]); + return applicationArray; + } + + @Override + protected void onPostExecute(String[] result) + { + if(progressDialog != null) + { + progressDialog.dismiss(); + progressDialog = null; + } + + ActivityManageSpecificRule.getInstance().showProcessDialog(result); + } + } + + void showProcessDialog(String[] programStrings) + { + getTriggerRunningProcessDialog1(ActivityManageSpecificRule.this, programStrings).show(); + } + + private AlertDialog getTriggerRunningProcessDialog1(final Context myContext, final String[] applicationArray) + { + AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(myContext); + alertDialogBuilder.setTitle(myContext.getResources().getString(R.string.selectApplication)); + alertDialogBuilder.setItems(applicationArray, new DialogInterface.OnClickListener() + { + @Override + public void onClick(DialogInterface dialog, int which) + { + String applicationName = applicationArray[which]; + getTriggerRunningProcessDialog2(myContext, applicationName).show(); + } + }); + AlertDialog alertDialog = alertDialogBuilder.create(); + +// Log.i("Amount of Applications", String.valueOf(applicationArray.length)); +// Log.i("Amount of Packages", String.valueOf(ActivityManageStartActivity.getPackageListString(myContext).length)); + + return alertDialog; + } + + private AlertDialog getTriggerRunningProcessDialog2(final Context myContext, String applicationName) + { + AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(myContext); + alertDialogBuilder.setTitle(myContext.getResources().getString(R.string.selectPackageOfApplication)); + final String[] packageArray = ActivityManageStartActivity.getPackageListString(myContext, applicationName); + alertDialogBuilder.setItems(packageArray, new DialogInterface.OnClickListener() + { + @Override + public void onClick(DialogInterface dialog, int which) + { + String packageName = packageArray[which]; + getTriggerRunningProcessDialog3(myContext, packageName).show(); + Miscellaneous.messageBox(getResources().getString(R.string.hint), getResources().getString(R.string.chooseActivityHint), ActivityManageSpecificRule.this).show(); + } + }); + AlertDialog alertDialog = alertDialogBuilder.create(); + + return alertDialog; + } + + private AlertDialog getTriggerRunningProcessDialog3(final Context myContext, final String packageName) + { + AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(myContext); + alertDialogBuilder.setTitle(myContext.getResources().getString(R.string.selectActivityToBeStarted)); + final String activityArray[] = ActivityManageStartActivity.getActivityListForPackageName(packageName); + alertDialogBuilder.setItems(activityArray, new DialogInterface.OnClickListener() + { + @Override + public void onClick(DialogInterface dialog, int which) + { + triggerProcess = activityArray[which]; + newTrigger.setTriggerType(Trigger_Enum.process_started_stopped); + newTrigger.setProcessName(triggerProcess); + ruleToEdit.getTriggerSet().add(newTrigger); + refreshTriggerList(); + } + }); + AlertDialog alertDialog = alertDialogBuilder.create(); + + return alertDialog; + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) + { + super.onActivityResult(requestCode, resultCode, data); + + if(requestCode == requestCodeActionTriggerUrlAdd) + { + if(resultCode == RESULT_OK) + { + //add TriggerUrl + ruleToEdit.getActionSet().add(ActivityEditTriggerUrl.resultingAction); + this.refreshActionList(); + } + } + else if(requestCode == requestCodeActionTriggerUrlEdit) + { + if(resultCode == RESULT_OK) + { + //edit TriggerUrl + this.refreshActionList(); + } + } + else if(requestCode == requestCodeTriggerTimeframeAdd) + { + //add TimeFrame + if(resultCode == RESULT_OK && ActivityManageTimeFrame.editedTimeFrameTrigger != null) + { + ruleToEdit.getTriggerSet().add(newTrigger); + this.refreshTriggerList(); + } + else + Miscellaneous.logEvent("w", "TimeFrameEdit", "No timeframe returned. Assuming abort.", 5); + } + else if(requestCode == requestCodeTriggerTimeframeEdit) + { + //edit TimeFrame + if(resultCode == RESULT_OK && ActivityManageTimeFrame.editedTimeFrameTrigger != null) + { + this.refreshTriggerList(); + } + else + Miscellaneous.logEvent("w", "TimeFrameEdit", "No timeframe returned. Assuming abort.", 5); + } + else if(requestCode == requestCodeActionStartActivityAdd) + { + // manage start of other activity + if(resultCode == RESULT_OK) + { + newAction = ActivityManageStartActivity.resultingAction; + ruleToEdit.getActionSet().add(newAction); + this.refreshActionList(); + } + } + else if(requestCode == requestCodeActionStartActivityEdit) + { + // manage start of other activity + if(resultCode == RESULT_OK) + { + newAction = ActivityManageStartActivity.resultingAction; +// ruleToEdit.getActionSet().add(newAction); + this.refreshActionList(); + } + } + else if(requestCode == requestCodeTriggerNfcTagAdd) + { + //add TimeFrame + if(resultCode == RESULT_OK && ActivityManageNfc.generatedId != null) + { + newTrigger.setNfcTagId(ActivityManageNfc.generatedId); + ruleToEdit.getTriggerSet().add(newTrigger); + this.refreshTriggerList(); + } + else + Miscellaneous.logEvent("w", "ActivityManageNfc", "No nfc id returned. Assuming abort.", 5); + } + else if(requestCode == requestCodeActionSpeakTextAdd) + { + if(resultCode == RESULT_OK) + { + //add SpeakText + ruleToEdit.getActionSet().add(ActivityEditSpeakText.resultingAction); + this.refreshActionList(); + } + } + else if(requestCode == requestCodeActionSpeakTextEdit) + { + if(resultCode == RESULT_OK) + { + //add SpeakText + ruleToEdit.getActionSet().add(ActivityEditSendTextMessage.resultingAction); + this.refreshActionList(); + } + } + else if(requestCode == requestCodeTriggerBluetoothAdd) + { + //add bluetooth trigger + if(resultCode == RESULT_OK && ActivityManageBluetoothTrigger.editedBluetoothTrigger != null) + { + ruleToEdit.getTriggerSet().add(newTrigger); + this.refreshTriggerList(); + } + else + Miscellaneous.logEvent("w", "BluetoothTriggerEdit", "No bluetooth trigger returned. Assuming abort.", 5); + } + else if(requestCode == requestCodeTriggerBluetoothEdit) + { + //edit bluetooth trigger + if(resultCode == RESULT_OK && ActivityManageBluetoothTrigger.editedBluetoothTrigger != null) + { + this.refreshTriggerList(); + } + else + Miscellaneous.logEvent("w", "BluetoothTriggerEdit", "No bluetooth trigger returned. Assuming abort.", 5); + } + else if(requestCode == requestCodeActionScreenBrightnessAdd) + { + if(resultCode == RESULT_OK) + { + newAction.setParameter1(data.getBooleanExtra("autoBrightness", false)); + newAction.setParameter2(String.valueOf(data.getIntExtra("brightnessValue", 0))); + ruleToEdit.getActionSet().add(newAction); + this.refreshActionList(); + } + } + else if(requestCode == requestCodeActionScreenBrightnessEdit) + { + if(resultCode == RESULT_OK) + { + if(data.hasExtra("autoBrightness")) + ruleToEdit.getActionSet().get(editIndex).setParameter1(data.getBooleanExtra("autoBrightness", false)); + + if(data.hasExtra("brightnessValue")) + ruleToEdit.getActionSet().get(editIndex).setParameter2(String.valueOf(data.getIntExtra("brightnessValue", 0))); + + this.refreshActionList(); + } + } + + //TODO: Check with has data been changed or something like that. + /*try + { + Miscellaneous.logEvent("i", "ActivityManageSpecificRule", getResources().getString(R.string.noDataChangedReadingAnyway), 4); + XmlFileInterface.readFile(); + } + catch (FileNotFoundException e) + { + Miscellaneous.logEvent("e", "ActivityManageSpecificRule", getResources().getString(R.string.errorReadingPoisAndRulesFromFile) + ": " + Log.getStackTraceString(e), 5); + }*/ + } + + protected Dialog getActionTypeDialog() + { + final ArrayList items = new ArrayList(); + + CharSequence[] types = Action.getActionTypesAsArray(); + CharSequence[] typesLong = Action.getActionTypesFullNameStringAsArray(this); + + for(int i=0; i(this, android.R.layout.select_dialog_item, android.R.id.text1, items) + ListAdapter adapter = new ArrayAdapter(this, android.R.layout.select_dialog_item, android.R.id.text1, items) + { + public View getView(int position, View convertView, ViewGroup parent) + { + //User super class to create the View + View v = super.getView(position, convertView, parent); + + TextView tv = (TextView)v.findViewById(android.R.id.text1); + + //Put the image on the TextView + tv.setCompoundDrawablesWithIntrinsicBounds(items.get(position).icon, 0, 0, 0); + + //Add margin between image and text (support various screen densities) + int dp5 = (int) (5 * getResources().getDisplayMetrics().density + 0.5f); + tv.setCompoundDrawablePadding(dp5); + + return v; + } + }; + + AlertDialog.Builder builder = new AlertDialog.Builder(this) + .setTitle(getResources().getString(R.string.selectTypeOfAction)) + .setAdapter(adapter, new DialogInterface.OnClickListener() + { + public void onClick(DialogInterface dialog, int which) + { + newAction = new Action(); + + if(Action.getActionTypesAsArray()[which].toString().equals(Action_Enum.triggerUrl.toString())) + { + //launch other activity to enter a url and parameters; + newAction.setAction(Action_Enum.triggerUrl); + ActivityEditTriggerUrl.resultingAction = null; + Intent editTriggerIntent = new Intent(context, ActivityEditTriggerUrl.class); + startActivityForResult(editTriggerIntent, 1000); + } + else if(Action.getActionTypesAsArray()[which].toString().equals(Action_Enum.setWifi.toString())) + { + newAction.setAction(Action_Enum.setWifi); + if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) + Toast.makeText(context, context.getResources().getString(R.string.android10WifiToggleNotice), Toast.LENGTH_LONG).show(); + getActionParameter1Dialog(ActivityManageSpecificRule.this).show(); + } + else if(Action.getActionTypesAsArray()[which].toString().equals(Action_Enum.setBluetooth.toString())) + { + if(!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH)) + Miscellaneous.messageBox("Bluetooth", getResources().getString(R.string.deviceDoesNotHaveBluetooth), ActivityManageSpecificRule.this).show();; + newAction.setAction(Action_Enum.setBluetooth); + getActionParameter1Dialog(ActivityManageSpecificRule.this).show(); + } + else if(Action.getActionTypesAsArray()[which].toString().equals(Action_Enum.setUsbTethering.toString())) + { + newAction.setAction(Action_Enum.setUsbTethering); + if(Build.VERSION.SDK_INT > Build.VERSION_CODES.GINGERBREAD_MR1) + Toast.makeText(context, context.getResources().getString(R.string.usbTetheringFailForAboveGingerbread), Toast.LENGTH_LONG).show(); + getActionParameter1Dialog(ActivityManageSpecificRule.this).show(); + } + else if(Action.getActionTypesAsArray()[which].toString().equals(Action_Enum.setWifiTethering.toString())) + { + newAction.setAction(Action_Enum.setWifiTethering); + getActionParameter1Dialog(ActivityManageSpecificRule.this).show(); + } + else if(Action.getActionTypesAsArray()[which].toString().equals(Action_Enum.setDisplayRotation.toString())) + { + newAction.setAction(Action_Enum.setDisplayRotation); + getActionParameter1Dialog(ActivityManageSpecificRule.this).show(); + } + else if(Action.getActionTypesAsArray()[which].toString().equals(Action_Enum.changeSoundProfile.toString())) + { + if(Profile.getProfileCollection().size() > 0) + { + newAction.setAction(Action_Enum.changeSoundProfile); + getActionSoundProfileDialog(context).show(); + } + else + Toast.makeText(context, getResources().getString(R.string.noProfilesCreateOneFirst), Toast.LENGTH_LONG).show(); + } + else if(Action.getActionTypesAsArray()[which].toString().equals(Action_Enum.startOtherActivity.toString())) + { + newAction.setAction(Action_Enum.startOtherActivity); + Intent intent = new Intent(ActivityManageSpecificRule.this, ActivityManageStartActivity.class); + startActivityForResult(intent, 3000); + } + else if(Action.getActionTypesAsArray()[which].toString().equals(Action_Enum.waitBeforeNextAction.toString())) + { + newAction.setAction(Action_Enum.waitBeforeNextAction); + getActionWaitBeforeNextActionDialog(ActivityManageSpecificRule.this).show(); + } + else if(Action.getActionTypesAsArray()[which].toString().equals(Action_Enum.wakeupDevice.toString())) + { + newAction.setAction(Action_Enum.wakeupDevice); + getActionWakeupDeviceDialog(ActivityManageSpecificRule.this).show(); + } + else if(Action.getActionTypesAsArray()[which].toString().equals(Action_Enum.setAirplaneMode.toString())) + { + if(Build.VERSION.SDK_INT >= 17) + { + Toast.makeText(context, getResources().getString(R.string.airplaneModeSdk17Warning), Toast.LENGTH_LONG).show(); + Miscellaneous.messageBox(getResources().getString(R.string.airplaneMode), getResources().getString(R.string.rootExplanation), ActivityManageSpecificRule.this).show(); + } + newAction.setAction(Action_Enum.setAirplaneMode); + getActionParameter1Dialog(ActivityManageSpecificRule.this).show(); + } + else if(Action.getActionTypesAsArray()[which].toString().equals(Action_Enum.setDataConnection.toString())) + { + newAction.setAction(Action_Enum.setDataConnection); + getActionParameter1Dialog(ActivityManageSpecificRule.this).show(); + Miscellaneous.messageBox(getResources().getString(R.string.actionDataConnection), getResources().getString(R.string.rootExplanation), ActivityManageSpecificRule.this).show(); + } + else if(Action.getActionTypesAsArray()[which].toString().equals(Action_Enum.speakText.toString())) + { + //launch other activity to enter a url and parameters; + newAction.setAction(Action_Enum.speakText); + ActivityEditSpeakText.resultingAction = null; + Intent editTriggerIntent = new Intent(context, ActivityEditSpeakText.class); + startActivityForResult(editTriggerIntent, 5000); + } + else if(Action.getActionTypesAsArray()[which].toString().equals(Action_Enum.sendTextMessage.toString())) + { +// if(ActivityPermissions.isPermissionDeclaratedInManifest(ActivityManageSpecificRule.this, "android.permission.SEND_SMS") && !Miscellaneous.isGooglePlayInstalled(ActivityManageSpecificRule.this)) + if(ActivityPermissions.isPermissionDeclaratedInManifest(ActivityManageSpecificRule.this, "android.permission.SEND_SMS")) + { + //launch other activity to enter parameters; + newAction.setAction(Action_Enum.sendTextMessage); + ActivityEditSendTextMessage.resultingAction = null; + Intent editTriggerIntent = new Intent(context, ActivityEditSendTextMessage.class); + startActivityForResult(editTriggerIntent, 5001); + } + } + else if(Action.getActionTypesAsArray()[which].toString().equals(Action_Enum.playMusic.toString())) + { + newAction.setAction(Action_Enum.playMusic); + ruleToEdit.getActionSet().add(newAction); + refreshActionList(); + } + else if(Action.getActionTypesAsArray()[which].toString().equals(Action_Enum.setScreenBrightness.toString())) + { + newAction.setAction(Action_Enum.setScreenBrightness); + Intent actionScreenBrightnessIntent = new Intent(context, ActivityManageBrightnessSetting.class); + startActivityForResult(actionScreenBrightnessIntent, requestCodeActionScreenBrightnessAdd); + } + } + }); + + return builder.create(); + } + private AlertDialog getActionSoundProfileDialog(final Context myContext) + { + AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(context); + alertDialogBuilder.setTitle(getResources().getString(R.string.selectSoundProfile)); + + final String[] choices; +// choices = new String[]{"silent", "vibrate", "normal"}; + ArrayList list = Profile.getProfileCollectionString(); + choices = list.toArray(new String[list.size()]); + + alertDialogBuilder.setItems(choices, new DialogInterface.OnClickListener() + { + @Override + public void onClick(DialogInterface dialog, int which) + { + newAction.setParameter2(choices[which]); + ruleToEdit.getActionSet().add(newAction); + refreshActionList(); + } + }); + AlertDialog alertDialog = alertDialogBuilder.create(); + + return alertDialog; + } + + private AlertDialog getTriggerDeleteDialog(Context myContext, final Trigger triggerToBeDeleted) + { + AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(context); + alertDialogBuilder.setTitle(getResources().getString(R.string.whatToDoWithTrigger)); + alertDialogBuilder.setItems(new String[]{getResources().getString(R.string.delete)}, new DialogInterface.OnClickListener() + { + @Override + public void onClick(DialogInterface dialog, int which) + { + // Only 1 choice at the moment, no need to check + ruleToEdit.getTriggerSet().remove(triggerToBeDeleted); + refreshTriggerList(); + } + }); + AlertDialog alertDialog = alertDialogBuilder.create(); + + return alertDialog; + } + private AlertDialog getActionDeleteDialog(Context myContext, final Action actionToBeDeleted) + { + AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(context); + alertDialogBuilder.setTitle(getResources().getString(R.string.whatToDoWithAction)); + alertDialogBuilder.setItems(new String[] { getResources().getString(R.string.delete), getResources().getString(R.string.moveUp), getResources().getString(R.string.moveDown)}, new DialogInterface.OnClickListener() + { + @Override + public void onClick(DialogInterface dialog, int which) + { + int positionOfSelected; + switch(which) + { + case 0: + // Delete + if(ruleToEdit.getActionSet().remove(actionToBeDeleted)) + { + refreshActionList(); + } + break; + case 1: + // Move up + positionOfSelected = ruleToEdit.getActionSet().indexOf(actionToBeDeleted); + if(positionOfSelected > 0) + { + Miscellaneous.logEvent("i", "Swap", "Swapping positions " + String.valueOf(positionOfSelected) + " and " + String.valueOf(positionOfSelected-1), 4); + Collections.swap(ruleToEdit.getActionSet(), positionOfSelected, positionOfSelected-1); + refreshActionList(); + } + else + { + // Is already at the top + Toast.makeText(ActivityManageSpecificRule.this, getResources().getString(R.string.cantMoveUp), Toast.LENGTH_LONG).show(); + } + break; + case 2: + // Move down + positionOfSelected = ruleToEdit.getActionSet().indexOf(actionToBeDeleted); + Miscellaneous.logEvent("i", "Swap", "Swapping positions " + String.valueOf(positionOfSelected) + " and " + String.valueOf(positionOfSelected+1), 4); + if(positionOfSelected < ruleToEdit.getActionSet().size()-1) + { + Collections.swap(ruleToEdit.getActionSet(), positionOfSelected, positionOfSelected+1); + refreshActionList(); + } + else + { + // Is already at the bottom + Toast.makeText(ActivityManageSpecificRule.this, getResources().getString(R.string.cantMoveDown), Toast.LENGTH_LONG).show(); + } + break; + } + } + }); + AlertDialog alertDialog = alertDialogBuilder.create(); + + return alertDialog; + } + + /*private AlertDialog getActionStartActivityDialog1(final Context myContext) + { + AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(context); + alertDialogBuilder.setTitle(myContext.getResources().getString(R.string.selectApplication)); + final String[] applicationArray = ActivityManageStartActivity.getApplicationNameListString(ActivityManageSpecificRule.this); + alertDialogBuilder.setItems(applicationArray, new DialogInterface.OnClickListener() + { + @Override + public void onClick(DialogInterface dialog, int which) + { + getActionStartActivityDialog2(myContext, applicationArray[which]).show(); + } + }); + AlertDialog alertDialog = alertDialogBuilder.create(); + + return alertDialog; + } + private AlertDialog getActionStartActivityDialog2(final Context myContext, String applicationName) + { + AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(context); + alertDialogBuilder.setTitle(myContext.getResources().getString(R.string.selectPackageOfApplication)); + final String[] packageArray = ActivityManageStartActivity.getPackageListString(ActivityManageSpecificRule.this, applicationName); + alertDialogBuilder.setItems(packageArray, new DialogInterface.OnClickListener() + { + @Override + public void onClick(DialogInterface dialog, int which) + { + getActionStartActivityDialog3(ActivityManageSpecificRule.this, packageArray[which]).show(); + } + }); + AlertDialog alertDialog = alertDialogBuilder.create(); + + return alertDialog; + } + private AlertDialog getActionStartActivityDialog3(final Context myContext, final String packageName) + { + AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(context); + alertDialogBuilder.setTitle(myContext.getResources().getString(R.string.selectActivityToBeStarted)); + final String activityArray[] = ActivityManageStartActivity.getActivityListForPackageName(packageName); + alertDialogBuilder.setItems(activityArray, new DialogInterface.OnClickListener() + { + @Override + public void onClick(DialogInterface dialog, int which) + { + ActivityInfo ai = ActivityManageStartActivity.getActivityInfoForPackageNameAndActivityName(packageName, activityArray[which]); +// Log.i("Selected", ai.packageName + " / " + ai.name); + newAction.setParameter2(ai.packageName + ";" + ai.name); + newAction.toString(); + ruleToEdit.getActionSet().add(newAction); + refreshActionList(); + } + }); + AlertDialog alertDialog = alertDialogBuilder.create(); + + return alertDialog; + }*/ + + private AlertDialog getActionWaitBeforeNextActionDialog(final Context myContext) + { + AlertDialog.Builder alertDialog = new AlertDialog.Builder(this); + + alertDialog.setTitle(myContext.getResources().getString(R.string.waitBeforeNextAction)); + alertDialog.setMessage(myContext.getResources().getString(R.string.waitBeforeNextActionEnterValue)); + + // Set an EditText view to get user input + final EditText input = new EditText(this); + input.setInputType(InputType.TYPE_CLASS_NUMBER); + alertDialog.setView(input); + + alertDialog.setPositiveButton("Ok", new DialogInterface.OnClickListener() + { + public void onClick(DialogInterface dialog, int whichButton) + { + if(input.getText().toString().length() == 0| input.getText().toString().equals("0") | input.getText().toString().contains(",") | input.getText().toString().contains(".")) + { + Toast.makeText(myContext, ActivityManageSpecificRule.this.getResources().getString(R.string.enterAPositiveValidNonDecimalNumber), Toast.LENGTH_LONG).show(); + getActionWaitBeforeNextActionDialog(ActivityManageSpecificRule.this).show(); + } + else + { + newAction.setParameter2(input.getText().toString()); + newAction.toString(); + ruleToEdit.getActionSet().add(newAction); + refreshActionList(); + } + } + }); + + alertDialog.setNegativeButton(myContext.getResources().getString(R.string.cancel), new DialogInterface.OnClickListener() + { + public void onClick(DialogInterface dialog, int whichButton) + { + // Canceled. + } + }); + + return alertDialog.create(); + } + + private AlertDialog getActionWakeupDeviceDialog(final Context myContext) + { + AlertDialog.Builder alertDialog = new AlertDialog.Builder(this); + + alertDialog.setTitle(myContext.getResources().getString(R.string.wakeupDevice)); + alertDialog.setMessage(myContext.getResources().getString(R.string.wakeupDeviceValue)); + + // Set an EditText view to get user input + final EditText input = new EditText(this); + input.setInputType(InputType.TYPE_CLASS_NUMBER); + alertDialog.setView(input); + + alertDialog.setPositiveButton("Ok", new DialogInterface.OnClickListener() + { + public void onClick(DialogInterface dialog, int whichButton) + { + if(input.getText().toString().length() == 0| input.getText().toString().contains(",") | input.getText().toString().contains(".")) + { + Toast.makeText(myContext, ActivityManageSpecificRule.this.getResources().getString(R.string.enterAPositiveValidNonDecimalNumber), Toast.LENGTH_LONG).show(); + getActionWakeupDeviceDialog(ActivityManageSpecificRule.this).show(); + } + else + { + newAction.setParameter2(input.getText().toString()); + newAction.toString(); + ruleToEdit.getActionSet().add(newAction); + refreshActionList(); + } + } + }); + + alertDialog.setNegativeButton(myContext.getResources().getString(R.string.cancel), new DialogInterface.OnClickListener() + { + public void onClick(DialogInterface dialog, int whichButton) + { + // Canceled. + } + }); + + return alertDialog.create(); + } + + private AlertDialog getActionParameter1Dialog(final Context myContext) + { + AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(context); + alertDialogBuilder.setTitle(myContext.getResources().getString(R.string.selectToggleDirection)); + final String choices[] = { myContext.getString(R.string.activate), myContext.getString(R.string.deactivate) }; + alertDialogBuilder.setItems(choices, new DialogInterface.OnClickListener() + { + @Override + public void onClick(DialogInterface dialog, int which) + { + if(which == 0) + newAction.setParameter1(true); + else + newAction.setParameter1(false); + + ruleToEdit.getActionSet().add(newAction); + refreshActionList(); + } + }); + AlertDialog alertDialog = alertDialogBuilder.create(); + + return alertDialog; + } + + protected void refreshTriggerList() + { + Miscellaneous.logEvent("i", "ListView", "Attempting to update TriggerListView", 4); + if(triggerListView.getAdapter() == null) + { + triggerListView.setAdapter(triggerListViewAdapter); + } + triggerListViewAdapter.notifyDataSetChanged(); + } + protected void refreshActionList() + { + Miscellaneous.logEvent("i", "ListView", "Attempting to update ActionListView", 4); + if(actionListView.getAdapter() == null) + { + actionListView.setAdapter(actionListViewAdapter); + } + actionListViewAdapter.notifyDataSetChanged(); + } + + public static class Item + { + public final String text; + public final int icon; + + public Item(String text, Integer icon) + { + this.text = text; + this.icon = icon; + } + + @Override + public String toString() { + return text; + } + } + + protected void hideKeyboard() + { + View view = this.getCurrentFocus(); + if (view != null) + { + InputMethodManager imm = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE); + imm.hideSoftInputFromWindow(view.getWindowToken(), 0); + } + } +} diff --git a/app/src/main/java/com/jens/automation2/ActivityManageStartActivity.java b/app/src/main/java/com/jens/automation2/ActivityManageStartActivity.java new file mode 100644 index 0000000..f7f29a3 --- /dev/null +++ b/app/src/main/java/com/jens/automation2/ActivityManageStartActivity.java @@ -0,0 +1,500 @@ +package com.jens.automation2; + +import android.app.Activity; +import android.app.AlertDialog; +import android.app.ProgressDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.os.AsyncTask; +import android.os.Bundle; +import android.text.InputType; +import android.view.MotionEvent; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.View.OnTouchListener; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemLongClickListener; +import android.widget.AdapterView.OnItemSelectedListener; +import android.widget.ArrayAdapter; +import android.widget.Button; +import android.widget.EditText; +import android.widget.ListView; +import android.widget.Spinner; +import android.widget.TextView; +import android.widget.Toast; + +import com.jens.automation2.Action.Action_Enum; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +public class ActivityManageStartActivity extends Activity +{ + ListView lvIntentPairs; + EditText etParameterName, etParameterValue; + Button bSelectApp, bAddIntentPair, bSaveActionStartOtherActivity; + Spinner spinnerParameterType; + TextView tvSelectedActivity; + boolean edit = false; + ProgressDialog progressDialog = null; + + private class CustomPackageInfo extends PackageInfo implements Comparable + { + @Override + public int compareTo(CustomPackageInfo another) + { + String name1 = ""; + String name2 = ""; + + ApplicationInfo aInfo1 = this.applicationInfo; + if (aInfo1 != null) + { + name1 = (String) ActivityManageStartActivity.this.getPackageManager().getApplicationLabel(aInfo1); + } + ApplicationInfo aInfo2 = another.applicationInfo; + if (aInfo2 != null) + { + name2 = (String) ActivityManageStartActivity.this.getPackageManager().getApplicationLabel(aInfo2); + } + + return name1.compareTo(name2); + } + + } + + private static List pInfos = null; + public static Action resultingAction; + + private static final String[] supportedIntentTypes = { "boolean", "byte", "char", "double", "float", "int", "long", "short", "String" }; + private ArrayList intentPairList = new ArrayList(); + + ArrayAdapter intentTypeSpinnerAdapter, intentPairAdapter; + + public static void getActivityList(final Context context) + { + if(pInfos == null) + { + pInfos = context.getPackageManager().getInstalledPackages(PackageManager.GET_ACTIVITIES); + Collections.sort(pInfos, new Comparator() + { + public int compare(PackageInfo obj1, PackageInfo obj2) + { + String name1 = ""; + String name2 = ""; + + ApplicationInfo aInfo1 = obj1.applicationInfo; + if (aInfo1 != null) + { + name1 = (String) context.getPackageManager().getApplicationLabel(aInfo1); + } + ApplicationInfo aInfo2 = obj2.applicationInfo; + if (aInfo2 != null) + { + name2 = (String) context.getPackageManager().getApplicationLabel(aInfo2); + } + + return name1.compareTo(name2); + } + }); + } + } + + public static String[] getApplicationNameListString(Context myContext) + { + // Generate the actual list + getActivityList(myContext); + + ArrayList returnList = new ArrayList(); + + for (PackageInfo pInfo : pInfos) + { + ApplicationInfo aInfo = pInfo.applicationInfo; + if (aInfo != null) + { + String aLabel; + + aLabel = (String) myContext.getPackageManager().getApplicationLabel(aInfo); + + ActivityInfo[] aInfos = pInfo.activities; + if (aInfos != null && aInfos.length > 0) // Only put Applications into the list that have packages. + { + if(!returnList.contains(aLabel)) + returnList.add(aLabel); + } + } + } + + return returnList.toArray(new String[returnList.size()]); + } + + public static String[] getPackageListString(Context myContext, String applicationLabel) + { + // Generate the actual list + getActivityList(myContext); + + ArrayList returnList = new ArrayList(); + + for (PackageInfo pInfo : pInfos) + { + if(myContext.getPackageManager().getApplicationLabel(pInfo.applicationInfo).equals(applicationLabel)) + { + ActivityInfo[] aInfos = pInfo.activities; + if (aInfos != null && aInfos.length > 0) + { + returnList.add(pInfo.packageName); + } + } + } + + return returnList.toArray(new String[returnList.size()]); + } + + public static String[] getPackageListString(Context myContext) + { + // Generate the actual list + getActivityList(myContext); + + ArrayList returnList = new ArrayList(); + + for (PackageInfo pInfo : pInfos) + { + ActivityInfo[] aInfos = pInfo.activities; + if (aInfos != null && aInfos.length > 0) + { + returnList.add(pInfo.packageName); + } + else + Miscellaneous.logEvent("w", "Empty Application", "Application " + myContext.getPackageManager().getApplicationLabel(pInfo.applicationInfo) + " doesn\'t have packages.", 5); + } + + return returnList.toArray(new String[returnList.size()]); + } + + public static String[] getActivityListForPackageName(String packageName) + { + ArrayList returnList = new ArrayList(); + + for (PackageInfo pInfo : pInfos) + { + if(pInfo.packageName.equals(packageName)) + { + ActivityInfo[] aInfos = pInfo.activities; + if (aInfos != null) + { + for (ActivityInfo activityInfo : aInfos) + { + returnList.add(activityInfo.name); + } + } + } + } + + return returnList.toArray(new String[returnList.size()]); + } + + public static ActivityInfo getActivityInfoForPackageNameAndActivityName(String packageName, String activityName) + { + for (PackageInfo pInfo : pInfos) + { + if(pInfo.packageName.equals(packageName)) + { + ActivityInfo[] aInfos = pInfo.activities; + if (aInfos != null) + { + for (ActivityInfo activityInfo : aInfos) + { + if(activityInfo.name.equals(activityName)) + return activityInfo; + } + } + } + } + + return null; + } + + private AlertDialog getActionStartActivityDialog1() + { + AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(this); + alertDialogBuilder.setTitle(getResources().getString(R.string.selectApplication)); + final String[] applicationArray = ActivityManageStartActivity.getApplicationNameListString(this); + alertDialogBuilder.setItems(applicationArray, new DialogInterface.OnClickListener() + { + @Override + public void onClick(DialogInterface dialog, int which) + { + dialog.dismiss(); + getActionStartActivityDialog2(applicationArray[which]).show(); + } + }); + AlertDialog alertDialog = alertDialogBuilder.create(); + + return alertDialog; + } + private AlertDialog getActionStartActivityDialog2(String applicationName) + { + AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(this); + alertDialogBuilder.setTitle(getResources().getString(R.string.selectPackageOfApplication)); + final String[] packageArray = ActivityManageStartActivity.getPackageListString(this, applicationName); + alertDialogBuilder.setItems(packageArray, new DialogInterface.OnClickListener() + { + @Override + public void onClick(DialogInterface dialog, int which) + { + getActionStartActivityDialog3(packageArray[which]).show(); + Miscellaneous.messageBox(getResources().getString(R.string.hint), getResources().getString(R.string.chooseActivityHint), ActivityManageStartActivity.this).show(); + + } + }); + AlertDialog alertDialog = alertDialogBuilder.create(); + + return alertDialog; + } + private AlertDialog getActionStartActivityDialog3(final String packageName) + { + AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(this); + alertDialogBuilder.setTitle(getResources().getString(R.string.selectActivityToBeStarted)); + final String activityArray[] = ActivityManageStartActivity.getActivityListForPackageName(packageName); + alertDialogBuilder.setItems(activityArray, new DialogInterface.OnClickListener() + { + @Override + public void onClick(DialogInterface dialog, int which) + { + ActivityInfo ai = ActivityManageStartActivity.getActivityInfoForPackageNameAndActivityName(packageName, activityArray[which]); + tvSelectedActivity.setText(ai.packageName + ";" + ai.name); + } + }); + AlertDialog alertDialog = alertDialogBuilder.create(); + + return alertDialog; + } + + @Override + protected void onCreate(Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + setContentView(R.layout.action_start_activity); + + lvIntentPairs = (ListView)findViewById(R.id.lvIntentPairs); + etParameterName = (EditText)findViewById(R.id.etParameterName); + etParameterValue = (EditText)findViewById(R.id.etParameterValue); + bSelectApp = (Button)findViewById(R.id.bSelectApp); + bAddIntentPair = (Button)findViewById(R.id.bAddIntentPair); + bSaveActionStartOtherActivity = (Button)findViewById(R.id.bSaveActionStartOtherActivity); + spinnerParameterType = (Spinner)findViewById(R.id.spinnerParameterType); + tvSelectedActivity = (TextView)findViewById(R.id.tvSelectedActivity); + + intentTypeSpinnerAdapter = new ArrayAdapter(this, R.layout.text_view_for_poi_listview_mediumtextsize, ActivityManageStartActivity.supportedIntentTypes); + spinnerParameterType.setAdapter(intentTypeSpinnerAdapter); + intentTypeSpinnerAdapter.notifyDataSetChanged(); + + intentPairAdapter = new ArrayAdapter(this, R.layout.text_view_for_poi_listview_smalltextsize, intentPairList); + + bSelectApp.setOnClickListener(new OnClickListener() + { + @Override + public void onClick(View v) + { + GetActivityListTask getActivityListTask = new GetActivityListTask(); + getActivityListTask.execute(); + progressDialog = ProgressDialog.show(ActivityManageStartActivity.this, "", ActivityManageStartActivity.this.getResources().getString(R.string.gettingListOfInstalledApplications)); + } + }); + + bAddIntentPair.setOnClickListener(new OnClickListener() + { + @Override + public void onClick(View v) + { + // type;name;value + if(spinnerParameterType.getSelectedItem().toString().length() == 0) + { + Toast.makeText(ActivityManageStartActivity.this, getResources().getString(R.string.selectTypeOfIntentPair), Toast.LENGTH_LONG).show(); + return; + } + + if(etParameterName.getText().toString().length() == 0) + { + Toast.makeText(ActivityManageStartActivity.this, getResources().getString(R.string.enterNameForIntentPair), Toast.LENGTH_LONG).show(); + return; + } + + if(etParameterValue.getText().toString().length() == 0) + { + Toast.makeText(ActivityManageStartActivity.this, getResources().getString(R.string.enterValueForIntentPair), Toast.LENGTH_LONG).show(); + return; + } + + String param = supportedIntentTypes[spinnerParameterType.getSelectedItemPosition()] + "/" + etParameterName.getText().toString() + "/" + etParameterValue.getText().toString(); + intentPairList.add(param); + + spinnerParameterType.setSelection(0); + etParameterName.setText(""); + etParameterValue.setText(""); + + updateIntentPairList(); + } + }); + + lvIntentPairs.setOnItemLongClickListener(new OnItemLongClickListener() + { + @Override + public boolean onItemLongClick(AdapterView arg0, View arg1, int arg2, long arg3) + { + getIntentPairDialog(arg2).show(); + return false; + } + }); + + bSaveActionStartOtherActivity.setOnClickListener(new OnClickListener() + { + @Override + public void onClick(View v) + { + if(saveAction()) + { + ActivityManageStartActivity.this.setResult(RESULT_OK); + finish(); + } + } + }); + + lvIntentPairs.setOnTouchListener(new OnTouchListener() + { + @Override + public boolean onTouch(View v, MotionEvent event) + { + v.getParent().requestDisallowInterceptTouchEvent(true); + return false; + } + }); + + spinnerParameterType.setOnItemSelectedListener(new OnItemSelectedListener() + { + @Override + public void onItemSelected(AdapterView arg0, View arg1, int arg2, long arg3) + { + if(supportedIntentTypes[arg2].equals("double") | supportedIntentTypes[arg2].equals("float") | supportedIntentTypes[arg2].equals("int") | supportedIntentTypes[arg2].equals("long") | supportedIntentTypes[arg2].equals("short")) + ActivityManageStartActivity.this.etParameterValue.setInputType(InputType.TYPE_CLASS_NUMBER); + else + ActivityManageStartActivity.this.etParameterValue.setInputType(InputType.TYPE_CLASS_TEXT); + } + + @Override + public void onNothingSelected(AdapterView arg0) + { + // TODO Auto-generated method stub + + } + }); + + Intent i = getIntent(); + if(i.getBooleanExtra("edit", false) == true) + { + edit = true; + loadValuesIntoGui(); + } + } + + private void loadValuesIntoGui() + { + String[] params = resultingAction.getParameter2().split(";"); + if(params.length >= 2) + { + tvSelectedActivity.setText(params[0] + ";" + params[1]); + + if(params.length > 2) + { + intentPairList.clear(); + + for(int i=2; i + { + @Override + protected Void doInBackground(Void... params) + { + getActivityList(ActivityManageStartActivity.this); + return null; + } + + @Override + protected void onPostExecute(Void result) + { + progressDialog.dismiss(); + getActionStartActivityDialog1().show(); + } + + + } +} diff --git a/app/src/main/java/com/jens/automation2/ActivityManageTimeFrame.java b/app/src/main/java/com/jens/automation2/ActivityManageTimeFrame.java new file mode 100644 index 0000000..b6c8320 --- /dev/null +++ b/app/src/main/java/com/jens/automation2/ActivityManageTimeFrame.java @@ -0,0 +1,163 @@ +package com.jens.automation2; + +import android.app.Activity; +import android.os.Bundle; +import android.view.View; +import android.view.View.OnClickListener; +import android.widget.Button; +import android.widget.CheckBox; +import android.widget.RadioButton; +import android.widget.TimePicker; +import android.widget.Toast; + +import java.sql.Time; +import java.util.ArrayList; +import java.util.Calendar; + +public class ActivityManageTimeFrame extends Activity +{ + Button bSaveTimeFrame; + TimePicker startPicker, stopPicker; + CheckBox checkMonday, checkTuesday, checkWednesday, checkThursday, checkFriday, checkSaturday, checkSunday; + RadioButton radioTimeFrameEntering, radioTimeFrameLeaving; + + public static Trigger editedTimeFrameTrigger = null; + + @Override + protected void onCreate(Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + setContentView(R.layout.trigger_timeframe_editor); + + startPicker = (TimePicker)findViewById(R.id.tpTimeFrameStart); + stopPicker = (TimePicker)findViewById(R.id.tpTimeFrameStop); + startPicker.setIs24HourView(true); + stopPicker.setIs24HourView(true); + + bSaveTimeFrame = (Button)findViewById(R.id.bSaveTimeFrame); + checkMonday = (CheckBox)findViewById(R.id.checkMonday); + checkTuesday = (CheckBox)findViewById(R.id.checkTuesday); + checkWednesday = (CheckBox)findViewById(R.id.checkWednesday); + checkThursday = (CheckBox)findViewById(R.id.checkThursday); + checkFriday = (CheckBox)findViewById(R.id.checkFriday); + checkSaturday = (CheckBox)findViewById(R.id.checkSaturday); + checkSunday = (CheckBox)findViewById(R.id.checkSunday); + radioTimeFrameEntering = (RadioButton)findViewById(R.id.radioTimeFrameEntering); + radioTimeFrameLeaving = (RadioButton)findViewById(R.id.radioTimeFrameLeaving); + + bSaveTimeFrame.setOnClickListener(new OnClickListener() + { + @Override + public void onClick(View v) + { + Time startTime = new Time(0); + startTime.setHours(startPicker.getCurrentHour()); + startTime.setMinutes(startPicker.getCurrentMinute()); + + Time stopTime = new Time(0); + stopTime.setHours(stopPicker.getCurrentHour()); + stopTime.setMinutes(stopPicker.getCurrentMinute()); + + ArrayList dayList = new ArrayList(); + if(checkMonday.isChecked()) + dayList.add(Calendar.MONDAY); + if(checkTuesday.isChecked()) + dayList.add(Calendar.TUESDAY); + if(checkWednesday.isChecked()) + dayList.add(Calendar.WEDNESDAY); + if(checkThursday.isChecked()) + dayList.add(Calendar.THURSDAY); + if(checkFriday.isChecked()) + dayList.add(Calendar.FRIDAY); + if(checkSaturday.isChecked()) + dayList.add(Calendar.SATURDAY); + if(checkSunday.isChecked()) + dayList.add(Calendar.SUNDAY); + + if( + !checkMonday.isChecked() + && + !checkTuesday.isChecked() + && + !checkWednesday.isChecked() + && + !checkThursday.isChecked() + && + !checkFriday.isChecked() + && + !checkSaturday.isChecked() + && + !checkSunday.isChecked() + ) + { + Toast.makeText(getBaseContext(), getResources().getString(R.string.selectOneDay), Toast.LENGTH_LONG).show(); + return; + } + + if(editedTimeFrameTrigger.getTimeFrame() == null) + // add new one + editedTimeFrameTrigger.setTimeFrame(new TimeFrame(startTime, stopTime, dayList)); + else + { + // edit one + editedTimeFrameTrigger.getTimeFrame().setTriggerTimeStart(startTime); + editedTimeFrameTrigger.getTimeFrame().setTriggerTimeStop(stopTime); + editedTimeFrameTrigger.getTimeFrame().getDayList().clear(); + editedTimeFrameTrigger.getTimeFrame().setDayList(dayList); + } + + editedTimeFrameTrigger.setTriggerParameter(radioTimeFrameEntering.isChecked()); + + setResult(RESULT_OK); + finish(); + } + }); + + if(editedTimeFrameTrigger.getTimeFrame() != null) + loadVariableIntoGui(); + } + + private void loadVariableIntoGui() + { + startPicker.setCurrentHour(editedTimeFrameTrigger.getTimeFrame().getTriggerTimeStart().getHours()); + startPicker.setCurrentMinute(editedTimeFrameTrigger.getTimeFrame().getTriggerTimeStart().getMinutes()); + + stopPicker.setCurrentHour(editedTimeFrameTrigger.getTimeFrame().getTriggerTimeStop().getHours()); + stopPicker.setCurrentMinute(editedTimeFrameTrigger.getTimeFrame().getTriggerTimeStop().getMinutes()); + + radioTimeFrameEntering.setChecked(editedTimeFrameTrigger.getTriggerParameter()); + radioTimeFrameLeaving.setChecked(!editedTimeFrameTrigger.getTriggerParameter()); + + for(int day : editedTimeFrameTrigger.getTimeFrame().getDayList()) + { + switch(day) + { + case Calendar.MONDAY: + checkMonday.setChecked(true); + break; + case Calendar.TUESDAY: + checkTuesday.setChecked(true); + break; + case Calendar.WEDNESDAY: + checkWednesday.setChecked(true); + break; + case Calendar.THURSDAY: + checkThursday.setChecked(true); + break; + case Calendar.FRIDAY: + checkFriday.setChecked(true); + break; + case Calendar.SATURDAY: + checkSaturday.setChecked(true); + break; + case Calendar.SUNDAY: + checkSunday.setChecked(true); + break; + default: + Miscellaneous.logEvent("w", "TimeFrame", "Daylist contains invalid day: " + String.valueOf(day), 4); + break; + } + } + } + +} diff --git a/app/src/main/java/com/jens/automation2/ActivityPermissions.java b/app/src/main/java/com/jens/automation2/ActivityPermissions.java new file mode 100644 index 0000000..d509ba2 --- /dev/null +++ b/app/src/main/java/com/jens/automation2/ActivityPermissions.java @@ -0,0 +1,1370 @@ +package com.jens.automation2; + +import android.app.Activity; +import android.app.NotificationManager; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.text.Html; +import android.util.Log; +import android.view.View; +import android.widget.Button; +import android.widget.TextView; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +public class ActivityPermissions extends Activity +{ + static Map mapGeneralPermissions = null; + static Map mapTriggerPermissions = null; + static Map mapActionPermissions = null; + + public static final int notificationIdPermissions = 1001; + + public static final int requestCodeForPermissions = 12042; + private static final int requestCodeForPermissionsWriteSettings = 12043; + private static final int requestCodeForPermissionsNotificationPolicy = 12044; + protected String[] specificPermissionsToRequest = null; + + public static String intentExtraName = "permissionsToBeRequested"; + + Button bCancelPermissions, bRequestPermissions; + TextView tvPermissionsExplanation, tvPermissionosExplanationSystemSettings, tvPermissionsExplanationLong; + static ActivityPermissions instance = null; + + public static final String writeSystemSettingsPermissionName = "android.permission.WRITE_SETTINGS"; + public static final String writeExternalStoragePermissionName = "android.permission.WRITE_EXTERNAL_STORAGE"; + public static final String accessNotificationPolicyPermissionName = "android.permission.ACCESS_NOTIFICATION_POLICY"; + public static final String permissionNameLocationFine = "android.permission.ACCESS_FINE_LOCATION"; + public static final String permissionNameLocationCoarse = "android.permission.ACCESS_COARSE_LOCATION"; + public static final String permissionNameLocationBackground = "android.permission.ACCESS_BACKGROUND_LOCATION"; + public static final String permissionNameCall = "android.permission.PROCESS_OUTGOING_CALLS"; + public static final String permissionNameStartService = "android.permission.FOREGROUND_SERVICE"; + + public static ActivityPermissions getInstance() + { + if(instance == null) + instance = new ActivityPermissions(); + + return instance; + } + + @Override + protected void onCreate(Bundle savedInstanceState) + { + instance = this; + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) + { + super.onCreate(savedInstanceState); + setContentView(R.layout.permissions_activity); + + bCancelPermissions = (Button)findViewById(R.id.bCancelPermissions); + bRequestPermissions = (Button)findViewById(R.id.bRequestPermissions); + tvPermissionsExplanation = (TextView)findViewById(R.id.tvPermissionsExplanation); + tvPermissionosExplanationSystemSettings = (TextView)findViewById(R.id.tvPermissionsExplanationSystemSettings); + tvPermissionsExplanationLong = (TextView)findViewById(R.id.tvPermissionsExplanationLong); + + bCancelPermissions.setOnClickListener(new View.OnClickListener() + { + @Override + public void onClick(View v) + { + finish(); + } + }); + + bRequestPermissions.setOnClickListener(new View.OnClickListener() + { + @Override + public void onClick(View v) + { + finish(); + } + }); + bRequestPermissions.setOnClickListener(new View.OnClickListener() + { + @Override + public void onClick(View v) + { + // Request the basic permissions, that are absolutely required. + //getRequiredPermissions(true); // request permissions to access sd card access and "receive boot completed" + + //fillPermissionMaps(); + + + if(specificPermissionsToRequest != null) + requestSpecificPermission(specificPermissionsToRequest); + else + { + ArrayList list = new ArrayList(); + for(String s : getRequiredPermissions(false)) + list.add(s); + requestPermissions(list, false); // request all other permissions, based on the rules setup + } + } + }); + + if(savedInstanceState == null) + { + Bundle extras = getIntent().getExtras(); + if(extras != null) + { + specificPermissionsToRequest = extras.getStringArray(ActivityPermissions.intentExtraName);; +// requestSpecificPermission(permissionsToRequest); + tvPermissionsExplanationLong.setText(R.string.permissionsExplanationSmall); + tvPermissionsExplanation.setText(""); + fillExplanationText(); + return; + } + } + + // Don't have to request specific permissions, so search for generally remaining ones. + if(needMorePermissions(ActivityPermissions.this)) + { + fillExplanationText(); + } + else + { + setHaveAllPermissions(); + finish(); + } + } + else + finish(); + } + + protected void fillExplanationText() + { + StringBuilder explanation = new StringBuilder(); + String[] requiredPerms; + if(specificPermissionsToRequest != null) + requiredPerms = specificPermissionsToRequest; + else + requiredPerms = getRequiredPermissions(false); + + boolean locationPermissionExplained = false; + + for(String s : requiredPerms) + { + /* + Filter location permission and only name it once + */ + if(s.equals(permissionNameLocationCoarse) | s.equals(permissionNameLocationFine)) + { + if(!locationPermissionExplained) + { + explanation.append( + + "
" + + + + "" + + getResources().getString(R.string.readLocation) + + "" + + + "
" + ); + + for (String reason : getReasonForPermission(s)) + explanation.append(reason + "
"); + + locationPermissionExplained = true; + } + } + else + { + explanation.append( + + "
" + + + + "" + + getResources().getString(getResources().getIdentifier(s, "string", getPackageName())) + + "" + + + "
"); + + for (String reason : getReasonForPermission(s)) + explanation.append(reason + "
"); + } + } + + tvPermissionsExplanation.setText(Html.fromHtml(explanation.toString())); + + for(String s : requiredPerms) + { + if (s.equalsIgnoreCase(writeSystemSettingsPermissionName)) + { + if (requiredPerms.length == 1) + tvPermissionosExplanationSystemSettings.setText(getResources().getString(R.string.systemSettingsNote1)); + else if (requiredPerms.length > 1) + tvPermissionosExplanationSystemSettings.setText(getResources().getString(R.string.systemSettingsNote1) + getResources().getString(R.string.systemSettingsNote2)); + + break; + } + } + + ActivityMainScreen.updateMainScreen(); + } + + protected static void addToArrayListUnique(String value, ArrayList list) + { + if (!list.contains(value)) + list.add(value); + } + + public static boolean needMorePermissions(Context context) + { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) + { + for (String s : getRequiredPermissions(false)) + { + if (!havePermission(s, context)) + return true; + } + } + + return false; + } + + public static boolean havePermission(String s, Context context) + { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) + { + if(s.equals(writeSystemSettingsPermissionName)) + return android.provider.Settings.System.canWrite(context); + else if (s.equals(accessNotificationPolicyPermissionName)) + { + if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) + { + NotificationManager mNotificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); + return mNotificationManager.isNotificationPolicyAccessGranted(); + } + else + return true; + } + else + { + int res = context.checkCallingOrSelfPermission(s); + return (res == PackageManager.PERMISSION_GRANTED); + } + } + else + return true; + } + + public static String[] getRequiredPermissions(boolean onlyGeneral) + { + ArrayList requiredPermissions = new ArrayList(); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) + { +// Toast.makeText(Miscellaneous.getAnyContext(), "Checking permissions.", Toast.LENGTH_LONG).show(); + + Context workingContext = Miscellaneous.getAnyContext(); + + // We do not need to ask for RECEIVE_BOOT_COMPLETED permission. It is always granted. + /* + if(!havePermission("android.permission.RECEIVE_BOOT_COMPLETED", workingContext)) + addToArrayListUnique("android.permission.RECEIVE_BOOT_COMPLETED", requiredPermissions); + */ + + if (!havePermission("android.permission.WRITE_EXTERNAL_STORAGE", workingContext)) + addToArrayListUnique("android.permission.WRITE_EXTERNAL_STORAGE", requiredPermissions); + + for(Profile profile : Profile.getProfileCollection()) + { + if(profile.changeIncomingCallsRingtone) + { + addToArrayListUnique("android.permission.WRITE_SETTINGS", requiredPermissions); + } + } + + if (!onlyGeneral) + { + for (Rule rule : Rule.getRuleCollection()) + { + for (String singlePermission : getPermissionsForRule(rule)) + if (!havePermission(singlePermission, workingContext)) + addToArrayListUnique(singlePermission, requiredPermissions); + } + } + + /* + Not all permissions need to be asked for. + */ + + /*if(shouldShowRequestPermissionRationale("android.permission.RECORD_AUDIO")) + Toast.makeText(ActivityMainScreen.this, "shouldShowRequestPermissionRationale", Toast.LENGTH_LONG).show(); + else + Toast.makeText(ActivityMainScreen.this, "not shouldShowRequestPermissionRationale", Toast.LENGTH_LONG).show();*/ + +// addToArrayListUnique("Manifest.permission.RECORD_AUDIO", requiredPermissions); + /*int hasPermission = checkSelfPermission(Manifest.permission.RECORD_AUDIO); + if (hasPermission == PackageManager.PERMISSION_DENIED) + { + Toast.makeText(ActivityMainScreen.this, "Don't have record_audio. Requesting...", Toast.LENGTH_LONG).show(); +// requestPermissions(new String[]{"Manifest.permission.CAMERA"}, requestCodeForPermissions); + ActivityCompat.requestPermissions(ActivityMainScreen.this, new String[]{"Manifest.permission.CAMERA"}, requestCodeForPermissions); + } + else + Toast.makeText(ActivityMainScreen.this, "Have record_audio.", Toast.LENGTH_LONG).show();*/ + + } + + return requiredPermissions.toArray(new String[requiredPermissions.size()]); + } + + public static boolean havePermissionsForRule(Rule rule, Context context) + { + for(String perm : getPermissionsForRule(rule)) + { + if(!havePermission(perm, context)) + return false; + } + + return true; + } + + protected static ArrayList getPermissionsForRule(Rule rule) + { + ArrayList requiredPermissions = new ArrayList<>(); + + if (rule.isRuleActive()) + { + for (Trigger trigger : rule.getTriggerSet()) + { + switch (trigger.getTriggerType()) + { + case activityDetection: + addToArrayListUnique("com.google.android.gms.permission.ACTIVITY_RECOGNITION", requiredPermissions); + addToArrayListUnique("android.permission.ACTIVITY_RECOGNITION", requiredPermissions); + break; + case airplaneMode: + addToArrayListUnique("android.permission.ACCESS_NETWORK_STATE", requiredPermissions); + break; + case batteryLevel: + addToArrayListUnique("android.permission.READ_PHONE_STATE", requiredPermissions); +// addToArrayListUnique("android.permission.BATTERY_STATS", requiredPermissions); + break; + case bluetoothConnection: + addToArrayListUnique("android.permission.BLUETOOTH_ADMIN", requiredPermissions); + addToArrayListUnique("android.permission.BLUETOOTH", requiredPermissions); + addToArrayListUnique("android.permission.ACCESS_NETWORK_STATE", requiredPermissions); + break; + case charging: + addToArrayListUnique("android.permission.READ_PHONE_STATE", requiredPermissions); +// addToArrayListUnique("android.permission.BATTERY_STATS", requiredPermissions); + break; + case headsetPlugged: + addToArrayListUnique("android.permission.READ_PHONE_STATE", requiredPermissions); + break; + case nfcTag: + addToArrayListUnique("android.permission.NFC", requiredPermissions); + break; + case noiseLevel: + addToArrayListUnique("android.permission.RECORD_AUDIO", requiredPermissions); + break; + case phoneCall: + addToArrayListUnique("android.permission.READ_PHONE_STATE", requiredPermissions); + addToArrayListUnique(permissionNameCall, requiredPermissions); + break; + case pointOfInterest: + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) + { + addToArrayListUnique(permissionNameLocationBackground, requiredPermissions); + addToArrayListUnique(permissionNameLocationFine, requiredPermissions); + addToArrayListUnique(permissionNameLocationCoarse, requiredPermissions); + } + else + { + addToArrayListUnique(permissionNameLocationFine, requiredPermissions); + addToArrayListUnique(permissionNameLocationCoarse, requiredPermissions); + } + addToArrayListUnique("android.permission.ACCESS_NETWORK_STATE", requiredPermissions); + addToArrayListUnique("android.permission.INTERNET", requiredPermissions); + addToArrayListUnique("android.permission.ACCESS_WIFI_STATE", requiredPermissions); + break; + case process_started_stopped: + addToArrayListUnique("android.permission.GET_TASKS", requiredPermissions); + break; + case roaming: + addToArrayListUnique("android.permission.ACCESS_NETWORK_STATE", requiredPermissions); + break; + case speed: + addToArrayListUnique(permissionNameLocationFine, requiredPermissions); + addToArrayListUnique(permissionNameLocationCoarse, requiredPermissions); + if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) + addToArrayListUnique(permissionNameLocationBackground, requiredPermissions); + addToArrayListUnique("android.permission.ACCESS_NETWORK_STATE", requiredPermissions); + addToArrayListUnique("android.permission.INTERNET", requiredPermissions); + break; + case timeFrame: + break; + case usb_host_connection: + addToArrayListUnique("android.permission.READ_PHONE_STATE", requiredPermissions); +// addToArrayListUnique("android.permission.BATTERY_STATS", requiredPermissions); + break; + case wifiConnection: + addToArrayListUnique("android.permission.ACCESS_NETWORK_STATE", requiredPermissions); + addToArrayListUnique("android.permission.ACCESS_WIFI_STATE", requiredPermissions); + break; + default: + break; + } + } + + for (Action action : rule.getActionSet()) + { + switch (action.getAction()) + { + case changeSoundProfile: + addToArrayListUnique("android.permission.MODIFY_AUDIO_SETTINGS", requiredPermissions); + if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) + addToArrayListUnique(accessNotificationPolicyPermissionName, requiredPermissions); + break; + case disableScreenRotation: + addToArrayListUnique(writeSystemSettingsPermissionName, requiredPermissions); + break; + case enableScreenRotation: + addToArrayListUnique(writeSystemSettingsPermissionName, requiredPermissions); + break; + case playMusic: + break; + case sendTextMessage: + addToArrayListUnique("android.permission.SEND_SMS", requiredPermissions); + break; + case setAirplaneMode: + addToArrayListUnique(writeSystemSettingsPermissionName, requiredPermissions); + addToArrayListUnique("android.permission.ACCESS_NETWORK_STATE", requiredPermissions); + addToArrayListUnique("android.permission.ACCESS_SUPERUSER", requiredPermissions); + addToArrayListUnique("android.permission.CHANGE_NETWORK_STATE", requiredPermissions); + break; + case setBluetooth: + addToArrayListUnique("android.permission.BLUETOOTH_ADMIN", requiredPermissions); + addToArrayListUnique("android.permission.BLUETOOTH", requiredPermissions); + addToArrayListUnique("android.permission.ACCESS_NETWORK_STATE", requiredPermissions); + addToArrayListUnique(writeSystemSettingsPermissionName, requiredPermissions); + break; + case setDataConnection: + addToArrayListUnique(writeSystemSettingsPermissionName, requiredPermissions); + addToArrayListUnique("android.permission.ACCESS_NETWORK_STATE", requiredPermissions); + addToArrayListUnique("android.permission.ACCESS_SUPERUSER", requiredPermissions); + addToArrayListUnique("android.permission.CHANGE_NETWORK_STATE", requiredPermissions); + break; + case setDisplayRotation: + addToArrayListUnique(writeSystemSettingsPermissionName, requiredPermissions); + break; + case setUsbTethering: + addToArrayListUnique(writeSystemSettingsPermissionName, requiredPermissions); + addToArrayListUnique("android.permission.CHANGE_NETWORK_STATE", requiredPermissions); + break; + case setWifi: + addToArrayListUnique(writeSystemSettingsPermissionName, requiredPermissions); + addToArrayListUnique("android.permission.CHANGE_NETWORK_STATE", requiredPermissions); + addToArrayListUnique("android.permission.ACCESS_NETWORK_STATE", requiredPermissions); + break; + case setWifiTethering: + addToArrayListUnique(writeSystemSettingsPermissionName, requiredPermissions); + addToArrayListUnique("android.permission.CHANGE_NETWORK_STATE", requiredPermissions); + addToArrayListUnique("android.permission.ACCESS_NETWORK_STATE", requiredPermissions); + break; + case speakText: + break; + case startOtherActivity: + break; + case triggerUrl: + addToArrayListUnique("android.permission.INTERNET", requiredPermissions); + // Hier m��te ein Hinweis kommen, da� nur die Variablen verwendet werden k�nnen, f�r die es Rechte gibt. + break; + case turnBluetoothOff: + addToArrayListUnique("android.permission.BLUETOOTH_ADMIN", requiredPermissions); + addToArrayListUnique(writeSystemSettingsPermissionName, requiredPermissions); + addToArrayListUnique("android.permission.BLUETOOTH", requiredPermissions); + addToArrayListUnique("android.permission.ACCESS_NETWORK_STATE", requiredPermissions); + break; + case turnBluetoothOn: + addToArrayListUnique("android.permission.BLUETOOTH_ADMIN", requiredPermissions); + addToArrayListUnique(writeSystemSettingsPermissionName, requiredPermissions); + addToArrayListUnique("android.permission.BLUETOOTH", requiredPermissions); + addToArrayListUnique("android.permission.ACCESS_NETWORK_STATE", requiredPermissions); + break; + case turnUsbTetheringOff: + addToArrayListUnique(writeSystemSettingsPermissionName, requiredPermissions); + addToArrayListUnique("android.permission.CHANGE_NETWORK_STATE", requiredPermissions); + break; + case turnUsbTetheringOn: + addToArrayListUnique(writeSystemSettingsPermissionName, requiredPermissions); + addToArrayListUnique("android.permission.CHANGE_NETWORK_STATE", requiredPermissions); + break; + case turnWifiOff: + addToArrayListUnique(writeSystemSettingsPermissionName, requiredPermissions); + addToArrayListUnique("android.permission.CHANGE_NETWORK_STATE", requiredPermissions); + addToArrayListUnique("android.permission.ACCESS_NETWORK_STATE", requiredPermissions); + break; + case turnWifiOn: + addToArrayListUnique(writeSystemSettingsPermissionName, requiredPermissions); + addToArrayListUnique("android.permission.CHANGE_NETWORK_STATE", requiredPermissions); + addToArrayListUnique("android.permission.ACCESS_NETWORK_STATE", requiredPermissions); + break; + case turnWifiTetheringOff: + addToArrayListUnique(writeSystemSettingsPermissionName, requiredPermissions); + addToArrayListUnique("android.permission.CHANGE_NETWORK_STATE", requiredPermissions); + addToArrayListUnique("android.permission.ACCESS_NETWORK_STATE", requiredPermissions); + break; + case turnWifiTetheringOn: + addToArrayListUnique(writeSystemSettingsPermissionName, requiredPermissions); + addToArrayListUnique("android.permission.CHANGE_NETWORK_STATE", requiredPermissions); + addToArrayListUnique("android.permission.ACCESS_NETWORK_STATE", requiredPermissions); + break; + case waitBeforeNextAction: + break; + case wakeupDevice: + addToArrayListUnique("android.permission.WAKE_LOCK", requiredPermissions); + break; + default: + break; + + } + } + } + + return requiredPermissions; + } + + protected ArrayList getRulesUsing(Trigger.Trigger_Enum triggerType) + { + ArrayList returnList = new ArrayList<>(); + + for (Rule rule : Rule.getRuleCollection()) + { + if (rule.isRuleActive()) + { + for (Trigger trigger : rule.getTriggerSet()) + { + if(trigger.getTriggerType().equals(triggerType)) + addToArrayListUnique(rule.getName(), returnList); + } + } + } + + return returnList; + } + + protected ArrayList getRulesUsing(Action.Action_Enum actionType) + { + ArrayList returnList = new ArrayList<>(); + + for (Rule rule : Rule.getRuleCollection()) + { + if (rule.isRuleActive()) + { + for (Action action : rule.getActionSet()) + { + if(action.equals(actionType)) + addToArrayListUnique(rule.getName(), returnList); + } + } + } + + return returnList; + } + + public ArrayList getReasonForPermission(String permission) + { + ArrayList usingElements = new ArrayList(); + + switch(permission) + { + case "android.permission.RECEIVE_BOOT_COMPLETED": + usingElements.add(getResources().getString(R.string.startAtSystemBoot)); + break; + case accessNotificationPolicyPermissionName: + usingElements.add(getResources().getString(R.string.actionChangeSoundProfile)); + break; + case "android.permission.WRITE_EXTERNAL_STORAGE": + usingElements.add(getResources().getString(R.string.storeSettings)); + break; + case "com.google.android.gms.permission.ACTIVITY_RECOGNITION": + for(String ruleName : getRulesUsing(Trigger.Trigger_Enum.activityDetection)) + usingElements.add(String.format(getResources().getString(R.string.ruleXrequiresThis), ruleName)); + + break; + case "android.permission.ACTIVITY_RECOGNITION": + for(String ruleName : getRulesUsing(Trigger.Trigger_Enum.activityDetection)) + usingElements.add(String.format(getResources().getString(R.string.ruleXrequiresThis), ruleName)); + + break; + case permissionNameLocationCoarse: +// usingElements.add(getResources().getString(R.string.android_permission_ACCESS_COARSE_LOCATION)); + usingElements.add(getResources().getString(R.string.manageLocations)); + for(String ruleName : getRulesUsing(Trigger.Trigger_Enum.pointOfInterest)) + usingElements.add(String.format(getResources().getString(R.string.ruleXrequiresThis), ruleName)); + for(String ruleName : getRulesUsing(Trigger.Trigger_Enum.speed)) + usingElements.add(String.format(getResources().getString(R.string.ruleXrequiresThis), ruleName)); + break; + case permissionNameLocationFine: + usingElements.add(getResources().getString(R.string.manageLocations)); + for(String ruleName : getRulesUsing(Trigger.Trigger_Enum.pointOfInterest)) + usingElements.add(String.format(getResources().getString(R.string.ruleXrequiresThis), ruleName)); + for(String ruleName : getRulesUsing(Trigger.Trigger_Enum.speed)) + usingElements.add(String.format(getResources().getString(R.string.ruleXrequiresThis), ruleName)); + break; + case permissionNameLocationBackground: + usingElements.add(getResources().getString(R.string.googleLocationChicanery)); + for(String ruleName : getRulesUsing(Trigger.Trigger_Enum.pointOfInterest)) + usingElements.add(String.format(getResources().getString(R.string.ruleXrequiresThis), ruleName)); + for(String ruleName : getRulesUsing(Trigger.Trigger_Enum.speed)) + usingElements.add(String.format(getResources().getString(R.string.ruleXrequiresThis), ruleName)); + break; + case "android.permission.ACCESS_NETWORK_STATE": + for(String ruleName : getRulesUsing(Trigger.Trigger_Enum.airplaneMode)) + usingElements.add(String.format(getResources().getString(R.string.ruleXrequiresThis), ruleName)); + for(String ruleName : getRulesUsing(Trigger.Trigger_Enum.bluetoothConnection)) + usingElements.add(String.format(getResources().getString(R.string.ruleXrequiresThis), ruleName)); + for(String ruleName : getRulesUsing(Trigger.Trigger_Enum.pointOfInterest)) + usingElements.add(String.format(getResources().getString(R.string.ruleXrequiresThis), ruleName)); + for(String ruleName : getRulesUsing(Trigger.Trigger_Enum.roaming)) + usingElements.add(String.format(getResources().getString(R.string.ruleXrequiresThis), ruleName)); + for(String ruleName : getRulesUsing(Trigger.Trigger_Enum.speed)) + usingElements.add(String.format(getResources().getString(R.string.ruleXrequiresThis), ruleName)); + for(String ruleName : getRulesUsing(Trigger.Trigger_Enum.wifiConnection)) + usingElements.add(String.format(getResources().getString(R.string.ruleXrequiresThis), ruleName)); + for(String ruleName : getRulesUsing(Action.Action_Enum.setAirplaneMode)) + usingElements.add(String.format(getResources().getString(R.string.ruleXrequiresThis), ruleName)); + for(String ruleName : getRulesUsing(Action.Action_Enum.setBluetooth)) + usingElements.add(String.format(getResources().getString(R.string.ruleXrequiresThis), ruleName)); + for(String ruleName : getRulesUsing(Action.Action_Enum.setDataConnection)) + usingElements.add(String.format(getResources().getString(R.string.ruleXrequiresThis), ruleName)); + for(String ruleName : getRulesUsing(Action.Action_Enum.setWifi)) + usingElements.add(String.format(getResources().getString(R.string.ruleXrequiresThis), ruleName)); + for(String ruleName : getRulesUsing(Action.Action_Enum.setWifiTethering)) + usingElements.add(String.format(getResources().getString(R.string.ruleXrequiresThis), ruleName)); + for(String ruleName : getRulesUsing(Action.Action_Enum.turnBluetoothOff)) + usingElements.add(String.format(getResources().getString(R.string.ruleXrequiresThis), ruleName)); + for(String ruleName : getRulesUsing(Action.Action_Enum.turnBluetoothOn)) + usingElements.add(String.format(getResources().getString(R.string.ruleXrequiresThis), ruleName)); + for(String ruleName : getRulesUsing(Action.Action_Enum.turnWifiOff)) + usingElements.add(String.format(getResources().getString(R.string.ruleXrequiresThis), ruleName)); + for(String ruleName : getRulesUsing(Action.Action_Enum.turnWifiOn)) + usingElements.add(String.format(getResources().getString(R.string.ruleXrequiresThis), ruleName)); + for(String ruleName : getRulesUsing(Action.Action_Enum.turnWifiTetheringOff)) + usingElements.add(String.format(getResources().getString(R.string.ruleXrequiresThis), ruleName)); + for(String ruleName : getRulesUsing(Action.Action_Enum.turnWifiTetheringOn)) + usingElements.add(String.format(getResources().getString(R.string.ruleXrequiresThis), ruleName)); + break; + case "android.permission.ACCESS_WIFI_STATE": + for(String ruleName : getRulesUsing(Trigger.Trigger_Enum.pointOfInterest)) + usingElements.add(String.format(getResources().getString(R.string.ruleXrequiresThis), ruleName)); + for(String ruleName : getRulesUsing(Trigger.Trigger_Enum.wifiConnection)) + usingElements.add(String.format(getResources().getString(R.string.ruleXrequiresThis), ruleName)); + break; + /*case "android.permission.BATTERY_STATS": + for(String ruleName : getRulesUsing(Trigger.Trigger_Enum.batteryLevel)) + usingElements.add(String.format(getResources().getString(R.string.ruleXrequiresThis), ruleName)); + for(String ruleName : getRulesUsing(Trigger.Trigger_Enum.charging)) + usingElements.add(String.format(getResources().getString(R.string.ruleXrequiresThis), ruleName)); + for(String ruleName : getRulesUsing(Trigger.Trigger_Enum.usb_host_connection)) + usingElements.add(String.format(getResources().getString(R.string.ruleXrequiresThis), ruleName)); + break;*/ + case "android.permission.BLUETOOTH_ADMIN": + for(String ruleName : getRulesUsing(Trigger.Trigger_Enum.bluetoothConnection)) + usingElements.add(String.format(getResources().getString(R.string.ruleXrequiresThis), ruleName)); + for(String ruleName : getRulesUsing(Action.Action_Enum.setBluetooth)) + usingElements.add(String.format(getResources().getString(R.string.ruleXrequiresThis), ruleName)); + for(String ruleName : getRulesUsing(Action.Action_Enum.turnBluetoothOff)) + usingElements.add(String.format(getResources().getString(R.string.ruleXrequiresThis), ruleName)); + for(String ruleName : getRulesUsing(Action.Action_Enum.turnBluetoothOn)) + usingElements.add(String.format(getResources().getString(R.string.ruleXrequiresThis), ruleName)); + break; + case "android.permission.BLUETOOTH": + for(String ruleName : getRulesUsing(Trigger.Trigger_Enum.bluetoothConnection)) + usingElements.add(String.format(getResources().getString(R.string.ruleXrequiresThis), ruleName)); + for(String ruleName : getRulesUsing(Action.Action_Enum.setBluetooth)) + usingElements.add(String.format(getResources().getString(R.string.ruleXrequiresThis), ruleName)); + for(String ruleName : getRulesUsing(Action.Action_Enum.turnBluetoothOff)) + usingElements.add(String.format(getResources().getString(R.string.ruleXrequiresThis), ruleName)); + for(String ruleName : getRulesUsing(Action.Action_Enum.turnBluetoothOn)) + usingElements.add(String.format(getResources().getString(R.string.ruleXrequiresThis), ruleName)); + break; + case "android.permission.GET_TASKS": + for(String ruleName : getRulesUsing(Trigger.Trigger_Enum.process_started_stopped)) + usingElements.add(String.format(getResources().getString(R.string.ruleXrequiresThis), ruleName)); + break; + case "android.permission.INTERNET": + for(String ruleName : getRulesUsing(Trigger.Trigger_Enum.pointOfInterest)) + usingElements.add(String.format(getResources().getString(R.string.ruleXrequiresThis), ruleName)); + for(String ruleName : getRulesUsing(Trigger.Trigger_Enum.speed)) + usingElements.add(String.format(getResources().getString(R.string.ruleXrequiresThis), ruleName)); + for(String ruleName : getRulesUsing(Action.Action_Enum.triggerUrl)) + usingElements.add(String.format(getResources().getString(R.string.ruleXrequiresThis), ruleName)); + break; + case "android.permission.NFC": + for(String ruleName : getRulesUsing(Trigger.Trigger_Enum.nfcTag)) + usingElements.add(String.format(getResources().getString(R.string.ruleXrequiresThis), ruleName)); + break; + case permissionNameCall: + for(String ruleName : getRulesUsing(Trigger.Trigger_Enum.phoneCall)) + usingElements.add(String.format(getResources().getString(R.string.ruleXrequiresThis), ruleName)); + break; + case "android.permission.READ_PHONE_STATE": + for(String ruleName : getRulesUsing(Trigger.Trigger_Enum.batteryLevel)) + usingElements.add(String.format(getResources().getString(R.string.ruleXrequiresThis), ruleName)); + for(String ruleName : getRulesUsing(Trigger.Trigger_Enum.charging)) + usingElements.add(String.format(getResources().getString(R.string.ruleXrequiresThis), ruleName)); + for(String ruleName : getRulesUsing(Trigger.Trigger_Enum.headsetPlugged)) + usingElements.add(String.format(getResources().getString(R.string.ruleXrequiresThis), ruleName)); + for(String ruleName : getRulesUsing(Trigger.Trigger_Enum.phoneCall)) + usingElements.add(String.format(getResources().getString(R.string.ruleXrequiresThis), ruleName)); + for(String ruleName : getRulesUsing(Trigger.Trigger_Enum.usb_host_connection)) + usingElements.add(String.format(getResources().getString(R.string.ruleXrequiresThis), ruleName)); + break; + case "android.permission.RECORD_AUDIO": + for(String ruleName : getRulesUsing(Trigger.Trigger_Enum.noiseLevel)) + usingElements.add(String.format(getResources().getString(R.string.ruleXrequiresThis), ruleName)); + break; + case "android.permission.SEND_SMS": + for(String ruleName : getRulesUsing(Action.Action_Enum.sendTextMessage)) + usingElements.add(String.format(getResources().getString(R.string.ruleXrequiresThis), ruleName)); + break; + case "android.permission.FOREGROUND_SERVICE": + usingElements.add(getResources().getString(R.string.startAutomationAsService)); + break; + } + + return usingElements; + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) + { + super.onActivityResult(requestCode, resultCode, data); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) + { + if (requestCode == requestCodeForPermissionsWriteSettings) + if(android.provider.Settings.System.canWrite(ActivityPermissions.this)) + requestPermissions(cachedPermissionsToRequest, true); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) + { + if (requestCode == requestCodeForPermissionsNotificationPolicy) + { + NotificationManager mNotificationManager = (NotificationManager) ActivityPermissions.this.getSystemService(Context.NOTIFICATION_SERVICE); + + if (mNotificationManager.isNotificationPolicyAccessGranted()) + requestPermissions(cachedPermissionsToRequest, true); + } + } + + } + } + + public static void requestSpecificPermission(String... permissionNames) + { + ArrayList permissionList = new ArrayList(); + for(String permission : permissionNames) + { + if(permissionNames.equals(permissionNameCall)) + { + if(ActivityPermissions.isPermissionDeclaratedInManifest(Miscellaneous.getAnyContext(), permissionNameCall) && !Miscellaneous.isGooglePlayInstalled(Miscellaneous.getAnyContext())) + { + permissionList.add(permission); + } + } + else if(permissionNames.equals("android.permission.SEND_SMS")) + { + if(ActivityPermissions.isPermissionDeclaratedInManifest(Miscellaneous.getAnyContext(), "android.permission.SEND_SMS") && !Miscellaneous.isGooglePlayInstalled(Miscellaneous.getAnyContext())) + { + permissionList.add(permission); + } + } + else + { + if(!havePermission(permission, Miscellaneous.getAnyContext())) + permissionList.add(permission); + } + } + + getInstance().requestPermissions(permissionList, true); + } + + ArrayList cachedPermissionsToRequest = null; + protected void requestPermissions(ArrayList requiredPermissions, boolean continueWithRemainingPermissions) + { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) + { + if(!continueWithRemainingPermissions) + { + for (String s : requiredPermissions) + { + if (s.equalsIgnoreCase(writeSystemSettingsPermissionName)) + { + requiredPermissions.remove(s); + cachedPermissionsToRequest = requiredPermissions; + Intent intent = new Intent(android.provider.Settings.ACTION_MANAGE_WRITE_SETTINGS); + intent.setData(Uri.parse("package:" + getPackageName())); + startActivityForResult(intent, requestCodeForPermissionsWriteSettings); + return; + } + else if (s.equalsIgnoreCase(accessNotificationPolicyPermissionName)) + { + requiredPermissions.remove(s); + cachedPermissionsToRequest = requiredPermissions; + Intent intent = new Intent(android.provider.Settings.ACTION_NOTIFICATION_POLICY_ACCESS_SETTINGS); +// intent.setData(Uri.parse("package:" + getPackageName())); + startActivityForResult(intent, requestCodeForPermissionsNotificationPolicy); + return; + } + } + } + + if(requiredPermissions.size() > 0) + { + if(requiredPermissions.contains(permissionNameCall)) + { + if(!ActivityPermissions.isPermissionDeclaratedInManifest(Miscellaneous.getAnyContext(), "android.permission.SEND_SMS") + && + Miscellaneous.isGooglePlayInstalled(Miscellaneous.getAnyContext()) + ) + { + requiredPermissions.remove(permissionNameCall); + Miscellaneous.messageBox("Problem", getResources().getString(R.string.googleSarcasm), ActivityPermissions.this).show(); + } + } + if(requiredPermissions.contains("android.permission.SEND_SMS")) + { + if(!ActivityPermissions.isPermissionDeclaratedInManifest(Miscellaneous.getAnyContext(), "android.permission.SEND_SMS") + && + Miscellaneous.isGooglePlayInstalled(Miscellaneous.getAnyContext()) + ) + { + requiredPermissions.remove("android.permission.SEND_SMS"); + Miscellaneous.messageBox("Problem", getResources().getString(R.string.googleSarcasm), ActivityPermissions.this).show(); + } + } + + + StringBuilder permissions = new StringBuilder(); + for (String perm : requiredPermissions) + permissions.append(perm + "; "); + if (permissions.length() > 0) + permissions.delete(permissions.length() - 2, permissions.length()); + + Miscellaneous.logEvent("i", "Permissions", "Requesting permissions: " + permissions, 2); + +// Toast.makeText(ActivityPermissions.this, "Requesting permissions. Amount: " + String.valueOf(requiredPermissions.size()), Toast.LENGTH_LONG).show(); + requestPermissions(requiredPermissions.toArray(new String[requiredPermissions.size()]), requestCodeForPermissions); + } + else + setHaveAllPermissions(); + } + } + + protected void applyChanges() + { + AutomationService service = AutomationService.getInstance(); + if(service != null) + service.applySettingsAndRules(); + } + + @Override + public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) + { + Miscellaneous.logEvent("i", "onRequestPermissionsResult()", "onRequestPermissionsResult()", 3); +// Toast.makeText(ActivityPermissions.this, "onRequestPermissionsResult()", Toast.LENGTH_LONG).show(); + +// ArrayList disabledFeatures = new ArrayList(); + ArrayList deniedPermissions = new ArrayList(); + + if (requestCode == requestCodeForPermissions) + { + /*ArrayList affectedGeneralList = new ArrayList(); + ArrayList affectedTriggersList = new ArrayList(); + ArrayList affectedActionList = new ArrayList();*/ + + for (int i=0; i < grantResults.length; i++) + { + if(permissions[i].equalsIgnoreCase(writeExternalStoragePermissionName) && grantResults[i] == PackageManager.PERMISSION_GRANTED) + { + // We just got permission to read the config file. Read again. + try + { + XmlFileInterface.readFile(); + ActivityMainScreen.updateMainScreen(); + ActivityMainPoi.getInstance().updateListView(); + ActivityMainRules.getInstance().updateListView(); + ActivityMainProfiles.getInstance().updateListView(); + } + catch(Exception e) + { + Log.e("Error", Log.getStackTraceString(e)); + } + } + + if (grantResults[i] == PackageManager.PERMISSION_DENIED) + { +// User didn't allow at least 1 permission. What do we do now? +// Display the corresponding rules and otherwise deactivate them. + + /* for (String s : getGeneralAffectedFromDenial(permissions[i])) + addToArrayListUnique(s, affectedGeneralList); + + for (String s : getTriggerAffectedFromDenial(permissions[i])) + addToArrayListUnique(s, affectedTriggersList); + + for (String s : getActionAffectedFromDenial(permissions[i])) + addToArrayListUnique(s, affectedActionList);*/ + + deniedPermissions.add(permissions[i]); + } + } + + /* + * In theory we now have 3 arrays that hold the features which can't function. + */ + /*StringBuilder message = new StringBuilder(); + + for (String s : affectedGeneralList) + message.append(Miscellaneous.lineSeparator + s); + + for (String s : affectedTriggersList) + message.append(Miscellaneous.lineSeparator + s); + + for (String s : affectedActionList) + message.append(Miscellaneous.lineSeparator + s);*/ + + if(deniedPermissions.size() < permissions.length) + { + // At least one permission was granted. Apply settings. + applyChanges(); + } + if(deniedPermissions.size() > 0) + { + /* + The user denied certain permissions. With the exception of write-storage we need to live with that + and simply disable features while keeping the notification alive. The user may dismiss it anyway. + */ + + Miscellaneous.logEvent("w", "Denied permissions", getResources().getString(R.string.theFollowingPermissionsHaveBeenDenied) + Miscellaneous.explode(deniedPermissions), 3); +// this.finish(); + } + else + { + // All permissions have been granted. + setHaveAllPermissions(); + } + } + else + { +// I don't remember asking for permissions.... + } + } + + private void setHaveAllPermissions() + { + setResult(RESULT_OK); + // All permissions have been granted. + NotificationManager mNotificationManager = (NotificationManager) Miscellaneous.getAnyContext().getSystemService(Context.NOTIFICATION_SERVICE); + mNotificationManager.cancel(notificationIdPermissions); + ActivityMainScreen.updateMainScreen(); + this.finish(); + } + + private String[] getGeneralAffectedFromDenial(String permissionName) + { + ArrayList returnList = new ArrayList(); + + Iterator it = mapGeneralPermissions.entrySet().iterator(); + while (it.hasNext()) + { + Map.Entry pair = (Map.Entry) it.next(); + if (pair.getValue().equals(permissionName)) + addToArrayListUnique(pair.getKey(), returnList); + + it.remove(); // avoids a ConcurrentModificationException + } + + return returnList.toArray(new String[returnList.size()]); + } + + private String[] getTriggerAffectedFromDenial(String permissionName) + { + ArrayList returnList = new ArrayList(); + + Iterator it = mapTriggerPermissions.entrySet().iterator(); + while (it.hasNext()) + { + Map.Entry pair = (Map.Entry) it.next(); + if (pair.getValue().equals(permissionName)) + addToArrayListUnique(pair.getKey(), returnList); + + it.remove(); // avoids a ConcurrentModificationException + } + + return returnList.toArray(new String[returnList.size()]); + } + + private String[] getActionAffectedFromDenial(String permissionName) + { + ArrayList returnList = new ArrayList(); + + Iterator it = mapActionPermissions.entrySet().iterator(); + while (it.hasNext()) + { + Map.Entry pair = (Map.Entry) it.next(); + if (pair.getValue().equals(permissionName)) + addToArrayListUnique(pair.getKey(), returnList); + + it.remove(); // avoids a ConcurrentModificationException + } + + return returnList.toArray(new String[returnList.size()]); + } + + + protected static void fillPermissionMaps() + { + mapGeneralPermissions = new HashMap(); + mapGeneralPermissions.put("general", "android.permission.RECEIVE_BOOT_COMPLETED"); + mapGeneralPermissions.put("general", "android.permission.WRITE_EXTERNAL_STORAGE"); + + mapTriggerPermissions = new HashMap(); + mapTriggerPermissions.put("activityDetection", "com.google.android.gms.permission.ACTIVITY_RECOGNITION"); + mapTriggerPermissions.put("activityDetection", "android.permission.ACTIVITY_RECOGNITION"); + mapTriggerPermissions.put("airplaneMode", "android.permission.ACCESS_NETWORK_STATE"); + mapTriggerPermissions.put("batteryLevel", "android.permission.READ_PHONE_STATE"); + mapTriggerPermissions.put("batteryLevel", "android.permission.BATTERY_STATS"); + mapTriggerPermissions.put("bluetoothConnection", "android.permission.BLUETOOTH_ADMIN"); + mapTriggerPermissions.put("bluetoothConnection", "android.permission.BLUETOOTH"); + mapTriggerPermissions.put("bluetoothConnection", "android.permission.ACCESS_NETWORK_STATE"); + mapTriggerPermissions.put("charging", "android.permission.READ_PHONE_STATE"); + mapTriggerPermissions.put("charging", "android.permission.BATTERY_STATS"); + mapTriggerPermissions.put("headsetPlugged", "android.permission.READ_PHONE_STATE"); + mapTriggerPermissions.put("nfcTag", "android.permission.NFC"); + mapTriggerPermissions.put("noiseLevel", "android.permission.RECORD_AUDIO"); + mapTriggerPermissions.put("phoneCall", "android.permission.READ_PHONE_STATE"); + mapTriggerPermissions.put("phoneCall", permissionNameCall); + mapTriggerPermissions.put("pointOfInterest", permissionNameLocationFine); + mapTriggerPermissions.put("pointOfInterest", permissionNameLocationCoarse); + if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) + mapTriggerPermissions.put("pointOfInterest", permissionNameLocationBackground); + mapTriggerPermissions.put("pointOfInterest", "android.permission.ACCESS_NETWORK_STATE"); + mapTriggerPermissions.put("pointOfInterest", "android.permission.INTERNET"); + mapTriggerPermissions.put("pointOfInterest", "android.permission.ACCESS_WIFI_STATE"); + mapTriggerPermissions.put("process_started_stopped", "android.permission.GET_TASKS"); + mapTriggerPermissions.put("roaming", "android.permission.ACCESS_NETWORK_STATE"); + mapTriggerPermissions.put("speed", permissionNameLocationFine); + mapTriggerPermissions.put("speed", permissionNameLocationCoarse); + if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) + mapTriggerPermissions.put("speed", permissionNameLocationBackground); + mapTriggerPermissions.put("speed", "android.permission.ACCESS_NETWORK_STATE"); + mapTriggerPermissions.put("speed", "android.permission.INTERNET"); +// map.put("timeFrame", ""); + mapTriggerPermissions.put("usb_host_connection", "android.permission.READ_PHONE_STATE"); + mapTriggerPermissions.put("usb_host_connection", "android.permission.BATTERY_STATS"); + mapTriggerPermissions.put("wifiConnection", "android.permission.ACCESS_NETWORK_STATE"); + mapTriggerPermissions.put("wifiConnection", "android.permission.ACCESS_WIFI_STATE"); + + mapActionPermissions = new HashMap(); + mapActionPermissions.put("changeSoundProfile", "android.permission.MODIFY_AUDIO_SETTINGS"); + mapActionPermissions.put("changeSoundProfile", accessNotificationPolicyPermissionName); + mapActionPermissions.put("disableScreenRotation", writeSystemSettingsPermissionName); + mapActionPermissions.put("enableScreenRotation", writeSystemSettingsPermissionName); +// mapActionPermissions.put("playMusic", ""); + mapActionPermissions.put("sendTextMessage", "android.permission.SEND_SMS"); + mapActionPermissions.put("setAirplaneMode", writeSystemSettingsPermissionName); + mapActionPermissions.put("setAirplaneMode", "android.permission.ACCESS_NETWORK_STATE"); + mapActionPermissions.put("setAirplaneMode", "android.permission.ACCESS_SUPERUSER"); + mapActionPermissions.put("setAirplaneMode", "android.permission.CHANGE_NETWORK_STATE"); + mapActionPermissions.put("setBluetooth", "android.permission.BLUETOOTH_ADMIN"); + mapActionPermissions.put("setBluetooth", "android.permission.BLUETOOTH"); + mapActionPermissions.put("setBluetooth", "android.permission.ACCESS_NETWORK_STATE"); + mapActionPermissions.put("setBluetooth", writeSystemSettingsPermissionName); + mapActionPermissions.put("setDataConnection", writeSystemSettingsPermissionName); + mapActionPermissions.put("setDataConnection", "android.permission.ACCESS_NETWORK_STATE"); + mapActionPermissions.put("setDataConnection", "android.permission.ACCESS_SUPERUSER"); + mapActionPermissions.put("setDataConnection", "android.permission.CHANGE_NETWORK_STATE"); + mapActionPermissions.put("setDisplayRotation", writeSystemSettingsPermissionName); + mapActionPermissions.put("setUsbTethering", writeSystemSettingsPermissionName); + mapActionPermissions.put("setUsbTethering", "android.permission.CHANGE_NETWORK_STATE"); + mapActionPermissions.put("setWifi", writeSystemSettingsPermissionName); + mapActionPermissions.put("setWifi", "android.permission.CHANGE_NETWORK_STATE"); + mapActionPermissions.put("setWifi", "android.permission.ACCESS_NETWORK_STATE"); + mapActionPermissions.put("setWifiTethering", writeSystemSettingsPermissionName); + mapActionPermissions.put("setWifiTethering", "android.permission.CHANGE_NETWORK_STATE"); + mapActionPermissions.put("setWifiTethering", "android.permission.ACCESS_NETWORK_STATE"); +// mapActionPermissions.put("speakText", accessNotificationPolicyPermissionName); +// mapActionPermissions.put("startOtherActivity", ""); + mapActionPermissions.put("triggerUrl", "android.permission.INTERNET"); +// Hier müßte ein Hinweis kommen, daß nur die Variablen verwendet werden können, für die es Rechte gibt. + mapActionPermissions.put("turnBluetoothOff", "android.permission.BLUETOOTH_ADMIN"); + mapActionPermissions.put("turnBluetoothOff", "android.permission.BLUETOOTH"); + mapActionPermissions.put("turnBluetoothOff", "android.permission.ACCESS_NETWORK_STATE"); + mapActionPermissions.put("turnBluetoothOff", writeSystemSettingsPermissionName); + mapActionPermissions.put("turnBluetoothOn", "android.permission.BLUETOOTH_ADMIN"); + mapActionPermissions.put("turnBluetoothOn", "android.permission.BLUETOOTH"); + mapActionPermissions.put("turnBluetoothOn", "android.permission.ACCESS_NETWORK_STATE"); + mapActionPermissions.put("turnBluetoothOn", writeSystemSettingsPermissionName); + mapActionPermissions.put("turnUsbTetheringOff", writeSystemSettingsPermissionName); + mapActionPermissions.put("turnUsbTetheringOff", "android.permission.CHANGE_NETWORK_STATE"); + mapActionPermissions.put("turnUsbTetheringOn", writeSystemSettingsPermissionName); + mapActionPermissions.put("turnUsbTetheringOn", "android.permission.CHANGE_NETWORK_STATE"); + mapActionPermissions.put("turnWifiOff", writeSystemSettingsPermissionName); + mapActionPermissions.put("turnWifiOff", "android.permission.CHANGE_NETWORK_STATE"); + mapActionPermissions.put("turnWifiOff", "android.permission.ACCESS_NETWORK_STATE"); + mapActionPermissions.put("turnWifiOn", writeSystemSettingsPermissionName); + mapActionPermissions.put("turnWifiOn", "android.permission.CHANGE_NETWORK_STATE"); + mapActionPermissions.put("turnWifiOn", "android.permission.ACCESS_NETWORK_STATE"); + mapActionPermissions.put("turnWifiTetheringOff", writeSystemSettingsPermissionName); + mapActionPermissions.put("turnWifiTetheringOff", "android.permission.CHANGE_NETWORK_STATE"); + mapActionPermissions.put("turnWifiTetheringOff", "android.permission.ACCESS_NETWORK_STATE"); + mapActionPermissions.put("turnWifiTetheringOn", writeSystemSettingsPermissionName); + mapActionPermissions.put("turnWifiTetheringOn", "android.permission.CHANGE_NETWORK_STATE"); + mapActionPermissions.put("turnWifiTetheringOn", "android.permission.ACCESS_NETWORK_STATE"); +// mapActionPermissions.put("waitBeforeNextAction", ""); + mapActionPermissions.put("wakeupDevice", "android.permission.WAKE_LOCK"); + } + + /* + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + */ + + public static boolean isPermissionDeclaratedInManifest(Context context, String permission) + { + PackageManager pm = context.getPackageManager(); + try + { + PackageInfo packageInfo = pm.getPackageInfo(context.getPackageName(), PackageManager.GET_PERMISSIONS); + String[] requestedPermissions = null; + if (packageInfo != null) + { + requestedPermissions = packageInfo.requestedPermissions; + } + + if (requestedPermissions != null && requestedPermissions.length > 0) + { + List requestedPermissionsList = Arrays.asList(requestedPermissions); + ArrayList requestedPermissionsArrayList = new ArrayList(); + requestedPermissionsArrayList.addAll(requestedPermissionsList); + return (requestedPermissionsArrayList.contains(permission)); +// Log.i(ExConsts.TAG, ""+requestedPermissionsArrayList); + } + } + catch (PackageManager.NameNotFoundException e) + { + e.printStackTrace(); + } + + return false; + } +} diff --git a/app/src/main/java/com/jens/automation2/ActivitySettings.java b/app/src/main/java/com/jens/automation2/ActivitySettings.java new file mode 100644 index 0000000..21fa8e8 --- /dev/null +++ b/app/src/main/java/com/jens/automation2/ActivitySettings.java @@ -0,0 +1,19 @@ +package com.jens.automation2; + +import android.os.Bundle; +import android.preference.ListPreference; +import android.preference.PreferenceActivity; + +import com.jens.automation2.R.layout; + +public class ActivitySettings extends PreferenceActivity +{ + ListPreference lpStartScreenOptionsValues; + + @Override + protected void onCreate(Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + addPreferencesFromResource(layout.settings); + } +} diff --git a/app/src/main/java/com/jens/automation2/ActivityVolumeTest.java b/app/src/main/java/com/jens/automation2/ActivityVolumeTest.java new file mode 100644 index 0000000..7c4de0b --- /dev/null +++ b/app/src/main/java/com/jens/automation2/ActivityVolumeTest.java @@ -0,0 +1,210 @@ +package com.jens.automation2; + +import android.annotation.SuppressLint; +import android.app.Activity; +import android.media.MediaRecorder; +import android.os.AsyncTask; +import android.os.Bundle; +import android.widget.EditText; +import android.widget.SeekBar; +import android.widget.SeekBar.OnSeekBarChangeListener; +import android.widget.TextView; +import android.widget.Toast; + +@SuppressLint("NewApi") +public class ActivityVolumeTest extends Activity +{ + TextView tvCurrentVolume, tvVolumeTestExplanation; + EditText etReferenceValue; + SeekBar sbReferenceValue; + + final int volumeRefreshInterval = 3; + + static ActivityVolumeTest instance = null; + + AsyncTask volumeTask = null; + + @Override + protected void onCreate(Bundle savedInstanceState) + { + instance = this; + + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_volume_test); + + tvCurrentVolume = (TextView)findViewById(R.id.tvCurrentVolume); + etReferenceValue = (EditText)findViewById(R.id.etReferenceValue); + sbReferenceValue = (SeekBar)findViewById(R.id.sbReferenceValue); + tvVolumeTestExplanation = (TextView)findViewById(R.id.tvVolumeTestExplanation); + + tvVolumeTestExplanation.setText(String.format(getResources().getString(R.string.volumeTesterExplanation), String.valueOf(volumeRefreshInterval))); + + etReferenceValue.setText(String.valueOf(Settings.referenceValueForNoiseLevelMeasurements)); + + sbReferenceValue.setMax(500); + sbReferenceValue.setProgress((int) Settings.referenceValueForNoiseLevelMeasurements); + sbReferenceValue.setOnSeekBarChangeListener(new OnSeekBarChangeListener() + { + @Override + public void onStopTrackingTouch(SeekBar seekBar) + { + // TODO Auto-generated method stub + + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) + { + // TODO Auto-generated method stub + + } + + @Override + public void onProgressChanged(SeekBar seekBar, int progress, + boolean fromUser) + { + etReferenceValue.setText(String.valueOf(sbReferenceValue.getProgress())); + } + }); + } + + @Override + protected void onResume() + { + super.onResume(); + + startVolumeMonitoring(); + } + + @Override + protected void onPause() + { + super.onPause(); + stopVolumeMonitoring(); + } + + public static ActivityVolumeTest getInstance() + { + return instance; + } + + @Override + public void onBackPressed() + { + try + { + stopVolumeMonitoring(); + Settings.referenceValueForNoiseLevelMeasurements = Long.parseLong(etReferenceValue.getText().toString()); + Settings.writeSettings(ActivityVolumeTest.this); + super.onBackPressed(); + } + catch (Exception e) + { + Toast.makeText(ActivityVolumeTest.this, ActivityVolumeTest.this.getResources().getString(R.string.enterValidReferenceValue), Toast.LENGTH_LONG).show(); + } + } + + synchronized private void startVolumeMonitoring() + { + volumeTask = new NoiseListenerMeasuring(); + +// if(!(volumeTask.getStatus() == AsyncTask.Status.PENDING | volumeTask.getStatus() != AsyncTask.Status.RUNNING)) + volumeTask.execute(volumeRefreshInterval); + } + + synchronized private void stopVolumeMonitoring() + { +// if(volumeTask != null && (volumeTask.getStatus() == Status.PENDING | volumeTask.getStatus() == Status.RUNNING)) + volumeTask.cancel(true); + } + + synchronized void updateDisplayedNoiseLevel(long noise) + { + tvCurrentVolume.setText(String.valueOf(noise) + " dB"); + } + + private static class NoiseListenerMeasuring extends AsyncTask + { + static boolean isNoiseMonitoringActive; + + @Override + protected Void doInBackground(Integer... interval) + { + if(!isNoiseMonitoringActive) + { + isNoiseMonitoringActive = true; + + Miscellaneous.logEvent("i", "Noise level", "Periodic noise level measurement started.", 5); + + while(!isCancelled()) + { + try + { + // Start recording but don't store data + MediaRecorder mediaRecorder = new MediaRecorder(); + mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); + mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP); + mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB); + mediaRecorder.setOutputFile("/dev/null"); + // Date myDate = new Date(); + // mediaRecorder.setOutputFile("/sdcard/temp/" + String.valueOf(myDate.getTime()) + ".3gpp"); + + mediaRecorder.prepare(); + mediaRecorder.getMaxAmplitude(); + mediaRecorder.start(); + mediaRecorder.getMaxAmplitude(); + + long noiseLevel; + + try + { + Thread.sleep(interval[0] * 1000); + // Obtain maximum amplitude since last call of getMaxAmplitude() + noiseLevel = mediaRecorder.getMaxAmplitude(); + } + catch(Exception e) + { + noiseLevel = -1; + Miscellaneous.logEvent("e", "Noise level", "Error getting noise level: " + e.getMessage(), 2); + } + + double currentReferenceValue = Double.parseDouble(ActivityVolumeTest.getInstance().etReferenceValue.getText().toString()); + double db = 20 * Math.log(noiseLevel / currentReferenceValue); + long noiseLevelDb = Math.round(db); + + publishProgress(noiseLevelDb); + + // Message answer = new Message(); + // Bundle answerBundle = new Bundle(); + // answerBundle.putLong("noiseLevelDb", noiseLevelDb); + // answer.setData(answerBundle); + // volumeHandler.sendMessage(answer); + + // Don't forget to release + mediaRecorder.reset(); + mediaRecorder.release(); + + Miscellaneous.logEvent("i", "Noise level", "Measured noise level: " + String.valueOf(noiseLevel) + " / converted to db: " + String.valueOf(db), 3); + } + catch(Exception e) + {} + } + + isNoiseMonitoringActive = false; + + Miscellaneous.logEvent("i", "Noise level", "Periodic noise level measurement stopped.", 5); + } + return null; + } + + @Override + protected void onProgressUpdate(Long... values) + { + ActivityVolumeTest.getInstance().updateDisplayedNoiseLevel(values[0]); + +// super.onProgressUpdate(values); + } + + + } +} diff --git a/app/src/main/java/com/jens/automation2/AutomationService.java b/app/src/main/java/com/jens/automation2/AutomationService.java new file mode 100644 index 0000000..aabe905 --- /dev/null +++ b/app/src/main/java/com/jens/automation2/AutomationService.java @@ -0,0 +1,713 @@ +package com.jens.automation2; + +import android.annotation.SuppressLint; +import android.app.ActivityManager; +import android.app.ActivityManager.RunningServiceInfo; +import android.app.Notification; +import android.app.Notification.Builder; +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.app.Service; +import android.content.Context; +import android.content.Intent; +import android.media.AudioManager; +import android.os.Binder; +import android.os.Build; +import android.os.Bundle; +import android.os.Environment; +import android.os.IBinder; +import android.speech.tts.TextToSpeech; +import android.speech.tts.TextToSpeech.OnInitListener; +import android.util.Log; +import android.widget.Toast; + +import androidx.core.app.NotificationCompat; + +import com.jens.automation2.Trigger.Trigger_Enum; +import com.jens.automation2.location.LocationProvider; +import com.jens.automation2.receivers.PackageReplacedReceiver; +import com.jens.automation2.receivers.PhoneStatusListener; + +import java.util.Calendar; + +@SuppressLint("NewApi") +public class AutomationService extends Service implements OnInitListener +{ + protected TextToSpeech ttsEngine = null; + protected final static int notificationId = 1000; + + final static String NOTIFICATION_CHANNEL_ID = "com.jens.automation2"; + final static String channelName = "Automation notifications"; + + protected static Notification myNotification; + protected static NotificationCompat.Builder notificationBuilder = null; + protected static PendingIntent myPendingIntent; + + protected Calendar lockSoundChangesEnd = null; + protected boolean isRunning; + + public void nullLockSoundChangesEnd() + { + lockSoundChangesEnd = null; + } + public Calendar getLockSoundChangesEnd() + { + return lockSoundChangesEnd; + } + public void lockSoundChangesEndAddTime() + { + if(lockSoundChangesEnd == null) + lockSoundChangesEnd = Calendar.getInstance(); + + lockSoundChangesEnd.add(Calendar.MINUTE, Settings.lockSoundChangesInterval); +// ActivityMainScreen.getActivityMainScreenInstance().updateMainScreen(); + } + + public void checkLockSoundChangesTimeElapsed() + { + Calendar now = Calendar.getInstance(); + if(getLockSoundChangesEnd() != null && getLockSoundChangesEnd().getTimeInMillis() <= now.getTimeInMillis()) + lockSoundChangesEnd = null; + } + + public void setLockSoundChangesEnd(Calendar lockSoundChangesEnd) + { + this.lockSoundChangesEnd = lockSoundChangesEnd; + } + + protected final IBinder myBinder = new LocalBinder(); + + protected LocationProvider myLocationProvider; + + public LocationProvider getLocationProvider() + { + return myLocationProvider; + } + + protected static AutomationService centralInstance = null; + + public static AutomationService getInstance() + { + return centralInstance; + } + + @Override + public void onCreate() + { + super.onCreate(); + + Thread.setDefaultUncaughtExceptionHandler(Miscellaneous.uncaughtExceptionHandler); + + // Store a reference to myself. Other classes often need a context or something, this can provide that. + centralInstance = this; + } + + public boolean checkStartupRequirements(Context context, boolean startAtBoot) + { + if (!ActivityPermissions.havePermission(ActivityPermissions.writeExternalStoragePermissionName, AutomationService.this)) + { + /* + Don't have permission to access external storage. This is a show stopper as + the configuration file is stored on external storage. + */ + Miscellaneous.logEvent("e", "Permission", "Don't have permission to access external storage. Will request it now.", 4); +// Toast.makeText(AutomationService.this, getResources().getString(R.string.appRequiresPermissiontoAccessExternalStorage), Toast.LENGTH_LONG).show(); + ActivityPermissions.requestSpecificPermission(ActivityPermissions.writeExternalStoragePermissionName); + return false; + } + + if(Build.VERSION.SDK_INT >= 28) + { + if (!ActivityPermissions.havePermission(ActivityPermissions.permissionNameStartService, AutomationService.this)) + { + /* + Don't have permission to start service. This is a show stopper. + */ + Miscellaneous.logEvent("e", "Permission", "Don't have permission to start foreground service. Will request it now.", 4); +// Toast.makeText(AutomationService.this, getResources().getString(R.string.appRequiresPermissiontoAccessExternalStorage), Toast.LENGTH_LONG).show(); + ActivityPermissions.requestSpecificPermission(ActivityPermissions.permissionNameStartService); + return false; + } + } + + if (PointOfInterest.getPointOfInterestCollection() == null | PointOfInterest.getPointOfInterestCollection().size() == 0 + | + Rule.getRuleCollection() == null | Rule.getRuleCollection().size() == 0 + ) + { + if (startAtBoot) + { + /* + * In case we start at boot the sd card may not have been mounted, yet. + * We will wait 3 seconds and check and do this 3 times. + */ + if (!XmlFileInterface.settingsFile.exists()) + { + for (int i = 0; i < 3; i++) + { + String state = Environment.getExternalStorageState(); + if (!state.equals(Environment.MEDIA_MOUNTED)) + { + try + { + Miscellaneous.logEvent("w", "AutoStart", "Service is started via boot. Settingsfile not available because storage is not mounted, yet. Waiting for 3 seconds.", 4); + Thread.sleep(3000); + } catch (InterruptedException e) + { + e.printStackTrace(); + } + } + if (XmlFileInterface.settingsFile.exists()) + break; + } + } + } + PointOfInterest.loadPoisFromFile(); + Rule.readFromFile(); + } + + //if still no POIs... + if (//PointOfInterest.getPointOfInterestCollection() == null | PointOfInterest.getPointOfInterestCollection().size() == 0 + // && + Rule.getRuleCollection() == null | Rule.getRuleCollection().size() == 0 + ) + { + Miscellaneous.logEvent("w", "AutomationService", context.getResources().getString(R.string.serviceWontStart), 1); + Toast.makeText(context, context.getResources().getString(R.string.serviceWontStart), Toast.LENGTH_LONG).show(); + return false; + } else + { + return true; + } + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) + { + boolean startAtBoot = false; + + if (intent != null) + { + Bundle b = intent.getExtras(); + startAtBoot = b.getBoolean("startAtBoot", false); + } + + if (checkStartupRequirements(this, startAtBoot)) + { + Miscellaneous.logEvent("i", "Service", this.getResources().getString(R.string.logServiceStarting), 1); + + startUpRoutine(); + + Intent myIntent = new Intent(this, ActivityMainTabLayout.class); + myPendingIntent = PendingIntent.getActivity(this, 0, myIntent, 0); + notificationBuilder = createDefaultNotificationBuilder(); + + updateNotification(); + + if (isMainActivityRunning(this)) + ActivityMainScreen.updateMainScreen(); + + this.isRunning = true; + Miscellaneous.logEvent("i", "Service", this.getResources().getString(R.string.serviceStarted) + " " + String.format(this.getResources().getString(R.string.version), BuildConfig.VERSION_NAME + "(Build " + BuildConfig.VERSION_CODE + ")"), 1); + Toast.makeText(this, this.getResources().getString(R.string.serviceStarted), Toast.LENGTH_LONG).show(); + // ********** Test area ********** +// Miscellaneous.logEvent("i", "setNetworkType", "bin hier.", 3); +// Actions.setData(true); + // ********** Test area ********** + + return START_STICKY; + } + else + { + Miscellaneous.logEvent("e", "Service", "checkStartupRequirements() delivered false. Stopping service...", 1); + this.stopSelf(); + return START_NOT_STICKY; + } + } + + @Override + public IBinder onBind(Intent arg0) + { + return myBinder; + } + + public enum serviceCommands + { + reloadSettings, reloadPointsOfInterest, reloadRules, updateNotification + } + + ; + + public void serviceInterface(serviceCommands command) + { + Miscellaneous.logEvent("i", "Bind", "Ahhhh, customers... How can I help you?", 5); + + Miscellaneous.logEvent("i", "ServiceBind", "Request to " + command.toString(), 5); + + switch (command) + { + case reloadPointsOfInterest: + PointOfInterest.loadPoisFromFile(); + break; + case reloadRules: + Rule.readFromFile(); + break; + case reloadSettings: + Settings.readFromPersistentStorage(this); + applySettingsAndRules(); + myLocationProvider.applySettingsAndRules(); + break; + case updateNotification: + this.updateNotification(); + ActivityMainScreen.updateMainScreen(); + break; + default: + break; + } + } + + public void applySettingsAndRules() + { + if (Settings.useTextToSpeechOnNormal | Settings.useTextToSpeechOnSilent | Settings.useTextToSpeechOnVibrate) + { + if (ttsEngine == null) + ttsEngine = new TextToSpeech(this, this); + } + else + { + if (ttsEngine != null) + ttsEngine.shutdown(); + } + + startLocationProvider(); + ReceiverCoordinator.startAllReceivers(); + if(myLocationProvider != null) // This condition can be met if the user has no locations defined. + myLocationProvider.applySettingsAndRules(); + + ReceiverCoordinator.applySettingsAndRules(); + } + + @Override + public void onDestroy() + { + Miscellaneous.logEvent("i", "Service", getResources().getString(R.string.logServiceStopping), 1); + + stopRoutine(); + this.isRunning = false; + Toast.makeText(this, getResources().getString(R.string.serviceStopped), Toast.LENGTH_LONG).show(); + Miscellaneous.logEvent("i", "Service", getResources().getString(R.string.serviceStopped), 1); + } + + public void checkForTtsEngine() + { + if (Settings.useTextToSpeechOnNormal | Settings.useTextToSpeechOnSilent | Settings.useTextToSpeechOnVibrate | Rule.isAnyRuleUsing(Action.Action_Enum.speakText)) + { + if (ttsEngine == null) + ttsEngine = new TextToSpeech(this, this); + } else + { + if (ttsEngine != null) + ttsEngine.shutdown(); + } + } + + private void startUpRoutine() + { + checkForTtsEngine(); + checkForPermissions(); + + Actions.context = this; + Actions.autoMationServerRef = this; + + startLocationProvider(); + ReceiverCoordinator.startAllReceivers(); + + PackageReplacedReceiver.setHasServiceBeenRunning(true, this); + } + + protected void startLocationProvider() + { + if(ActivityPermissions.havePermission("android.permission.ACCESS_COARSE_LOCATION", AutomationService.this)) + myLocationProvider = new LocationProvider(this); //autostart with this (only) constructor + } + + protected void checkForPermissions() + { + if(ActivityPermissions.needMorePermissions(AutomationService.this)) + { + boolean displayNotification = false; + + for(Rule r : Rule.getRuleCollection()) + { + if(r.isRuleActive()) + { + if(!r.haveEnoughPermissions()) +// for (String permission : ActivityPermissions.getPermissionsForRule(r)) + { +// if (!ActivityPermissions.havePermission(permission, AutomationService.this)) + { +// r.setRuleActive(false); +// r.change(AutomationService.this); + if(!displayNotification) + displayNotification = true; + } + } + } + } + + if(displayNotification) + { +// Toast.makeText(Miscellaneous.getAnyContext(), "Require more permissions.", Toast.LENGTH_LONG).show(); + // Update notification or show new one that notifiies of the lack or permissions. + + Intent intent = new Intent(AutomationService.this, ActivityPermissions.class); + PendingIntent pi = PendingIntent.getActivity(AutomationService.this, 0, intent, 0); + Miscellaneous.createDismissableNotification(getResources().getString(R.string.appRunningInLimitedMode), ActivityPermissions.notificationIdPermissions, pi); + } +// else +// Toast.makeText(Miscellaneous.getAnyContext(), "Have all required permissions.", Toast.LENGTH_LONG).show(); + } + } + + public static void startAutomationService(Context context, boolean startAtBoot) + { + if(!(isMyServiceRunning(context))) + { + Intent myServiceIntent = new Intent(context, AutomationService.class); + myServiceIntent.putExtra("startAtBoot", startAtBoot); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) + context.startForegroundService(myServiceIntent); + else + context.startService(myServiceIntent); + } + else + Miscellaneous.logEvent("i", "Service", "Service is already running.", 1); + } + + private void stopRoutine() + { + Log.i("STOP", "Stopping"); + try + { + myLocationProvider.stopLocationService(); + ReceiverCoordinator.stopAllReceivers(); + } + catch(NullPointerException e) + { + Miscellaneous.logEvent("e", getResources().getString(R.string.serviceNotRunning), getResources().getString(R.string.serviceNotRunning) + ". " + getResources().getString(R.string.cantStopIt), 3); + } + + if(ttsEngine != null) + ttsEngine.shutdown(); + + PackageReplacedReceiver.setHasServiceBeenRunning(false, this); + } + + protected static Builder createDefaultNotificationBuilderOld() + { + Builder builder = new Builder(AutomationService.getInstance()); + builder.setContentTitle("Automation"); + + if(Settings.showIconWhenServiceIsRunning) + builder.setSmallIcon(R.drawable.ic_launcher); + + builder.setCategory(Notification.CATEGORY_SERVICE); + builder.setWhen(System.currentTimeMillis()); + builder.setContentIntent(myPendingIntent); + +// Notification defaultNotification = new Notification(); + Notification defaultNotification = builder.build(); + + defaultNotification.icon = R.drawable.ic_launcher; + defaultNotification.when = System.currentTimeMillis(); + +// defaultNotification.defaults |= Notification.DEFAULT_VIBRATE; +// defaultNotification.defaults |= Notification.DEFAULT_LIGHTS; + + defaultNotification.flags |= Notification.FLAG_AUTO_CANCEL; +// defaultNotification.flags |= Notification.FLAG_SHOW_LIGHTS; + defaultNotification.flags |= Notification.FLAG_ONLY_ALERT_ONCE; + +// defaultNotification.ledARGB = Color.YELLOW; +// defaultNotification.ledOnMS = 1500; +// defaultNotification.ledOffMS = 1500; + + return builder; + + /*NotificationManager mNotificationManager = (NotificationManager) AutomationService.getInstance().getSystemService(Context.NOTIFICATION_SERVICE); + + NotificationCompat.Builder builder; + builder = new NotificationCompat.Builder(AutomationService.getInstance()); + + if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) + builder.setCategory(Notification.CATEGORY_EVENT); + + builder.setWhen(System.currentTimeMillis()); + + builder.setContentTitle("Automation"); + builder.setSmallIcon(R.drawable.ic_launcher); +// builder.setContentText(textToDisplay); +// builder.setSmallIcon(icon); +// builder.setContentIntent(pendingIntent); +// builder.setStyle(new NotificationCompat.BigTextStyle().bigText(textToDisplay)); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) + { + NotificationChannel channel = new NotificationChannel("notify_001", "Channel human readable title", NotificationManager.IMPORTANCE_DEFAULT); + mNotificationManager.createNotificationChannel(channel); + } + + return builder;*/ + } + + protected static NotificationCompat.Builder createDefaultNotificationBuilder() + { + NotificationManager mNotificationManager = (NotificationManager) AutomationService.getInstance().getSystemService(Context.NOTIFICATION_SERVICE); + + NotificationCompat.Builder builder; + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) + { + NotificationChannel chan = new NotificationChannel(NOTIFICATION_CHANNEL_ID, channelName, NotificationManager.IMPORTANCE_LOW); +// chan.setLightColor(Color.BLUE); + chan.enableVibration(false); + chan.setSound(null, null); + chan.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE); + mNotificationManager.createNotificationChannel(chan); + + builder = new NotificationCompat.Builder(AutomationService.getInstance(), NOTIFICATION_CHANNEL_ID); + } + else + builder = new NotificationCompat.Builder(AutomationService.getInstance()); + + if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) + builder.setCategory(Notification.CATEGORY_SERVICE); + + builder.setWhen(System.currentTimeMillis()); + builder.setContentIntent(myPendingIntent); + + builder.setContentTitle(AutomationService.getInstance().getResources().getString(R.string.app_name)); + builder.setOnlyAlertOnce(true); + + if(Settings.showIconWhenServiceIsRunning) + builder.setSmallIcon(R.drawable.ic_launcher); + +// builder.setContentText(textToDisplay); +// builder.setSmallIcon(icon); +// builder.setStyle(new NotificationCompat.BigTextStyle().bigText(textToDisplay)); + + return builder; + } + + @SuppressLint("NewApi") + @SuppressWarnings("deprecation") + public static void updateNotification() + { + AutomationService instance = getInstance(); + + if(instance != null) + { +// if(Settings.showIconWhenServiceIsRunning) +// { + Miscellaneous.logEvent("i", "Notification", "Request to update notification.", 4); + + String bodyText=""; + String lastRuleString = ""; + + if(PointOfInterest.getPointOfInterestCollection() != null && PointOfInterest.getPointOfInterestCollection().size() > 0) + { + try + { + PointOfInterest activePoi = PointOfInterest.getActivePoi(); + if(activePoi == null) + { + PointOfInterest closestPoi = PointOfInterest.getClosestPOI(instance.getLocationProvider().getCurrentLocation()); + bodyText = "Active POI: none" + "\n" + "Closest POI: " + closestPoi.getName() + lastRuleString; + } + else + { + bodyText = "Active POI: " + activePoi.getName() + lastRuleString; + } + } + catch(NullPointerException e) + { + if( + Rule.isAnyRuleUsing(Trigger_Enum.pointOfInterest) + && + ActivityPermissions.havePermission(ActivityPermissions.permissionNameLocationCoarse, AutomationService.getInstance()) + && + ActivityPermissions.havePermission(ActivityPermissions.permissionNameLocationFine, AutomationService.getInstance()) + ) + bodyText = instance.getResources().getString(R.string.stillGettingPosition); + else + bodyText = instance.getResources().getString(R.string.locationEngineNotActive); + } + } + + try + { + lastRuleString = instance.getResources().getString(R.string.lastRule) + " " + Rule.getLastActivatedRule().getName() + " " + instance.getResources().getString(R.string.at) + " " + Rule.getLastActivatedRuleActivationTime().toLocaleString(); + } + catch(Exception e) + { + lastRuleString = instance.getResources().getString(R.string.lastRule) + " n./a."; + } + + String textToDisplay = bodyText + " " + lastRuleString; +// if(Build.VERSION.SDK_INT < 11) +// { +// myNotification.setLatestEventInfo(instance, "Automation", textToDisplay, myPendingIntent); +// } +// else +// { + notificationBuilder.setContentText(textToDisplay); + notificationBuilder.setStyle(new NotificationCompat.BigTextStyle().bigText(textToDisplay)); + + myNotification = notificationBuilder.build(); + myNotification.defaults = 0; +// } + +// NotificationManager notificationManager = (NotificationManager) instance.getSystemService(NOTIFICATION_SERVICE); + // hide the notification after its selected +// myNotification.flags |= Notification.FLAG_AUTO_CANCEL; + myNotification.flags |= Notification.FLAG_NO_CLEAR; +// notificationManager.notify(notificationId, myNotification); + + instance.startForeground(notificationId, myNotification); +// } +// else +// instance.startForeground(notificationId, null); // do not show icon in task bar + } + } + + public class LocalBinder extends Binder + { + public AutomationService getService() + { + return AutomationService.this; + } + } + + @Override + public void onInit(int status) + { + // TODO Auto-generated method stub + + } + + /** + * force will skip volume settings and stuff + **/ + public void speak(String text, boolean force) + { + if(text.length() > 0 && (force | Settings.useTextToSpeechOnNormal | Settings.useTextToSpeechOnSilent | Settings.useTextToSpeechOnVibrate)) + { + AudioManager myAudioManager = (AudioManager)getSystemService(Context.AUDIO_SERVICE); + int mode = myAudioManager.getRingerMode(); + + if( + (mode == AudioManager.RINGER_MODE_NORMAL && Settings.useTextToSpeechOnNormal) + | + (mode == AudioManager.RINGER_MODE_VIBRATE && Settings.useTextToSpeechOnVibrate) + | + (mode == AudioManager.RINGER_MODE_SILENT && Settings.useTextToSpeechOnSilent) + | + force + ) + { + if(Settings.muteTextToSpeechDuringCalls && PhoneStatusListener.isInACall() && !force) + { + Miscellaneous.logEvent("i", "TextToSpeech", "Currently in a call. Not speaking as requested.", 4); + return; + } + else + { + try + { + for(int i = 0; i < 5; i++) + { + if(ttsEngine != null) + { + break; + } + else + { + try + { + Miscellaneous.logEvent("i", "TTS", "Waiting for a moment to give the TTS service time to load...", 4); + Thread.sleep(1000); // give the tts engine time to load + } + catch(Exception e) + {} + } + } + this.ttsEngine.speak(text, TextToSpeech.QUEUE_ADD, null); + } + catch(Exception e) + { + Miscellaneous.logEvent("e", "TextToSpeech", Log.getStackTraceString(e), 3); + e.printStackTrace(); + } + } + } + } + } + + public static boolean isMainActivityRunning(Context context) + { + if(ActivityMainScreen.getActivityMainScreenInstance() == null) + return false; + else + return true; + +// boolean isActivityFound = false; +// ActivityManager activityManager = (ActivityManager)context.getSystemService (Context.ACTIVITY_SERVICE); +// List activitys = activityManager.getRunningTasks(Integer.MAX_VALUE); +// isActivityFound = false; +// for (int i = 0; i < activitys.size(); i++) +// { +// if (activitys.get(i).topActivity.toString().equalsIgnoreCase("ComponentInfo{com.jens.automation/com.jens.automation.ActivityMainScreen}")) +// { +// isActivityFound = true; +// } +// } +// Miscellaneous.logEvent("i", "ActivityMainScreen", "Activity running status: " + String.valueOf(isActivityFound), 5); +// return isActivityFound; + +// ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); +// List tasks = activityManager.getRunningTasks(Integer.MAX_VALUE); +// +// for (RunningTaskInfo task : tasks) +// { +// if (context.getPackageName().equalsIgnoreCase(task.baseActivity.getPackageName())) +// return true; +// } +// +// return false; + } + + public static boolean isMyServiceRunning(Context context) + { + try + { + ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); + for (RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) + { + if(AutomationService.class.getName().equals(service.service.getClassName())) + { +// return AutomationService.getInstance() != null && AutomationService.getInstance().isRunning; + return true; + } + } + } + catch(NullPointerException e) + { + if(Log.getStackTraceString(e).contains("activate")) // Means that a poi has been activated/deactivated. Service is running. + return true; +// return AutomationService.getInstance() != null && AutomationService.getInstance().isRunning; + } + + return false; + } +} diff --git a/app/src/main/java/com/jens/automation2/CompensateCrappyAndroidPaths.java b/app/src/main/java/com/jens/automation2/CompensateCrappyAndroidPaths.java new file mode 100644 index 0000000..cbf2d62 --- /dev/null +++ b/app/src/main/java/com/jens/automation2/CompensateCrappyAndroidPaths.java @@ -0,0 +1,371 @@ +package com.jens.automation2; + +import android.annotation.SuppressLint; +import android.content.ContentUris; +import android.content.Context; +import android.database.Cursor; +import android.net.Uri; +import android.os.Build; +import android.os.Environment; +import android.provider.DocumentsContract; +import android.provider.MediaStore; +import android.provider.OpenableColumns; +import android.text.TextUtils; +import android.util.Log; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.InputStream; + +public class CompensateCrappyAndroidPaths +{ + private static Uri contentUri = null; + + /** + * Get a file path from a Uri. This will get the the path for Storage Access + * Framework Documents, as well as the _data field for the MediaStore and + * other file-based ContentProviders.
+ *
+ * Callers should check whether the path is local before assuming it + * represents a local file. + * + * @param context The context. + * @param uri The Uri to query. + */ + @SuppressLint("NewApi") + public static String getPath(final Context context, final Uri uri) { + // check here to KITKAT or new version + final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT; + String selection = null; + String[] selectionArgs = null; + // DocumentProvider + if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) { + // ExternalStorageProvider + if (isExternalStorageDocument(uri)) { + final String docId = DocumentsContract.getDocumentId(uri); + final String[] split = docId.split(":"); + final String type = split[0]; + + String fullPath = getPathFromExtSD(split); + if (fullPath != "") { + return fullPath; + } else { + return null; + } + } + + // DownloadsProvider + else if (isDownloadsDocument(uri)) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + final String id; + Cursor cursor = null; + try { + cursor = context.getContentResolver().query(uri, new String[]{MediaStore.MediaColumns.DISPLAY_NAME}, null, null, null); + if (cursor != null && cursor.moveToFirst()) { + String fileName = cursor.getString(0); + String path = Environment.getExternalStorageDirectory().toString() + "/Download/" + fileName; + if (!TextUtils.isEmpty(path)) { + return path; + } + } + } finally { + if (cursor != null) + cursor.close(); + } + id = DocumentsContract.getDocumentId(uri); + if (!TextUtils.isEmpty(id)) { + if (id.startsWith("raw:")) { + return id.replaceFirst("raw:", ""); + } + String[] contentUriPrefixesToTry = new String[]{ + "content://downloads/public_downloads", + "content://downloads/my_downloads" + }; + for (String contentUriPrefix : contentUriPrefixesToTry) { + try { + final Uri contentUri = ContentUris.withAppendedId(Uri.parse(contentUriPrefix), Long.valueOf(id)); + + /* final Uri contentUri = ContentUris.withAppendedId( + Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));*/ + + return getDataColumn(context, contentUri, null, null); + } catch (NumberFormatException e) { + //In Android 8 and Android P the id is not a number + return uri.getPath().replaceFirst("^/document/raw:", "").replaceFirst("^raw:", ""); + } + } + + + } + + } else { + final String id = DocumentsContract.getDocumentId(uri); + final boolean isOreo = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O; + if (id.startsWith("raw:")) { + return id.replaceFirst("raw:", ""); + } + try { + contentUri = ContentUris.withAppendedId( + Uri.parse("content://downloads/public_downloads"), Long.valueOf(id)); + + } catch (NumberFormatException e) { + e.printStackTrace(); + } + if (contentUri != null) { + return getDataColumn(context, contentUri, null, null); + } + } + + + } + // MediaProvider + else if (isMediaDocument(uri)) { + final String docId = DocumentsContract.getDocumentId(uri); + final String[] split = docId.split(":"); + final String type = split[0]; + + Uri contentUri = null; + + if ("image".equals(type)) { + contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; + } else if ("video".equals(type)) { + contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI; + } else if ("audio".equals(type)) { + contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI; + } + selection = "_id=?"; + selectionArgs = new String[]{split[1]}; + + + return getDataColumn(context, contentUri, selection, + selectionArgs); + } else if (isGoogleDriveUri(uri)) { + return getDriveFilePath(uri, context); + } + } + + + // MediaStore (and general) + else if ("content".equalsIgnoreCase(uri.getScheme())) { + + if (isGooglePhotosUri(uri)) { + return uri.getLastPathSegment(); + } + + if (isGoogleDriveUri(uri)) { + return getDriveFilePath(uri, context); + } + if (Build.VERSION.SDK_INT == Build.VERSION_CODES.N) { + // return getFilePathFromURI(context,uri); + return getMediaFilePathForN(uri, context); + // return getRealPathFromURI(context,uri); + } else { + + return getDataColumn(context, uri, null, null); + } + + + } + // File + else if ("file".equalsIgnoreCase(uri.getScheme())) { + return uri.getPath(); + } + + return null; + } + + /** + * Check if a file exists on device + * + * @param filePath The absolute file path + */ + private static boolean fileExists(String filePath) { + File file = new File(filePath); + + return file.exists(); + } + + + /** + * Get full file path from external storage + * + * @param pathData The storage type and the relative path + */ + private static String getPathFromExtSD(String[] pathData) { + final String type = pathData[0]; + final String relativePath = "/" + pathData[1]; + String fullPath = ""; + + // on my Sony devices (4.4.4 & 5.1.1), `type` is a dynamic string + // something like "71F8-2C0A", some kind of unique id per storage + // don't know any API that can get the root path of that storage based on its id. + // + // so no "primary" type, but let the check here for other devices + if ("primary".equalsIgnoreCase(type)) { + fullPath = Environment.getExternalStorageDirectory() + relativePath; + if (fileExists(fullPath)) { + return fullPath; + } + } + + // Environment.isExternalStorageRemovable() is `true` for external and internal storage + // so we cannot relay on it. + // + // instead, for each possible path, check if file exists + // we'll start with secondary storage as this could be our (physically) removable sd card + fullPath = System.getenv("SECONDARY_STORAGE") + relativePath; + if (fileExists(fullPath)) { + return fullPath; + } + + fullPath = System.getenv("EXTERNAL_STORAGE") + relativePath; + if (fileExists(fullPath)) { + return fullPath; + } + + return fullPath; + } + + private static String getDriveFilePath(Uri uri, Context context) { + Uri returnUri = uri; + Cursor returnCursor = context.getContentResolver().query(returnUri, null, null, null, null); + /* + * Get the column indexes of the data in the Cursor, + * * move to the first row in the Cursor, get the data, + * * and display it. + * */ + int nameIndex = returnCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME); + int sizeIndex = returnCursor.getColumnIndex(OpenableColumns.SIZE); + returnCursor.moveToFirst(); + String name = (returnCursor.getString(nameIndex)); + String size = (Long.toString(returnCursor.getLong(sizeIndex))); + File file = new File(context.getCacheDir(), name); + try { + InputStream inputStream = context.getContentResolver().openInputStream(uri); + FileOutputStream outputStream = new FileOutputStream(file); + int read = 0; + int maxBufferSize = 1 * 1024 * 1024; + int bytesAvailable = inputStream.available(); + + //int bufferSize = 1024; + int bufferSize = Math.min(bytesAvailable, maxBufferSize); + + final byte[] buffers = new byte[bufferSize]; + while ((read = inputStream.read(buffers)) != -1) { + outputStream.write(buffers, 0, read); + } + Log.e("File Size", "Size " + file.length()); + inputStream.close(); + outputStream.close(); + Log.e("File Path", "Path " + file.getPath()); + Log.e("File Size", "Size " + file.length()); + } catch (Exception e) { + Log.e("Exception", e.getMessage()); + } + return file.getPath(); + } + + private static String getMediaFilePathForN(Uri uri, Context context) { + Uri returnUri = uri; + Cursor returnCursor = context.getContentResolver().query(returnUri, null, null, null, null); + /* + * Get the column indexes of the data in the Cursor, + * * move to the first row in the Cursor, get the data, + * * and display it. + * */ + int nameIndex = returnCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME); + int sizeIndex = returnCursor.getColumnIndex(OpenableColumns.SIZE); + returnCursor.moveToFirst(); + String name = (returnCursor.getString(nameIndex)); + String size = (Long.toString(returnCursor.getLong(sizeIndex))); + File file = new File(context.getFilesDir(), name); + try { + InputStream inputStream = context.getContentResolver().openInputStream(uri); + FileOutputStream outputStream = new FileOutputStream(file); + int read = 0; + int maxBufferSize = 1 * 1024 * 1024; + int bytesAvailable = inputStream.available(); + + //int bufferSize = 1024; + int bufferSize = Math.min(bytesAvailable, maxBufferSize); + + final byte[] buffers = new byte[bufferSize]; + while ((read = inputStream.read(buffers)) != -1) { + outputStream.write(buffers, 0, read); + } + Log.e("File Size", "Size " + file.length()); + inputStream.close(); + outputStream.close(); + Log.e("File Path", "Path " + file.getPath()); + Log.e("File Size", "Size " + file.length()); + } catch (Exception e) { + Log.e("Exception", e.getMessage()); + } + return file.getPath(); + } + + + private static String getDataColumn(Context context, Uri uri, + String selection, String[] selectionArgs) { + Cursor cursor = null; + final String column = "_data"; + final String[] projection = {column}; + + try { + cursor = context.getContentResolver().query(uri, projection, + selection, selectionArgs, null); + + if (cursor != null && cursor.moveToFirst()) { + final int index = cursor.getColumnIndexOrThrow(column); + return cursor.getString(index); + } + } finally { + if (cursor != null) + cursor.close(); + } + + return null; + } + + /** + * @param uri - The Uri to check. + * @return - Whether the Uri authority is ExternalStorageProvider. + */ + private static boolean isExternalStorageDocument(Uri uri) { + return "com.android.externalstorage.documents".equals(uri.getAuthority()); + } + + /** + * @param uri - The Uri to check. + * @return - Whether the Uri authority is DownloadsProvider. + */ + private static boolean isDownloadsDocument(Uri uri) { + return "com.android.providers.downloads.documents".equals(uri.getAuthority()); + } + + /** + * @param uri - The Uri to check. + * @return - Whether the Uri authority is MediaProvider. + */ + private static boolean isMediaDocument(Uri uri) { + return "com.android.providers.media.documents".equals(uri.getAuthority()); + } + + /** + * @param uri - The Uri to check. + * @return - Whether the Uri authority is Google Photos. + */ + private static boolean isGooglePhotosUri(Uri uri) { + return "com.google.android.apps.photos.content".equals(uri.getAuthority()); + } + + + /** + * @param uri The Uri to check. + * @return Whether the Uri authority is Google Drive. + */ + private static boolean isGoogleDriveUri(Uri uri) { + return "com.google.android.apps.docs.storage".equals(uri.getAuthority()) || "com.google.android.apps.docs.storage.legacy".equals(uri.getAuthority()); + } + +} diff --git a/app/src/main/java/com/jens/automation2/Miscellaneous.java b/app/src/main/java/com/jens/automation2/Miscellaneous.java new file mode 100644 index 0000000..1eadfc7 --- /dev/null +++ b/app/src/main/java/com/jens/automation2/Miscellaneous.java @@ -0,0 +1,887 @@ +package com.jens.automation2; + +import android.annotation.SuppressLint; +import android.app.AlertDialog; +import android.app.Notification; +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.app.Service; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.database.Cursor; +import android.net.Uri; +import android.os.Build; +import android.os.Environment; +import android.os.IBinder; +import android.provider.MediaStore; +import android.provider.Settings.Secure; +import android.util.Base64; +import android.util.Log; +import android.widget.Toast; + +import androidx.core.app.NotificationCompat; + +import com.jens.automation2.location.LocationProvider; +import com.jens.automation2.receivers.PhoneStatusListener; + +import org.apache.http.HttpEntity; +import org.apache.http.HttpResponse; +import org.apache.http.HttpVersion; +import org.apache.http.client.HttpClient; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.conn.ssl.SSLSocketFactory; +import org.apache.http.impl.client.DefaultHttpClient; +import org.apache.http.params.BasicHttpParams; +import org.apache.http.params.HttpParams; +import org.apache.http.params.HttpProtocolParams; +import org.apache.http.util.EntityUtils; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.lang.Thread.UncaughtExceptionHandler; +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.net.HttpURLConnection; +import java.net.URL; +import java.security.KeyManagementException; +import java.security.KeyStore; +import java.security.NoSuchAlgorithmException; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.sql.Time; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Date; + +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSession; +import javax.net.ssl.TrustManager; +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; + public static final String lineSeparator = System.getProperty("line.separator"); + + public static String downloadURL(String url, String username, String password) + { + HttpClient httpclient = new DefaultHttpClient(); + StringBuilder responseBody = new StringBuilder(); + boolean errorFound = false; + + try + { + try + { + URL urlObject = new URL(url); + HttpURLConnection connection; + + if(url.toLowerCase().contains("https")) + { + connection = (HttpsURLConnection) urlObject.openConnection(); + +// if(Settings.httpAcceptAllCertificates) +// { +// SSLContext sc = SSLContext.getInstance("TLS"); +// sc.init(null, getInsecureTrustManager(), new java.security.SecureRandom()); +// HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory()); +// Miscellaneous.disableSSLCertificateChecking(); +// HttpsURLConnection.setDefaultHostnameVerifier(getInsecureHostnameVerifier()); +// } + } + else + connection = (HttpURLConnection) urlObject.openConnection(); + + // Add http simple authentication if specified + if(username != null && password != null) + { + String encodedCredentials = Base64.encodeToString(new String(username + ":" + password).getBytes(), Base64.DEFAULT); + connection.setRequestMethod("POST"); + connection.setDoOutput(true); + connection.setRequestProperty ("Authorization", "Basic " + encodedCredentials); + } + + InputStream content = (InputStream)connection.getInputStream(); + BufferedReader in = new BufferedReader (new InputStreamReader (content)); + String line; + while ((line = in.readLine()) != null) + responseBody.append(line + Miscellaneous.lineSeparator); + } + catch(Exception e) + { + Miscellaneous.logEvent("e", "HTTP error", Log.getStackTraceString(e), 3); + errorFound = true; + } + } + finally + { + // When HttpClient instance is no longer needed, + // shut down the connection manager to ensure + // immediate deallocation of all system resources + httpclient.getConnectionManager().shutdown(); + if(errorFound) + return "httpError"; + else + return responseBody.toString(); + } + } + + public static String downloadURLwithoutCertificateChecking(String url, String username, String password) + { +// HttpClient httpclient = new DefaultHttpClient(); +// StringBuilder responseBody = new StringBuilder(); + boolean errorFound = false; + + try + { + HttpParams params = new BasicHttpParams(); + params.setParameter(HttpProtocolParams.USE_EXPECT_CONTINUE, false); + HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1); + HttpClient httpclient = new DefaultHttpClient(params); + httpclient = Actions.getInsecureSslClient(httpclient); + + HttpPost httppost = new HttpPost(url); + + // Add http simple authentication if specified + if(username != null && password != null) + { + String encodedCredentials = Base64.encodeToString(new String(username + ":" + password).getBytes(), Base64.DEFAULT); +// List nameValuePairs = new ArrayList(1); + httppost.addHeader("Authorization", "Basic " + encodedCredentials); +// nameValuePairs.add(new BasicNameValuePair("Authorization", "Basic " + encodedCredentials)); +// httppost.setEntity(new UrlEncodedFormEntity(nameValuePairs, "UTF-8")); + } + + HttpResponse response = httpclient.execute(httppost); + HttpEntity entity = response.getEntity(); + if (entity != null) + { +// System.out.println(EntityUtils.toString(entity)); + return EntityUtils.toString(entity); + } + } + catch(Exception e) + { + Miscellaneous.logEvent("e", "HTTP error", Log.getStackTraceString(e), 3); + errorFound = true; + return "httpError"; + } +// finally +// { +// // When HttpClient instance is no longer needed, +// // shut down the connection manager to ensure +// // immediate deallocation of all system resources +// httpclient.getConnectionManager().shutdown(); +// return responseBody.toString(); +// } + + return null; + } + + @Override + public IBinder onBind(Intent arg0) + { + // TODO Auto-generated method stub + return null; + } + +// public static void logEvent(String type, String header, String description) +// { +// if(type.equals("e")) +// Log.e(header, description); +// +// if(type.equals("w")) +// Log.w(header, description); +// +// if(type.equals("i")) +// Log.i(header, description); +// +// if(Settings.writeLogFile) +// writeToLogFile(type, header, description); +// } + + public static void logEvent(String type, String header, String description, int logLevel) + { + try + { + header = getAnyContext().getResources().getString(R.string.app_name); + } + catch(NullPointerException e) + { + header = "Automation"; + } + + if(type.equals("e")) + Log.e(header, description); + + if(type.equals("w")) + Log.w(header, description); + + if(type.equals("i")) + Log.i(header, description); + + if(Settings.writeLogFile && Settings.logLevel >= logLevel) + { + writeToLogFile(type, header, description); + + if(!logCleanerRunning && Math.random() < 0.1) // tidy up with 10% probability + { + rotateLogFile(getLogFile()); + } + } + } + + protected static boolean logCleanerRunning = false; + protected static void rotateLogFile(File logFile) + { + logCleanerRunning = true; + + + long maxSizeInBytes = (long)Settings.logFileMaxSize * 1024 * 1024; + + if(logFile.exists() && logFile.length() > (maxSizeInBytes)) + { + Miscellaneous.logEvent("i", "Logfile", "Cleaning up log file.", 3); + File archivedLogFile = new File(getWriteableFolder() + "/" + logFileName + "-old"); + logFile.renameTo(archivedLogFile); + Miscellaneous.logEvent("i", "Logfile", "Cleaning up log file finished. Old log renamed to " + archivedLogFile.getAbsolutePath(), 3); + } + + logCleanerRunning = false; + } + + protected static boolean testFolder(String folderPath) + { + File folder = new File(folderPath + "/" + Settings.folderName); + final String testFileName = "AutomationTestFile.txt"; + + try + { + if(folder.exists() || folder.mkdirs()) + { + XmlFileInterface.migrateFilesFromRootToFolder(folderPath, folder.getAbsolutePath()); + + File testFile = new File(folder + "/" + testFileName); + if(!testFile.exists()) + testFile.createNewFile(); + + if(testFile.canRead() && testFile.canWrite()) + { + testFile.delete(); + writeableFolderStringCache = testFile.getParent(); + Miscellaneous.logEvent("i", "File", "Test of " + folder.getAbsolutePath() + " succeeded.", 3); + return true; + } + } + } + catch (IOException e) + { + e.printStackTrace(); + } + + Miscellaneous.logEvent("w", "File", "Test of " + folder.getAbsolutePath() + " failed.", 3); + return false; + } + + public static String getWriteableFolder() + { + if(writeableFolderStringCache == null) + { + String testPath = null; + File folder = null; + + try + { + String[] foldersToTestArray = new String[] + { + Environment.getExternalStorageDirectory().getAbsolutePath(), + "/storage/emulated/0", + "/HWUserData", + "/mnt/sdcard" + }; + + for(String f : foldersToTestArray) + { + if (testFolder(f)) + { + String pathToUse = f + "/" + Settings.folderName; + Miscellaneous.logEvent("i", "Path", "Using " + pathToUse + " to store settings and log.", 2); +// Toast.makeText(getAnyContext(), "Using " + pathToUse + " to store settings and log.", Toast.LENGTH_LONG).show(); + return pathToUse; + } + else + Miscellaneous.logEvent("e", "getWritableFolder", folder.getAbsolutePath() + " does not exist and could not be created.", 3); + } + } + catch(Exception e) + { + Log.w("getWritableFolder", folder + " not writable."); + } + + // do not change to logEvent() - we can't write + Toast.makeText(getAnyContext(), "No writable folder could be found.", Toast.LENGTH_LONG).show(); + Log.e("getWritableFolder", "No writable folder could be found."); + + return null; + } + else + return writeableFolderStringCache; + } + + protected final static String logFileName = "Automation_logfile.txt"; + protected static File getLogFile() + { + File logFile = null; + logFile = new File(getWriteableFolder() + "/" + logFileName); + if(!logFile.exists()) + { + Log.i("LogFile", "Creating new logfile: " + logFile.getAbsolutePath()); + try + { + logFile.createNewFile(); + } + catch(Exception e) + { + Log.e("LogFile", "Error writing logs to file: " + e.getMessage()); + } + } + + return logFile; + } + private static void writeToLogFile(String type, String header, String description) + { + try + { + FileWriter fileWriter = new FileWriter(getLogFile(), true); + BufferedWriter bufferedWriter = new BufferedWriter(fileWriter); + Date date = new Date(); + + bufferedWriter.write("\n" + date + ": " + type + " / " + header + " / " + description); + bufferedWriter.close(); + +// Log.i("LogFile", "Log entry written."); + } + catch(Exception e) + { + Log.e("LogFile", "Error writing logs to file: " + e.getMessage()); + } + } + + public static boolean isAndroidEmulator() + { + String TAG = "EmulatorTest"; + String model = Build.MODEL; +// Miscellaneous.logEvent("i", TAG, "model=" + model); + String product = Build.PRODUCT; +// Miscellaneous.logEvent("i", TAG, "product=" + product); + boolean isEmulator = false; + if (product != null) + { + isEmulator = product.equals("sdk") || product.contains("_sdk") || product.contains("sdk_"); + } +// Miscellaneous.logEvent("i", TAG, "isEmulator=" + isEmulator); + return isEmulator; + } + + public static int compareTimes(Time time1, Time time2) + { +// Miscellaneous.logEvent("i", "TimeCompare", "To compare: " + time1.toString() + " / " + time2.toString()); + + if(time1.getHours() == time2.getHours() && time1.getMinutes() == time2.getMinutes()) + { +// Miscellaneous.logEvent("i", "TimeCompare", "Times are equal."); + return 0; + } + + if(time1.getHours() > time2.getHours()) + { +// Miscellaneous.logEvent("i", "TimeCompare", "Time1 is bigger/later by hours."); + return -1; + } + + if(time1.getHours() < time2.getHours()) + { +// Miscellaneous.logEvent("i", "TimeCompare", "Time2 is bigger/later by hours."); + return 1; + } + + if(time1.getHours() == time2.getHours()) + { + if(time1.getMinutes() < time2.getMinutes()) + { +// Miscellaneous.logEvent("i", "TimeCompare", "Hours are equal. Time2 is bigger/later by minutes."); + return 1; + } + + if(time1.getMinutes() > time2.getMinutes()) + { +// Miscellaneous.logEvent("i", "TimeCompare", "Hours are equal. Time1 is bigger/later by minutes."); + return -1; + } + } + + + Miscellaneous.logEvent("i", "TimeCompare", "Default return code. Shouldn't be here.", 5); + return 0; + + } + + public static String convertStreamToString(InputStream is) + { + java.util.Scanner s = new java.util.Scanner(is).useDelimiter("\\A"); + return s.hasNext() ? s.next() : ""; + } + + public static Context getAnyContext() + { + Context returnContext; + + returnContext = AutomationService.getInstance(); + if(returnContext != null) + return returnContext; + + returnContext = ActivityMainScreen.getActivityMainScreenInstance(); + if(returnContext != null) + return returnContext; + + returnContext = ActivityPermissions.getInstance().getApplicationContext(); + if(returnContext != null) + return returnContext; + + return null; + } + + @SuppressLint("NewApi") + public static String replaceVariablesInText(String source, Context context) throws Exception + { + // Replace variable with actual content +// Miscellaneous.logEvent("i", "Raw source", source); + if(source.contains("[uniqueid]")) + source = source.replace("[uniqueid]", Secure.getString(context.getContentResolver(), Secure.ANDROID_ID)); + + if(source.contains("[latitude]") | source.contains("[longitude]")) + { + if(LocationProvider.getLastKnownLocation() != null) + { + source = source.replace("[latitude]", String.valueOf(LocationProvider.getLastKnownLocation().getLatitude())); + source = source.replace("[longitude]", String.valueOf(LocationProvider.getLastKnownLocation().getLongitude())); + } + else + { + Miscellaneous.logEvent("w", "TriggerURL", context.getResources().getString(R.string.triggerUrlReplacementPositionError), 3); + } + } + + if(source.contains("[phonenr]")) + { + String lastPhoneNr = PhoneStatusListener.getLastPhoneNumber(); + + if(lastPhoneNr != null && lastPhoneNr.length() > 0) + source = source.replace("[phonenr]", PhoneStatusListener.getLastPhoneNumber()); + else + Miscellaneous.logEvent("w", "TriggerURL", context.getResources().getString(R.string.triggerUrlReplacementPositionError), 3); + } + + if(source.contains("[serialnr]")) + if(Build.VERSION.SDK_INT > 8) + source = source.replace("[serialnr]", Secure.getString(context.getContentResolver(), Build.SERIAL)); + else + source = source.replace("[serialnr]", "serialUnknown"); + + if( + source.contains("[d]") | + source.contains("[m]") | + source.contains("[Y]") | + source.contains("[h]") | + source.contains("[H]") | + source.contains("[i]") | + source.contains("[s]") | + source.contains("[ms]") + ) + { + Calendar cal = Calendar.getInstance(); + + source = source.replace("[d]", String.valueOf(cal.get(Calendar.DAY_OF_MONTH))); + source = source.replace("[m]", String.valueOf(cal.get(Calendar.MONTH))); + source = source.replace("[Y]", String.valueOf(cal.get(Calendar.YEAR))); + source = source.replace("[h]", String.valueOf(cal.get(Calendar.HOUR))); + source = source.replace("[H]", String.valueOf(cal.get(Calendar.HOUR_OF_DAY))); + source = source.replace("[i]", String.valueOf(cal.get(Calendar.MINUTE))); + source = source.replace("[s]", String.valueOf(cal.get(Calendar.SECOND))); + source = source.replace("[ms]", String.valueOf(cal.get(Calendar.MILLISECOND))); + } + +// Miscellaneous.logEvent("i", "URL after replace", source); + + return source; + } + + /** + * Write a log entry and exit the application, so the crash is actually visible. + * Might even cause the activity to be automatically restarted by the OS. + */ + public static UncaughtExceptionHandler uncaughtExceptionHandler = new UncaughtExceptionHandler() + { + @Override + public void uncaughtException(Thread thread, Throwable ex) + { + Miscellaneous.logEvent("e", "UncaughtException", Log.getStackTraceString(ex), 1); + System.exit(0); + } + }; + + public static AlertDialog messageBox(String title, String message, Context context) + { + AlertDialog.Builder alertDialog = new AlertDialog.Builder(context); + + alertDialog.setTitle(title); + alertDialog.setMessage(message); + + alertDialog.setPositiveButton("Ok", new DialogInterface.OnClickListener() + { + public void onClick(DialogInterface dialog, int whichButton) + { + dialog.dismiss(); + } + }); + +// alertDialog.setNegativeButton(context.getResources().getString(R.string.cancel), new DialogInterface.OnClickListener() +// { +// public void onClick(DialogInterface dialog, int whichButton) +// { +// // Canceled. +// } +// }); + + return alertDialog.create(); + } + + /** + * Checks if the device is rooted. + * + * @return true if the device is rooted, false otherwise. + */ + public static boolean isPhoneRooted() + { + // get from build info + String buildTags = Build.TAGS; + if (buildTags != null && buildTags.contains("test-keys")) { + return true; + } + + // check if /system/app/Superuser.apk is present + try + { + File file = new File("/system/app/Superuser.apk"); + if (file.exists()) + { + return true; + } + } + catch (Exception e1) + { + // ignore + } + + // try executing commands + return canExecuteCommand("/system/xbin/which su") + || + canExecuteCommand("/system/bin/which su") + || + canExecuteCommand("which su"); + } + + // executes a command on the system + private static boolean canExecuteCommand(String command) + { + boolean executedSuccesfully; + try + { + Runtime.getRuntime().exec(command); + executedSuccesfully = true; + } + catch (Exception e) + { + executedSuccesfully = false; + } + + return executedSuccesfully; + } + + public static boolean isNumeric(String str) + { + return str.matches("-?\\d+(\\.\\d+)?"); //match a number with optional '-' and decimal. + } + + /** + * Disables the SSL certificate checking for new instances of {@link HttpsURLConnection} This has been created to + * aid testing on a local box, not for use on production. + */ + private static void disableSSLCertificateChecking() + { + try + { + SSLSocketFactory ssf = null; + + try + { + SSLContext ctx = SSLContext.getInstance("TLS"); + + KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType()); + trustStore.load(null, null); + ssf = new MySSLSocketFactoryInsecure(trustStore); + ssf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER); + ctx.init(null, null, null); + +// return new DefaultHttpClient(ccm, client.getParams()); + } + catch (Exception ex) + { + ex.printStackTrace(); +// return null; + } + + // Install the all-trusting trust manager + SSLContext sc = SSLContext.getInstance("TLS"); + sc.init(null, getInsecureTrustManager(), new java.security.SecureRandom()); +// HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory()); +// HttpsURLConnection.setDefaultSSLSocketFactory(ssf); + + // Install the all-trusting host verifier + HttpsURLConnection.setDefaultHostnameVerifier(getInsecureHostnameVerifier()); + HttpsURLConnection.setDefaultHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER); + } + catch (KeyManagementException e) + { + // TODO Auto-generated catch block + e.printStackTrace(); + } + catch (NoSuchAlgorithmException e) + { + // TODO Auto-generated catch block + e.printStackTrace(); + } + finally + { + + } + } + + public static TrustManager[] getInsecureTrustManager() + { + TrustManager[] trustAllCerts = + new TrustManager[] + { + new X509TrustManager() + { + public X509Certificate[] getAcceptedIssuers() + { + return null; + } + + @Override + public void checkClientTrusted(X509Certificate[] arg0, String arg1) throws CertificateException + { + // Not implemented + } + + @Override + public void checkServerTrusted(X509Certificate[] arg0, String arg1) throws CertificateException + { + // Not implemented + } + } + }; + + return trustAllCerts; + } + + public static HostnameVerifier getInsecureHostnameVerifier() + { + HostnameVerifier allHostsValid = new HostnameVerifier() + { + public boolean verify(String hostname, SSLSession session) + { + return true; + } + }; + + return allHostsValid; + } + + @SuppressLint("NewApi") + @SuppressWarnings("deprecation") + public static void createDismissableNotification(String textToDisplay, int notificationId, PendingIntent pendingIntent) + { + NotificationManager mNotificationManager = (NotificationManager) Miscellaneous.getAnyContext().getSystemService(Context.NOTIFICATION_SERVICE); + + NotificationCompat.Builder dismissableNotificationBuilder = createDismissableNotificationBuilder(pendingIntent); + dismissableNotificationBuilder.setContentText(textToDisplay); + dismissableNotificationBuilder.setContentIntent(pendingIntent); + dismissableNotificationBuilder.setStyle(new NotificationCompat.BigTextStyle().bigText(textToDisplay)); + + Notification dismissableNotification = dismissableNotificationBuilder.build(); + + mNotificationManager.notify(notificationId, dismissableNotification); + + /*NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(this) + .setSmallIcon(R.drawable.ic_launcher) // notification icon + .setContentTitle("Notification!") // title for notification + .setContentText("Hello word") // message for notification + .setAutoCancel(true); // clear notification after click + Intent intent = new Intent(this, MainActivity.class); + PendingIntent pi = PendingIntent.getActivity(this,0,intent,Intent.FLAG_ACTIVITY_NEW_TASK); + mBuilder.setContentIntent(pi); + NotificationManager mNotificationManager = + (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); + mNotificationManager.notify(0, dismissableNotification);*/ + } + + /*protected static Notification.Builder createDismissableNotificationBuilder() + { + Notification.Builder builder = new Notification.Builder(AutomationService.getInstance()); + builder.setContentTitle("Automation"); + builder.setSmallIcon(R.drawable.ic_launcher); + builder.setCategory(Notification.CATEGORY_EVENT); + builder.setWhen(System.currentTimeMillis()); + + //static PendingIntent myPendingIntent = PendingIntent.getActivity(this, 0, myIntent, 0); + + //builder.setContentIntent(myPendingIntent); + +// Notification defaultNotification = new Notification(); +*//* Notification defaultNotification = builder.build(); + + defaultNotification.icon = R.drawable.ic_launcher; + defaultNotification.when = System.currentTimeMillis(); + +// defaultNotification.defaults |= Notification.DEFAULT_VIBRATE; +// defaultNotification.defaults |= Notification.DEFAULT_LIGHTS; + + defaultNotification.flags |= Notification.FLAG_AUTO_CANCEL; +// defaultNotification.flags |= Notification.FLAG_SHOW_LIGHTS; + defaultNotification.flags |= Notification.FLAG_ONLY_ALERT_ONCE; + +// defaultNotification.ledARGB = Color.YELLOW; +// defaultNotification.ledOnMS = 1500; +// defaultNotification.ledOffMS = 1500; +*//* + return builder; + }*/ + + protected static NotificationCompat.Builder createDismissableNotificationBuilder(PendingIntent myPendingIntent) + { + NotificationManager mNotificationManager = (NotificationManager) AutomationService.getInstance().getSystemService(Context.NOTIFICATION_SERVICE); + + NotificationCompat.Builder builder; + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) + { + NotificationChannel chan = new NotificationChannel(NOTIFICATION_CHANNEL_ID, channelName, NotificationManager.IMPORTANCE_LOW); +// chan.setLightColor(Color.BLUE); +// chan.enableVibration(false); +// chan.setSound(null, null); + chan.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE); + mNotificationManager.createNotificationChannel(chan); + + builder = new NotificationCompat.Builder(AutomationService.getInstance(), NOTIFICATION_CHANNEL_ID); + } + else + builder = new NotificationCompat.Builder(AutomationService.getInstance()); + + if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) + builder.setCategory(Notification.CATEGORY_SERVICE); + + builder.setWhen(System.currentTimeMillis()); + builder.setContentIntent(myPendingIntent); + + builder.setContentTitle(AutomationService.getInstance().getResources().getString(R.string.app_name)); +// builder.setOnlyAlertOnce(true); + + builder.setSmallIcon(R.drawable.priority); + +// builder.setContentText(textToDisplay); +// builder.setSmallIcon(icon); +// builder.setStyle(new NotificationCompat.BigTextStyle().bigText(textToDisplay)); + + return builder; + } + + public static String explode(ArrayList arrayList) + { + StringBuilder builder = new StringBuilder(); + for(String s : arrayList) + builder.append(s); + + return builder.toString(); + } + + public static boolean isGooglePlayInstalled(Context context) + { +// return false; + PackageManager pm = context.getPackageManager(); + boolean app_installed = false; + try + { + PackageInfo info = pm.getPackageInfo("com.android.vending", PackageManager.GET_ACTIVITIES); + String label = (String) info.applicationInfo.loadLabel(pm); + app_installed = (label != null && !label.equals("Market")); + } + catch (PackageManager.NameNotFoundException e) + { + app_installed = false; + } + return app_installed; + } + + public static double round(double value, int places) + { + if (places < 0) throw new IllegalArgumentException(); + + BigDecimal bd = new BigDecimal(Double.toString(value)); + bd = bd.setScale(places, RoundingMode.HALF_UP); + return bd.doubleValue(); + } + + public static String getRealPathFromURI(Context context, Uri contentUri) + { + Cursor cursor = null; + try + { + String[] proj = { MediaStore.Images.Media.DATA }; + cursor = context.getContentResolver().query(contentUri, proj, null, null, null); + int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA); + cursor.moveToFirst(); + return cursor.getString(column_index); + } + catch (Exception e) + { + Miscellaneous.logEvent("e", "Uri", "getRealPathFromURI Exception : " + Log.getStackTraceString(e), 1); + return null; + } + finally + { + if (cursor != null) + { + cursor.close(); + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/jens/automation2/MyHttpClient.java b/app/src/main/java/com/jens/automation2/MyHttpClient.java new file mode 100644 index 0000000..6005b1a --- /dev/null +++ b/app/src/main/java/com/jens/automation2/MyHttpClient.java @@ -0,0 +1,57 @@ +package com.jens.automation2; + +import android.content.Context; + +import org.apache.http.conn.ClientConnectionManager; +import org.apache.http.conn.scheme.PlainSocketFactory; +import org.apache.http.conn.scheme.Scheme; +import org.apache.http.conn.scheme.SchemeRegistry; +import org.apache.http.conn.ssl.SSLSocketFactory; +import org.apache.http.impl.client.DefaultHttpClient; +import org.apache.http.impl.conn.SingleClientConnManager; + +import java.security.KeyStore; + +public class MyHttpClient extends DefaultHttpClient +{ + + final Context context; + + public MyHttpClient(Context context) + { + this.context = context; + } + + @Override + protected ClientConnectionManager createClientConnectionManager() + { + SchemeRegistry registry = new SchemeRegistry(); + registry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80)); + // Register for port 443 our SSLSocketFactory with our keystore + // to the ConnectionManager + registry.register(new Scheme("https", newSslSocketFactory(), 443)); + return new SingleClientConnManager(getParams(), registry); + } + + private SSLSocketFactory newSslSocketFactory() + { + try + { + // Get an instance of the Bouncy Castle KeyStore format + KeyStore trusted = KeyStore.getInstance("BKS"); + // Get the raw resource, which contains the keystore with + // your trusted certificates (root and any intermediate certs) + // Pass the keystore to the SSLSocketFactory. The factory is responsible + // for the verification of the server certificate. + SSLSocketFactory sf = new SSLSocketFactory(trusted); + // Hostname verification from certificate + // http://hc.apache.org/httpcomponents-client-ga/tutorial/html/connmgmt.html#d4e506 + sf.setHostnameVerifier(SSLSocketFactory.STRICT_HOSTNAME_VERIFIER); + return sf; + } + catch (Exception e) + { + throw new AssertionError(e); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/jens/automation2/MySSLSocketFactoryInsecure.java b/app/src/main/java/com/jens/automation2/MySSLSocketFactoryInsecure.java new file mode 100644 index 0000000..2a5ebfb --- /dev/null +++ b/app/src/main/java/com/jens/automation2/MySSLSocketFactoryInsecure.java @@ -0,0 +1,57 @@ +package com.jens.automation2; + +import org.apache.http.conn.ssl.SSLSocketFactory; + +import java.io.IOException; +import java.net.Socket; +import java.net.UnknownHostException; +import java.security.KeyManagementException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.UnrecoverableKeyException; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; + +public class MySSLSocketFactoryInsecure extends SSLSocketFactory +{ + SSLContext sslContext = SSLContext.getInstance("TLS"); + + public MySSLSocketFactoryInsecure(KeyStore truststore) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException { + super(truststore); + + TrustManager tm = new X509TrustManager() + { + public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException + { + } + + public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException + { + } + + public X509Certificate[] getAcceptedIssuers() + { + return null; + } + }; + + sslContext.init(null, new TrustManager[] { tm }, null); + } + + @Override + public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException, UnknownHostException + { + return sslContext.getSocketFactory().createSocket(socket, host, port, autoClose); + } + + @Override + public Socket createSocket() throws IOException + { + return sslContext.getSocketFactory().createSocket(); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/jens/automation2/PointOfInterest.java b/app/src/main/java/com/jens/automation2/PointOfInterest.java new file mode 100644 index 0000000..5f1b0ec --- /dev/null +++ b/app/src/main/java/com/jens/automation2/PointOfInterest.java @@ -0,0 +1,825 @@ +package com.jens.automation2; + +import android.content.Context; +import android.location.Criteria; +import android.location.Location; +import android.location.LocationListener; +import android.location.LocationManager; +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.util.Log; +import android.widget.Toast; + +import com.jens.automation2.Trigger.Trigger_Enum; + +import java.io.FileNotFoundException; +import java.util.ArrayList; +import java.util.Collections; + +public class PointOfInterest implements Comparable +{ + // The array containing all POIs + private static ArrayList pointOfInterestCollection = new ArrayList(); + + public static ArrayList getPointOfInterestCollection() + { + Collections.sort(pointOfInterestCollection); + return pointOfInterestCollection; + } + + public static void setPointOfInterestCollection(ArrayList pointOfInterestCollection) + { + Collections.sort(pointOfInterestCollection); + PointOfInterest.pointOfInterestCollection = pointOfInterestCollection; + } + + // name and location + private String name; + private Location location; + private double radius; + + private String oldName; + private boolean activated=false; + + private static Location[] locationRingBuffer = new Location[Settings.locationRingBufferSize]; + private static int locationRingBufferLastPosition = -1; + + private static boolean gpsLocationListenerArmed = false; + private static LocationManager gpsComparisonLocationManager; + private static GpsComparisonLocationListener gpsComparisonLocationListener; + private static TimeoutHandler timeoutHandler = new TimeoutHandler(); + private static boolean timeoutHandlerActive = false; + public String getName() + { + return name; + } + + public static void stopRoutine() + { + if(gpsLocationListenerArmed) + stopGpsMeasurement(); + } + + public void setName(String desiredName) + { + this.oldName = this.name; + this.name = desiredName; + } + + public Location getLocation() + { + return location; + } + + public void setLocation(Location location) + { + this.location = location; + } + + public double getRadius() + { + return radius; + } + + public void setRadius(double radius, Context context) throws Exception + { + if(radius <= 0) + throw new Exception(context.getResources().getString(R.string.radiusHasToBePositive)); + + this.radius = radius; + } + + public void setActivated(boolean value) + { + this.activated = value; + } + public boolean isActivated() + { + return activated; + } + + public static void positionUpdate(Location newLocation, AutomationService parentService, boolean forceApply, boolean skipVerfication) + { +// StackTraceElement[] trace = Thread.currentThread().getStackTrace(); +// for(StackTraceElement element : trace) +// { +// Log.i("Trace", Arrays.toString(trace)); +// } + + // Assumption "active POI = closest POI" is wrong! + + if(newLocation != null) + { + String accuracyString = "n./a."; + if(newLocation.hasAccuracy()) + accuracyString = String.valueOf(newLocation.getAccuracy() + " m"); + Miscellaneous.logEvent("i", "POI", "Got position update (" + String.valueOf(newLocation.getLatitude()) + " / " + String.valueOf(newLocation.getLongitude()) + " / provider: " + newLocation.getProvider() + " / Accuracy: " + accuracyString + "), checking rules.", 2); + + PointOfInterest closestPoi = PointOfInterest.getClosestPOI(newLocation); + + if(getActivePoi() != null) + Miscellaneous.logEvent("i", "POI", "Active POI: " + getActivePoi().getName() + ", distance : " + String.valueOf(newLocation.distanceTo(getActivePoi().getLocation())), 4); + + if(closestPoi == null) + { + // There are no POIs defined. Not much we can do. + // Miscellaneous.logEvent("i", "POI", "Closest POI: n/a, distance : n/a", 4); + Miscellaneous.logEvent("i", "POI", "Got position update, but there are no POIs defined. Can't trigger a rule.", 3); + // return; + } + else + Miscellaneous.logEvent("i", "POI", "Closest POI: " + closestPoi.getName() + ", distance : " + String.valueOf(newLocation.distanceTo(closestPoi.getLocation())), 4); + + if( + (getActivePoi() != null && getActivePoi().isActivated() && !getActivePoi().reachedPoiArea(newLocation)) + | + (closestPoi != null && !closestPoi.isActivated() && closestPoi.reachedPoiArea(newLocation)) + ) + { + // only an active POI can be left while only a closestPOI can be entered, hence the complex if/else + if(getActivePoi() != null && getActivePoi().isActivated() && !getActivePoi().reachedPoiArea(newLocation)) + Miscellaneous.logEvent("i", "POI", "May have left POI " + getActivePoi().getName() + ", checking location accuracy...", 4); + if(closestPoi != null && !closestPoi.isActivated() && closestPoi.reachedPoiArea(newLocation)) + Miscellaneous.logEvent("i", "POI", "May have entered POI " + closestPoi.getName() + ", checking location accuracy...", 4); + + if(forceApply) + { + Miscellaneous.logEvent("i", parentService.getResources().getString(R.string.forcedLocationUpdate), parentService.getResources().getString(R.string.forcedLocationUpdateLong), 4); + + // only an active POI can be left while only a closestPOI can be entered, hence the complex if/else + if(getActivePoi() != null && getActivePoi().isActivated() && !getActivePoi().reachedPoiArea(newLocation)) + { + addPositionToRingBuffer(newLocation); + getActivePoi().deactivate(parentService); + } + if(closestPoi != null && !closestPoi.isActivated() && closestPoi.reachedPoiArea(newLocation)) + { + addPositionToRingBuffer(newLocation); + closestPoi.activate(parentService); + } + } + else if(newLocation.hasAccuracy() && newLocation.getAccuracy() > Settings.satisfactoryAccuracyNetwork && !newLocation.getProvider().equals(LocationManager.GPS_PROVIDER)) + { + Miscellaneous.logEvent("i", "POI", "Location update with unsatisfactory accuracy: " + String.valueOf(newLocation.getAccuracy()) + ", demanded: " + String.valueOf(Settings.satisfactoryAccuracyNetwork), 4); + if(!skipVerfication) + { + if(PointOfInterest.isPoiInRelevantRange(newLocation)) + startGpsMeasurement(parentService); + else + { + Miscellaneous.logEvent("i", "POI", "Applying update with unsatisfactory accuracy because no defined location is in a relevant range.", 4); + positionUpdate(newLocation, parentService, true, false); + } + } + else + { + Miscellaneous.logEvent("i", "POI", "Location update with unsatisfactory accuracy, but skipping verfication as requested. Effectively ignoring this update. It's probably from a passive source. Verifying it would cost battery.", 4); + } + } + else + { + Miscellaneous.logEvent("i", "POI", "Location update with acceptable accuracy.", 4); + /* It may be that a previous location wasn't accurate enough, we now got a better location via network, + * but the GPS listener is still active and trying to find out a precise location. We need to deactivate + * it if we are here + */ + if(gpsLocationListenerArmed) + stopGpsMeasurement(); + + // only an active POI can be left while only a closestPOI can be entered, hence the complex if/else + if(getActivePoi() != null && getActivePoi().isActivated() && !getActivePoi().reachedPoiArea(newLocation)) + { + addPositionToRingBuffer(newLocation); + getActivePoi().deactivate(parentService); + } + if(closestPoi != null && !closestPoi.isActivated() && closestPoi.reachedPoiArea(newLocation)) + { + addPositionToRingBuffer(newLocation); + closestPoi.activate(parentService); + } + } + } + } + else + Miscellaneous.logEvent("e", "POI", "Given location is null. Aborting.", 3); + } + + public Boolean reachedPoiArea(Location currentLocation) + { + float distance = this.location.distanceTo(currentLocation); + + if(distance < this.radius) + { + return true; + } + else + { + return false; + } + } + + public void activate(AutomationService parentService) + { + if(!this.isActivated()) + { + // Deactivate all others in case nobody deactivated them + for(int i = 0; i < pointOfInterestCollection.size(); i++) + pointOfInterestCollection.get(i).deactivate(parentService); + + /* + ConcurrentModificationErrors have been seen when using this method: for(PointOfInterest onePoi : pointOfInterestCollection) + + Tue Nov 20 19:21:50 GMT+01:00 2018: e / Automation / java.util.ConcurrentModificationException + at java.util.ArrayList$Itr.next(ArrayList.java:860) + at com.jens.automation2.PointOfInterest.activate(PointOfInterest.java:227) + at com.jens.automation2.PointOfInterest.positionUpdate(PointOfInterest.java:199) + at com.jens.automation2.location.LocationProvider.setCurrentLocation(LocationProvider.java:126) + at com.jens.automation2.location.LocationProvider$MyPassiveLocationListener.onLocationChanged(LocationProvider.java:289) + at android.location.LocationManager$ListenerTransport._handleMessage(LocationManager.java:291) + at android.location.LocationManager$ListenerTransport.-wrap0(Unknown Source:0) + at android.location.LocationManager$ListenerTransport$1.handleMessage(LocationManager.java:236) + at android.os.Handler.dispatchMessage(Handler.java:105) + at android.os.Looper.loop(Looper.java:164) + at android.app.ActivityThread.main(ActivityThread.java:6944) + at java.lang.reflect.Method.invoke(Native Method) + at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:327) + at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1374) + */ + + this.activated = true; + Settings.lastActivePoi = this; + Settings.writeSettings(parentService); + + Miscellaneous.logEvent("i", "POI", "Reached POI " + this.getName() + ". Checking if there's a rule that applies to that.", 2); + + ArrayList ruleCandidates = Rule.findRuleCandidatesByPoi(this, true); + if(ruleCandidates.size()==0) + { + Miscellaneous.logEvent("i", "POI", "POI " + this.getName() + " not found in ANY rule.", 2); + } + else + { + Miscellaneous.logEvent("i", "POI", "POI " + this.getName() + " found in " + ruleCandidates.size() + " rule(s).", 2); + + for(int i=0; i ruleCandidates = Rule.findRuleCandidatesByPoi(this, false); + if(ruleCandidates.size()==0) + { + Miscellaneous.logEvent("i", "POI", "POI " + this.getName() + " not found in ANY rule.", 2); + } + else + { + Miscellaneous.logEvent("i", "POI", "POI " + this.getName() + " found in " + ruleCandidates.size() + " rule(s).", 2); + for(int i=0; i 1) + { + Toast.makeText(context, context.getResources().getString(R.string.anotherPoiByThatName), Toast.LENGTH_LONG).show(); + return false; + } + + // Check if rules reference this poi + ArrayList rulesThatReferenceMe = Rule.findRuleCandidatesByPoi(this); + if (rulesThatReferenceMe.size() > 0) + { + for (Rule oneRule : rulesThatReferenceMe) + { + for (Trigger oneTrigger : oneRule.getTriggerSet()) + { + if (oneTrigger.getTriggerType() == Trigger_Enum.pointOfInterest) + { + oneTrigger.setPointOfInterest(this); + // We don't need to save the file. This will happen anyway in PointOfInterest.writePoisToFile() below. + } + } + } + } + } + /* + Check for change of rule name END + */ + + if (plausibilityCheck()) + { + if(PointOfInterest.writePoisToFile()) + { + AutomationService service = AutomationService.getInstance(); + if (service != null) + { + service.applySettingsAndRules(); + //Easiest way to check for changes in location, reset the last known location. + service.getLocationProvider().setCurrentLocation(service.getLocationProvider().getCurrentLocation(), true); + } + + return true; + } + else + return false; + } + } + catch(Exception e) + { + Toast.makeText(context, context.getResources().getString(R.string.unknownError), Toast.LENGTH_LONG).show(); + } + + return false; + } + public boolean delete(Context context) + { + //Check if there's a rule that contains this poi + ArrayList rulesThatReferenceMe = Rule.findRuleCandidatesByPoi(this); + if(rulesThatReferenceMe.size() > 0) + { + String rulesString = ""; + for(Rule rule : rulesThatReferenceMe) + rulesString += rule.getName() + "; "; + + rulesString = rulesString.substring(0, rulesString.length()-2); + + Toast.makeText(context, String.format(context.getResources().getString(R.string.poiStillReferenced), rulesString), Toast.LENGTH_LONG).show(); + return false; + } + else + { + PointOfInterest.pointOfInterestCollection.remove(this); + PointOfInterest.writePoisToFile(); + + AutomationService service = AutomationService.getInstance(); + if(service != null) + { + service.applySettingsAndRules(); + + //Easiest way to check for changes in location, reset the last known location. + service.getLocationProvider().setCurrentLocation(service.getLocationProvider().getCurrentLocation(), true); + } + + + return true; + } + } + + public static PointOfInterest getByName(String searchName) throws Exception + { + for(PointOfInterest poi : pointOfInterestCollection) + { + if(poi.name.equals(searchName)) + return poi; + } + + throw new Exception("PointOfInterest with name " + searchName + " not found."); + } + + public static String[] getNamesInArray() + { + ArrayList nameList = new ArrayList(); + for(PointOfInterest poi : pointOfInterestCollection) + { + nameList.add(poi.name); + } + + return (String[])nameList.toArray(new String[pointOfInterestCollection.size()]); + } + + public static PointOfInterest getActivePoi() + { + for(PointOfInterest poi : PointOfInterest.pointOfInterestCollection) + { + if(poi.isActivated()) + return poi; + } + + return null; + } + + @Override + public int compareTo(PointOfInterest another) + { + return this.getName().compareTo(another.getName()); + } + + private static boolean addPositionToRingBuffer(Location newLocation) + { + /* + * This method's purpose is to record the last n positions and check if they are different. + * In reality you will never get the exact same position twice. If you do the location engine + * seems to have hung up. + */ + + try + { + if(++locationRingBufferLastPosition > locationRingBuffer.length-1) + locationRingBufferLastPosition = 0; + + Miscellaneous.logEvent("i", "Ringbuffer.", "Adding location " + String.valueOf(newLocation.getLatitude()) + " / " + String.valueOf(newLocation.getLongitude()) + " to ringbuffer at index " + String.valueOf(locationRingBufferLastPosition), 5); + locationRingBuffer[locationRingBufferLastPosition] = newLocation; + + /* + * Return values: + * true if the new location is different to the last one + * false if we get repeated values, comparing all values + * + * false indicates problems with hangups in getting locations. + */ + + int counter = locationRingBufferLastPosition+1-1; // make a copy, not a reference + int previousIndex; + do + { + if(counter>0) + previousIndex = counter-1; + else + previousIndex = Settings.locationRingBufferSize-1; + + try + { + if(locationRingBuffer[counter].getLatitude() != locationRingBuffer[previousIndex].getLatitude() | locationRingBuffer[counter].getLongitude() != locationRingBuffer[previousIndex].getLongitude()) + { + // If location different from last one we're fine. + Miscellaneous.logEvent("w", "Ringbuffer.", "Location has changed from the last one. We\'re fine.", 5); + return true; + } + } + catch(NullPointerException ne) + { + /* + * Just null pointer exception. Ringbuffer isn't filled to its maximum, yet. + */ + return true; + } + + if(counter>0) + counter--; + else + counter = Settings.locationRingBufferSize-1; + } while(counter != locationRingBufferLastPosition); + + Miscellaneous.logEvent("w", "Ringbuffer", "Location has not changed from the last one. Something\'s odd. Maybe the location engine kind of hung up.", 2); + return false; + } + catch(ArrayIndexOutOfBoundsException e) + { + Miscellaneous.logEvent("e", "Ringbuffer", "Probably not enough values, yet.", 5); + return true; + } + catch(Exception e) + { + Miscellaneous.logEvent("e", "Ringbuffer", "Error in ringbuffer: " + Log.getStackTraceString(e), 4); + return true; + } + } + + public static class GpsComparisonLocationListener implements LocationListener + { + public AutomationService parent = null; + + @Override + public void onLocationChanged(Location up2DateLocation) + { + stopGpsMeasurement(); + + PointOfInterest.positionUpdate(up2DateLocation, parent, true, false); + + Miscellaneous.logEvent("i", "LocationListener", "Disarmed location listener, accuracy reached", 4); + } + + @Override + public void onProviderDisabled(String provider) + { + // TODO Auto-generated method stub + + } + + @Override + public void onProviderEnabled(String provider) + { + // TODO Auto-generated method stub + + } + + @Override + public void onStatusChanged(String provider, int status, Bundle extras) + { + // TODO Auto-generated method stub + + } + } + + private static void startGpsMeasurement(AutomationService parentService) + { + // Arm location updates + if(!gpsLocationListenerArmed) + { + Miscellaneous.logEvent("i", "PointOfInterest", "Unsatisfactory accuracy of network location. Performing comparison measurement via GPS.", 3); + + String myGpsComparisonProviderName; + + if(Settings.privacyLocationing) + { + Miscellaneous.logEvent("i", "PointOfInterest", parentService.getResources().getString(R.string.enforcingGps), 4); + myGpsComparisonProviderName = LocationManager.GPS_PROVIDER; + } + else + { +// Miscellaneous.logEvent("i", "PointOfInterest", parentService.getResources().getString(R.string.notEnforcingGps), 4); + Criteria crit = new Criteria(); + // crit.setPowerRequirement(Criteria.POWER_LOW); + // crit.setAltitudeRequired(false); + // crit.setSpeedRequired(false); + // crit.setBearingRequired(false); + crit.setCostAllowed(true); + crit.setAccuracy(Criteria.ACCURACY_FINE); + gpsComparisonLocationManager = (LocationManager)parentService.getSystemService(parentService.LOCATION_SERVICE); + myGpsComparisonProviderName = gpsComparisonLocationManager.getBestProvider(crit, true); + } + + Miscellaneous.logEvent("i", "LocationListener", "Arming location listener, Provider: " + myGpsComparisonProviderName, 4); + gpsComparisonLocationListener = new GpsComparisonLocationListener(); + gpsComparisonLocationListener.parent = parentService; + gpsComparisonLocationManager.requestLocationUpdates(myGpsComparisonProviderName, Settings.minimumTimeBetweenUpdate, Settings.minimumDistanceChangeForNetworkUpdate, gpsComparisonLocationListener); + gpsLocationListenerArmed = true; + + // set timeout + Message message = new Message(); + message.what = 1; + Miscellaneous.logEvent("i", parentService.getResources().getString(R.string.gpsComparison), parentService.getResources().getString(R.string.startingGpsTimeout), 4); + if(timeoutHandler.parentService == null) + timeoutHandler.parentService = parentService; + timeoutHandler.sendMessageDelayed(message, Settings.gpsTimeout * 1000); + timeoutHandlerActive = true; + } + else + Miscellaneous.logEvent("i", "PointOfInterest", "Comparison measurement via GPS requested, but already active.", 3); + } + + private static void stopGpsMeasurement() + { + if(gpsLocationListenerArmed) + { + gpsComparisonLocationManager.removeUpdates(gpsComparisonLocationListener); + gpsLocationListenerArmed = false; + } + + if(timeoutHandlerActive) + { + timeoutHandler.removeMessages(1); + timeoutHandlerActive = false; + } + + } + + private static class TimeoutHandler extends Handler + { + public AutomationService parentService = null; + public Location locationToApplyIfGpsFails = null; + + @Override + public void handleMessage(Message msg) + { + super.handleMessage(msg); + + if(msg.what == 1) + { + Miscellaneous.logEvent("i", parentService.getResources().getString(R.string.gpsComparison), parentService.getResources().getString(R.string.gpsComparisonTimeoutStop), 4); + stopGpsMeasurement(); + } + } + } + + private boolean plausibilityCheck() + { + double distance, minimumDistance, overlap; + + if(this.getName().equals("null")) + { + // Invalid name + String text = Miscellaneous.getAnyContext().getResources().getString(R.string.invalidPoiName); + Miscellaneous.logEvent("w", "POI", text, 2); + Toast.makeText(Miscellaneous.getAnyContext(), text, Toast.LENGTH_LONG).show(); + return false; + } + + for(PointOfInterest otherPoi : this.getPointOfInterestCollection()) + { + distance = otherPoi.getLocation().distanceTo(this.getLocation()); + minimumDistance = otherPoi.getRadius()/2 + this.getRadius()/2; + overlap = Math.round(Math.abs(distance - minimumDistance)); + + if(distance <= minimumDistance && !otherPoi.getName().equals(this.getName())) + { + String text = String.format(Miscellaneous.getAnyContext().getResources().getString(R.string.overlapBetweenPois), otherPoi.getName(), String.valueOf(overlap)); + Miscellaneous.logEvent("w", "POI", text, 2); + Toast.makeText(Miscellaneous.getAnyContext(), text, Toast.LENGTH_LONG).show(); + return false; + } + } + Miscellaneous.logEvent("w", "POI", Miscellaneous.getAnyContext().getResources().getString(R.string.noOverLap), 2); + + return true; + } + + public static boolean reachedPoiWithActivateWifiRule() + { + PointOfInterest activePoi = PointOfInterest.getActivePoi(); + if(activePoi != null) + { + for(Rule rule : Rule.findRuleCandidatesByPoi(activePoi, true)) + { + for(Action action : rule.getActionSet()) + { + if(action.getAction().equals(Action.Action_Enum.setWifi) && action.getParameter1()) + { + // We are at a POI that specifies to enable wifi. + return true; + } + } + } + } + + return false; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/jens/automation2/Profile.java b/app/src/main/java/com/jens/automation2/Profile.java new file mode 100644 index 0000000..821c390 --- /dev/null +++ b/app/src/main/java/com/jens/automation2/Profile.java @@ -0,0 +1,543 @@ +package com.jens.automation2; + +import android.content.ContentValues; +import android.content.Context; +import android.media.AudioManager; +import android.media.RingtoneManager; +import android.net.Uri; +import android.provider.MediaStore; +import android.util.Log; +import android.widget.Toast; + +import com.jens.automation2.Action.Action_Enum; + +import java.io.File; +import java.util.ArrayList; + +public class Profile implements Comparable +{ + protected static ArrayList profileCollection = new ArrayList(); + + protected String name; + protected String oldName; + + protected boolean changeSoundMode; + protected int soundMode; + + boolean changeVolumeMusicVideoGameMedia; + protected int volumeMusic; + + protected boolean changeVolumeNotifications; + protected int volumeNotifications; + + protected boolean changeVolumeAlarms; + protected int volumeAlarms; + + protected boolean changeIncomingCallsRingtone; + protected File incomingCallsRingtone; + + protected boolean changeVibrateWhenRinging; + protected boolean vibrateWhenRinging; + + protected boolean changeNotificationRingtone; + protected File notificationRingtone; + + protected boolean changeAudibleSelection; + protected boolean audibleSelection; + + protected boolean changeScreenLockUnlockSound; + boolean screenLockUnlockSound; + + protected boolean changeHapticFeedback; + protected boolean hapticFeedback; + + + public void setName(String name) + { + this.oldName = this.name; + this.name = name; + } + + public String getName() + { + return name; + } + + public void setChangeSoundMode(boolean changeSoundMode) + { + this.changeSoundMode = changeSoundMode; + } + public boolean getChangeSoundMode() + { + return changeSoundMode; + } + + public void setSoundMode(int soundMode) + { + this.soundMode = soundMode; + } + public int getSoundMode() + { + return soundMode; + } + + public void setChangeVolumeMusicVideoGameMedia(boolean changeVolumeMusicVideoGameMedia) + { + this.changeVolumeMusicVideoGameMedia = changeVolumeMusicVideoGameMedia; + } + public boolean getChangeVolumeMusicVideoGameMedia() + { + return changeVolumeMusicVideoGameMedia; + } + + public void setVolumeMusic(int volumeMusic) + { + this.volumeMusic = volumeMusic; + } + public int getVolumeMusic() + { + return volumeMusic; + } + + public void setChangeVolumeNotifications(boolean changeVolumeRingtoneNotifications) + { + this.changeVolumeNotifications = changeVolumeRingtoneNotifications; + } + public boolean getChangeVolumeNotifications() + { + return changeVolumeNotifications; + } + + public void setVolumeNotifications(int volumeNotifications) + { + this.volumeNotifications = volumeNotifications; + } + public int getVolumeNotifications() + { + return volumeNotifications; + } + + public void setChangeVolumeAlarms(boolean changeVolumeAlarms) + { + this.changeVolumeAlarms = changeVolumeAlarms; + } + public boolean getChangeVolumeAlarms() + { + return changeVolumeAlarms; + } + + public void setVolumeAlarms(int volumeAlarms) + { + this.volumeAlarms = volumeAlarms; + } + public int getVolumeAlarms() + { + return volumeAlarms; + } + + public void setChangeIncomingCallsRingtone(boolean changeIncomingCallsRingtone) + { + this.changeIncomingCallsRingtone = changeIncomingCallsRingtone; + } + public boolean getChangeIncomingCallsRingtone() + { + return changeIncomingCallsRingtone; + } + + public void setIncomingCallsRingtone(File incomingCallsRingtone) + { + this.incomingCallsRingtone = incomingCallsRingtone; + } + public File getIncomingCallsRingtone() + { + return incomingCallsRingtone; + } + + public void setChangeVibrateWhenRinging(boolean changeVibrateWhenRinging) + { + this.changeVibrateWhenRinging = changeVibrateWhenRinging; + } + public boolean getChangeVibrateWhenRinging() + { + return changeVibrateWhenRinging; + } + + public void setVibrateWhenRinging(boolean vibrateWhenRinging) + { + this.vibrateWhenRinging = vibrateWhenRinging; + } + public boolean getVibrateWhenRinging() + { + return vibrateWhenRinging; + } + + public void setChangeNotificationRingtone(boolean changeNotificationRingtone) + { + this.changeNotificationRingtone = changeNotificationRingtone; + } + public boolean getChangeNotificationRingtone() + { + return changeNotificationRingtone; + } + + public void setNotificationRingtone(File notificationsRingtone) + { + this.notificationRingtone = notificationsRingtone; + } + public File getNotificationRingtone() + { + return notificationRingtone; + } + + public void setChangeAudibleSelection(boolean changeAudibleSelection) + { + this.changeAudibleSelection = changeAudibleSelection; + } + public boolean getChangeAudibleSelection() + { + return changeAudibleSelection; + } + + public void setAudibleSelection(boolean audibleSelection) + { + this.audibleSelection = audibleSelection; + } + public boolean getAudibleSelection() + { + return audibleSelection; + } + + public void setChangeScreenLockUnlockSound(boolean changeScreenLockUnlockSound) + { + this.changeScreenLockUnlockSound = changeScreenLockUnlockSound; + } + public boolean getChangeScreenLockUnlockSound() + { + return changeScreenLockUnlockSound; + } + + public void setScreenLockUnlockSound(boolean screenLockUnlockSound) + { + this.screenLockUnlockSound = screenLockUnlockSound; + } + public boolean getScreenLockUnlockSound() + { + return screenLockUnlockSound; + } + + public void setChangeHapticFeedback(boolean changeHapticFeedback) + { + this.changeHapticFeedback = changeHapticFeedback; + } + public boolean getChangeHapticFeedback() + { + return changeHapticFeedback; + } + + public void setHapticFeedback(boolean hapticFeedback) + { + this.hapticFeedback = hapticFeedback; + } + public boolean getHapticFeedback() + { + return hapticFeedback; + } + + public static ArrayList getProfileCollection() + { + return profileCollection; + } + public static ArrayList getProfileCollectionString() + { + ArrayList returnList = new ArrayList(); + for(Profile p : profileCollection) + returnList.add(p.getName()); + + return returnList; + } + + public static Profile getByName(String name) + { + for(Profile p : Profile.getProfileCollection()) + if(p.getName().equals(name)) + return p; + + return null; + } + + public boolean delete(AutomationService myAutomationService) + { + // TODO Auto-generated method stub + return false; + } + + private boolean applyRingTone(File ringtoneFile, int ringtoneType, Context context) + { + Miscellaneous.logEvent("i", "Profile", "Request to set ringtone to " + ringtoneFile.getAbsolutePath(), 3); + + if(!ringtoneFile.exists() | !ringtoneFile.canRead()) + { + String message = "Ringtone file does not exist or cannot read it: " + ringtoneFile.getAbsolutePath(); + Miscellaneous.logEvent("i", "Profile", message, 3); + Toast.makeText(context, message, Toast.LENGTH_SHORT).show(); + return false; + } + + ContentValues values = new ContentValues(); + values.put(MediaStore.MediaColumns.DATA, ringtoneFile.getAbsolutePath()); +// values.put(MediaStore.MediaColumns.TITLE, context.getResources().getString(R.string.app_name) + " ringtone"); +// values.put(MediaStore.MediaColumns.TITLE, ringtoneFile.getName().replace(".mp3", "").replace(".", "")); + values.put(MediaStore.MediaColumns.TITLE, ringtoneFile.getName()); +// values.put(MediaStore.MediaColumns.MIME_TYPE, "audio/*"); + values.put(MediaStore.MediaColumns.MIME_TYPE, "audio/mp3"); + values.put(MediaStore.MediaColumns.SIZE, ringtoneFile.length()); +// values.put(MediaStore.Audio.Media.ARTIST, R.string.app_name); + values.put(MediaStore.Audio.Media.IS_RINGTONE, ringtoneType == RingtoneManager.TYPE_RINGTONE); + values.put(MediaStore.Audio.Media.IS_NOTIFICATION, ringtoneType == RingtoneManager.TYPE_NOTIFICATION); + values.put(MediaStore.Audio.Media.IS_ALARM, false); + values.put(MediaStore.Audio.Media.IS_MUSIC, false); + + Uri existingRingTone = MediaStore.Audio.Media.getContentUriForPath(ringtoneFile.getAbsolutePath()); + if(existingRingTone != null) + context.getContentResolver().delete(existingRingTone, MediaStore.MediaColumns.DATA + "=\"" + ringtoneFile.getAbsolutePath() + "\"", null); + Uri newRingTone = context.getContentResolver().insert(existingRingTone, values); + + try + { + RingtoneManager.setActualDefaultRingtoneUri(context, ringtoneType, newRingTone); + Miscellaneous.logEvent("i", "Profile", "Ringtone set to: " + newRingTone.toString(), 1); +// Ringtone tone = RingtoneManager.getRingtone(context, RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE)); +// tone.play(); + return true; + } + catch (Throwable t) + { + String message = "Error setting ringtone: " + Log.getStackTraceString(t); + Miscellaneous.logEvent("e", "Profile", message, 1); + } + + return false; + } + + public boolean create(Context context, boolean writeToFile) + { + for(Profile profile : Profile.profileCollection) + { + if (profile.getName().equals(this.getName())) + { + Toast.makeText(context, context.getResources().getString(R.string.anotherProfileByThatName), Toast.LENGTH_LONG).show(); + return false; + } + } + + if(plausibilityCheck()) + { + // add to collection + Profile.getProfileCollection().add(this); + + // write to file + if(writeToFile) + return XmlFileInterface.writeFile(); + } + + return false; + } + + public boolean change(Context context) + { + if(this.oldName != null && !this.oldName.equals(this.name)) + { + //Name has changed. We need to look for rules that reference it by its name and update those references + + // Check if the name is still available + int counter = 0; // this method should only be a temporary workaround, directly editing the referenced object may cause problems until reloading the config file + for(Profile profile : Profile.profileCollection) + { + if (profile.getName().equals(this.getName())) + { + counter++; + } + } + + if(counter > 1) + { + Toast.makeText(context, context.getResources().getString(R.string.anotherProfileByThatName), Toast.LENGTH_LONG).show(); + return false; + } + + // Check if rules reference this profile + ArrayList rulesThatReferenceMe = Rule.findRuleCandidatesByProfile(this); + if(rulesThatReferenceMe.size() > 0) + { + for(Rule oneRule : rulesThatReferenceMe) + { + for(Action oneAction : oneRule.getActionSet()) + { + if(oneAction.getAction() == Action_Enum.changeSoundProfile) + { + oneAction.setParameter2(this.name); + // We don't need to save the file. This will happen anyway in PointOfInterest.writePoisToFile() below. + } + } + } + } + } + + if(plausibilityCheck()) + { + // write to file + if(XmlFileInterface.writeFile()) + { + AutomationService service = AutomationService.getInstance(); + if(service != null) + service.applySettingsAndRules(); + + return true; + } + } + + return false; + } + + public boolean delete() + { + for(int i = 0; i< Profile.getProfileCollection().size(); i++) + { + if(Profile.getProfileCollection().get(i).getName().equals(this.getName())) + { + Profile.getProfileCollection().remove(0); + + // write to file + return XmlFileInterface.writeFile(); + } + } + + return false; + } + + private boolean plausibilityCheck() + { + if(this.getName().equals("null")) + { + // Invalid name + String text = Miscellaneous.getAnyContext().getResources().getString(R.string.invalidProfileName); + Miscellaneous.logEvent("w", "Profile", text, 2); + Toast.makeText(Miscellaneous.getAnyContext(), text, Toast.LENGTH_LONG).show(); + return false; + } + + return true; + } + + @Override + public int compareTo(Profile another) + { + return this.getName().compareTo(another.getName()); + } + + public void activate(Context context) + { + Miscellaneous.logEvent("i", "Profile " + this.getName(), String.format(context.getResources().getString(R.string.profileActivate), this.getName()), 3); + + AutomationService.getInstance().checkLockSoundChangesTimeElapsed(); + + if(AutomationService.getInstance().getLockSoundChangesEnd() == null) + { + try + { + AudioManager am = (AudioManager) Miscellaneous.getAnyContext().getSystemService(Context.AUDIO_SERVICE); + + if(changeSoundMode) + Actions.setSound(context, soundMode); + + if(changeVolumeMusicVideoGameMedia) + am.setStreamVolume(AudioManager.STREAM_MUSIC, volumeMusic, AudioManager.FLAG_PLAY_SOUND); + + if(changeVolumeNotifications) + am.setStreamVolume(AudioManager.STREAM_NOTIFICATION, volumeNotifications, AudioManager.FLAG_PLAY_SOUND); + + if(changeVolumeAlarms) + am.setStreamVolume(AudioManager.STREAM_ALARM, volumeAlarms, AudioManager.FLAG_PLAY_SOUND); + + if(changeIncomingCallsRingtone) + if(incomingCallsRingtone != null) + applyRingTone(incomingCallsRingtone, RingtoneManager.TYPE_RINGTONE, context); + + if(changeVibrateWhenRinging) + if(vibrateWhenRinging) + am.setVibrateSetting(AudioManager.VIBRATE_TYPE_RINGER, AudioManager.VIBRATE_SETTING_ON); + else + am.setVibrateSetting(AudioManager.VIBRATE_TYPE_RINGER, AudioManager.VIBRATE_SETTING_OFF); + + if(changeNotificationRingtone) + if(notificationRingtone != null) + applyRingTone(notificationRingtone, RingtoneManager.TYPE_NOTIFICATION, context); + + if(changeScreenLockUnlockSound) + { + android.provider.Settings.System.putInt(context.getContentResolver(), "lockscreen_sounds_enabled" , screenLockUnlockSound ? 1 : 0); + } + + if(changeAudibleSelection) + { + if(audibleSelection) + android.provider.Settings.System.putInt(context.getContentResolver(), android.provider.Settings.System.SOUND_EFFECTS_ENABLED, 1); // enable + else + android.provider.Settings.System.putInt(context.getContentResolver(), android.provider.Settings.System.SOUND_EFFECTS_ENABLED, 0); // dissable + } + + if(changeHapticFeedback) + { + if(hapticFeedback) + android.provider.Settings.System.putInt(context.getContentResolver(), android.provider.Settings.System.HAPTIC_FEEDBACK_ENABLED, 1); // enable + else + android.provider.Settings.System.putInt(context.getContentResolver(), android.provider.Settings.System.HAPTIC_FEEDBACK_ENABLED, 0); // disable + } + } + catch(Exception e) + { + Miscellaneous.logEvent("e", "Profile " + this.getName(), context.getResources().getString(R.string.errorActivatingProfile) + " " + Log.getStackTraceString(e), 1); + } + } + else + { + Miscellaneous.logEvent("i", "Profile " + this.getName(), context.getResources().getString(R.string.noProfileChangeSoundLocked), 3); + } + } + + @Override + public String toString() + { + return this.getName(); + } + + public String toStringLong() + { + return "no implemented, yet"; + } + + public static boolean createDummyProfile(Context context, String tagContent) + { + Profile newProfile = new Profile(); + + newProfile.setName(tagContent); + newProfile.setChangeSoundMode(true); + + if(tagContent.equals("silent")) + newProfile.setSoundMode(AudioManager.RINGER_MODE_SILENT); + else if(tagContent.equals("vibrate")) + newProfile.setSoundMode(AudioManager.RINGER_MODE_VIBRATE); + else if(tagContent.equals("normal")) + newProfile.setSoundMode(AudioManager.RINGER_MODE_NORMAL); + else + return false; + + return newProfile.create(context, false); + } + + public String getOldName() + { + return this.oldName; + } + +} diff --git a/app/src/main/java/com/jens/automation2/ReceiverCoordinator.java b/app/src/main/java/com/jens/automation2/ReceiverCoordinator.java new file mode 100644 index 0000000..f831ce9 --- /dev/null +++ b/app/src/main/java/com/jens/automation2/ReceiverCoordinator.java @@ -0,0 +1,277 @@ +package com.jens.automation2; + +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; +import com.jens.automation2.receivers.BluetoothReceiver; +import com.jens.automation2.receivers.ConnectivityReceiver; +import com.jens.automation2.receivers.HeadphoneJackListener; +import com.jens.automation2.receivers.NoiseListener; +import com.jens.automation2.receivers.PhoneStatusListener; +import com.jens.automation2.receivers.ProcessListener; +import com.jens.automation2.receivers.TimeZoneListener; + +/** + * Created by jens on 08.03.2017. + */ + +public class ReceiverCoordinator +{ + /* + * This class will manage getting the device's location. It will utilize the following methods: + * - CellLocationListener + * - WifiListener + * - Accelerometer + */ + + public static final Class[] allImplementers = { + ActivityDetectionReceiver.class, + AlarmListener.class, + BatteryReceiver.class, + BluetoothReceiver.class, + ConnectivityReceiver.class, + HeadphoneJackListener.class, + //NfcReceiver.class, + NoiseListener.class, + PhoneStatusListener.class, + ProcessListener.class, + TimeZoneListener.class + }; + + private static AutomationListenerInterface[] listeners = null; + + public static void startAllReceivers() + { + /* + * New procedure: + * Save instances of Listeners in ArrayList and run them. + */ + try + { + if(listeners == null) + { + listeners = new AutomationListenerInterface[allImplementers.length]; + int i = 0; + for(Class c : allImplementers) + { + try + { + listeners[i] = (AutomationListenerInterface) c.newInstance(); + + // UNCOMMENT THE NEXT LINE WHEN THIS PART OF THE CODE GOES ONLINE +// listeners[i].startListener(AutomationService.getInstance()); + } + catch (InstantiationException e) + { + e.printStackTrace(); + } + catch (IllegalAccessException e) + { + e.printStackTrace(); + } + finally + { + i++; + } + } + } + for(AutomationListenerInterface listener : listeners) + { + if(listener != null && listener.getMonitoredTrigger() != null) + { + String jobDescription = ""; + for (Trigger.Trigger_Enum name : listener.getMonitoredTrigger()) + jobDescription += name + ", "; + jobDescription = jobDescription.substring(0, jobDescription.length() - 2); + Miscellaneous.logEvent("i", "Listener", "Listener instance: " + listener.getClass().getName() + ", monitoring: " + jobDescription, 5); + } + } + } + catch(Exception e) + { + Miscellaneous.logEvent("w", "Error in new model", Log.getStackTraceString(e), 3); + } + +// if(Settings.useAccelerometerForPositioning && !Miscellaneous.isAndroidEmulator()) +// { +// accelerometerHandler = new AccelerometerHandler(); +// mySensorActivity = new SensorActivity(this); +// } + + // startPhoneStateListener + PhoneStatusListener.startPhoneStatusListener(AutomationService.getInstance()); // also used to mute anouncements during calls + + // startConnectivityReceiver + ConnectivityReceiver.startConnectivityReceiver(AutomationService.getInstance()); + + // startCellLocationChangedReceiver + if(!ConnectivityReceiver.isAirplaneMode(AutomationService.getInstance()) && WifiBroadcastReceiver.mayCellLocationReceiverBeActivated() && (Rule.isAnyRuleUsing(Trigger.Trigger_Enum.pointOfInterest) | Rule.isAnyRuleUsing(Trigger.Trigger_Enum.speed))) + CellLocationChangedReceiver.startCellLocationChangedReceiver(); + + // startBatteryReceiver + if(Rule.isAnyRuleUsing(Trigger.Trigger_Enum.charging) | Rule.isAnyRuleUsing(Trigger.Trigger_Enum.usb_host_connection) | Rule.isAnyRuleUsing(Trigger.Trigger_Enum.batteryLevel)) + BatteryReceiver.startBatteryReceiver(AutomationService.getInstance()); + + // startAlarmListener + AlarmListener.startAlarmListener(AutomationService.getInstance()); + TimeZoneListener.startTimeZoneListener(AutomationService.getInstance()); + + // startNoiseListener + if(Rule.isAnyRuleUsing(Trigger.Trigger_Enum.noiseLevel)) + NoiseListener.startNoiseListener(AutomationService.getInstance()); + + // startNoiseListener + if(Rule.isAnyRuleUsing(Trigger.Trigger_Enum.process_started_stopped)) + ProcessListener.startProcessListener(AutomationService.getInstance()); + + //startActivityDetectionReceiver + if(Rule.isAnyRuleUsing(Trigger.Trigger_Enum.activityDetection)) + ActivityDetectionReceiver.startActivityDetectionReceiver(); + + //startBluetoothReceiver + if(Rule.isAnyRuleUsing(Trigger.Trigger_Enum.bluetoothConnection)) + BluetoothReceiver.startBluetoothReceiver(); + + //startHeadsetJackListener + if(Rule.isAnyRuleUsing(Trigger.Trigger_Enum.headsetPlugged)) + HeadphoneJackListener.getInstance().startListener(AutomationService.getInstance()); + } + + public static void stopAllReceivers() + { + try + { + PhoneStatusListener.stopPhoneStatusListener(AutomationService.getInstance()); + ConnectivityReceiver.stopConnectivityReceiver(); + WifiBroadcastReceiver.stopWifiReceiver(); + BatteryReceiver.stopBatteryReceiver(); + TimeZoneListener.stopTimeZoneListener(); + AlarmListener.stopAlarmListener(AutomationService.getInstance()); + NoiseListener.stopNoiseListener(); + ProcessListener.stopProcessListener(AutomationService.getInstance()); + ActivityDetectionReceiver.stopActivityDetectionReceiver(); + BluetoothReceiver.stopBluetoothReceiver(); + HeadphoneJackListener.getInstance().stopListener(AutomationService.getInstance()); + } + catch(Exception e) + { + Miscellaneous.logEvent("e", "cellReceiver", "Error stopping LocationReceiver: " + Log.getStackTraceString(e), 3); + } + } + + public static void applySettingsAndRules() + { + /* + * This method's purpose is to check settings and rules and determine + * if changes in them require monitors to be started or stopped. + * It takes care only of those which are more expensive. + */ + + // TextToSpeech is handled in AutomationService class + + Miscellaneous.logEvent("i", "LocationProvider", AutomationService.getInstance().getResources().getString(R.string.applyingSettingsAndRules), 3); + + // *********** RULE CHANGES *********** + + // timeFrame -> too inexpensive to shutdown + + if(Rule.isAnyRuleUsing(Trigger.Trigger_Enum.charging) | Rule.isAnyRuleUsing(Trigger.Trigger_Enum.usb_host_connection) | Rule.isAnyRuleUsing(Trigger.Trigger_Enum.batteryLevel)) + { + if(BatteryReceiver.haveAllPermission()) + BatteryReceiver.startBatteryReceiver(AutomationService.getInstance()); + } + else + BatteryReceiver.stopBatteryReceiver(); + + if(Rule.isAnyRuleUsing(Trigger.Trigger_Enum.noiseLevel)) + { + Miscellaneous.logEvent("i", "LocationProvider", "Starting NoiseListener because used in a new/changed rule.", 4); + if(NoiseListener.haveAllPermission()) + NoiseListener.startNoiseListener(AutomationService.getInstance()); + } + else + { + Miscellaneous.logEvent("i", "LocationProvider", "Shutting down NoiseListener because not used in any rule.", 4); + NoiseListener.stopNoiseListener(); + } + + if(Rule.isAnyRuleUsing(Trigger.Trigger_Enum.process_started_stopped)) + { + Miscellaneous.logEvent("i", "LocationProvider", "Starting ProcessListener because used in a new/changed rule.", 4); + if(ProcessListener.haveAllPermission()) + ProcessListener.startProcessListener(AutomationService.getInstance()); + } + else + { + Miscellaneous.logEvent("i", "LocationProvider", "Shutting down ProcessListener because not used in any rule.", 4); + ProcessListener.stopProcessListener(AutomationService.getInstance()); + } + + if(Rule.isAnyRuleUsing(Trigger.Trigger_Enum.activityDetection)) + { + if(ActivityDetectionReceiver.isActivityDetectionReceiverRunning()) + { + Miscellaneous.logEvent("i", "LocationProvider", "Restarting ActivityDetectionReceiver because used in a new/changed rule.", 4); + if(ActivityDetectionReceiver.haveAllPermission()) + ActivityDetectionReceiver.restartActivityDetectionReceiver(); + } + else + { + Miscellaneous.logEvent("i", "LocationProvider", "Starting ActivityDetectionReceiver because used in a new/changed rule.", 4); + if(ActivityDetectionReceiver.haveAllPermission()) + ActivityDetectionReceiver.startActivityDetectionReceiver(); + } + } + else + { + if(ActivityDetectionReceiver.isActivityDetectionReceiverRunning()) + { + Miscellaneous.logEvent("i", "LocationProvider", "Shutting down ActivityDetectionReceiver because not used in any rule.", 4); + ActivityDetectionReceiver.stopActivityDetectionReceiver(); + } + } + + if(Rule.isAnyRuleUsing(Trigger.Trigger_Enum.bluetoothConnection)) + { + if(!BluetoothReceiver.isBluetoothReceiverActive()) + { + Miscellaneous.logEvent("i", "LocationProvider", "Starting BluetoothReceiver because used in a new/changed rule.", 4); + if(BluetoothReceiver.haveAllPermission()) + BluetoothReceiver.startBluetoothReceiver(); + } + } + else + { + if(BluetoothReceiver.isBluetoothReceiverActive()) + { + Miscellaneous.logEvent("i", "LocationProvider", "Shutting down BluetoothReceiver because not used in any rule.", 4); + BluetoothReceiver.stopBluetoothReceiver(); + } + } + + if(Rule.isAnyRuleUsing(Trigger.Trigger_Enum.headsetPlugged)) + { + if(!HeadphoneJackListener.isHeadphoneJackListenerActive()) + { + Miscellaneous.logEvent("i", "LocationProvider", "Starting HeadphoneJackListener because used in a new/changed rule.", 4); + if(HeadphoneJackListener.getInstance().haveAllPermission()) + HeadphoneJackListener.getInstance().startListener(AutomationService.getInstance()); + } + } + else + { + if(HeadphoneJackListener.isHeadphoneJackListenerActive()) + { + Miscellaneous.logEvent("i", "LocationProvider", "Shutting down HeadphoneJackListener because not used in any rule.", 4); + HeadphoneJackListener.getInstance().stopListener(AutomationService.getInstance()); + } + } + + AutomationService.updateNotification(); + } +} diff --git a/app/src/main/java/com/jens/automation2/Rule.java b/app/src/main/java/com/jens/automation2/Rule.java new file mode 100644 index 0000000..e6e5801 --- /dev/null +++ b/app/src/main/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/main/java/com/jens/automation2/Settings.java b/app/src/main/java/com/jens/automation2/Settings.java new file mode 100644 index 0000000..3c2ff8e --- /dev/null +++ b/app/src/main/java/com/jens/automation2/Settings.java @@ -0,0 +1,503 @@ +package com.jens.automation2; + +import android.content.Context; +import android.content.SharedPreferences; +import android.preference.PreferenceManager; +import android.util.Log; + +import java.util.Map; +import java.util.Set; + +public class Settings implements SharedPreferences +{ + public static final int rulesThatHaveBeenRanHistorySize = 10; + public static final String folderName = "Automation"; + + public static long minimumDistanceChangeForGpsUpdate; + public static long minimumDistanceChangeForNetworkUpdate; + public static long satisfactoryAccuracyGps; + public static long satisfactoryAccuracyNetwork; + public static int gpsTimeout; + public static long minimumTimeBetweenUpdate; + public static boolean startServiceAtSystemBoot; + public static boolean writeLogFile; + public static long logLevel; + public static int logFileMaxSize; + public static boolean useTextToSpeechOnNormal; + public static boolean useTextToSpeechOnVibrate; + public static boolean useTextToSpeechOnSilent; + public static boolean muteTextToSpeechDuringCalls; + public static int positioningEngine; + public static boolean useWifiForPositioning; + public static boolean useAccelerometerForPositioning; + public static long useAccelerometerAfterIdleTime; + public static long accelerometerMovementThreshold; + public static long speedMaximumTimeBetweenLocations; + public static long timeBetweenNoiseLevelMeasurements; + public static long lengthOfNoiseLevelMeasurements; + public static long referenceValueForNoiseLevelMeasurements; + public static boolean hasServiceBeenRunning; + public static boolean startServiceAfterAppUpdate; + public static boolean startNewThreadForRuleActivation; + public static boolean showIconWhenServiceIsRunning; + public static boolean httpAcceptAllCertificates; + public static int httpAttempts; + public static int httpAttemptsTimeout; + public static int httpAttemptGap; + public static PointOfInterest lastActivePoi; + public static boolean rememberLastActivePoi; + public static int locationRingBufferSize; + public static long timeBetweenProcessMonitorings; + public static int activityDetectionFrequency; + public static int activityDetectionRequiredProbability; + public static boolean privacyLocationing; + public static int startScreen; + public static boolean executeRulesAndProfilesWithSingleClick; + public static boolean lockSoundChanges; + public static boolean noticeAndroid9MicrophoneShown; + public static boolean noticeAndroid10WifiShown; + + /* + Generic settings valid for all installations and not changable + */ + public static final String dateFormat = "E dd.MM.yyyy HH:mm:ss:ssss"; + + protected static final int default_positioningEngine = 0; + protected static final long default_minimumDistanceChangeForGpsUpdate = 100; + protected static final long default_minimumDistanceChangeForNetworkUpdate = 500; // in Meters + protected static final long default_satisfactoryAccuracyGps = 50; + protected static final long default_satisfactoryAccuracyNetwork = 1000; + protected static final int default_gpsTimeout = 300; // seconds + protected static final long default_minimumTimeBetweenUpdate = 30000; // in Milliseconds + protected static final boolean default_startServiceAtSystemBoot = false; + protected static final boolean default_writeLogFile = false; + protected static final long default_logLevel = 2; + protected static final int default_logFileMaxSize = 10; + protected static final boolean default_useTextToSpeechOnNormal = false; + protected static final boolean default_useTextToSpeechOnVibrate = false; + protected static final boolean default_useTextToSpeechOnSilent = false; + protected static final boolean default_muteTextToSpeechDuringCalls = true; + protected static final boolean default_useWifiForPositioning = true; + protected static final boolean default_useAccelerometerForPositioning = true; + protected static final long default_useAccelerometerAfterIdleTime = 5; + protected static final long default_accelerometerMovementThreshold = 2; + protected static final long default_speedMaximumTimeBetweenLocations = 4; + protected static final long default_timeBetweenNoiseLevelMeasurements = 60; + protected static final long default_lengthOfNoiseLevelMeasurements = 5; + protected static final long default_referenceValueForNoiseLevelMeasurements = 20; + protected static final boolean default_hasServiceBeenRunning = false; + protected static final boolean default_startServiceAfterAppUpdate = true; + protected static final boolean default_startNewThreadForRuleActivation = true; + protected static final boolean default_showIconWhenServiceIsRunning = true; + protected static final boolean default_httpAcceptAllCertificates = false; + protected static final int default_httpAttempts = 3; + protected static final int default_httpAttemptsTimeout = 60; + protected static final int default_httpAttemptGap = 2; + protected static final PointOfInterest default_lastActivePoi = null; + protected static final boolean default_rememberLastActivePoi = true; + protected static final int default_locationRingBufferSize=3; + protected static final long default_timeBetweenProcessMonitorings = 60; + protected static final int default_activityDetectionFrequency = 60; + protected static final int default_activityDetectionRequiredProbability = 75; + protected static final boolean default_privacyLocationing = false; + protected static final int default_startScreen = 0; + protected static final boolean default_executeRulesAndProfilesWithSingleClick = false; + protected static final boolean default_lockSoundChanges = false; + public final static int lockSoundChangesInterval = 15; + + @Override + public boolean contains(String arg0) + { + return false; + } + @Override + public Editor edit() + { + // TODO Auto-generated method stub + return null; + } + @Override + public Map getAll() + { + // TODO Auto-generated method stub + return null; + } + @Override + public boolean getBoolean(String arg0, boolean arg1) + { + // TODO Auto-generated method stub + return false; + } + @Override + public float getFloat(String arg0, float arg1) + { + // TODO Auto-generated method stub + return 0; + } + @Override + public int getInt(String arg0, int arg1) + { + // TODO Auto-generated method stub + return 0; + } + @Override + public long getLong(String arg0, long arg1) + { + // TODO Auto-generated method stub + return 0; + } + @Override + public String getString(String arg0, String arg1) + { + // TODO Auto-generated method stub + return null; + } + @Override + public void registerOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener arg0) + { + // TODO Auto-generated method stub + + } + @Override + public void unregisterOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener arg0) + { + // TODO Auto-generated method stub + + } + + public static void readFromPersistentStorage(Context context) + { + try + { + Miscellaneous.logEvent("i", context.getResources().getString(R.string.settings), context.getResources().getString(R.string.refreshingSettingsFromFileToMemory), 4); + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); + + startServiceAtSystemBoot = prefs.getBoolean("startServiceAtSystemBoot", default_startServiceAtSystemBoot); + writeLogFile = prefs.getBoolean("writeLogFile", default_writeLogFile); + + boolean useTextToSpeech = false; + if(prefs.contains("useTextToSpeech")) + { + useTextToSpeech = prefs.getBoolean("useTextToSpeech", false); + } + if(prefs.contains("useTextToSpeech") && !useTextToSpeech) // until all old users have been upgraded + { + useTextToSpeechOnNormal = false; + useTextToSpeechOnVibrate = false; + useTextToSpeechOnSilent = false; + } + else + { + useTextToSpeechOnNormal = prefs.getBoolean("useTextToSpeechOnNormal", default_useTextToSpeechOnNormal); + useTextToSpeechOnVibrate = prefs.getBoolean("useTextToSpeechOnVibrate", default_useTextToSpeechOnVibrate); + useTextToSpeechOnSilent = prefs.getBoolean("useTextToSpeechOnSilent", default_useTextToSpeechOnSilent); + } + + muteTextToSpeechDuringCalls = prefs.getBoolean("muteTextToSpeechDuringCalls", default_muteTextToSpeechDuringCalls); + + positioningEngine = Integer.parseInt(prefs.getString("positioningEngineOption", String.valueOf(default_positioningEngine))); + useWifiForPositioning = prefs.getBoolean("useWifiForPositioning", default_useWifiForPositioning); + useAccelerometerForPositioning = prefs.getBoolean("useAccelerometerForPositioning", default_useAccelerometerForPositioning); + useAccelerometerAfterIdleTime = Long.parseLong(prefs.getString("useAccelerometerAfterIdleTime", String.valueOf(default_useAccelerometerAfterIdleTime))); + accelerometerMovementThreshold = Long.parseLong(prefs.getString("accelerometerMovementThreshold", String.valueOf(default_accelerometerMovementThreshold))); + speedMaximumTimeBetweenLocations = Long.parseLong(prefs.getString("speedMaximumTimeBetweenLocations", String.valueOf(default_speedMaximumTimeBetweenLocations))); + hasServiceBeenRunning = prefs.getBoolean("hasServiceBeenRunning", default_hasServiceBeenRunning); + startServiceAfterAppUpdate = prefs.getBoolean("startServiceAfterAppUpdate", default_startServiceAfterAppUpdate); + startNewThreadForRuleActivation = prefs.getBoolean("startNewThreadForRuleActivation", default_startNewThreadForRuleActivation); + showIconWhenServiceIsRunning = prefs.getBoolean("showIconWhenServiceIsRunning", default_showIconWhenServiceIsRunning); + + minimumDistanceChangeForGpsUpdate = Long.parseLong(prefs.getString("MINIMUM_DISTANCE_CHANGE_FOR_GPS_UPDATE", String.valueOf(default_minimumDistanceChangeForGpsUpdate))); + minimumDistanceChangeForNetworkUpdate = Long.parseLong(prefs.getString("MINIMUM_DISTANCE_CHANGE_FOR_NETWORK_UPDATE", String.valueOf(default_minimumDistanceChangeForNetworkUpdate))); + satisfactoryAccuracyGps = Long.parseLong(prefs.getString("SATISFACTORY_ACCURACY_GPS", String.valueOf(default_satisfactoryAccuracyGps))); + satisfactoryAccuracyNetwork = Long.parseLong(prefs.getString("SATISFACTORY_ACCURACY_NETWORK", String.valueOf(default_satisfactoryAccuracyNetwork))); + gpsTimeout = Integer.parseInt(prefs.getString("gpsTimeout", String.valueOf(default_gpsTimeout))); + minimumTimeBetweenUpdate = Long.parseLong(prefs.getString("MINIMUM_TIME_BETWEEN_UPDATE", String.valueOf(default_minimumTimeBetweenUpdate))); + timeBetweenNoiseLevelMeasurements = Long.parseLong(prefs.getString("timeBetweenNoiseLevelMeasurements", String.valueOf(default_timeBetweenNoiseLevelMeasurements))); + lengthOfNoiseLevelMeasurements = Long.parseLong(prefs.getString("lengthOfNoiseLevelMeasurements", String.valueOf(default_lengthOfNoiseLevelMeasurements))); + referenceValueForNoiseLevelMeasurements = Long.parseLong(prefs.getString("referenceValueForNoiseLevelMeasurements", String.valueOf(default_referenceValueForNoiseLevelMeasurements))); + timeBetweenProcessMonitorings = Long.parseLong(prefs.getString("timeBetweenProcessMonitorings", String.valueOf(default_timeBetweenProcessMonitorings))); + + httpAcceptAllCertificates = prefs.getBoolean("httpAcceptAllCertificates", default_httpAcceptAllCertificates); + httpAttempts = Integer.parseInt(prefs.getString("httpAttempts", String.valueOf(default_httpAttempts))); + httpAttemptsTimeout = Integer.parseInt(prefs.getString("httpAttemptsTimeout", String.valueOf(default_httpAttemptsTimeout))); + httpAttemptGap = Integer.parseInt(prefs.getString("httpAttemptGap", String.valueOf(default_httpAttemptGap))); + + logLevel = Long.parseLong(prefs.getString("logLevel", String.valueOf(default_logLevel))); + logFileMaxSize = Integer.parseInt(prefs.getString("logFileMaxSize", String.valueOf(default_logFileMaxSize))); + + lastActivePoi = default_lastActivePoi; + rememberLastActivePoi = prefs.getBoolean("rememberLastActivePoi", default_rememberLastActivePoi); + + locationRingBufferSize = Integer.parseInt(prefs.getString("locationRingBufferSize", String.valueOf(default_locationRingBufferSize))); + + activityDetectionFrequency = Integer.parseInt(prefs.getString("activityDetectionFrequency", String.valueOf(default_activityDetectionFrequency))); + activityDetectionRequiredProbability = Integer.parseInt(prefs.getString("activityDetectionRequiredProbability", String.valueOf(default_activityDetectionRequiredProbability))); + + privacyLocationing = prefs.getBoolean("privacyLocationing", default_privacyLocationing); + startScreen = Integer.parseInt(prefs.getString("startScreen", String.valueOf(default_startScreen))); + + executeRulesAndProfilesWithSingleClick = prefs.getBoolean("executeRulesAndProfilesWithSingleClick", default_executeRulesAndProfilesWithSingleClick); + + lockSoundChanges = prefs.getBoolean("lockSoundChanges", default_lockSoundChanges); + noticeAndroid9MicrophoneShown = prefs.getBoolean("noticeAndroid9MicrophoneShown", false); + noticeAndroid10WifiShown = prefs.getBoolean("noticeAndroid10WifiShown", false); + } + catch(Exception e) + { + Miscellaneous.logEvent("e", context.getResources().getString(R.string.settings), context.getResources().getString(R.string.errorReadingSettings) + " " + Log.getStackTraceString(e), 1); + + } + finally + { + initializeSettings(context, false); + } + } + + /**Makes sure a settings has a valid setting. If not it will assign a reasonable default setting to it. + * If force settings will be initialized even if the user has set something.**/ + public static boolean initializeSettings(Context context, boolean force) + { + if(force) + eraseSettings(context); + + try + { + Miscellaneous.logEvent("i", context.getResources().getString(R.string.settings), context.getResources().getString(R.string.initializingSettingsToPersistentMemory), 5); + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); + + Editor editor = prefs.edit(); + + if(!prefs.contains("startServiceAtSystemBoot") | force) + editor.putBoolean("startServiceAtSystemBoot", default_startServiceAtSystemBoot); + + if(!prefs.contains("writeLogFile") | force) + editor.putBoolean("writeLogFile", default_writeLogFile); + +// if(!prefs.contains("useTextToSpeech") | force) +// editor.putBoolean("useTextToSpeech", default_useTextToSpeech); + + if(!prefs.contains("useTextToSpeechOnNormal") | force) + editor.putBoolean("useTextToSpeechOnNormal", default_useTextToSpeechOnNormal); + + if(!prefs.contains("useTextToSpeechOnVibrate") | force) + editor.putBoolean("useTextToSpeechOnVibrate", default_useTextToSpeechOnVibrate); + + if(!prefs.contains("useTextToSpeechOnSilent") | force) + editor.putBoolean("useTextToSpeechOnSilent", default_useTextToSpeechOnSilent); + + if(!prefs.contains("muteTextToSpeechDuringCalls") | force) + editor.putBoolean("muteTextToSpeechDuringCalls", default_muteTextToSpeechDuringCalls); + + if(!prefs.contains("positioningEngine") | force) + editor.putString("positioningEngine", String.valueOf(default_positioningEngine)); + + if(!prefs.contains("useWifiForPositioning") | force) + editor.putBoolean("useWifiForPositioning", default_useWifiForPositioning); + + if(!prefs.contains("hasServiceBeenRunning") | force) + editor.putBoolean("hasServiceBeenRunning", default_hasServiceBeenRunning); + + if(!prefs.contains("startServiceAfterAppUpdate") | force) + editor.putBoolean("startServiceAfterAppUpdate", default_startServiceAfterAppUpdate); + + if(!prefs.contains("startNewThreadForRuleActivation") | force) + editor.putBoolean("startNewThreadForRuleActivation", default_startNewThreadForRuleActivation); + + if(!prefs.contains("showIconWhenServiceIsRunning") | force) + editor.putBoolean("showIconWhenServiceIsRunning", default_showIconWhenServiceIsRunning); + + if(!prefs.contains("useAccelerometerForPositioning") | force) + editor.putBoolean("useAccelerometerForPositioning", default_useAccelerometerForPositioning); + + if(!prefs.contains("useAccelerometerAfterIdleTime") | force) + editor.putString("useAccelerometerAfterIdleTime", String.valueOf(default_useAccelerometerAfterIdleTime)); + + if(!prefs.contains("accelerometerMovementThreshold") | force) + editor.putString("accelerometerMovementThreshold", String.valueOf(default_accelerometerMovementThreshold)); + + if(!prefs.contains("speedMaximumTimeBetweenLocations") | force) + editor.putString("speedMaximumTimeBetweenLocations", String.valueOf(default_speedMaximumTimeBetweenLocations)); + + if(!prefs.contains("MINIMUM_DISTANCE_CHANGE_FOR_GPS_UPDATE") | force) + editor.putString("MINIMUM_DISTANCE_CHANGE_FOR_GPS_UPDATE", String.valueOf(default_minimumDistanceChangeForGpsUpdate)); + + if(!prefs.contains("MINIMUM_DISTANCE_CHANGE_FOR_NETWORK_UPDATE") | force) + editor.putString("MINIMUM_DISTANCE_CHANGE_FOR_NETWORK_UPDATE", String.valueOf(default_minimumDistanceChangeForNetworkUpdate)); + + if(!prefs.contains("SATISFACTORY_ACCURACY_GPS") | force) + editor.putString("SATISFACTORY_ACCURACY_GPS", String.valueOf(default_satisfactoryAccuracyGps)); + + if(!prefs.contains("SATISFACTORY_ACCURACY_NETWORK") | force) + editor.putString("SATISFACTORY_ACCURACY_NETWORK", String.valueOf(default_satisfactoryAccuracyNetwork)); + + if(!prefs.contains("gpsTimeout") | force) + editor.putString("gpsTimeout", String.valueOf(default_gpsTimeout)); + + if(!prefs.contains("MINIMUM_TIME_BETWEEN_UPDATE") | force) + editor.putString("MINIMUM_TIME_BETWEEN_UPDATE", String.valueOf(default_minimumTimeBetweenUpdate)); + + if(!prefs.contains("timeBetweenNoiseLevelMeasurements") | force) + editor.putString("timeBetweenNoiseLevelMeasurements", String.valueOf(default_timeBetweenNoiseLevelMeasurements)); + + if(!prefs.contains("lengthOfNoiseLevelMeasurements") | force) + editor.putString("lengthOfNoiseLevelMeasurements", String.valueOf(default_lengthOfNoiseLevelMeasurements)); + + if(!prefs.contains("referenceValueForNoiseLevelMeasurements") | force) + editor.putString("referenceValueForNoiseLevelMeasurements", String.valueOf(default_referenceValueForNoiseLevelMeasurements)); + + if(!prefs.contains("logLevel") | force) + editor.putString("logLevel", String.valueOf(default_logLevel)); + + if(!prefs.contains("logFileMaxSize") | force) + editor.putString("logFileMaxSize", String.valueOf(default_logFileMaxSize)); + + if(!prefs.contains("httpAcceptAllCertificates") | force) + editor.putBoolean("httpAcceptAllCertificates", default_httpAcceptAllCertificates); + + if(!prefs.contains("httpAttempts") | force) + editor.putString("httpAttempts", String.valueOf(default_httpAttempts)); + + if(!prefs.contains("httpAttemptsTimeout") | force) + editor.putString("httpAttemptsTimeout", String.valueOf(default_httpAttemptsTimeout)); + + if(!prefs.contains("httpAttemptGap") | force) + editor.putString("httpAttemptGap", String.valueOf(default_httpAttemptGap)); + + if(!prefs.contains("lastActivePoi") | force) + editor.putString("lastActivePoi", "null"); + + if(!prefs.contains("rememberLastActivePoi") | force) + editor.putBoolean("rememberLastActivePoi", default_rememberLastActivePoi); + + if(!prefs.contains("locationRingBufferSize") | force) + editor.putString("locationRingBufferSize", String.valueOf(default_locationRingBufferSize)); + + if(!prefs.contains("timeBetweenProcessMonitorings") | force) + editor.putString("timeBetweenProcessMonitorings", String.valueOf(default_timeBetweenProcessMonitorings)); + + if(!prefs.contains("activityDetectionFrequency") | force) + editor.putString("activityDetectionFrequency", String.valueOf(default_activityDetectionFrequency)); + + if(!prefs.contains("activityDetectionRequiredProbability") | force) + editor.putString("activityDetectionRequiredProbability", String.valueOf(default_activityDetectionRequiredProbability)); + + if(!prefs.contains("privacyLocationing") | force) + editor.putBoolean("privacyLocationing", default_privacyLocationing); + + if(!prefs.contains("startScreen") | force) + editor.putString("startScreen", String.valueOf(default_startScreen)); + + if(!prefs.contains("executeRulesAndProfilesWithSingleClick") | force) + editor.putBoolean("executeRulesAndProfilesWithSingleClick", default_executeRulesAndProfilesWithSingleClick); + + if(!prefs.contains("lockSoundChanges") | force) + editor.putBoolean("lockSoundChanges", default_lockSoundChanges); + + if(!prefs.contains("noticeAndroid9MicrophoneShown") | force) + editor.putBoolean("noticeAndroid9MicrophoneShown", false); + + editor.commit(); + + return true; + } + catch(Exception ex) + { + Miscellaneous.logEvent("e", context.getResources().getString(R.string.settings), context.getResources().getString(R.string.errorInitializingSettingsToPersistentMemory), 1); +// eraseSettings(context); + } + return false; + } + + public static void writeSettings(Context context) + { + try + { + Miscellaneous.logEvent("i", context.getResources().getString(R.string.settings), context.getResources().getString(R.string.writingSettingsToPersistentMemory), 5); + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); + + Editor editor = prefs.edit(); + + editor.putBoolean("startServiceAtSystemBoot", startServiceAtSystemBoot); + editor.putBoolean("writeLogFile", writeLogFile); +// editor.putBoolean("useTextToSpeech", useTextToSpeech); + editor.putBoolean("useTextToSpeechOnNormal", useTextToSpeechOnNormal); + editor.putBoolean("useTextToSpeechOnVibrate", useTextToSpeechOnVibrate); + editor.putBoolean("useTextToSpeechOnSilent", useTextToSpeechOnSilent); + editor.putBoolean("muteTextToSpeechDuringCalls", muteTextToSpeechDuringCalls); + + editor.putString("positioningEngine", String.valueOf(positioningEngine)); + editor.putBoolean("useWifiForPositioning", useWifiForPositioning); + editor.putBoolean("hasServiceBeenRunning", hasServiceBeenRunning); + editor.putBoolean("startServiceAfterAppUpdate", startServiceAfterAppUpdate); + editor.putBoolean("startNewThreadForRuleActivation", startNewThreadForRuleActivation); + editor.putBoolean("showIconWhenServiceIsRunning", showIconWhenServiceIsRunning); + editor.putBoolean("useAccelerometerForPositioning", useAccelerometerForPositioning); + editor.putString("useAccelerometerAfterIdleTime", String.valueOf(useAccelerometerAfterIdleTime)); + editor.putString("accelerometerMovementThreshold", String.valueOf(accelerometerMovementThreshold)); + editor.putString("speedMaximumTimeBetweenLocations", String.valueOf(speedMaximumTimeBetweenLocations)); + editor.putString("MINIMUM_DISTANCE_CHANGE_FOR_GPS_UPDATE", String.valueOf(minimumDistanceChangeForGpsUpdate)); + editor.putString("MINIMUM_DISTANCE_CHANGE_FOR_NETWORK_UPDATE", String.valueOf(minimumDistanceChangeForNetworkUpdate)); + editor.putString("SATISFACTORY_ACCURACY_GPS", String.valueOf(satisfactoryAccuracyGps)); + editor.putString("SATISFACTORY_ACCURACY_NETWORK", String.valueOf(satisfactoryAccuracyNetwork)); + editor.putString("gpsTimeout", String.valueOf(gpsTimeout)); + editor.putString("MINIMUM_TIME_BETWEEN_UPDATE", String.valueOf(minimumTimeBetweenUpdate)); + editor.putString("timeBetweenNoiseLevelMeasurements", String.valueOf(timeBetweenNoiseLevelMeasurements)); + editor.putString("lengthOfNoiseLevelMeasurements", String.valueOf(lengthOfNoiseLevelMeasurements)); + editor.putString("referenceValueForNoiseLevelMeasurements", String.valueOf(referenceValueForNoiseLevelMeasurements)); + editor.putString("logLevel", String.valueOf(logLevel)); + editor.putString("logFileMaxSize", String.valueOf(logFileMaxSize)); + editor.putBoolean("httpAcceptAllCertificates", httpAcceptAllCertificates); + editor.putString("httpAttempts", String.valueOf(httpAttempts)); + editor.putString("httpAttemptsTimeout", String.valueOf(httpAttemptsTimeout)); + editor.putString("httpAttemptGap", String.valueOf(httpAttemptGap)); + editor.putString("locationRingBufferSize", String.valueOf(locationRingBufferSize)); + editor.putString("timeBetweenProcessMonitorings", String.valueOf(timeBetweenProcessMonitorings)); + editor.putString("activityDetectionFrequency", String.valueOf(activityDetectionFrequency)); + editor.putString("activityDetectionRequiredProbability", String.valueOf(activityDetectionRequiredProbability)); + editor.putBoolean("privacyLocationing", privacyLocationing); + editor.putString("startScreen", String.valueOf(startScreen)); + editor.putBoolean("executeRulesAndProfilesWithSingleClick", executeRulesAndProfilesWithSingleClick); + + editor.putBoolean("lockSoundChanges", lockSoundChanges); + editor.putBoolean("noticeAndroid9MicrophoneShown", noticeAndroid9MicrophoneShown); + editor.putBoolean("noticeAndroid10WifiShown", noticeAndroid10WifiShown); + + if(lastActivePoi == null) + editor.putString("lastActivePoi", "null"); + else + editor.putString("lastActivePoi", lastActivePoi.getName()); + + editor.putBoolean("rememberLastActivePoi", rememberLastActivePoi); + + editor.commit(); + } + catch(Exception ex) + { + Miscellaneous.logEvent("e", context.getResources().getString(R.string.settings), context.getResources().getString(R.string.errorWritingSettingsToPersistentMemory), 1); + } + } + + public static boolean eraseSettings(Context context) + { + try + { + Miscellaneous.logEvent("e", context.getResources().getString(R.string.settings), context.getResources().getString(R.string.invalidStuffStoredInSettingsErasing), 1); + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); + prefs.edit().clear().commit(); + return true; + } + catch(Exception ex) + { + Miscellaneous.logEvent("e", context.getResources().getString(R.string.settings), context.getResources().getString(R.string.errorWritingSettingsToPersistentMemory), 1); + } + return false; + } + + @Override + public Set getStringSet(String arg0, Set arg1) + { + // TODO Auto-generated method stub + return null; + } + +} diff --git a/app/src/main/java/com/jens/automation2/TimeFrame.java b/app/src/main/java/com/jens/automation2/TimeFrame.java new file mode 100644 index 0000000..757d85b --- /dev/null +++ b/app/src/main/java/com/jens/automation2/TimeFrame.java @@ -0,0 +1,75 @@ +package com.jens.automation2; + +import java.sql.Time; +import java.util.ArrayList; + +public class TimeFrame +{ + // Defines a timeframe + private Time triggerTimeStart; + private Time triggerTimeStop; + + private ArrayList dayList = new ArrayList(); + public ArrayList getDayList() + { + return dayList; + } + public void setDayList(ArrayList dayList) + { + this.dayList = dayList; + } + public void setDayListFromString(String dayListString) + { +// Log.i("Parsing", "Full string: " + dayListString); + char[] dayListCharArray = dayListString.toCharArray(); + + dayList = new ArrayList(); + for(char item : dayListCharArray) + { +// Log.i("Parsing", String.valueOf(item)); + dayList.add(Integer.parseInt(String.valueOf(item))); + } + } + + + public Time getTriggerTimeStart() + { + return triggerTimeStart; + } + public void setTriggerTimeStart(Time triggerTimeStart) + { + this.triggerTimeStart = triggerTimeStart; + } + public Time getTriggerTimeStop() + { + return triggerTimeStop; + } + public void setTriggerTimeStop(Time triggerTimeStop) + { + this.triggerTimeStop = triggerTimeStop; + } + + public TimeFrame (Time timeStart, Time timeEnd, ArrayList dayList2) + { + this.setTriggerTimeStart(timeStart); + this.setTriggerTimeStop(timeEnd); + this.setDayList(dayList2); + } + TimeFrame (String fileContent) + { + String[] dateArray = fileContent.split("/"); // example: timestart/timestop/days[int] + this.setTriggerTimeStart(Time.valueOf(dateArray[0])); + this.setTriggerTimeStop(Time.valueOf(dateArray[1])); + this.setDayListFromString(dateArray[2]); + } + @Override + public String toString() + { + String returnString = this.getTriggerTimeStart().toString() + "/" + this.getTriggerTimeStop().toString() + "/"; + + for(Integer oneDay : this.getDayList()) + returnString += String.valueOf(oneDay); + + return returnString; + } +} diff --git a/app/src/main/java/com/jens/automation2/Trigger.java b/app/src/main/java/com/jens/automation2/Trigger.java new file mode 100644 index 0000000..e6e43a8 --- /dev/null +++ b/app/src/main/java/com/jens/automation2/Trigger.java @@ -0,0 +1,479 @@ +package com.jens.automation2; + +import android.bluetooth.BluetoothDevice; +import android.content.Context; + +import com.jens.automation2.receivers.ActivityDetectionReceiver; +import com.jens.automation2.receivers.BluetoothReceiver; + +import java.util.ArrayList; + + +public class Trigger +{ + /* + * Can be several things: + * -PointOfInterest + * -TimeFrame + * -Event (like charging, cable plugged, etc.) + */ + + public enum Trigger_Enum { + pointOfInterest, timeFrame, charging, batteryLevel, usb_host_connection, speed, noiseLevel, wifiConnection, process_started_stopped, airplaneMode, roaming, nfcTag, activityDetection, bluetoothConnection, headsetPlugged, phoneCall; //phoneCall always needs to be at the very end because of Google's shitty so called privacy + + public String getFullName(Context context) + { + switch(this) + { + case pointOfInterest: + return context.getResources().getString(R.string.triggerPointOfInterest); + case timeFrame: + return context.getResources().getString(R.string.triggerTimeFrame); + case charging: + return context.getResources().getString(R.string.triggerCharging); + case batteryLevel: + return context.getResources().getString(R.string.batteryLevel); + case usb_host_connection: + return context.getResources().getString(R.string.triggerUsb_host_connection); + case speed: + return context.getResources().getString(R.string.triggerSpeed); + case noiseLevel: + return context.getResources().getString(R.string.triggerNoiseLevel); + case wifiConnection: + return context.getResources().getString(R.string.wifiConnection); + case process_started_stopped: + return context.getResources().getString(R.string.anotherAppIsRunning); + case airplaneMode: + return context.getResources().getString(R.string.airplaneMode); + case roaming: + return context.getResources().getString(R.string.roaming); + case phoneCall: + return context.getResources().getString(R.string.phoneCall); + case nfcTag: + return context.getResources().getString(R.string.nfcTag); + case activityDetection: + return context.getResources().getString(R.string.activityDetection); + case bluetoothConnection: + return context.getResources().getString(R.string.bluetoothConnection); + case headsetPlugged: + return context.getResources().getString(R.string.triggerHeadsetPlugged); + default: + return "Unknown"; + } + } + + }; + + private boolean triggerParameter; //if true->started event, if false->stopped + + private Trigger_Enum triggerType = null; + private PointOfInterest pointOfInterest = null; + private TimeFrame timeFrame; + + private double speed; //km/h + private long noiseLevelDb; + private String wifiName = ""; + private String processName = null; + private int batteryLevel; + private int phoneDirection = 0; // 0=any, 1=incoming, 2=outgoing + private String phoneNumber = null; + private String nfcTagId = null; + private String bluetoothEvent = null; + private String bluetoothDeviceAddress = null; + private int activityDetectionType = -1; + private int headphoneType = -1; + + public int getHeadphoneType() + { + return headphoneType; + } + public void setHeadphoneType(int headphoneType) + { + this.headphoneType = headphoneType; + } + public String getNfcTagId() + { + return nfcTagId; + } + public void setNfcTagId(String nfcTagId) + { + this.nfcTagId = nfcTagId; + } + + public int getActivityDetectionType() + { + return activityDetectionType; + } + public void setActivityDetectionType(int activityDetectionType) + { + this.activityDetectionType = activityDetectionType; + } + public String getBluetoothDeviceAddress() + { + return bluetoothDeviceAddress; + } + public void setBluetoothDeviceAddress(String bluetoothDeviceAddress) + { + this.bluetoothDeviceAddress = bluetoothDeviceAddress; + } + public void setPhoneNumber(String phoneNumber) + { + this.phoneNumber = phoneNumber; + } + public String getPhoneNumber() + { + return phoneNumber; + } + + public void setPhoneDirection(int phoneDirection) + { + this.phoneDirection = phoneDirection; + } + public int getPhoneDirection() + { + return phoneDirection; + } + + public int getBatteryLevel() + { + return batteryLevel; + } + + public void setBatteryLevel(int batteryLevel) + { + this.batteryLevel = batteryLevel; + } + + public String getProcessName() + { + return processName; + } + + public void setProcessName(String processName) + { + this.processName = processName; + } + + public PointOfInterest getPointOfInterest() + { + return pointOfInterest; + } + + public void setPointOfInterest(PointOfInterest setPointOfInterest) + { + this.pointOfInterest = setPointOfInterest; + } + + public double getSpeed() + { + return speed; + } + + public void setSpeed(double speed) + { + this.speed = speed; + } + + public long getNoiseLevelDb() + { + return noiseLevelDb; + } + + public void setNoiseLevelDb(long noiseLevelDb) + { + this.noiseLevelDb = noiseLevelDb; + } + + public Trigger_Enum getTriggerType() + { + return triggerType; + } + + public void setTriggerType(Trigger_Enum settriggerType) + { + this.triggerType = settriggerType; + } + + public boolean getTriggerParameter() + { + return triggerParameter; + } + + public void setTriggerParameter(boolean triggerParameter) + { + this.triggerParameter = triggerParameter; + } + + public TimeFrame getTimeFrame() + { + return timeFrame; + } + + public void setTimeFrame(TimeFrame timeFrame) + { + this.timeFrame = timeFrame; + } + + + @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: + if(getTriggerParameter()) + returnString.append(Miscellaneous.getAnyContext().getResources().getString(R.string.starting) + " "); + else + returnString.append(Miscellaneous.getAnyContext().getResources().getString(R.string.stopping) + " "); + returnString.append(Miscellaneous.getAnyContext().getResources().getString(R.string.triggerCharging)); + break; + case batteryLevel: + returnString.append(Miscellaneous.getAnyContext().getResources().getString(R.string.batteryLevel)); + if(getTriggerParameter()) + returnString.append(" " + Miscellaneous.getAnyContext().getResources().getString(R.string.exceeds) + " "); + else + returnString.append(" " + Miscellaneous.getAnyContext().getResources().getString(R.string.dropsBelow) + " "); + returnString.append(String.valueOf(this.getBatteryLevel()) + " %"); + break; + case usb_host_connection: + if(getTriggerParameter()) + returnString.append(Miscellaneous.getAnyContext().getResources().getString(R.string.connecting) + " "); + else + returnString.append(Miscellaneous.getAnyContext().getResources().getString(R.string.disconnecting) + " "); + + returnString.append(Miscellaneous.getAnyContext().getResources().getString(R.string.triggerUsb_host_connection)); + break; + case pointOfInterest: + if(this.getPointOfInterest() != null) + { + if(getTriggerParameter()) + returnString.append(Miscellaneous.getAnyContext().getResources().getString(R.string.entering) + " "); + else + returnString.append(Miscellaneous.getAnyContext().getResources().getString(R.string.leaving) + " "); + + returnString.append(this.getPointOfInterest().getName().toString()); + } + else + { + if(getTriggerParameter()) + returnString.append(Miscellaneous.getAnyContext().getResources().getString(R.string.leaving) + " " + Miscellaneous.getAnyContext().getResources().getString(R.string.anyLocation)); + else + returnString.append(Miscellaneous.getAnyContext().getResources().getString(R.string.entering) + " " + Miscellaneous.getAnyContext().getResources().getString(R.string.anyLocation)); + } + break; + case timeFrame: + if(getTriggerParameter()) + returnString.append(Miscellaneous.getAnyContext().getResources().getString(R.string.entering) + " "); + else + returnString.append(Miscellaneous.getAnyContext().getResources().getString(R.string.leaving) + " "); + + returnString.append(Miscellaneous.getAnyContext().getResources().getString(R.string.triggerTimeFrame) + ": " + this.getTimeFrame().getTriggerTimeStart().toString() + " " + Miscellaneous.getAnyContext().getResources().getString(R.string.until) + " " + this.getTimeFrame().getTriggerTimeStop().toString() + " on days " + this.getTimeFrame().getDayList().toString()); + break; + case speed: + if(getTriggerParameter()) + returnString.append(Miscellaneous.getAnyContext().getResources().getString(R.string.exceeding) + " "); + else + returnString.append(Miscellaneous.getAnyContext().getResources().getString(R.string.droppingBelow) + " "); + returnString.append(Miscellaneous.getAnyContext().getResources().getString(R.string.triggerSpeed) + ": " + String.valueOf(this.getSpeed()) + " km/h"); + break; + case noiseLevel: + if(getTriggerParameter()) + returnString.append(Miscellaneous.getAnyContext().getResources().getString(R.string.exceeding) + " "); + else + returnString.append(Miscellaneous.getAnyContext().getResources().getString(R.string.droppingBelow) + " "); + + returnString.append(Miscellaneous.getAnyContext().getResources().getString(R.string.triggerNoiseLevel) + ": " + String.valueOf(this.getNoiseLevelDb()) + " dB"); + break; + case wifiConnection: + String wifiDisplayName = ""; + if(this.getWifiName().length() == 0) + wifiDisplayName += Miscellaneous.getAnyContext().getResources().getString(R.string.anyWifi); + else + wifiDisplayName += this.getWifiName(); + + if(getTriggerParameter()) + returnString.append(String.format(Miscellaneous.getAnyContext().getResources().getString(R.string.connectedToWifi), wifiDisplayName)); + else + returnString.append(String.format(Miscellaneous.getAnyContext().getResources().getString(R.string.disconnectedFromWifi), wifiDisplayName)); + + break; + case process_started_stopped: + returnString.append(Miscellaneous.getAnyContext().getResources().getString(R.string.application) + " " + this.getProcessName() + " " + Miscellaneous.getAnyContext().getResources().getString(R.string.is) + " "); + if(this.triggerParameter) + returnString.append(Miscellaneous.getAnyContext().getResources().getString(R.string.started)); + else + returnString.append(Miscellaneous.getAnyContext().getResources().getString(R.string.stopped)); + break; + case airplaneMode: + if(getTriggerParameter()) + returnString.append(Miscellaneous.getAnyContext().getResources().getString(R.string.activated) + " "); + else + returnString.append(Miscellaneous.getAnyContext().getResources().getString(R.string.deactivated) + " "); + returnString.append(Miscellaneous.getAnyContext().getResources().getString(R.string.airplaneMode)); + break; + case roaming: + if(getTriggerParameter()) + returnString.append(Miscellaneous.getAnyContext().getResources().getString(R.string.activated) + " "); + else + returnString.append(Miscellaneous.getAnyContext().getResources().getString(R.string.deactivated) + " "); + returnString.append(Miscellaneous.getAnyContext().getResources().getString(R.string.roaming)); + break; + case phoneCall: + if(getPhoneDirection() == 1) + returnString.append(Miscellaneous.getAnyContext().getResources().getString(R.string.incomingAdjective) + " "); + else if(getPhoneDirection() == 2) + returnString.append(Miscellaneous.getAnyContext().getResources().getString(R.string.outgoingAdjective) + " "); + + returnString.append(Miscellaneous.getAnyContext().getResources().getString(R.string.phoneCall)); + if(phoneNumber != null && !phoneNumber.equals("any")) + returnString.append(" " + Miscellaneous.getAnyContext().getResources().getString(R.string.with) + " " + Miscellaneous.getAnyContext().getResources().getString(R.string.number) + " " + phoneNumber); + else + returnString.append(" " + Miscellaneous.getAnyContext().getResources().getString(R.string.with) + " " + Miscellaneous.getAnyContext().getResources().getString(R.string.anyNumber)); + + if(getTriggerParameter()) + returnString.append(" " + Miscellaneous.getAnyContext().getResources().getString(R.string.started)); + else + returnString.append(" " + Miscellaneous.getAnyContext().getResources().getString(R.string.stopped)); + break; + case nfcTag: + // This type doesn't have an activate/deactivate equivalent +// if(getTriggerParameter()) +// returnString += Miscellaneous.getAnyContext().getResources().getString(R.string.activated) + " "; +// else +// returnString += Miscellaneous.getAnyContext().getResources().getString(R.string.deactivated) + " "; + + 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())); + break; + case bluetoothConnection: + String device = Miscellaneous.getAnyContext().getResources().getString(R.string.anyDevice); +// if(this.bluetoothDeviceAddress != null) +// { + if(bluetoothDeviceAddress.equals("")) + { + device = Miscellaneous.getAnyContext().getResources().getString(R.string.any); + } + else if(bluetoothDeviceAddress.equals("")) + { + device = Miscellaneous.getAnyContext().getResources().getString(R.string.noDevice); + } + else + { + try + { + device = BluetoothReceiver.getDeviceByAddress(bluetoothDeviceAddress).getName() + " (" + this.bluetoothDeviceAddress + ")"; + } + catch(NullPointerException e) + { + device = Miscellaneous.getAnyContext().getResources().getString(R.string.invalidDevice); + Miscellaneous.logEvent("w", "Trigger", Miscellaneous.getAnyContext().getResources().getString(R.string.invalidDevice), 3); + } + } + + if(bluetoothEvent.equals(BluetoothDevice.ACTION_ACL_CONNECTED) | bluetoothEvent.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED)) + if(this.triggerParameter) + returnString.append(String.format(Miscellaneous.getAnyContext().getResources().getString(R.string.bluetoothConnectionTo), device)); + else + returnString.append(String.format(Miscellaneous.getAnyContext().getResources().getString(R.string.bluetoothDisconnectFrom), device)); + else if(bluetoothEvent.equals(BluetoothDevice.ACTION_FOUND)) + if(this.triggerParameter) + returnString.append(String.format(Miscellaneous.getAnyContext().getResources().getString(R.string.bluetoothDeviceInRange), device)); + else + returnString.append(String.format(Miscellaneous.getAnyContext().getResources().getString(R.string.bluetoothDeviceOutOfRange), device)); +// } + break; + case headsetPlugged: + String type; + switch(headphoneType) + { + case 0: + type = Miscellaneous.getAnyContext().getResources().getString(R.string.headphoneSimple); + break; + case 1: + type = Miscellaneous.getAnyContext().getResources().getString(R.string.headphoneMicrophone); + break; + case 2: + type = Miscellaneous.getAnyContext().getResources().getString(R.string.headphoneAny); + break; + default: + type = "not set"; + break; + } + if(getTriggerParameter()) + returnString.append(String.format(Miscellaneous.getAnyContext().getResources().getString(R.string.headsetConnected), type)); + else + returnString.append(String.format(Miscellaneous.getAnyContext().getResources().getString(R.string.headsetDisconnected), type)); + break; + default: + returnString.append("error"); + break; + } + + return returnString.toString(); + } + + + public static String[] getTriggerTypesAsArray() + { + ArrayList triggerTypesList = new ArrayList(); + + /*for(int i=0; i triggerTypesList = new ArrayList(); + + /*for(int i=0; i + // serializer.attribute(null, "attribute", "value"); + // serializer.endTag(null, "location"); + + serializer.startTag(null, "radius"); + serializer.text(String.valueOf(PointOfInterest.getPointOfInterestCollection().get(i).getRadius())); + serializer.endTag(null, "radius"); + + serializer.endTag(null, "PointOfInterest"); + } + serializer.endTag(null, "PointOfInterestCollection"); + + + serializer.startTag(null, "ProfileCollection"); + for(int i=0; i ruleCollection = new ArrayList(); + public static void parseSettingsFile(InputStream in) throws XmlPullParserException, IOException + { + try + { + XmlPullParser parser = Xml.newPullParser(); + parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false); + parser.setInput(in, null); + parser.nextTag(); + + XmlFileInterface.readFile3(parser); + } + finally + { + in.close(); + } + } + + protected static String ns=""; + private static void readFile3(XmlPullParser parser) throws XmlPullParserException, IOException + { + Miscellaneous.logEvent("i", "File", "Reading settings file", 4); + parser.require(XmlPullParser.START_TAG, ns, "Automation"); + while (parser.next() != XmlPullParser.END_TAG) + { + if (parser.getEventType() != XmlPullParser.START_TAG) + { + continue; + } + String name = parser.getName(); + // Starts by looking for the entry tag + if (name.equals("PointOfInterestCollection")) + { + PointOfInterest.getPointOfInterestCollection().clear(); + readPoiCollection(parser); + } + else if (name.equals("ProfileCollection")) + { + Profile.getProfileCollection().clear(); + readProfileCollection(parser); + } + else if (name.equals("RuleCollection")) + { + XmlFileInterface.ruleCollection.clear(); + readRuleCollection(parser); + } + else + { + skip(parser); + } + } + Miscellaneous.logEvent("i", "File", "Reading settings file->done", 4); + } + + private static void readPoiCollection(XmlPullParser parser) throws XmlPullParserException, IOException + { + parser.require(XmlPullParser.START_TAG, ns, "PointOfInterestCollection"); + PointOfInterest newPoi = null; + + while (parser.next() != XmlPullParser.END_TAG) + { + if (parser.getEventType() != XmlPullParser.START_TAG) + { + continue; + } + String name = parser.getName(); + if (name.equals("PointOfInterest")) + { + newPoi = new PointOfInterest(); + newPoi = readPoi(parser); + PointOfInterest.getPointOfInterestCollection().add(newPoi); + } + else + { + skip(parser); + } + } + + Collections.sort(PointOfInterest.getPointOfInterestCollection()); + + if(newPoi != null) + Miscellaneous.logEvent("i", "New POI from file", newPoi.toString(), 5); + else + Miscellaneous.logEvent("i", "File", "No POIs in file.", 4); + } + + // Parses the contents of an entry. If it encounters a title, summary, or link tag, hands them off + // to their respective "read" methods for processing. Otherwise, skips the tag. + private static PointOfInterest readPoi(XmlPullParser parser) throws XmlPullParserException, IOException + { + parser.require(XmlPullParser.START_TAG, ns, "PointOfInterest"); + PointOfInterest newPoi = new PointOfInterest(); +// newPoi.parentService = (AutomationService)context; + newPoi.setLocation(new Location("POINT_LOCATION")); + + while (parser.next() != XmlPullParser.END_TAG) + { + if (parser.getEventType() != XmlPullParser.START_TAG) + { + continue; + } + String name = parser.getName(); + if (name.equals("name")) + { + newPoi.setName(readTag(parser, "name")); + + // This checks if the last activated POI + if(Settings.rememberLastActivePoi) + if(newPoi.getName().equals(Settings.lastActivePoi)) + newPoi.setActivated(true); + } + else if (name.equals("latitude")) + { + newPoi.getLocation().setLatitude(Double.valueOf(readTag(parser, "latitude"))); + } + else if (name.equals("longitude")) + { + newPoi.getLocation().setLongitude(Double.valueOf(readTag(parser, "longitude"))); + } + else if (name.equals("radius")) + { + try + { + newPoi.setRadius(Double.valueOf(readTag(parser, "radius")), context); + } + catch (NumberFormatException e) + { + e.printStackTrace(); + } + catch (Exception e) + { + e.printStackTrace(); + } + } + else + { + skip(parser); + } + } + + Miscellaneous.logEvent("i", "New POI from file", newPoi.toStringLong(), 5); + + return newPoi; + } + + private static void readProfileCollection(XmlPullParser parser) throws XmlPullParserException, IOException + { + parser.require(XmlPullParser.START_TAG, ns, "ProfileCollection"); + Profile newProfile = null; + + while (parser.next() != XmlPullParser.END_TAG) + { + if (parser.getEventType() != XmlPullParser.START_TAG) + { + continue; + } + String name = parser.getName(); + if (name.equals("Profile")) + { + newProfile = new Profile(); + newProfile = readProfile(parser); + Profile.getProfileCollection().add(newProfile); + } + else + { + skip(parser); + } + } + + Collections.sort(Profile.getProfileCollection()); + + if(newProfile != null) + Miscellaneous.logEvent("i", "New Profile from file", newProfile.toString(), 5); + else + Miscellaneous.logEvent("i", "File", "No Profiles in file.", 4); + } + + private static Profile readProfile(XmlPullParser parser) throws XmlPullParserException, IOException + { + parser.require(XmlPullParser.START_TAG, ns, "Profile"); + Profile newProfile = new Profile(); + + while (parser.next() != XmlPullParser.END_TAG) + { + if (parser.getEventType() != XmlPullParser.START_TAG) + { + continue; + } + String name = parser.getName(); + + if (name.equals("name")) + newProfile.setName(readTag(parser, "name")); + else if (name.equals("changeSoundMode")) + newProfile.setChangeSoundMode(Boolean.parseBoolean(readTag(parser, "changeSoundMode"))); + else if (name.equals("soundMode")) + newProfile.setSoundMode(Integer.parseInt(readTag(parser, "soundMode"))); + else if (name.equals("changeVolumeMusicVideoGameMedia")) + newProfile.setChangeVolumeMusicVideoGameMedia(Boolean.parseBoolean(readTag(parser, "changeVolumeMusicVideoGameMedia"))); + else if (name.equals("volumeMusic")) + newProfile.setVolumeMusic(Integer.parseInt(readTag(parser, "volumeMusic"))); + else if (name.equals("changeVolumeNotifications")) + newProfile.setChangeVolumeNotifications(Boolean.parseBoolean(readTag(parser, "changeVolumeNotifications"))); + else if (name.equals("volumeNotifications")) + newProfile.setVolumeNotifications(Integer.parseInt(readTag(parser, "volumeNotifications"))); + else if (name.equals("changeVolumeAlarms")) + newProfile.setChangeVolumeAlarms(Boolean.parseBoolean(readTag(parser, "changeVolumeAlarms"))); + else if (name.equals("volumeAlarms")) + newProfile.setVolumeAlarms(Integer.parseInt(readTag(parser, "volumeAlarms"))); + else if (name.equals("changeIncomingCallsRingtone")) + newProfile.setChangeIncomingCallsRingtone(Boolean.parseBoolean(readTag(parser, "changeIncomingCallsRingtone"))); + else if (name.equals("incomingCallsRingtone")) + { + String path = readTag(parser, "incomingCallsRingtone"); + if(!path.equals("null")) + newProfile.setIncomingCallsRingtone(new File(path)); + else + newProfile.setIncomingCallsRingtone(null); + } + else if (name.equals("changeVibrateWhenRinging")) + newProfile.setChangeVibrateWhenRinging(Boolean.parseBoolean(readTag(parser, "changeVibrateWhenRinging"))); + else if (name.equals("changeNotificationRingtone")) + newProfile.setChangeNotificationRingtone(Boolean.parseBoolean(readTag(parser, "changeNotificationRingtone"))); + else if (name.equals("notificationRingtone")) + { + String path = readTag(parser, "notificationRingtone"); + if(!path.equals("null")) + newProfile.setNotificationRingtone(new File(path)); + else + newProfile.setNotificationRingtone(null); + } + else if (name.equals("changeAudibleSelection")) + newProfile.setChangeAudibleSelection(Boolean.parseBoolean(readTag(parser, "changeAudibleSelection"))); + else if (name.equals("audibleSelection")) + newProfile.setAudibleSelection(Boolean.parseBoolean(readTag(parser, "audibleSelection"))); + else if (name.equals("changeScreenLockUnlockSound")) + newProfile.setChangeScreenLockUnlockSound(Boolean.parseBoolean(readTag(parser, "changeScreenLockUnlockSound"))); + else if (name.equals("screenLockUnlockSound")) + newProfile.setScreenLockUnlockSound(Boolean.parseBoolean(readTag(parser, "screenLockUnlockSound"))); + else if (name.equals("changeHapticFeedback")) + newProfile.setChangeHapticFeedback(Boolean.parseBoolean(readTag(parser, "changeHapticFeedback"))); + else if (name.equals("hapticFeedback")) + newProfile.setHapticFeedback(Boolean.parseBoolean(readTag(parser, "hapticFeedback"))); + else + skip(parser); + + } + + Miscellaneous.logEvent("i", "New Profile from file", newProfile.toStringLong(), 5); + + return newProfile; + } + + private static void readRuleCollection(XmlPullParser parser) throws XmlPullParserException, IOException + { + Miscellaneous.logEvent("i", "File", "Reading Rule Collection from file", 5); + + parser.require(XmlPullParser.START_TAG, ns, "RuleCollection"); + while (parser.next() != XmlPullParser.END_TAG) + { + if (parser.getEventType() != XmlPullParser.START_TAG) + { + continue; + } + String name = parser.getName(); + // Starts by looking for the entry tag + if (name.equals("Rule")) + { + Rule newRule = readRule(parser); + XmlFileInterface.ruleCollection.add(newRule); + } + else + { + skip(parser); + } + } + Collections.sort(XmlFileInterface.ruleCollection); + Miscellaneous.logEvent("i", "File", "Reading Rule Collection from file->done", 5); + } + + private static Rule readRule(XmlPullParser parser) throws XmlPullParserException, IOException + { + /* FILE EXAMPE: + * ***************** + * + * + * + someName + someLatitude + someLongitude + someRadius + * + * + * + * + * String + * true/false + * + * + * String: pointOfInterest, timeFrame, charging, usb_connection + * true/false + * POI-Name, TimeFrame, USB-Device-Name + * + * + * + * + * String + * String + * + * + * + * + * + */ + + parser.require(XmlPullParser.START_TAG, ns, "Rule"); + Rule newRule = new Rule(); + + while (parser.next() != XmlPullParser.END_TAG) + { + if (parser.getEventType() != XmlPullParser.START_TAG) + { + continue; + } + String name = parser.getName(); + if (name.equals("Name")) + { + newRule.setName(readTag(parser, "Name")); + } + else if(name.equals("RuleActive")) + { + newRule.setRuleActive(Boolean.valueOf(readTag(parser, "RuleActive"))); + } + else if(name.equals("RuleToggle")) + { + newRule.setRuleToggle(Boolean.valueOf(readTag(parser, "RuleToggle"))); + } + else if (name.equals("TriggerCollection")) + { + try + { + newRule.setTriggerSet(readTriggerCollection(parser)); + } + catch (XmlPullParserException e) + { + // TODO Auto-generated catch block + e.printStackTrace(); + } + catch (IOException e) + { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + else if (name.equals("ActionCollection")) + { + try + { + newRule.setActionSet(readActionCollection(parser)); + } + catch (XmlPullParserException e) + { + // TODO Auto-generated catch block + e.printStackTrace(); + } + catch (IOException e) + { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + else + { + skip(parser); + } + } + + Miscellaneous.logEvent("i", "New Rule from file", newRule.toString(), 5); + + return newRule; + } + + private static ArrayList readTriggerCollection(XmlPullParser parser) throws XmlPullParserException, IOException + { + ArrayList triggerCollection = new ArrayList(); + + parser.require(XmlPullParser.START_TAG, ns, "TriggerCollection"); + while (parser.next() != XmlPullParser.END_TAG) + { + if (parser.getEventType() != XmlPullParser.START_TAG) + { + continue; + } + String name = parser.getName(); + // Starts by looking for the entry tag + if (name.equals("Trigger")) + { + triggerCollection.add(readTrigger(parser)); + } + else + { + skip(parser); + } + } + + return (triggerCollection); + } + + + private static Trigger readTrigger(XmlPullParser parser) throws IOException, XmlPullParserException + { + + /* FILE EXAMPE: + * ***************** + * + * + * + someName + someLatitude + someLongitude + someRadius + * + * + * + * + * String + * true/false + * + * + * String: pointOfInterest, timeFrame, charging, usb_connection + * true/false + * POI-Name, TimeFrame, USB-Device-Name, Speed + * + * + * + * + * String + * String + * + * + * + * + * + */ + + parser.require(XmlPullParser.START_TAG, ns, "Trigger"); + Trigger newTrigger = new Trigger(); + + while (parser.next() != XmlPullParser.END_TAG) + { + if (parser.getEventType() != XmlPullParser.START_TAG) + { + continue; + } + String name = parser.getName(); + + if (name.equals("TriggerEvent")) + { + String triggerEventString = readTag(parser, "TriggerEvent"); + if(triggerEventString.equals("pointOfInterest")) + newTrigger.setTriggerType(Trigger_Enum.pointOfInterest); + else if(triggerEventString.equals("timeFrame")) + newTrigger.setTriggerType(Trigger_Enum.timeFrame); + else if(triggerEventString.equals("charging")) + newTrigger.setTriggerType(Trigger_Enum.charging); + else if(triggerEventString.equals("usb_host_connection")) + newTrigger.setTriggerType(Trigger_Enum.usb_host_connection); + else if(triggerEventString.equals("batteryLevel")) + newTrigger.setTriggerType(Trigger_Enum.batteryLevel); + else if(triggerEventString.equals("speed")) + newTrigger.setTriggerType(Trigger_Enum.speed); + else if(triggerEventString.equals("noiseLevel")) + newTrigger.setTriggerType(Trigger_Enum.noiseLevel); + else if(triggerEventString.equals("wifiConnection")) + newTrigger.setTriggerType(Trigger_Enum.wifiConnection); + else if(triggerEventString.equals("process_started_stopped") | triggerEventString.equals("process_running")) + newTrigger.setTriggerType(Trigger_Enum.process_started_stopped); + else if(triggerEventString.equals("airplaneMode")) + newTrigger.setTriggerType(Trigger_Enum.airplaneMode); + else if(triggerEventString.equals("roaming")) + newTrigger.setTriggerType(Trigger_Enum.roaming); + else if(triggerEventString.equals("phoneCall")) + newTrigger.setTriggerType(Trigger_Enum.phoneCall); + else if(triggerEventString.equals("nfcTag")) + newTrigger.setTriggerType(Trigger_Enum.nfcTag); + else if(triggerEventString.equals("activityDetection")) + newTrigger.setTriggerType(Trigger_Enum.activityDetection); + else if(triggerEventString.equals("bluetoothConnection")) + newTrigger.setTriggerType(Trigger_Enum.bluetoothConnection); + else if(triggerEventString.equals("headsetPlugged")) + newTrigger.setTriggerType(Trigger_Enum.headsetPlugged); + } + else if (name.equals("TriggerParameter1")) + { + newTrigger.setTriggerParameter(Boolean.valueOf(readTag(parser, "TriggerParameter1"))); + } + else if (name.equals("TriggerParameter2")) + { + String triggerParameter2 = readTag(parser, "TriggerParameter2"); + if(newTrigger.getTriggerType() == Trigger_Enum.pointOfInterest) + { + try + { + if(triggerParameter2.equals("null")) + newTrigger.setPointOfInterest(null); + else + newTrigger.setPointOfInterest(PointOfInterest.getByName(triggerParameter2)); + } + catch (Exception e) + { + Miscellaneous.logEvent("e", "XmlFileInterface", Log.getStackTraceString(e), 2); + Toast.makeText(context, "Error while writing file: " + Log.getStackTraceString(e), Toast.LENGTH_LONG).show(); + } + } + else if(newTrigger.getTriggerType() == Trigger_Enum.timeFrame) + { + newTrigger.setTimeFrame(new TimeFrame(triggerParameter2)); + } + else if(newTrigger.getTriggerType() == Trigger_Enum.batteryLevel) + { + newTrigger.setBatteryLevel(Integer.parseInt(triggerParameter2)); + } + else if(newTrigger.getTriggerType() == Trigger_Enum.speed) + { + newTrigger.setSpeed(Double.parseDouble(triggerParameter2)); + } + else if(newTrigger.getTriggerType() == Trigger_Enum.noiseLevel) + { + newTrigger.setNoiseLevelDb(Long.parseLong(triggerParameter2)); + } + else if(newTrigger.getTriggerType() == Trigger_Enum.wifiConnection) + { + newTrigger.setWifiName(triggerParameter2); + } + else if(newTrigger.getTriggerType() == Trigger_Enum.process_started_stopped) + { + newTrigger.setProcessName(triggerParameter2); + } + else if(newTrigger.getTriggerType() == Trigger_Enum.phoneCall) + { + // 0/1/2,number + int direction = Integer.parseInt(triggerParameter2.substring(0, 1)); + String number = triggerParameter2.substring(2); + newTrigger.setPhoneDirection(direction); + newTrigger.setPhoneNumber(number); + } + else if(newTrigger.getTriggerType() == Trigger_Enum.nfcTag) + { + newTrigger.setNfcTagId(triggerParameter2); + } + else if(newTrigger.getTriggerType() == Trigger_Enum.activityDetection) + { + try + { + newTrigger.setActivityDetectionType(Integer.parseInt(triggerParameter2)); + } + catch(NumberFormatException e) + { + newTrigger.setActivityDetectionType(0); + } + } + else if(newTrigger.getTriggerType() == Trigger_Enum.bluetoothConnection) + { + if(triggerParameter2.contains(";")) + { + String[] substrings = triggerParameter2.split(";"); + newTrigger.setBluetoothEvent(substrings[0]); + newTrigger.setBluetoothDeviceAddress(substrings[1]); + } + } + else if(newTrigger.getTriggerType() == Trigger_Enum.headsetPlugged) + { + try + { + newTrigger.setHeadphoneType(Integer.parseInt(triggerParameter2)); + } + catch(NumberFormatException e) + { + newTrigger.setHeadphoneType(-1); + } + } + } + else + { + skip(parser); + } + } + + return newTrigger; + } + + private static ArrayList readActionCollection(XmlPullParser parser) throws XmlPullParserException, IOException + { + ArrayList actionCollection = new ArrayList(); + + parser.require(XmlPullParser.START_TAG, ns, "ActionCollection"); + while (parser.next() != XmlPullParser.END_TAG) + { + if (parser.getEventType() != XmlPullParser.START_TAG) + { + continue; + } + String name = parser.getName(); + // Starts by looking for the entry tag + if (name.equals("Action")) + { + actionCollection.add(readAction(parser)); + } + else + { + skip(parser); + } + } + return (actionCollection); + } + + + private static Action readAction(XmlPullParser parser) throws IOException, XmlPullParserException + { + /* FILE EXAMPE: + * ***************** + * + * + * + someName + someLatitude + someLongitude + someRadius + * + * + * + * + * String + * true/false + * + * + * String: pointOfInterest, timeFrame, charging, usb_connection + * true/false + * POI-Name, TimeFrame, USB-Device-Name + * + * + * + * + * String + * String + * String + * + * + * + * + * + */ + + parser.require(XmlPullParser.START_TAG, ns, "Action"); + Action newAction = new Action(); + + while (parser.next() != XmlPullParser.END_TAG) + { + if (parser.getEventType() != XmlPullParser.START_TAG) + { + continue; + } + String name = parser.getName(); + + if (name.equals("ActionName")) // convert legacy stuff to new format + { + String actionNameString = readTag(parser, "ActionName"); + + if(actionNameString.equals("setWifi")) + newAction.setAction(Action_Enum.setWifi); + else if(actionNameString.equals("setBluetooth")) + newAction.setAction(Action_Enum.setBluetooth); + else if(actionNameString.equals("setUsbTethering")) + newAction.setAction(Action_Enum.setUsbTethering); + else if(actionNameString.equals("setWifiTethering")) + newAction.setAction(Action_Enum.setWifiTethering); + else if(actionNameString.equals("setDisplayRotation")) + newAction.setAction(Action_Enum.setDisplayRotation); + + // *** deprecated + else if(actionNameString.equals("turnWifiOn")) + newAction.setAction(Action_Enum.turnWifiOn); + else if(actionNameString.equals("turnWifiOff")) + newAction.setAction(Action_Enum.turnWifiOff); + else if(actionNameString.equals("turnBluetoothOn")) + newAction.setAction(Action_Enum.turnBluetoothOn); + else if(actionNameString.equals("turnBluetoothOff")) + newAction.setAction(Action_Enum.turnBluetoothOff); + else if(actionNameString.equals("turnUsbTetheringOn")) + newAction.setAction(Action_Enum.turnUsbTetheringOn); + else if(actionNameString.equals("turnUsbTetheringOff")) + newAction.setAction(Action_Enum.turnUsbTetheringOff); + else if(actionNameString.equals("turnWifiTetheringOn")) + newAction.setAction(Action_Enum.turnWifiTetheringOn); + else if(actionNameString.equals("turnWifiTetheringOff")) + newAction.setAction(Action_Enum.turnWifiTetheringOff); + else if(actionNameString.equals("enableScreenRotation")) + newAction.setAction(Action_Enum.enableScreenRotation); + else if(actionNameString.equals("disableScreenRotation")) + newAction.setAction(Action_Enum.disableScreenRotation); + // *** deprecated + + else if(actionNameString.equals("triggerUrl")) + newAction.setAction(Action_Enum.triggerUrl); + else if(actionNameString.equals("changeSoundProfile")) + newAction.setAction(Action_Enum.changeSoundProfile); + else if(actionNameString.equals("startOtherActivity")) + newAction.setAction(Action_Enum.startOtherActivity); + else if(actionNameString.equals("waitBeforeNextAction")) + newAction.setAction(Action_Enum.waitBeforeNextAction); + else if(actionNameString.equals("wakeupDevice")) + newAction.setAction(Action_Enum.wakeupDevice); + else if(actionNameString.equals("setAirplaneMode")) + newAction.setAction(Action_Enum.setAirplaneMode); + else if(actionNameString.equals("setDataConnection")) + newAction.setAction(Action_Enum.setDataConnection); + else if(actionNameString.equals("speakText")) + newAction.setAction(Action_Enum.speakText); + else if(actionNameString.equals("sendTextMessage")) + newAction.setAction(Action_Enum.sendTextMessage); + else if(actionNameString.equals("playMusic")) + newAction.setAction(Action_Enum.playMusic); + else if(actionNameString.equals("setScreenBrightness")) + newAction.setAction(Action_Enum.setScreenBrightness); + } + else if (name.equals("ActionParameter1")) + { + // exclusion for deprecated types + if(newAction.getAction().equals(Action_Enum.turnWifiOn)) + { + newAction.setAction(Action_Enum.setWifi); + newAction.setParameter1(true); + readTag(parser, "ActionParameter1"); //read the tag for the parser to head on + } + else if(newAction.getAction().equals(Action_Enum.turnWifiOff)) + { + newAction.setAction(Action_Enum.setWifi); + newAction.setParameter1(false); + readTag(parser, "ActionParameter1"); //read the tag for the parser to head on + } + else if(newAction.getAction().equals(Action_Enum.turnBluetoothOn)) + { + newAction.setAction(Action_Enum.setBluetooth); + newAction.setParameter1(true); + readTag(parser, "ActionParameter1"); //read the tag for the parser to head on + } + else if(newAction.getAction().equals(Action_Enum.turnBluetoothOff)) + { + newAction.setAction(Action_Enum.setBluetooth); + newAction.setParameter1(false); + readTag(parser, "ActionParameter1"); //read the tag for the parser to head on + } + else if(newAction.getAction().equals(Action_Enum.turnUsbTetheringOn)) + { + newAction.setAction(Action_Enum.setUsbTethering); + newAction.setParameter1(true); + readTag(parser, "ActionParameter1"); //read the tag for the parser to head on + } + else if(newAction.getAction().equals(Action_Enum.turnUsbTetheringOff)) + { + newAction.setAction(Action_Enum.setUsbTethering); + newAction.setParameter1(false); + readTag(parser, "ActionParameter1"); //read the tag for the parser to head on + } + else if(newAction.getAction().equals(Action_Enum.turnWifiTetheringOn)) + { + newAction.setAction(Action_Enum.setWifiTethering); + newAction.setParameter1(true); + readTag(parser, "ActionParameter1"); //read the tag for the parser to head on + } + else if(newAction.getAction().equals(Action_Enum.turnWifiTetheringOff)) + { + newAction.setAction(Action_Enum.setWifiTethering); + newAction.setParameter1(false); + readTag(parser, "ActionParameter1"); //read the tag for the parser to head on + } + else if(newAction.getAction().equals(Action_Enum.enableScreenRotation)) + { + newAction.setAction(Action_Enum.setDisplayRotation); + newAction.setParameter1(true); + readTag(parser, "ActionParameter1"); //read the tag for the parser to head on + } + else if(newAction.getAction().equals(Action_Enum.disableScreenRotation)) + { + newAction.setAction(Action_Enum.setDisplayRotation); + newAction.setParameter1(false); + readTag(parser, "ActionParameter1"); //read the tag for the parser to head on + } + else + // exclusion for deprecated types + newAction.setParameter1(Boolean.parseBoolean(readTag(parser, "ActionParameter1"))); + } + else if (name.equals("ActionParameter2")) + { + String tag = readTag(parser, "ActionParameter2"); + if(newAction.getAction().equals(Action_Enum.triggerUrl)) // decrypt url because of credentials + { + if(tag.toLowerCase().contains("http")) // not encrypted, yet + newAction.setParameter2(tag); + else + { + try + { + newAction.setParameter2(AESCrypt.decrypt(encryptionKey, tag)); + } + catch(GeneralSecurityException e) + { + newAction.setParameter2(tag); + } + } + } + else + newAction.setParameter2(tag); + } + else if (name.equals("ActionParameter")) // old version, should be removed eventually + { + newAction.setParameter2(readTag(parser, "ActionParameter")); + } + else + { + skip(parser); + } + + if(newAction.getAction().equals(Action_Enum.changeSoundProfile)) + { + String[] replacements = new String[] { "silent", "vibrate", "normal" }; + for(String s : replacements) + { + if(newAction.getParameter2().equals(s) && Profile.getByName(s) == null) // using an old profile name and there is no backing new profile by the same name + Profile.createDummyProfile(context, s); + } + } + } + +// Miscellaneous.logEvent("i", "New Rule from file", newPoi.name + "/" + String.valueOf(newPoi.radius) + "/" + String.valueOf(newPoi.location.getLatitude()) + "/" + String.valueOf(newPoi.location.getLongitude()) + "/" + String.valueOf(newPoi.changeWifiState) + "/" + String.valueOf(newPoi.desiredWifiState) + "/" + String.valueOf(newPoi.changeCameraState) + "/" + String.valueOf(newPoi.desiredCameraState) + "/" + String.valueOf(newPoi.changeSoundSetting) + "/" + String.valueOf(newPoi.desiredSoundSetting)); + + return newAction; + } + + // Processes title tags in the feed. + private static String readTag(XmlPullParser parser, String tagName) throws IOException, XmlPullParserException + { + parser.require(XmlPullParser.START_TAG, null, tagName); + String title = readText(parser); + parser.require(XmlPullParser.END_TAG, null, tagName); + return title; + } + + // For the tags title and summary, extracts their text values. + private static String readText(XmlPullParser parser) throws IOException, XmlPullParserException + { + String result = ""; + if (parser.next() == XmlPullParser.TEXT) + { + result = parser.getText(); + parser.nextTag(); + } + return result; + } + + private static void skip(XmlPullParser parser) throws XmlPullParserException, IOException + { + if (parser.getEventType() != XmlPullParser.START_TAG) + { + throw new IllegalStateException(); + } + int depth = 1; + while (depth != 0) + { + switch (parser.next()) + { + case XmlPullParser.END_TAG: + depth--; + break; + case XmlPullParser.START_TAG: + depth++; + break; + } + } + } + + public static boolean migrateFilesFromRootToFolder(String oldPath, String newPath) + { + File oldDir = new File(oldPath); + if(oldDir.isDirectory()) + { + File newDir = new File(newPath); + + File[] files = oldDir.listFiles(); + for(File file : files) + { + if(file.getName().startsWith("Automation") && file.isFile()) + file.renameTo(new File(newDir, file.getName())); + } + + return true; + } + + return false; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/jens/automation2/location/CellLocationChangedReceiver.java b/app/src/main/java/com/jens/automation2/location/CellLocationChangedReceiver.java new file mode 100644 index 0000000..bdd85e9 --- /dev/null +++ b/app/src/main/java/com/jens/automation2/location/CellLocationChangedReceiver.java @@ -0,0 +1,407 @@ +package com.jens.automation2.location; + +import android.content.Context; +import android.location.Criteria; +import android.location.Location; +import android.location.LocationListener; +import android.location.LocationManager; +import android.os.Build; +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.telephony.CellLocation; +import android.telephony.PhoneStateListener; +import android.telephony.TelephonyManager; +import android.util.Log; +import android.widget.Toast; + +import com.jens.automation2.Action; +import com.jens.automation2.ActivityPermissions; +import com.jens.automation2.AutomationService; +import com.jens.automation2.Miscellaneous; +import com.jens.automation2.PointOfInterest; +import com.jens.automation2.R; +import com.jens.automation2.Rule; +import com.jens.automation2.Settings; +import com.jens.automation2.receivers.ConnectivityReceiver; + +import java.util.Date; + +public class CellLocationChangedReceiver extends PhoneStateListener +{ + private LocationManager myLocationManager; + private Location currentLocation; + public MyLocationListener myLocationListener = new MyLocationListener(); + public Boolean locationListenerArmed = false; + public Date lastCellLocationUpdate; + protected static boolean followUpdate = true; + protected static TimeoutHandler timeoutHandler = null; + protected static boolean timeoutHandlerActive = false; + protected static boolean cellLocationListenerActive = false; + protected static CellLocationChangedReceiver instance; + protected static TelephonyManager telephonyManager; + + public static boolean isCellLocationListenerActive() + { + return cellLocationListenerActive; + } + + protected static CellLocationChangedReceiver getInstance() + { + if(instance == null) + instance = new CellLocationChangedReceiver(); + + return instance; + } + + @Override + public void onCellLocationChanged(CellLocation location) + { + super.onCellLocationChanged(location); + + if(Settings.useAccelerometerForPositioning) + SensorActivity.startAccelerometerTimer(); + + if(followUpdate) + { + Date currentDate = new Date(); + + Miscellaneous.logEvent("i", "CellLocation", String.format(Miscellaneous.getAnyContext().getResources().getString(R.string.cellMastChanged), location.toString()), 3); + + if(Settings.useAccelerometerForPositioning) //and last cell mast change longer than x minutes in the past + { + PointOfInterest possiblyActivePoi = PointOfInterest.getActivePoi(); + if( possiblyActivePoi != null ) //if any poi is active + { + // Did the last activated rule activate wifi? Then we don't need accelerometer, we'll use wifiReceiver + try + { + for(Action action : Rule.getLastActivatedRule().getActionSet()) + { + if(action.getAction() == Action.Action_Enum.turnWifiOn) + { + // we will be using wifiReceiver, deactivate AccelerometerTimer if applicable + SensorActivity.stopAccelerometerTimer(); + } + } + } + catch(NullPointerException ne) + { + // Nothing to do, there is no last activated rule. Wifi hasn't been activated so we don't + // deactive accelerometer receiver. + } + } + else + { + if(lastCellLocationUpdate == null) + SensorActivity.startAccelerometerTimer(); + else + { + long timeSinceLastUpdate = currentDate.getTime() - lastCellLocationUpdate.getTime(); //in milliseconds + if(timeSinceLastUpdate > Settings.useAccelerometerAfterIdleTime*60*1000) + { + SensorActivity.startAccelerometerTimer(); + } + else + { + //reset timer + SensorActivity.resetAccelerometerTimer(); + } + } + } + } + lastCellLocationUpdate = currentDate; + + myLocationManager = (LocationManager) AutomationService.getInstance().getSystemService(Context.LOCATION_SERVICE); + currentLocation = getLocation("coarse"); + AutomationService.getInstance().getLocationProvider().setCurrentLocation(currentLocation, false); + } + else + { + Miscellaneous.logEvent("i", "CellLocation", "Cell mast changed, but only initial update, ignoring this one.", 4); + followUpdate = true; //for next run + } + } + + + public Location getLocation(String accuracy) + { + Criteria crit = new Criteria(); + + String myProviderName; + + // If privacy mode or no data connection available + if(Settings.privacyLocationing | !ConnectivityReceiver.isDataConnectionAvailable(AutomationService.getInstance())) + { + Miscellaneous.logEvent("i", "CellLocation", Miscellaneous.getAnyContext().getResources().getString(R.string.enforcingGps), 4); + myProviderName = LocationManager.GPS_PROVIDER; + } + else + { + Miscellaneous.logEvent("i", "CellLocation", Miscellaneous.getAnyContext().getResources().getString(R.string.notEnforcingGps), 4); + + if(accuracy.equals("coarse")) + { + crit.setPowerRequirement(Criteria.POWER_LOW); + crit.setAltitudeRequired(false); + crit.setSpeedRequired(false); + crit.setBearingRequired(false); + crit.setCostAllowed(false); + crit.setAccuracy(Criteria.ACCURACY_COARSE); + } + else //equals "fine" + { + crit.setPowerRequirement(Criteria.POWER_LOW); + crit.setAltitudeRequired(false); + crit.setSpeedRequired(false); + crit.setBearingRequired(false); + //crit.setCostAllowed(false); + crit.setAccuracy(Criteria.ACCURACY_FINE); + } + + myProviderName = myLocationManager.getBestProvider(crit, true); + } + + if(myProviderName == null) + { + Toast.makeText(Miscellaneous.getAnyContext(), "No suitable location provider could be used.", Toast.LENGTH_LONG).show(); + return null; + } + else + { + if(!myLocationManager.isProviderEnabled(myProviderName)) + { + if(myProviderName.equals(LocationManager.NETWORK_PROVIDER)) + myProviderName = LocationManager.GPS_PROVIDER; + } + + if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) + myProviderName = LocationManager.GPS_PROVIDER; + + // Arm location updates + if(!locationListenerArmed) + startLocationListener(myProviderName); + + try + { + return myLocationManager.getLastKnownLocation(myProviderName); + } + catch(NullPointerException e) + { + Toast.makeText(Miscellaneous.getAnyContext(), "No last known location. Aborting...", Toast.LENGTH_LONG).show(); + return null; + } + } + } + + private void startLocationListener(String providerToBeUsed) + { + Miscellaneous.logEvent("i", "LocationListener", "Arming location listener, Provider " + providerToBeUsed, 4); + myLocationManager.requestLocationUpdates(providerToBeUsed, Settings.minimumTimeBetweenUpdate, Settings.minimumDistanceChangeForNetworkUpdate, myLocationListener); + locationListenerArmed = true; + + // (re)set timeout + if(timeoutHandlerActive) + stopTimeOutHandler(); + startTimeOutHandler(); + } + private void stopLocationListener() + { + Miscellaneous.logEvent("i", "LocationListener", "Disarming location listener.", 4); + myLocationManager.removeUpdates(myLocationListener); + locationListenerArmed = false; + + if(timeoutHandlerActive) + stopTimeOutHandler(); + } + + public Location getCurrentLocation() + { + return currentLocation; + } + + + public void setCurrentLocation(Location currentLocation) + { + this.currentLocation = currentLocation; + } + + + public class MyLocationListener implements LocationListener + { + @Override + public void onLocationChanged(Location up2DateLocation) + { + if(timeoutHandlerActive) + { + stopTimeOutHandler(); + } + + setCurrentLocation(up2DateLocation); + AutomationService.getInstance().getLocationProvider().setCurrentLocation(up2DateLocation, false); + // This is relevant if the program just started, knows where it is, but hasn't reached any POI. + // The below PointOfInterest.positionUpdate() will not update the notification in that case. +// if(!currentLocation.equals(up2DateLocation)) +// parentLocationProvider.parentService.updateNotification(); + + if(up2DateLocation.getAccuracy() < Settings.satisfactoryAccuracyNetwork) + { + myLocationManager.removeUpdates(this); + locationListenerArmed = false; + Miscellaneous.logEvent("i", "LocationListener", "Disarmed location listener, accuracy reached", 4); + } + +// Miscellaneous.logEvent("i", "LocationListener", "Giving update to POI class"); +// PointOfInterest.positionUpdate(up2DateLocation, parentLocationProvider.parentService); + } + + @Override + public void onProviderDisabled(String provider) + { + // TODO Auto-generated method stub + + } + + @Override + public void onProviderEnabled(String provider) + { + // TODO Auto-generated method stub + + } + + @Override + public void onStatusChanged(String provider, int status, Bundle extras) + { + // TODO Auto-generated method stub + + } + } + + class TimeoutHandler extends Handler + { + @Override + public void handleMessage(Message msg) + { + super.handleMessage(msg); + + if(msg.what == 1) + { + Context context = Miscellaneous.getAnyContext(); + Miscellaneous.logEvent("i", context.getResources().getString(R.string.gpsMeasurement), context.getResources().getString(R.string.gpsMeasurementTimeout), 4); + stopLocationListener(); + } + } + } + + private void startTimeOutHandler() + { + if(timeoutHandler == null) + timeoutHandler = new TimeoutHandler(); + + Message message = new Message(); + message.what = 1; + timeoutHandler.sendMessageDelayed(message, Settings.gpsTimeout * 1000); + timeoutHandlerActive = true; + } + private void stopTimeOutHandler() + { + if(timeoutHandler == null) + timeoutHandler = new TimeoutHandler(); + + timeoutHandler.removeMessages(1); + timeoutHandlerActive = false; + } + + public static void startCellLocationChangedReceiver() + { + if(telephonyManager == null) + telephonyManager = (TelephonyManager) AutomationService.getInstance().getSystemService(Context.TELEPHONY_SERVICE); + + try + { + if(!cellLocationListenerActive) + { + if(!ConnectivityReceiver.isAirplaneMode(AutomationService.getInstance())) + { + if(WifiBroadcastReceiver.mayCellLocationReceiverBeActivated()) + { + // if(!ConnectivityReceiver.isDataConnectionAvailable(parentService)) + // { + telephonyManager.listen(getInstance(), PhoneStateListener.LISTEN_CELL_LOCATION); + cellLocationListenerActive = true; + Miscellaneous.logEvent("i", "cellReceiver", "Starting cellLocationListener", 4); + + SensorActivity.stopAccelerometerTimer(); + SensorActivity.stopAccelerometerReceiver(); + // this.stopWifiReceiver(); + + /* + We could now set a timer when we could activate a location check. + If that fires we need to check if maybe another location check has been performed. + */ + + if(!LocationProvider.speedTimerActive) + LocationProvider.startSpeedTimer(LocationProvider.getEtaAtNextPoi()); + // } + // else + // Miscellaneous.logEvent("i", "cellReceiver", "Not starting cellLocationListener because we have no data connection.", 4); + } + else + Miscellaneous.logEvent("w", "cellReceiver", "Wanted to activate CellLocationChangedReceiver, but Wifi-Receiver says not to.", 4); + } + else + Miscellaneous.logEvent("i", "cellReceiver", "Not starting cellLocationListener because Airplane mode is active.", 4); + } + } + catch(Exception ex) + { + Miscellaneous.logEvent("e", "Wifi Listener", "Error starting cellLocationListener: " + Log.getStackTraceString(ex), 3); + } + } + + public static void stopCellLocationChangedReceiver() + { + try + { + if(cellLocationListenerActive) + { + Miscellaneous.logEvent("i", "cellReceiver", "Stopping cellLocationListener", 4); + + getInstance().stopTimeOutHandler(); + getInstance().stopLocationListener(); + + if(LocationProvider.speedTimerActive) + LocationProvider.stopSpeedTimer(); + + telephonyManager.listen(instance, PhoneStateListener.LISTEN_NONE); + cellLocationListenerActive = false; + + // May have comparison measurements active. + PointOfInterest.stopRoutine(); + } + } + catch(Exception ex) + { + Miscellaneous.logEvent("e", "Wifi Listener", "Error stopping cellLocationListener: " + Log.getStackTraceString(ex), 3); + } + } + + public static void resetFollowUpdate() + { + followUpdate = false; + } + + public static boolean haveAllPermission() + { + return ActivityPermissions.havePermission("android.permission.ACCESS_FINE_LOCATION", Miscellaneous.getAnyContext()) + && + ActivityPermissions.havePermission("android.permission.ACCESS_COARSE_LOCATION", Miscellaneous.getAnyContext()) + && + ActivityPermissions.havePermission("android.permission.ACCESS_NETWORK_STATE", Miscellaneous.getAnyContext()) + && + ActivityPermissions.havePermission("android.permission.INTERNET", Miscellaneous.getAnyContext()) + && + ActivityPermissions.havePermission("android.permission.ACCESS_WIFI_STATE", Miscellaneous.getAnyContext()); + } +} + diff --git a/app/src/main/java/com/jens/automation2/location/GeofenceBroadcastReceiver.java b/app/src/main/java/com/jens/automation2/location/GeofenceBroadcastReceiver.java new file mode 100644 index 0000000..d2c78e6 --- /dev/null +++ b/app/src/main/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/main/java/com/jens/automation2/location/GeofenceIntentService.java b/app/src/main/java/com/jens/automation2/location/GeofenceIntentService.java new file mode 100644 index 0000000..3b20edb --- /dev/null +++ b/app/src/main/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/location/LocationProvider.java b/app/src/main/java/com/jens/automation2/location/LocationProvider.java new file mode 100644 index 0000000..76ec703 --- /dev/null +++ b/app/src/main/java/com/jens/automation2/location/LocationProvider.java @@ -0,0 +1,506 @@ +package com.jens.automation2.location; + +import android.content.Context; +import android.location.Location; +import android.location.LocationListener; +import android.location.LocationManager; +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.util.Log; + +import com.jens.automation2.ActivityMainScreen; +import com.jens.automation2.AutomationService; +import com.jens.automation2.Miscellaneous; +import com.jens.automation2.PointOfInterest; +import com.jens.automation2.R; +import com.jens.automation2.Rule; +import com.jens.automation2.Settings; +import com.jens.automation2.Trigger.Trigger_Enum; +import com.jens.automation2.receivers.ConnectivityReceiver; +import com.jens.automation2.receivers.PhoneStatusListener; + +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Calendar; + +public class LocationProvider +{ + + protected static boolean passiveLocationListenerActive = false; + + protected static LocationListener passiveLocationListener; + + protected static LocationProvider locationProviderInstance = null; + + protected AutomationService parentService; + public AutomationService getParentService() + { + return parentService; + } + + protected Location currentLocation; + protected static Location currentLocationStaticCopy; + protected static double speed; + protected ArrayList locationList = new ArrayList(); + protected static Handler speedHandler = null; + protected static boolean speedTimerActive = false; + protected static Calendar etaAtNextPoi = null; + + public static Calendar getEtaAtNextPoi() + { + return etaAtNextPoi; + } + + public LocationProvider(AutomationService parent) + { + parentService = parent; + locationProviderInstance = this; + + startLocationService(); + } + + public static LocationProvider getInstance() + { + return locationProviderInstance; + } + + public Location getCurrentLocation() + { + return currentLocation; + } + public static Location getLastKnownLocation() + { + return currentLocationStaticCopy; + } + + public static double getSpeed() + { + return speed; + } + + public static void setSpeed(double speed) + { + LocationProvider.speed = speed; + + /* + Check if the last location update may be to old. + It could be that for whatever reason we didn't get a recent location, but the current speed + indicates we have moved quite a bit. + */ + + Calendar now = Calendar.getInstance(); + + float distanceToClosestPoi = PointOfInterest.getClosestPOI(getLastKnownLocation()).getLocation().distanceTo(getLastKnownLocation()); + long timeInSecondsPassedSinceLastLocationUpdate = (now.getTimeInMillis() - getLastKnownLocation().getTime()) / 1000; + + // Could be we were driving towards it instead of away, but we'll ignore that for the moment. + + long secondsRequiredForArrival = now.getTimeInMillis()/1000 / (1000 / 60 * 60); + now.add(Calendar.SECOND, (int)secondsRequiredForArrival); + + etaAtNextPoi = now; + + if(speedTimerActive) + resetSpeedTimer(etaAtNextPoi); + else + startSpeedTimer(etaAtNextPoi); + } + + public void setCurrentLocation(Location newLocation, boolean skipVerification) + { + Miscellaneous.logEvent("i", "Location", "Setting location.", 4); + + currentLocation = newLocation; + currentLocationStaticCopy = newLocation; + + Miscellaneous.logEvent("i", "LocationListener", "Giving update to POI class", 4); + PointOfInterest.positionUpdate(newLocation, parentService, false, skipVerification); + + try + { + if( + locationList.size() >= 1 + && + locationList.get(locationList.size()-1).getTime() == newLocation.getTime() + && + locationList.get(locationList.size()-1).getProvider().equals(newLocation.getProvider()) + ) + { + // This is a duplicate update, do not store it + Miscellaneous.logEvent("i", "LocationListener", "Duplicate location, ignoring.", 4); + } + else + { + Miscellaneous.logEvent("i", "Speed", "Commencing speed calculation.", 4); + // This part keeps the last two location entries to determine the current speed. + + locationList.add(newLocation); + + if(newLocation.hasSpeed()) + { + Miscellaneous.logEvent("i", "Speed", "Location has speed, taking that: " + String.valueOf(newLocation.getSpeed()) + " km/h", 4); + setSpeed(newLocation.getSpeed()); // Take the value that came with the location, that should be more precise + } + else + { + if (locationList.size() >= 2) + { + Miscellaneous.logEvent("i", "Speed", "Trying to calculate speed based on the last locations.", 4); + + double currentSpeed; + long timeDifferenceInSeconds = (Math.abs(locationList.get(locationList.size() - 2).getTime() - locationList.get(locationList.size() - 1).getTime())) / 1000; //milliseconds + if (timeDifferenceInSeconds <= Settings.speedMaximumTimeBetweenLocations * 60) + { + double distanceTraveled = locationList.get(locationList.size() - 2).distanceTo(locationList.get(locationList.size() - 1)); //results in meters + + if (timeDifferenceInSeconds == 0) + { + Miscellaneous.logEvent("w", "Speed", "No time passed since last position. Can't calculate speed here.", 4); + return; + } + + currentSpeed = distanceTraveled / timeDifferenceInSeconds * 3.6; // convert m/s --> km/h + + /* + Due to strange factors the time difference might be 0 resulting in mathematical error. + */ + if (Double.isInfinite(currentSpeed) | Double.isNaN(currentSpeed)) + Miscellaneous.logEvent("i", "Speed", "Error while calculating speed.", 4); + else + { + Miscellaneous.logEvent("i", "Speed", "Current speed: " + String.valueOf(currentSpeed) + " km/h", 2); + + setSpeed(currentSpeed); + + // execute matching rules containing speed + ArrayList ruleCandidates = Rule.findRuleCandidatesBySpeed(); + for (Rule oneRule : ruleCandidates) + { + if (oneRule.applies(this.getParentService())) + oneRule.activate(getParentService(), false); + } + } + } + else + Miscellaneous.logEvent("i", "Speed", "Last two locations are too far apart in terms of time. Cannot use them for speed calculation.", 4); + + + while (locationList.size() > 2) + { + // Remove all entries except for the last 2 + Miscellaneous.logEvent("i", "Speed", "About to delete oldest position record until only 2 left. Currently have " + String.valueOf(locationList.size()) + " records.", 4); + locationList.remove(0); + } + } + else + { + Miscellaneous.logEvent("w", "Speed", "Don't have enough values for speed calculation, yet.", 3); + } + } + } + } + catch(Exception e) + { + Miscellaneous.logEvent("e", "Speed", "Error during speed calculation: " + Log.getStackTraceString(e), 3); + } + + AutomationService.updateNotification(); + + if(AutomationService.isMainActivityRunning(parentService)) + ActivityMainScreen.updateMainScreen(); + } + + public void startLocationService() + { +// if(Settings.useAccelerometerForPositioning && !Miscellaneous.isAndroidEmulator()) +// { +// accelerometerHandler = new AccelerometerHandler(); +// mySensorActivity = new SensorActivity(this); +// } + + // startPhoneStateListener + PhoneStatusListener.startPhoneStatusListener(parentService); // also used to mute anouncements during calls + + // startConnectivityReceiver + ConnectivityReceiver.startConnectivityReceiver(parentService); + + if(Settings.positioningEngine == 0) + { + // startCellLocationChangedReceiver + if (!ConnectivityReceiver.isAirplaneMode(this.parentService) && WifiBroadcastReceiver.mayCellLocationReceiverBeActivated() && (Rule.isAnyRuleUsing(Trigger_Enum.pointOfInterest) | Rule.isAnyRuleUsing(Trigger_Enum.speed))) + CellLocationChangedReceiver.startCellLocationChangedReceiver(); + + // startPassiveLocationListener + if(Rule.isAnyRuleUsing(Trigger_Enum.pointOfInterest) | Rule.isAnyRuleUsing(Trigger_Enum.speed)) + startPassiveLocationListener(); + } + else + { + if(Rule.isAnyRuleUsing(Trigger_Enum.pointOfInterest)) + GeofenceIntentService.startService(); + } + } + + public void stopLocationService() + { + try + { + PhoneStatusListener.stopPhoneStatusListener(parentService); + CellLocationChangedReceiver.stopCellLocationChangedReceiver(); + SensorActivity.stopAccelerometerReceiver(); + WifiBroadcastReceiver.stopWifiReceiver(); + SensorActivity.stopAccelerometerReceiver(); + stopPassiveLocationListener(); + } + catch(Exception e) + { + Miscellaneous.logEvent("e", "cellReceiver", "Error stopping LocationReceiver: " + Log.getStackTraceString(e), 3); + } + } + + public void startPassiveLocationListener() + { + if(!passiveLocationListenerActive) + { + Miscellaneous.logEvent("i", "LocationListener", "Arming passive location listener.", 4); + LocationManager myLocationManager = (LocationManager) parentService.getSystemService(Context.LOCATION_SERVICE); + passiveLocationListener = new MyPassiveLocationListener(); + try + { + myLocationManager.requestLocationUpdates(LocationManager.PASSIVE_PROVIDER, Settings.minimumTimeBetweenUpdate, Settings.minimumDistanceChangeForNetworkUpdate, passiveLocationListener); + } + catch(SecurityException e) + {} + passiveLocationListenerActive = true; + } + } + public void stopPassiveLocationListener() + { + if(passiveLocationListenerActive) + { + Miscellaneous.logEvent("i", "LocationListener", "Disarming passive location listener.", 4); + LocationManager myLocationManager = (LocationManager) parentService.getSystemService(Context.LOCATION_SERVICE); + myLocationManager.removeUpdates(passiveLocationListener); + passiveLocationListenerActive = false; + } + } + + public class MyPassiveLocationListener implements LocationListener + { + @Override + public void onLocationChanged(Location up2DateLocation) + { + Miscellaneous.logEvent("i", "Location", "Got passive location update, provider: " + up2DateLocation.getProvider(), 3); + setCurrentLocation(up2DateLocation, true); + } + + @Override + public void onProviderDisabled(String provider) + { + // TODO Auto-generated method stub + + } + + @Override + public void onProviderEnabled(String provider) + { + // TODO Auto-generated method stub + + } + + @Override + public void onStatusChanged(String provider, int status, Bundle extras) + { + // TODO Auto-generated method stub + + } + } + + public void handleAirplaneMode(boolean state) + { + if(state) + { + Miscellaneous.logEvent("i", "Airplane mode", "CellLocationChangedReceiver will be deactivated due to Airplane mode.", 2); + CellLocationChangedReceiver.stopCellLocationChangedReceiver(); + } + else + { + Miscellaneous.logEvent("i", "Airplane mode", "CellLocationChangedReceiver will be activated due to end of Airplane mode.", 2); + CellLocationChangedReceiver.startCellLocationChangedReceiver(); + } + } + + public void handleRoaming(Boolean roaming) + { + if(roaming) + { + Miscellaneous.logEvent("i", "Roaming", "We're on roaming.", 4); + if(CellLocationChangedReceiver.isCellLocationListenerActive()) + { + Miscellaneous.logEvent("i", "Roaming", "Disabling CellLocationChangedReceiver because we're on roaming.", 3); + CellLocationChangedReceiver.stopCellLocationChangedReceiver(); + } + } + else + { + Miscellaneous.logEvent("i", "Roaming", "We're not on roaming.", 4); + if(!CellLocationChangedReceiver.isCellLocationListenerActive()) + { + Miscellaneous.logEvent("i", "Roaming", "Enabling CellLocationChangedReceiver because we're not on roaming.", 3); + CellLocationChangedReceiver.startCellLocationChangedReceiver(); + } + } + } + + public void applySettingsAndRules() + { + /* + * This method's purpose is to check settings and rules and determine + * if changes in them require monitors to be started or stopped. + * It takes care only of those which are more expensive. + */ + + // TextToSpeech is handled in AutomationService class + + Miscellaneous.logEvent("i", "LocationProvider", this.getParentService().getResources().getString(R.string.applyingSettingsAndRules), 3); + + // *********** SETTING CHANGES *********** + if(Settings.useWifiForPositioning && !WifiBroadcastReceiver.isWifiListenerActive()) + { + Miscellaneous.logEvent("i", "LocationProvider", "Starting WifiReceiver because settings now allow to.", 4); + WifiBroadcastReceiver.startWifiReceiver(this); + } + else if(!Settings.useWifiForPositioning && WifiBroadcastReceiver.isWifiListenerActive()) + { + Miscellaneous.logEvent("i", "LocationProvider", "Shutting down WifiReceiver because settings forbid to.", 4); + WifiBroadcastReceiver.stopWifiReceiver(); + } + + if(Settings.useAccelerometerForPositioning && !SensorActivity.isAccelerometerReceiverActive()) + { + Miscellaneous.logEvent("i", "LocationProvider", "Starting accelerometerReceiver because settings now allow to.", 4); + SensorActivity.startAccelerometerReceiver(); + } + else if(!Settings.useAccelerometerForPositioning && SensorActivity.isAccelerometerReceiverActive()) + { + Miscellaneous.logEvent("i", "LocationProvider", "Shutting down accelerometerReceiver because settings forbid to.", 4); + SensorActivity.stopAccelerometerReceiver(); + } + + // *********** RULE CHANGES *********** + if(!CellLocationChangedReceiver.isCellLocationListenerActive() && (Rule.isAnyRuleUsing(Trigger_Enum.pointOfInterest) | Rule.isAnyRuleUsing(Trigger_Enum.speed))) + { + Miscellaneous.logEvent("i", "LocationProvider", "Starting NoiseListener CellLocationChangedReceiver because used in a new/changed rule.", 4); + if(CellLocationChangedReceiver.haveAllPermission()) + CellLocationChangedReceiver.startCellLocationChangedReceiver(); + } + else + { + Miscellaneous.logEvent("i", "LocationProvider", "Shutting down CellLocationChangedReceiver because not used in any rule.", 4); + CellLocationChangedReceiver.stopCellLocationChangedReceiver(); + } + + AutomationService.updateNotification(); + } + + public static void startSpeedTimer(Calendar timeOfForcedLocationCheck) + { + if(!speedTimerActive) + { + if(timeOfForcedLocationCheck == null) + { + Miscellaneous.logEvent("i", "SpeedTimer", "Have no value for speed timer. Using 5 minutes in the future.", 4); + timeOfForcedLocationCheck = Calendar.getInstance(); + timeOfForcedLocationCheck.add(Calendar.MINUTE, 5); + } + + Calendar calendar = Calendar.getInstance(); + SimpleDateFormat sdf = new SimpleDateFormat(Settings.dateFormat); + + Miscellaneous.logEvent("i", "SpeedTimer", "Starting SpeedTimer. Next forced location check would be at " + sdf.format(calendar.getTime()), 4); + + Message msg = new Message(); + msg.what = 1; + + if(speedHandler == null) + speedHandler = new SpeedHandler(); + + speedHandler.sendMessageAtTime(msg, timeOfForcedLocationCheck.getTimeInMillis()); +// speedHandler.sendMessageDelayed(msg, delayTime); + speedTimerActive = true; + } + } + + public static void stopSpeedTimer() + { + if(speedTimerActive) + { + Miscellaneous.logEvent("i", "SpeedTimer", "Stopping SpeedTimer.", 4); + +// Message msg = new Message(); +// msg.what = 0; + + if(speedHandler == null) + speedHandler = new SpeedHandler(); + else + speedHandler.removeMessages(1); + + speedTimerActive = false; + } + } + + + public static void resetSpeedTimer(Calendar timeOfForcedLocationCheck) + { + if(speedTimerActive) + { + if(timeOfForcedLocationCheck == null) + { + Miscellaneous.logEvent("i", "SpeedTimer", "Have no value for speed timer. Using 5 minutes in the future.", 4); + timeOfForcedLocationCheck = Calendar.getInstance(); + timeOfForcedLocationCheck.add(Calendar.MINUTE, 5); + } + + Calendar calendar = Calendar.getInstance(); + SimpleDateFormat sdf = new SimpleDateFormat(Settings.dateFormat); + + Miscellaneous.logEvent("i", "SpeedTimer", "Resetting SpeedTimer. Next forced location check would be at " + sdf.format(calendar.getTime()), 5); + speedHandler.removeMessages(1); + + Message msg = new Message(); + msg.what = 1; + speedHandler.sendMessageAtTime(msg, timeOfForcedLocationCheck.getTimeInMillis()); +// speedHandler.sendMessageDelayed(msg, delayTime); + speedTimerActive = true; + } + else + startSpeedTimer(timeOfForcedLocationCheck); + } + + static class SpeedHandler extends Handler + { + @Override + public void handleMessage(Message msg) + { + super.handleMessage(msg); + + if(msg.what == 1) + { + // time is up, no cell location updates since x minutes, start accelerometer + String text = "Timer triggered. Based on the last location and speed we may be at a POI. Forcing location update in case CellLocationChangedReceiver didn\'t fire."; +// Miscellaneous.logEvent("i", "AccelerometerHandler", text, 5); +// CellLocationChangedReceiver.stopCellLocationChangedReceiver(); +// startAccelerometerReceiver(); + Location currentLocation = CellLocationChangedReceiver.getInstance().getLocation("coarse"); + AutomationService.getInstance().getLocationProvider().setCurrentLocation(currentLocation, false); + } + /*else if(msg.what == 0) + { + String text = "Abort command received, deactivating SpeedReceiver"; + Miscellaneous.logEvent("i", "SpeedHandler", text, 4); + stopAccelerometerReceiver(); + }*/ + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/jens/automation2/location/SensorActivity.java b/app/src/main/java/com/jens/automation2/location/SensorActivity.java new file mode 100644 index 0000000..9a06a09 --- /dev/null +++ b/app/src/main/java/com/jens/automation2/location/SensorActivity.java @@ -0,0 +1,220 @@ +package com.jens.automation2.location; + + +import android.hardware.Sensor; +import android.hardware.SensorEvent; +import android.hardware.SensorEventListener; +import android.hardware.SensorManager; +import android.os.Handler; +import android.os.Message; +import android.util.Log; + +import com.jens.automation2.AutomationService; +import com.jens.automation2.Miscellaneous; +import com.jens.automation2.Settings; + +public class SensorActivity implements SensorEventListener +{ + protected static SensorActivity instance; + private final SensorManager mSensorManager; + private final Sensor mAccelerometer; + public LocationProvider parentLocationProvider; + public static boolean mInitialized = false; + public static float lastX, lastY, lastZ, deltaX, deltaY, deltaZ; + protected static Handler accelerometerHandler = null; + protected static boolean accelerometerReceiverActive = false; + protected static boolean accelerometerTimerActive = false; + + public static boolean isAccelerometerReceiverActive() + { + return accelerometerReceiverActive; + } + + public static boolean isAccelerometerTimerActive() + { + return accelerometerTimerActive; + } + + public static SensorActivity getInstance() + { + if(instance == null) + instance = new SensorActivity(AutomationService.getInstance().getLocationProvider()); + + return instance; + } + + public SensorActivity(LocationProvider parent) + { + this.parentLocationProvider = parent; + mSensorManager = (SensorManager)parentLocationProvider.parentService.getSystemService(parentLocationProvider.parentService.SENSOR_SERVICE); + mAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); + } + + protected void start() + { + mSensorManager.registerListener(this, mAccelerometer, SensorManager.SENSOR_DELAY_NORMAL); + } + + protected void stop() + { + mSensorManager.unregisterListener(this); + } + + public void onAccuracyChanged(Sensor sensor, int accuracy) + { + } + + public void onSensorChanged(SensorEvent event) + { + // Device has been moved + + float x = event.values[0]; + float y = event.values[1]; + float z = event.values[2]; + + if(mInitialized) + { + deltaX = Math.abs(lastX-x); + deltaY = Math.abs(lastY-y); + deltaZ = Math.abs(lastZ-z); + //Wenn das jetzt einen gewissen Grenzwert übersteigt, müßten wir den CellListener wieder aktivieren + if(deltaX > Settings.accelerometerMovementThreshold | deltaY > Settings.accelerometerMovementThreshold | deltaZ > Settings.accelerometerMovementThreshold) + { + String text = "Device has been moved. " + String.valueOf(deltaX)+" / "+String.valueOf(deltaY)+" / "+String.valueOf(deltaZ); + Miscellaneous.logEvent("i", "Accelerometer", text, 5); + CellLocationChangedReceiver.resetFollowUpdate(); + CellLocationChangedReceiver.startCellLocationChangedReceiver(); + } + } + else + { + lastX = x; + lastY = y; + lastZ = z; + mInitialized = true; + } + } + + protected static void startAccelerometerReceiver() + { + if(Settings.useAccelerometerForPositioning && !Miscellaneous.isAndroidEmulator()) + { + if(!accelerometerReceiverActive) + { + try + { + getInstance().start(); + accelerometerReceiverActive = true; + Miscellaneous.logEvent("i", "AccelerometerReceiver", "Starting AccelerometerReceiver", 4); + } + catch(Exception ex) + { + Miscellaneous.logEvent("e", "AccelerometerReceiver", "Error starting AccelerometerReceiver: " + Log.getStackTraceString(ex), 3); + } + } + } + } + protected static void stopAccelerometerReceiver() + { + if(Settings.useAccelerometerForPositioning && !Miscellaneous.isAndroidEmulator() && accelerometerReceiverActive) + { + try + { + getInstance().stop(); + accelerometerReceiverActive = false; + Miscellaneous.logEvent("i", "AccelerometerReceiver", "Stopping AccelerometerReceiver", 4); + } + catch(Exception ex) + { + Miscellaneous.logEvent("e", "AccelerometerReceiver", "Error stopping AccelerometerReceiver: " + Log.getStackTraceString(ex), 3); + } + } + } + + public static void startAccelerometerTimer() + { + if(Settings.useAccelerometerForPositioning && !Miscellaneous.isAndroidEmulator()) + { + if(!accelerometerTimerActive) + { + Miscellaneous.logEvent("i", "AccelerometerTimer", "Starting AccelerometerTimer", 4); + + long delayTime = Settings.useAccelerometerAfterIdleTime * 60 * 1000; + + Message msg = new Message(); + msg.what = 1; + + if(accelerometerHandler == null) + accelerometerHandler = new AccelerometerHandler(); + + accelerometerHandler.sendMessageDelayed(msg, delayTime); + accelerometerTimerActive = true; + } + } + /* + * else + * reset timer + */ + } + public static void stopAccelerometerTimer() + { + if(accelerometerTimerActive) + { + Miscellaneous.logEvent("i", "AccelerometerTimer", "Stopping AccelerometerTimer", 4); + +// Message msg = new Message(); +// msg.what = 0; + + if(accelerometerHandler == null) + accelerometerHandler = new AccelerometerHandler(); + else + accelerometerHandler.removeMessages(1); + +// accelerometerHandler.sendMessageDelayed(msg, 0); + accelerometerTimerActive = false; + } + } + + + public static void resetAccelerometerTimer() + { + if(accelerometerTimerActive) + { + Miscellaneous.logEvent("i", "AccelerometerTimer", "Resetting AccelerometerTimer", 5); + accelerometerHandler.removeMessages(1); + + long delayTime = Settings.useAccelerometerAfterIdleTime * 60 * 1000; +// Toast.makeText(parentService, "Sending message, delayed for " + String.valueOf(delayTime), Toast.LENGTH_LONG).show(); + + Message msg = new Message(); + msg.what = 1; + accelerometerHandler.sendMessageDelayed(msg, delayTime); + accelerometerTimerActive = true; + } + } + + static class AccelerometerHandler extends Handler + { + @Override + public void handleMessage(Message msg) + { + super.handleMessage(msg); + + if(msg.what == 1) + { + // time is up, no cell location updates since x minutes, start accelerometer + String text = String.valueOf(Settings.useAccelerometerAfterIdleTime) + " minutes passed"; + Miscellaneous.logEvent("i", "AccelerometerHandler", text, 5); + CellLocationChangedReceiver.stopCellLocationChangedReceiver(); + startAccelerometerReceiver(); + } + else if(msg.what == 0) + { + String text = "Abort command received, deactivating accelerometerReceiver"; + Miscellaneous.logEvent("i", "AccelerometerHandler", text, 4); + stopAccelerometerReceiver(); + } + } + + } +} diff --git a/app/src/main/java/com/jens/automation2/location/WifiBroadcastReceiver.java b/app/src/main/java/com/jens/automation2/location/WifiBroadcastReceiver.java new file mode 100644 index 0000000..54f8a2f --- /dev/null +++ b/app/src/main/java/com/jens/automation2/location/WifiBroadcastReceiver.java @@ -0,0 +1,229 @@ +package com.jens.automation2.location; + + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; +import android.net.wifi.WifiManager; +import android.util.Log; + +import com.jens.automation2.Miscellaneous; +import com.jens.automation2.PointOfInterest; +import com.jens.automation2.R; +import com.jens.automation2.Rule; +import com.jens.automation2.Settings; + +import java.util.ArrayList; + +public class WifiBroadcastReceiver extends BroadcastReceiver +{ + public static LocationProvider parentLocationProvider; + public static Boolean wasConnected = false; + protected static String lastWifiSsid = ""; + public static boolean lastConnectedState = false; + protected static boolean mayCellLocationChangedReceiverBeActivatedFromWifiPointOfWifi = true; + protected static WifiBroadcastReceiver wifiBrInstance; + protected static IntentFilter wifiListenerIntentFilter; + protected static boolean wifiListenerActive=false; + + + + public static String getLastWifiSsid() + { + return lastWifiSsid; + } + + public static void setLastWifiSsid(String newWifiSsid) + { + if(newWifiSsid.startsWith("\"") && newWifiSsid.endsWith("\"")) + newWifiSsid = newWifiSsid.substring(1, newWifiSsid.length()-1); + + WifiBroadcastReceiver.lastWifiSsid = newWifiSsid; + } + + public static boolean isWifiListenerActive() + { + return wifiListenerActive; + } + + public static boolean mayCellLocationReceiverBeActivated() + { + return mayCellLocationChangedReceiverBeActivatedFromWifiPointOfWifi; + } + + @Override + public void onReceive(Context context, Intent intent) + { + try + { + // int state = -1; + NetworkInfo myWifi = null; + + // if(intent.getAction().equals(WifiManager.RSSI_CHANGED_ACTION)) //gefeuert bei Verbindung + // { + // Miscellaneous.logEvent("i", "WifiReceiver", "RSSI_CHANGED_ACTION: " + String.valueOf(intent.getIntExtra(WifiManager.RSSI_CHANGED_ACTION, -1))); + // } + // else + if(intent.getAction().equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) //gefeuert bei Trennung + { + // state = intent.getIntExtra(WifiManager.NETWORK_STATE_CHANGED_ACTION, -1); + // Miscellaneous.logEvent("i", "WifiReceiver", "NETWORK_STATE_CHANGED_ACTION: " + String.valueOf(state)); + myWifi = intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO); + } + + + WifiManager myWifiManager = (WifiManager)context.getSystemService(Context.WIFI_SERVICE); + // ConnectivityManager connManager = (ConnectivityManager)context.getSystemService(context.CONNECTIVITY_SERVICE); + // myWifi = connManager.getNetworkInfo(ConnectivityManager.TYPE_WIFI); + // myWifi = state + // WifiInfo wifiInfo = myWifiManager.getConnectionInfo(); + + // SupplicantState supState = wifiInfo.getSupplicantState(); + + if(intent.getAction().equals(WifiManager.RSSI_CHANGED_ACTION)) // fired upon connection + { + String ssid = myWifiManager.getConnectionInfo().getSSID(); + setLastWifiSsid(ssid); + Miscellaneous.logEvent("i", "WifiReceiver", String.format(context.getResources().getString(R.string.connectedToWifi), getLastWifiSsid()), 2); + wasConnected = true; + lastConnectedState = true; + + if(Settings.useWifiForPositioning && PointOfInterest.reachedPoiWithActivateWifiRule()) // Poi has wifi + { + Miscellaneous.logEvent("i", "WifiReceiver", context.getResources().getString(R.string.poiHasWifiStoppingCellLocationListener), 2); + mayCellLocationChangedReceiverBeActivatedFromWifiPointOfWifi = false; + CellLocationChangedReceiver.stopCellLocationChangedReceiver(); + } + else + { + if(!PointOfInterest.reachedPoiWithActivateWifiRule()) // Poi has no wifi + Miscellaneous.logEvent("i", "WifiReceiver", context.getResources().getString(R.string.poiHasNoWifiNotStoppingCellLocationListener), 2); + } + + findRules(parentLocationProvider); + } + else if(myWifi.isConnectedOrConnecting()) // first time connect from wifi-listener-perspective + { + wasConnected = true; + Miscellaneous.logEvent("i", "WifiReceiver", "WifiReceiver just activated. Wifi already connected. Stopping CellLocationReceiver", 3); + mayCellLocationChangedReceiverBeActivatedFromWifiPointOfWifi = false; + CellLocationChangedReceiver.stopCellLocationChangedReceiver(); + SensorActivity.stopAccelerometerTimer(); + String ssid = myWifiManager.getConnectionInfo().getSSID(); + setLastWifiSsid(ssid); + lastConnectedState = true; + findRules(parentLocationProvider); + } + else if(!myWifi.isConnectedOrConnecting()) // really disconnected? because sometimes also fires on connect + { + if(wasConnected) // wir könnten einfach noch nicht daheim sein + { + try + { + wasConnected = false; + Miscellaneous.logEvent("i", "WifiReceiver", String.format(context.getResources().getString(R.string.disconnectedFromWifi), getLastWifiSsid()) + " Switching to CellLocationChangedReceiver.", 3); + mayCellLocationChangedReceiverBeActivatedFromWifiPointOfWifi = true; + CellLocationChangedReceiver.startCellLocationChangedReceiver(); + lastConnectedState = false; + findRules(parentLocationProvider); + } + catch(Exception e) + { + Miscellaneous.logEvent("e", "WifiReceiver", "Error starting CellLocationChangedReceiver", 3); + } + } + } + } + catch(Exception e) + { + Miscellaneous.logEvent("e", "WifiReceiver", "Error in WifiReceiver->onReceive(): " + e.getMessage(), 3); + } + } + + public static void findRules(LocationProvider parentLocationProvider) + { + ArrayList ruleCandidates = Rule.findRuleCandidatesByWifiConnection(); + for(Rule oneRule : ruleCandidates) + { + if(oneRule.applies(parentLocationProvider.parentService)) + oneRule.activate(parentLocationProvider.parentService, false); + } + } + + public static boolean isWifiEnabled(Context context) + { + try + { + WifiManager myWifiManager = (WifiManager)context.getSystemService(Context.WIFI_SERVICE); + return myWifiManager.isWifiEnabled(); + } + catch(Exception e) + { + Miscellaneous.logEvent("e", "WifiReceiver->isWifiEnabled()", Log.getStackTraceString(e), 3); + return false; + } + } + + public static boolean isWifiConnected(Context context) + { + try + { + ConnectivityManager connManager = (ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE); + NetworkInfo myWifi = connManager.getNetworkInfo(ConnectivityManager.TYPE_WIFI); + return myWifi.isConnected(); + } + catch(Exception e) + { + Miscellaneous.logEvent("e", "WifiReceiver->isWifiConnected()", Log.getStackTraceString(e), 3); + return false; + } + } + + public static void startWifiReceiver(LocationProvider loc) + { + try + { + if(!wifiListenerActive) + { + Miscellaneous.logEvent("i", "Wifi Listener", "Starting wifiListener", 4); + if(wifiListenerIntentFilter == null) + { + wifiListenerIntentFilter = new IntentFilter(); + wifiListenerIntentFilter.addAction(WifiManager.RSSI_CHANGED_ACTION); + wifiListenerIntentFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION); + } + if(wifiBrInstance == null) + { + wifiBrInstance = new WifiBroadcastReceiver(); + WifiBroadcastReceiver.parentLocationProvider = loc; + } + loc.getParentService().registerReceiver(wifiBrInstance, wifiListenerIntentFilter); + wifiListenerActive = true; + } + } + catch(Exception ex) + { + Miscellaneous.logEvent("e", "Wifi Listener", "Error starting wifiListener: " + Log.getStackTraceString(ex), 3); + } + } + public static void stopWifiReceiver() + { + try + { + if(wifiListenerActive) + { + Miscellaneous.logEvent("i", "Wifi Listener", "Stopping wifiListener", 4); + wifiListenerActive = false; + parentLocationProvider.getParentService().unregisterReceiver(wifiBrInstance); + } + } + catch(Exception ex) + { + Miscellaneous.logEvent("e", "Wifi Listener", "Error stopping wifiListener: " + Log.getStackTraceString(ex), 3); + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/jens/automation2/receivers/ActivityDetectionReceiver.java b/app/src/main/java/com/jens/automation2/receivers/ActivityDetectionReceiver.java new file mode 100644 index 0000000..d4a5158 --- /dev/null +++ b/app/src/main/java/com/jens/automation2/receivers/ActivityDetectionReceiver.java @@ -0,0 +1,400 @@ +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); + } + } + + public static boolean isPlayServiceAvailable() + { + if(GooglePlayServicesUtil.isGooglePlayServicesAvailable(Miscellaneous.getAnyContext()) == ConnectionResult.SUCCESS) + return true; + else + return false; + } + + + @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/main/java/com/jens/automation2/receivers/AlarmListener.java b/app/src/main/java/com/jens/automation2/receivers/AlarmListener.java new file mode 100644 index 0000000..4c7bf10 --- /dev/null +++ b/app/src/main/java/com/jens/automation2/receivers/AlarmListener.java @@ -0,0 +1,301 @@ +package com.jens.automation2.receivers; + +import android.app.AlarmManager; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; + +import com.jens.automation2.AutomationService; +import com.jens.automation2.Miscellaneous; +import com.jens.automation2.Rule; +import com.jens.automation2.Trigger; +import com.jens.automation2.Trigger.Trigger_Enum; + +import java.sql.Time; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Date; + +public class AlarmListener extends BroadcastReceiver implements AutomationListenerInterface +{ + private static AutomationService automationServiceRef; + private static AlarmManager centralAlarmManagerInstance; +// private static Intent alarmIntent; +// private static PendingIntent alarmPendingIntent; + private static boolean alarmListenerActive=false; + private static ArrayList alarmCandidates = new ArrayList(); + + private static ArrayList requestCodeList = new ArrayList(); + + public static void startAlarmListener(final AutomationService automationServiceRef) + { + AlarmListener.startAlarmListenerInternal(automationServiceRef); + } + public static void stopAlarmListener(Context context) + { + AlarmListener.stopAlarmListenerInternal(); + } + + public static boolean isAlarmListenerActive() + { + return alarmListenerActive; + } + + @Override + public void onReceive(Context context, Intent intent) + { + Miscellaneous.logEvent("i", "AlarmListener", "Alarm received", 2); + Date now = new Date(); + String timeString = String.valueOf(now.getHours()) + ":" + String.valueOf(now.getMinutes()) + ":" + String.valueOf(now.getSeconds()); + Time passTime = Time.valueOf(timeString); + + ArrayList allRulesWithNowInTimeFrame = Rule.findRuleCandidatesByTime(passTime); + for(int i=0; i allRulesWithTimeFrames = new ArrayList(); + allRulesWithTimeFrames = Rule.findRuleCandidatesByTimeFrame(); + for(Rule oneRule : allRulesWithTimeFrames) + { + for(Trigger oneTrigger : oneRule.getTriggerSet()) + { + if(oneTrigger.getTriggerType() == Trigger_Enum.timeFrame) + { + Calendar calNow, calSet; + Time setTime; + + if(oneTrigger.getTriggerParameter()) + setTime = oneTrigger.getTimeFrame().getTriggerTimeStart(); + else + setTime = oneTrigger.getTimeFrame().getTriggerTimeStop(); + + calNow = Calendar.getInstance(); + calSet = (Calendar) calNow.clone(); + calSet.set(Calendar.HOUR_OF_DAY, setTime.getHours()); + calSet.set(Calendar.MINUTE, setTime.getMinutes()); + calSet.set(Calendar.SECOND, 0); + calSet.set(Calendar.MILLISECOND, 0); + // At this point calSet would be a scheduling candidate. It's just the day the might not be right, yet. + + long milliSecondsInAWeek = 1000 * 60 * 60 * 24 * 7; + + for(int dayOfWeek : oneTrigger.getTimeFrame().getDayList()) + { + Calendar calSetWorkingCopy = (Calendar) calSet.clone(); + +// calSetWorkingCopy.set(Calendar.HOUR_OF_DAY, setTime.getHours()); +// calSetWorkingCopy.set(Calendar.MINUTE, setTime.getMinutes()); +// calSetWorkingCopy.set(Calendar.SECOND, 0); +// calSetWorkingCopy.set(Calendar.MILLISECOND, 0); + + int diff = dayOfWeek - calNow.get(Calendar.DAY_OF_WEEK); +// Log.i("AlarmManager", "Today: " + String.valueOf(calNow.get(Calendar.DAY_OF_WEEK)) + " / Sched.Day: " + String.valueOf(dayOfWeek) + " Difference to target day is: " + String.valueOf(diff)); + if(diff == 0) //if we're talking about the current day, is the time still in the future? + { + if(calSetWorkingCopy.getTime().getHours() < calNow.getTime().getHours()) + { +// Log.i("AlarmManager", "calSetWorkingCopy.getTime().getHours(" + String.valueOf(calSetWorkingCopy.getTime().getHours()) + ") < calNow.getTime().getHours(" + String.valueOf(calNow.getTime().getHours()) + ")"); + calSetWorkingCopy.add(Calendar.DAY_OF_MONTH, 7); //add a week + } + else if(calSetWorkingCopy.getTime().getHours() == calNow.getTime().getHours()) + { +// Log.i("AlarmManager", "calSetWorkingCopy.getTime().getHours() == calNow.getTime().getHours()"); + if(calSetWorkingCopy.getTime().getMinutes() <= calNow.getTime().getMinutes()) + { +// Log.i("AlarmManager", "calSetWorkingCopy.getTime().getMinutes() < calNow.getTime().getMinutes()"); + calSetWorkingCopy.add(Calendar.DAY_OF_MONTH, 7); //add a week + } + } + } + else if(diff < 0) + { +// Miscellaneous.logEvent("i", "AlarmManager", "Adding " + String.valueOf(diff+7) + " on top of " + String.valueOf(calSetWorkingCopy.get(Calendar.DAY_OF_WEEK))); + calSetWorkingCopy.add(Calendar.DAY_OF_WEEK, diff+7); // it's a past weekday, schedule for next week + } + else + { +// Miscellaneous.logEvent("i", "AlarmManager", "Adding " + String.valueOf(diff) + " on top of " + String.valueOf(calSetWorkingCopy.get(Calendar.DAY_OF_WEEK))); + calSetWorkingCopy.add(Calendar.DAY_OF_WEEK, diff); // it's a future weekday, schedule for that day + } + + i++; + i=(int)System.currentTimeMillis(); + String calSetWorkingCopyString = sdf.format(calSetWorkingCopy.getTime()) + " RequestCode: " + String.valueOf(i); +// Miscellaneous.logEvent("i", "AlarmManager", "Setting repeating alarm because of rule: " + oneRule.getName() + " beginning at " + calSetWorkingCopyString); + + alarmCandidates.add(calSetWorkingCopy.getTimeInMillis()); +// Intent alarmIntent = new Intent(automationServiceRef, AlarmListener.class); +// alarmIntent.setData(Uri.parse("myalarms://" + i)); +// PendingIntent alarmPendingIntent = PendingIntent.getBroadcast(automationServiceRef, i, alarmIntent, PendingIntent.FLAG_UPDATE_CURRENT); +// centralAlarmManagerInstance.setInexactRepeating(AlarmManager.RTC_WAKEUP, calSetWorkingCopy.getTimeInMillis(), milliSecondsInAWeek, alarmPendingIntent); +// requestCodeList.add(i); + } + } + } + } + +// // get a Calendar object with current time +// Calendar cal = Calendar.getInstance(); +// cal.add(Calendar.SECOND, 10); +// String calSetWorkingCopyString2 = sdf.format(cal.getTime()); +// Miscellaneous.logEvent("i", "AlarmManager", "Setting repeating alarm because of hardcoded test: beginning at " + calSetWorkingCopyString2); +// Intent alarmIntent2 = new Intent(automationServiceRef, AlarmListener.class); +// PendingIntent alarmPendingIntent2 = PendingIntent.getBroadcast(automationServiceRef, 0, alarmIntent2, 0); +// centralAlarmManagerInstance.setInexactRepeating(AlarmManager.RTC_WAKEUP, cal.getTimeInMillis(), 5000, alarmPendingIntent2); +// requestCodeList.add(0); + + scheduleNextAlarm(); + } + + private static void scheduleNextAlarm() + { + Long currentTime = System.currentTimeMillis(); + Long scheduleCandidate = null; + + if(alarmCandidates.size() == 0) + { + Miscellaneous.logEvent("i", "AlarmManager", "No alarms to be scheduled.", 3); + return; + } + else if(alarmCandidates.size() == 1) + { + // only one alarm, schedule that + scheduleCandidate = alarmCandidates.get(0); + } + else if(alarmCandidates.size() > 1) + { + scheduleCandidate = alarmCandidates.get(0); + + for(long alarmCandidate : alarmCandidates) + { + if(Math.abs(currentTime - alarmCandidate) < Math.abs(currentTime - scheduleCandidate)) + scheduleCandidate = alarmCandidate; + } + } + + Intent alarmIntent = new Intent(automationServiceRef, AlarmListener.class); + PendingIntent alarmPendingIntent = PendingIntent.getBroadcast(automationServiceRef, 0, alarmIntent, PendingIntent.FLAG_UPDATE_CURRENT); + centralAlarmManagerInstance.set(AlarmManager.RTC_WAKEUP, scheduleCandidate, alarmPendingIntent); + + + SimpleDateFormat sdf = new SimpleDateFormat("E dd.MM.yyyy HH:mm"); + Calendar calendar = Calendar.getInstance(); + calendar.setTimeInMillis(scheduleCandidate); + Miscellaneous.logEvent("i", "AlarmManager", "Chose " + sdf.format(calendar.getTime()) + " as next scheduled alarm.", 4); + + } + + public static void clearAlarms() + { + Miscellaneous.logEvent("i", "AlarmManager", "Clearing possibly standing alarms.", 4); + for(int requestCode : requestCodeList) + { + Intent alarmIntent = new Intent(automationServiceRef, AlarmListener.class); + PendingIntent alarmPendingIntent = PendingIntent.getBroadcast(automationServiceRef, requestCode, alarmIntent, 0); +// Miscellaneous.logEvent("i", "AlarmManager", "Clearing alarm with request code: " + String.valueOf(requestCode)); + centralAlarmManagerInstance.cancel(alarmPendingIntent); + } + requestCodeList.clear(); + } + + private static void startAlarmListenerInternal(AutomationService givenAutomationServiceRef) + { + if(!alarmListenerActive) + { + Miscellaneous.logEvent("i", "AlarmListener", "Starting alarm listener.", 4); + AlarmListener.automationServiceRef = givenAutomationServiceRef; + centralAlarmManagerInstance = (AlarmManager)automationServiceRef.getSystemService(automationServiceRef.ALARM_SERVICE); +// alarmIntent = new Intent(automationServiceRef, AlarmListener.class); +// alarmPendingIntent = PendingIntent.getBroadcast(automationServiceRef, 0, alarmIntent, 0); + alarmListenerActive = true; + Miscellaneous.logEvent("i", "AlarmListener", "Alarm listener started.", 4); + AlarmListener.setAlarms(); + +// // get a Calendar object with current time +// Calendar cal = Calendar.getInstance(); +// // add 5 minutes to the calendar object +// cal.add(Calendar.SECOND, 10); +// centralAlarmManagerInstance.setInexactRepeating(AlarmManager.RTC_WAKEUP, cal.getTimeInMillis(), 5000, alarmPendingIntent); + } + else + Miscellaneous.logEvent("i", "AlarmListener", "Request to start AlarmListener. But it's already active.", 5); + } + + private static void stopAlarmListenerInternal() + { + if(alarmListenerActive) + { + Miscellaneous.logEvent("i", "AlarmListener", "Stopping alarm listener.", 4); + clearAlarms(); +// centralAlarmManagerInstance.cancel(alarmPendingIntent); + alarmListenerActive = false; + } + else + Miscellaneous.logEvent("i", "AlarmListener", "Request to stop AlarmListener. But it's not running.", 5); + } + public static void reloadAlarms() + { + AlarmListener.setAlarms(); + } + @Override + public void startListener(AutomationService automationService) + { + AlarmListener.startAlarmListener(automationService); + } + @Override + public void stopListener(AutomationService automationService) + { + AlarmListener.stopAlarmListener(automationService); + } + + public static boolean haveAllPermission() + { + return true; + } + + @Override + public boolean isListenerRunning() + { + return isAlarmListenerActive(); + } + @Override + public Trigger_Enum[] getMonitoredTrigger() + { + return new Trigger_Enum[] { Trigger_Enum.timeFrame }; + } + +} diff --git a/app/src/main/java/com/jens/automation2/receivers/AutomationListenerInterface.java b/app/src/main/java/com/jens/automation2/receivers/AutomationListenerInterface.java new file mode 100644 index 0000000..a40cc6b --- /dev/null +++ b/app/src/main/java/com/jens/automation2/receivers/AutomationListenerInterface.java @@ -0,0 +1,12 @@ +package com.jens.automation2.receivers; + +import com.jens.automation2.AutomationService; +import com.jens.automation2.Trigger.Trigger_Enum; + +public interface AutomationListenerInterface +{ + public void startListener(AutomationService automationService); + public void stopListener(AutomationService automationService); + public boolean isListenerRunning(); + public Trigger_Enum[] getMonitoredTrigger(); +} diff --git a/app/src/main/java/com/jens/automation2/receivers/BatteryReceiver.java b/app/src/main/java/com/jens/automation2/receivers/BatteryReceiver.java new file mode 100644 index 0000000..0d75214 --- /dev/null +++ b/app/src/main/java/com/jens/automation2/receivers/BatteryReceiver.java @@ -0,0 +1,314 @@ +package com.jens.automation2.receivers; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.BatteryManager; +import android.util.Log; +import android.widget.Toast; + +import com.jens.automation2.ActivityPermissions; +import com.jens.automation2.AutomationService; +import com.jens.automation2.Miscellaneous; +import com.jens.automation2.Rule; +import com.jens.automation2.Trigger.Trigger_Enum; + +import java.util.ArrayList; + +public class BatteryReceiver extends BroadcastReceiver implements AutomationListenerInterface +{ + private static int batteryLevel=-1; // initialize with a better value than this + public static AutomationService automationServiceRef = null; + private static boolean usbHostConnected = false; + + private static boolean batteryReceiverActive = false; + private static IntentFilter batteryIntentFilter = null; + private static Intent batteryStatus = null; + private static BroadcastReceiver batteryInfoReceiverInstance = null; + public static void startBatteryReceiver(final AutomationService automationServiceRef) + { + if(!batteryReceiverActive) + { + BatteryReceiver.automationServiceRef = automationServiceRef; + + if(batteryInfoReceiverInstance == null) + batteryInfoReceiverInstance = new BatteryReceiver(); + + if(batteryIntentFilter == null) + { + batteryIntentFilter = new IntentFilter(); + batteryIntentFilter.addAction(Intent.ACTION_BATTERY_CHANGED); + batteryIntentFilter.addAction(Intent.ACTION_BATTERY_LOW); + // batteryIntentFilter.addAction(Intent.ACTION_POWER_CONNECTED); + // batteryIntentFilter.addAction(Intent.ACTION_POWER_DISCONNECTED); + } + + batteryStatus = automationServiceRef.registerReceiver(batteryInfoReceiverInstance, batteryIntentFilter); + + batteryReceiverActive = true; + } + } + public static void stopBatteryReceiver() + { + if(batteryReceiverActive) + { + if(batteryInfoReceiverInstance != null) + { + automationServiceRef.unregisterReceiver(batteryInfoReceiverInstance); + batteryInfoReceiverInstance = null; + } + + batteryReceiverActive = false; + } + } + + public static boolean isBatteryReceiverActive() + { + return batteryReceiverActive; + } + + public static boolean isUsbHostConnected() + { + return usbHostConnected; + } + + public static int getBatteryLevel() + { + return batteryLevel; + } + + private static int deviceIsCharging = 0; //0=unknown, 1=no, 2=yes + + public static int getDeviceIsCharging() + { + return deviceIsCharging; + } + + @Override + public void onReceive(Context context, Intent intent) + { +// Log.i("Battery", "Some battery event"); + + if (intent == null) + return; + if (context == null) + return; + + if(intent.getAction().equals(Intent.ACTION_BATTERY_LOW)) + { + Log.i("Battery", "Low battery event"); + } + else + { + try + { + // Miscellaneous.logEvent("i", "BatteryReceiver", "Received battery event."); + // if(intent.getAction().equals(Intent.ACTION_BATTERY_CHANGED)) + // { + batteryLevel = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1); + // int scale = -1; + // int voltage = -1; + // int temp = -1; + // scale = intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1); + // temp = intent.getIntExtra(BatteryManager.EXTRA_TEMPERATURE, -1); + // voltage = intent.getIntExtra(BatteryManager.EXTRA_VOLTAGE, -1); + Log.i("Battery", "Level: " + String.valueOf(batteryLevel)); + this.actionBatteryLevel(context); + + int status = intent.getIntExtra(BatteryManager.EXTRA_STATUS, -1); + int statusPlugged = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1); + // Miscellaneous.logEvent("i", "BatteryReceiver", "Status: " + String.valueOf(statusPlugged)); + + switch(statusPlugged) + { + case BatteryManager.BATTERY_PLUGGED_AC: + // Toast.makeText(context, "Regular charging", Toast.LENGTH_LONG).show(); + // Miscellaneous.logEvent("i", "BatteryReceiver", "Regular charging."); + this.actionCharging(context); + break; + case BatteryManager.BATTERY_PLUGGED_USB: + this.actionUsbConnected(context); + break; + } + + switch(status) + { + // case BatteryManager.BATTERY_STATUS_CHARGING: + // break; + case BatteryManager.BATTERY_STATUS_FULL: + // Toast.makeText(context, "Regular charging full", Toast.LENGTH_LONG).show(); + // Miscellaneous.logEvent("i", "BatteryReceiver", "Device has been fully charged."); + this.actionCharging(context); + break; + case BatteryManager.BATTERY_STATUS_DISCHARGING: + this.actionDischarging(context); + break; + } + // } + // else if(intent.getAction().equals(Intent.ACTION_POWER_CONNECTED)) + // { + //// Miscellaneous.logEvent("i", "BatteryReceiver", "Battery is charging or full."); + // deviceIsCharging = 2; + // //activate rule(s) + // ArrayList ruleCandidates = Rule.findRuleCandidatesByCharging(true); + // for(int i=0; i ruleCandidates = Rule.findRuleCandidatesByCharging(false); + // for(int i=0; i ruleCandidates = Rule.findRuleCandidatesByCharging(true); + for(int i=0; i ruleCandidates = Rule.findRuleCandidatesByBatteryLevel(); + for(int i=0; i ruleCandidates = Rule.findRuleCandidatesByCharging(false); + for(int i=0; i ruleCandidates = Rule.findRuleCandidatesByUsbHost(true); + for(Rule oneRule : ruleCandidates) + { + if(oneRule.applies(context)) + oneRule.activate(automationServiceRef, false); + } + + this.actionCharging(context); + } + } + + private void actionUsbDisconnected(Context context) + { + // Event usbDisConnected + + if(usbHostConnected) + { + usbHostConnected = false; + Miscellaneous.logEvent("i", "BatteryReceiver", "Disconnected from computer.", 3); + Toast.makeText(context, "Disconnected from computer.", Toast.LENGTH_LONG).show(); + + ArrayList ruleCandidates = Rule.findRuleCandidatesByUsbHost(false); + for(Rule oneRule : ruleCandidates) + { + if(oneRule.applies(context)) + oneRule.activate(automationServiceRef, false); + } + } + } + @Override + public void startListener(AutomationService automationService) + { + BatteryReceiver.startBatteryReceiver(automationService); + } + @Override + public void stopListener(AutomationService automationService) + { + BatteryReceiver.stopBatteryReceiver(); + } + + public static boolean haveAllPermission() + { + return ActivityPermissions.havePermission("android.permission.READ_PHONE_STATE", Miscellaneous.getAnyContext()) && + ActivityPermissions.havePermission("android.permission.BATTERY_STATS", Miscellaneous.getAnyContext()); + } + + @Override + public boolean isListenerRunning() + { + return BatteryReceiver.isBatteryReceiverActive(); + } + @Override + public Trigger_Enum[] getMonitoredTrigger() + { + // actually monitores several + return new Trigger_Enum[] { Trigger_Enum.batteryLevel, Trigger_Enum.charging, Trigger_Enum.usb_host_connection }; + } +} diff --git a/app/src/main/java/com/jens/automation2/receivers/BluetoothReceiver.java b/app/src/main/java/com/jens/automation2/receivers/BluetoothReceiver.java new file mode 100644 index 0000000..0415c78 --- /dev/null +++ b/app/src/main/java/com/jens/automation2/receivers/BluetoothReceiver.java @@ -0,0 +1,303 @@ +package com.jens.automation2.receivers; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.util.Log; + +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.Trigger.Trigger_Enum; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.Set; + +public class BluetoothReceiver extends BroadcastReceiver implements AutomationListenerInterface +{ + protected static ArrayList connectedDevices = new ArrayList(); + protected static ArrayList devicesInRange = new ArrayList(); + + protected static BluetoothDevice lastAffectedDevice = null; + protected static String lastAction = null; + + protected static IntentFilter bluetoothReceiverIntentFilter = null; + protected static boolean bluetoothReceiverActive = false; + protected static BluetoothReceiver bluetoothReceiverInstance = null; + + public static boolean isBluetoothReceiverActive() + { + return bluetoothReceiverActive; + } + + public static void startBluetoothReceiver() + { + if(bluetoothReceiverInstance == null) + bluetoothReceiverInstance = new BluetoothReceiver(); + + if(bluetoothReceiverIntentFilter == null) + { + bluetoothReceiverIntentFilter = new IntentFilter(); + bluetoothReceiverIntentFilter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED); + bluetoothReceiverIntentFilter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECT_REQUESTED); + bluetoothReceiverIntentFilter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED); + } + + try + { + if(!bluetoothReceiverActive) + { + Miscellaneous.logEvent("i", "BluetoothReceiver", "Starting BluetoothReceiver", 4); + bluetoothReceiverActive = true; + AutomationService.getInstance().registerReceiver(bluetoothReceiverInstance, bluetoothReceiverIntentFilter); + } + } + catch(Exception ex) + { + Miscellaneous.logEvent("e", "BluetoothReceiver", "Error starting BluetoothReceiver: " + Log.getStackTraceString(ex), 3); + } + } + public static void stopBluetoothReceiver() + { + try + { + if(bluetoothReceiverActive) + { + Miscellaneous.logEvent("i", "BluetoothReceiver", "Stopping BluetoothReceiver", 4); + bluetoothReceiverActive = false; + AutomationService.getInstance().unregisterReceiver(bluetoothReceiverInstance); + } + } + catch(Exception ex) + { + Miscellaneous.logEvent("e", "BluetoothReceiver", "Error stopping BluetoothReceiver: " + Log.getStackTraceString(ex), 3); + } + } + + public static BluetoothDevice getLastAffectedDevice() + { + return lastAffectedDevice; + } + + public static String getLastAction() + { + return lastAction; + } + + @Override + public void onReceive(Context context, Intent intent) + { +// Miscellaneous.logEvent("i", "BluetoothReceiver", "Bluetooth event.", 4); + + String action = intent.getAction(); + BluetoothDevice bluetoothDevice = null; + + if(action.equals(BluetoothDevice.ACTION_ACL_CONNECTED) | action.equals("android.bluetooth.device.action.ACL_CONNECTED")) + { + bluetoothDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); + lastAffectedDevice = bluetoothDevice; + lastAction = action; + connectedDevices.add(bluetoothDevice); + Miscellaneous.logEvent("i", "BluetoothReceiver", String.format(context.getResources().getString(R.string.bluetoothConnectionTo), bluetoothDevice.getName()), 3); + } + else if(action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED) | action.equals(BluetoothDevice.ACTION_ACL_DISCONNECT_REQUESTED) | action.equals("android.bluetooth.device.ACTION_ACL_DISCONNECTED") | action.equals("android.bluetooth.device.ACTION_ACL_DISCONNECT_REQUESTED")) + { + bluetoothDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); + lastAffectedDevice = bluetoothDevice; + lastAction = action; + connectedDevices.remove(bluetoothDevice); + Miscellaneous.logEvent("i", "BluetoothReceiver", String.format(context.getResources().getString(R.string.bluetoothDisconnectFrom), bluetoothDevice.getName()), 3); + } + else if(action.equals(BluetoothDevice.ACTION_FOUND) | action.equals("android.bluetooth.device.ACTION_FOUND")) + { + bluetoothDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); + lastAffectedDevice = bluetoothDevice; + lastAction = action; + devicesInRange.add(bluetoothDevice); + Miscellaneous.logEvent("i", "BluetoothReceiver", String.format(context.getResources().getString(R.string.bluetoothDeviceInRange), bluetoothDevice.getName()), 3); + } + + ArrayList ruleCandidates = Rule.findRuleCandidatesByBluetoothConnection(); + for(int i=0; i deviceList = BluetoothAdapter.getDefaultAdapter().getBondedDevices(); + returnArray = deviceList.toArray(new BluetoothDevice[deviceList.size()]); + + Arrays.sort(returnArray, new Comparator() + { + @Override + public int compare(BluetoothDevice lhs, BluetoothDevice rhs) + { + return lhs.getName().compareTo(rhs.getName()); + } + + ; + }); + } + catch(NullPointerException e) + { + // There are no paired bluetooth devices. + + returnArray = new BluetoothDevice[] {}; + } + + return returnArray; + } + + public static String[] getAllPairedBluetoothDevicesStrings() + { + ArrayList names = new ArrayList(); + for(BluetoothDevice device : getAllPairedBluetoothDevices()) + names.add(device.getName() + " (" + device.getAddress() + ")"); + + return names.toArray(new String[names.size()]); + } + + public static BluetoothDevice getDeviceByName(String name) + { + for(BluetoothDevice device : getAllPairedBluetoothDevices()) + { + if(device.getName().equals(name)) + return device; + } + + return null; + } + + public static BluetoothDevice getDeviceByAddress(String address) + { + for(BluetoothDevice device : getAllPairedBluetoothDevices()) + { + if(device.getAddress().equals(address)) + return device; + } + + return null; + } + + public static int getDevicePositionByAddress(String address) + { + BluetoothDevice[] allDevices = getAllPairedBluetoothDevices(); + for(int i=0; i 0) + return true; + else + return false; + } + + public static boolean isAnyDeviceInRange() + { + if(devicesInRange.size() > 0) + return true; + else + return false; + } + + public static boolean isDeviceInRange(BluetoothDevice searchDevice) + { + for(BluetoothDevice device : devicesInRange) + if(device.getAddress().equals(searchDevice.getAddress())) + return true; + + return false; + } + + private void discovery() + { + BluetoothAdapter.getDefaultAdapter().startDiscovery(); + BroadcastReceiver discoveryReceiver = new BroadcastReceiver() + { + public void onReceive(Context context, Intent intent) + { + String action = intent.getAction(); + //ACTION_DISCOVERY_STARTED and ACTION_DISCOVERY_FINISHED + if(action.equals(BluetoothAdapter.ACTION_DISCOVERY_FINISHED)) + { + // This would be a good point to look for devices that are not in range anymore. + } + } + }; + } + + @Override + public void startListener(AutomationService automationService) + { + BluetoothReceiver.startBluetoothReceiver(); + } + + @Override + public void stopListener(AutomationService automationService) + { + BluetoothReceiver.stopBluetoothReceiver(); + } + + public static boolean haveAllPermission() + { + return ActivityPermissions.havePermission("android.permission.BLUETOOTH_ADMIN", Miscellaneous.getAnyContext()) && + ActivityPermissions.havePermission("android.permission.BLUETOOTH", Miscellaneous.getAnyContext()) && + ActivityPermissions.havePermission("android.permission.ACCESS_NETWORK_STATE", Miscellaneous.getAnyContext()); + } + + @Override + public boolean isListenerRunning() + { + return BluetoothReceiver.isBluetoothReceiverActive(); + } + + @Override + public Trigger_Enum[] getMonitoredTrigger() + { + return new Trigger_Enum[] { Trigger_Enum.bluetoothConnection }; + } +} diff --git a/app/src/main/java/com/jens/automation2/receivers/ConnectivityReceiver.java b/app/src/main/java/com/jens/automation2/receivers/ConnectivityReceiver.java new file mode 100644 index 0000000..43e0b70 --- /dev/null +++ b/app/src/main/java/com/jens/automation2/receivers/ConnectivityReceiver.java @@ -0,0 +1,263 @@ +package com.jens.automation2.receivers; + +import android.annotation.SuppressLint; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; +import android.net.wifi.WifiInfo; +import android.net.wifi.WifiManager; +import android.os.Build; +import android.telephony.TelephonyManager; +import android.util.Log; + +import com.jens.automation2.ActivityPermissions; +import com.jens.automation2.AutomationService; +import com.jens.automation2.Miscellaneous; +import com.jens.automation2.Rule; +import com.jens.automation2.Trigger.Trigger_Enum; +import com.jens.automation2.location.WifiBroadcastReceiver; + +import java.util.ArrayList; + +public class ConnectivityReceiver extends BroadcastReceiver implements AutomationListenerInterface +{ + protected static boolean connectivityReceiverActive = false; + private static ConnectivityReceiver connectivityReceiverInstance = null; + private static IntentFilter connectivityIntentFilter = null; + private static AutomationService automationServiceRef = null; + protected static boolean dataConnectionLastState = false; + protected static boolean roamingLastState = false; + + public static boolean isConnectivityReceiverActive() + { + return connectivityReceiverActive; + } + + public static void startConnectivityReceiver(AutomationService ref) + { + automationServiceRef = ref; + + if(connectivityReceiverInstance == null) + connectivityReceiverInstance = new ConnectivityReceiver(); + + + if(connectivityIntentFilter == null) + { + connectivityIntentFilter = new IntentFilter(); + connectivityIntentFilter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED); + connectivityIntentFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION); + } + + try + { + if(!connectivityReceiverActive) + { + Miscellaneous.logEvent("i", "Wifi Listener", "Starting connectivityReceiver", 4); + connectivityReceiverActive = true; + automationServiceRef.registerReceiver(connectivityReceiverInstance, connectivityIntentFilter); + } + } + catch(Exception ex) + { + Miscellaneous.logEvent("e", "Wifi Listener", "Error starting connectivityReceiver: " + Log.getStackTraceString(ex), 3); + } + } + + + public static void stopConnectivityReceiver() + { + try + { + if(connectivityReceiverActive) + { + Miscellaneous.logEvent("i", "Wifi Listener", "Stopping connectivityReceiver", 4); + connectivityReceiverActive = false; + automationServiceRef.unregisterReceiver(connectivityReceiverInstance); + } + } + catch(Exception ex) + { + Miscellaneous.logEvent("e", "Wifi Listener", "Error stopping connectivityReceiver: " + Log.getStackTraceString(ex), 3); + } + } + + // Get roaming state from telephony manager + public static Boolean isRoaming(Context context) + { + TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); + return telephonyManager.isNetworkRoaming(); + } + + public static void setDataConnectionLastState(boolean newState) + { + if(dataConnectionLastState != newState) + { + dataConnectionLastState = newState; + + // Run rules if I decide to create such a trigger +// automationServiceRef.getLocationProvider().handleDataConnectionChange(newState); + } + } + public static Boolean isDataConnectionAvailable(Context context) + { + ConnectivityManager connManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + NetworkInfo ni = connManager.getActiveNetworkInfo(); + return ni != null && ni.isConnected(); + } + + // Get airplane mode state from system settings + @SuppressLint("NewApi") + public static boolean isAirplaneMode(Context context) + { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) + { + int value = android.provider.Settings.System.getInt(context.getContentResolver(), android.provider.Settings.System.AIRPLANE_MODE_ON, 0); + return value != 0; + } + else + { + return android.provider.Settings.Global.getInt(context.getContentResolver(), android.provider.Settings.Global.AIRPLANE_MODE_ON, 0) != 0; + } + } + + @Override + public void onReceive(Context context, Intent intent) + { + try + { + if (context == null) + return; + + if(intent.getAction().equals(Intent.ACTION_AIRPLANE_MODE_CHANGED)) + { + // Airplane mode status has changed. + Miscellaneous.logEvent("i", "Connectivity", "Airplane mode changed.", 2); + boolean isAirplaneMode = isAirplaneMode(context); + automationServiceRef.getLocationProvider().handleAirplaneMode(isAirplaneMode); + + ArrayList ruleCandidates = Rule.findRuleCandidatesByAirplaneMode(isAirplaneMode); + for(int i=0; i ruleCandidates = Rule.findRuleCandidatesByRoaming(isRoaming); + for(int i=0; i ruleCandidates = Rule.findRuleCandidatesByRoaming(isRoaming); +// for(int i=0; ionReceive(): " + Log.getStackTraceString(e), 3); + } + } + + @Override + public void startListener(AutomationService automationService) + { + ConnectivityReceiver.startConnectivityReceiver(automationService); + } + + @Override + public void stopListener(AutomationService automationService) + { + ConnectivityReceiver.stopConnectivityReceiver(); + } + + public static boolean haveAllPermission() + { + return ActivityPermissions.havePermission("android.permission.ACCESS_NETWORK_STATE", Miscellaneous.getAnyContext()) && + ActivityPermissions.havePermission("android.permission.ACCESS_WIFI_STATE", Miscellaneous.getAnyContext()) && + ActivityPermissions.havePermission("android.permission.ACCESS_NETWORK_STATE", Miscellaneous.getAnyContext()); + } + + @Override + public boolean isListenerRunning() + { + return ConnectivityReceiver.isConnectivityReceiverActive(); + } + + @Override + public Trigger_Enum[] getMonitoredTrigger() + { + return new Trigger_Enum[] { Trigger_Enum.airplaneMode, Trigger_Enum.roaming, Trigger_Enum.wifiConnection }; + } +} diff --git a/app/src/main/java/com/jens/automation2/receivers/HeadphoneJackListener.java b/app/src/main/java/com/jens/automation2/receivers/HeadphoneJackListener.java new file mode 100644 index 0000000..fc37718 --- /dev/null +++ b/app/src/main/java/com/jens/automation2/receivers/HeadphoneJackListener.java @@ -0,0 +1,152 @@ +package com.jens.automation2.receivers; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.util.Log; + +import com.jens.automation2.ActivityPermissions; +import com.jens.automation2.AutomationService; +import com.jens.automation2.Miscellaneous; +import com.jens.automation2.Rule; +import com.jens.automation2.Trigger.Trigger_Enum; + +import java.util.ArrayList; + +public class HeadphoneJackListener extends BroadcastReceiver implements AutomationListenerInterface +{ + private static boolean headsetConnected = false; + private static int headphoneType = -1; + + protected static boolean headphoneJackListenerActive=false; + protected static IntentFilter headphoneJackListenerIntentFilter = null; + protected static HeadphoneJackListener instance; + + public static HeadphoneJackListener getInstance() + { + if(instance == null) + instance = new HeadphoneJackListener(); + + return instance; + } + + public static boolean isHeadphoneJackListenerActive() + { + return headphoneJackListenerActive; + } + + + public static boolean isHeadsetConnected() + { + return headsetConnected; + } + + public static int getHeadphoneType() + { + return headphoneType; + } + + @Override + public void onReceive(Context context, Intent intent) + { + try + { + /*Broadcast Action: Wired Headset plugged in or unplugged. + The intent will have the following extra values: + + state - 0 for unplugged, 1 for plugged. + name - Headset type, human readable string + microphone - 1 if headset has a microphone, 0 otherwise*/ + + int state = intent.getExtras().getInt("state"); + String name = intent.getExtras().getString("name"); + headphoneType = intent.getExtras().getInt("microphone"); + + if(state == 0) + { + headsetConnected = false; + Miscellaneous.logEvent("i", "HeadphoneJackListener", "Headset " + name + " unplugged.", 4); + } + else + { + headsetConnected = true; + Miscellaneous.logEvent("i", "HeadphoneJackListener", "Headset " + name + " plugged in.", 4); + } + + ArrayList ruleCandidates = Rule.findRuleCandidatesByHeadphoneJack(isHeadsetConnected()); + for(int i=0; i + { + @Override + protected String doInBackground(Tag... params) + { + Tag tag = params[0]; + + Ndef ndef = Ndef.get(tag); + if (ndef == null) + { + // NDEF is not supported by this Tag. + return null; + } + NdefMessage ndefMessage = ndef.getCachedNdefMessage(); + NdefRecord[] records = ndefMessage.getRecords(); + for (NdefRecord ndefRecord : records) + { + if (ndefRecord.getTnf() == NdefRecord.TNF_WELL_KNOWN && Arrays.equals(ndefRecord.getType(), NdefRecord.RTD_TEXT)) + { + try + { + return readText(ndefRecord); + } + catch (UnsupportedEncodingException e) + { + Miscellaneous.logEvent("e", "NFC", Miscellaneous.getAnyContext().getString(R.string.nfcUnsupportedEncoding) + " " + Log.getStackTraceString(e), 3); + } + } + } + return null; + +// return readTag(tag); + } + + @Override + protected void onPostExecute(String result) + { + if (result != null && result.length() > 0) + { + // Text of tag is now stored in variable "result" + lastReadLabel = result; + Miscellaneous.logEvent("i", "NFC", Miscellaneous.getAnyContext().getResources().getString(R.string.nfcTagFoundWithText) + " " + result, 3); + Toast.makeText(Miscellaneous.getAnyContext(), Miscellaneous.getAnyContext().getResources().getString(R.string.nfcTagFoundWithText) + " " + result, Toast.LENGTH_LONG).show(); + + AutomationService asInstance = AutomationService.getInstance(); + if(asInstance == null) + { + Context context = Miscellaneous.getAnyContext(); + if(context != null) + { + Miscellaneous.logEvent("w", "NFC", context.getResources().getString(R.string.serviceNotRunning) + " " + context.getResources().getString(R.string.cantRunRule), 4); + Toast.makeText(context, context.getResources().getString(R.string.serviceNotRunning) + " " + context.getResources().getString(R.string.cantRunRule), Toast.LENGTH_LONG).show(); + } + } + else + { + ArrayList allRulesWithNfcTags = Rule.findRuleCandidatesByNfc(); + for(int i=0; i + { + @Override + protected Boolean doInBackground(Object... params) + { + String textToWrite = (String)params[0]; + Tag tagToWrite = (Tag)params[1]; + return writeTag(textToWrite, tagToWrite); + } + +// @Override +// protected void onPostExecute(Boolean result) +// { +// return result; +// } + } + + public static String readTag(Tag tag) + { +// if(tag == null) +// { +// Toast.makeText(Miscellaneous.getAnyContext(), Miscellaneous.getAnyContext().getResources().getString(R.string.nfcNoTag), Toast.LENGTH_LONG).show(); +// return null; +// } + + Ndef ndef = Ndef.get(tag); + if (ndef == null) + { + // NDEF is not supported by this Tag. + return null; + } + + NdefMessage ndefMessage = ndef.getCachedNdefMessage(); + + NdefRecord[] records = ndefMessage.getRecords(); + for (NdefRecord ndefRecord : records) + { + if (ndefRecord.getTnf() == NdefRecord.TNF_WELL_KNOWN && Arrays.equals(ndefRecord.getType(), NdefRecord.RTD_TEXT)) + { + try + { + return readText(ndefRecord); + } + catch (UnsupportedEncodingException e) + { + Miscellaneous.logEvent("w", "NFC", "Unsupported Encoding: " + Log.getStackTraceString(e), 3); + } + } + } + + return null; + } + + private static String readText(NdefRecord record) throws UnsupportedEncodingException + { + /* + * See NFC forum specification for "Text Record Type Definition" at 3.2.1 + * + * http://www.nfc-forum.org/specs/ + * + * bit_7 defines encoding + * bit_6 reserved for future use, must be 0 + * bit_5..0 length of IANA language code + */ + + byte[] payload = record.getPayload(); + + // Get the Text Encoding + String textEncoding = ((payload[0] & 128) == 0) ? "UTF-8" : "UTF-16"; + + // Get the Language Code + int languageCodeLength = payload[0] & 0063; + + // String languageCode = new String(payload, 1, languageCodeLength, "US-ASCII"); + // e.g. "en" + + // Get the Text + return new String(payload, languageCodeLength + 1, payload.length - languageCodeLength - 1, textEncoding); + } + + public static boolean writeTag(String textToWrite, Tag tag) + { + Miscellaneous.logEvent("i", "NFC", "Attempting to write tag...", 2); + + String packageName = Miscellaneous.getAnyContext().getPackageName(); + NdefRecord appRecord = NdefRecord.createApplicationRecord(packageName); + // Record with actual data we care about + byte[] textBytes = textToWrite.getBytes(); + byte[] textPayload = new byte[textBytes.length + 3]; + textPayload[0] = 0x02; // 0x02 = UTF8 + textPayload[1] = 'e'; // Language = en + textPayload[2] = 'n'; + System.arraycopy(textBytes, 0, textPayload, 3, textBytes.length); + NdefRecord textRecord = new NdefRecord(NdefRecord.TNF_WELL_KNOWN, NdefRecord.RTD_TEXT, new byte[0], textPayload); + + // Complete NDEF message with both records + NdefMessage completeMessageToWrite = new NdefMessage(new NdefRecord[] {textRecord, appRecord}); + + int size = completeMessageToWrite.toByteArray().length; + try + { + Ndef ndef = Ndef.get(tag); + if (ndef != null) + { + ndef.connect(); + if (ndef.isWritable() && ndef.getMaxSize() > size) + { + ndef.writeNdefMessage(completeMessageToWrite); + Miscellaneous.logEvent("i", "NFC", "Done writing tag.", 2); + return true; + } + } + else + { + NdefFormatable format = NdefFormatable.get(tag); + if (format != null) + { + try + { + format.connect(); + format.format(completeMessageToWrite); + Miscellaneous.logEvent("i", "NFC", "Done writing tag.", 2); + return true; + } + catch(IOException e) + { + Miscellaneous.logEvent("e", "NFC", "Error writing tag: " + Log.getStackTraceString(e), 2); + } + } + } + } + catch(Exception e) + { + Miscellaneous.logEvent("e", "NFC", "Error writing tag: " + Log.getStackTraceString(e), 2); + } + + return false; + } + + public static boolean checkNfcRequirements(Context context, boolean showErrorMessage) + { + if(!context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_NFC)) + { + if(showErrorMessage) + Toast.makeText(context, context.getResources().getString(R.string.deviceDoesNotHaveNfc), Toast.LENGTH_LONG).show(); + + return false; + } + else if(Build.VERSION.SDK_INT <= 10) + { + // NFC not supported until after Gingerbread. + if(showErrorMessage) + Toast.makeText(context, context.getResources().getString(R.string.nfcNotSupportedInThisAndroidVersionYet), Toast.LENGTH_LONG).show(); + + return false; + } + + return true; + } +} diff --git a/app/src/main/java/com/jens/automation2/receivers/NoiseListener.java b/app/src/main/java/com/jens/automation2/receivers/NoiseListener.java new file mode 100644 index 0000000..7ae0442 --- /dev/null +++ b/app/src/main/java/com/jens/automation2/receivers/NoiseListener.java @@ -0,0 +1,215 @@ +package com.jens.automation2.receivers; + +import android.media.MediaRecorder; +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; + +import com.jens.automation2.ActivityPermissions; +import com.jens.automation2.AutomationService; +import com.jens.automation2.Miscellaneous; +import com.jens.automation2.Rule; +import com.jens.automation2.Settings; +import com.jens.automation2.Trigger.Trigger_Enum; + +import java.util.ArrayList; + +public class NoiseListener implements AutomationListenerInterface +{ + private static AutomationService automationService; + private static boolean isMeasuringActive = false; + private static boolean isTimerActive = false; + private static long noiseLevelDb; + private static Handler workHandler = new Handler() + { + @Override + public void handleMessage(Message msg) + { + Miscellaneous.logEvent("i", "Noise level", "Message received stating measurement is complete.", 5); + // This will take care of results delivered by the actual measuring instance + noiseLevelDb = msg.getData().getLong("noiseLevelDb"); + + // execute matching rules containing noise + ArrayList ruleCandidates = Rule.findRuleCandidatesByNoiseLevel(); + for(Rule oneRule : ruleCandidates) + { + if(oneRule.applies(automationService)) + oneRule.activate(automationService, false); + } + } + }; + private static NoiseListenerMeasuring listener; + + private static boolean stopRequested = false; + private static Handler schedulingHandler = new Handler() + { + @Override + public void handleMessage(Message msg) + { + if(msg.arg1 == 1) + { + if(!stopRequested) + { + if(listener == null) + listener = new NoiseListenerMeasuring(); + listener.doMeasuring(); + Miscellaneous.logEvent("i", "Noise level", "Rearming noise level message.", 5); + Message message = new Message(); + message.arg1 = 1; + schedulingHandler.sendMessageDelayed(message, Settings.timeBetweenNoiseLevelMeasurements * 1000); + } + else + Miscellaneous.logEvent("i", "Noise level", "Not rearming noise level message, stop requested.", 5); + } + } + + }; + + private static class NoiseListenerMeasuring + { + Thread measuringThread; + + public void doMeasuring() + { + measuringThread = new Thread() + { + @Override + public void run() + { + if(!isMeasuringActive) + { + isMeasuringActive = true; + + Miscellaneous.logEvent("i", "Noise level", "Periodic noise level measurement started.", 5); + + // Start recording but don't store data + MediaRecorder mediaRecorder = new MediaRecorder(); + mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); + mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP); + mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB); + mediaRecorder.setOutputFile("/dev/null"); + // Date myDate = new Date(); + // mediaRecorder.setOutputFile("/sdcard/temp/" + String.valueOf(myDate.getTime()) + ".3gpp"); + try + { + mediaRecorder.prepare(); + mediaRecorder.getMaxAmplitude(); + mediaRecorder.start(); + mediaRecorder.getMaxAmplitude(); + + long noiseLevel; + + try + { + sleep(Settings.lengthOfNoiseLevelMeasurements * 1000); + // Obtain maximum amplitude since last call of getMaxAmplitude() + noiseLevel = mediaRecorder.getMaxAmplitude(); + } + catch(Exception e) + { + noiseLevel = -1; + Miscellaneous.logEvent("e", "Noise level", "Error getting sound level: " + e.getMessage(), 2); + } + + double db = 20 * Math.log(noiseLevel / Settings.referenceValueForNoiseLevelMeasurements); + noiseLevelDb = Math.round(db); + + Message answer = new Message(); + Bundle answerBundle = new Bundle(); + answerBundle.putLong("noiseLevelDb", noiseLevelDb); + answer.setData(answerBundle); + workHandler.sendMessage(answer); + + Miscellaneous.logEvent("i", "Noise level", "Measured noise level: " + String.valueOf(noiseLevel) + " / converted to db: " + String.valueOf(db), 3); + + // Don't forget to release + mediaRecorder.reset(); + mediaRecorder.release(); + } + catch(Exception e) + {} + + isMeasuringActive = false; + + Miscellaneous.logEvent("i", "Noise level", "Periodic noise level measurement stopped.", 5); + } + } + }; + + measuringThread.start(); + } + + public void interrupt() + { + measuringThread.interrupt(); + } + } + + public static void startNoiseListener(AutomationService newAutomationService) + { + automationService = newAutomationService; + + if(!isTimerActive) + { + Miscellaneous.logEvent("i", "Noise level", "Starting periodic noise level measurement engine.", 2); + isTimerActive = true; + + Message message = new Message(); + message.arg1 = 1; + schedulingHandler.sendMessageDelayed(message, Settings.timeBetweenNoiseLevelMeasurements * 1000); + } + else + Miscellaneous.logEvent("i", "Noise level", "Periodic noise level measurement is already running. Won't start it again.", 2); + } + public static void stopNoiseListener() + { + if(isTimerActive) + { + stopRequested = true; + Miscellaneous.logEvent("i", "Noise level", "Stopping periodic noise level measurement engine.", 2); + + if(schedulingHandler.hasMessages(1)) + schedulingHandler.removeMessages(1); + + if(listener != null) + listener.interrupt(); + + isTimerActive = false; + } + else + Miscellaneous.logEvent("i", "Noise level", "Periodic noise level measurement is not active. Can't stop it.", 2); + } + + public static long getNoiseLevelDb() + { + return noiseLevelDb; + } + + @Override + public void startListener(AutomationService automationService) + { + NoiseListener.startNoiseListener(automationService); + } + @Override + public void stopListener(AutomationService automationService) + { + NoiseListener.stopNoiseListener(); + } + + public static boolean haveAllPermission() + { + return ActivityPermissions.havePermission("android.permission.RECORD_AUDIO", Miscellaneous.getAnyContext()); + } + + @Override + public boolean isListenerRunning() + { + return NoiseListener.isMeasuringActive | NoiseListener.isTimerActive; + } + @Override + public Trigger_Enum[] getMonitoredTrigger() + { + return new Trigger_Enum[] { Trigger_Enum.noiseLevel }; + } + +} diff --git a/app/src/main/java/com/jens/automation2/receivers/PackageReplacedReceiver.java b/app/src/main/java/com/jens/automation2/receivers/PackageReplacedReceiver.java new file mode 100644 index 0000000..de80fd0 --- /dev/null +++ b/app/src/main/java/com/jens/automation2/receivers/PackageReplacedReceiver.java @@ -0,0 +1,56 @@ +package com.jens.automation2.receivers; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; + +import com.jens.automation2.AutomationService; +import com.jens.automation2.Miscellaneous; +import com.jens.automation2.R; +import com.jens.automation2.Settings; + +public class PackageReplacedReceiver extends BroadcastReceiver +{ + @Override + public void onReceive(Context context, Intent intent) + { +// Toast.makeText(context, "package replaced", Toast.LENGTH_LONG).show(); +// int intentUid = intent.getExtras().getInt("android.intent.extra.UID"); // userid of the application that has just been updated +// int myUid = android.os.Process.myUid(); // userid of this application +// +// boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false); + +// if(intentUid == myUid) +// { + Settings.readFromPersistentStorage(context); + + Miscellaneous.logEvent("i", context.getResources().getString(R.string.applicationHasBeenUpdated), context.getResources().getString(R.string.applicationHasBeenUpdated), 2); + if(hasServiceBeenRunning() && Settings.startServiceAfterAppUpdate) + { + Miscellaneous.logEvent("i", "Service", context.getResources().getString(R.string.logStartingServiceAfterAppUpdate), 1); + AutomationService.startAutomationService(context, true); + } + else + { + Miscellaneous.logEvent("i", "Service", context.getResources().getString(R.string.logNotStartingServiceAfterAppUpdate), 2); + } +// } +// else +// Miscellaneous.logEvent("i", "Service", "Some other app has been updated.", 5); + } + + private static boolean hasServiceBeenRunning() + { + return Settings.hasServiceBeenRunning; + } + + public static void setHasServiceBeenRunning(boolean state, Context context) + { + Miscellaneous.logEvent("i", "State", "Writing stateFile to " + String.valueOf(state), 4); + Settings.readFromPersistentStorage(context); +// Settings.initializeSettings(context, false); + Settings.hasServiceBeenRunning = state; + Settings.writeSettings(context); + } + +} diff --git a/app/src/main/java/com/jens/automation2/receivers/PhoneStatusListener.java b/app/src/main/java/com/jens/automation2/receivers/PhoneStatusListener.java new file mode 100644 index 0000000..16f7671 --- /dev/null +++ b/app/src/main/java/com/jens/automation2/receivers/PhoneStatusListener.java @@ -0,0 +1,331 @@ +package com.jens.automation2.receivers; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.telephony.PhoneStateListener; +import android.telephony.TelephonyManager; +import android.util.Log; + +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.Trigger.Trigger_Enum; + +import java.util.ArrayList; + +public class PhoneStatusListener implements AutomationListenerInterface +{ + protected static int currentStateIncoming = -1; + protected static int currentStateOutgoing = -1; + protected static String lastPhoneNumber=""; + protected static int lastPhoneDirection = -1; //0=incoming, 1=outgoing + + protected static boolean incomingCallsReceiverActive = false; + protected static boolean outgoingCallsReceiverActive = false; + + protected static IntentFilter outgoingCallsIntentFilter; + protected static IncomingCallsReceiver incomingCallsReceiverInstance; + protected static BroadcastReceiver outgoingCallsReceiverInstance; + + + public static boolean isIncomingCallsReceiverActive() + { + return incomingCallsReceiverActive; + } + + public static boolean isOutgoingCallsReceiverActive() + { + return outgoingCallsReceiverActive; + } + + protected static boolean receivedInitialIncomingSignal = false; + + public static int getLastPhoneDirection() + { + return lastPhoneDirection; + } + + protected static void setLastPhoneNumber(String lastPhoneNumber) + { + PhoneStatusListener.lastPhoneNumber = lastPhoneNumber; + } + + public static String getLastPhoneNumber() + { + return lastPhoneNumber; + } + + public static class IncomingCallsReceiver extends PhoneStateListener + { + @Override + public void onCallStateChanged(int state, String incomingNumber) + { +// Miscellaneous.logEvent("i", "Call state", "New call state: " + String.valueOf(state), 4); + + if(incomingNumber != null && incomingNumber.length() > 0) // check for null in case call comes in with suppressed number. + setLastPhoneNumber(incomingNumber); + + switch(state) + { + case TelephonyManager.CALL_STATE_IDLE: + Miscellaneous.logEvent("i", "Call state", "New call state: CALL_STATE_IDLE", 4); + if(currentStateIncoming == TelephonyManager.CALL_STATE_OFFHOOK) + setCurrentStateIncoming(state); + else if(currentStateOutgoing == TelephonyManager.CALL_STATE_OFFHOOK) + setCurrentStateOutgoing(state); + else + currentStateIncoming = state; + currentStateOutgoing = state; + break; + case TelephonyManager.CALL_STATE_OFFHOOK: + Miscellaneous.logEvent("i", "Call state", "New call state: CALL_STATE_OFFHOOK", 4); + if(currentStateIncoming == TelephonyManager.CALL_STATE_RINGING) + setCurrentStateIncoming(state); + else if(currentStateOutgoing == TelephonyManager.CALL_STATE_RINGING) + setCurrentStateOutgoing(state); + break; + case TelephonyManager.CALL_STATE_RINGING: + String number = "unknown"; + if(incomingNumber != null && incomingNumber.length() > 0) + number = incomingNumber; + Miscellaneous.logEvent("i", "Call state", String.format(Miscellaneous.getAnyContext().getResources().getString(R.string.incomingCallFrom), number), 4); + + setCurrentStateIncoming(state); + break; + } + } + } + + public static class OutgoingCallsReceiver extends BroadcastReceiver + { + @Override + public void onReceive(Context context, Intent intent) + { + setCurrentStateOutgoing(2); + setLastPhoneNumber(intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER)); + Miscellaneous.logEvent("i", "Call state", String.format(Miscellaneous.getAnyContext().getResources().getString(R.string.outgoingCallFrom), getLastPhoneNumber()), 4); + } + } + + public static boolean isInACall() + { + if(isInIncomingCall() | isInOutgoingCall()) + return true; + + return false; + } + + public static boolean isInIncomingCall() + { +// Miscellaneous.logEvent("i", "Incoming call state", String.valueOf(currentStateIncoming), 5); + switch(currentStateIncoming) + { +// case -1: +// return false; +// case 0: +// return false; +// case 1: +// return true; + case 2: + return true; +// case 3: +// return true; +// case 4: +// return true; +// default: +// return false; + } + + return false; + } + + public static boolean isInOutgoingCall() + { +// Miscellaneous.logEvent("i", "Outgoing call state", String.valueOf(currentStateOutgoing), 5); + switch(currentStateOutgoing) + { +// case -1: +// return false; +// case 0: +// return false; +// case 1: +// return true; + case 2: + return true; +// case 3: +// return true; +// case 4: +// return true; +// default: +// return false; + } + + return false; + } + + private static void setCurrentStateIncoming(int state) + { +// Miscellaneous.logEvent("i", "Call state", "New incoming call state: " + String.valueOf(state), 4); + if(currentStateIncoming != state) + { + if(lastPhoneDirection != 1) + lastPhoneDirection = 1; + + if( + (state == 0 && currentStateIncoming == 2) + | + (state == 2 && (currentStateIncoming == 0 | currentStateIncoming == 1)) + ) + { + currentStateIncoming = state; + + ArrayList ruleCandidates = Rule.findRuleCandidatesByPhoneCall(isInIncomingCall()); + for(int i=0; i ruleCandidates = Rule.findRuleCandidatesByPhoneCall(isInOutgoingCall()); + for(int i=0; iincomingCallsReceiver", 4); + TelephonyManager tm = (TelephonyManager)automationService.getSystemService(Context.TELEPHONY_SERVICE); + tm.listen(incomingCallsReceiverInstance, PhoneStateListener.LISTEN_CALL_STATE); + incomingCallsReceiverActive = true; + } + + if(!outgoingCallsReceiverActive) + { + Miscellaneous.logEvent("i", "PhoneStatusListener", "Starting PhoneStatusListener->outgoingCallsReceiver", 4); + automationService.registerReceiver(outgoingCallsReceiverInstance, outgoingCallsIntentFilter); + outgoingCallsReceiverActive = true; + } + } + catch(Exception ex) + { + Miscellaneous.logEvent("e", "PhoneStatusListener", "Error starting PhoneStatusListener: " + Log.getStackTraceString(ex), 3); + } + } + + public static void stopPhoneStatusListener(AutomationService automationService) + { + try + { + if(incomingCallsReceiverActive) + { + Miscellaneous.logEvent("i", "PhoneStatusListener", "Stopping phoneStatusListener", 4); + TelephonyManager tm = (TelephonyManager)automationService.getSystemService(Context.TELEPHONY_SERVICE); + tm.listen(incomingCallsReceiverInstance, PhoneStateListener.LISTEN_NONE); + incomingCallsReceiverActive = false; + } + + if(outgoingCallsReceiverActive) + { + Miscellaneous.logEvent("i", "PhoneStatusListener", "Stopping phoneStatusListener", 4); + automationService.unregisterReceiver(outgoingCallsReceiverInstance); + outgoingCallsReceiverActive = false; + } + } + catch(Exception ex) + { + Miscellaneous.logEvent("e", "PhoneStatusListener", "Error stopping phoneStatusListener: " + Log.getStackTraceString(ex), 3); + } + } + + @Override + public void startListener(AutomationService automationService) + { + PhoneStatusListener.startPhoneStatusListener(automationService); + } + + @Override + public void stopListener(AutomationService automationService) + { + PhoneStatusListener.stopPhoneStatusListener(automationService); + } + + public static boolean haveAllPermission() + { + return + ActivityPermissions.havePermission("android.permission.READ_PHONE_STATE", Miscellaneous.getAnyContext()) + && + ActivityPermissions.havePermission(ActivityPermissions.permissionNameCall, Miscellaneous.getAnyContext()); + } + + @Override + public boolean isListenerRunning() + { + return PhoneStatusListener.incomingCallsReceiverActive | PhoneStatusListener.isOutgoingCallsReceiverActive(); + } + + @Override + public Trigger_Enum[] getMonitoredTrigger() + { + return new Trigger_Enum[] { Trigger_Enum.phoneCall }; + } +} diff --git a/app/src/main/java/com/jens/automation2/receivers/ProcessListener.java b/app/src/main/java/com/jens/automation2/receivers/ProcessListener.java new file mode 100644 index 0000000..42778dc --- /dev/null +++ b/app/src/main/java/com/jens/automation2/receivers/ProcessListener.java @@ -0,0 +1,470 @@ +package com.jens.automation2.receivers; + +import android.app.ActivityManager; +import android.app.ActivityManager.RunningAppProcessInfo; +import android.app.ActivityManager.RunningServiceInfo; +import android.app.ActivityManager.RunningTaskInfo; +import android.content.Context; +import android.os.Handler; +import android.os.Message; + +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.Collection; +import java.util.Iterator; +import java.util.List; + +public class ProcessListener implements AutomationListenerInterface +{ + private static ArrayList runningAppsList1 = new ArrayList(); + private static ArrayList runningAppsList2 = new ArrayList(); + private static int lastWritten = 2; + private static int runCounter = 0; + private static AutomationService automationService; + private static boolean isMonitoringActive = false; + private static boolean isTimerActive = false; + private static ArrayList runningAppProcessInfoList; + private static ProcessListenerMonitoring listener = null; + + public static boolean isProcessListenerActive() + { + return isMonitoringActive; + } + + private static Handler workHandler = new Handler() + { + @Override + public void handleMessage(Message msg) + { +// try +// { + Miscellaneous.logEvent("i", automationService.getResources().getString(R.string.processMonitoring), automationService.getResources().getString(R.string.messageReceivedStatingProcessMonitoringIsComplete), 5); + // This will take care of results delivered by the actual monitoring instance + + for(String entry : getRunningApps()) + Miscellaneous.logEvent("i", automationService.getResources().getString(R.string.runningApp), entry, 5); + + // execute matching rules containing processes + if(getRecentlyStartedApps().size()>0 | getRecentlyStoppedApps().size()>0) + { + for(String entry : getRecentlyStartedApps()) + Miscellaneous.logEvent("i", automationService.getResources().getString(R.string.appStarted), entry, 3); + for(String entry : getRecentlyStoppedApps()) + Miscellaneous.logEvent("i", automationService.getResources().getString(R.string.appStopped), entry, 3); + + ArrayList ruleCandidates = Rule.findRuleCandidatesByProcess(); + for(int i=0; ihandleMessage(): " + e.getMessage()); +// } + } + }; + + public static ArrayList getRunningApps() + { + if(runningAppsList1.size() == 0 && runningAppsList2.size() == 0) + ProcessListenerMonitoring.refreshRunningAppsList(); + + ArrayList runningAppsListReference; + + if(lastWritten == 1) + { + runningAppsListReference = runningAppsList1; + } + else + { + runningAppsListReference = runningAppsList2; + } + + return runningAppsListReference; + } + + public static ArrayList getRecentlyStartedApps() + { + ArrayList returnList = new ArrayList(); + + if(runCounter == 0) // Nothing ever happened. + return returnList; + + if(runCounter == 1) + // Only one run so far, all running apps are considered to have just started. + return runningAppsList1; + + ArrayList oldOne = null, newOne = null; + if(lastWritten == 1) + { + oldOne = runningAppsList2; + newOne = runningAppsList1; + } + else if(lastWritten == 2) + { + oldOne = runningAppsList1; + newOne = runningAppsList2; + } + + for(String runningApp : newOne) + { + if(!oldOne.contains(runningApp)) + //Started + returnList.add(runningApp); + } + + return returnList; + } + + public static ArrayList getRecentlyStoppedApps() + { + ArrayList returnList = new ArrayList(); + + if(runCounter == 1) // Nothing ever happened. + return returnList; + + if(runCounter == 1) + // Only one run so far, all running apps are considered to have just started, so return empty list. + return returnList; + + ArrayList oldOne = null, newOne = null; + if(lastWritten == 1) + { + oldOne = runningAppsList2; + newOne = runningAppsList1; + } + else if(lastWritten == 2) + { + oldOne = runningAppsList1; + newOne = runningAppsList2; + } + + for(String runningApp : oldOne) + { + if(!newOne.contains(runningApp)) + //Stopped + returnList.add(runningApp); + } + + return returnList; + } + + private static boolean stopRequested = false; + private static Handler schedulingHandler = new Handler() + { + @Override + public void handleMessage(Message msg) + { +// try +// { + if(msg.arg1 == 1) + { + if(!stopRequested) + { + listener = new ProcessListenerMonitoring(); + listener.doMonitoring(); + Miscellaneous.logEvent("i", automationService.getResources().getString(R.string.processMonitoring), automationService.getResources().getString(R.string.rearmingProcessMonitoringMessage), 5); + Message message = new Message(); + message.arg1 = 1; + schedulingHandler.sendMessageDelayed(message, Settings.timeBetweenProcessMonitorings * 1000); + } + else + Miscellaneous.logEvent("i", automationService.getResources().getString(R.string.processMonitoring), automationService.getResources().getString(R.string.notRearmingProcessMonitoringMessageStopRequested), 5); + } +// } +// catch(Exception e) +// { +// Miscellaneous.logEvent("e", "Noise level", "Error in schedulingHandler->handleMessage(): " + e.getMessage()); +// } + } + + }; + + private static class ProcessListenerMonitoring + { + Thread monitoringThread; + + public void doMonitoring() + { + monitoringThread = new Thread() + { + @Override + public void run() + { + if(!isMonitoringActive) + { + isMonitoringActive = true; + + Miscellaneous.logEvent("i", automationService.getResources().getString(R.string.processMonitoring), automationService.getResources().getString(R.string.periodicProcessMonitoringStarted), 5); + + refreshRunningAppsList(); + + Message answer = new Message(); +// Bundle answerBundle = new Bundle(); +// answer.setData(answerBundle); + workHandler.sendMessage(answer); + + //activate rule(s) + /*ArrayList ruleCandidates = Rule.findRuleCandidatesByProcess(); + for(int i=0; i services = activityManager.getRunningTasks(Integer.MAX_VALUE); + + ArrayList runningAppsListReference; + if(lastWritten == 1) + { +// Log.i("Processes", "Writing var 2"); + runningAppsListReference = runningAppsList2; + } + else + { +// Log.i("Processes", "Writing var 1"); + runningAppsListReference = runningAppsList1; + } + + runningAppsListReference.clear(); + + for (int i = 0; i < services.size(); i++) + { + if(!runningAppsListReference.contains(services.get(i).baseActivity.getClassName())) + { + // you may broadcast a new application launch here. + runningAppsListReference.add(services.get(i).baseActivity.getClassName()); + } + } + +// for(String runningApp : runningAppsListReference) +// { +// Miscellaneous.logEvent("i", "Running app", runningApp, 5); +// } + +// List procInfos = activityManager.getRunningAppProcesses(); +// for(int i = 0; i < procInfos.size(); i++) +// { +// ArrayList runningPkgs = new ArrayList(Arrays.asList(procInfos.get(i).pkgList)); +// +// Collection diff = subtractSets(runningPkgs, stalkList); +// +// if(diff != null) +// { +// stalkList.removeAll(diff); +// } +// } + + // Set marker to the one to be written next. + if(lastWritten == 1) + lastWritten = 2; + else if(lastWritten == 2) + lastWritten = 1; + else + lastWritten = -1; + + if(runCounter == 0 | runCounter == 1) + runCounter++; + } + + public void interrupt() + { + monitoringThread.interrupt(); + } + + private RunningAppProcessInfo getForegroundApp() + { + RunningAppProcessInfo result = null, info = null; + + final ActivityManager activityManager = (ActivityManager)automationService.getSystemService(Context.ACTIVITY_SERVICE); + + List l = activityManager.getRunningAppProcesses(); + Iterator i = l.iterator(); + while(i.hasNext()) + { + info = i.next(); + if(info.importance == RunningAppProcessInfo.IMPORTANCE_FOREGROUND + && !isRunningService(info.processName)) + { + result = info; + break; + } + } + return result; + } + + private boolean isRunningService(String processName) + { + if(processName == null) + return false; + + RunningServiceInfo service; + + final ActivityManager activityManager = (ActivityManager)automationService.getSystemService(Context.ACTIVITY_SERVICE); + + List l = activityManager.getRunningServices(9999); + Iterator i = l.iterator(); + while(i.hasNext()) + { + service = i.next(); + if(service.process.equals(processName)) + return true; + } + return false; + } + + private boolean isRunningApp(String processName) + { + if(processName == null) + return false; + + RunningAppProcessInfo app; + + final ActivityManager activityManager = (ActivityManager)automationService.getSystemService(Context.ACTIVITY_SERVICE); + + List l = activityManager.getRunningAppProcesses(); + Iterator i = l.iterator(); + while(i.hasNext()) + { + app = i.next(); + if(app.processName.equals(processName) && app.importance != RunningAppProcessInfo.IMPORTANCE_SERVICE) + return true; + } + return false; + } + + private boolean checkifThisIsActive(RunningAppProcessInfo target) + { + boolean result = false; + RunningTaskInfo info; + + if(target == null) + return false; + + final ActivityManager activityManager = (ActivityManager)automationService.getSystemService(Context.ACTIVITY_SERVICE); + + List l = activityManager.getRunningTasks(9999); + Iterator i = l.iterator(); + + while(i.hasNext()) + { + info=i.next(); + if(info.baseActivity.getPackageName().equals(target.processName)) + { + result = true; + break; + } + } + + return result; + } + + // what is in b that is not in a ? + public static Collection subtractSets(Collection a, Collection b) + { + Collection result = new ArrayList(b); + result.removeAll(a); + return result; + } + } + + public static void startProcessListener(AutomationService newAutomationService) + { + automationService = newAutomationService; + + if(!isTimerActive) + { + Miscellaneous.logEvent("i", automationService.getResources().getString(R.string.processMonitoring), automationService.getResources().getString(R.string.startingPeriodicProcessMonitoringEngine), 2); + isTimerActive = true; + + Message message = new Message(); + message.arg1 = 1; +// schedulingHandler.sendMessageDelayed(message, Settings.timeBetweenNoiseLevelMeasurements * 1000); + schedulingHandler.sendMessageDelayed(message, 10000); + } + else + Miscellaneous.logEvent("i", automationService.getResources().getString(R.string.processMonitoring), automationService.getResources().getString(R.string.periodicProcessMonitoringIsAlreadyRunning), 2); + } + public static void stopProcessListener(AutomationService newAutomationService) + { + if(isTimerActive) + { + stopRequested = true; + Miscellaneous.logEvent("i", automationService.getResources().getString(R.string.processMonitoring), automationService.getResources().getString(R.string.stoppingPeriodicProcessMonitoringEngine), 2); + + if(schedulingHandler.hasMessages(1)) + schedulingHandler.removeMessages(1); + + if(listener != null) + listener.interrupt(); + + isTimerActive = false; + } + else + { + automationService = newAutomationService; + Miscellaneous.logEvent("i", automationService.getResources().getString(R.string.processMonitoring), automationService.getResources().getString(R.string.periodicProcessMonitoringIsNotActive), 2); + } + } + + public static ArrayList getRunningAppProcessInfo() + { + return runningAppProcessInfoList; + } + + @Override + public void startListener(AutomationService automationService) + { + ProcessListener.startProcessListener(automationService); + } + + @Override + public void stopListener(AutomationService automationService) + { + ProcessListener.stopProcessListener(automationService); + } + + public static boolean haveAllPermission() + { + return ActivityPermissions.havePermission("android.permission.GET_TASKS", Miscellaneous.getAnyContext()); + } + + @Override + public boolean isListenerRunning() + { + return ProcessListener.isProcessListenerActive(); + } + + @Override + public Trigger_Enum[] getMonitoredTrigger() + { + return new Trigger_Enum[] { Trigger_Enum.process_started_stopped }; + } + + +} diff --git a/app/src/main/java/com/jens/automation2/receivers/StartupIntentReceiver.java b/app/src/main/java/com/jens/automation2/receivers/StartupIntentReceiver.java new file mode 100644 index 0000000..671714a --- /dev/null +++ b/app/src/main/java/com/jens/automation2/receivers/StartupIntentReceiver.java @@ -0,0 +1,34 @@ +package com.jens.automation2.receivers; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; + +import com.jens.automation2.AutomationService; +import com.jens.automation2.Miscellaneous; +import com.jens.automation2.R; +import com.jens.automation2.Settings; + +public class StartupIntentReceiver extends BroadcastReceiver +{ + + @Override + public void onReceive(Context context, Intent intent) + { + Settings.readFromPersistentStorage(context); + + Miscellaneous.logEvent("i", "Boot event", "Received event: " + intent.getAction(), 5); + + if(Settings.startServiceAtSystemBoot) + { + Miscellaneous.logEvent("i", "Service", context.getResources().getString(R.string.logStartingServiceAtPhoneBoot), 1); +// Settings.readFromPersistentStorage(context); + AutomationService.startAutomationService(context, true); + } + else + { + Miscellaneous.logEvent("i", "Service", context.getResources().getString(R.string.logNotStartingServiceAtPhoneBoot), 2); + } + } + +} diff --git a/app/src/main/java/com/jens/automation2/receivers/TimeZoneListener.java b/app/src/main/java/com/jens/automation2/receivers/TimeZoneListener.java new file mode 100644 index 0000000..c2856f2 --- /dev/null +++ b/app/src/main/java/com/jens/automation2/receivers/TimeZoneListener.java @@ -0,0 +1,114 @@ +package com.jens.automation2.receivers; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.util.Log; + +import com.jens.automation2.AutomationService; +import com.jens.automation2.Miscellaneous; +import com.jens.automation2.Rule; +import com.jens.automation2.Trigger.Trigger_Enum; + +public class TimeZoneListener extends BroadcastReceiver implements AutomationListenerInterface +{ + private static TimeZoneListener timeZoneListenerInstance = null; + protected static boolean timeZoneListenerActive = false; + protected static AutomationService automationServiceRef = null; + protected static IntentFilter timeZoneListenerIntentFilter = null; + + + public static boolean isTimeZoneListenerActive() + { + return timeZoneListenerActive; + } + + public static void startTimeZoneListener(AutomationService automationService) + { + if(timeZoneListenerInstance == null) + timeZoneListenerInstance = new TimeZoneListener(); + + automationServiceRef = automationService; + + try + { + if(!timeZoneListenerActive && Rule.isAnyRuleUsing(Trigger_Enum.timeFrame)) + { + Miscellaneous.logEvent("i", "TimeZoneListener", "Starting TimeZoneListener", 4); + timeZoneListenerActive = true; + + if(timeZoneListenerIntentFilter == null) + { + timeZoneListenerIntentFilter = new IntentFilter(); + timeZoneListenerIntentFilter.addAction(Intent.ACTION_TIMEZONE_CHANGED); + timeZoneListenerIntentFilter.addAction(Intent.ACTION_TIME_CHANGED); + } + + automationService.registerReceiver(timeZoneListenerInstance, timeZoneListenerIntentFilter); + } + } + catch(Exception ex) + { + Miscellaneous.logEvent("e", "TimeZoneListener", "Error starting TimeZoneListener: " + Log.getStackTraceString(ex), 3); + } + } + public static void stopTimeZoneListener() + { + try + { + if(timeZoneListenerActive) + { + Miscellaneous.logEvent("i", "TimeZoneListener", "Stopping TimeZoneListener", 4); + automationServiceRef.unregisterReceiver(timeZoneListenerInstance); + timeZoneListenerActive = false; + } + } + catch(Exception ex) + { + Miscellaneous.logEvent("e", "TimeZoneListener", "Error stopping TimeZoneListener: " + Log.getStackTraceString(ex), 3); + } + } + + @Override + public void onReceive(Context context, Intent intent) + { + String action = intent.getAction(); + if(action.equals(Intent.ACTION_TIMEZONE_CHANGED)) + { + Miscellaneous.logEvent("i", "TimeZoneListener", "Device timezone changed. Reloading alarms.", 3); + AlarmListener.reloadAlarms(); + } + else if(action.equals(Intent.ACTION_TIME_CHANGED)) + { + Miscellaneous.logEvent("i", "TimeZoneListener", "Device time changed. Reloading alarms.", 4); + AlarmListener.reloadAlarms(); + } + } + @Override + public void startListener(AutomationService automationService) + { + TimeZoneListener.startTimeZoneListener(automationService); + } + @Override + public void stopListener(AutomationService automationService) + { + TimeZoneListener.stopTimeZoneListener(); + } + + public static boolean haveAllPermission() + { + return true; + } + + @Override + public boolean isListenerRunning() + { + return TimeZoneListener.isTimeZoneListenerActive(); + } + @Override + public Trigger_Enum[] getMonitoredTrigger() + { + return null; + } +} diff --git a/app/src/main/java/eu/chainfire/libsuperuser/Application.java b/app/src/main/java/eu/chainfire/libsuperuser/Application.java new file mode 100644 index 0000000..095c720 --- /dev/null +++ b/app/src/main/java/eu/chainfire/libsuperuser/Application.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2012-2014 Jorrit "Chainfire" Jongma + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package eu.chainfire.libsuperuser; + +import android.content.Context; +import android.os.Handler; +import android.widget.Toast; + +/** + * Base application class to extend from, solving some issues with + * toasts and AsyncTasks you are likely to run into + */ +public class Application extends android.app.Application { + /** + * Shows a toast message + * + * @param context Any context belonging to this application + * @param message The message to show + */ + public static void toast(Context context, String message) { + // this is a static method so it is easier to call, + // as the context checking and casting is done for you + + if (context == null) return; + + if (!(context instanceof Application)) { + context = context.getApplicationContext(); + } + + if (context instanceof Application) { + final Context c = context; + final String m = message; + + ((Application)context).runInApplicationThread(new Runnable() { + @Override + public void run() { + Toast.makeText(c, m, Toast.LENGTH_LONG).show(); + } + }); + } + } + + private static Handler mApplicationHandler = new Handler(); + + /** + * Run a runnable in the main application thread + * + * @param r Runnable to run + */ + public void runInApplicationThread(Runnable r) { + mApplicationHandler.post(r); + } + + @Override + public void onCreate() { + super.onCreate(); + + try { + // workaround bug in AsyncTask, can show up (for example) when you toast from a service + // this makes sure AsyncTask's internal handler is created from the right (main) thread + Class.forName("android.os.AsyncTask"); + } catch (ClassNotFoundException e) { + // will never happen + } + } +} diff --git a/app/src/main/java/eu/chainfire/libsuperuser/Debug.java b/app/src/main/java/eu/chainfire/libsuperuser/Debug.java new file mode 100644 index 0000000..4ad0718 --- /dev/null +++ b/app/src/main/java/eu/chainfire/libsuperuser/Debug.java @@ -0,0 +1,242 @@ +/* + * Copyright (C) 2012-2014 Jorrit "Chainfire" Jongma + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package eu.chainfire.libsuperuser; + +import android.os.Looper; +import android.util.Log; + +import com.jens.automation2.BuildConfig; + +/** + * Utility class for logging and debug features that (by default) does nothing when not in debug mode + */ +public class Debug { + + // ----- DEBUGGING ----- + + private static boolean debug = BuildConfig.DEBUG; + + /** + *

Enable or disable debug mode

+ * + *

By default, debug mode is enabled for development + * builds and disabled for exported APKs - see + * BuildConfig.DEBUG

+ * + * @param enable Enable debug mode ? + */ + public static void setDebug(boolean enable) { + debug = enable; + } + + /** + *

Is debug mode enabled ?

+ * + * @return Debug mode enabled + */ + public static boolean getDebug() { + return debug; + } + + // ----- LOGGING ----- + + public interface OnLogListener { + void onLog(int type, String typeIndicator, String message); + } + + public static final String TAG = "libsuperuser"; + + public static final int LOG_GENERAL = 0x0001; + public static final int LOG_COMMAND = 0x0002; + public static final int LOG_OUTPUT = 0x0004; + + public static final int LOG_NONE = 0x0000; + public static final int LOG_ALL = 0xFFFF; + + private static int logTypes = LOG_ALL; + + private static OnLogListener logListener = null; + + /** + *

Log a message (internal)

+ * + *

Current debug and enabled logtypes decide what gets logged - + * even if a custom callback is registered

+ * + * @param type Type of message to log + * @param typeIndicator String indicator for message type + * @param message The message to log + */ + private static void logCommon(int type, String typeIndicator, String message) { + if (debug && ((logTypes & type) == type)) { + if (logListener != null) { + logListener.onLog(type, typeIndicator, message); + } else { + Log.d(TAG, "[" + TAG + "][" + typeIndicator + "]" + (!message.startsWith("[") && !message.startsWith(" ") ? " " : "") + message); + } + } + } + + /** + *

Log a "general" message

+ * + *

These messages are infrequent and mostly occur at startup/shutdown or on error

+ * + * @param message The message to log + */ + public static void log(String message) { + logCommon(LOG_GENERAL, "G", message); + } + + /** + *

Log a "per-command" message

+ * + *

This could produce a lot of output if the client runs many commands in the session

+ * + * @param message The message to log + */ + public static void logCommand(String message) { + logCommon(LOG_COMMAND, "C", message); + } + + /** + *

Log a line of stdout/stderr output

+ * + *

This could produce a lot of output if the shell commands are noisy

+ * + * @param message The message to log + */ + public static void logOutput(String message) { + logCommon(LOG_OUTPUT, "O", message); + } + + /** + *

Enable or disable logging specific types of message

+ * + *

You may | (or) LOG_* constants together. Note that + * debug mode must also be enabled for actual logging to + * occur.

+ * + * @param type LOG_* constants + * @param enable Enable or disable + */ + public static void setLogTypeEnabled(int type, boolean enable) { + if (enable) { + logTypes |= type; + } else { + logTypes &= ~type; + } + } + + /** + *

Is logging for specific types of messages enabled ?

+ * + *

You may | (or) LOG_* constants together, to learn if + * all passed message types are enabled for logging. Note + * that debug mode must also be enabled for actual logging + * to occur.

+ * + * @param type LOG_* constants + */ + public static boolean getLogTypeEnabled(int type) { + return ((logTypes & type) == type); + } + + /** + *

Is logging for specific types of messages enabled ?

+ * + *

You may | (or) LOG_* constants together, to learn if + * all message types are enabled for logging. Takes + * debug mode into account for the result.

+ * + * @param type LOG_* constants + */ + public static boolean getLogTypeEnabledEffective(int type) { + return getDebug() && getLogTypeEnabled(type); + } + + /** + *

Register a custom log handler

+ * + *

Replaces the log method (write to logcat) with your own + * handler. Whether your handler gets called is still dependent + * on debug mode and message types being enabled for logging.

+ * + * @param onLogListener Custom log listener or NULL to revert to default + */ + public static void setOnLogListener(OnLogListener onLogListener) { + logListener = onLogListener; + } + + /** + *

Get the currently registered custom log handler

+ * + * @return Current custom log handler or NULL if none is present + */ + public static OnLogListener getOnLogListener() { + return logListener; + } + + // ----- SANITY CHECKS ----- + + private static boolean sanityChecks = true; + + /** + *

Enable or disable sanity checks

+ * + *

Enables or disables the library crashing when su is called + * from the main thread.

+ * + * @param enable Enable or disable + */ + public static void setSanityChecksEnabled(boolean enable) { + sanityChecks = enable; + } + + /** + *

Are sanity checks enabled ?

+ * + *

Note that debug mode must also be enabled for actual + * sanity checks to occur.

+ * + * @return True if enabled + */ + public static boolean getSanityChecksEnabled() { + return sanityChecks; + } + + /** + *

Are sanity checks enabled ?

+ * + *

Takes debug mode into account for the result.

+ * + * @return True if enabled + */ + public static boolean getSanityChecksEnabledEffective() { + return getDebug() && getSanityChecksEnabled(); + } + + /** + *

Are we running on the main thread ?

+ * + * @return Running on main thread ? + */ + public static boolean onMainThread() { + return ((Looper.myLooper() != null) && (Looper.myLooper() == Looper.getMainLooper())); + } + +} diff --git a/app/src/main/java/eu/chainfire/libsuperuser/HideOverlaysReceiver.java b/app/src/main/java/eu/chainfire/libsuperuser/HideOverlaysReceiver.java new file mode 100644 index 0000000..4b4ce5a --- /dev/null +++ b/app/src/main/java/eu/chainfire/libsuperuser/HideOverlaysReceiver.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2012-2014 Jorrit "Chainfire" Jongma + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package eu.chainfire.libsuperuser; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; + +/** + *

+ * Base receiver to extend to catch notifications when overlays should be + * hidden. + *

+ *

+ * Tapjacking protection in SuperSU prevents some dialogs from receiving user + * input when overlays are present. For security reasons this notification is + * only sent to apps that have previously been granted root access, so even if + * your app does not require root, you still need to request + * it, and the user must grant it. + *

+ *

+ * Note that the word overlay as used here should be interpreted as "any view or + * window possibly obscuring SuperSU dialogs". + *

+ */ +public abstract class HideOverlaysReceiver extends BroadcastReceiver { + public static final String ACTION_HIDE_OVERLAYS = "eu.chainfire.supersu.action.HIDE_OVERLAYS"; + public static final String CATEGORY_HIDE_OVERLAYS = Intent.CATEGORY_INFO; + public static final String EXTRA_HIDE_OVERLAYS = "eu.chainfire.supersu.extra.HIDE"; + + @Override + public final void onReceive(Context context, Intent intent) { + if (intent.hasExtra(EXTRA_HIDE_OVERLAYS)) { + onHideOverlays(intent.getBooleanExtra(EXTRA_HIDE_OVERLAYS, false)); + } + } + + /** + * Called when overlays should be hidden or may be shown + * again. + * + * @param hide Should overlays be hidden? + */ + public abstract void onHideOverlays(boolean hide); +} diff --git a/app/src/main/java/eu/chainfire/libsuperuser/Shell.java b/app/src/main/java/eu/chainfire/libsuperuser/Shell.java new file mode 100644 index 0000000..2c2386d --- /dev/null +++ b/app/src/main/java/eu/chainfire/libsuperuser/Shell.java @@ -0,0 +1,1757 @@ +/* + * Copyright (C) 2012-2014 Jorrit "Chainfire" Jongma + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package eu.chainfire.libsuperuser; + +import android.os.Handler; +import android.os.Looper; + +import java.io.DataOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +import eu.chainfire.libsuperuser.StreamGobbler.OnLineListener; + +/** + * Class providing functionality to execute commands in a (root) shell + */ +public class Shell { + /** + *

+ * Runs commands using the supplied shell, and returns the output, or null + * in case of errors. + *

+ *

+ * This method is deprecated and only provided for backwards compatibility. + * Use {@link #run(String, String[], String[], boolean)} instead, and see + * that same method for usage notes. + *

+ * + * @param shell The shell to use for executing the commands + * @param commands The commands to execute + * @param wantSTDERR Return STDERR in the output ? + * @return Output of the commands, or null in case of an error + */ + @Deprecated + public static List run(String shell, String[] commands, boolean wantSTDERR) { + return run(shell, commands, null, wantSTDERR); + } + + /** + *

+ * Runs commands using the supplied shell, and returns the output, or null + * in case of errors. + *

+ *

+ * Note that due to compatibility with older Android versions, wantSTDERR is + * not implemented using redirectErrorStream, but rather appended to the + * output. STDOUT and STDERR are thus not guaranteed to be in the correct + * order in the output. + *

+ *

+ * Note as well that this code will intentionally crash when run in debug + * mode from the main thread of the application. You should always execute + * shell commands from a background thread. + *

+ *

+ * When in debug mode, the code will also excessively log the commands + * passed to and the output returned from the shell. + *

+ *

+ * Though this function uses background threads to gobble STDOUT and STDERR + * so a deadlock does not occur if the shell produces massive output, the + * output is still stored in a List<String>, and as such doing + * something like 'ls -lR /' will probably have you run out of + * memory. + *

+ * + * @param shell The shell to use for executing the commands + * @param commands The commands to execute + * @param environment List of all environment variables (in 'key=value' + * format) or null for defaults + * @param wantSTDERR Return STDERR in the output ? + * @return Output of the commands, or null in case of an error + */ + public static List run(String shell, String[] commands, String[] environment, + boolean wantSTDERR) { + String shellUpper = shell.toUpperCase(Locale.ENGLISH); + + if (Debug.getSanityChecksEnabledEffective() && Debug.onMainThread()) { + // check if we're running in the main thread, and if so, crash if + // we're in debug mode, to let the developer know attention is + // needed here. + + Debug.log(ShellOnMainThreadException.EXCEPTION_COMMAND); + throw new ShellOnMainThreadException(ShellOnMainThreadException.EXCEPTION_COMMAND); + } + Debug.logCommand(String.format("[%s%%] START", shellUpper)); + + List res = Collections.synchronizedList(new ArrayList()); + + try { + // Combine passed environment with system environment + if (environment != null) { + Map newEnvironment = new HashMap(); + newEnvironment.putAll(System.getenv()); + int split; + for (String entry : environment) { + if ((split = entry.indexOf("=")) >= 0) { + newEnvironment.put(entry.substring(0, split), entry.substring(split + 1)); + } + } + int i = 0; + environment = new String[newEnvironment.size()]; + for (Map.Entry entry : newEnvironment.entrySet()) { + environment[i] = entry.getKey() + "=" + entry.getValue(); + i++; + } + } + + // setup our process, retrieve STDIN stream, and STDOUT/STDERR + // gobblers + Process process = Runtime.getRuntime().exec(shell, environment); + DataOutputStream STDIN = new DataOutputStream(process.getOutputStream()); + StreamGobbler STDOUT = new StreamGobbler(shellUpper + "-", process.getInputStream(), + res); + StreamGobbler STDERR = new StreamGobbler(shellUpper + "*", process.getErrorStream(), + wantSTDERR ? res : null); + + // start gobbling and write our commands to the shell + STDOUT.start(); + STDERR.start(); + try { + for (String write : commands) { + Debug.logCommand(String.format("[%s+] %s", shellUpper, write)); + STDIN.write((write + "\n").getBytes("UTF-8")); + STDIN.flush(); + } + STDIN.write("exit\n".getBytes("UTF-8")); + STDIN.flush(); + } catch (IOException e) { + if (e.getMessage().contains("EPIPE")) { + // method most horrid to catch broken pipe, in which case we + // do nothing. the command is not a shell, the shell closed + // STDIN, the script already contained the exit command, etc. + // these cases we want the output instead of returning null + } else { + // other issues we don't know how to handle, leads to + // returning null + throw e; + } + } + + // wait for our process to finish, while we gobble away in the + // background + process.waitFor(); + + // make sure our threads are done gobbling, our streams are closed, + // and the process is destroyed - while the latter two shouldn't be + // needed in theory, and may even produce warnings, in "normal" Java + // they are required for guaranteed cleanup of resources, so lets be + // safe and do this on Android as well + try { + STDIN.close(); + } catch (IOException e) { + // might be closed already + } + STDOUT.join(); + STDERR.join(); + process.destroy(); + + // in case of su, 255 usually indicates access denied + if (SU.isSU(shell) && (process.exitValue() == 255)) { + res = null; + } + } catch (IOException e) { + // shell probably not found + res = null; + } catch (InterruptedException e) { + // this should really be re-thrown + res = null; + } + + Debug.logCommand(String.format("[%s%%] END", shell.toUpperCase(Locale.ENGLISH))); + return res; + } + + protected static String[] availableTestCommands = new String[] { + "echo -BOC-", + "id" + }; + + /** + * See if the shell is alive, and if so, check the UID + * + * @param ret Standard output from running availableTestCommands + * @param checkForRoot true if we are expecting this shell to be running as + * root + * @return true on success, false on error + */ + protected static boolean parseAvailableResult(List ret, boolean checkForRoot) { + if (ret == null) + return false; + + // this is only one of many ways this can be done + boolean echo_seen = false; + + for (String line : ret) { + if (line.contains("uid=")) { + // id command is working, let's see if we are actually root + return !checkForRoot || line.contains("uid=0"); + } else if (line.contains("-BOC-")) { + // if we end up here, at least the su command starts some kind + // of shell, + // let's hope it has root privileges - no way to know without + // additional + // native binaries + echo_seen = true; + } + } + + return echo_seen; + } + + /** + * This class provides utility functions to easily execute commands using SH + */ + public static class SH { + /** + * Runs command and return output + * + * @param command The command to run + * @return Output of the command, or null in case of an error + */ + public static List run(String command) { + return Shell.run("sh", new String[] { + command + }, null, false); + } + + /** + * Runs commands and return output + * + * @param commands The commands to run + * @return Output of the commands, or null in case of an error + */ + public static List run(List commands) { + return Shell.run("sh", commands.toArray(new String[commands.size()]), null, false); + } + + /** + * Runs commands and return output + * + * @param commands The commands to run + * @return Output of the commands, or null in case of an error + */ + public static List run(String[] commands) { + return Shell.run("sh", commands, null, false); + } + } + + /** + * This class provides utility functions to easily execute commands using SU + * (root shell), as well as detecting whether or not root is available, and + * if so which version. + */ + public static class SU { + private static Boolean isSELinuxEnforcing = null; + private static String[] suVersion = new String[] { + null, null + }; + + /** + * Runs command as root (if available) and return output + * + * @param command The command to run + * @return Output of the command, or null if root isn't available or in + * case of an error + */ + public static List run(String command) { + return Shell.run("su", new String[] { + command + }, null, false); + } + + /** + * Runs commands as root (if available) and return output + * + * @param commands The commands to run + * @return Output of the commands, or null if root isn't available or in + * case of an error + */ + public static List run(List commands) { + return Shell.run("su", commands.toArray(new String[commands.size()]), null, false); + } + + /** + * Runs commands as root (if available) and return output + * + * @param commands The commands to run + * @return Output of the commands, or null if root isn't available or in + * case of an error + */ + public static List run(String[] commands) { + return Shell.run("su", commands, null, false); + } + + /** + * Detects whether or not superuser access is available, by checking the + * output of the "id" command if available, checking if a shell runs at + * all otherwise + * + * @return True if superuser access available + */ + public static boolean available() { + // this is only one of many ways this can be done + + List ret = run(Shell.availableTestCommands); + return Shell.parseAvailableResult(ret, true); + } + + /** + *

+ * Detects the version of the su binary installed (if any), if supported + * by the binary. Most binaries support two different version numbers, + * the public version that is displayed to users, and an internal + * version number that is used for version number comparisons. Returns + * null if su not available or retrieving the version isn't supported. + *

+ *

+ * Note that su binary version and GUI (APK) version can be completely + * different. + *

+ *

+ * This function caches its result to improve performance on multiple + * calls + *

+ * + * @param internal Request human-readable version or application + * internal version + * @return String containing the su version or null + */ + public static synchronized String version(boolean internal) { + int idx = internal ? 0 : 1; + if (suVersion[idx] == null) { + String version = null; + + List ret = Shell.run( + internal ? "su -V" : "su -v", + new String[] { "exit" }, + null, + false + ); + + if (ret != null) { + for (String line : ret) { + if (!internal) { + if (!line.trim().equals("")) { + version = line; + break; + } + } else { + try { + if (Integer.parseInt(line) > 0) { + version = line; + break; + } + } catch (NumberFormatException e) { + // should be parsable, try next line otherwise + } + } + } + } + + suVersion[idx] = version; + } + return suVersion[idx]; + } + + /** + * Attempts to deduce if the shell command refers to a su shell + * + * @param shell Shell command to run + * @return Shell command appears to be su + */ + public static boolean isSU(String shell) { + // Strip parameters + int pos = shell.indexOf(' '); + if (pos >= 0) { + shell = shell.substring(0, pos); + } + + // Strip path + pos = shell.lastIndexOf('/'); + if (pos >= 0) { + shell = shell.substring(pos + 1); + } + + return shell.equals("su"); + } + + /** + * Constructs a shell command to start a su shell using the supplied uid + * and SELinux context. This is can be an expensive operation, consider + * caching the result. + * + * @param uid Uid to use (0 == root) + * @param context (SELinux) context name to use or null + * @return Shell command + */ + public static String shell(int uid, String context) { + // su[ --context ][ ] + String shell = "su"; + + if ((context != null) && isSELinuxEnforcing()) { + String display = version(false); + String internal = version(true); + + // We only know the format for SuperSU v1.90+ right now + if ((display != null) && + (internal != null) && + (display.endsWith("SUPERSU")) && + (Integer.valueOf(internal) >= 190)) { + shell = String.format(Locale.ENGLISH, "%s --context %s", shell, context); + } + } + + // Most su binaries support the "su " format, but in case + // they don't, lets skip it for the default 0 (root) case + if (uid > 0) { + shell = String.format(Locale.ENGLISH, "%s %d", shell, uid); + } + + return shell; + } + + /** + * Constructs a shell command to start a su shell connected to mount + * master daemon, to perform public mounts on Android 4.3+ (or 4.2+ in + * SELinux enforcing mode) + * + * @return Shell command + */ + public static String shellMountMaster() { + if (android.os.Build.VERSION.SDK_INT >= 17) { + return "su --mount-master"; + } + return "su"; + } + + /** + * Detect if SELinux is set to enforcing, caches result + * + * @return true if SELinux set to enforcing, or false in the case of + * permissive or not present + */ + public static synchronized boolean isSELinuxEnforcing() { + if (isSELinuxEnforcing == null) { + Boolean enforcing = null; + + // First known firmware with SELinux built-in was a 4.2 (17) + // leak + if (android.os.Build.VERSION.SDK_INT >= 17) { + // Detect enforcing through sysfs, not always present + File f = new File("/sys/fs/selinux/enforce"); + if (f.exists()) { + try { + InputStream is = new FileInputStream("/sys/fs/selinux/enforce"); + try { + enforcing = (is.read() == '1'); + } finally { + is.close(); + } + } catch (Exception e) { + // we might not be allowed to read, thanks SELinux + } + } + + // 4.4+ builds are enforcing by default, take the gamble + if (enforcing == null) { + enforcing = (android.os.Build.VERSION.SDK_INT >= 19); + } + } + + if (enforcing == null) { + enforcing = false; + } + + isSELinuxEnforcing = enforcing; + } + return isSELinuxEnforcing; + } + + /** + *

+ * Clears results cached by isSELinuxEnforcing() and version(boolean + * internal) calls. + *

+ *

+ * Most apps should never need to call this, as neither enforcing status + * nor su version is likely to change on a running device - though it is + * not impossible. + *

+ */ + public static synchronized void clearCachedResults() { + isSELinuxEnforcing = null; + suVersion[0] = null; + suVersion[1] = null; + } + } + + private interface OnResult { + // for any onCommandResult callback + int WATCHDOG_EXIT = -1; + int SHELL_DIED = -2; + + // for Interactive.open() callbacks only + int SHELL_EXEC_FAILED = -3; + int SHELL_WRONG_UID = -4; + int SHELL_RUNNING = 0; + } + + /** + * Command result callback, notifies the recipient of the completion of a + * command block, including the (last) exit code, and the full output + */ + public interface OnCommandResultListener extends OnResult { + /** + *

+ * Command result callback + *

+ *

+ * Depending on how and on which thread the shell was created, this + * callback may be executed on one of the gobbler threads. In that case, + * it is important the callback returns as quickly as possible, as + * delays in this callback may pause the native process or even result + * in a deadlock + *

+ *

+ * See {@link Interactive} for threading details + *

+ * + * @param commandCode Value previously supplied to addCommand + * @param exitCode Exit code of the last command in the block + * @param output All output generated by the command block + */ + void onCommandResult(int commandCode, int exitCode, List output); + } + + /** + * Command per line callback for parsing the output line by line without + * buffering It also notifies the recipient of the completion of a command + * block, including the (last) exit code. + */ + public interface OnCommandLineListener extends OnResult, OnLineListener + { + /** + *

+ * Command result callback + *

+ *

+ * Depending on how and on which thread the shell was created, this + * callback may be executed on one of the gobbler threads. In that case, + * it is important the callback returns as quickly as possible, as + * delays in this callback may pause the native process or even result + * in a deadlock + *

+ *

+ * See {@link Interactive} for threading details + *

+ * + * @param commandCode Value previously supplied to addCommand + * @param exitCode Exit code of the last command in the block + */ + void onCommandResult(int commandCode, int exitCode); + } + + /** + * Internal class to store command block properties + */ + private static class Command { + private static int commandCounter = 0; + + private final String[] commands; + private final int code; + private final OnCommandResultListener onCommandResultListener; + private final OnCommandLineListener onCommandLineListener; + private final String marker; + + public Command(String[] commands, int code, + OnCommandResultListener onCommandResultListener, + OnCommandLineListener onCommandLineListener) { + this.commands = commands; + this.code = code; + this.onCommandResultListener = onCommandResultListener; + this.onCommandLineListener = onCommandLineListener; + this.marker = UUID.randomUUID().toString() + String.format("-%08x", ++commandCounter); + } + } + + /** + * Builder class for {@link Interactive} + */ + public static class Builder { + private Handler handler = null; + private boolean autoHandler = true; + private String shell = "sh"; + private boolean wantSTDERR = false; + private List commands = new LinkedList(); + private Map environment = new HashMap(); + private OnLineListener onSTDOUTLineListener = null; + private OnLineListener onSTDERRLineListener = null; + private int watchdogTimeout = 0; + + /** + *

+ * Set a custom handler that will be used to post all callbacks to + *

+ *

+ * See {@link Interactive} for further details on threading and + * handlers + *

+ * + * @param handler Handler to use + * @return This Builder object for method chaining + */ + public Builder setHandler(Handler handler) { + this.handler = handler; + return this; + } + + /** + *

+ * Automatically create a handler if possible ? Default to true + *

+ *

+ * See {@link Interactive} for further details on threading and + * handlers + *

+ * + * @param autoHandler Auto-create handler ? + * @return This Builder object for method chaining + */ + public Builder setAutoHandler(boolean autoHandler) { + this.autoHandler = autoHandler; + return this; + } + + /** + * Set shell binary to use. Usually "sh" or "su", do not use a full path + * unless you have a good reason to + * + * @param shell Shell to use + * @return This Builder object for method chaining + */ + public Builder setShell(String shell) { + this.shell = shell; + return this; + } + + /** + * Convenience function to set "sh" as used shell + * + * @return This Builder object for method chaining + */ + public Builder useSH() { + return setShell("sh"); + } + + /** + * Convenience function to set "su" as used shell + * + * @return This Builder object for method chaining + */ + public Builder useSU() { + return setShell("su"); + } + + /** + * Set if error output should be appended to command block result output + * + * @param wantSTDERR Want error output ? + * @return This Builder object for method chaining + */ + public Builder setWantSTDERR(boolean wantSTDERR) { + this.wantSTDERR = wantSTDERR; + return this; + } + + /** + * Add or update an environment variable + * + * @param key Key of the environment variable + * @param value Value of the environment variable + * @return This Builder object for method chaining + */ + public Builder addEnvironment(String key, String value) { + environment.put(key, value); + return this; + } + + /** + * Add or update environment variables + * + * @param addEnvironment Map of environment variables + * @return This Builder object for method chaining + */ + public Builder addEnvironment(Map addEnvironment) { + environment.putAll(addEnvironment); + return this; + } + + /** + * Add a command to execute + * + * @param command Command to execute + * @return This Builder object for method chaining + */ + public Builder addCommand(String command) { + return addCommand(command, 0, null); + } + + /** + *

+ * Add a command to execute, with a callback to be called on completion + *

+ *

+ * The thread on which the callback executes is dependent on various + * factors, see {@link Interactive} for further details + *

+ * + * @param command Command to execute + * @param code User-defined value passed back to the callback + * @param onCommandResultListener Callback to be called on completion + * @return This Builder object for method chaining + */ + public Builder addCommand(String command, int code, + OnCommandResultListener onCommandResultListener) { + return addCommand(new String[] { + command + }, code, onCommandResultListener); + } + + /** + * Add commands to execute + * + * @param commands Commands to execute + * @return This Builder object for method chaining + */ + public Builder addCommand(List commands) { + return addCommand(commands, 0, null); + } + + /** + *

+ * Add commands to execute, with a callback to be called on completion + * (of all commands) + *

+ *

+ * The thread on which the callback executes is dependent on various + * factors, see {@link Interactive} for further details + *

+ * + * @param commands Commands to execute + * @param code User-defined value passed back to the callback + * @param onCommandResultListener Callback to be called on completion + * (of all commands) + * @return This Builder object for method chaining + */ + public Builder addCommand(List commands, int code, + OnCommandResultListener onCommandResultListener) { + return addCommand(commands.toArray(new String[commands.size()]), code, + onCommandResultListener); + } + + /** + * Add commands to execute + * + * @param commands Commands to execute + * @return This Builder object for method chaining + */ + public Builder addCommand(String[] commands) { + return addCommand(commands, 0, null); + } + + /** + *

+ * Add commands to execute, with a callback to be called on completion + * (of all commands) + *

+ *

+ * The thread on which the callback executes is dependent on various + * factors, see {@link Interactive} for further details + *

+ * + * @param commands Commands to execute + * @param code User-defined value passed back to the callback + * @param onCommandResultListener Callback to be called on completion + * (of all commands) + * @return This Builder object for method chaining + */ + public Builder addCommand(String[] commands, int code, + OnCommandResultListener onCommandResultListener) { + this.commands.add(new Command(commands, code, onCommandResultListener, null)); + return this; + } + + /** + *

+ * Set a callback called for every line output to STDOUT by the shell + *

+ *

+ * The thread on which the callback executes is dependent on various + * factors, see {@link Interactive} for further details + *

+ * + * @param onLineListener Callback to be called for each line + * @return This Builder object for method chaining + */ + public Builder setOnSTDOUTLineListener(OnLineListener onLineListener) { + this.onSTDOUTLineListener = onLineListener; + return this; + } + + /** + *

+ * Set a callback called for every line output to STDERR by the shell + *

+ *

+ * The thread on which the callback executes is dependent on various + * factors, see {@link Interactive} for further details + *

+ * + * @param onLineListener Callback to be called for each line + * @return This Builder object for method chaining + */ + public Builder setOnSTDERRLineListener(OnLineListener onLineListener) { + this.onSTDERRLineListener = onLineListener; + return this; + } + + /** + *

+ * Enable command timeout callback + *

+ *

+ * This will invoke the onCommandResult() callback with exitCode + * WATCHDOG_EXIT if a command takes longer than watchdogTimeout seconds + * to complete. + *

+ *

+ * If a watchdog timeout occurs, it generally means that the Interactive + * session is out of sync with the shell process. The caller should + * close the current session and open a new one. + *

+ * + * @param watchdogTimeout Timeout, in seconds; 0 to disable + * @return This Builder object for method chaining + */ + public Builder setWatchdogTimeout(int watchdogTimeout) { + this.watchdogTimeout = watchdogTimeout; + return this; + } + + /** + *

+ * Enable/disable reduced logcat output + *

+ *

+ * Note that this is a global setting + *

+ * + * @param useMinimal true for reduced output, false for full output + * @return This Builder object for method chaining + */ + public Builder setMinimalLogging(boolean useMinimal) { + Debug.setLogTypeEnabled(Debug.LOG_COMMAND | Debug.LOG_OUTPUT, !useMinimal); + return this; + } + + /** + * Construct a {@link Interactive} instance, and start the shell + */ + public Interactive open() { + return new Interactive(this, null); + } + + /** + * Construct a {@link Interactive} instance, try to start the + * shell, and call onCommandResultListener to report success or failure + * + * @param onCommandResultListener Callback to return shell open status + */ + public Interactive open(OnCommandResultListener onCommandResultListener) { + return new Interactive(this, onCommandResultListener); + } + } + + /** + *

+ * An interactive shell - initially created with {@link Builder} - + * that executes blocks of commands you supply in the background, optionally + * calling callbacks as each block completes. + *

+ *

+ * STDERR output can be supplied as well, but due to compatibility with + * older Android versions, wantSTDERR is not implemented using + * redirectErrorStream, but rather appended to the output. STDOUT and STDERR + * are thus not guaranteed to be in the correct order in the output. + *

+ *

+ * Note as well that the close() and waitForIdle() methods will + * intentionally crash when run in debug mode from the main thread of the + * application. Any blocking call should be run from a background thread. + *

+ *

+ * When in debug mode, the code will also excessively log the commands + * passed to and the output returned from the shell. + *

+ *

+ * Though this function uses background threads to gobble STDOUT and STDERR + * so a deadlock does not occur if the shell produces massive output, the + * output is still stored in a List<String>, and as such doing + * something like 'ls -lR /' will probably have you run out of + * memory when using a {@link OnCommandResultListener}. A work-around + * is to not supply this callback, but using (only) + * {@link Builder#setOnSTDOUTLineListener(OnLineListener)}. This way, + * an internal buffer will not be created and wasting your memory. + *

+ *

Callbacks, threads and handlers

+ *

+ * On which thread the callbacks execute is dependent on your + * initialization. You can supply a custom Handler using + * {@link Builder#setHandler(Handler)} if needed. If you do not supply + * a custom Handler - unless you set + * {@link Builder#setAutoHandler(boolean)} to false - a Handler will + * be auto-created if the thread used for instantiation of the object has a + * Looper. + *

+ *

+ * If no Handler was supplied and it was also not auto-created, all + * callbacks will be called from either the STDOUT or STDERR gobbler + * threads. These are important threads that should be blocked as little as + * possible, as blocking them may in rare cases pause the native process or + * even create a deadlock. + *

+ *

+ * The main thread must certainly have a Looper, thus if you call + * {@link Builder#open()} from the main thread, a handler will (by + * default) be auto-created, and all the callbacks will be called on the + * main thread. While this is often convenient and easy to code with, you + * should be aware that if your callbacks are 'expensive' to execute, this + * may negatively impact UI performance. + *

+ *

+ * Background threads usually do not have a Looper, so calling + * {@link Builder#open()} from such a background thread will (by + * default) result in all the callbacks being executed in one of the gobbler + * threads. You will have to make sure the code you execute in these + * callbacks is thread-safe. + *

+ */ + public static class Interactive { + private final Handler handler; + private final boolean autoHandler; + private final String shell; + private final boolean wantSTDERR; + private final List commands; + private final Map environment; + private final OnLineListener onSTDOUTLineListener; + private final OnLineListener onSTDERRLineListener; + private int watchdogTimeout; + + private Process process = null; + private DataOutputStream STDIN = null; + private StreamGobbler STDOUT = null; + private StreamGobbler STDERR = null; + private ScheduledThreadPoolExecutor watchdog = null; + + private volatile boolean running = false; + private volatile boolean idle = true; // read/write only synchronized + private volatile boolean closed = true; + private volatile int callbacks = 0; + private volatile int watchdogCount; + + private final Object idleSync = new Object(); + private final Object callbackSync = new Object(); + + private volatile int lastExitCode = 0; + private volatile String lastMarkerSTDOUT = null; + private volatile String lastMarkerSTDERR = null; + private volatile Command command = null; + private volatile List buffer = null; + + /** + * The only way to create an instance: Shell.Builder::open() + * + * @param builder Builder class to take values from + */ + private Interactive(final Builder builder, + final OnCommandResultListener onCommandResultListener) { + autoHandler = builder.autoHandler; + shell = builder.shell; + wantSTDERR = builder.wantSTDERR; + commands = builder.commands; + environment = builder.environment; + onSTDOUTLineListener = builder.onSTDOUTLineListener; + onSTDERRLineListener = builder.onSTDERRLineListener; + watchdogTimeout = builder.watchdogTimeout; + + // If a looper is available, we offload the callbacks from the + // gobbling threads + // to whichever thread created us. Would normally do this in open(), + // but then we could not declare handler as final + if ((Looper.myLooper() != null) && (builder.handler == null) && autoHandler) { + handler = new Handler(); + } else { + handler = builder.handler; + } + + if (onCommandResultListener != null) { + // Allow up to 60 seconds for SuperSU/Superuser dialog, then enable + // the user-specified timeout for all subsequent operations + watchdogTimeout = 60; + commands.add(0, new Command(Shell.availableTestCommands, 0, new OnCommandResultListener() { + public void onCommandResult(int commandCode, int exitCode, List output) { + if ((exitCode == OnCommandResultListener.SHELL_RUNNING) && + !Shell.parseAvailableResult(output, SU.isSU(shell))) { + // shell is up, but it's brain-damaged + exitCode = OnCommandResultListener.SHELL_WRONG_UID; + } + watchdogTimeout = builder.watchdogTimeout; + onCommandResultListener.onCommandResult(0, exitCode, output); + } + }, null)); + } + + if (!open() && (onCommandResultListener != null)) { + onCommandResultListener.onCommandResult(0, + OnCommandResultListener.SHELL_EXEC_FAILED, null); + } + } + + @Override + protected void finalize() throws Throwable { + if (!closed && Debug.getSanityChecksEnabledEffective()) { + // waste of resources + Debug.log(ShellNotClosedException.EXCEPTION_NOT_CLOSED); + throw new ShellNotClosedException(); + } + super.finalize(); + } + + /** + * Add a command to execute + * + * @param command Command to execute + */ + public void addCommand(String command) { + addCommand(command, 0, (OnCommandResultListener) null); + } + + /** + *

+ * Add a command to execute, with a callback to be called on completion + *

+ *

+ * The thread on which the callback executes is dependent on various + * factors, see {@link Interactive} for further details + *

+ * + * @param command Command to execute + * @param code User-defined value passed back to the callback + * @param onCommandResultListener Callback to be called on completion + */ + public void addCommand(String command, int code, + OnCommandResultListener onCommandResultListener) { + addCommand(new String[] { + command + }, code, onCommandResultListener); + } + + /** + *

+ * Add a command to execute, with a callback. This callback gobbles the + * output line by line without buffering it and also returns the result + * code on completion. + *

+ *

+ * The thread on which the callback executes is dependent on various + * factors, see {@link Interactive} for further details + *

+ * + * @param command Command to execute + * @param code User-defined value passed back to the callback + * @param onCommandLineListener Callback + */ + public void addCommand(String command, int code, OnCommandLineListener onCommandLineListener) { + addCommand(new String[] { + command + }, code, onCommandLineListener); + } + + /** + * Add commands to execute + * + * @param commands Commands to execute + */ + public void addCommand(List commands) { + addCommand(commands, 0, (OnCommandResultListener) null); + } + + /** + *

+ * Add commands to execute, with a callback to be called on completion + * (of all commands) + *

+ *

+ * The thread on which the callback executes is dependent on various + * factors, see {@link Interactive} for further details + *

+ * + * @param commands Commands to execute + * @param code User-defined value passed back to the callback + * @param onCommandResultListener Callback to be called on completion + * (of all commands) + */ + public void addCommand(List commands, int code, + OnCommandResultListener onCommandResultListener) { + addCommand(commands.toArray(new String[commands.size()]), code, onCommandResultListener); + } + + /** + *

+ * Add commands to execute, with a callback. This callback gobbles the + * output line by line without buffering it and also returns the result + * code on completion. + *

+ *

+ * The thread on which the callback executes is dependent on various + * factors, see {@link Interactive} for further details + *

+ * + * @param commands Commands to execute + * @param code User-defined value passed back to the callback + * @param onCommandLineListener Callback + */ + public void addCommand(List commands, int code, + OnCommandLineListener onCommandLineListener) { + addCommand(commands.toArray(new String[commands.size()]), code, onCommandLineListener); + } + + /** + * Add commands to execute + * + * @param commands Commands to execute + */ + public void addCommand(String[] commands) { + addCommand(commands, 0, (OnCommandResultListener) null); + } + + /** + *

+ * Add commands to execute, with a callback to be called on completion + * (of all commands) + *

+ *

+ * The thread on which the callback executes is dependent on various + * factors, see {@link Interactive} for further details + *

+ * + * @param commands Commands to execute + * @param code User-defined value passed back to the callback + * @param onCommandResultListener Callback to be called on completion + * (of all commands) + */ + public synchronized void addCommand(String[] commands, int code, + OnCommandResultListener onCommandResultListener) { + this.commands.add(new Command(commands, code, onCommandResultListener, null)); + runNextCommand(); + } + + /** + *

+ * Add commands to execute, with a callback. This callback gobbles the + * output line by line without buffering it and also returns the result + * code on completion. + *

+ *

+ * The thread on which the callback executes is dependent on various + * factors, see {@link Interactive} for further details + *

+ * + * @param commands Commands to execute + * @param code User-defined value passed back to the callback + * @param onCommandLineListener Callback + */ + public synchronized void addCommand(String[] commands, int code, + OnCommandLineListener onCommandLineListener) { + this.commands.add(new Command(commands, code, null, onCommandLineListener)); + runNextCommand(); + } + + /** + * Run the next command if any and if ready, signals idle state if no + * commands left + */ + private void runNextCommand() { + runNextCommand(true); + } + + /** + * Called from a ScheduledThreadPoolExecutor timer thread every second + * when there is an outstanding command + */ + private synchronized void handleWatchdog() { + final int exitCode; + + if (watchdog == null) + return; + if (watchdogTimeout == 0) + return; + + if (!isRunning()) { + exitCode = OnCommandResultListener.SHELL_DIED; + Debug.log(String.format("[%s%%] SHELL_DIED", shell.toUpperCase(Locale.ENGLISH))); + } else if (watchdogCount++ < watchdogTimeout) { + return; + } else { + exitCode = OnCommandResultListener.WATCHDOG_EXIT; + Debug.log(String.format("[%s%%] WATCHDOG_EXIT", shell.toUpperCase(Locale.ENGLISH))); + } + + if (handler != null) { + postCallback(command, exitCode, buffer); + } + + // prevent multiple callbacks for the same command + command = null; + buffer = null; + idle = true; + + watchdog.shutdown(); + watchdog = null; + kill(); + } + + /** + * Start the periodic timer when a command is submitted + */ + private void startWatchdog() { + if (watchdogTimeout == 0) { + return; + } + watchdogCount = 0; + watchdog = new ScheduledThreadPoolExecutor(1); + watchdog.scheduleAtFixedRate(new Runnable() { + @Override + public void run() { + handleWatchdog(); + } + }, 1, 1, TimeUnit.SECONDS); + } + + /** + * Disable the watchdog timer upon command completion + */ + private void stopWatchdog() { + if (watchdog != null) { + watchdog.shutdownNow(); + watchdog = null; + } + } + + /** + * Run the next command if any and if ready + * + * @param notifyIdle signals idle state if no commands left ? + */ + private void runNextCommand(boolean notifyIdle) { + // must always be called from a synchronized method + + boolean running = isRunning(); + if (!running) + idle = true; + + if (running && idle && (commands.size() > 0)) { + Command command = commands.get(0); + commands.remove(0); + + buffer = null; + lastExitCode = 0; + lastMarkerSTDOUT = null; + lastMarkerSTDERR = null; + + if (command.commands.length > 0) { + try { + if (command.onCommandResultListener != null) { + // no reason to store the output if we don't have an + // OnCommandResultListener + // user should catch the output with an + // OnLineListener in this case + buffer = Collections.synchronizedList(new ArrayList()); + } + + idle = false; + this.command = command; + startWatchdog(); + for (String write : command.commands) { + Debug.logCommand(String.format("[%s+] %s", + shell.toUpperCase(Locale.ENGLISH), write)); + STDIN.write((write + "\n").getBytes("UTF-8")); + } + STDIN.write(("echo " + command.marker + " $?\n").getBytes("UTF-8")); + STDIN.write(("echo " + command.marker + " >&2\n").getBytes("UTF-8")); + STDIN.flush(); + } catch (IOException e) { + // STDIN might have closed + } + } else { + runNextCommand(false); + } + } else if (!running) { + // our shell died for unknown reasons - abort all submissions + while (commands.size() > 0) { + postCallback(commands.remove(0), OnCommandResultListener.SHELL_DIED, null); + } + } + + if (idle && notifyIdle) { + synchronized (idleSync) { + idleSync.notifyAll(); + } + } + } + + /** + * Processes a STDOUT/STDERR line containing an end/exitCode marker + */ + private synchronized void processMarker() { + if (command.marker.equals(lastMarkerSTDOUT) + && (command.marker.equals(lastMarkerSTDERR))) { + postCallback(command, lastExitCode, buffer); + stopWatchdog(); + command = null; + buffer = null; + idle = true; + runNextCommand(); + } + } + + /** + * Process a normal STDOUT/STDERR line + * + * @param line Line to process + * @param listener Callback to call or null + */ + private synchronized void processLine(String line, OnLineListener listener) { + if (listener != null) { + if (handler != null) { + final String fLine = line; + final OnLineListener fListener = listener; + + startCallback(); + handler.post(new Runnable() { + @Override + public void run() { + try { + fListener.onLine(fLine); + } finally { + endCallback(); + } + } + }); + } else { + listener.onLine(line); + } + } + } + + /** + * Add line to internal buffer + * + * @param line Line to add + */ + private synchronized void addBuffer(String line) { + if (buffer != null) { + buffer.add(line); + } + } + + /** + * Increase callback counter + */ + private void startCallback() { + synchronized (callbackSync) { + callbacks++; + } + } + + /** + * Schedule a callback to run on the appropriate thread + */ + private void postCallback(final Command fCommand, final int fExitCode, + final List fOutput) { + if (fCommand.onCommandResultListener == null && fCommand.onCommandLineListener == null) { + return; + } + if (handler == null) { + if ((fCommand.onCommandResultListener != null) && (fOutput != null)) + fCommand.onCommandResultListener.onCommandResult(fCommand.code, fExitCode, + fOutput); + if (fCommand.onCommandLineListener != null) + fCommand.onCommandLineListener.onCommandResult(fCommand.code, fExitCode); + return; + } + startCallback(); + handler.post(new Runnable() { + @Override + public void run() { + try { + if ((fCommand.onCommandResultListener != null) && (fOutput != null)) + fCommand.onCommandResultListener.onCommandResult(fCommand.code, + fExitCode, fOutput); + if (fCommand.onCommandLineListener != null) + fCommand.onCommandLineListener + .onCommandResult(fCommand.code, fExitCode); + } finally { + endCallback(); + } + } + }); + } + + /** + * Decrease callback counter, signals callback complete state when + * dropped to 0 + */ + private void endCallback() { + synchronized (callbackSync) { + callbacks--; + if (callbacks == 0) { + callbackSync.notifyAll(); + } + } + } + + /** + * Internal call that launches the shell, starts gobbling, and starts + * executing commands. See {@link Interactive} + * + * @return Opened successfully ? + */ + private synchronized boolean open() { + Debug.log(String.format("[%s%%] START", shell.toUpperCase(Locale.ENGLISH))); + + try { + // setup our process, retrieve STDIN stream, and STDOUT/STDERR + // gobblers + if (environment.size() == 0) { + process = Runtime.getRuntime().exec(shell); + } else { + Map newEnvironment = new HashMap(); + newEnvironment.putAll(System.getenv()); + newEnvironment.putAll(environment); + int i = 0; + String[] env = new String[newEnvironment.size()]; + for (Map.Entry entry : newEnvironment.entrySet()) { + env[i] = entry.getKey() + "=" + entry.getValue(); + i++; + } + process = Runtime.getRuntime().exec(shell, env); + } + + STDIN = new DataOutputStream(process.getOutputStream()); + STDOUT = new StreamGobbler(shell.toUpperCase(Locale.ENGLISH) + "-", + process.getInputStream(), new OnLineListener() { + @Override + public void onLine(String line) { + synchronized (Interactive.this) { + if (command == null) { + return; + } + if (line.startsWith(command.marker)) { + try { + lastExitCode = Integer.valueOf( + line.substring(command.marker.length() + 1), 10); + } catch (Exception e) { + // this really shouldn't happen + e.printStackTrace(); + } + lastMarkerSTDOUT = command.marker; + processMarker(); + } else { + addBuffer(line); + processLine(line, onSTDOUTLineListener); + processLine(line, command.onCommandLineListener); + } + } + } + }); + STDERR = new StreamGobbler(shell.toUpperCase(Locale.ENGLISH) + "*", + process.getErrorStream(), new OnLineListener() { + @Override + public void onLine(String line) { + synchronized (Interactive.this) { + if (command == null) { + return; + } + if (line.startsWith(command.marker)) { + lastMarkerSTDERR = command.marker; + processMarker(); + } else { + if (wantSTDERR) + addBuffer(line); + processLine(line, onSTDERRLineListener); + } + } + } + }); + + // start gobbling and write our commands to the shell + STDOUT.start(); + STDERR.start(); + + running = true; + closed = false; + + runNextCommand(); + + return true; + } catch (IOException e) { + // shell probably not found + return false; + } + } + + /** + * Close shell and clean up all resources. Call this when you are done + * with the shell. If the shell is not idle (all commands completed) you + * should not call this method from the main UI thread because it may + * block for a long time. This method will intentionally crash your app + * (if in debug mode) if you try to do this anyway. + */ + public void close() { + boolean _idle = isIdle(); // idle must be checked synchronized + + synchronized (this) { + if (!running) + return; + running = false; + closed = true; + } + + // This method should not be called from the main thread unless the + // shell is idle and can be cleaned up with (minimal) waiting. Only + // throw in debug mode. + if (!_idle && Debug.getSanityChecksEnabledEffective() && Debug.onMainThread()) { + Debug.log(ShellOnMainThreadException.EXCEPTION_NOT_IDLE); + throw new ShellOnMainThreadException(ShellOnMainThreadException.EXCEPTION_NOT_IDLE); + } + + if (!_idle) + waitForIdle(); + + try { + try { + STDIN.write(("exit\n").getBytes("UTF-8")); + STDIN.flush(); + } catch (IOException e) { + if (e.getMessage().contains("EPIPE")) { + // we're not running a shell, the shell closed STDIN, + // the script already contained the exit command, etc. + } else { + throw e; + } + } + + // wait for our process to finish, while we gobble away in the + // background + process.waitFor(); + + // make sure our threads are done gobbling, our streams are + // closed, and the process is destroyed - while the latter two + // shouldn't be needed in theory, and may even produce warnings, + // in "normal" Java they are required for guaranteed cleanup of + // resources, so lets be safe and do this on Android as well + try { + STDIN.close(); + } catch (IOException e) { + // STDIN going missing is no reason to abort + } + STDOUT.join(); + STDERR.join(); + stopWatchdog(); + process.destroy(); + } catch (IOException e) { + // various unforseen IO errors may still occur + } catch (InterruptedException e) { + // this should really be re-thrown + } + + Debug.log(String.format("[%s%%] END", shell.toUpperCase(Locale.ENGLISH))); + } + + /** + * Try to clean up as much as possible from a shell that's gotten itself + * wedged. Hopefully the StreamGobblers will croak on their own when the + * other side of the pipe is closed. + */ + public synchronized void kill() { + running = false; + closed = true; + + try { + STDIN.close(); + } catch (IOException e) { + // in case it was closed + } + try { + process.destroy(); + } catch (Exception e) { + // in case it was already destroyed or can't be + } + } + + /** + * Is our shell still running ? + * + * @return Shell running ? + */ + public boolean isRunning() { + if (process == null) { + return false; + } + try { + process.exitValue(); + return false; + } catch (IllegalThreadStateException e) { + // if this is thrown, we're still running + } + return true; + } + + /** + * Have all commands completed executing ? + * + * @return Shell idle ? + */ + public synchronized boolean isIdle() { + if (!isRunning()) { + idle = true; + synchronized (idleSync) { + idleSync.notifyAll(); + } + } + return idle; + } + + /** + *

+ * Wait for idle state. As this is a blocking call, you should not call + * it from the main UI thread. If you do so and debug mode is enabled, + * this method will intentionally crash your app. + *

+ *

+ * If not interrupted, this method will not return until all commands + * have finished executing. Note that this does not necessarily mean + * that all the callbacks have fired yet. + *

+ *

+ * If no Handler is used, all callbacks will have been executed when + * this method returns. If a Handler is used, and this method is called + * from a different thread than associated with the Handler's Looper, + * all callbacks will have been executed when this method returns as + * well. If however a Handler is used but this method is called from the + * same thread as associated with the Handler's Looper, there is no way + * to know. + *

+ *

+ * In practice this means that in most simple cases all callbacks will + * have completed when this method returns, but if you actually depend + * on this behavior, you should make certain this is indeed the case. + *

+ *

+ * See {@link Interactive} for further details on threading and + * handlers + *

+ * + * @return True if wait complete, false if wait interrupted + */ + public boolean waitForIdle() { + if (Debug.getSanityChecksEnabledEffective() && Debug.onMainThread()) { + Debug.log(ShellOnMainThreadException.EXCEPTION_WAIT_IDLE); + throw new ShellOnMainThreadException(ShellOnMainThreadException.EXCEPTION_WAIT_IDLE); + } + + if (isRunning()) { + synchronized (idleSync) { + while (!idle) { + try { + idleSync.wait(); + } catch (InterruptedException e) { + return false; + } + } + } + + if ((handler != null) && + (handler.getLooper() != null) && + (handler.getLooper() != Looper.myLooper())) { + // If the callbacks are posted to a different thread than + // this one, we can wait until all callbacks have called + // before returning. If we don't use a Handler at all, the + // callbacks are already called before we get here. If we do + // use a Handler but we use the same Looper, waiting here + // would actually block the callbacks from being called + + synchronized (callbackSync) { + while (callbacks > 0) { + try { + callbackSync.wait(); + } catch (InterruptedException e) { + return false; + } + } + } + } + } + + return true; + } + + /** + * Are we using a Handler to post callbacks ? + * + * @return Handler used ? + */ + public boolean hasHandler() { + return (handler != null); + } + } +} diff --git a/app/src/main/java/eu/chainfire/libsuperuser/ShellNotClosedException.java b/app/src/main/java/eu/chainfire/libsuperuser/ShellNotClosedException.java new file mode 100644 index 0000000..2a44083 --- /dev/null +++ b/app/src/main/java/eu/chainfire/libsuperuser/ShellNotClosedException.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2012-2014 Jorrit "Chainfire" Jongma + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package eu.chainfire.libsuperuser; + +/** + * Exception class used to notify developer that a shell was not close()d + */ +@SuppressWarnings("serial") +public class ShellNotClosedException extends RuntimeException { + public static final String EXCEPTION_NOT_CLOSED = "Application did not close() interactive shell"; + + public ShellNotClosedException() { + super(EXCEPTION_NOT_CLOSED); + } +} diff --git a/app/src/main/java/eu/chainfire/libsuperuser/ShellOnMainThreadException.java b/app/src/main/java/eu/chainfire/libsuperuser/ShellOnMainThreadException.java new file mode 100644 index 0000000..69732e3 --- /dev/null +++ b/app/src/main/java/eu/chainfire/libsuperuser/ShellOnMainThreadException.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2012-2014 Jorrit "Chainfire" Jongma + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package eu.chainfire.libsuperuser; + +/** + * Exception class used to crash application when shell commands are executed + * from the main thread, and we are in debug mode. + */ +@SuppressWarnings("serial") +public class ShellOnMainThreadException extends RuntimeException { + public static final String EXCEPTION_COMMAND = "Application attempted to run a shell command from the main thread"; + public static final String EXCEPTION_NOT_IDLE = "Application attempted to wait for a non-idle shell to close on the main thread"; + public static final String EXCEPTION_WAIT_IDLE = "Application attempted to wait for a shell to become idle on the main thread"; + + public ShellOnMainThreadException(String message) { + super(message); + } +} diff --git a/app/src/main/java/eu/chainfire/libsuperuser/StreamGobbler.java b/app/src/main/java/eu/chainfire/libsuperuser/StreamGobbler.java new file mode 100644 index 0000000..4ce6697 --- /dev/null +++ b/app/src/main/java/eu/chainfire/libsuperuser/StreamGobbler.java @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2012-2014 Jorrit "Chainfire" Jongma + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package eu.chainfire.libsuperuser; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.List; + +/** + * Thread utility class continuously reading from an InputStream + */ +public class StreamGobbler extends Thread { + /** + * Line callback interface + */ + public interface OnLineListener { + /** + *

Line callback

+ * + *

This callback should process the line as quickly as possible. + * Delays in this callback may pause the native process or even + * result in a deadlock

+ * + * @param line String that was gobbled + */ + void onLine(String line); + } + + private String shell = null; + private BufferedReader reader = null; + private List writer = null; + private OnLineListener listener = null; + + /** + *

StreamGobbler constructor

+ * + *

We use this class because shell STDOUT and STDERR should be read as quickly as + * possible to prevent a deadlock from occurring, or Process.waitFor() never + * returning (as the buffer is full, pausing the native process)

+ * + * @param shell Name of the shell + * @param inputStream InputStream to read from + * @param outputList List to write to, or null + */ + public StreamGobbler(String shell, InputStream inputStream, List outputList) { + this.shell = shell; + reader = new BufferedReader(new InputStreamReader(inputStream)); + writer = outputList; + } + + /** + *

StreamGobbler constructor

+ * + *

We use this class because shell STDOUT and STDERR should be read as quickly as + * possible to prevent a deadlock from occurring, or Process.waitFor() never + * returning (as the buffer is full, pausing the native process)

+ * + * @param shell Name of the shell + * @param inputStream InputStream to read from + * @param onLineListener OnLineListener callback + */ + public StreamGobbler(String shell, InputStream inputStream, OnLineListener onLineListener) { + this.shell = shell; + reader = new BufferedReader(new InputStreamReader(inputStream)); + listener = onLineListener; + } + + @Override + public void run() { + // keep reading the InputStream until it ends (or an error occurs) + try { + String line; + while ((line = reader.readLine()) != null) { + Debug.logOutput(String.format("[%s] %s", shell, line)); + if (writer != null) writer.add(line); + if (listener != null) listener.onLine(line); + } + } catch (IOException e) { + // reader probably closed, expected exit condition + } + + // make sure our stream is closed and resources will be freed + try { + reader.close(); + } catch (IOException e) { + // read already closed + } + } +} diff --git a/app/src/main/res/drawable-hdpi/activitydetection.png b/app/src/main/res/drawable-hdpi/activitydetection.png new file mode 100644 index 0000000..e1ca6ab Binary files /dev/null and b/app/src/main/res/drawable-hdpi/activitydetection.png differ diff --git a/app/src/main/res/drawable-hdpi/alarm.png b/app/src/main/res/drawable-hdpi/alarm.png new file mode 100644 index 0000000..ef7a244 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/alarm.png differ diff --git a/app/src/main/res/drawable-hdpi/battery.png b/app/src/main/res/drawable-hdpi/battery.png new file mode 100644 index 0000000..7c2f50a Binary files /dev/null and b/app/src/main/res/drawable-hdpi/battery.png differ diff --git a/app/src/main/res/drawable-hdpi/bluetooth.png b/app/src/main/res/drawable-hdpi/bluetooth.png new file mode 100644 index 0000000..34ca5fc Binary files /dev/null and b/app/src/main/res/drawable-hdpi/bluetooth.png differ diff --git a/app/src/main/res/drawable-hdpi/brightness.png b/app/src/main/res/drawable-hdpi/brightness.png new file mode 100644 index 0000000..d6fd6dc Binary files /dev/null and b/app/src/main/res/drawable-hdpi/brightness.png differ diff --git a/app/src/main/res/drawable-hdpi/compass.png b/app/src/main/res/drawable-hdpi/compass.png new file mode 100644 index 0000000..dfd280f Binary files /dev/null and b/app/src/main/res/drawable-hdpi/compass.png differ diff --git a/app/src/main/res/drawable-hdpi/compass_small.png b/app/src/main/res/drawable-hdpi/compass_small.png new file mode 100644 index 0000000..5ef15b6 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/compass_small.png differ diff --git a/app/src/main/res/drawable-hdpi/contacts.png b/app/src/main/res/drawable-hdpi/contacts.png new file mode 100644 index 0000000..1eaa428 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/contacts.png differ diff --git a/app/src/main/res/drawable-hdpi/dataconnection.png b/app/src/main/res/drawable-hdpi/dataconnection.png new file mode 100644 index 0000000..b4fd0e5 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/dataconnection.png differ diff --git a/app/src/main/res/drawable-hdpi/displayrotation.png b/app/src/main/res/drawable-hdpi/displayrotation.png new file mode 100644 index 0000000..1298396 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/displayrotation.png differ diff --git a/app/src/main/res/drawable-hdpi/ear.png b/app/src/main/res/drawable-hdpi/ear.png new file mode 100644 index 0000000..916b101 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ear.png differ diff --git a/app/src/main/res/drawable-hdpi/gear.png b/app/src/main/res/drawable-hdpi/gear.png new file mode 100644 index 0000000..8689dc0 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/gear.png differ diff --git a/app/src/main/res/drawable-hdpi/gears.png b/app/src/main/res/drawable-hdpi/gears.png new file mode 100644 index 0000000..acbcc4d Binary files /dev/null and b/app/src/main/res/drawable-hdpi/gears.png differ diff --git a/app/src/main/res/drawable-hdpi/headphone.png b/app/src/main/res/drawable-hdpi/headphone.png new file mode 100644 index 0000000..d1f6b10 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/headphone.png differ diff --git a/app/src/main/res/drawable-hdpi/help.png b/app/src/main/res/drawable-hdpi/help.png new file mode 100644 index 0000000..69926ff Binary files /dev/null and b/app/src/main/res/drawable-hdpi/help.png differ diff --git a/app/src/main/res/drawable-hdpi/home.png b/app/src/main/res/drawable-hdpi/home.png new file mode 100644 index 0000000..02b0007 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/home.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_action_search.png b/app/src/main/res/drawable-hdpi/ic_action_search.png new file mode 100644 index 0000000..67de12d Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_action_search.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_launcher.png b/app/src/main/res/drawable-hdpi/ic_launcher.png new file mode 100644 index 0000000..b6bbc16 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_launcher.png differ diff --git a/app/src/main/res/drawable-hdpi/icon_overview_tab.xml b/app/src/main/res/drawable-hdpi/icon_overview_tab.xml new file mode 100644 index 0000000..1a9541c --- /dev/null +++ b/app/src/main/res/drawable-hdpi/icon_overview_tab.xml @@ -0,0 +1,8 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-hdpi/icon_poi_tab.xml b/app/src/main/res/drawable-hdpi/icon_poi_tab.xml new file mode 100644 index 0000000..8cbb2d3 --- /dev/null +++ b/app/src/main/res/drawable-hdpi/icon_poi_tab.xml @@ -0,0 +1,8 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-hdpi/icon_rules_tab.xml b/app/src/main/res/drawable-hdpi/icon_rules_tab.xml new file mode 100644 index 0000000..3beb26d --- /dev/null +++ b/app/src/main/res/drawable-hdpi/icon_rules_tab.xml @@ -0,0 +1,8 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-hdpi/map.png b/app/src/main/res/drawable-hdpi/map.png new file mode 100644 index 0000000..1e7e97e Binary files /dev/null and b/app/src/main/res/drawable-hdpi/map.png differ diff --git a/app/src/main/res/drawable-hdpi/message.png b/app/src/main/res/drawable-hdpi/message.png new file mode 100644 index 0000000..3b51571 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/message.png differ diff --git a/app/src/main/res/drawable-hdpi/nfc.png b/app/src/main/res/drawable-hdpi/nfc.png new file mode 100644 index 0000000..74ac9d6 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/nfc.png differ diff --git a/app/src/main/res/drawable-hdpi/phone.png b/app/src/main/res/drawable-hdpi/phone.png new file mode 100644 index 0000000..66d705a Binary files /dev/null and b/app/src/main/res/drawable-hdpi/phone.png differ diff --git a/app/src/main/res/drawable-hdpi/placeholder.png b/app/src/main/res/drawable-hdpi/placeholder.png new file mode 100644 index 0000000..5db1e40 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/placeholder.png differ diff --git a/app/src/main/res/drawable-hdpi/plane.png b/app/src/main/res/drawable-hdpi/plane.png new file mode 100644 index 0000000..a8050a4 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/plane.png differ diff --git a/app/src/main/res/drawable-hdpi/power.png b/app/src/main/res/drawable-hdpi/power.png new file mode 100644 index 0000000..a0beb90 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/power.png differ diff --git a/app/src/main/res/drawable-hdpi/roaming.png b/app/src/main/res/drawable-hdpi/roaming.png new file mode 100644 index 0000000..99b4422 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/roaming.png differ diff --git a/app/src/main/res/drawable-hdpi/router.png b/app/src/main/res/drawable-hdpi/router.png new file mode 100644 index 0000000..f1122e2 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/router.png differ diff --git a/app/src/main/res/drawable-hdpi/rule.png b/app/src/main/res/drawable-hdpi/rule.png new file mode 100644 index 0000000..1f5f83a Binary files /dev/null and b/app/src/main/res/drawable-hdpi/rule.png differ diff --git a/app/src/main/res/drawable-hdpi/sound.png b/app/src/main/res/drawable-hdpi/sound.png new file mode 100644 index 0000000..f6f4ac3 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/sound.png differ diff --git a/app/src/main/res/drawable-hdpi/speed.png b/app/src/main/res/drawable-hdpi/speed.png new file mode 100644 index 0000000..4e13c1a Binary files /dev/null and b/app/src/main/res/drawable-hdpi/speed.png differ diff --git a/app/src/main/res/drawable-hdpi/speedlimit.png b/app/src/main/res/drawable-hdpi/speedlimit.png new file mode 100644 index 0000000..f3e4616 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/speedlimit.png differ diff --git a/app/src/main/res/drawable-hdpi/startprogram.png b/app/src/main/res/drawable-hdpi/startprogram.png new file mode 100644 index 0000000..e00a0e2 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/startprogram.png differ diff --git a/app/src/main/res/drawable-hdpi/status_active.png b/app/src/main/res/drawable-hdpi/status_active.png new file mode 100644 index 0000000..d4e9b4c Binary files /dev/null and b/app/src/main/res/drawable-hdpi/status_active.png differ diff --git a/app/src/main/res/drawable-hdpi/status_inactive.png b/app/src/main/res/drawable-hdpi/status_inactive.png new file mode 100644 index 0000000..c1d0cfc Binary files /dev/null and b/app/src/main/res/drawable-hdpi/status_inactive.png differ diff --git a/app/src/main/res/drawable-hdpi/status_unable.png b/app/src/main/res/drawable-hdpi/status_unable.png new file mode 100644 index 0000000..0397872 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/status_unable.png differ diff --git a/app/src/main/res/drawable-hdpi/talking.png b/app/src/main/res/drawable-hdpi/talking.png new file mode 100644 index 0000000..e486e8b Binary files /dev/null and b/app/src/main/res/drawable-hdpi/talking.png differ diff --git a/app/src/main/res/drawable-hdpi/triggerurl.png b/app/src/main/res/drawable-hdpi/triggerurl.png new file mode 100644 index 0000000..07c796d Binary files /dev/null and b/app/src/main/res/drawable-hdpi/triggerurl.png differ diff --git a/app/src/main/res/drawable-hdpi/tune.png b/app/src/main/res/drawable-hdpi/tune.png new file mode 100644 index 0000000..4759ec3 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/tune.png differ diff --git a/app/src/main/res/drawable-hdpi/usb.png b/app/src/main/res/drawable-hdpi/usb.png new file mode 100644 index 0000000..c3d8f5a Binary files /dev/null and b/app/src/main/res/drawable-hdpi/usb.png differ diff --git a/app/src/main/res/drawable-hdpi/wait.png b/app/src/main/res/drawable-hdpi/wait.png new file mode 100644 index 0000000..e20e14a Binary files /dev/null and b/app/src/main/res/drawable-hdpi/wait.png differ diff --git a/app/src/main/res/drawable-hdpi/wifi.png b/app/src/main/res/drawable-hdpi/wifi.png new file mode 100644 index 0000000..01bf028 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/wifi.png differ diff --git a/app/src/main/res/drawable-ldpi/gears.png b/app/src/main/res/drawable-ldpi/gears.png new file mode 100644 index 0000000..cc2dd79 Binary files /dev/null and b/app/src/main/res/drawable-ldpi/gears.png differ diff --git a/app/src/main/res/drawable-ldpi/ic_launcher.png b/app/src/main/res/drawable-ldpi/ic_launcher.png new file mode 100644 index 0000000..36354a4 Binary files /dev/null and b/app/src/main/res/drawable-ldpi/ic_launcher.png differ diff --git a/app/src/main/res/drawable-mdpi/cancel.png b/app/src/main/res/drawable-mdpi/cancel.png new file mode 100644 index 0000000..8f141bd Binary files /dev/null and b/app/src/main/res/drawable-mdpi/cancel.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_action_search.png b/app/src/main/res/drawable-mdpi/ic_action_search.png new file mode 100644 index 0000000..134d549 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_action_search.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_launcher.png b/app/src/main/res/drawable-mdpi/ic_launcher.png new file mode 100644 index 0000000..64e48c1 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_launcher.png differ diff --git a/app/src/main/res/drawable-mdpi/priority.png b/app/src/main/res/drawable-mdpi/priority.png new file mode 100644 index 0000000..58b3852 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/priority.png differ diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 0000000..2b068d1 --- /dev/null +++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-xhdpi/ic_action_search.png b/app/src/main/res/drawable-xhdpi/ic_action_search.png new file mode 100644 index 0000000..d699c6b Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_action_search.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_launcher.png b/app/src/main/res/drawable-xhdpi/ic_launcher.png new file mode 100644 index 0000000..28bf15a Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_launcher.png differ diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..07d5da9 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/action_start_activity.xml b/app/src/main/res/layout/action_start_activity.xml new file mode 100644 index 0000000..f3f7296 --- /dev/null +++ b/app/src/main/res/layout/action_start_activity.xml @@ -0,0 +1,117 @@ + + + + + + + +