├── .github └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── .gitignore ├── LICENSE ├── README.md ├── V.png ├── app-debug.apk ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── main │ ├── AndroidManifest.xml │ ├── assets │ │ ├── help-de.md │ │ └── help.md │ ├── ic_launcher-web.png │ ├── java │ │ └── de │ │ │ └── vier_bier │ │ │ └── habpanelviewer │ │ │ ├── AdminReceiver.java │ │ │ ├── AppRestartingExceptionHandler.java │ │ │ ├── ClientWebView.java │ │ │ ├── Constants.java │ │ │ ├── EmptyActivity.java │ │ │ ├── IntroActivity.java │ │ │ ├── LogActivity.java │ │ │ ├── MainActivity.java │ │ │ ├── NetworkTracker.java │ │ │ ├── PermissionUtil.java │ │ │ ├── PermissionValidatingActivity.java │ │ │ ├── ResourcesUtil.java │ │ │ ├── ScreenCapturer.java │ │ │ ├── ScreenControllingActivity.java │ │ │ ├── StartActivity.java │ │ │ ├── TrackShutdownService.java │ │ │ ├── UiUtil.java │ │ │ ├── command │ │ │ ├── AdminHandler.java │ │ │ ├── BluetoothHandler.java │ │ │ ├── Command.java │ │ │ ├── CommandQueue.java │ │ │ ├── FlashHandler.java │ │ │ ├── ICommandHandler.java │ │ │ ├── InternalCommandHandler.java │ │ │ ├── NotificationHandler.java │ │ │ ├── ScreenHandler.java │ │ │ ├── TtsHandler.java │ │ │ ├── VolumeHandler.java │ │ │ ├── WebViewHandler.java │ │ │ └── log │ │ │ │ ├── CommandLog.java │ │ │ │ ├── CommandLogActivity.java │ │ │ │ └── CommandLogClient.java │ │ │ ├── connection │ │ │ ├── ConnectionStatistics.java │ │ │ ├── OkHttpClientFactory.java │ │ │ └── ssl │ │ │ │ ├── CertificateManager.java │ │ │ │ └── LocalTrustManager.java │ │ │ ├── db │ │ │ ├── AppDatabase.java │ │ │ ├── Credential.java │ │ │ ├── CredentialDao.java │ │ │ └── CredentialManager.java │ │ │ ├── help │ │ │ └── HelpActivity.java │ │ │ ├── openhab │ │ │ ├── FutureState.java │ │ │ ├── ISseConnectionListener.java │ │ │ ├── ISseListener.java │ │ │ ├── IStateUpdateListener.java │ │ │ ├── ISubscriptionListener.java │ │ │ ├── IUrlListener.java │ │ │ ├── ItemState.java │ │ │ ├── OpenhabSseConnection.java │ │ │ ├── RestClient.java │ │ │ ├── ServerConnection.java │ │ │ ├── ServerDiscovery.java │ │ │ ├── SseConnection.java │ │ │ └── average │ │ │ │ ├── Average.java │ │ │ │ ├── AveragePropagator.java │ │ │ │ ├── FloatAverage.java │ │ │ │ ├── IStatePropagator.java │ │ │ │ └── Sample.java │ │ │ ├── preferences │ │ │ ├── AutocompleteTextPreference.java │ │ │ ├── ItemValidator.java │ │ │ ├── PreferenceActivity.java │ │ │ ├── PreferenceCallback.java │ │ │ ├── PreferenceFragment.java │ │ │ ├── PreferenceUtil.java │ │ │ ├── PreferencesBrowser.java │ │ │ ├── PreferencesCamera.java │ │ │ ├── PreferencesConnected.java │ │ │ ├── PreferencesConnection.java │ │ │ ├── PreferencesMain.java │ │ │ ├── PreferencesMotion.java │ │ │ ├── PreferencesNoiseLevel.java │ │ │ ├── PreferencesOther.java │ │ │ ├── PreferencesReporting.java │ │ │ └── PreferencesUi.java │ │ │ ├── reporting │ │ │ ├── AbstractAveragingDeviceMonitor.java │ │ │ ├── AbstractDeviceMonitor.java │ │ │ ├── AccelerometerMonitor.java │ │ │ ├── BatteryMonitor.java │ │ │ ├── BrightnessMonitor.java │ │ │ ├── ConnectedIndicator.java │ │ │ ├── DockingStateMonitor.java │ │ │ ├── IDeviceMonitor.java │ │ │ ├── NoiseLevelMonitor.java │ │ │ ├── PressureMonitor.java │ │ │ ├── ProximityMonitor.java │ │ │ ├── ScreenMonitor.java │ │ │ ├── SensorMissingException.java │ │ │ ├── TemperatureMonitor.java │ │ │ ├── VolumeMonitor.java │ │ │ └── motion │ │ │ │ ├── AbstractCameraImpl.java │ │ │ │ ├── Camera.java │ │ │ │ ├── CameraException.java │ │ │ │ ├── CameraImplNone.java │ │ │ │ ├── CameraImplV1.java │ │ │ │ ├── CameraImplV2.java │ │ │ │ ├── Comparer.java │ │ │ │ ├── ICamera.java │ │ │ │ ├── IMotionDetector.java │ │ │ │ ├── IMotionListener.java │ │ │ │ ├── LumaData.java │ │ │ │ ├── MotionDetector.java │ │ │ │ ├── MotionReporter.java │ │ │ │ └── MotionVisualizer.java │ │ │ └── status │ │ │ ├── ApplicationStatus.java │ │ │ ├── StatusInfoActivity.java │ │ │ └── StatusItem.java │ └── res │ │ ├── drawable-mdpi │ │ ├── book.png │ │ ├── browser.png │ │ ├── commanding.png │ │ ├── configuration.png │ │ ├── credentials.png │ │ ├── database.png │ │ ├── logo.png │ │ ├── ready.png │ │ ├── reporting.png │ │ └── server.png │ │ ├── drawable │ │ ├── activity_main_border.xml │ │ ├── ic_access_time_black_24dp.xml │ │ ├── ic_arrow_upward_black_24dp.xml │ │ ├── ic_baseline_enhanced_encryption_24.xml │ │ ├── ic_clear_all_black_24dp.xml │ │ ├── ic_delete_black_24dp.xml │ │ ├── ic_forum_black_24dp.xml │ │ ├── ic_help_outline_black_24dp.xml │ │ ├── ic_info_outline_black_24dp.xml │ │ ├── ic_play_arrow_black_24dp.xml │ │ ├── ic_preferences_black_24dp.xml │ │ ├── ic_replay_black_24dp.xml │ │ ├── ic_search_black_24dp.xml │ │ ├── ic_share_black_24dp.xml │ │ ├── ic_view_headline_black_24dp.xml │ │ ├── ic_vpn_key_black_24dp.xml │ │ ├── ic_wiki_black_24dp.xml │ │ └── side_nav_bar.xml │ │ ├── layout │ │ ├── activity_commandlog.xml │ │ ├── activity_empty.xml │ │ ├── activity_help.xml │ │ ├── activity_intro_discover.xml │ │ ├── activity_log.xml │ │ ├── activity_main.xml │ │ ├── activity_main_content.xml │ │ ├── activity_preferences.xml │ │ ├── activity_start.xml │ │ ├── activity_statusinfo.xml │ │ ├── dialog_credentials.xml │ │ ├── dialog_master_password.xml │ │ ├── dialog_scrollable_text.xml │ │ ├── navheader_main.xml │ │ ├── row_commandlog.xml │ │ └── toolbar.xml │ │ ├── menu │ │ ├── activity_main_drawer.xml │ │ ├── commandlog_toolbar_menu.xml │ │ ├── help_toolbar_menu.xml │ │ ├── log_menu.xml │ │ ├── preferences_toolbar_menu.xml │ │ └── webview_context_menu.xml │ │ ├── mipmap-anydpi-v26 │ │ └── ic_launcher.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_foreground.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_foreground.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_foreground.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_foreground.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_foreground.png │ │ ├── raw-de │ │ └── releasenotes.txt │ │ ├── raw │ │ ├── mytruststore.bks │ │ └── releasenotes.txt │ │ ├── values-de │ │ ├── arrays.xml │ │ └── strings.xml │ │ ├── values │ │ ├── arrays.xml │ │ ├── dimens.xml │ │ ├── ic_launcher_background.xml │ │ └── strings.xml │ │ └── xml │ │ ├── device_admin.xml │ │ ├── network_security_config.xml │ │ ├── preferences_accelerometer.xml │ │ ├── preferences_battery.xml │ │ ├── preferences_brightness.xml │ │ ├── preferences_browser.xml │ │ ├── preferences_camera.xml │ │ ├── preferences_connected.xml │ │ ├── preferences_connection.xml │ │ ├── preferences_docking_state.xml │ │ ├── preferences_main.xml │ │ ├── preferences_motion.xml │ │ ├── preferences_noise_level.xml │ │ ├── preferences_other.xml │ │ ├── preferences_pressure.xml │ │ ├── preferences_proximity.xml │ │ ├── preferences_reporting.xml │ │ ├── preferences_restart.xml │ │ ├── preferences_screen.xml │ │ ├── preferences_temperature.xml │ │ ├── preferences_ui.xml │ │ ├── preferences_usage.xml │ │ └── preferences_volume.xml │ └── test │ └── java │ └── de │ └── vier_bier │ └── habpanelviewer │ └── openhab │ ├── OpenhabSseConnectionTest.java │ └── SseConnectionTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── habpanelviewer.iml ├── issue_template.md ├── logo.xcf ├── logo2.xcf └── settings.gradle /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: vbier 7 | 8 | --- 9 | 10 | **Before reporting bugs, make sure you have searched for your problem in the openHAB community thread at: https://community.openhab.org/t/habpanelviewer-0-9-25/34112** 11 | 12 | **Describe the bug** 13 | A clear and concise description of what the bug is. 14 | 15 | **To Reproduce** 16 | Steps to reproduce the behavior: 17 | 1. Go to '...' 18 | 2. Click on '....' 19 | 3. Scroll down to '....' 20 | 4. See error 21 | 22 | **Expected behavior** 23 | A clear and concise description of what you expected to happen. 24 | 25 | **Screenshots** 26 | If applicable, add screenshots to help explain your problem. 27 | 28 | **Smartphone/Tablet (please complete the following information):** 29 | - Device: [e.g. iPhone6] 30 | - OS: [e.g. Android 4.5] 31 | - Version [e.g. 0.9.24] 32 | 33 | **Additional context** 34 | Add any other context about the problem here. 35 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: vbier 7 | 8 | --- 9 | 10 | **Before asking for a feature, make sure you have searched the openHAB community thread at: https://community.openhab.org/t/habpanelviewer-0-9-25/34112. Check if the requested feature was already discussed there.** 11 | 12 | **Is your feature request related to a problem? Please describe.** 13 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 14 | 15 | **Describe the solution you'd like** 16 | A clear and concise description of what you want to happen. 17 | 18 | **Describe alternatives you've considered** 19 | A clear and concise description of any alternative solutions or features you've considered. 20 | 21 | **Additional context** 22 | Add any other context or screenshots about the feature request here. 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .gradle 3 | build 4 | local.properties 5 | app/src/test/java/de/vier_bier/habpanelviewer/openhab/PrivateSseConnectionTest.java 6 | app/src/test/resources/keystore2 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Logo HABPanelViewer 2 | 3 | #### At the end of 2021, I have stopped maintenance and development of the app. 4 | 5 | An openHAB integrated kiosk browser. 6 | 7 | #### Version 0.9.26 is the last version to work with openHAB 2. 8 | #### 0.9.27 works with openHAB 3 as default, but can be changed to openHAB 2 in the preferences. 9 | 10 | 11 | [Get it on F-Droid](https://f-droid.org/packages/de.vier_bier.habpanelviewer/) 14 | 15 | Main features: 16 | - **mDNS server discovery**: finds the openHAB server on your network by using mDNS discovery 17 | - **launcher functionality**: the app can be set as launcher, thus starting automatically with the tablet 18 | - **app shortcut**: allows to start an app from the main menu 19 | - **device control**: allows to control different device features via an openHAB item 20 | - **motion detection**: reports motion to openHAB 21 | - **device sensor value reporting**: reports device sensor values to openHAB 22 | 23 | The app does support android from SDK version 19, which means it will run on devices running Android 4.4+ (Kitkat). 24 | 25 | For further details see the app's help file. 26 | 27 | The app uses the following libraries: 28 | - **com.github.heremaps:oksse** licensed under the Apache License 2.0: https://www.apache.org/licenses/LICENSE-2.0 29 | - **com.squareup.okhttp3:okhttp** licensed under the Apache License 2.0: https://www.apache.org/licenses/LICENSE-2.0 30 | - **com.burgstaller:okhttp-digest** licensed under the Apache License 2.0: https://www.apache.org/licenses/LICENSE-2.0 31 | - **com.jakewharton:process-phoenix** licensed under the Apache License 2.0: https://www.apache.org/licenses/LICENSE-2.0 32 | - **org.greenrobot:eventbus** licensed under the Apache License 2.0: https://www.apache.org/licenses/LICENSE-2.0 33 | - **com.github.mukeshsolanki:MarkdownView-Android** licensed under the Apache License 2.0: https://www.apache.org/licenses/LICENSE-2.0 34 | - **com.github.apl-devs:appintro** licensed under the Apache License 2.0: https://www.apache.org/licenses/LICENSE-2.0 35 | - **com.github.hedzr:android-file-chooser** licensed under the Apache License 2.0: https://www.apache.org/licenses/LICENSE-2.0 36 | 37 | The app uses code from the following projects: 38 | - LogActivity is based on the LogActivity from the openHAB client for Android (https://github.com/openhab/openhab-android) 39 | 40 | The app uses following icons: 41 | - Icons made by Webalys Freebies from www.flaticon.com is licensed by CC 3.0 BY 42 | - Icons made by Freepik from www.flaticon.com is licensed by CC 3.0 BY 43 | - Icons made by itim2101 from www.flaticon.com is licensed by CC 3.0 BY 44 | - Icons made by Lucy G from www.flaticon.com is licensed by CC 3.0 BY -------------------------------------------------------------------------------- /V.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbier/habpanelviewer/0c5756752e45562fc880fd096a8c67a703f83382/V.png -------------------------------------------------------------------------------- /app-debug.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbier/habpanelviewer/0c5756752e45562fc880fd096a8c67a703f83382/app-debug.apk -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | app.iml 3 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 29 5 | defaultConfig { 6 | applicationId "de.vier_bier.habpanelviewer" 7 | minSdkVersion 21 8 | targetSdkVersion 29 9 | versionCode 10 10 | versionName "0.9.27" 11 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 12 | multiDexEnabled true 13 | buildConfigField "long", "TIMESTAMP", System.currentTimeMillis() + "L" 14 | } 15 | buildTypes { 16 | release { 17 | minifyEnabled false 18 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 19 | } 20 | } 21 | compileOptions { 22 | sourceCompatibility JavaVersion.VERSION_1_8 23 | targetCompatibility JavaVersion.VERSION_1_8 24 | } 25 | lintOptions { 26 | abortOnError false 27 | } 28 | testOptions { 29 | unitTests.returnDefaultValues = true 30 | } 31 | } 32 | 33 | gradle.projectsEvaluated { 34 | tasks.withType(JavaCompile) { 35 | //options.compilerArgs << "-Xlint:unchecked" << "-Xlint:deprecation" 36 | } 37 | } 38 | 39 | dependencies { 40 | androidTestImplementation('androidx.test.espresso:espresso-core:3.1.0-beta02', { 41 | exclude group: 'com.android.support', module: 'support-annotations' 42 | }) 43 | 44 | def room_version = "2.3.0" 45 | 46 | implementation "androidx.room:room-runtime:$room_version" 47 | annotationProcessor "androidx.room:room-compiler:$room_version" 48 | 49 | implementation "net.zetetic:android-database-sqlcipher:4.4.2" 50 | implementation "androidx.sqlite:sqlite:2.1.0" 51 | 52 | implementation 'androidx.constraintlayout:constraintlayout:2.1.0' 53 | implementation "androidx.legacy:legacy-support-v13:1.0.0" 54 | 55 | implementation 'androidx.multidex:multidex:2.0.1' 56 | implementation 'com.google.android.material:material:1.4.0' 57 | 58 | implementation 'com.squareup.okhttp3:okhttp-sse:4.7.2' 59 | implementation 'com.squareup.okhttp3:okhttp:4.7.2' 60 | implementation 'io.github.rburgst:okhttp-digest:2.5' 61 | 62 | //noinspection GradleDependency,InvalidPackage 63 | implementation 'com.github.mukeshsolanki:MarkdownView-Android:1.1.1' 64 | implementation 'org.greenrobot:eventbus:3.2.0' 65 | implementation 'com.jakewharton:process-phoenix:2.0.0' 66 | implementation 'com.github.AppIntro:AppIntro:6.1.0' 67 | implementation 'com.github.hedzr:android-file-chooser:v1.2.0-final' 68 | 69 | testImplementation 'junit:junit:4.12' 70 | testImplementation 'com.github.webbit:webbit:0.4.19' 71 | testImplementation 'io.netty:netty:3.6.5.Final' 72 | testImplementation "androidx.arch.core:core-testing:2.1.0" 73 | } 74 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /home/volla/Android/Sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | 19 | # Uncomment this to preserve the line number information for 20 | # debugging stack traces. 21 | #-keepattributes SourceFile,LineNumberTable 22 | 23 | # If you keep the line number information, uncomment this to 24 | # hide the original source file name. 25 | #-renamesourcefileattribute SourceFile 26 | -------------------------------------------------------------------------------- /app/src/main/ic_launcher-web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbier/habpanelviewer/0c5756752e45562fc880fd096a8c67a703f83382/app/src/main/ic_launcher-web.png -------------------------------------------------------------------------------- /app/src/main/java/de/vier_bier/habpanelviewer/AdminReceiver.java: -------------------------------------------------------------------------------- 1 | package de.vier_bier.habpanelviewer; 2 | 3 | import android.app.admin.DeviceAdminReceiver; 4 | import android.content.ComponentName; 5 | 6 | /** 7 | * Specific implementation of a DeviceAdminReceiver. 8 | */ 9 | public class AdminReceiver extends DeviceAdminReceiver { 10 | public static final ComponentName COMP = 11 | new ComponentName("de.vier_bier.habpanelviewer", "de.vier_bier.habpanelviewer.AdminReceiver"); 12 | 13 | public AdminReceiver() { 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /app/src/main/java/de/vier_bier/habpanelviewer/EmptyActivity.java: -------------------------------------------------------------------------------- 1 | package de.vier_bier.habpanelviewer; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.content.Intent; 5 | import android.os.Bundle; 6 | import android.os.Handler; 7 | import android.os.Looper; 8 | import android.view.View; 9 | import android.view.WindowManager; 10 | 11 | import org.greenrobot.eventbus.EventBus; 12 | 13 | public class EmptyActivity extends ScreenControllingActivity { 14 | @SuppressLint("ClickableViewAccessibility") 15 | @Override 16 | protected void onCreate(Bundle savedInstanceState) { 17 | super.onCreate(savedInstanceState); 18 | 19 | boolean dim = getIntent().getExtras() != null && getIntent().getExtras().getBoolean(Constants.INTENT_FLAG_DIM); 20 | if (dim) { 21 | setContentView(R.layout.activity_empty); 22 | 23 | View view = findViewById(R.id.blankView); 24 | view.setSystemUiVisibility( 25 | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION 26 | | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN 27 | | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION 28 | | View.SYSTEM_UI_FLAG_FULLSCREEN 29 | | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY); 30 | 31 | view.setOnTouchListener((view1, motionEvent) -> { 32 | finish(); 33 | EventBus.getDefault().post(new Constants.LoadStartUrl()); 34 | return true; 35 | }); 36 | 37 | new Handler(Looper.getMainLooper()).postDelayed(() -> { 38 | final WindowManager.LayoutParams layout = getWindow().getAttributes(); 39 | layout.screenBrightness = 0f; 40 | getWindow().setAttributes(layout); 41 | }, 500); 42 | } else { 43 | EventBus.getDefault().post(new Constants.LoadStartUrl()); 44 | finish(); 45 | } 46 | } 47 | 48 | @Override 49 | public View getScreenOnView() { 50 | return findViewById(R.id.blankView); 51 | } 52 | 53 | @Override 54 | protected void onNewIntent(Intent intent) { 55 | super.onNewIntent(intent); 56 | 57 | boolean dim = intent.getExtras() != null && intent.getExtras().getBoolean(Constants.INTENT_FLAG_DIM); 58 | if (!dim) { 59 | finish(); 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /app/src/main/java/de/vier_bier/habpanelviewer/NetworkTracker.java: -------------------------------------------------------------------------------- 1 | package de.vier_bier.habpanelviewer; 2 | 3 | import android.content.BroadcastReceiver; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | import android.content.IntentFilter; 7 | import android.net.ConnectivityManager; 8 | import android.net.NetworkInfo; 9 | import android.util.Log; 10 | 11 | import java.util.ArrayList; 12 | 13 | public class NetworkTracker extends BroadcastReceiver { 14 | private static final String TAG = "HPV-NetworkTracker"; 15 | 16 | private final ConnectivityManager cm; 17 | private final ArrayList mListeners = new ArrayList<>(); 18 | private boolean mConnected; 19 | 20 | public NetworkTracker(Context context) { 21 | cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); 22 | 23 | final IntentFilter intentFilter = new IntentFilter(); 24 | intentFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION); 25 | 26 | Log.d(TAG, "registering network receiver..."); 27 | context.registerReceiver(this, intentFilter); 28 | 29 | updateStatus(); 30 | } 31 | 32 | public void addListener(INetworkListener l) { 33 | synchronized (mListeners) { 34 | mListeners.add(l); 35 | 36 | if (isConnected()) { 37 | l.connected(); 38 | } else { 39 | l.disconnected(); 40 | } 41 | } 42 | } 43 | 44 | public void removeListener(INetworkListener l) { 45 | synchronized (mListeners) { 46 | mListeners.remove(l); 47 | } 48 | } 49 | 50 | public boolean isConnected() { 51 | return mConnected; 52 | } 53 | 54 | @Override 55 | public void onReceive(Context context, Intent intent) { 56 | Log.d(TAG, "network broadcast received"); 57 | updateStatus(); 58 | } 59 | 60 | private void updateStatus() { 61 | NetworkInfo activeNetwork = cm == null ? null : cm.getActiveNetworkInfo(); 62 | 63 | if (activeNetwork != null && activeNetwork.isConnected()) { 64 | Log.d(TAG, "network is active: " + activeNetwork); 65 | 66 | if (!mConnected) { 67 | synchronized (mListeners) { 68 | mConnected = true; 69 | Log.d(TAG, "notifying listeners of active network..."); 70 | for (INetworkListener listener : mListeners) { 71 | listener.connected(); 72 | } 73 | } 74 | } 75 | } else { 76 | Log.d(TAG, "network is NOT active: " + activeNetwork); 77 | 78 | if (mConnected) { 79 | synchronized (mListeners) { 80 | mConnected = false; 81 | Log.d(TAG, "notifying listeners of inactive network..."); 82 | for (INetworkListener listener : mListeners) { 83 | listener.disconnected(); 84 | } 85 | } 86 | } 87 | } 88 | } 89 | 90 | public void terminate(Context context) { 91 | Log.d(TAG, "unregistering network receiver..."); 92 | try { 93 | context.unregisterReceiver(this); 94 | } catch (IllegalArgumentException e) { 95 | Log.e(TAG, "unregistering network receiver failed", e); 96 | } 97 | 98 | synchronized (mListeners) { 99 | mListeners.clear(); 100 | } 101 | } 102 | 103 | public interface INetworkListener { 104 | void disconnected(); 105 | 106 | void connected(); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /app/src/main/java/de/vier_bier/habpanelviewer/PermissionUtil.java: -------------------------------------------------------------------------------- 1 | package de.vier_bier.habpanelviewer; 2 | 3 | import android.Manifest; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | import android.content.SharedPreferences; 7 | import android.content.pm.PackageManager; 8 | import android.preference.PreferenceManager; 9 | 10 | import androidx.core.content.ContextCompat; 11 | 12 | import java.util.ArrayList; 13 | import java.util.HashMap; 14 | 15 | public class PermissionUtil { 16 | private static final HashMap PERMISSIONS = new HashMap<>(); 17 | 18 | static { 19 | PERMISSIONS.put(Manifest.permission.RECORD_AUDIO, new String[]{Constants.PREF_ALLOW_WEBRTC, Constants.PREF_NOISE_LEVEL_ENABLED}); 20 | PERMISSIONS.put(Manifest.permission.CAMERA, new String[]{Constants.PREF_ALLOW_WEBRTC, 21 | Constants.PREF_MOTION_DETECTION_ENABLED, Constants.PREF_MOTION_DETECTION_PREVIEW}); 22 | } 23 | 24 | public static String[] getMissingPermissions(Context context) { 25 | final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); 26 | ArrayList missing = new ArrayList<>(); 27 | 28 | for (String permission : PERMISSIONS.keySet()) { 29 | if (ContextCompat.checkSelfPermission(context, permission) != PackageManager.PERMISSION_GRANTED) { 30 | String[] prefKeys = PERMISSIONS.get(permission); 31 | 32 | boolean prefEnabled = false; 33 | for (String key : prefKeys) { 34 | prefEnabled = prefEnabled || prefs.getBoolean(key, false); 35 | } 36 | 37 | if (prefEnabled) { 38 | missing.add(permission); 39 | } 40 | } 41 | } 42 | 43 | return missing.toArray(new String[0]); 44 | } 45 | 46 | public static String[] getDependingPreferences(String permission) { 47 | return PERMISSIONS.get(permission); 48 | } 49 | 50 | public static Intent createRequestPermissionsIntent(Context context) { 51 | Intent mainIntent = new Intent(context, PermissionValidatingActivity.class); 52 | String[] missingPermissions = getMissingPermissions(context); 53 | 54 | if (missingPermissions.length > 0) { 55 | mainIntent.putExtra(Constants.INTENT_FLAG_PERMISSIONS, missingPermissions); 56 | return mainIntent; 57 | } 58 | 59 | return null; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /app/src/main/java/de/vier_bier/habpanelviewer/PermissionValidatingActivity.java: -------------------------------------------------------------------------------- 1 | package de.vier_bier.habpanelviewer; 2 | 3 | import android.app.Activity; 4 | import android.content.SharedPreferences; 5 | import android.content.pm.PackageManager; 6 | import android.os.Bundle; 7 | import android.preference.PreferenceManager; 8 | 9 | import androidx.annotation.NonNull; 10 | import androidx.core.app.ActivityCompat; 11 | 12 | public class PermissionValidatingActivity extends Activity { 13 | String[] mMissingPerms; 14 | 15 | @Override 16 | protected void onCreate(final Bundle savedInstanceState) { 17 | super.onCreate(null); 18 | 19 | mMissingPerms = getIntent().getStringArrayExtra(Constants.INTENT_FLAG_PERMISSIONS); 20 | 21 | if (mMissingPerms.length == 0) { 22 | setResult(Activity.RESULT_OK); 23 | finish(); 24 | } else { 25 | ActivityCompat.requestPermissions(this, mMissingPerms, Constants.REQUEST_VALIDATE); 26 | } 27 | } 28 | 29 | @Override 30 | public void onRequestPermissionsResult(int requestCode, 31 | @NonNull String[] permissions, @NonNull int[] grantResults) { 32 | if (requestCode == Constants.REQUEST_VALIDATE) { 33 | final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getBaseContext()); 34 | boolean permissionsDisabled = false; 35 | 36 | int idx = 0; 37 | SharedPreferences.Editor editor1 = prefs.edit(); 38 | for (String permission : mMissingPerms) { 39 | if (grantResults[idx] != PackageManager.PERMISSION_GRANTED) { 40 | 41 | String[] prefKeys = PermissionUtil.getDependingPreferences(permission); 42 | for (String key : prefKeys) { 43 | editor1.putBoolean(key, false); 44 | } 45 | permissionsDisabled = true; 46 | } 47 | 48 | idx++; 49 | } 50 | editor1.apply(); 51 | 52 | setResult(permissionsDisabled ? Activity.RESULT_CANCELED : Activity.RESULT_OK); 53 | finish(); 54 | } 55 | 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /app/src/main/java/de/vier_bier/habpanelviewer/ResourcesUtil.java: -------------------------------------------------------------------------------- 1 | package de.vier_bier.habpanelviewer; 2 | 3 | import android.content.Context; 4 | import androidx.annotation.NonNull; 5 | 6 | import java.io.BufferedReader; 7 | import java.io.IOException; 8 | import java.io.InputStream; 9 | import java.io.InputStreamReader; 10 | 11 | /** 12 | * Utility methods for reading resources. 13 | */ 14 | class ResourcesUtil { 15 | @NonNull 16 | private static String readRawTextFile(Context ctx, int resId) { 17 | InputStream inputStream = ctx.getResources().openRawResource(resId); 18 | 19 | InputStreamReader inputreader = new InputStreamReader(inputStream); 20 | BufferedReader buffreader = new BufferedReader(inputreader); 21 | String line; 22 | StringBuilder text = new StringBuilder(); 23 | 24 | try { 25 | while ((line = buffreader.readLine()) != null) { 26 | text.append(line); 27 | text.append('\n'); 28 | } 29 | } catch (IOException e) { 30 | return ""; 31 | } 32 | 33 | return text.toString(); 34 | } 35 | 36 | static String fetchReleaseNotes(Context ctx, String lastVersion) { 37 | String text = readRawTextFile(ctx, R.raw.releasenotes); 38 | 39 | final int pos = text.indexOf(lastVersion); 40 | 41 | if (pos != -1) { 42 | text = text.substring(0, pos).trim(); 43 | } 44 | 45 | return text; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /app/src/main/java/de/vier_bier/habpanelviewer/ScreenCapturer.java: -------------------------------------------------------------------------------- 1 | package de.vier_bier.habpanelviewer; 2 | 3 | import android.graphics.Bitmap; 4 | import android.graphics.PixelFormat; 5 | import android.hardware.display.DisplayManager; 6 | import android.hardware.display.VirtualDisplay; 7 | import android.media.Image; 8 | import android.media.ImageReader; 9 | import android.media.projection.MediaProjection; 10 | import android.os.Handler; 11 | 12 | import java.nio.ByteBuffer; 13 | import java.util.concurrent.CountDownLatch; 14 | import java.util.concurrent.TimeUnit; 15 | import java.util.concurrent.atomic.AtomicReference; 16 | 17 | /** 18 | * Captures screen shots 19 | */ 20 | public class ScreenCapturer { 21 | 22 | private final MediaProjection mProjection; 23 | private final Handler mHandler; 24 | private final int mWidth; 25 | private final int mHeight; 26 | private final int mDensity; 27 | 28 | ScreenCapturer(MediaProjection projection, int width, int height, int density) { 29 | mHandler = new Handler(); 30 | mProjection = projection; 31 | mWidth = width; 32 | mHeight = height; 33 | mDensity = density; 34 | } 35 | 36 | public synchronized Bitmap captureScreen() throws IllegalStateException { 37 | AtomicReference imageHolder = new AtomicReference<>(); 38 | final CountDownLatch latch = new CountDownLatch(1); 39 | 40 | ImageReader mImageReader = ImageReader.newInstance(mWidth, mHeight, PixelFormat.RGBA_8888, 2); 41 | mImageReader.setOnImageAvailableListener(imageReader -> { 42 | imageHolder.set(mImageReader.acquireLatestImage()); 43 | latch.countDown(); 44 | }, mHandler); 45 | 46 | VirtualDisplay display = mProjection.createVirtualDisplay("screen-mirror", mWidth, mHeight, mDensity, 47 | DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY | DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC, mImageReader.getSurface(), 48 | null, null); 49 | 50 | try { 51 | latch.await(1, TimeUnit.SECONDS); 52 | 53 | if (latch.getCount() == 1) { 54 | throw new IllegalStateException("Screen capturing timed out"); 55 | } 56 | 57 | final Image image = imageHolder.get(); 58 | Image.Plane[] planes = image.getPlanes(); 59 | ByteBuffer buffer = planes[0].getBuffer(); 60 | int pixelStride = planes[0].getPixelStride(); 61 | int rowStride = planes[0].getRowStride(); 62 | int rowPadding = rowStride - pixelStride * mWidth; 63 | 64 | // create bitmap 65 | Bitmap bmp = Bitmap.createBitmap(mWidth + rowPadding / pixelStride, mHeight, Bitmap.Config.ARGB_8888); 66 | bmp.copyPixelsFromBuffer(buffer); 67 | image.close(); 68 | 69 | return bmp; 70 | } catch (InterruptedException e) { 71 | throw new IllegalStateException("Got interrupt while capturing screen"); 72 | } finally { 73 | display.release(); 74 | } 75 | } 76 | 77 | public void terminate() { 78 | mProjection.stop(); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /app/src/main/java/de/vier_bier/habpanelviewer/TrackShutdownService.java: -------------------------------------------------------------------------------- 1 | package de.vier_bier.habpanelviewer; 2 | 3 | import android.app.Service; 4 | import android.content.Intent; 5 | import android.os.Binder; 6 | import android.os.IBinder; 7 | import android.util.Log; 8 | 9 | public class TrackShutdownService extends Service { 10 | private static final String TAG = "HPV-TrackShutdownServ"; 11 | private final IBinder binder = new LocalBinder(); 12 | 13 | @Override 14 | public IBinder onBind(Intent intent) { 15 | return binder; 16 | } 17 | 18 | @Override 19 | public int onStartCommand(Intent intent, int flags, int startId) { 20 | Log.d(TAG, "Service Started"); 21 | return START_STICKY; 22 | } 23 | 24 | @Override 25 | public void onDestroy() { 26 | Log.d(TAG, "Service Destroyed"); 27 | super.onDestroy(); 28 | } 29 | 30 | @Override 31 | public void onTaskRemoved(Intent rootIntent) { 32 | Log.e(TAG, "END"); 33 | stopSelf(); 34 | } 35 | 36 | /** 37 | * Class used for the client Binder. Because we know this service always 38 | * runs in the same process as its clients, we don't need to deal with IPC. 39 | */ 40 | class LocalBinder extends Binder { 41 | TrackShutdownService getService() { 42 | // Return this instance of LocalService so clients can call public methods 43 | return TrackShutdownService.this; 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /app/src/main/java/de/vier_bier/habpanelviewer/command/AdminHandler.java: -------------------------------------------------------------------------------- 1 | package de.vier_bier.habpanelviewer.command; 2 | 3 | import android.app.Activity; 4 | import android.app.admin.DevicePolicyManager; 5 | import android.content.Context; 6 | 7 | import de.vier_bier.habpanelviewer.AdminReceiver; 8 | 9 | /** 10 | * Handler for ADMIN_LOCK_SCREEN command. 11 | */ 12 | public class AdminHandler implements ICommandHandler { 13 | private final DevicePolicyManager mDPM; 14 | 15 | public AdminHandler(Activity activity) { 16 | mDPM = (DevicePolicyManager) activity.getSystemService(Context.DEVICE_POLICY_SERVICE); 17 | } 18 | 19 | @Override 20 | public boolean handleCommand(Command cmd) { 21 | final String cmdStr = cmd.getCommand(); 22 | 23 | if ("ADMIN_LOCK_SCREEN".equals(cmdStr)) { 24 | if (mDPM.isAdminActive(AdminReceiver.COMP)) { 25 | cmd.start(); 26 | mDPM.lockNow(); 27 | cmd.finished(); 28 | } else { 29 | cmd.failed("device admin privileges missing"); 30 | } 31 | return true; 32 | } 33 | 34 | return false; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /app/src/main/java/de/vier_bier/habpanelviewer/command/BluetoothHandler.java: -------------------------------------------------------------------------------- 1 | package de.vier_bier.habpanelviewer.command; 2 | 3 | import android.Manifest; 4 | import android.bluetooth.BluetoothManager; 5 | import android.content.Context; 6 | import android.content.pm.PackageManager; 7 | import androidx.core.content.ContextCompat; 8 | 9 | /** 10 | * Handles BLUETOOTH_ON and BLUETOOTH_OFF commands. 11 | */ 12 | public class BluetoothHandler implements ICommandHandler { 13 | private final Context mContext; 14 | private final BluetoothManager mManager; 15 | 16 | public BluetoothHandler(Context ctx, BluetoothManager manager) { 17 | mContext = ctx; 18 | mManager = manager; 19 | } 20 | 21 | @Override 22 | public boolean handleCommand(Command cmd) { 23 | final String cmdStr = cmd.getCommand(); 24 | 25 | if ("BLUETOOTH_ON".equals(cmdStr)) { 26 | if (ContextCompat.checkSelfPermission(mContext, Manifest.permission.BLUETOOTH_ADMIN) != PackageManager.PERMISSION_GRANTED) { 27 | cmd.failed("bluetooth permission missing"); 28 | } 29 | 30 | cmd.start(); 31 | mManager.getAdapter().enable(); 32 | } else if ("BLUETOOTH_OFF".equals(cmdStr)) { 33 | if (ContextCompat.checkSelfPermission(mContext, Manifest.permission.BLUETOOTH_ADMIN) != PackageManager.PERMISSION_GRANTED) { 34 | cmd.failed("bluetooth permission missing"); 35 | } 36 | 37 | cmd.start(); 38 | mManager.getAdapter().disable(); 39 | } else { 40 | return false; 41 | } 42 | 43 | cmd.finished(); 44 | return true; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /app/src/main/java/de/vier_bier/habpanelviewer/command/Command.java: -------------------------------------------------------------------------------- 1 | package de.vier_bier.habpanelviewer.command; 2 | 3 | import android.graphics.Color; 4 | 5 | /** 6 | * Info about an received command. 7 | */ 8 | public class Command { 9 | private final long mTime = System.currentTimeMillis(); 10 | private final String mCommand; 11 | 12 | private CommandStatus mStatus; 13 | private String mDetails; 14 | 15 | private boolean mShowDetails; 16 | 17 | public Command(String command) { 18 | mCommand = command; 19 | mStatus = CommandStatus.UNHANDLED; 20 | } 21 | 22 | public String getCommand() { 23 | return mCommand; 24 | } 25 | 26 | public long getTime() { 27 | return mTime; 28 | } 29 | 30 | public void failed(String errorMessage) { 31 | mDetails = errorMessage; 32 | mStatus = CommandStatus.FAILED; 33 | } 34 | 35 | public void start() { 36 | if (mStatus == CommandStatus.UNHANDLED) { 37 | setStatus(CommandStatus.EXECUTING); 38 | } 39 | } 40 | 41 | public void progress(String message) { 42 | if (mStatus == CommandStatus.EXECUTING) { 43 | if (mDetails == null) { 44 | mDetails = message; 45 | } else { 46 | mDetails = mDetails + "\n" + message; 47 | } 48 | } 49 | } 50 | 51 | public void finished() { 52 | if (mStatus == CommandStatus.EXECUTING) { 53 | setStatus(CommandStatus.OK); 54 | } 55 | } 56 | 57 | private void setStatus(CommandStatus status) { 58 | mStatus = status; 59 | } 60 | 61 | public String getDetails() { 62 | return mDetails; 63 | } 64 | 65 | public CommandStatus getStatus() { 66 | return mStatus; 67 | } 68 | 69 | public void toggleShowDetails() { 70 | mShowDetails = !mShowDetails; 71 | } 72 | 73 | public boolean hasVisibleDetails() { 74 | return mShowDetails && mDetails != null; 75 | } 76 | 77 | public enum CommandStatus { 78 | UNHANDLED { 79 | @Override 80 | public int getColor() { 81 | return Color.YELLOW; 82 | } 83 | }, EXECUTING { 84 | @Override 85 | public int getColor() { 86 | return Color.GRAY; 87 | } 88 | }, FAILED { 89 | @Override 90 | public int getColor() { 91 | return Color.RED; 92 | } 93 | }, OK { 94 | @Override 95 | public int getColor() { 96 | return Color.GREEN; 97 | } 98 | }; 99 | 100 | public abstract int getColor(); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /app/src/main/java/de/vier_bier/habpanelviewer/command/CommandQueue.java: -------------------------------------------------------------------------------- 1 | package de.vier_bier.habpanelviewer.command; 2 | 3 | import android.content.SharedPreferences; 4 | import android.os.Handler; 5 | import android.os.HandlerThread; 6 | import android.os.Looper; 7 | 8 | import org.greenrobot.eventbus.EventBus; 9 | import org.greenrobot.eventbus.Subscribe; 10 | import org.greenrobot.eventbus.ThreadMode; 11 | 12 | import java.util.ArrayList; 13 | 14 | import de.vier_bier.habpanelviewer.Constants; 15 | import de.vier_bier.habpanelviewer.command.log.CommandLog; 16 | import de.vier_bier.habpanelviewer.command.log.CommandLogClient; 17 | import de.vier_bier.habpanelviewer.openhab.IStateUpdateListener; 18 | import de.vier_bier.habpanelviewer.openhab.ServerConnection; 19 | 20 | /** 21 | * Queue for commands sent from openHAB. 22 | */ 23 | public class CommandQueue extends HandlerThread implements IStateUpdateListener { 24 | private final ServerConnection mServerConnection; 25 | 26 | private final Handler mUiHandler; 27 | private Handler mWorkerHandler; 28 | 29 | private final ArrayList mHandlers = new ArrayList<>(); 30 | private final CommandLog mCmdLog = new CommandLog(); 31 | 32 | public CommandQueue(ServerConnection serverConnection) { 33 | super("CommandQueue"); 34 | 35 | EventBus.getDefault().register(this); 36 | 37 | mServerConnection = serverConnection; 38 | mUiHandler = new Handler(Looper.getMainLooper()); 39 | 40 | start(); 41 | } 42 | 43 | @Subscribe(threadMode = ThreadMode.MAIN) 44 | public void onMessageEvent(CommandLogClient client) { 45 | client.setCommandLog(mCmdLog); 46 | } 47 | 48 | public void addHandler(ICommandHandler h) { 49 | synchronized (mHandlers) { 50 | if (!mHandlers.contains(h)) { 51 | mHandlers.add(h); 52 | } 53 | } 54 | } 55 | 56 | @Override 57 | protected void onLooperPrepared() { 58 | super.onLooperPrepared(); 59 | 60 | mWorkerHandler = new Handler(getLooper(), msg -> { 61 | if (msg.what == 12) { 62 | Command cmd = (Command) msg.obj; 63 | 64 | synchronized (mHandlers) { 65 | for (ICommandHandler mHandler : mHandlers) { 66 | try { 67 | if (mHandler.handleCommand(cmd)) { 68 | break; 69 | } 70 | } catch (Throwable t) { 71 | cmd.failed(t.getLocalizedMessage()); 72 | break; 73 | } 74 | } 75 | } 76 | } 77 | 78 | return true; 79 | }); 80 | } 81 | 82 | @Override 83 | public void itemUpdated(String name, String value) { 84 | if (value != null && !value.isEmpty()) { 85 | Command cmd = new Command(value); 86 | 87 | mUiHandler.post(() -> mCmdLog.add(cmd)); 88 | mWorkerHandler.obtainMessage(12, cmd).sendToTarget(); 89 | } 90 | } 91 | 92 | public void updateFromPreferences(final SharedPreferences prefs) { 93 | String mCmdItemName = prefs.getString(Constants.PREF_CMD_ITEM, ""); 94 | 95 | mUiHandler.post(() -> mCmdLog.setSize(prefs.getInt(Constants.PREF_CMD_LOG_SIZE, 100))); 96 | 97 | mServerConnection.subscribeCommandItem(this, mCmdItemName); 98 | } 99 | 100 | public void terminate() { 101 | EventBus.getDefault().unregister(this); 102 | quit(); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /app/src/main/java/de/vier_bier/habpanelviewer/command/ICommandHandler.java: -------------------------------------------------------------------------------- 1 | package de.vier_bier.habpanelviewer.command; 2 | 3 | /** 4 | * Handler for commands sent from openHAB. 5 | */ 6 | interface ICommandHandler { 7 | boolean handleCommand(Command cmd); 8 | } 9 | -------------------------------------------------------------------------------- /app/src/main/java/de/vier_bier/habpanelviewer/command/TtsHandler.java: -------------------------------------------------------------------------------- 1 | package de.vier_bier.habpanelviewer.command; 2 | 3 | import android.content.Context; 4 | import android.speech.tts.TextToSpeech; 5 | 6 | import java.util.Locale; 7 | import java.util.regex.Matcher; 8 | import java.util.regex.Pattern; 9 | 10 | public class TtsHandler implements ICommandHandler { 11 | private final Pattern TTS_PATTERN = Pattern.compile("TTS_(SPEAK|SET_LANG) (.*)"); 12 | 13 | private final Context mContext; 14 | 15 | private TextToSpeech mTTS; 16 | private int mStatus; 17 | 18 | public TtsHandler(Context ctx) { 19 | mContext = ctx; 20 | } 21 | 22 | @Override 23 | public boolean handleCommand(Command cmd) { 24 | final String cmdStr = cmd.getCommand(); 25 | 26 | Matcher m = TTS_PATTERN.matcher(cmdStr); 27 | if (m.matches()) { 28 | cmd.start(); 29 | 30 | final String ttsCmd = m.group(1); 31 | final String arg = m.group(2); 32 | 33 | if (mTTS == null) { 34 | mTTS = new TextToSpeech(mContext, i -> { 35 | mStatus = i; 36 | 37 | doHandleCommand(ttsCmd, arg, cmd); 38 | }); 39 | } else { 40 | doHandleCommand(ttsCmd, arg, cmd); 41 | } 42 | } 43 | 44 | return m.matches(); 45 | } 46 | 47 | private void doHandleCommand(String ttsCmd, String arg, Command cmd) { 48 | if (mStatus == TextToSpeech.SUCCESS) { 49 | if ("SPEAK".equalsIgnoreCase(ttsCmd)) { 50 | mTTS.speak(arg, TextToSpeech.QUEUE_ADD, null); 51 | } else { 52 | Locale l = new Locale(arg); 53 | 54 | if (mTTS.isLanguageAvailable(l) >= TextToSpeech.LANG_AVAILABLE) { 55 | mTTS.setLanguage(l); 56 | } else { 57 | cmd.failed("Given locale invalid: " + arg); 58 | } 59 | } 60 | cmd.finished(); 61 | } else { 62 | cmd.failed("Could not initialize TTS engine!"); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /app/src/main/java/de/vier_bier/habpanelviewer/command/VolumeHandler.java: -------------------------------------------------------------------------------- 1 | package de.vier_bier.habpanelviewer.command; 2 | 3 | import android.content.Context; 4 | import android.media.AudioManager; 5 | 6 | import java.util.regex.Matcher; 7 | import java.util.regex.Pattern; 8 | 9 | import de.vier_bier.habpanelviewer.R; 10 | 11 | /** 12 | * Handler for MUTE, UNMUTE, SET_VOLUME commands. 13 | */ 14 | public class VolumeHandler implements ICommandHandler { 15 | private final Pattern SET_PATTERN = Pattern.compile("SET_VOLUME ([0-9]+)"); 16 | 17 | private final Context mCtx; 18 | private final AudioManager mAudioManager; 19 | 20 | private final int mMaxVolume; 21 | private int mVolume = -1; 22 | 23 | public VolumeHandler(Context ctx, AudioManager audioManager) { 24 | mCtx = ctx; 25 | mAudioManager = audioManager; 26 | mMaxVolume = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC); 27 | } 28 | 29 | @Override 30 | public boolean handleCommand(Command cmd) { 31 | final String cmdStr = cmd.getCommand(); 32 | 33 | Matcher m = SET_PATTERN.matcher(cmdStr); 34 | 35 | if ("MUTE".equals(cmdStr)) { 36 | cmd.start(); 37 | mVolume = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC); 38 | mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, 0, AudioManager.FLAG_SHOW_UI); 39 | } else if ("UNMUTE".equals(cmdStr)) { 40 | if (mVolume == -1) { 41 | cmd.failed(mCtx.getString(R.string.device_not_muted)); 42 | } else { 43 | cmd.start(); 44 | mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, mVolume, AudioManager.FLAG_SHOW_UI); 45 | mVolume = -1; 46 | } 47 | } else if (m.matches()) { 48 | mVolume = -1; 49 | String value = m.group(1); 50 | 51 | try { 52 | int volume = Integer.parseInt(value); 53 | if (volume < 0) { 54 | volume = 0; 55 | } else if (volume > mMaxVolume) { 56 | volume = mMaxVolume; 57 | } 58 | 59 | cmd.start(); 60 | mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, volume, AudioManager.FLAG_SHOW_UI); 61 | } catch (NumberFormatException e) { 62 | cmd.failed("failed to parse volume from command"); 63 | } 64 | } else { 65 | return false; 66 | } 67 | 68 | cmd.finished(); 69 | return true; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /app/src/main/java/de/vier_bier/habpanelviewer/command/WebViewHandler.java: -------------------------------------------------------------------------------- 1 | package de.vier_bier.habpanelviewer.command; 2 | 3 | import java.util.regex.Matcher; 4 | import java.util.regex.Pattern; 5 | 6 | import de.vier_bier.habpanelviewer.ClientWebView; 7 | 8 | /** 9 | * Handler for SHOW_START_URL, SHOW_URL, SHOW_DASHBOARD, RELOAD command. 10 | */ 11 | public class WebViewHandler implements ICommandHandler { 12 | private final Pattern SHOW_PATTERN = Pattern.compile("SHOW_(URL|DASHBOARD) (.+)"); 13 | 14 | private final ClientWebView mWebView; 15 | 16 | public WebViewHandler(ClientWebView webView) { 17 | mWebView = webView; 18 | } 19 | 20 | @Override 21 | public boolean handleCommand(Command cmd) { 22 | final String cmdStr = cmd.getCommand(); 23 | 24 | final Matcher m = SHOW_PATTERN.matcher(cmdStr); 25 | if (m.matches()) { 26 | cmd.start(); 27 | final String type = m.group(1); 28 | 29 | mWebView.post(() -> { 30 | if ("URL".matches(type)) { 31 | mWebView.loadUrl(m.group(2)); 32 | } else { 33 | mWebView.loadDashboard(m.group(2)); 34 | } 35 | cmd.finished(); 36 | }); 37 | } else if ("SHOW_START_URL".equals(cmdStr)) { 38 | cmd.start(); 39 | mWebView.post(() -> { 40 | mWebView.loadStartUrl(); 41 | cmd.finished(); 42 | }); 43 | } else if ("RELOAD".equals(cmdStr)) { 44 | cmd.start(); 45 | mWebView.post(() -> { 46 | mWebView.reload(); 47 | cmd.finished(); 48 | }); 49 | } else { 50 | return false; 51 | } 52 | 53 | return true; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /app/src/main/java/de/vier_bier/habpanelviewer/command/log/CommandLog.java: -------------------------------------------------------------------------------- 1 | package de.vier_bier.habpanelviewer.command.log; 2 | 3 | import java.util.ArrayList; 4 | 5 | import de.vier_bier.habpanelviewer.command.Command; 6 | 7 | /** 8 | * Log for commands received from openHAB. 9 | */ 10 | public class CommandLog { 11 | private final ArrayList mCommands = new ArrayList<>(); 12 | private final ArrayList mListeners = new ArrayList<>(); 13 | private int mSize; 14 | 15 | public void setSize(int size) { 16 | mSize = size; 17 | trim(); 18 | } 19 | 20 | public void add(Command commandInfo) { 21 | synchronized (mCommands) { 22 | if (mCommands.isEmpty()) { 23 | mCommands.add(commandInfo); 24 | } else { 25 | mCommands.add(0, commandInfo); 26 | } 27 | } 28 | 29 | if (mCommands.size() > mSize) { 30 | mCommands.remove(mSize); 31 | } 32 | 33 | notifyListeners(); 34 | } 35 | 36 | private void trim() { 37 | synchronized (mCommands) { 38 | while (mCommands.size() > mSize) { 39 | mCommands.remove(mSize); 40 | } 41 | } 42 | notifyListeners(); 43 | } 44 | 45 | public ArrayList getCommands() { 46 | return mCommands; 47 | } 48 | 49 | private void notifyListeners() { 50 | synchronized (mListeners) { 51 | for (CommandLogListener l : mListeners) { 52 | l.logChanged(); 53 | } 54 | } 55 | } 56 | 57 | void addListener(CommandLogListener listener) { 58 | synchronized (mListeners) { 59 | mListeners.add(listener); 60 | } 61 | } 62 | 63 | public interface CommandLogListener { 64 | void logChanged(); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /app/src/main/java/de/vier_bier/habpanelviewer/command/log/CommandLogClient.java: -------------------------------------------------------------------------------- 1 | package de.vier_bier.habpanelviewer.command.log; 2 | 3 | /** 4 | * Client for a CommandLog. 5 | */ 6 | public interface CommandLogClient { 7 | void setCommandLog(CommandLog mCmdLog); 8 | } 9 | -------------------------------------------------------------------------------- /app/src/main/java/de/vier_bier/habpanelviewer/db/AppDatabase.java: -------------------------------------------------------------------------------- 1 | package de.vier_bier.habpanelviewer.db; 2 | 3 | import androidx.room.Database; 4 | import androidx.room.RoomDatabase; 5 | 6 | @Database(entities = {Credential.class}, version = 1, exportSchema = false) 7 | public abstract class AppDatabase extends RoomDatabase { 8 | public abstract CredentialDao credentialDao(); 9 | } 10 | -------------------------------------------------------------------------------- /app/src/main/java/de/vier_bier/habpanelviewer/db/Credential.java: -------------------------------------------------------------------------------- 1 | package de.vier_bier.habpanelviewer.db; 2 | 3 | import androidx.room.Entity; 4 | import androidx.room.Index; 5 | import androidx.annotation.NonNull; 6 | 7 | import java.util.Objects; 8 | 9 | 10 | @Entity(primaryKeys = {"host", "realm"}, 11 | indices = {@Index(value = {"host", "realm"}, unique = true)}) 12 | public class Credential { 13 | @NonNull 14 | private final String host; 15 | @NonNull 16 | private final String realm; 17 | 18 | private final String user; 19 | private final String passwd; 20 | 21 | Credential(@NonNull String host, @NonNull String realm, String user, String passwd) { 22 | this.host = host; 23 | this.realm = realm; 24 | this.user = user; 25 | this.passwd = passwd; 26 | } 27 | 28 | @NonNull 29 | public String getHost() { 30 | return host; 31 | } 32 | 33 | @NonNull 34 | String getRealm() { 35 | return realm; 36 | } 37 | 38 | public String getUser() { 39 | return user; 40 | } 41 | 42 | public String getPasswd() { 43 | return passwd; 44 | } 45 | 46 | @Override 47 | public boolean equals(Object o) { 48 | if (this == o) return true; 49 | if (o == null || getClass() != o.getClass()) return false; 50 | Credential that = (Credential) o; 51 | return Objects.equals(host, that.host) && 52 | Objects.equals(realm, that.realm); 53 | } 54 | 55 | @Override 56 | public int hashCode() { 57 | return Objects.hash(host, realm); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /app/src/main/java/de/vier_bier/habpanelviewer/db/CredentialDao.java: -------------------------------------------------------------------------------- 1 | package de.vier_bier.habpanelviewer.db; 2 | 3 | import androidx.room.Dao; 4 | import androidx.room.Delete; 5 | import androidx.room.Insert; 6 | import androidx.room.OnConflictStrategy; 7 | import androidx.room.Query; 8 | 9 | import java.util.List; 10 | 11 | @Dao 12 | public interface CredentialDao { 13 | @Query("SELECT * FROM credential where host = :host and realm = :realm") 14 | Credential get(String host, String realm); 15 | 16 | @Query("DELETE FROM credential where host = :host and realm = :realm") 17 | void remove(String host, String realm); 18 | 19 | @Delete 20 | void remove(Credential credential); 21 | 22 | @Insert(onConflict = OnConflictStrategy.REPLACE) 23 | void insert(Credential... users); 24 | 25 | @Query("SELECT * FROM credential") 26 | List getAll(); 27 | } 28 | -------------------------------------------------------------------------------- /app/src/main/java/de/vier_bier/habpanelviewer/openhab/FutureState.java: -------------------------------------------------------------------------------- 1 | package de.vier_bier.habpanelviewer.openhab; 2 | 3 | import androidx.annotation.NonNull; 4 | 5 | import java.util.concurrent.Delayed; 6 | import java.util.concurrent.TimeUnit; 7 | 8 | public class FutureState extends ItemState implements Delayed { 9 | private final int delayInMillis; 10 | private long origin = System.currentTimeMillis(); 11 | 12 | public FutureState(String item, int interval, String state) { 13 | super(item, state); 14 | 15 | delayInMillis = interval * 1000; 16 | 17 | resetTime(); 18 | } 19 | 20 | public void resetTime() { 21 | origin = System.currentTimeMillis(); 22 | } 23 | 24 | @Override 25 | public long getDelay(@NonNull TimeUnit timeUnit) { 26 | return timeUnit.convert(delayInMillis - (System.currentTimeMillis() - origin), TimeUnit.MILLISECONDS); 27 | } 28 | 29 | @Override 30 | public int compareTo(@NonNull Delayed delayed) { 31 | if (delayed == this) { 32 | return 0; 33 | } 34 | 35 | return Long.compare(getDelay(TimeUnit.MILLISECONDS), delayed.getDelay(TimeUnit.MILLISECONDS)); 36 | } 37 | 38 | @Override 39 | public boolean equals(Object obj) { 40 | return obj instanceof FutureState && mItemName.equals(((FutureState) obj).mItemName); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /app/src/main/java/de/vier_bier/habpanelviewer/openhab/ISseConnectionListener.java: -------------------------------------------------------------------------------- 1 | package de.vier_bier.habpanelviewer.openhab; 2 | 3 | public interface ISseConnectionListener extends ISseListener { 4 | void statusChanged(SseConnection.Status newStatus); 5 | } 6 | -------------------------------------------------------------------------------- /app/src/main/java/de/vier_bier/habpanelviewer/openhab/ISseListener.java: -------------------------------------------------------------------------------- 1 | package de.vier_bier.habpanelviewer.openhab; 2 | 3 | interface ISseListener {} 4 | -------------------------------------------------------------------------------- /app/src/main/java/de/vier_bier/habpanelviewer/openhab/IStateUpdateListener.java: -------------------------------------------------------------------------------- 1 | package de.vier_bier.habpanelviewer.openhab; 2 | 3 | /** 4 | * Item subscription listener 5 | */ 6 | public interface IStateUpdateListener { 7 | /** 8 | * The state of an item has changed. 9 | */ 10 | void itemUpdated(String name, String value); 11 | } 12 | -------------------------------------------------------------------------------- /app/src/main/java/de/vier_bier/habpanelviewer/openhab/ISubscriptionListener.java: -------------------------------------------------------------------------------- 1 | package de.vier_bier.habpanelviewer.openhab; 2 | 3 | /** 4 | * Listener for item subscriptions. 5 | */ 6 | interface ISubscriptionListener extends IStateUpdateListener { 7 | void itemInvalid(String name); 8 | } 9 | -------------------------------------------------------------------------------- /app/src/main/java/de/vier_bier/habpanelviewer/openhab/IUrlListener.java: -------------------------------------------------------------------------------- 1 | package de.vier_bier.habpanelviewer.openhab; 2 | 3 | /** 4 | * Interface to be notified when the web view changes URL. 5 | */ 6 | public interface IUrlListener { 7 | void changed(String url, boolean isHabPanelUrl); 8 | } 9 | -------------------------------------------------------------------------------- /app/src/main/java/de/vier_bier/habpanelviewer/openhab/ItemState.java: -------------------------------------------------------------------------------- 1 | package de.vier_bier.habpanelviewer.openhab; 2 | 3 | /** 4 | * Item with state. 5 | */ 6 | class ItemState { 7 | final String mItemName; 8 | final String mItemState; 9 | 10 | ItemState(String itemName, String itemState) { 11 | mItemName = itemName; 12 | mItemState = itemState; 13 | } 14 | 15 | public String getItemName() { 16 | return mItemName; 17 | } 18 | 19 | public String getItemState() { 20 | return mItemState; 21 | } 22 | 23 | @Override 24 | public int hashCode() { 25 | return mItemName.hashCode(); 26 | } 27 | 28 | @Override 29 | public boolean equals(Object obj) { 30 | return obj instanceof ItemState && mItemName != null && mItemName.equals(((ItemState) obj).mItemName); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app/src/main/java/de/vier_bier/habpanelviewer/openhab/average/Average.java: -------------------------------------------------------------------------------- 1 | package de.vier_bier.habpanelviewer.openhab.average; 2 | 3 | import androidx.annotation.NonNull; 4 | 5 | import java.util.ArrayList; 6 | import java.util.Iterator; 7 | import java.util.concurrent.Delayed; 8 | import java.util.concurrent.TimeUnit; 9 | 10 | /** 11 | * Numeric delayed average. 12 | */ 13 | abstract class Average implements Delayed { 14 | private final ArrayList> samples = new ArrayList<>(); 15 | private final String itemName; 16 | 17 | private int delayInMillis; 18 | R total; 19 | 20 | private long origin = System.currentTimeMillis(); 21 | 22 | Average(String item, int interval, R zero) { 23 | itemName = item; 24 | delayInMillis = interval * 1000; 25 | total = zero; 26 | 27 | resetTime(); 28 | } 29 | 30 | public void add(R state) { 31 | Sample newSample = new Sample<>(state); 32 | 33 | synchronized (samples) { 34 | samples.add(newSample); 35 | } 36 | addToTotal(newSample.getValue()); 37 | } 38 | 39 | public String getAverage() { 40 | removeOldSamples(System.currentTimeMillis()); 41 | 42 | R avg; 43 | synchronized (samples) { 44 | if (samples.isEmpty()) { 45 | return null; 46 | } 47 | 48 | avg = divideTotal(samples.size()); 49 | } 50 | return String.valueOf(avg); 51 | } 52 | 53 | private void removeOldSamples(long time) { 54 | synchronized (samples) { 55 | Iterator> i = samples.iterator(); 56 | Sample s; 57 | while (i.hasNext()) { 58 | s = i.next(); 59 | 60 | if (s.getTime() < time - delayInMillis) { 61 | i.remove(); 62 | removeFromTotal(s.getValue()); 63 | } else { 64 | return; 65 | } 66 | } 67 | } 68 | } 69 | 70 | protected abstract void removeFromTotal(R value); 71 | 72 | protected abstract void addToTotal(R value); 73 | 74 | protected abstract R divideTotal(int count); 75 | 76 | String getItemName() { 77 | return itemName; 78 | } 79 | 80 | void resetTime() { 81 | origin = System.currentTimeMillis(); 82 | } 83 | 84 | @Override 85 | public long getDelay(@NonNull TimeUnit timeUnit) { 86 | return timeUnit.convert(delayInMillis - (System.currentTimeMillis() - origin), TimeUnit.MILLISECONDS); 87 | } 88 | 89 | @Override 90 | public int compareTo(@NonNull Delayed delayed) { 91 | if (delayed == this) { 92 | return 0; 93 | } 94 | 95 | return Long.compare(getDelay(TimeUnit.MILLISECONDS), delayed.getDelay(TimeUnit.MILLISECONDS)); 96 | } 97 | 98 | boolean setInterval(int interval) { 99 | if (delayInMillis != interval * 1000) { 100 | delayInMillis = interval * 1000; 101 | 102 | return true; 103 | } 104 | 105 | return false; 106 | } 107 | 108 | @Override 109 | public boolean equals(Object obj) { 110 | return obj instanceof Average && itemName.equals(((Average) obj).itemName); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /app/src/main/java/de/vier_bier/habpanelviewer/openhab/average/FloatAverage.java: -------------------------------------------------------------------------------- 1 | package de.vier_bier.habpanelviewer.openhab.average; 2 | 3 | /** 4 | * Float average. 5 | */ 6 | public class FloatAverage extends Average { 7 | public FloatAverage(String item, int interval) { 8 | super(item, interval, 0f); 9 | 10 | resetTime(); 11 | } 12 | 13 | @Override 14 | public void removeFromTotal(Float value) { 15 | total = total - value; 16 | } 17 | 18 | @Override 19 | public void addToTotal(Float value) { 20 | total = total + value; 21 | } 22 | 23 | @Override 24 | public Float divideTotal(int count) { 25 | return total / count; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/src/main/java/de/vier_bier/habpanelviewer/openhab/average/IStatePropagator.java: -------------------------------------------------------------------------------- 1 | package de.vier_bier.habpanelviewer.openhab.average; 2 | 3 | /** 4 | * Propagates item state updates. 5 | */ 6 | public interface IStatePropagator { 7 | void updateState(String item, String state); 8 | } 9 | -------------------------------------------------------------------------------- /app/src/main/java/de/vier_bier/habpanelviewer/openhab/average/Sample.java: -------------------------------------------------------------------------------- 1 | package de.vier_bier.habpanelviewer.openhab.average; 2 | 3 | /** 4 | * A value with its acquisition time. 5 | */ 6 | class Sample { 7 | private final N fValue; 8 | private final long fTime; 9 | 10 | public Sample(N value) { 11 | fValue = value; 12 | fTime = System.currentTimeMillis(); 13 | } 14 | 15 | public N getValue() { 16 | return fValue; 17 | } 18 | 19 | public long getTime() { 20 | return fTime; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/src/main/java/de/vier_bier/habpanelviewer/preferences/AutocompleteTextPreference.java: -------------------------------------------------------------------------------- 1 | package de.vier_bier.habpanelviewer.preferences; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.content.Context; 5 | import android.preference.EditTextPreference; 6 | import android.util.AttributeSet; 7 | import android.view.View; 8 | import android.view.ViewGroup; 9 | import android.view.ViewParent; 10 | import android.widget.AutoCompleteTextView; 11 | import android.widget.EditText; 12 | 13 | /** 14 | * EditTextPreference with auto completion. 15 | */ 16 | class AutocompleteTextPreference extends EditTextPreference { 17 | private final AutoCompleteTextView mTextView; 18 | 19 | public AutocompleteTextPreference(Context context, AttributeSet attrs) { 20 | super(context, attrs); 21 | 22 | mTextView = new AutoCompleteTextView(context, attrs); 23 | } 24 | 25 | @SuppressLint("MissingSuperCall") 26 | @Override 27 | protected void onBindDialogView(View view) { 28 | EditText editText = mTextView; 29 | editText.setText(getText()); 30 | 31 | ViewParent oldParent = editText.getParent(); 32 | if (oldParent != view) { 33 | if (oldParent != null) { 34 | ((ViewGroup) oldParent).removeView(editText); 35 | } 36 | onAddEditTextToDialogView(view, editText); 37 | } 38 | } 39 | 40 | /** 41 | * Returns the {@link AutoCompleteTextView} widget that will be shown in the dialog. 42 | * 43 | * @return The {@link AutoCompleteTextView} widget that will be shown in the dialog. 44 | */ 45 | public AutoCompleteTextView getEditText() { 46 | return mTextView; 47 | } 48 | 49 | @Override 50 | protected void onDialogClosed(boolean positiveResult) { 51 | super.onDialogClosed(positiveResult); 52 | 53 | if (positiveResult) { 54 | String value = getEditText().getText().toString(); 55 | if (callChangeListener(value)) { 56 | setText(value); 57 | } 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /app/src/main/java/de/vier_bier/habpanelviewer/preferences/ItemValidator.java: -------------------------------------------------------------------------------- 1 | package de.vier_bier.habpanelviewer.preferences; 2 | 3 | import android.util.Log; 4 | 5 | import org.jetbrains.annotations.NotNull; 6 | import org.json.JSONArray; 7 | import org.json.JSONException; 8 | import org.json.JSONObject; 9 | 10 | import java.io.IOException; 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | 14 | import de.vier_bier.habpanelviewer.connection.OkHttpClientFactory; 15 | import okhttp3.Call; 16 | import okhttp3.Callback; 17 | import okhttp3.OkHttpClient; 18 | import okhttp3.Request; 19 | import okhttp3.Response; 20 | import okhttp3.ResponseBody; 21 | 22 | class ItemValidator { 23 | private static final String TAG = "HPV-ItemValidator"; 24 | private final List mNames = new ArrayList<>(); 25 | 26 | void setServerUrl(String serverUrl, ValidationStateListener l) { 27 | OkHttpClient client = OkHttpClientFactory.getInstance().create(); 28 | 29 | try { 30 | Request request = new Request.Builder() 31 | .url(serverUrl + "/rest/items") 32 | .build(); 33 | 34 | client.newCall(request) 35 | .enqueue(new Callback() { 36 | @Override 37 | public void onFailure(@NotNull final Call call, @NotNull IOException e) { 38 | Log.e(TAG, "Failed to fetch item names from server " + serverUrl, e); 39 | mNames.clear(); 40 | l.validationUnavailable(); 41 | } 42 | 43 | @Override 44 | public void onResponse(@NotNull Call call, @NotNull final Response response) throws IOException { 45 | mNames.clear(); 46 | 47 | if (response.code() == 200) { 48 | try (ResponseBody responseBody = response.body()) { 49 | if (responseBody != null) { 50 | JSONArray itemArr = new JSONArray(responseBody.string()); 51 | for (int i = 0; i < itemArr.length(); i++) { 52 | JSONObject item = (JSONObject) itemArr.get(i); 53 | mNames.add(item.getString("name")); 54 | } 55 | } else { 56 | mNames.clear(); 57 | l.validationUnavailable(); 58 | } 59 | } catch (JSONException e) { 60 | Log.e(TAG, "Failed to fetch item names from server " + serverUrl, e); 61 | } 62 | l.validationAvailable(mNames); 63 | } else { 64 | mNames.clear(); 65 | l.validationUnavailable(); 66 | } 67 | } 68 | }); 69 | } catch (IllegalArgumentException e) { 70 | mNames.clear(); 71 | l.validationUnavailable(); 72 | } 73 | } 74 | 75 | boolean isValid(String itemName) { 76 | return mNames.contains(itemName); 77 | } 78 | 79 | interface ValidationStateListener { 80 | void validationAvailable(List items); 81 | void validationUnavailable(); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /app/src/main/java/de/vier_bier/habpanelviewer/preferences/PreferenceCallback.java: -------------------------------------------------------------------------------- 1 | package de.vier_bier.habpanelviewer.preferences; 2 | 3 | interface PreferenceCallback { 4 | void onNestedPreferenceSelected(String id); 5 | } 6 | -------------------------------------------------------------------------------- /app/src/main/java/de/vier_bier/habpanelviewer/preferences/PreferencesConnected.java: -------------------------------------------------------------------------------- 1 | package de.vier_bier.habpanelviewer.preferences; 2 | 3 | import android.os.Bundle; 4 | import android.preference.EditTextPreference; 5 | import android.preference.Preference; 6 | 7 | import de.vier_bier.habpanelviewer.Constants; 8 | import de.vier_bier.habpanelviewer.R; 9 | import de.vier_bier.habpanelviewer.UiUtil; 10 | 11 | public class PreferencesConnected extends PreferenceFragment { 12 | @Override 13 | public void onCreate(Bundle savedInstanceState) { 14 | super.onCreate(savedInstanceState); 15 | 16 | addPreferencesFromResource(R.xml.preferences_connected); 17 | 18 | EditTextPreference connectedIntervalPreference = 19 | (EditTextPreference) findPreference(Constants.PREF_CONNECTED_INTERVAL); 20 | connectedIntervalPreference.setOnPreferenceChangeListener(new NumberValidatingListener(0, 6000)); 21 | } 22 | 23 | private class NumberValidatingListener implements Preference.OnPreferenceChangeListener { 24 | private final int minVal; 25 | private final int maxVal; 26 | 27 | NumberValidatingListener(int minVal, int maxVal) { 28 | this.minVal = minVal; 29 | this.maxVal = maxVal; 30 | } 31 | 32 | @Override 33 | public boolean onPreferenceChange(final Preference preference, Object o) { 34 | boolean invalid; 35 | try { 36 | int val = Integer.parseInt((String) o); 37 | 38 | invalid = val < minVal || val > maxVal; 39 | } catch (NumberFormatException e) { 40 | invalid = true; 41 | } 42 | 43 | if (invalid && getActivity() != null && !getActivity().isFinishing()) { 44 | UiUtil.showDialog(getActivity(), 45 | preference.getTitle() + " " + getResources().getString(R.string.invalid), 46 | getString(R.string.noValidIntInRange, minVal, maxVal)); 47 | return false; 48 | } 49 | 50 | return true; 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /app/src/main/java/de/vier_bier/habpanelviewer/preferences/PreferencesConnection.java: -------------------------------------------------------------------------------- 1 | package de.vier_bier.habpanelviewer.preferences; 2 | 3 | import android.os.Bundle; 4 | import android.preference.EditTextPreference; 5 | import android.preference.Preference; 6 | 7 | import java.net.MalformedURLException; 8 | import java.net.URL; 9 | 10 | import de.vier_bier.habpanelviewer.Constants; 11 | import de.vier_bier.habpanelviewer.R; 12 | import de.vier_bier.habpanelviewer.UiUtil; 13 | 14 | public class PreferencesConnection extends PreferenceFragment { 15 | @Override 16 | public void onCreate(Bundle savedInstanceState) { 17 | super.onCreate(savedInstanceState); 18 | 19 | addPreferencesFromResource(R.xml.preferences_connection); 20 | 21 | EditTextPreference urlPreference = (EditTextPreference) findPreference(Constants.PREF_SERVER_URL); 22 | urlPreference.setOnPreferenceChangeListener(new URLValidatingListener()); 23 | } 24 | 25 | private class URLValidatingListener implements Preference.OnPreferenceChangeListener { 26 | @Override 27 | public boolean onPreferenceChange(final Preference preference, Object o) { 28 | String text = (String) o; 29 | 30 | if (text == null || text.isEmpty()) { 31 | return true; 32 | } 33 | 34 | String dialogText = null; 35 | 36 | try { 37 | URL uri = new URL(text); 38 | 39 | int port = uri.getPort(); 40 | if (port == -1) { 41 | port = uri.getDefaultPort(); 42 | } 43 | 44 | if (port < 0 || port > 65535) { 45 | dialogText = "Port invalid: " + port; 46 | } 47 | } catch (MalformedURLException e) { 48 | dialogText = "URL invalid: " + e.getLocalizedMessage(); 49 | } 50 | 51 | if (dialogText != null) { 52 | UiUtil.showDialog(getActivity(), preference.getTitle() + " " 53 | + getActivity().getResources().getString(R.string.invalid), dialogText); 54 | } 55 | 56 | return true; 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /app/src/main/java/de/vier_bier/habpanelviewer/preferences/PreferencesMain.java: -------------------------------------------------------------------------------- 1 | package de.vier_bier.habpanelviewer.preferences; 2 | 3 | import android.os.Bundle; 4 | 5 | import de.vier_bier.habpanelviewer.Constants; 6 | import de.vier_bier.habpanelviewer.R; 7 | 8 | public class PreferencesMain extends PreferenceFragment { 9 | @Override 10 | public void onCreate(Bundle savedInstanceState) { 11 | super.onCreate(savedInstanceState); 12 | 13 | addPreferencesFromResource(R.xml.preferences_main); 14 | 15 | Bundle bundle = this.getArguments(); 16 | if (bundle == null || !Boolean.TRUE.equals(bundle.getBoolean(Constants.INTENT_FLAG_CAMERA_ENABLED))) { 17 | findPreference("nested_pref_camera").setEnabled(false); 18 | findPreference("nested_pref_camera").setSummary(getString(R.string.notAvailableOnDevice, getString(R.string.pref_camera))); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/src/main/java/de/vier_bier/habpanelviewer/preferences/PreferencesNoiseLevel.java: -------------------------------------------------------------------------------- 1 | package de.vier_bier.habpanelviewer.preferences; 2 | 3 | import android.Manifest; 4 | import android.content.SharedPreferences; 5 | import android.content.pm.PackageManager; 6 | import android.os.Bundle; 7 | import android.preference.CheckBoxPreference; 8 | import android.preference.Preference; 9 | import android.preference.PreferenceManager; 10 | 11 | import androidx.annotation.NonNull; 12 | import androidx.core.content.ContextCompat; 13 | import androidx.legacy.app.FragmentCompat; 14 | 15 | import de.vier_bier.habpanelviewer.Constants; 16 | import de.vier_bier.habpanelviewer.R; 17 | import de.vier_bier.habpanelviewer.UiUtil; 18 | 19 | public class PreferencesNoiseLevel extends PreferenceFragment implements Preference.OnPreferenceChangeListener { 20 | @Override 21 | public void onCreate(Bundle savedInstanceState) { 22 | super.onCreate(savedInstanceState); 23 | 24 | addPreferencesFromResource(R.xml.preferences_noise_level); 25 | 26 | CheckBoxPreference cbPref = (CheckBoxPreference) findPreference(Constants.PREF_NOISE_LEVEL_ENABLED); 27 | cbPref.setOnPreferenceChangeListener(this); 28 | } 29 | 30 | @Override 31 | public boolean onPreferenceChange(Preference preference, Object o) { 32 | boolean value = (Boolean) o; 33 | 34 | if (value) { 35 | if (needsPermissions()) { 36 | requestMissingPermissions(); 37 | return false; 38 | } 39 | } 40 | 41 | return true; 42 | } 43 | 44 | private boolean needsPermissions() { 45 | return ContextCompat.checkSelfPermission(getActivity(), Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED; 46 | } 47 | 48 | private void requestMissingPermissions() { 49 | FragmentCompat.requestPermissions(this, new String[]{Manifest.permission.RECORD_AUDIO}, 50 | Constants.REQUEST_RECORD_AUDIO); 51 | } 52 | 53 | @Override 54 | public void onRequestPermissionsResult(int requestCode, 55 | @NonNull String[] permissions, 56 | @NonNull int[] grantResults) { 57 | if (requestCode == Constants.REQUEST_RECORD_AUDIO) { 58 | setAllowPreviewPref(grantResults.length > 0 59 | && grantResults[0] == PackageManager.PERMISSION_GRANTED); 60 | } 61 | } 62 | 63 | private void setAllowPreviewPref(boolean allowPreview) { 64 | final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getActivity()); 65 | if (!prefs.getBoolean(Constants.PREF_NOISE_LEVEL_ENABLED, false)) { 66 | SharedPreferences.Editor editor1 = prefs.edit(); 67 | editor1.putBoolean(Constants.PREF_NOISE_LEVEL_ENABLED, allowPreview); 68 | editor1.apply(); 69 | 70 | CheckBoxPreference allowPreference = (CheckBoxPreference) findPreference(Constants.PREF_NOISE_LEVEL_ENABLED); 71 | allowPreference.setChecked(allowPreview); 72 | } 73 | } 74 | } 75 | 76 | -------------------------------------------------------------------------------- /app/src/main/java/de/vier_bier/habpanelviewer/preferences/PreferencesOther.java: -------------------------------------------------------------------------------- 1 | package de.vier_bier.habpanelviewer.preferences; 2 | 3 | import android.app.admin.DevicePolicyManager; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | import android.content.SharedPreferences; 7 | import android.os.Bundle; 8 | import android.preference.CheckBoxPreference; 9 | import android.preference.Preference; 10 | import android.preference.PreferenceManager; 11 | 12 | import de.vier_bier.habpanelviewer.AdminReceiver; 13 | import de.vier_bier.habpanelviewer.Constants; 14 | import de.vier_bier.habpanelviewer.R; 15 | 16 | import static android.app.Activity.RESULT_OK; 17 | 18 | public class PreferencesOther extends PreferenceFragment { 19 | private DevicePolicyManager mDPM; 20 | 21 | @Override 22 | public void onCreate(Bundle savedInstanceState) { 23 | super.onCreate(savedInstanceState); 24 | 25 | addPreferencesFromResource(R.xml.preferences_other); 26 | 27 | mDPM = (DevicePolicyManager) getActivity().getSystemService(Context.DEVICE_POLICY_SERVICE); 28 | 29 | // add validation to the device admin 30 | CheckBoxPreference adminPreference = (CheckBoxPreference) findPreference(Constants.PREF_DEVICE_ADMIN); 31 | adminPreference.setOnPreferenceChangeListener(new AdminValidatingListener()); 32 | } 33 | 34 | @Override 35 | public void onStart() { 36 | super.onStart(); 37 | 38 | onActivityResult(Constants.REQUEST_DEVICE_ADMIN, RESULT_OK, null); 39 | } 40 | 41 | @Override 42 | public void onActivityResult(int requestCode, int resultCode, Intent data) { 43 | if (requestCode == Constants.REQUEST_DEVICE_ADMIN && resultCode == RESULT_OK) { 44 | final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getActivity()); 45 | boolean isActive = mDPM.isAdminActive(AdminReceiver.COMP); 46 | 47 | if (prefs.getBoolean(Constants.PREF_DEVICE_ADMIN, false) != isActive) { 48 | SharedPreferences.Editor editor1 = prefs.edit(); 49 | editor1.putBoolean(Constants.PREF_DEVICE_ADMIN, isActive); 50 | editor1.apply(); 51 | 52 | CheckBoxPreference adminPreference = (CheckBoxPreference) findPreference(Constants.PREF_DEVICE_ADMIN); 53 | adminPreference.setChecked(isActive); 54 | } 55 | } 56 | } 57 | 58 | private void removeAsAdmin() { 59 | mDPM.removeActiveAdmin(AdminReceiver.COMP); 60 | 61 | CheckBoxPreference adminPreference = (CheckBoxPreference) findPreference(Constants.PREF_DEVICE_ADMIN); 62 | adminPreference.setChecked(false); 63 | } 64 | 65 | private void installAsAdmin() { 66 | Intent intent = new Intent(DevicePolicyManager.ACTION_ADD_DEVICE_ADMIN); 67 | intent.putExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN, AdminReceiver.COMP); 68 | intent.putExtra(DevicePolicyManager.EXTRA_ADD_EXPLANATION, getString(R.string.deviceAdminDescription)); 69 | startActivityForResult(intent, Constants.REQUEST_DEVICE_ADMIN); 70 | } 71 | 72 | private class AdminValidatingListener implements Preference.OnPreferenceChangeListener { 73 | @Override 74 | public boolean onPreferenceChange(final Preference preference, Object o) { 75 | boolean value = (Boolean) o; 76 | 77 | if (value && !mDPM.isAdminActive(AdminReceiver.COMP)) { 78 | installAsAdmin(); 79 | } else if (!value && mDPM.isAdminActive(AdminReceiver.COMP)) { 80 | removeAsAdmin(); 81 | } 82 | 83 | return false; 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /app/src/main/java/de/vier_bier/habpanelviewer/preferences/PreferencesReporting.java: -------------------------------------------------------------------------------- 1 | package de.vier_bier.habpanelviewer.preferences; 2 | 3 | import android.os.Bundle; 4 | 5 | import de.vier_bier.habpanelviewer.Constants; 6 | import de.vier_bier.habpanelviewer.R; 7 | 8 | public class PreferencesReporting extends PreferenceFragment { 9 | @Override 10 | public void onCreate(Bundle savedInstanceState) { 11 | super.onCreate(savedInstanceState); 12 | 13 | addPreferencesFromResource(R.xml.preferences_reporting); 14 | 15 | boolean motionEnabled = false; 16 | boolean proximityEnabled = false; 17 | boolean pressureEnabled = false; 18 | boolean brightnessEnabled = false; 19 | boolean temperatureEnabled = false; 20 | boolean acceleroMeterEnabled = false; 21 | 22 | Bundle bundle = this.getArguments(); 23 | if (bundle != null) { 24 | motionEnabled = bundle.getBoolean(Constants.INTENT_FLAG_MOTION_ENABLED); 25 | proximityEnabled = bundle.getBoolean("proximity_enabled"); 26 | pressureEnabled = bundle.getBoolean("pressure_enabled"); 27 | brightnessEnabled = bundle.getBoolean("brightness_enabled"); 28 | temperatureEnabled = bundle.getBoolean("temperature_enabled"); 29 | acceleroMeterEnabled = bundle.getBoolean("accelerometer_enabled"); 30 | } 31 | 32 | // disable preferences if functionality is not available 33 | if (!motionEnabled) { 34 | findPreference("nested_pref_motion").setEnabled(false); 35 | findPreference("nested_pref_motion").setSummary(getString(R.string.notAvailableOnDevice, getString(R.string.pref_motion))); 36 | } 37 | if (!proximityEnabled) { 38 | findPreference("nested_pref_proximity").setEnabled(false); 39 | findPreference("nested_pref_proximity").setSummary(getString(R.string.notAvailableOnDevice, getString(R.string.pref_proximity))); 40 | } 41 | if (!pressureEnabled) { 42 | findPreference("nested_pref_pressure").setEnabled(false); 43 | findPreference("nested_pref_pressure").setSummary(getString(R.string.notAvailableOnDevice, getString(R.string.pref_pressure))); 44 | } 45 | if (!brightnessEnabled) { 46 | findPreference("nested_pref_brightness").setEnabled(false); 47 | findPreference("nested_pref_brightness").setSummary(getString(R.string.notAvailableOnDevice, getString(R.string.pref_brightness))); 48 | } 49 | if (!temperatureEnabled) { 50 | findPreference("nested_pref_temperature").setEnabled(false); 51 | findPreference("nested_pref_temperature").setSummary(getString(R.string.notAvailableOnDevice, getString(R.string.pref_temperature))); 52 | } 53 | if (!acceleroMeterEnabled) { 54 | findPreference("nested_pref_accelerometer").setEnabled(false); 55 | findPreference("nested_pref_accelerometer").setSummary(getString(R.string.notAvailableOnDevice, getString(R.string.pref_accelerometer))); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /app/src/main/java/de/vier_bier/habpanelviewer/preferences/PreferencesUi.java: -------------------------------------------------------------------------------- 1 | package de.vier_bier.habpanelviewer.preferences; 2 | 3 | import android.os.Bundle; 4 | import android.preference.ListPreference; 5 | 6 | import org.greenrobot.eventbus.EventBus; 7 | 8 | import de.vier_bier.habpanelviewer.Constants; 9 | import de.vier_bier.habpanelviewer.R; 10 | import de.vier_bier.habpanelviewer.UiUtil; 11 | 12 | public class PreferencesUi extends PreferenceFragment { 13 | @Override 14 | public void onCreate(Bundle savedInstanceState) { 15 | super.onCreate(savedInstanceState); 16 | 17 | addPreferencesFromResource(R.xml.preferences_ui); 18 | 19 | ListPreference themePreference = (ListPreference) findPreference(Constants.PREF_THEME); 20 | themePreference.setOnPreferenceChangeListener((preference, o) -> { 21 | if (getActivity() != null && !getActivity().isFinishing()) { 22 | if (UiUtil.themeChanged((String) o, getActivity())) { 23 | UiUtil.showSnackBar(getActivity().findViewById(R.id.myCoordinatorLayout), 24 | R.string.themeChangedRestartRequired, R.string.action_restart, 25 | view -> EventBus.getDefault().post(new Constants.Restart())); 26 | } 27 | } 28 | 29 | return true; 30 | }); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app/src/main/java/de/vier_bier/habpanelviewer/reporting/AbstractAveragingDeviceMonitor.java: -------------------------------------------------------------------------------- 1 | package de.vier_bier.habpanelviewer.reporting; 2 | 3 | import android.content.Context; 4 | import android.content.SharedPreferences; 5 | import android.content.res.Resources; 6 | import android.hardware.SensorEvent; 7 | import android.hardware.SensorManager; 8 | 9 | import de.vier_bier.habpanelviewer.Constants; 10 | import de.vier_bier.habpanelviewer.R; 11 | import de.vier_bier.habpanelviewer.openhab.ServerConnection; 12 | import de.vier_bier.habpanelviewer.status.ApplicationStatus; 13 | 14 | public abstract class AbstractAveragingDeviceMonitor extends AbstractDeviceMonitor { 15 | private boolean mDoAverage; 16 | private int mInterval; 17 | Float mValue; 18 | 19 | AbstractAveragingDeviceMonitor(Context ctx, SensorManager sensorManager, ServerConnection serverConnection, 20 | String sensorName, String prefkey, int sensorType) { 21 | super(ctx, sensorManager, serverConnection, sensorName, prefkey, sensorType); 22 | } 23 | 24 | protected synchronized void addStatusItems(ApplicationStatus status) { 25 | if (mSensorEnabled) { 26 | String state = mCtx.getString(R.string.enabled); 27 | if (mDoAverage) { 28 | Resources res = mCtx.getResources(); 29 | state += "\n" + res.getQuantityString(R.plurals.updateInterval, mInterval, mInterval); 30 | } 31 | if (!mSensorItem.isEmpty()) { 32 | state += "\n" + getInfoString(mValue, mSensorItem, mSensorState); 33 | } 34 | 35 | status.set(mSensorName, state); 36 | } else { 37 | status.set(mSensorName, mCtx.getString(R.string.disabled)); 38 | } 39 | } 40 | 41 | abstract String getInfoString(Float value, String item, String state); 42 | 43 | @Override 44 | public synchronized void updateFromPreferences(SharedPreferences prefs) { 45 | boolean removeFromAverage = false; 46 | if (mDoAverage != prefs.getBoolean(Constants.PREF_PREFIX + mPreferenceKey + Constants.PREF_SUFFIX_AVERAGE, true)) { 47 | mDoAverage = prefs.getBoolean(Constants.PREF_PREFIX + mPreferenceKey + Constants.PREF_SUFFIX_AVERAGE, true); 48 | 49 | if (!mDoAverage) { 50 | removeFromAverage = true; 51 | } 52 | } 53 | 54 | if (mInterval != Integer.parseInt(prefs.getString(Constants.PREF_PREFIX + mPreferenceKey + Constants.PREF_SUFFIX_INTERVAL, "60"))) { 55 | mInterval = Integer.parseInt(prefs.getString(Constants.PREF_PREFIX + mPreferenceKey + Constants.PREF_SUFFIX_INTERVAL, "60")); 56 | } 57 | boolean wasEnabled = mSensorEnabled; 58 | super.updateFromPreferences(prefs); 59 | 60 | // if sensor or averaging turned off remove current average so it is not propagated 61 | if ((!mSensorEnabled && wasEnabled) || (wasEnabled && removeFromAverage)) { 62 | mServerConnection.removeFromAverage(mSensorItem); 63 | } 64 | } 65 | @Override 66 | public void onSensorChanged(SensorEvent event) { 67 | mValue = event.values[0]; 68 | 69 | if (mDoAverage) { 70 | mServerConnection.addStateToAverage(mSensorItem, mValue, mInterval); 71 | } else { 72 | mServerConnection.updateState(mSensorItem, String.valueOf(mValue)); 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /app/src/main/java/de/vier_bier/habpanelviewer/reporting/AbstractDeviceMonitor.java: -------------------------------------------------------------------------------- 1 | package de.vier_bier.habpanelviewer.reporting; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | import android.content.SharedPreferences; 6 | import android.hardware.Sensor; 7 | import android.hardware.SensorEventListener; 8 | import android.hardware.SensorManager; 9 | import android.util.Log; 10 | 11 | import org.greenrobot.eventbus.EventBus; 12 | import org.greenrobot.eventbus.Subscribe; 13 | import org.greenrobot.eventbus.ThreadMode; 14 | 15 | import de.vier_bier.habpanelviewer.Constants; 16 | import de.vier_bier.habpanelviewer.R; 17 | import de.vier_bier.habpanelviewer.openhab.IStateUpdateListener; 18 | import de.vier_bier.habpanelviewer.openhab.ServerConnection; 19 | import de.vier_bier.habpanelviewer.status.ApplicationStatus; 20 | 21 | /** 22 | * Abstract base class for device sensor monitors. 23 | */ 24 | public abstract class AbstractDeviceMonitor implements IDeviceMonitor, SensorEventListener, IStateUpdateListener { 25 | private static final String TAG = "HPV-AbstractDevMon"; 26 | 27 | final Context mCtx; 28 | private final SensorManager mSensorManager; 29 | final ServerConnection mServerConnection; 30 | final Sensor mSensor; 31 | 32 | final String mPreferenceKey; 33 | final String mSensorName; 34 | boolean mSensorEnabled; 35 | 36 | String mSensorItem; 37 | String mSensorState; 38 | 39 | AbstractDeviceMonitor(Context ctx, SensorManager sensorManager, ServerConnection serverConnection, 40 | String sensorName, String prefkey, int sensorType) { 41 | mCtx = ctx; 42 | mSensorManager = sensorManager; 43 | mServerConnection = serverConnection; 44 | mPreferenceKey = prefkey; 45 | mSensor = mSensorManager.getDefaultSensor(sensorType); 46 | mSensorName = sensorName; 47 | 48 | EventBus.getDefault().register(this); 49 | } 50 | 51 | @Subscribe(threadMode = ThreadMode.MAIN) 52 | public void onMessageEvent(ApplicationStatus status) { 53 | if (mSensor == null) { 54 | status.set(mSensorName, mCtx.getString(R.string.unavailable)); 55 | } 56 | addStatusItems(status); 57 | } 58 | 59 | public synchronized void terminate() { 60 | EventBus.getDefault().unregister(this); 61 | mSensorManager.unregisterListener(this); 62 | } 63 | 64 | @Override 65 | public void disablePreferences(Intent intent) { 66 | intent.putExtra(mPreferenceKey + Constants.PREF_SUFFIX_ENABLED, mSensor != null); 67 | } 68 | 69 | protected abstract void addStatusItems(ApplicationStatus status); 70 | 71 | public synchronized void updateFromPreferences(SharedPreferences prefs) { 72 | if (mSensor != null) { 73 | if (mSensorEnabled != prefs.getBoolean(Constants.PREF_PREFIX + mPreferenceKey + Constants.PREF_SUFFIX_ENABLED, false)) { 74 | mSensorEnabled = !mSensorEnabled; 75 | 76 | if (mSensorEnabled) { 77 | mSensorManager.registerListener(this, mSensor, SensorManager.SENSOR_DELAY_NORMAL); 78 | } else { 79 | mSensorManager.unregisterListener(this); 80 | } 81 | } 82 | 83 | mSensorItem = prefs.getString(Constants.PREF_PREFIX + mPreferenceKey + Constants.PREF_SUFFIX_ITEM, ""); 84 | mServerConnection.subscribeItems(this, mSensorItem); 85 | } 86 | } 87 | 88 | @Override 89 | public void itemUpdated(String name, String value) { 90 | mSensorState = value; 91 | } 92 | 93 | @Override 94 | public void onAccuracyChanged(Sensor sensor, int i) { 95 | Log.v(TAG, "onAccuracyChanged: " + i); 96 | } 97 | } 98 | 99 | -------------------------------------------------------------------------------- /app/src/main/java/de/vier_bier/habpanelviewer/reporting/AccelerometerMonitor.java: -------------------------------------------------------------------------------- 1 | package de.vier_bier.habpanelviewer.reporting; 2 | 3 | import android.content.Context; 4 | import android.content.SharedPreferences; 5 | import android.hardware.Sensor; 6 | import android.hardware.SensorEvent; 7 | import android.hardware.SensorManager; 8 | 9 | import java.util.Arrays; 10 | 11 | import de.vier_bier.habpanelviewer.Constants; 12 | import de.vier_bier.habpanelviewer.R; 13 | import de.vier_bier.habpanelviewer.openhab.ServerConnection; 14 | import de.vier_bier.habpanelviewer.status.ApplicationStatus; 15 | 16 | /** 17 | * Monitors accelerometer sensor state and reports to openHAB. 18 | */ 19 | public class AccelerometerMonitor extends AbstractDeviceMonitor { 20 | private static final String TAG = "HPV-AccelerometerMon"; 21 | 22 | private float mSensitivity = 1f; 23 | private String mSensitivityStr = "1.0"; 24 | private long mLastMotionTime; 25 | private Boolean mMotion = Boolean.FALSE; 26 | 27 | public AccelerometerMonitor(Context ctx, SensorManager sensorManager, ServerConnection serverConnection) { 28 | super(ctx, sensorManager, serverConnection, ctx.getString(R.string.pref_accelerometer), "accelerometer", Sensor.TYPE_LINEAR_ACCELERATION); 29 | } 30 | 31 | @Override 32 | public void onSensorChanged(SensorEvent event) { 33 | // Shake detection 34 | float x = event.values[0]; 35 | float y = event.values[1]; 36 | float z = event.values[2]; 37 | //Log.v(TAG, "onSensorChanged: x=" + x + ", y=" + y + ",z=" + z); 38 | 39 | float mAccel = (float) Math.sqrt(x * x + y * y + z * z); 40 | 41 | //Log.v(TAG, "onSensorChanged: mAccel=" + mAccel); 42 | if (mAccel > mSensitivity) { 43 | mLastMotionTime = System.currentTimeMillis(); 44 | if (!mMotion) { 45 | mMotion = true; 46 | mServerConnection.updateState(mSensorItem, "CLOSED"); 47 | } 48 | } else { 49 | if (mMotion && System.currentTimeMillis() - mLastMotionTime > 60000) { 50 | mMotion = false; 51 | mServerConnection.updateState(mSensorItem, "OPEN"); 52 | } 53 | } 54 | } 55 | 56 | protected synchronized void addStatusItems(ApplicationStatus status) { 57 | if (mSensorEnabled) { 58 | String state = mCtx.getString(R.string.enabled); 59 | if (!mSensorItem.isEmpty()) { 60 | state += "\n" + mCtx.getString(R.string.deviceMoving, mMotion, mSensorItem, mSensorState); 61 | } 62 | state += "\n" + mCtx.getString(R.string.pref_accelerometerSensitivity) + ": " + sensText(); 63 | 64 | status.set(mCtx.getString(R.string.pref_accelerometer), state); 65 | } else { 66 | status.set(mCtx.getString(R.string.pref_accelerometer), mCtx.getString(R.string.disabled)); 67 | } 68 | } 69 | 70 | private String sensText() { 71 | String[] sensVals = mCtx.getResources().getStringArray(R.array.sensitivityValues); 72 | String[] sensitivities = mCtx.getResources().getStringArray(R.array.sensitivityNames); 73 | 74 | int idx = Arrays.asList(sensVals).indexOf(mSensitivityStr); 75 | return idx == -1 ? "??" : sensitivities[idx]; 76 | } 77 | 78 | public synchronized void updateFromPreferences(SharedPreferences prefs) { 79 | super.updateFromPreferences(prefs); 80 | 81 | mSensitivityStr = prefs.getString(Constants.PREF_PREFIX + mPreferenceKey + Constants.PREF_SUFFIX_SENSITIVITY, "3.0"); 82 | mSensitivity = Float.parseFloat(mSensitivityStr); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /app/src/main/java/de/vier_bier/habpanelviewer/reporting/BrightnessMonitor.java: -------------------------------------------------------------------------------- 1 | package de.vier_bier.habpanelviewer.reporting; 2 | 3 | import android.content.Context; 4 | import android.hardware.Sensor; 5 | import android.hardware.SensorManager; 6 | 7 | import de.vier_bier.habpanelviewer.R; 8 | import de.vier_bier.habpanelviewer.openhab.ServerConnection; 9 | 10 | /** 11 | * Monitors brightness sensor state and reports to openHAB. 12 | */ 13 | public class BrightnessMonitor extends AbstractAveragingDeviceMonitor { 14 | public BrightnessMonitor(Context ctx, SensorManager sensorManager, ServerConnection serverConnection) { 15 | super(ctx, sensorManager, serverConnection, ctx.getString(R.string.pref_brightness), "brightness", Sensor.TYPE_LIGHT); 16 | } 17 | 18 | @Override 19 | String getInfoString(Float value, String item, String state) { 20 | return mCtx.getString(R.string.brightness, mValue, mSensorItem, mSensorState); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/src/main/java/de/vier_bier/habpanelviewer/reporting/IDeviceMonitor.java: -------------------------------------------------------------------------------- 1 | package de.vier_bier.habpanelviewer.reporting; 2 | 3 | import android.content.Intent; 4 | import android.content.SharedPreferences; 5 | 6 | public interface IDeviceMonitor { 7 | void terminate(); 8 | void updateFromPreferences(SharedPreferences prefs); 9 | void disablePreferences(Intent intent); 10 | } 11 | -------------------------------------------------------------------------------- /app/src/main/java/de/vier_bier/habpanelviewer/reporting/PressureMonitor.java: -------------------------------------------------------------------------------- 1 | package de.vier_bier.habpanelviewer.reporting; 2 | 3 | import android.content.Context; 4 | import android.hardware.Sensor; 5 | import android.hardware.SensorManager; 6 | 7 | import de.vier_bier.habpanelviewer.R; 8 | import de.vier_bier.habpanelviewer.openhab.ServerConnection; 9 | 10 | /** 11 | * Monitors pressure sensor state and reports to openHAB. 12 | */ 13 | public class PressureMonitor extends AbstractAveragingDeviceMonitor { 14 | public PressureMonitor(Context ctx, SensorManager sensorManager, ServerConnection serverConnection) { 15 | super(ctx, sensorManager, serverConnection, ctx.getString(R.string.pref_pressure), "pressure", Sensor.TYPE_PRESSURE); 16 | } 17 | 18 | @Override 19 | String getInfoString(Float value, String item, String state) { 20 | return mCtx.getString(R.string.pressure, mValue, mSensorItem, mSensorState); 21 | } 22 | } -------------------------------------------------------------------------------- /app/src/main/java/de/vier_bier/habpanelviewer/reporting/ProximityMonitor.java: -------------------------------------------------------------------------------- 1 | package de.vier_bier.habpanelviewer.reporting; 2 | 3 | import android.content.Context; 4 | import android.hardware.Sensor; 5 | import android.hardware.SensorEvent; 6 | import android.hardware.SensorManager; 7 | import android.util.Log; 8 | 9 | import de.vier_bier.habpanelviewer.R; 10 | import de.vier_bier.habpanelviewer.openhab.ServerConnection; 11 | import de.vier_bier.habpanelviewer.status.ApplicationStatus; 12 | 13 | /** 14 | * Monitors proximity sensor state and reports to openHAB. 15 | */ 16 | public class ProximityMonitor extends AbstractDeviceMonitor { 17 | private static final String TAG = "HPV-ProximityMonitor"; 18 | 19 | private Boolean mProximity; 20 | 21 | public ProximityMonitor(Context ctx, SensorManager sensorManager, ServerConnection serverConnection) { 22 | super(ctx, sensorManager, serverConnection, ctx.getString(R.string.pref_proximity), "proximity", Sensor.TYPE_PROXIMITY); 23 | } 24 | 25 | @Override 26 | public void onSensorChanged(SensorEvent event) { 27 | float distance = event.values[0]; 28 | 29 | Log.v(TAG, "onSensorChanged" + distance); 30 | 31 | if (distance < mSensor.getMaximumRange()) { 32 | if (mProximity == null || !mProximity) { 33 | mProximity = true; 34 | mServerConnection.updateState(mSensorItem, "CLOSED"); 35 | } 36 | } else if (mProximity == null || mProximity) { 37 | mProximity = false; 38 | mServerConnection.updateState(mSensorItem, "OPEN"); 39 | } 40 | } 41 | 42 | protected synchronized void addStatusItems(ApplicationStatus status) { 43 | if (mSensorEnabled) { 44 | String state = mCtx.getString(R.string.enabled); 45 | if (!mSensorItem.isEmpty()) { 46 | state += "\n" + mCtx.getString(R.string.objectClose, mProximity, mSensorItem, mSensorState); 47 | } 48 | 49 | state += "\n" + mCtx.getString(R.string.maxRangeResolution, mSensor.getMaximumRange(), mSensor.getResolution()); 50 | 51 | status.set(mCtx.getString(R.string.pref_proximity), state); 52 | } else { 53 | status.set(mCtx.getString(R.string.pref_proximity), mCtx.getString(R.string.disabled)); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /app/src/main/java/de/vier_bier/habpanelviewer/reporting/SensorMissingException.java: -------------------------------------------------------------------------------- 1 | package de.vier_bier.habpanelviewer.reporting; 2 | 3 | /** 4 | * Exception for indicating a missing device sensor. 5 | */ 6 | class SensorMissingException extends Exception { 7 | } 8 | -------------------------------------------------------------------------------- /app/src/main/java/de/vier_bier/habpanelviewer/reporting/TemperatureMonitor.java: -------------------------------------------------------------------------------- 1 | package de.vier_bier.habpanelviewer.reporting; 2 | 3 | import android.content.Context; 4 | import android.hardware.Sensor; 5 | import android.hardware.SensorManager; 6 | 7 | import de.vier_bier.habpanelviewer.R; 8 | import de.vier_bier.habpanelviewer.openhab.ServerConnection; 9 | 10 | /** 11 | * Monitors temperature sensor state and reports to openHAB. 12 | */ 13 | public class TemperatureMonitor extends AbstractAveragingDeviceMonitor { 14 | public TemperatureMonitor(Context ctx, SensorManager sensorManager, ServerConnection serverConnection) { 15 | super(ctx, sensorManager, serverConnection, ctx.getString(R.string.pref_temperature), "temperature", Sensor.TYPE_AMBIENT_TEMPERATURE); 16 | } 17 | 18 | 19 | @Override 20 | String getInfoString(Float value, String item, String state) { 21 | return mCtx.getString(R.string.temperature, mValue, mSensorItem, mSensorState); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/src/main/java/de/vier_bier/habpanelviewer/reporting/VolumeMonitor.java: -------------------------------------------------------------------------------- 1 | package de.vier_bier.habpanelviewer.reporting; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | import android.content.SharedPreferences; 6 | import android.database.ContentObserver; 7 | import android.media.AudioManager; 8 | import android.os.Handler; 9 | 10 | import org.greenrobot.eventbus.EventBus; 11 | import org.greenrobot.eventbus.Subscribe; 12 | import org.greenrobot.eventbus.ThreadMode; 13 | 14 | import de.vier_bier.habpanelviewer.Constants; 15 | import de.vier_bier.habpanelviewer.R; 16 | import de.vier_bier.habpanelviewer.openhab.IStateUpdateListener; 17 | import de.vier_bier.habpanelviewer.openhab.ServerConnection; 18 | import de.vier_bier.habpanelviewer.status.ApplicationStatus; 19 | 20 | /** 21 | * Monitors the device volume. 22 | */ 23 | public class VolumeMonitor implements IDeviceMonitor, IStateUpdateListener { 24 | private final Context mCtx; 25 | private final AudioManager mAudioManager; 26 | private final ServerConnection mServerConnection; 27 | 28 | private boolean mVolumeEnabled; 29 | 30 | private String mVolumeItem; 31 | private Integer mVolume; 32 | private final Integer mMaxVolume; 33 | private String mVolumeState; 34 | 35 | private final ContentObserver mVolumeObserver = new ContentObserver(new Handler()) { 36 | @Override 37 | public void onChange(boolean selfChange) { 38 | super.onChange(selfChange); 39 | 40 | mVolume = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC); 41 | mServerConnection.updateState(mVolumeItem, String.valueOf(mVolume)); 42 | } 43 | }; 44 | 45 | public VolumeMonitor(Context context, AudioManager audioManager, ServerConnection serverConnection) { 46 | mCtx = context; 47 | mAudioManager = audioManager; 48 | mServerConnection = serverConnection; 49 | 50 | EventBus.getDefault().register(this); 51 | 52 | mMaxVolume = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC); 53 | } 54 | 55 | @Override 56 | public void disablePreferences(Intent intent) { } 57 | 58 | @Override 59 | public synchronized void updateFromPreferences(SharedPreferences prefs) { 60 | mVolumeItem = prefs.getString(Constants.PREF_VOLUME_ITEM, ""); 61 | 62 | if (mVolumeEnabled != prefs.getBoolean(Constants.PREF_VOLUME_ENABLED, false)) { 63 | mVolumeEnabled = !mVolumeEnabled; 64 | 65 | if (mVolumeEnabled) { 66 | mCtx.getContentResolver().registerContentObserver( 67 | android.provider.Settings.System.CONTENT_URI, true, mVolumeObserver); 68 | 69 | mVolume = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC); 70 | mServerConnection.updateState(mVolumeItem, String.valueOf(mVolume)); 71 | } else { 72 | mCtx.getContentResolver().unregisterContentObserver(mVolumeObserver); 73 | } 74 | } 75 | 76 | mServerConnection.subscribeItems(this, mVolumeItem); 77 | } 78 | 79 | @Subscribe(threadMode = ThreadMode.MAIN) 80 | public void onMessageEvent(ApplicationStatus status) { 81 | if (mVolumeEnabled) { 82 | String state = mCtx.getString(R.string.enabled); 83 | if (!mVolumeItem.isEmpty()) { 84 | state += "\n" + mCtx.getString(R.string.volumeIsOf, mVolume, mMaxVolume, mVolumeItem, mVolumeState); 85 | } 86 | status.set(mCtx.getString(R.string.pref_volume), state); 87 | } else { 88 | status.set(mCtx.getString(R.string.pref_volume), mCtx.getString(R.string.disabled)); 89 | } 90 | } 91 | 92 | @Override 93 | public void terminate() { 94 | EventBus.getDefault().unregister(this); 95 | mCtx.getContentResolver().unregisterContentObserver(mVolumeObserver); 96 | } 97 | 98 | @Override 99 | public void itemUpdated(String name, String value) { 100 | mVolumeState = value; 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /app/src/main/java/de/vier_bier/habpanelviewer/reporting/motion/AbstractCameraImpl.java: -------------------------------------------------------------------------------- 1 | package de.vier_bier.habpanelviewer.reporting.motion; 2 | 3 | import android.app.Activity; 4 | import android.graphics.Point; 5 | import android.util.Log; 6 | import android.view.TextureView; 7 | 8 | import java.util.ArrayList; 9 | import java.util.Collections; 10 | import java.util.Comparator; 11 | import java.util.List; 12 | 13 | /** 14 | * Base camera implementation. 15 | */ 16 | abstract class AbstractCameraImpl implements ICamera { 17 | private static final String TAG = "HPV-AbstractCameraImpl"; 18 | 19 | final Activity mActivity; 20 | final TextureView mPreviewView; 21 | int mDeviceOrientation; 22 | 23 | final List mListeners = new ArrayList<>(); 24 | 25 | AbstractCameraImpl(Activity activity, TextureView previewView) { 26 | mActivity = activity; 27 | mPreviewView = previewView; 28 | mDeviceOrientation = mActivity.getWindowManager().getDefaultDisplay().getRotation(); 29 | } 30 | 31 | @Override 32 | public void addLumaListener(ILumaListener l) { 33 | mListeners.add(l); 34 | } 35 | 36 | @Override 37 | public void removeLumaListener(ILumaListener l) { 38 | mListeners.remove(l); 39 | } 40 | 41 | Point chooseOptimalSize(Point[] choices) { 42 | 43 | // Collect the supported resolutions that are at least as big as the preview Surface 44 | List bigEnough = new ArrayList<>(); 45 | // Collect the supported resolutions that are smaller than the preview Surface 46 | List notBigEnough = new ArrayList<>(); 47 | int w = 640; 48 | int h = 480; 49 | for (Point option : choices) { 50 | if (option.y == option.x * h / w) { 51 | if (option.x >= 640 && 52 | option.y >= 480) { 53 | bigEnough.add(option); 54 | } else { 55 | notBigEnough.add(option); 56 | } 57 | } 58 | } 59 | 60 | // Pick the smallest of those big enough. If there is no one big enough, pick the 61 | // largest of those not big enough. 62 | if (bigEnough.size() > 0) { 63 | return Collections.min(bigEnough, new CompareSizesByArea()); 64 | } else if (notBigEnough.size() > 0) { 65 | return Collections.max(notBigEnough, new CompareSizesByArea()); 66 | } else { 67 | Log.e(TAG, "Couldn't find any suitable preview size"); 68 | return choices[0]; 69 | } 70 | } 71 | 72 | private static class CompareSizesByArea implements Comparator { 73 | @Override 74 | public int compare(Point lhs, Point rhs) { 75 | return Long.signum((long) lhs.x * lhs.y - 76 | (long) rhs.x * rhs.y); 77 | } 78 | 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /app/src/main/java/de/vier_bier/habpanelviewer/reporting/motion/CameraException.java: -------------------------------------------------------------------------------- 1 | package de.vier_bier.habpanelviewer.reporting.motion; 2 | 3 | /** 4 | * Camera related exception independent on Android API level. 5 | */ 6 | class CameraException extends Exception { 7 | CameraException(String s) { 8 | super(s); 9 | } 10 | 11 | CameraException(String s, Exception e) { 12 | super(s, e); 13 | } 14 | 15 | CameraException(Exception e) { 16 | super(e); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/src/main/java/de/vier_bier/habpanelviewer/reporting/motion/CameraImplNone.java: -------------------------------------------------------------------------------- 1 | package de.vier_bier.habpanelviewer.reporting.motion; 2 | 3 | import android.graphics.SurfaceTexture; 4 | 5 | public class CameraImplNone implements ICamera { 6 | private final String mErrMsg; 7 | 8 | CameraImplNone(String error) { 9 | mErrMsg = error; 10 | } 11 | 12 | @Override 13 | public void lockCamera() { 14 | } 15 | 16 | @Override 17 | public void unlockCamera() { 18 | } 19 | 20 | @Override 21 | public boolean isCameraLocked() { 22 | return false; 23 | } 24 | 25 | @Override 26 | public void startPreview(SurfaceTexture surfaceTexture, IPreviewListener previewListener) { 27 | previewListener.error(""); 28 | previewListener.started(); 29 | } 30 | 31 | @Override 32 | public void stopPreview() { 33 | } 34 | 35 | @Override 36 | public boolean isPreviewRunning() { 37 | return false; 38 | } 39 | 40 | @Override 41 | public void addLumaListener(ILumaListener l) { 42 | } 43 | 44 | @Override 45 | public void removeLumaListener(ILumaListener l) { 46 | } 47 | 48 | @Override 49 | public int getCameraOrientation() { 50 | return 0; 51 | } 52 | 53 | @Override 54 | public void takePicture(IPictureListener iPictureHandler) { 55 | } 56 | 57 | @Override 58 | public void setDeviceRotation(int deviceOrientation) { 59 | } 60 | 61 | public String getMessage() { 62 | return mErrMsg; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /app/src/main/java/de/vier_bier/habpanelviewer/reporting/motion/Comparer.java: -------------------------------------------------------------------------------- 1 | package de.vier_bier.habpanelviewer.reporting.motion; 2 | 3 | import android.graphics.Point; 4 | 5 | import java.util.ArrayList; 6 | 7 | /** 8 | * Compares to images for equality. Does this by dividing the images into smaller rectangles, 9 | * computing the brightness average and comparing that. 10 | */ 11 | class Comparer { 12 | private final int boxes; 13 | 14 | private final Point[] boxStart; 15 | private final Point[] boxEnd; 16 | private final int leniency; 17 | 18 | Comparer(int width, int height, int boxes, int leniency) { 19 | this.boxes = boxes; 20 | this.leniency = leniency; 21 | 22 | // how many points per box 23 | float xPixelsPerBox = width / (float) boxes; 24 | float yPixelsPerBox = height / (float) boxes; 25 | 26 | boxStart = new Point[boxes]; 27 | boxEnd = new Point[boxes]; 28 | 29 | for (int i = 0; i < boxes; i++) { 30 | int startX = (int) (i * xPixelsPerBox); 31 | int endX = i == (boxes - 1) ? width - 1 : (int) ((i + 1) * xPixelsPerBox) - 1; 32 | 33 | int startY = (int) (i * yPixelsPerBox); 34 | int endY = i == (boxes - 1) ? height - 1 : (int) ((i + 1) * yPixelsPerBox) - 1; 35 | 36 | boxStart[i] = new Point(startX, startY); 37 | boxEnd[i] = new Point(endX, endY); 38 | } 39 | } 40 | 41 | /** 42 | * Compare two images for difference 43 | */ 44 | ArrayList isDifferent(LumaData s1, LumaData s2) { 45 | ArrayList differing = new ArrayList<>(); 46 | 47 | s1.setBoxCount(boxes); 48 | s2.setBoxCount(boxes); 49 | 50 | int b1; 51 | int b2; 52 | int diff; 53 | for (int y = 0; y < boxes; y++) { 54 | for (int x = 0; x < boxes; x++) { 55 | b1 = calcAverage(s1, x, y); 56 | b2 = calcAverage(s2, x, y); 57 | diff = Math.abs(b1 - b2); 58 | 59 | if (diff > leniency * 2.55f) { 60 | differing.add(new Point(x, y)); 61 | } 62 | } 63 | } 64 | return differing; 65 | } 66 | 67 | private int calcAverage(LumaData luma, int xBox, int yBox) { 68 | if (luma.getAverage(xBox, yBox) != -1) { 69 | return luma.getAverage(xBox, yBox); 70 | } 71 | 72 | byte[] data = luma.getData(); 73 | if (data == null) throw new NullPointerException(); 74 | 75 | int yPix = boxEnd[yBox].y - boxStart[yBox].y + 1; 76 | int xPix = boxEnd[xBox].x - boxStart[xBox].x + 1; 77 | 78 | int i = 0; 79 | int idx = boxStart[yBox].y * luma.getWidth() + boxStart[xBox].x; 80 | for (int y = boxStart[yBox].y; y <= boxEnd[yBox].y; y++) { 81 | for (int x = 0; x < xPix; x++) { 82 | i += data[idx++]; 83 | } 84 | idx += luma.getWidth() - xPix; 85 | } 86 | 87 | byte result = (byte) (i / (xPix * yPix)); 88 | luma.setAverage(xBox, yBox, result); 89 | 90 | return result; 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /app/src/main/java/de/vier_bier/habpanelviewer/reporting/motion/ICamera.java: -------------------------------------------------------------------------------- 1 | package de.vier_bier.habpanelviewer.reporting.motion; 2 | 3 | import android.graphics.SurfaceTexture; 4 | import android.util.Log; 5 | 6 | /** 7 | * Generic camera interface. 8 | */ 9 | public interface ICamera { 10 | void lockCamera() throws CameraException; 11 | 12 | void unlockCamera(); 13 | 14 | boolean isCameraLocked(); 15 | 16 | void startPreview(SurfaceTexture surfaceTexture, IPreviewListener previewListener); 17 | 18 | void stopPreview() throws CameraException; 19 | 20 | boolean isPreviewRunning(); 21 | 22 | void addLumaListener(ILumaListener l); 23 | 24 | void removeLumaListener(ILumaListener l); 25 | 26 | int getCameraOrientation(); 27 | 28 | void takePicture(IPictureListener iPictureHandler); 29 | 30 | void setDeviceRotation(int deviceOrientation); 31 | 32 | interface ILumaListener { 33 | void preview(LumaData previewBytes); 34 | 35 | boolean needsPreview(); 36 | } 37 | 38 | interface IPictureListener { 39 | void picture(byte[] data); 40 | 41 | void error(String message); 42 | 43 | void progress(String message); 44 | } 45 | 46 | interface IPreviewListener { 47 | void started(); 48 | 49 | void error(String message); 50 | 51 | void exception(Exception e); 52 | 53 | void progress(String message); 54 | } 55 | 56 | class LoggingPreviewListener implements IPreviewListener { 57 | private static final String TAG = "HPV-LoggingPreviewLi"; 58 | 59 | @Override 60 | public void started() { 61 | Log.d(TAG, "preview started"); 62 | } 63 | 64 | @Override 65 | public void error(String message) { 66 | Log.e(TAG, "preview error: " + message); 67 | } 68 | 69 | @Override 70 | public void exception(Exception e) { 71 | Log.e(TAG, "preview error", e); 72 | } 73 | 74 | @Override 75 | public void progress(String message) { 76 | Log.d(TAG, "preview progress: " + message); 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /app/src/main/java/de/vier_bier/habpanelviewer/reporting/motion/IMotionDetector.java: -------------------------------------------------------------------------------- 1 | package de.vier_bier.habpanelviewer.reporting.motion; 2 | 3 | import android.content.SharedPreferences; 4 | 5 | /** 6 | * Interface for motion detectors capabilities for the MainActivity 7 | */ 8 | public interface IMotionDetector { 9 | void terminate(); 10 | 11 | void updateFromPreferences(SharedPreferences prefs); 12 | 13 | Camera getCamera(); 14 | } 15 | -------------------------------------------------------------------------------- /app/src/main/java/de/vier_bier/habpanelviewer/reporting/motion/IMotionListener.java: -------------------------------------------------------------------------------- 1 | package de.vier_bier.habpanelviewer.reporting.motion; 2 | 3 | import android.graphics.Point; 4 | 5 | import java.util.ArrayList; 6 | 7 | /** 8 | * Interface for being notified about motion events. 9 | */ 10 | public interface IMotionListener { 11 | void motionDetected(ArrayList differing); 12 | 13 | void noMotion(); 14 | 15 | void tooDark(); 16 | 17 | class MotionAdapter implements IMotionListener { 18 | @Override 19 | public void motionDetected(ArrayList differing) { 20 | } 21 | 22 | @Override 23 | public void noMotion() { 24 | } 25 | 26 | @Override 27 | public void tooDark() { 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/src/main/java/de/vier_bier/habpanelviewer/reporting/motion/MotionReporter.java: -------------------------------------------------------------------------------- 1 | package de.vier_bier.habpanelviewer.reporting.motion; 2 | 3 | import android.content.SharedPreferences; 4 | import android.graphics.Point; 5 | 6 | import java.util.ArrayList; 7 | 8 | import de.vier_bier.habpanelviewer.Constants; 9 | import de.vier_bier.habpanelviewer.openhab.ServerConnection; 10 | 11 | /** 12 | * Reports motion events to openHAB. 13 | */ 14 | public class MotionReporter extends IMotionListener.MotionAdapter { 15 | private final ServerConnection mServerConnection; 16 | private final IMotionListener mListener; 17 | 18 | private long mLastMotionTime; 19 | private boolean mMotion; 20 | 21 | private String mMotionItem; 22 | private int mMotionTimeout; 23 | 24 | MotionReporter(IMotionListener l, ServerConnection serverConnection) { 25 | mServerConnection = serverConnection; 26 | mListener = l; 27 | } 28 | 29 | @Override 30 | public void motionDetected(ArrayList differing) { 31 | mListener.motionDetected(differing); 32 | 33 | mLastMotionTime = System.currentTimeMillis(); 34 | if (!mMotion) { 35 | mMotion = true; 36 | mServerConnection.updateState(mMotionItem, "CLOSED"); 37 | } 38 | } 39 | 40 | @Override 41 | public void noMotion() { 42 | mListener.noMotion(); 43 | 44 | if (mMotion && System.currentTimeMillis() - mLastMotionTime > mMotionTimeout) { 45 | mMotion = false; 46 | mServerConnection.updateState(mMotionItem, "OPEN"); 47 | } 48 | } 49 | 50 | public void updateFromPreferences(SharedPreferences prefs) { 51 | if (mMotionItem == null || !mMotionItem.equalsIgnoreCase(prefs.getString(Constants.PREF_MOTION_DETECTION_ITEM, ""))) { 52 | mMotionItem = prefs.getString(Constants.PREF_MOTION_DETECTION_ITEM, ""); 53 | mMotion = false; 54 | } 55 | 56 | mMotionTimeout = Integer.parseInt(prefs.getString(Constants.PREF_MOTION_DETECTION_TIMEOUT, "60")) * 1000; 57 | mServerConnection.updateState(mMotionItem, mMotion ? "CLOSED" : "OPEN"); 58 | } 59 | 60 | public void terminate() { 61 | mMotion = false; 62 | mServerConnection.updateState(mMotionItem, "OPEN"); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /app/src/main/java/de/vier_bier/habpanelviewer/status/ApplicationStatus.java: -------------------------------------------------------------------------------- 1 | package de.vier_bier.habpanelviewer.status; 2 | 3 | import java.util.ArrayList; 4 | import java.util.HashMap; 5 | 6 | /** 7 | * Class holding StatusItems for visualization in the Status Screen. 8 | */ 9 | public class ApplicationStatus { 10 | private final ArrayList mValues = new ArrayList<>(); 11 | private final HashMap mIndices = new HashMap<>(); 12 | 13 | public synchronized void set(String key, String value) { 14 | StatusItem item = mIndices.get(key); 15 | if (item == null) { 16 | item = new StatusItem(key, value); 17 | mValues.add(item); 18 | mIndices.put(key, item); 19 | } else { 20 | item.setValue(value); 21 | } 22 | } 23 | 24 | int getItemCount() { 25 | return mValues.size(); 26 | } 27 | 28 | StatusItem getItem(int i) { 29 | return mValues.get(i); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/src/main/java/de/vier_bier/habpanelviewer/status/StatusInfoActivity.java: -------------------------------------------------------------------------------- 1 | package de.vier_bier.habpanelviewer.status; 2 | 3 | import android.app.Activity; 4 | import android.os.Bundle; 5 | import androidx.annotation.NonNull; 6 | import androidx.appcompat.app.ActionBar; 7 | import androidx.appcompat.widget.Toolbar; 8 | import android.view.LayoutInflater; 9 | import android.view.View; 10 | import android.view.ViewGroup; 11 | import android.widget.BaseAdapter; 12 | import android.widget.ListView; 13 | import android.widget.TextView; 14 | 15 | import org.greenrobot.eventbus.EventBus; 16 | 17 | import java.util.concurrent.Executors; 18 | import java.util.concurrent.ScheduledExecutorService; 19 | import java.util.concurrent.TimeUnit; 20 | 21 | import de.vier_bier.habpanelviewer.R; 22 | import de.vier_bier.habpanelviewer.ScreenControllingActivity; 23 | 24 | /** 25 | * Status Information Activity showing interesting app or hardware details. 26 | */ 27 | public class StatusInfoActivity extends ScreenControllingActivity { 28 | private final ApplicationStatus status = new ApplicationStatus(); 29 | private ScheduledExecutorService executor; 30 | 31 | @Override 32 | protected void onCreate(Bundle savedInstanceState) { 33 | super.onCreate(savedInstanceState); 34 | 35 | final StatusItemAdapter adapter = new StatusItemAdapter(this, status); 36 | 37 | executor = Executors.newSingleThreadScheduledExecutor(); 38 | executor.scheduleWithFixedDelay(() -> { 39 | EventBus.getDefault().post(status); 40 | 41 | runOnUiThread(adapter::notifyDataSetChanged); 42 | }, 0, 1, TimeUnit.SECONDS); 43 | 44 | setContentView(R.layout.activity_statusinfo); 45 | 46 | Toolbar myToolbar = findViewById(R.id.toolbar); 47 | setSupportActionBar(myToolbar); 48 | 49 | ActionBar ab = getSupportActionBar(); 50 | if (ab != null) { 51 | ab.setDisplayHomeAsUpEnabled(true); 52 | } 53 | 54 | final ListView listview = findViewById(R.id.info_listview); 55 | listview.setAdapter(adapter); 56 | } 57 | 58 | @Override 59 | public View getScreenOnView() { 60 | return findViewById(R.id.info_listview); 61 | } 62 | 63 | @Override 64 | protected void onDestroy() { 65 | if (executor != null) { 66 | executor.shutdown(); 67 | executor = null; 68 | } 69 | super.onDestroy(); 70 | } 71 | 72 | private static class StatusItemAdapter extends BaseAdapter { 73 | private final Activity mContext; 74 | private final ApplicationStatus mStatus; 75 | 76 | StatusItemAdapter(Activity context, ApplicationStatus status) { 77 | mContext = context; 78 | mStatus = status; 79 | } 80 | 81 | @Override 82 | public int getCount() { 83 | return mStatus.getItemCount(); 84 | } 85 | 86 | @Override 87 | public StatusItem getItem(int i) { 88 | return mStatus.getItem(i); 89 | } 90 | 91 | @Override 92 | public long getItemId(int i) { 93 | return mStatus.getItem(i).getId(); 94 | } 95 | 96 | @NonNull 97 | @Override 98 | public View getView(int position, View view, @NonNull ViewGroup parent) { 99 | LayoutInflater inflater = mContext.getLayoutInflater(); 100 | View rowView= inflater.inflate(R.layout.row_commandlog, parent, false); 101 | 102 | TextView txtTitle = rowView.findViewById(R.id.name); 103 | TextView txtValue = rowView.findViewById(R.id.value); 104 | txtTitle.setText(getItem(position).getName()); 105 | txtValue.setText(getItem(position).getValue()); 106 | 107 | return rowView; 108 | } 109 | 110 | @Override 111 | public CharSequence[] getAutofillOptions() { 112 | return new CharSequence[0]; 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /app/src/main/java/de/vier_bier/habpanelviewer/status/StatusItem.java: -------------------------------------------------------------------------------- 1 | package de.vier_bier.habpanelviewer.status; 2 | 3 | /** 4 | * Info Item that will be shown in the Status Information Screen 5 | */ 6 | public class StatusItem { 7 | private static long COUNTER; 8 | private final long id = COUNTER++; 9 | 10 | private final String mName; 11 | private String mValue; 12 | 13 | StatusItem(String name, String value) { 14 | mName = name; 15 | mValue = value; 16 | } 17 | 18 | public String getName() { 19 | return mName; 20 | } 21 | 22 | public String getValue() { 23 | return mValue; 24 | } 25 | 26 | public long getId() { 27 | return id; 28 | } 29 | 30 | public void setValue(String value) { 31 | mValue = value; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/book.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbier/habpanelviewer/0c5756752e45562fc880fd096a8c67a703f83382/app/src/main/res/drawable-mdpi/book.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/browser.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbier/habpanelviewer/0c5756752e45562fc880fd096a8c67a703f83382/app/src/main/res/drawable-mdpi/browser.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/commanding.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbier/habpanelviewer/0c5756752e45562fc880fd096a8c67a703f83382/app/src/main/res/drawable-mdpi/commanding.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/configuration.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbier/habpanelviewer/0c5756752e45562fc880fd096a8c67a703f83382/app/src/main/res/drawable-mdpi/configuration.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/credentials.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbier/habpanelviewer/0c5756752e45562fc880fd096a8c67a703f83382/app/src/main/res/drawable-mdpi/credentials.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/database.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbier/habpanelviewer/0c5756752e45562fc880fd096a8c67a703f83382/app/src/main/res/drawable-mdpi/database.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbier/habpanelviewer/0c5756752e45562fc880fd096a8c67a703f83382/app/src/main/res/drawable-mdpi/logo.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ready.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbier/habpanelviewer/0c5756752e45562fc880fd096a8c67a703f83382/app/src/main/res/drawable-mdpi/ready.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/reporting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbier/habpanelviewer/0c5756752e45562fc880fd096a8c67a703f83382/app/src/main/res/drawable-mdpi/reporting.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/server.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbier/habpanelviewer/0c5756752e45562fc880fd096a8c67a703f83382/app/src/main/res/drawable-mdpi/server.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/activity_main_border.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_access_time_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_arrow_upward_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_enhanced_encryption_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_clear_all_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_delete_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_forum_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_help_outline_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_info_outline_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_play_arrow_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_preferences_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_replay_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_search_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_share_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_view_headline_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_vpn_key_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_wiki_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/side_nav_bar.xml: -------------------------------------------------------------------------------- 1 | 3 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_commandlog.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 10 | 17 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_empty.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_help.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 12 | 13 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_intro_discover.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | 32 | 33 | 47 | 48 | 60 | 61 | 75 | 76 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_log.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 11 | 18 | 19 | 23 | 24 | 35 | 36 | 44 | 45 | 53 | 63 | 64 | 65 | 72 | 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 12 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main_content.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | 17 | 18 | 25 | 26 | 32 | 33 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_preferences.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 13 | 14 | 15 | 16 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_statusinfo.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 10 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/layout/dialog_credentials.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 12 | 13 | 24 | 25 | 37 | 38 | 46 | -------------------------------------------------------------------------------- /app/src/main/res/layout/dialog_master_password.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 12 | 13 | 25 | -------------------------------------------------------------------------------- /app/src/main/res/layout/dialog_scrollable_text.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 15 | 16 | 20 | 21 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /app/src/main/res/layout/navheader_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 25 | 26 | 36 | 37 | 47 | 48 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /app/src/main/res/layout/row_commandlog.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 14 | 15 | 23 | -------------------------------------------------------------------------------- /app/src/main/res/layout/toolbar.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/menu/activity_main_drawer.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 14 | 19 | 24 | 29 | 34 | 39 | 44 | 49 | 54 | 59 | 60 | -------------------------------------------------------------------------------- /app/src/main/res/menu/commandlog_toolbar_menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/menu/help_toolbar_menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 10 | 11 | 15 | 16 | 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/res/menu/log_menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 10 | 11 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/menu/preferences_toolbar_menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 11 | 12 | 15 | 16 | 19 | 20 | -------------------------------------------------------------------------------- /app/src/main/res/menu/webview_context_menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 8 | 12 | 16 | 20 | 24 | 28 | 32 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbier/habpanelviewer/0c5756752e45562fc880fd096a8c67a703f83382/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbier/habpanelviewer/0c5756752e45562fc880fd096a8c67a703f83382/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbier/habpanelviewer/0c5756752e45562fc880fd096a8c67a703f83382/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbier/habpanelviewer/0c5756752e45562fc880fd096a8c67a703f83382/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbier/habpanelviewer/0c5756752e45562fc880fd096a8c67a703f83382/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbier/habpanelviewer/0c5756752e45562fc880fd096a8c67a703f83382/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbier/habpanelviewer/0c5756752e45562fc880fd096a8c67a703f83382/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbier/habpanelviewer/0c5756752e45562fc880fd096a8c67a703f83382/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbier/habpanelviewer/0c5756752e45562fc880fd096a8c67a703f83382/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbier/habpanelviewer/0c5756752e45562fc880fd096a8c67a703f83382/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/raw/mytruststore.bks: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbier/habpanelviewer/0c5756752e45562fc880fd096a8c67a703f83382/app/src/main/res/raw/mytruststore.bks -------------------------------------------------------------------------------- /app/src/main/res/values-de/arrays.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | niedrig 5 | mittel 6 | hoch 7 | 8 | 9 | 5.0 10 | 1.0 11 | 0.1 12 | 13 | 14 | 10 15 | 20 16 | 30 17 | 40 18 | 50 19 | 60 20 | 70 21 | 80 22 | 90 23 | 100 24 | 25 | 26 | 5 27 | 10 28 | 15 29 | 20 30 | 25 31 | 30 32 | 35 33 | 40 34 | 45 35 | 50 36 | 55 37 | 60 38 | 65 39 | 70 40 | 75 41 | 80 42 | 85 43 | 90 44 | 45 | 46 | links 47 | rechts 48 | 49 | 50 | left 51 | right 52 | 53 | 54 | 0 55 | 100 56 | 200 57 | 300 58 | 400 59 | 500 60 | 600 61 | 700 62 | 800 63 | 900 64 | 1000 65 | 66 | 67 | dunkel 68 | hell 69 | 70 | 71 | dark 72 | light 73 | 74 | -------------------------------------------------------------------------------- /app/src/main/res/values/arrays.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | low 5 | medium 6 | high 7 | 8 | 9 | 5.0 10 | 1.0 11 | 0.1 12 | 13 | 14 | 10 15 | 20 16 | 30 17 | 40 18 | 50 19 | 60 20 | 70 21 | 80 22 | 90 23 | 100 24 | 25 | 26 | 5 27 | 10 28 | 15 29 | 20 30 | 25 31 | 30 32 | 35 33 | 40 34 | 45 35 | 50 36 | 55 37 | 60 38 | 65 39 | 70 40 | 75 41 | 80 42 | 85 43 | 90 44 | 45 | 46 | left 47 | right 48 | 49 | 50 | left 51 | right 52 | 53 | 54 | 0 55 | 100 56 | 200 57 | 300 58 | 400 59 | 500 60 | 600 61 | 700 62 | 800 63 | 900 64 | 1000 65 | 66 | 67 | dark 68 | light 69 | 70 | 71 | dark 72 | light 73 | 74 | 75 | OH2 76 | OH3 77 | 78 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16dp 4 | 16dp 5 | 16dp 6 | 160dp 7 | 8 | 20sp 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/values/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #4CAF50 4 | -------------------------------------------------------------------------------- /app/src/main/res/xml/device_admin.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/xml/network_security_config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/xml/preferences_accelerometer.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 11 | 17 | 25 | -------------------------------------------------------------------------------- /app/src/main/res/xml/preferences_battery.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 11 | 17 | 23 | 29 | 35 | 36 | -------------------------------------------------------------------------------- /app/src/main/res/xml/preferences_brightness.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 11 | 17 | 23 | 30 | 31 | -------------------------------------------------------------------------------- /app/src/main/res/xml/preferences_browser.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 11 | 16 | 21 | 26 | 31 | 36 | 41 | 46 | 51 | 56 | 57 | -------------------------------------------------------------------------------- /app/src/main/res/xml/preferences_camera.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 11 | 16 | 22 | 29 | 34 | -------------------------------------------------------------------------------- /app/src/main/res/xml/preferences_connected.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 8 | 13 | 19 | 20 | 21 | 23 | 28 | 34 | 41 | 42 | 43 | 45 | 50 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /app/src/main/res/xml/preferences_connection.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 9 | 14 | 21 | 22 | 23 | 26 | 30 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /app/src/main/res/xml/preferences_docking_state.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 11 | 17 | 18 | -------------------------------------------------------------------------------- /app/src/main/res/xml/preferences_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 12 | 13 | 17 | 18 | 23 | 24 | 29 | 30 | 34 | 35 | 39 | 40 | -------------------------------------------------------------------------------- /app/src/main/res/xml/preferences_motion.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 11 | 17 | 25 | 33 | 41 | 49 | 50 | -------------------------------------------------------------------------------- /app/src/main/res/xml/preferences_noise_level.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 12 | 18 | 25 | 26 | -------------------------------------------------------------------------------- /app/src/main/res/xml/preferences_other.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 10 | 15 | 16 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/res/xml/preferences_pressure.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 11 | 17 | 23 | 30 | 31 | -------------------------------------------------------------------------------- /app/src/main/res/xml/preferences_proximity.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 11 | 17 | 18 | -------------------------------------------------------------------------------- /app/src/main/res/xml/preferences_reporting.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 9 | 10 | 14 | 15 | 19 | 20 | 24 | 25 | 29 | 30 | 34 | 35 | 36 | 37 | 40 | 41 | 45 | 46 | 50 | 51 | 55 | 56 | 60 | 61 | 65 | 66 | 70 | 71 | 75 | 76 | 80 | 81 | -------------------------------------------------------------------------------- /app/src/main/res/xml/preferences_restart.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 11 | 18 | 19 | -------------------------------------------------------------------------------- /app/src/main/res/xml/preferences_screen.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 11 | 17 | 18 | -------------------------------------------------------------------------------- /app/src/main/res/xml/preferences_temperature.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 11 | 17 | 23 | 30 | 31 | -------------------------------------------------------------------------------- /app/src/main/res/xml/preferences_ui.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | android:persistent="false" 4 | android:title="@string/pref_ui"> 5 | 6 | 13 | 20 | 25 | 30 | 35 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /app/src/main/res/xml/preferences_usage.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 11 | 17 | 24 | 25 | -------------------------------------------------------------------------------- /app/src/main/res/xml/preferences_volume.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 11 | 17 | 18 | -------------------------------------------------------------------------------- /app/src/test/java/de/vier_bier/habpanelviewer/openhab/OpenhabSseConnectionTest.java: -------------------------------------------------------------------------------- 1 | package de.vier_bier.habpanelviewer.openhab; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.assertEquals; 6 | 7 | public class OpenhabSseConnectionTest { 8 | @Test 9 | public void testBuildUrl() { 10 | OpenhabSseConnection c = new OpenhabSseConnection(); 11 | c.setServerUrl("URL"); 12 | c.setItemNames("cmdName"); 13 | assertEquals("URL/rest/events?topics=openhab/items/cmdName/command", c.buildUrl()); 14 | 15 | c.setItemNames("cmdName","item1", "item2"); 16 | assertEquals("URL/rest/events?topics=openhab/items/item1/statechanged,openhab/items/item2/statechanged,openhab/items/cmdName/command", c.buildUrl()); 17 | 18 | c.setServer("URL", OpenhabSseConnection.OHVersion.OH2); 19 | assertEquals("URL/rest/events?topics=smarthome/items/item1/statechanged,smarthome/items/item2/statechanged,smarthome/items/cmdName/command", c.buildUrl()); 20 | 21 | c.setItemNames("cmdName"); 22 | assertEquals("URL/rest/events?topics=smarthome/items/cmdName/command", c.buildUrl()); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | repositories { 5 | mavenCentral() 6 | google() 7 | } 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:7.0.4' 10 | 11 | // NOTE: Do not place your application dependencies here; they belong 12 | // in the individual module build.gradle files 13 | } 14 | } 15 | 16 | allprojects { 17 | repositories { 18 | mavenCentral() 19 | flatDir { 20 | dirs 'libs' 21 | } 22 | maven { url "https://jitpack.io" } 23 | google() 24 | } 25 | } 26 | 27 | task clean(type: Delete) { 28 | delete rootProject.buildDir 29 | } 30 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | ## For more details on how to configure your build environment visit 2 | # http://www.gradle.org/docs/current/userguide/build_environment.html 3 | # 4 | # Specifies the JVM arguments used for the daemon process. 5 | # The setting is particularly useful for tweaking memory settings. 6 | # Default value: -Xmx1024m -XX:MaxPermSize=256m 7 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 8 | # 9 | # When configured, Gradle will run in incubating parallel mode. 10 | # This option should only be used with decoupled projects. More details, visit 11 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 12 | # org.gradle.parallel=true 13 | #Sun Aug 01 11:05:47 CEST 2021 14 | org.gradle.jvmargs=-Xmx2048M -Dkotlin.daemon.jvm.options\="-Xmx1536M" 15 | android.enableJetifier=true 16 | android.useAndroidX=true 17 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbier/habpanelviewer/0c5756752e45562fc880fd096a8c67a703f83382/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Dec 11 19:00:18 CET 2020 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip 7 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /habpanelviewer.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /issue_template.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | ## Expected Behavior 7 | 8 | 9 | 10 | ## Current Behavior 11 | 12 | 13 | 14 | 15 | 16 | ## Possible Solution 17 | 18 | 19 | ## Steps to Reproduce (for Bugs) 20 | 21 | 1. 22 | 2. 23 | 24 | ## Your Environment 25 | 26 | * Device 27 | * Android Version 28 | * HabPanelViewer Version 29 | 30 | -------------------------------------------------------------------------------- /logo.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbier/habpanelviewer/0c5756752e45562fc880fd096a8c67a703f83382/logo.xcf -------------------------------------------------------------------------------- /logo2.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbier/habpanelviewer/0c5756752e45562fc880fd096a8c67a703f83382/logo2.xcf -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | --------------------------------------------------------------------------------