├── .gitignore
├── LICENSE
├── README.md
├── app
├── build.gradle.kts
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── assets
│ └── jsinterfacetest.html
│ ├── java
│ └── me
│ │ └── rapierxbox
│ │ └── shellyelevatev2
│ │ ├── BootReceiver.java
│ │ ├── Constants.java
│ │ ├── HttpServer.java
│ │ ├── MainActivity.kt
│ │ ├── SettingsActivity.kt
│ │ ├── SettingsParser.java
│ │ ├── ShellyElevateApplication.java
│ │ ├── ShellyElevateJavascriptInterface.java
│ │ ├── backbutton
│ │ ├── BackAccessibilityService.kt
│ │ └── FloatingBackButtonService.kt
│ │ ├── helper
│ │ ├── DeviceHelper.java
│ │ ├── DeviceSensorManager.java
│ │ ├── MediaHelper.java
│ │ ├── ServiceHelper.java
│ │ └── SwipeHelper.java
│ │ ├── mqtt
│ │ ├── MQTTServer.java
│ │ └── ShellyElevateMQTTCallback.java
│ │ └── screensavers
│ │ ├── DigitalClockAndDateScreenSaver.java
│ │ ├── DigitalClockScreenSaver.java
│ │ ├── ScreenOffScreenSaver.java
│ │ ├── ScreenSaver.java
│ │ ├── ScreenSaverManager.java
│ │ ├── ScreenSaverManagerHolder.kt
│ │ ├── UserInteractionReceiver.kt
│ │ └── activities
│ │ └── DigitalClockAndDateScreenSaverActivity.kt
│ └── res
│ ├── drawable
│ ├── ic_back_arrow.xml
│ ├── ic_exit.xml
│ ├── ic_launcher_background.xml
│ ├── ic_launcher_foreground.xml
│ ├── ic_settings.xml
│ └── round_button_bg.xml
│ ├── layout
│ ├── digital_clock_and_date_screen_saver.xml
│ ├── floating_button_layout.xml
│ ├── main_activity.xml
│ └── settings_activity.xml
│ ├── menu
│ └── settings_menu.xml
│ ├── mipmap-anydpi-v26
│ ├── ic_launcher.xml
│ └── ic_launcher_round.xml
│ ├── mipmap-hdpi
│ ├── ic_launcher.webp
│ └── ic_launcher_round.webp
│ ├── mipmap-mdpi
│ ├── ic_launcher.webp
│ └── ic_launcher_round.webp
│ ├── mipmap-xhdpi
│ ├── ic_launcher.webp
│ └── ic_launcher_round.webp
│ ├── mipmap-xxhdpi
│ ├── ic_launcher.webp
│ └── ic_launcher_round.webp
│ ├── mipmap-xxxhdpi
│ ├── ic_launcher.webp
│ └── ic_launcher_round.webp
│ ├── values-night
│ ├── colors.xml
│ ├── theme_overlays.xml
│ └── themes.xml
│ ├── values
│ ├── arrays.xml
│ ├── colors.xml
│ ├── donottranslate.xml
│ ├── strings.xml
│ ├── theme_overlays.xml
│ └── themes.xml
│ └── xml
│ └── accessibility_service_config.xml
├── gradle.properties
├── gradle
├── libs.versions.toml
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── settings.gradle.kts
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/caches
5 | /.idea/libraries
6 | /.idea/modules.xml
7 | /.idea/workspace.xml
8 | /.idea/navEditor.xml
9 | /.idea/assetWizardSettings.xml
10 | .DS_Store
11 | /build
12 | /captures
13 | .externalNativeBuild
14 | .cxx
15 | local.properties
16 | app/build/*
17 | .kotlin/*
18 | .idea/*
19 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 RapierXbox
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ShellyElevate
2 | > [!IMPORTANT]
3 | > Make sure to update your Home Assistant config.yaml file to comply with the new API. You can find the new yaml in the wiki.
4 |
5 | > [!CAUTION]
6 | > All content in this repository is provided "as is" and may render your device unusable. Always exercise caution when working with your device. No warranty or guarantee is provided.
7 |
8 | Shelly Elevate is an app designed for the Shelly Wall Display, codenamed Stargate, that add's full Home Assistant functionality to the device. The Wiki also provides a detailed tutorial on hacking your device, installing a launcher, configuring Shelly Elevate, and integrating it with Home Assistant.
9 |
10 | https://github.com/user-attachments/assets/adf46edd-9bf1-45da-b553-bf7781d17fbd
11 |
12 | ### Features
13 | * full screen Home Assistant controll
14 | * automatic Home Assistant IP detection
15 | * autostart
16 | * swipe to switch
17 | * full access to sensors and the relay over a api
18 | * playing sound files over the api
19 | * hidden settings
20 | * automatic brightness
21 | * multiple screen savers with settable delay
22 | * changing settings over the api
23 | * lite mode
24 | * support for all displays
25 | * viewing any url
26 |
27 | And of couse you can disable each feature compeltely.
28 |
29 | If you'd like to contribute or have a feature request, please do so by creating a pull request or opening an issue.
30 |
31 | ### Dont know where to start?
32 | Hack your display using the [guide](https://github.com/RapierXbox/ShellyElevate/wiki/Jailbreak) or check out the [releases](https://github.com/RapierXbox/ShellyElevate/releases).
33 | If you want to add the modified display to Home Assistant, check out [this](https://github.com/RapierXbox/ShellyElevate/wiki/Integration-into-Home-Assistant).
34 |
--------------------------------------------------------------------------------
/app/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.android.application)
3 | alias(libs.plugins.jetbrains.kotlin.android)
4 | }
5 |
6 | android {
7 | namespace = "me.rapierxbox.shellyelevatev2"
8 | compileSdk = 35
9 |
10 | defaultConfig {
11 | applicationId = "me.rapierxbox.shellyelevatev2"
12 | minSdk = 24
13 | //noinspection ExpiredTargetSdkVersion
14 | targetSdk = 24
15 | versionCode = 2_03_00
16 | versionName = "2.3.0"
17 |
18 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
19 | vectorDrawables {
20 | useSupportLibrary = true
21 | }
22 | }
23 |
24 | buildTypes {
25 | release {
26 | isMinifyEnabled = false
27 | proguardFiles(
28 | getDefaultProguardFile("proguard-android-optimize.txt"),
29 | "proguard-rules.pro"
30 | )
31 | }
32 | }
33 | compileOptions {
34 | sourceCompatibility = JavaVersion.VERSION_17
35 | targetCompatibility = JavaVersion.VERSION_17
36 | }
37 | kotlinOptions {
38 | jvmTarget = "17"
39 | }
40 | packaging {
41 | resources {
42 | excludes += "/META-INF/{AL2.0,LGPL2.1}"
43 | }
44 | }
45 | buildFeatures {
46 | viewBinding = true
47 | buildConfig = true
48 | }
49 | }
50 |
51 | dependencies {
52 |
53 | implementation(libs.appcompat)
54 | implementation(libs.material)
55 | implementation(libs.lifecycle.runtime.ktx)
56 | implementation(libs.preference)
57 | implementation(libs.nanohttpd)
58 | implementation(libs.org.eclipse.paho.mqttv5.client)
59 |
60 | implementation(platform(libs.okhttpbom))
61 | implementation(libs.okhttp)
62 | implementation(libs.appcompat)
63 |
64 | testImplementation(libs.junit)
65 | androidTestImplementation(libs.ext.junit)
66 | androidTestImplementation(libs.espresso.core)
67 | }
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
12 |
15 |
16 |
19 |
20 |
21 |
30 |
34 |
35 |
39 |
43 |
44 |
45 |
46 |
47 |
50 |
51 |
52 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
70 |
71 |
72 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
--------------------------------------------------------------------------------
/app/src/main/assets/jsinterfacetest.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | JS Interface Test
7 |
14 |
15 |
16 | Test ShellyElevate Interface
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
104 |
105 |
--------------------------------------------------------------------------------
/app/src/main/java/me/rapierxbox/shellyelevatev2/BootReceiver.java:
--------------------------------------------------------------------------------
1 | package me.rapierxbox.shellyelevatev2;
2 |
3 | import static android.content.Context.MODE_PRIVATE;
4 | import static me.rapierxbox.shellyelevatev2.Constants.SHARED_PREFERENCES_NAME;
5 | import static me.rapierxbox.shellyelevatev2.Constants.SP_LITE_MODE;
6 |
7 | import android.content.BroadcastReceiver;
8 | import android.content.Context;
9 | import android.content.Intent;
10 | import android.util.Log;
11 |
12 | import java.util.Objects;
13 |
14 |
15 | public class BootReceiver extends BroadcastReceiver {
16 | @Override
17 | public void onReceive(Context context, Intent intent) {
18 | if (Objects.equals(intent.getAction(), Intent.ACTION_BOOT_COMPLETED)) {
19 | Log.i("ShellyElevateV2", "Starting... (If not already started)");
20 |
21 | if (context.getSharedPreferences(SHARED_PREFERENCES_NAME, MODE_PRIVATE).getBoolean(SP_LITE_MODE, false)) {
22 | Intent appIntent = new Intent(context, ShellyElevateApplication.class);
23 | context.startService(appIntent);
24 | } else {
25 | Log.i("ShellyElevateV2", "Starting MainActivity");
26 | Intent activityIntent = new Intent(context, MainActivity.class);
27 | context.startActivity(activityIntent);
28 | }
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/app/src/main/java/me/rapierxbox/shellyelevatev2/Constants.java:
--------------------------------------------------------------------------------
1 | package me.rapierxbox.shellyelevatev2;
2 |
3 | public class Constants {
4 | public static final String SHARED_PREFERENCES_NAME = "ShellyElevateV2";
5 |
6 | public static final String SP_WEBVIEW_URL = "webviewUrl";
7 | public static final String SP_HTTP_SERVER_ENABLED = "httpServer";
8 | public static final String SP_SWITCH_ON_SWIPE = "switchOnSwipe";
9 | public static final String SP_AUTOMATIC_BRIGHTNESS = "automaticBrightness";
10 | public static final String SP_MIN_BRIGHTNESS = "minBrightness";
11 | public static final String SP_BRIGHTNESS = "brightness";
12 | public static final String SP_SCREEN_SAVER_ENABLED = "screenSaver";
13 | public static final String SP_SCREEN_SAVER_DELAY = "screenSaverDelay";
14 | public static final String SP_SCREEN_SAVER_ID = "screenSaverId";
15 | public static final String SP_LITE_MODE = "liteMode";
16 | public static final String SP_EXTENDED_JAVASCRIPT_INTERFACE = "extendedJavascriptInterface";
17 |
18 | public static final String SP_MQTT_ENABLED = "mqttEnabled";
19 | public static final String SP_MQTT_BROKER = "mqttBroker";
20 | public static final String SP_MQTT_PORT = "mqttPort";
21 | public static final String SP_MQTT_USERNAME = "mqttUsername";
22 | public static final String SP_MQTT_PASSWORD = "mqttPassword";
23 | public static final String SP_MQTT_DEVICE_ID = "mqttDeviceId";
24 |
25 | public static final String SP_DEPRECATED_HA_IP = "homeAssistantIp";
26 |
27 | public static final String INTENT_WEBVIEW_REFRESH = "me.rapierxbox.shellyelevatev2.REFRESH_WEBVIEW";
28 | public static final String INTENT_WEBVIEW_INJECT_JAVASCRIPT = "me.rapierxbox.shellyelevatev2.WEBVIEW_INJECT_JAVASCRIPT";
29 |
30 | public static final String MQTT_TOPIC_CONFIG_DEVICE = "homeassistant/device/%s/config";
31 | public static final String MQTT_TOPIC_STATUS = "shellyelevatev2/%s/status";
32 |
33 | public static final String MQTT_TOPIC_TEMP_SENSOR = "shellyelevatev2/%s/temp";
34 | public static final String MQTT_TOPIC_HUM_SENSOR = "shellyelevatev2/%s/hum";
35 | public static final String MQTT_TOPIC_LUX_SENSOR = "shellyelevatev2/%s/lux";
36 | public static final String MQTT_TOPIC_RELAY_STATE = "shellyelevatev2/%s/relay_state";
37 | public static final String MQTT_TOPIC_RELAY_COMMAND = "shellyelevatev2/%s/relay_command";
38 | public static final String MQTT_TOPIC_SLEEP_BUTTON = "shellyelevatev2/%s/sleep";
39 | public static final String MQTT_TOPIC_WAKE_BUTTON = "shellyelevatev2/%s/wake";
40 | public static final String MQTT_TOPIC_REFRESH_WEBVIEW_BUTTON = "shellyelevatev2/%s/refresh_webview";
41 | public static final String MQTT_TOPIC_REBOOT_BUTTON = "shellyelevatev2/%s/reboot";
42 | public static final String MQTT_TOPIC_SWIPE_EVENT = "shellyelevatev2/%s/swipe_event";
43 | public static final String MQTT_TOPIC_SLEEPING_BINARY_SENSOR = "shellyelevatev2/%s/sleeping";
44 |
45 | public static final String MQTT_TOPIC_HOME_ASSISTANT_STATUS = "homeassistant/status";
46 | }
47 |
--------------------------------------------------------------------------------
/app/src/main/java/me/rapierxbox/shellyelevatev2/HttpServer.java:
--------------------------------------------------------------------------------
1 | package me.rapierxbox.shellyelevatev2;
2 |
3 | import static me.rapierxbox.shellyelevatev2.Constants.INTENT_WEBVIEW_INJECT_JAVASCRIPT;
4 | import static me.rapierxbox.shellyelevatev2.Constants.INTENT_WEBVIEW_REFRESH;
5 | import static me.rapierxbox.shellyelevatev2.ShellyElevateApplication.mApplicationContext;
6 | import static me.rapierxbox.shellyelevatev2.ShellyElevateApplication.mDeviceHelper;
7 | import static me.rapierxbox.shellyelevatev2.ShellyElevateApplication.mDeviceSensorManager;
8 |
9 | import android.content.Intent;
10 | import android.net.Uri;
11 | import android.util.Log;
12 | import android.widget.Toast;
13 |
14 | import androidx.localbroadcastmanager.content.LocalBroadcastManager;
15 |
16 | import org.json.JSONException;
17 | import org.json.JSONObject;
18 |
19 | import java.io.IOException;
20 | import java.util.HashMap;
21 | import java.util.Map;
22 |
23 | import fi.iki.elonen.NanoHTTPD;
24 | import me.rapierxbox.shellyelevatev2.helper.MediaHelper;
25 | import me.rapierxbox.shellyelevatev2.screensavers.ScreenSaverManagerHolder;
26 |
27 | public class HttpServer extends NanoHTTPD {
28 | public HttpServer() {
29 | super(8080);
30 | }
31 |
32 | SettingsParser mSettingsParser = new SettingsParser();
33 | MediaHelper mMediaHelper = new MediaHelper();
34 |
35 | @Override
36 | public Response serve(IHTTPSession session) {
37 | Method method = session.getMethod();
38 | String uri = session.getUri();
39 | JSONObject jsonResponse = new JSONObject();
40 |
41 | try {
42 | if (uri.startsWith("/media/")) {
43 | return handleMediaRequest(session);
44 | } else if (uri.startsWith("/device/")) {
45 | return handleDeviceRequest(session);
46 | } else if (uri.startsWith("/webview/")) {
47 | return handleWebviewRequest(session);
48 | } else if (uri.equals("/settings")) {
49 | if (method.equals(Method.GET)) {
50 | jsonResponse.put("success", true);
51 | jsonResponse.put("settings", mSettingsParser.getSettings());
52 | } else if (method.equals(Method.POST)) {
53 | Map files = new HashMap<>();
54 | session.parseBody(files);
55 | String postData = files.get("postData");
56 | JSONObject jsonObject = new JSONObject(postData);
57 |
58 | mSettingsParser.setSettings(jsonObject);
59 |
60 | jsonResponse.put("success", true);
61 | jsonResponse.put("settings", mSettingsParser.getSettings());
62 | } else {
63 | jsonResponse.put("success", false);
64 | jsonResponse.put("error", "Invalid request method");
65 | }
66 | return newFixedLengthResponse(Response.Status.OK, "application/json", jsonResponse.toString());
67 | } else if (uri.equals("/")) {
68 | return newFixedLengthResponse(Response.Status.OK, "application/json",
69 | "{\"message\": \"ShellyElevateV2 by RapierXbox\"}");
70 | }
71 | } catch (JSONException | ResponseException | IOException e) {
72 | Log.e("HttpServer", "Error handling request", e);
73 | }
74 |
75 | return newFixedLengthResponse(Response.Status.NOT_FOUND, "application/json", jsonResponse.toString());
76 | }
77 |
78 | private Response handleWebviewRequest(IHTTPSession session) throws JSONException, ResponseException, IOException {
79 | Method method = session.getMethod();
80 | String uri = session.getUri();
81 | JSONObject jsonResponse = new JSONObject();
82 |
83 | switch (uri.replace("/webview/", "")) {
84 | case "refresh":
85 | if (method.equals(Method.GET)) {
86 | Intent intent = new Intent(INTENT_WEBVIEW_REFRESH);
87 | LocalBroadcastManager.getInstance(ShellyElevateApplication.mApplicationContext).sendBroadcast(intent);
88 | jsonResponse.put("success", true);
89 | }
90 | case "inject":
91 | if (method.equals(Method.POST)) {
92 | Map files = new HashMap<>();
93 | session.parseBody(files);
94 | String postData = files.get("postData");
95 | JSONObject jsonObject = new JSONObject(postData);
96 |
97 | String javascript = jsonObject.getString("javascript");
98 |
99 | Intent intent = new Intent(INTENT_WEBVIEW_INJECT_JAVASCRIPT);
100 | intent.putExtra("javascript", javascript);
101 | LocalBroadcastManager.getInstance(ShellyElevateApplication.mApplicationContext).sendBroadcast(intent);
102 |
103 | jsonResponse.put("success", true);
104 | }
105 | }
106 |
107 | return newFixedLengthResponse(jsonResponse.getBoolean("success") ? Response.Status.OK : Response.Status.INTERNAL_ERROR, "application/json", jsonResponse.toString());
108 | }
109 |
110 | private Response handleMediaRequest(IHTTPSession session) throws JSONException, ResponseException, IOException {
111 | Method method = session.getMethod();
112 | String uri = session.getUri();
113 | JSONObject jsonResponse = new JSONObject();
114 |
115 | switch (uri.replace("/media/", "")) {
116 | case "play":
117 | if (method.equals(Method.POST)) {
118 | Map files = new HashMap<>();
119 | session.parseBody(files);
120 | String postData = files.get("postData");
121 | JSONObject jsonObject = new JSONObject(postData);
122 |
123 | Uri mediaUri = Uri.parse(jsonObject.getString("url"));
124 | boolean music = jsonObject.getBoolean("music");
125 | double volume = jsonObject.getDouble("volume");
126 |
127 | mMediaHelper.setVolume(volume);
128 |
129 | if (music) {
130 | mMediaHelper.playMusic(mediaUri);
131 | } else {
132 | mMediaHelper.playEffect(mediaUri);
133 | }
134 |
135 | jsonResponse.put("success", true);
136 | jsonResponse.put("url", jsonObject.getString("url"));
137 | jsonResponse.put("music", music);
138 | jsonResponse.put("volume", volume);
139 | } else {
140 | jsonResponse.put("success", false);
141 | jsonResponse.put("error", "Invalid request method");
142 | }
143 | break;
144 | case "pause":
145 | if (method.equals(Method.POST)) {
146 | mMediaHelper.pauseMusic();
147 | jsonResponse.put("success", true);
148 | } else {
149 | jsonResponse.put("success", false);
150 | jsonResponse.put("error", "Invalid request method");
151 | }
152 | break;
153 | case "resume":
154 | if (method.equals(Method.POST)) {
155 | mMediaHelper.resumeMusic();
156 | jsonResponse.put("success", true);
157 | } else {
158 | jsonResponse.put("success", false);
159 | jsonResponse.put("error", "Invalid request method");
160 | }
161 | break;
162 | case "stop":
163 | if (method.equals(Method.POST)) {
164 | mMediaHelper.stopAll();
165 | jsonResponse.put("success", true);
166 | } else {
167 | jsonResponse.put("success", false);
168 | jsonResponse.put("error", "Invalid request method");
169 | }
170 | break;
171 | case "volume":
172 | if (method.equals(Method.POST)) {
173 | Map files = new HashMap<>();
174 | session.parseBody(files);
175 | String postData = files.get("postData");
176 | JSONObject jsonObject = new JSONObject(postData);
177 |
178 | double volume = jsonObject.getDouble("volume");
179 |
180 | mMediaHelper.setVolume(volume);
181 |
182 | jsonResponse.put("success", true);
183 | jsonResponse.put("volume", mMediaHelper.getVolume());
184 | } else if (method.equals(Method.GET)) {
185 | jsonResponse.put("success", true);
186 | jsonResponse.put("volume", mMediaHelper.getVolume());
187 | } else {
188 | jsonResponse.put("success", false);
189 | jsonResponse.put("error", "Invalid request method");
190 | }
191 | break;
192 | default:
193 | jsonResponse.put("success", false);
194 | jsonResponse.put("error", "Invalid request URI");
195 | break;
196 | }
197 |
198 | return newFixedLengthResponse(jsonResponse.getBoolean("success") ? Response.Status.OK : Response.Status.INTERNAL_ERROR, "application/json", jsonResponse.toString());
199 | }
200 |
201 | private Response handleDeviceRequest(IHTTPSession session) throws JSONException, ResponseException, IOException {
202 | Method method = session.getMethod();
203 | String uri = session.getUri();
204 | JSONObject jsonResponse = new JSONObject();
205 |
206 | switch (uri.replace("/device/", "")) {
207 | case "relay":
208 | if (method.equals(Method.GET)) {
209 | jsonResponse.put("success", true);
210 | jsonResponse.put("state", mDeviceHelper.getRelay());
211 | } else if (method.equals(Method.POST)) {
212 | Map files = new HashMap<>();
213 | session.parseBody(files);
214 | String postData = files.get("postData");
215 | JSONObject jsonObject = new JSONObject(postData);
216 |
217 | mDeviceHelper.setRelay(jsonObject.getBoolean("state"));
218 | jsonResponse.put("success", true);
219 | jsonResponse.put("state", mDeviceHelper.getRelay());
220 | } else {
221 | jsonResponse.put("success", false);
222 | jsonResponse.put("error", "Invalid request method");
223 | }
224 | break;
225 | case "getTemperature":
226 | if (method.equals(Method.GET)) {
227 | jsonResponse.put("success", true);
228 | jsonResponse.put("temperature", mDeviceHelper.getTemperature());
229 | } else {
230 | jsonResponse.put("success", false);
231 | jsonResponse.put("error", "Invalid request method");
232 | }
233 | break;
234 | case "getHumidity":
235 | if (method.equals(Method.GET)) {
236 | jsonResponse.put("success", true);
237 | jsonResponse.put("humidity", mDeviceHelper.getHumidity());
238 | } else {
239 | jsonResponse.put("success", false);
240 | jsonResponse.put("error", "Invalid request method");
241 | }
242 | break;
243 | case "getLux":
244 | if (method.equals(Method.GET)) {
245 | jsonResponse.put("success", true);
246 | jsonResponse.put("lux", mDeviceSensorManager.getLastMeasuredLux());
247 | } else {
248 | jsonResponse.put("success", false);
249 | jsonResponse.put("error", "Invalid request method");
250 | }
251 | break;
252 | case "wake":
253 | jsonResponse.put("success", false);
254 | if (method.equals(Method.POST)) {
255 | ScreenSaverManagerHolder.getInstance().stopScreenSaver();
256 | jsonResponse.put("success", true);
257 | }
258 | break;
259 | case "sleep":
260 | jsonResponse.put("success", false);
261 | if (method.equals(Method.POST)) {
262 | ScreenSaverManagerHolder.getInstance().startScreenSaver();
263 | jsonResponse.put("success", true);
264 | }
265 | break;
266 | case "reboot":
267 | jsonResponse.put("success", false);
268 | if (method.equals(Method.POST)) {
269 | long deltaTime = System.currentTimeMillis() - ShellyElevateApplication.getApplicationStartTime();
270 | deltaTime /= 1000;
271 | if (deltaTime > 20) {
272 | try {
273 | Runtime.getRuntime().exec("reboot");
274 | jsonResponse.put("success", true);
275 | } catch (IOException e) {
276 | Log.e("MQTT", "Error rebooting:", e);
277 | }
278 | } else {
279 | Toast.makeText(mApplicationContext, "Please wait %s seconds before rebooting".replace("%s", String.valueOf(20 - deltaTime)), Toast.LENGTH_LONG).show();
280 | }
281 | }
282 | break;
283 | default:
284 | jsonResponse.put("success", false);
285 | jsonResponse.put("error", "Invalid request URI");
286 | break;
287 | }
288 |
289 | return newFixedLengthResponse(jsonResponse.getBoolean("success") ? Response.Status.OK : Response.Status.INTERNAL_ERROR, "application/json", jsonResponse.toString());
290 | }
291 |
292 | public void onDestroy() {
293 | closeAllConnections();
294 | stop();
295 | mMediaHelper.onDestroy();
296 | }
297 | }
298 |
--------------------------------------------------------------------------------
/app/src/main/java/me/rapierxbox/shellyelevatev2/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package me.rapierxbox.shellyelevatev2
2 |
3 | import android.annotation.SuppressLint
4 | import android.content.BroadcastReceiver
5 | import android.content.Context
6 | import android.content.Intent
7 | import android.content.IntentFilter
8 | import android.os.Bundle
9 | import android.util.Log
10 | import android.view.MotionEvent
11 | import android.webkit.WebChromeClient
12 | import android.webkit.WebSettings
13 | import android.webkit.WebViewClient
14 | import androidx.activity.ComponentActivity
15 | import androidx.activity.enableEdgeToEdge
16 | import androidx.localbroadcastmanager.content.LocalBroadcastManager
17 | import me.rapierxbox.shellyelevatev2.BuildConfig
18 | import me.rapierxbox.shellyelevatev2.Constants.INTENT_WEBVIEW_INJECT_JAVASCRIPT
19 | import me.rapierxbox.shellyelevatev2.Constants.INTENT_WEBVIEW_REFRESH
20 | import me.rapierxbox.shellyelevatev2.Constants.SHARED_PREFERENCES_NAME
21 | import me.rapierxbox.shellyelevatev2.ShellyElevateApplication.mShellyElevateJavascriptInterface
22 | import me.rapierxbox.shellyelevatev2.ShellyElevateApplication.mSwipeHelper
23 | import me.rapierxbox.shellyelevatev2.databinding.MainActivityBinding
24 | import me.rapierxbox.shellyelevatev2.helper.ServiceHelper
25 | import me.rapierxbox.shellyelevatev2.screensavers.ScreenSaverManagerHolder
26 | import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
27 |
28 | class MainActivity : ComponentActivity() {
29 | private lateinit var binding: MainActivityBinding // Declare the binding object
30 |
31 | private var clicksButtonRight: Int = 0
32 | private var clicksButtonLeft: Int = 0
33 |
34 | private var webviewRefreshBroadcastReceiver: BroadcastReceiver = object : BroadcastReceiver() {
35 | override fun onReceive(context: Context?, intent: Intent?) {
36 | binding.myWebView.loadUrl(ServiceHelper.getWebviewUrl())
37 | }
38 | }
39 |
40 | private var webviewJavascriptInjectorBroadcastReceiver: BroadcastReceiver = object : BroadcastReceiver() {
41 | override fun onReceive(context: Context?, intent: Intent?) {
42 | intent?.getStringExtra("javascript")?.let { extra ->
43 | binding.myWebView.evaluateJavascript(extra, null)
44 | }
45 | }
46 | }
47 |
48 | @SuppressLint("ClickableViewAccessibility")
49 | private fun setupSettingsButtons() {
50 | binding.settingButtonOverlayRight.setOnTouchListener { _, event ->
51 | if (event.action == MotionEvent.ACTION_DOWN) {
52 | clicksButtonRight++
53 | }
54 | return@setOnTouchListener false
55 | }
56 |
57 | binding.settingButtonOverlayLeft.setOnTouchListener { _, event ->
58 | if (event.action == MotionEvent.ACTION_DOWN) {
59 | if (clicksButtonRight == 10) {
60 | clicksButtonLeft++
61 | } else {
62 | clicksButtonRight = 0
63 | clicksButtonLeft = 0
64 | }
65 |
66 | if (clicksButtonLeft == 10) {
67 | startActivity(Intent(this, SettingsActivity::class.java))
68 |
69 | clicksButtonRight = 0
70 | clicksButtonLeft = 0
71 | }
72 | }
73 | return@setOnTouchListener false
74 | }
75 | }
76 |
77 | @SuppressLint("SetJavaScriptEnabled")
78 | private fun configureWebView() {
79 | val webSettings: WebSettings = binding.myWebView.settings
80 |
81 | webSettings.javaScriptEnabled = true
82 | webSettings.domStorageEnabled = true
83 | webSettings.databaseEnabled = true
84 |
85 | webSettings.javaScriptCanOpenWindowsAutomatically = true
86 |
87 | webSettings.mixedContentMode = WebSettings.MIXED_CONTENT_ALWAYS_ALLOW
88 |
89 | binding.myWebView.webViewClient = WebViewClient()
90 | binding.myWebView.webChromeClient = WebChromeClient()
91 |
92 | binding.myWebView.addJavascriptInterface(mShellyElevateJavascriptInterface, "ShellyElevate")
93 |
94 | binding.myWebView.loadUrl(ServiceHelper.getWebviewUrl())
95 | }
96 |
97 | override fun onResume() {
98 | super.onResume()
99 | if (binding.myWebView.originalUrl?.toHttpUrlOrNull() != ServiceHelper.getWebviewUrl().toHttpUrlOrNull())
100 | binding.myWebView.loadUrl(ServiceHelper.getWebviewUrl())
101 | }
102 |
103 | @SuppressLint("ClickableViewAccessibility")
104 | override fun onCreate(savedInstanceState: Bundle?) {
105 | super.onCreate(savedInstanceState)
106 | enableEdgeToEdge()
107 |
108 | binding = MainActivityBinding.inflate(layoutInflater) // Inflate the binding
109 | setContentView(binding.root) // Set the content view using binding.root
110 |
111 | configureWebView()
112 | setupSettingsButtons()
113 |
114 | binding.swipeDetectionOverlay.setOnTouchListener { _, event ->
115 | if (ScreenSaverManagerHolder.getInstance().onTouchEvent()) {
116 | Log.d("ShellyElevateV2", "Touch blocked by ScreenSaverManager")
117 | return@setOnTouchListener true
118 | }
119 | mSwipeHelper.onTouchEvent(event)
120 | binding.myWebView.onTouchEvent(event)
121 |
122 | return@setOnTouchListener true
123 | }
124 |
125 | val localBroadcastManager: LocalBroadcastManager = LocalBroadcastManager.getInstance(this)
126 | localBroadcastManager.registerReceiver(webviewRefreshBroadcastReceiver, IntentFilter(INTENT_WEBVIEW_REFRESH))
127 | localBroadcastManager.registerReceiver(webviewJavascriptInjectorBroadcastReceiver, IntentFilter(INTENT_WEBVIEW_INJECT_JAVASCRIPT))
128 |
129 | if (!getSharedPreferences(SHARED_PREFERENCES_NAME, MODE_PRIVATE).getBoolean("settingEverShown", false) || BuildConfig.DEBUG)
130 | startActivity(Intent(this, SettingsActivity::class.java))
131 | }
132 | }
--------------------------------------------------------------------------------
/app/src/main/java/me/rapierxbox/shellyelevatev2/SettingsActivity.kt:
--------------------------------------------------------------------------------
1 | package me.rapierxbox.shellyelevatev2
2 |
3 | import android.annotation.SuppressLint
4 | import android.content.Intent
5 | import android.os.Bundle
6 | import android.provider.Settings
7 | import android.util.Log
8 | import android.view.Menu
9 | import android.view.MenuItem
10 | import android.view.inputmethod.EditorInfo
11 | import android.widget.Toast
12 | import androidx.appcompat.app.AppCompatActivity
13 | import androidx.core.content.edit
14 | import androidx.core.net.toUri
15 | import androidx.core.view.isVisible
16 | import com.google.android.material.slider.Slider
17 | import me.rapierxbox.shellyelevatev2.Constants.SHARED_PREFERENCES_NAME
18 | import me.rapierxbox.shellyelevatev2.Constants.SP_AUTOMATIC_BRIGHTNESS
19 | import me.rapierxbox.shellyelevatev2.Constants.SP_BRIGHTNESS
20 | import me.rapierxbox.shellyelevatev2.Constants.SP_EXTENDED_JAVASCRIPT_INTERFACE
21 | import me.rapierxbox.shellyelevatev2.Constants.SP_HTTP_SERVER_ENABLED
22 | import me.rapierxbox.shellyelevatev2.Constants.SP_LITE_MODE
23 | import me.rapierxbox.shellyelevatev2.Constants.SP_MIN_BRIGHTNESS
24 | import me.rapierxbox.shellyelevatev2.Constants.SP_MQTT_BROKER
25 | import me.rapierxbox.shellyelevatev2.Constants.SP_MQTT_ENABLED
26 | import me.rapierxbox.shellyelevatev2.Constants.SP_MQTT_PASSWORD
27 | import me.rapierxbox.shellyelevatev2.Constants.SP_MQTT_PORT
28 | import me.rapierxbox.shellyelevatev2.Constants.SP_MQTT_USERNAME
29 | import me.rapierxbox.shellyelevatev2.Constants.SP_SCREEN_SAVER_DELAY
30 | import me.rapierxbox.shellyelevatev2.Constants.SP_SCREEN_SAVER_ENABLED
31 | import me.rapierxbox.shellyelevatev2.Constants.SP_SCREEN_SAVER_ID
32 | import me.rapierxbox.shellyelevatev2.Constants.SP_SWITCH_ON_SWIPE
33 | import me.rapierxbox.shellyelevatev2.Constants.SP_WEBVIEW_URL
34 | import me.rapierxbox.shellyelevatev2.ShellyElevateApplication.mDeviceHelper
35 | import me.rapierxbox.shellyelevatev2.ShellyElevateApplication.mHttpServer
36 | import me.rapierxbox.shellyelevatev2.ShellyElevateApplication.mSwipeHelper
37 | import me.rapierxbox.shellyelevatev2.backbutton.BackAccessibilityService
38 | import me.rapierxbox.shellyelevatev2.backbutton.FloatingBackButtonService
39 | import me.rapierxbox.shellyelevatev2.databinding.SettingsActivityBinding
40 | import me.rapierxbox.shellyelevatev2.helper.ServiceHelper
41 | import me.rapierxbox.shellyelevatev2.screensavers.ScreenSaverManagerHolder
42 | import java.net.NetworkInterface
43 |
44 | @SuppressLint("UseSwitchCompatOrMaterialCode")
45 | class SettingsActivity : AppCompatActivity() {
46 |
47 | private lateinit var binding: SettingsActivityBinding // Declare the binding object
48 |
49 | private fun loadValues() {
50 |
51 | val preferences = getSharedPreferences(SHARED_PREFERENCES_NAME, MODE_PRIVATE)
52 |
53 | binding.webviewURL.setText(ServiceHelper.getWebviewUrl())
54 | binding.switchOnSwipe.isChecked = preferences.getBoolean(SP_SWITCH_ON_SWIPE, true)
55 | binding.automaticBrightness.isChecked = preferences.getBoolean(SP_AUTOMATIC_BRIGHTNESS, true)
56 | binding.minBrightness.value = preferences.getInt(SP_MIN_BRIGHTNESS, 48).toFloat()
57 | binding.brightnessSetting.value = preferences.getInt(SP_BRIGHTNESS, DEFAULT_BRIGHTNESS).toFloat()
58 | binding.screenSaver.isChecked = preferences.getBoolean(SP_SCREEN_SAVER_ENABLED, true)
59 | binding.screenSaverDelay.setText(preferences.getInt(SP_SCREEN_SAVER_DELAY, SCREEN_SAVER_DEFAULT_DELAY).toString())
60 | binding.screenSaverType.setSelection(preferences.getInt(SP_SCREEN_SAVER_ID, 0))
61 |
62 | binding.httpServerEnabled.isChecked = preferences.getBoolean(SP_HTTP_SERVER_ENABLED, true)
63 | binding.httpServerAddress.text = getString(R.string.server_url, getLocalIpAddress())
64 |
65 | binding.httpServerStatus.text = getString(if (mHttpServer.isAlive) R.string.http_server_running else R.string.http_server_not_running)
66 | binding.extendedJavascriptInterface.isChecked = preferences.getBoolean(SP_EXTENDED_JAVASCRIPT_INTERFACE, false)
67 | binding.liteMode.isChecked = preferences.getBoolean(SP_LITE_MODE, false)
68 | binding.mqttEnabled.isChecked = preferences.getBoolean(SP_MQTT_ENABLED, false)
69 | binding.mqttBroker.setText(preferences.getString(SP_MQTT_BROKER, ""))
70 | binding.mqttPort.setText(preferences.getInt(SP_MQTT_PORT, MQTT_DEFAULT_PORT).toString())
71 | binding.mqttUsername.setText(preferences.getString(SP_MQTT_USERNAME, ""))
72 | binding.mqttPassword.setText(preferences.getString(SP_MQTT_PASSWORD, ""))
73 |
74 | binding.screenSaverDelayLayout.isVisible = binding.screenSaver.isChecked
75 | binding.screenSaverTypeLayout.isVisible = binding.screenSaver.isChecked
76 |
77 | binding.brightnessSettingLayout.isVisible = !binding.automaticBrightness.isChecked
78 | binding.minBrightnessLayout.isVisible = binding.automaticBrightness.isChecked
79 |
80 | binding.httpServerAddressLayout.isVisible = binding.httpServerEnabled.isChecked
81 | binding.httpServerLayout.isVisible = binding.httpServerEnabled.isChecked
82 |
83 | binding.httpServerButton.isVisible = !mHttpServer.isAlive
84 |
85 | binding.mqttBrokerLayout.isVisible = binding.mqttEnabled.isChecked
86 | binding.mqttPortLayout.isVisible = binding.mqttEnabled.isChecked
87 | binding.mqttUsernameLayout.isVisible = binding.mqttEnabled.isChecked
88 | binding.mqttPasswordLayout.isVisible = binding.mqttEnabled.isChecked
89 |
90 | preferences.edit {
91 | putBoolean("settingEverShown", true)
92 | }
93 | }
94 |
95 | @SuppressLint("ClickableViewAccessibility")
96 | override fun onCreate(savedInstanceState: Bundle?) {
97 | super.onCreate(savedInstanceState)
98 |
99 | binding = SettingsActivityBinding.inflate(layoutInflater) // Inflate the binding
100 | setContentView(binding.root) // Set the content view using binding.root
101 |
102 | setSupportActionBar(binding.toolbar)
103 |
104 | supportActionBar?.let {
105 | it.setHomeButtonEnabled(true)
106 | it.setDisplayHomeAsUpEnabled(true)
107 | title = getString(R.string.settings)
108 | }
109 |
110 | binding.screenSaverType.adapter = ScreenSaverManagerHolder.getInstance().screenSaverSpinnerAdapter
111 |
112 | loadValues()
113 |
114 | binding.findURLButton.setOnClickListener {
115 | ServiceHelper.getHAURL(applicationContext) { url: String ->
116 | runOnUiThread {
117 | binding.webviewURL.setText(url)
118 | }
119 | }
120 | }
121 |
122 | binding.brightnessSetting.addOnChangeListener(object : Slider.OnChangeListener {
123 |
124 | override fun onValueChange(slider: Slider, value: Float, fromUser: Boolean) {
125 | mDeviceHelper.forceScreenBrightness(value.toInt())
126 | }
127 | })
128 |
129 | binding.screenSaver.setOnCheckedChangeListener { _, isChecked ->
130 | binding.screenSaverDelayLayout.isVisible = isChecked
131 | binding.screenSaverTypeLayout.isVisible = isChecked
132 | }
133 |
134 | binding.automaticBrightness.setOnCheckedChangeListener { _, isChecked ->
135 | binding.brightnessSettingLayout.isVisible = !isChecked
136 | binding.minBrightnessLayout.isVisible = isChecked
137 | }
138 |
139 | binding.mqttEnabled.setOnCheckedChangeListener { _, isChecked ->
140 | binding.mqttBrokerLayout.isVisible = isChecked
141 | binding.mqttPortLayout.isVisible = isChecked
142 | binding.mqttUsernameLayout.isVisible = isChecked
143 | binding.mqttPasswordLayout.isVisible = isChecked
144 | }
145 |
146 | binding.httpServerEnabled.setOnCheckedChangeListener { _, isChecked ->
147 | binding.httpServerLayout.isVisible = isChecked
148 | binding.httpServerAddressLayout.isVisible = isChecked
149 | }
150 |
151 | binding.httpServerButton.setOnClickListener {
152 | mHttpServer.start()
153 | binding.httpServerText.text = getString(R.string.http_server_running)
154 | binding.httpServerButton.isVisible = false
155 | }
156 |
157 | binding.swipeDetectionOverlay.setOnTouchListener { _, event ->
158 | if (ScreenSaverManagerHolder.getInstance().onTouchEvent()) {
159 | Log.d("ShellyElevateV2", "Touch blocked by ScreenSaverManager")
160 | return@setOnTouchListener true
161 | }
162 | mSwipeHelper.onTouchEvent(event)
163 |
164 | return@setOnTouchListener false
165 | }
166 |
167 | binding.screenSaverDelay.setOnEditorActionListener { _, actionId, _ ->
168 | if (actionId == EditorInfo.IME_ACTION_DONE) {
169 |
170 | if ((binding.screenSaverDelay.text.toString().toIntOrNull() ?: 5) < 5) {
171 | binding.screenSaverDelay.setText("5")
172 | Toast.makeText(this, R.string.delay_must_be_bigger_then_5s, Toast.LENGTH_SHORT).show()
173 | }
174 | }
175 |
176 | return@setOnEditorActionListener false
177 | }
178 | }
179 |
180 | override fun onSupportNavigateUp(): Boolean {
181 | saveSettings()
182 | return true
183 | }
184 |
185 | override fun onCreateOptionsMenu(menu: Menu?): Boolean {
186 | menuInflater.inflate(R.menu.settings_menu, menu)
187 | return true
188 | }
189 |
190 | override fun onOptionsItemSelected(item: MenuItem): Boolean {
191 | return when (item.itemId) {
192 | R.id.action_settings -> {
193 |
194 | if (checkAccessibilityPermission()) {
195 | val intent = Intent(Settings.ACTION_SETTINGS)
196 | startActivity(intent)
197 | }
198 | true
199 | }
200 |
201 | R.id.action_exit -> {
202 | if (checkAccessibilityPermission()) {
203 | moveTaskToBack(true)
204 | finishAffinity()
205 | }
206 | true
207 | }
208 |
209 | else -> super.onOptionsItemSelected(item)
210 | }
211 | }
212 |
213 | private fun checkAccessibilityPermission(): Boolean {
214 | if (!Settings.canDrawOverlays(this)) {
215 | Toast.makeText(this, "Please, grant overlay permission to show the floating back button", Toast.LENGTH_LONG).show()
216 | val intent = Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, "package:$packageName".toUri())
217 | startActivity(intent)
218 | return false
219 | }
220 |
221 | startService(Intent(this, FloatingBackButtonService::class.java))
222 |
223 | if (!BackAccessibilityService.isAccessibilityEnabled(this)) {
224 | Toast.makeText(this, "Please, grant accessibility permission to use the floating back button", Toast.LENGTH_LONG).show()
225 | startActivity(Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS))
226 | return false
227 | }
228 |
229 | return true
230 | }
231 |
232 | private fun saveSettings() {
233 | getSharedPreferences(SHARED_PREFERENCES_NAME, MODE_PRIVATE).edit {
234 | //Functional mode
235 | putBoolean(SP_LITE_MODE, binding.liteMode.isChecked)
236 |
237 | //WebView
238 | putString(SP_WEBVIEW_URL, binding.webviewURL.text.toString())
239 | putBoolean(SP_EXTENDED_JAVASCRIPT_INTERFACE, binding.extendedJavascriptInterface.isChecked)
240 |
241 | //MQTT
242 | putBoolean(SP_MQTT_ENABLED, binding.mqttEnabled.isChecked)
243 | putString(SP_MQTT_BROKER, binding.mqttBroker.text.toString())
244 | putString(SP_MQTT_USERNAME, binding.mqttUsername.text.toString())
245 | putString(SP_MQTT_PASSWORD, binding.mqttPassword.text.toString())
246 | putInt(SP_MQTT_PORT, binding.mqttPort.text.toString().toIntOrNull() ?: MQTT_DEFAULT_PORT)
247 |
248 | //Switch
249 | putBoolean(SP_SWITCH_ON_SWIPE, binding.switchOnSwipe.isChecked)
250 |
251 | //Brightness management
252 | putBoolean(SP_AUTOMATIC_BRIGHTNESS, binding.automaticBrightness.isChecked)
253 | putInt(SP_BRIGHTNESS, binding.brightnessSetting.value.toInt())
254 | putInt(SP_MIN_BRIGHTNESS, binding.minBrightness.value.toInt())
255 |
256 | //Screen saver
257 | putBoolean(SP_SCREEN_SAVER_ENABLED, binding.screenSaver.isChecked)
258 | putInt(SP_SCREEN_SAVER_DELAY, binding.screenSaverDelay.text.toString().toIntOrNull() ?: SCREEN_SAVER_DEFAULT_DELAY)
259 | putInt(SP_SCREEN_SAVER_ID, binding.screenSaverType.selectedItemPosition)
260 |
261 | //Http Server
262 | putBoolean(SP_HTTP_SERVER_ENABLED, binding.httpServerEnabled.isChecked)
263 | }
264 |
265 | val serverEnabled = binding.httpServerEnabled.isChecked
266 |
267 | if (!serverEnabled && mHttpServer.isAlive) {
268 | mHttpServer.stop()
269 | } else if (serverEnabled && !mHttpServer.isAlive) {
270 | mHttpServer.start()
271 | }
272 |
273 | ShellyElevateApplication.updateSPValues()
274 | Toast.makeText(this, getString(R.string.settings_saved), Toast.LENGTH_SHORT).show()
275 |
276 | finish()
277 | }
278 |
279 | override fun onResume() {
280 | super.onResume()
281 | val intent = Intent(this, FloatingBackButtonService::class.java)
282 | intent.action = FloatingBackButtonService.HIDE_FLOATING_BUTTON
283 | startService(intent)
284 | }
285 |
286 | fun getLocalIpAddress() = NetworkInterface.getNetworkInterfaces().toList().flatMap { it.inetAddresses.toList() }.firstOrNull { it.isSiteLocalAddress }?.hostAddress
287 |
288 | companion object {
289 | const val SCREEN_SAVER_DEFAULT_DELAY = 45
290 | const val MQTT_DEFAULT_PORT = 1833
291 | const val DEFAULT_BRIGHTNESS = 255
292 | }
293 | }
--------------------------------------------------------------------------------
/app/src/main/java/me/rapierxbox/shellyelevatev2/SettingsParser.java:
--------------------------------------------------------------------------------
1 | package me.rapierxbox.shellyelevatev2;
2 |
3 | import static me.rapierxbox.shellyelevatev2.ShellyElevateApplication.mSharedPreferences;
4 | import static me.rapierxbox.shellyelevatev2.ShellyElevateApplication.updateSPValues;
5 |
6 | import android.content.SharedPreferences;
7 |
8 | import org.json.JSONException;
9 | import org.json.JSONObject;
10 |
11 | import java.util.Iterator;
12 | import java.util.Map;
13 |
14 | public class SettingsParser {
15 | public JSONObject getSettings() throws JSONException {
16 | JSONObject settings = new JSONObject();
17 | Map allPreferences = mSharedPreferences.getAll();
18 | for (String key : allPreferences.keySet()) {
19 | settings.put(key, allPreferences.get(key));
20 | }
21 | return settings;
22 | }
23 |
24 | public void setSettings(JSONObject settings) throws JSONException {
25 | SharedPreferences.Editor editor = mSharedPreferences.edit();
26 | for (Iterator it = settings.keys(); it.hasNext(); ) {
27 | String key = it.next();
28 | Class> type = settings.get(key).getClass();
29 | if (type.equals(String.class)) {
30 | editor.putString(key, settings.getString(key));
31 | } else if (type.equals(Integer.class)) {
32 | editor.putInt(key, settings.getInt(key));
33 | } else if (type.equals(Long.class)) {
34 | editor.putLong(key, settings.getLong(key));
35 | } else if (type.equals(Boolean.class)) {
36 | editor.putBoolean(key, settings.getBoolean(key));
37 | }
38 | }
39 | editor.apply();
40 | updateSPValues();
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/app/src/main/java/me/rapierxbox/shellyelevatev2/ShellyElevateApplication.java:
--------------------------------------------------------------------------------
1 | package me.rapierxbox.shellyelevatev2;
2 |
3 | import static me.rapierxbox.shellyelevatev2.Constants.INTENT_WEBVIEW_REFRESH;
4 | import static me.rapierxbox.shellyelevatev2.Constants.SHARED_PREFERENCES_NAME;
5 | import static me.rapierxbox.shellyelevatev2.Constants.SP_HTTP_SERVER_ENABLED;
6 |
7 | import android.app.Application;
8 | import android.content.Context;
9 | import android.content.Intent;
10 | import android.content.SharedPreferences;
11 | import android.hardware.Sensor;
12 | import android.hardware.SensorManager;
13 | import android.util.Log;
14 |
15 | import androidx.localbroadcastmanager.content.LocalBroadcastManager;
16 |
17 | import java.io.IOException;
18 |
19 | import me.rapierxbox.shellyelevatev2.helper.DeviceHelper;
20 | import me.rapierxbox.shellyelevatev2.helper.DeviceSensorManager;
21 | import me.rapierxbox.shellyelevatev2.helper.SwipeHelper;
22 | import me.rapierxbox.shellyelevatev2.mqtt.MQTTServer;
23 | import me.rapierxbox.shellyelevatev2.screensavers.ScreenSaverManagerHolder;
24 |
25 | public class ShellyElevateApplication extends Application {
26 | public static HttpServer mHttpServer;
27 |
28 | public static DeviceHelper mDeviceHelper;
29 | public static DeviceSensorManager mDeviceSensorManager;
30 | public static SwipeHelper mSwipeHelper;
31 | public static ShellyElevateJavascriptInterface mShellyElevateJavascriptInterface;
32 | public static MQTTServer mMQTTServer;
33 |
34 | public static Context mApplicationContext;
35 | public static SharedPreferences mSharedPreferences;
36 |
37 | private static long applicationStartTime;
38 |
39 | @Override
40 | public void onCreate() {
41 | super.onCreate();
42 |
43 | applicationStartTime = System.currentTimeMillis();
44 |
45 | mApplicationContext = getApplicationContext();
46 | mSharedPreferences = getSharedPreferences(SHARED_PREFERENCES_NAME, MODE_PRIVATE);
47 |
48 | mHttpServer = new HttpServer();
49 |
50 | mDeviceHelper = new DeviceHelper();
51 | ScreenSaverManagerHolder.initialize();
52 |
53 | mSwipeHelper = new SwipeHelper();
54 | mShellyElevateJavascriptInterface = new ShellyElevateJavascriptInterface();
55 | mMQTTServer = new MQTTServer();
56 |
57 | if (mSharedPreferences.getBoolean(SP_HTTP_SERVER_ENABLED, true)) {
58 | try {
59 | mHttpServer.start();
60 | } catch (IOException e) {
61 | throw new RuntimeException(e);
62 | }
63 | }
64 |
65 | // Sensors Init
66 | mDeviceSensorManager = new DeviceSensorManager();
67 | SensorManager sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
68 | Sensor lightSensor = sensorManager.getDefaultSensor(Sensor.TYPE_LIGHT);
69 | sensorManager.registerListener(mDeviceSensorManager, lightSensor, SensorManager.SENSOR_DELAY_NORMAL);
70 |
71 | //When everything is running, update values
72 | updateSPValues();
73 |
74 | Log.i("ShellyElevateV2", "Application started");
75 | }
76 |
77 | public static long getApplicationStartTime() {
78 | return applicationStartTime;
79 | }
80 |
81 | public static void updateSPValues() {
82 | ScreenSaverManagerHolder.getInstance().updateValues();
83 | mDeviceSensorManager.updateValues();
84 | mSwipeHelper.updateValues();
85 | mShellyElevateJavascriptInterface.updateValues();
86 | mDeviceHelper.updateValues();
87 | mMQTTServer.updateValues();
88 |
89 | Intent intent = new Intent(INTENT_WEBVIEW_REFRESH);
90 | LocalBroadcastManager.getInstance(ShellyElevateApplication.mApplicationContext).sendBroadcast(intent);
91 | }
92 |
93 | @Override
94 | public void onTerminate() {
95 | mHttpServer.onDestroy();
96 | ((SensorManager) getSystemService(Context.SENSOR_SERVICE)).unregisterListener(mDeviceSensorManager);
97 | ScreenSaverManagerHolder.getInstance().onDestroy();
98 | mMQTTServer.onDestroy();
99 |
100 | mDeviceHelper.setScreenOn(true);
101 |
102 | Log.i("ShellyElevateV2", "BYEEEEEEEEEEEEEEEEEEEE :)");
103 |
104 | super.onTerminate();
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/app/src/main/java/me/rapierxbox/shellyelevatev2/ShellyElevateJavascriptInterface.java:
--------------------------------------------------------------------------------
1 | package me.rapierxbox.shellyelevatev2;
2 |
3 | import static me.rapierxbox.shellyelevatev2.ShellyElevateApplication.mDeviceHelper;
4 | import static me.rapierxbox.shellyelevatev2.ShellyElevateApplication.mDeviceSensorManager;
5 | import static me.rapierxbox.shellyelevatev2.ShellyElevateApplication.mSharedPreferences;
6 | import static me.rapierxbox.shellyelevatev2.Constants.*;
7 |
8 | import android.webkit.JavascriptInterface;
9 |
10 | import me.rapierxbox.shellyelevatev2.screensavers.ScreenSaverManagerHolder;
11 |
12 |
13 | public class ShellyElevateJavascriptInterface {
14 | private boolean eJSa = false; //Extended Javascript allowed
15 |
16 | ShellyElevateJavascriptInterface() {
17 | updateValues();
18 | }
19 |
20 | public void updateValues() {
21 | eJSa = mSharedPreferences.getBoolean(SP_EXTENDED_JAVASCRIPT_INTERFACE, false);
22 | }
23 |
24 | @JavascriptInterface
25 | public boolean getRelay() {return mDeviceHelper.getRelay();}
26 | @JavascriptInterface
27 | public int getLux() {return Math.round(mDeviceSensorManager.getLastMeasuredLux());}
28 | @JavascriptInterface
29 | public double getTemperature() {return mDeviceHelper.getTemperature();}
30 | @JavascriptInterface
31 | public double getHumidity() {return mDeviceHelper.getHumidity();}
32 | @JavascriptInterface
33 | public int getScreenBrightness() {return mDeviceHelper.getScreenBrightness();}
34 | @JavascriptInterface
35 | public boolean getScreenSaverRunning() {return ScreenSaverManagerHolder.getInstance().isScreenSaverRunning();}
36 | @JavascriptInterface
37 | public boolean getScreenSaverEnabled() {return ScreenSaverManagerHolder.getInstance().isScreenSaverEnabled();}
38 | @JavascriptInterface
39 | public int getScreenSaverId() {return ScreenSaverManagerHolder.getInstance().getCurrentScreenSaverId();}
40 | @JavascriptInterface
41 | public boolean getExtendedJavascriptInterfaceEnabled() {return eJSa;}
42 |
43 | @JavascriptInterface
44 | public void setRelay(boolean state) {if (eJSa) {mDeviceHelper.setRelay(state);}}
45 | @JavascriptInterface
46 | public void sleep() {if (eJSa) {ScreenSaverManagerHolder.getInstance().startScreenSaver();}}
47 | public void wake() {if (eJSa) {ScreenSaverManagerHolder.getInstance().stopScreenSaver();}}
48 | @JavascriptInterface
49 | public void setScreenBrightness(int brightness) {if (eJSa) {mDeviceHelper.setScreenBrightness(brightness);}}
50 | public void setScreenSaverEnabled(boolean enabled) {
51 | if (eJSa) {
52 | mSharedPreferences.edit().putBoolean(SP_SCREEN_SAVER_ENABLED, enabled).apply();
53 | ScreenSaverManagerHolder.getInstance().updateValues();
54 | }
55 | }
56 | @JavascriptInterface
57 | public void setScreenSaverId(int id) {
58 | if (eJSa) {
59 | mSharedPreferences.edit().putInt(SP_SCREEN_SAVER_ID, id).apply();
60 | ScreenSaverManagerHolder.getInstance().updateValues();
61 | }
62 | }
63 | }
64 |
65 |
--------------------------------------------------------------------------------
/app/src/main/java/me/rapierxbox/shellyelevatev2/backbutton/BackAccessibilityService.kt:
--------------------------------------------------------------------------------
1 | package me.rapierxbox.shellyelevatev2.backbutton
2 |
3 | import android.accessibilityservice.AccessibilityService
4 | import android.annotation.SuppressLint
5 | import android.content.BroadcastReceiver
6 | import android.content.Context
7 | import android.content.Intent
8 | import android.content.IntentFilter
9 | import android.view.accessibility.AccessibilityEvent
10 | import android.view.accessibility.AccessibilityEvent.*
11 |
12 | class BackAccessibilityService : AccessibilityService() {
13 | private val backReceiver = object : BroadcastReceiver() {
14 | override fun onReceive(context: Context?, intent: Intent?) {
15 | if (intent?.action == ACTION_BACK) {
16 | performGlobalAction(GLOBAL_ACTION_BACK)
17 | }
18 | }
19 | }
20 |
21 | override fun onServiceConnected() {
22 | super.onServiceConnected()
23 | val filter = IntentFilter(ACTION_BACK)
24 | registerReceiver(backReceiver, filter)
25 | }
26 |
27 | @SuppressLint("SwitchIntDef")
28 | override fun onAccessibilityEvent(event: AccessibilityEvent?) {
29 | when (event?.eventType) {
30 | TYPE_TOUCH_INTERACTION_START,
31 | TYPE_TOUCH_INTERACTION_END,
32 | TYPE_VIEW_CLICKED,
33 | TYPE_VIEW_SCROLLED,
34 | TYPE_VIEW_FOCUSED,
35 | TYPE_GESTURE_DETECTION_START,
36 | TYPE_GESTURE_DETECTION_END -> {
37 | val intent = Intent(ACTION_USER_INTERACTION)
38 | sendBroadcast(intent)
39 | }
40 | }
41 | }
42 |
43 | override fun onInterrupt() {}
44 |
45 | override fun onDestroy() {
46 | unregisterReceiver(backReceiver)
47 | super.onDestroy()
48 | }
49 |
50 | companion object {
51 | fun isAccessibilityEnabled(context: Context): Boolean {
52 | val enabledServices = android.provider.Settings.Secure.getString(
53 | context.contentResolver,
54 | android.provider.Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES
55 | ) ?: return false
56 | return enabledServices.contains(context.packageName)
57 | }
58 |
59 | const val ACTION_USER_INTERACTION = "shellyelevate.ACTION_USER_INTERACTION"
60 | const val ACTION_BACK = "shellyelevate.ACTION_USER_BACK"
61 | }
62 |
63 | }
64 |
65 |
--------------------------------------------------------------------------------
/app/src/main/java/me/rapierxbox/shellyelevatev2/backbutton/FloatingBackButtonService.kt:
--------------------------------------------------------------------------------
1 | package me.rapierxbox.shellyelevatev2.backbutton
2 |
3 | import android.annotation.SuppressLint
4 | import android.app.Service
5 | import android.content.Intent
6 | import android.content.SharedPreferences
7 | import android.graphics.PixelFormat
8 | import android.os.Build
9 | import android.os.IBinder
10 | import android.util.Log
11 | import android.view.Gravity
12 | import android.view.LayoutInflater
13 | import android.view.MotionEvent
14 | import android.view.View
15 | import android.view.WindowManager
16 | import android.widget.ImageView
17 | import android.widget.Toast
18 | import androidx.core.content.edit
19 | import me.rapierxbox.shellyelevatev2.R
20 |
21 | class FloatingBackButtonService : Service() {
22 |
23 | private lateinit var windowManager: WindowManager
24 | private var floatingView: View? = null
25 |
26 | private lateinit var prefs: SharedPreferences
27 |
28 | private var wasVisibleBeforePause = false
29 |
30 | fun pauseFloatingButton() {
31 | wasVisibleBeforePause = (floatingView != null)
32 | hideFloatingButton()
33 | }
34 |
35 | fun resumeFloatingButton() {
36 | if (wasVisibleBeforePause) showFloatingButton()
37 | }
38 |
39 | override fun onCreate() {
40 | super.onCreate()
41 | prefs = getSharedPreferences(FLOATING_BUTTON_PREFS, MODE_PRIVATE)
42 | showFloatingButton()
43 | }
44 |
45 | override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
46 | Log.d("FloatingBackButtonService", "onStartCommand() called with: intent = $intent, flags = $flags, startId = $startId, action = ${intent?.action}")
47 |
48 | when (intent?.action) {
49 | SHOW_FLOATING_BUTTON -> showFloatingButton()
50 | HIDE_FLOATING_BUTTON -> hideFloatingButton()
51 | PAUSE_BUTTON -> pauseFloatingButton()
52 | RESUME_BUTTON -> resumeFloatingButton()
53 | else -> showFloatingButton()
54 | }
55 | return START_STICKY
56 | }
57 |
58 | @SuppressLint("ClickableViewAccessibility")
59 | fun showFloatingButton() {
60 | //This overrides pause status
61 | wasVisibleBeforePause = true
62 |
63 | if (floatingView != null) return
64 |
65 | floatingView = LayoutInflater.from(this).inflate(R.layout.floating_button_layout, null)
66 |
67 | val layoutParams = WindowManager.LayoutParams(
68 | WindowManager.LayoutParams.WRAP_CONTENT,
69 | WindowManager.LayoutParams.WRAP_CONTENT,
70 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
71 | WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
72 | else
73 | WindowManager.LayoutParams.TYPE_PHONE,
74 | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS,
75 | PixelFormat.TRANSLUCENT
76 | )
77 |
78 | layoutParams.gravity = Gravity.TOP or Gravity.START
79 | layoutParams.x = prefs.getInt(POS_X, 0)
80 | layoutParams.y = prefs.getInt(POS_Y, 300)
81 |
82 | val button = floatingView!!.findViewById(R.id.floating_back_button)
83 |
84 | windowManager = getSystemService(WINDOW_SERVICE) as WindowManager
85 | windowManager.addView(floatingView, layoutParams)
86 |
87 | var initialX = 0
88 | var initialY = 0
89 | var initialTouchX = 0f
90 | var initialTouchY = 0f
91 | var isClick = false
92 |
93 | button.setOnTouchListener { _, event ->
94 | when (event.action) {
95 | MotionEvent.ACTION_DOWN -> {
96 | initialX = layoutParams.x
97 | initialY = layoutParams.y
98 | initialTouchX = event.rawX
99 | initialTouchY = event.rawY
100 | isClick = true
101 | true
102 | }
103 |
104 | MotionEvent.ACTION_MOVE -> {
105 | val dx = (event.rawX - initialTouchX).toInt()
106 | val dy = (event.rawY - initialTouchY).toInt()
107 | layoutParams.x = initialX + dx
108 | layoutParams.y = initialY + dy
109 | windowManager.updateViewLayout(floatingView, layoutParams)
110 | if (dx != 0 || dy != 0) isClick = false
111 | true
112 | }
113 |
114 | MotionEvent.ACTION_UP -> {
115 | if (isClick) {
116 | performClick()
117 | } else {
118 | prefs.edit {
119 | putInt(POS_X, layoutParams.x)
120 | putInt(POS_Y, layoutParams.y)
121 | }
122 | }
123 | true
124 | }
125 |
126 | else -> false
127 | }
128 | }
129 | }
130 |
131 | private fun performClick() {
132 | if (BackAccessibilityService.isAccessibilityEnabled(this)) {
133 | sendBroadcast(Intent(BackAccessibilityService.ACTION_BACK))
134 | } else {
135 | Toast.makeText(this, getString(R.string.accessibility_service_not_enabled), Toast.LENGTH_SHORT).show()
136 | }
137 | }
138 |
139 | fun hideFloatingButton() {
140 | //This overrides pause status
141 | wasVisibleBeforePause = false
142 |
143 | if (floatingView != null) {
144 | windowManager.removeView(floatingView)
145 | floatingView = null
146 | }
147 | }
148 |
149 | override fun onDestroy() {
150 | hideFloatingButton()
151 | super.onDestroy()
152 | }
153 |
154 |
155 | override fun onBind(intent: Intent?): IBinder? = null
156 |
157 | companion object {
158 | const val SHOW_FLOATING_BUTTON = "SHOW_FLOATING_BUTTON"
159 | const val HIDE_FLOATING_BUTTON = "HIDE_FLOATING_BUTTON"
160 | const val PAUSE_BUTTON = "PAUSE_BUTTON"
161 | const val RESUME_BUTTON = "RESUME_BUTTON"
162 |
163 | const val POS_X = "pos_x"
164 | const val POS_Y = "pos_y"
165 |
166 | const val FLOATING_BUTTON_PREFS = "floating_button_prefs"
167 | }
168 | }
169 |
--------------------------------------------------------------------------------
/app/src/main/java/me/rapierxbox/shellyelevatev2/helper/DeviceHelper.java:
--------------------------------------------------------------------------------
1 | package me.rapierxbox.shellyelevatev2.helper;
2 |
3 | import static me.rapierxbox.shellyelevatev2.Constants.SP_AUTOMATIC_BRIGHTNESS;
4 | import static me.rapierxbox.shellyelevatev2.Constants.SP_BRIGHTNESS;
5 | import static me.rapierxbox.shellyelevatev2.ShellyElevateApplication.mDeviceSensorManager;
6 | import static me.rapierxbox.shellyelevatev2.ShellyElevateApplication.mMQTTServer;
7 | import static me.rapierxbox.shellyelevatev2.ShellyElevateApplication.mSharedPreferences;
8 |
9 | import android.util.Log;
10 |
11 | import java.io.BufferedReader;
12 | import java.io.File;
13 | import java.io.FileReader;
14 | import java.io.FileWriter;
15 | import java.io.IOException;
16 | import java.util.ArrayList;
17 | import java.util.List;
18 | import java.util.Objects;
19 |
20 | public class DeviceHelper {
21 | private static final String[] possibleRelayFiles = {
22 | "/sys/devices/platform/leds/green_enable",
23 | "/sys/devices/platform/leds/red_enable",
24 | "/sys/class/strelay/relay1",
25 | "/sys/class/strelay/relay2"
26 | };
27 | private static final String tempAndHumFile = "/sys/devices/platform/sht3x-user/sht3x_access";
28 | private static final String[] screenBrightnessFiles = {
29 | "/sys/devices/platform/leds-mt65xx/leds/lcd-backlight/brightness",
30 | "/sys/devices/platform/sprd_backlight/backlight/sprd_backlight/brightness",
31 | "/sys/devices/platform/backlight/backlight/backlight/brightness"
32 | };
33 | private String screenBrightnessFile;
34 | private final String[] relayFiles;
35 | private boolean screenOn = true;
36 | private int screenBrightness;
37 | private boolean automaticBrightness;
38 |
39 | private final String TAG = "DeviceHelper";
40 |
41 | public DeviceHelper() {
42 | for (String brightnessFile : screenBrightnessFiles) {
43 | if (new File(brightnessFile).exists()){
44 | screenBrightnessFile = brightnessFile;
45 | }
46 | }
47 | if (screenBrightnessFile == null) {
48 | Log.e("FATAL ERROR", "no brightness file found");
49 | screenBrightnessFile = "";
50 | }
51 |
52 | List relayFileList = new ArrayList<>();
53 | for (String relayFile : possibleRelayFiles) {
54 | if (new File(relayFile).exists()){
55 | relayFileList.add(relayFile);
56 | }
57 | }
58 | if (relayFileList.isEmpty()) {
59 | Log.e("FATAL ERROR", "no relay files found");
60 | relayFileList.add("");
61 | }
62 | relayFiles = relayFileList.toArray(new String[0]);
63 |
64 | updateValues();
65 | }
66 |
67 | public void setScreenOn(boolean on) {
68 | screenOn = on;
69 | int brightness = automaticBrightness ? DeviceSensorManager.getScreenBrightnessFromLux(mDeviceSensorManager.getLastMeasuredLux()) : screenBrightness;
70 | forceScreenBrightness(on ? brightness : 0);
71 | }
72 |
73 | public boolean getScreenOn() {
74 | return screenOn;
75 | }
76 |
77 | public void setScreenBrightness(int brightness) {
78 | if (!screenOn)
79 | return;
80 |
81 | forceScreenBrightness(brightness);
82 |
83 | if (!automaticBrightness) {
84 | mSharedPreferences.edit().putInt(SP_BRIGHTNESS, brightness).apply();
85 | screenBrightness = brightness;
86 | }
87 | }
88 |
89 | public void forceScreenBrightness(int brightness) {
90 | brightness = Math.max(0, Math.min(brightness, 255));
91 |
92 | Log.d(TAG, "Set brightness to: " + brightness);
93 |
94 | writeFileContent(screenBrightnessFile, String.valueOf(brightness));
95 | }
96 | public int getScreenBrightness() {
97 | return Integer.parseInt(readFileContent(screenBrightnessFile));
98 | }
99 | public boolean getRelay() {
100 | boolean relayState = false;
101 | for (String relayFile : relayFiles) {
102 | relayState |= readFileContent(relayFile).contains("1");
103 | }
104 | return relayState;
105 | }
106 | public void setRelay(boolean state) {
107 | for (String relayFile : relayFiles) {
108 | writeFileContent(relayFile, state ? "1" : "0");
109 | }
110 | if (mMQTTServer.shouldSend()) {
111 | mMQTTServer.publishRelay(state);
112 | }
113 | }
114 | public double getTemperature() {
115 | String[] tempSplit = readFileContent(tempAndHumFile).split(":");
116 | double temp = (((Double.parseDouble(tempSplit[1]) * 175.0) / 65535.0) - 45.0) - 1.1;
117 | return Math.round(temp * 10.0) / 10.0;
118 | }
119 | public double getHumidity() {
120 | String[] humiditySplit = readFileContent(tempAndHumFile).split(":");
121 | double humidity = ((Double.parseDouble(humiditySplit[0]) * 100.0) / 65535.0) + 18.0;
122 | return Math.round(humidity);
123 | }
124 |
125 | public void updateValues() {
126 | screenBrightness = mSharedPreferences.getInt(SP_BRIGHTNESS, 255);
127 | automaticBrightness = mSharedPreferences.getBoolean(SP_AUTOMATIC_BRIGHTNESS, true);
128 | }
129 |
130 | private static String readFileContent(String filePath) {
131 | StringBuilder content = new StringBuilder();
132 | try (BufferedReader br = new BufferedReader(new FileReader(filePath))) {
133 | String line;
134 | while ((line = br.readLine()) != null) {
135 | content.append(line).append("\n");
136 | }
137 | } catch (IOException e) {
138 | Log.e("DeviceHelper", "Error when reading file with path:" + filePath + ":" + Objects.requireNonNull(e.getMessage()));
139 | }
140 | return content.toString();
141 | }
142 |
143 | private static void writeFileContent(String filePath, String content) {
144 | try (FileWriter writer = new FileWriter(filePath)) {
145 | writer.write(content);
146 | } catch (IOException e) {
147 | Log.e("DeviceHelper", "Error when writing file with path:" + filePath + ":" + Objects.requireNonNull(e.getMessage()));
148 | }
149 | }
150 | }
151 |
--------------------------------------------------------------------------------
/app/src/main/java/me/rapierxbox/shellyelevatev2/helper/DeviceSensorManager.java:
--------------------------------------------------------------------------------
1 | package me.rapierxbox.shellyelevatev2.helper;
2 |
3 | import static me.rapierxbox.shellyelevatev2.ShellyElevateApplication.mMQTTServer;
4 | import static me.rapierxbox.shellyelevatev2.ShellyElevateApplication.mSharedPreferences;
5 | import static me.rapierxbox.shellyelevatev2.Constants.*;
6 |
7 | import android.util.Log;
8 |
9 | import android.hardware.Sensor;
10 | import android.hardware.SensorEvent;
11 | import android.hardware.SensorEventListener;
12 |
13 | import me.rapierxbox.shellyelevatev2.ShellyElevateApplication;
14 |
15 |
16 | public class DeviceSensorManager implements SensorEventListener {
17 | private static final String TAG = "DeviceSensorManager" ;
18 | private float lastMeasuredLux = 0.0f;
19 | private boolean automaticBrightness = true;
20 |
21 | public float getLastMeasuredLux() {
22 | return lastMeasuredLux;
23 | }
24 |
25 | public void updateValues() {
26 | automaticBrightness = mSharedPreferences.getBoolean(SP_AUTOMATIC_BRIGHTNESS, true);
27 | }
28 |
29 | @Override
30 | public void onSensorChanged(SensorEvent event) {
31 | if (event.sensor.getType() == Sensor.TYPE_LIGHT) {
32 | lastMeasuredLux = event.values[0];
33 | Log.d(TAG, "Light sensor value: " + lastMeasuredLux);
34 |
35 | if (automaticBrightness) {
36 | ShellyElevateApplication.mDeviceHelper.setScreenBrightness(getScreenBrightnessFromLux(lastMeasuredLux));
37 | }
38 | if (mMQTTServer.shouldSend()) {
39 | mMQTTServer.publishLux(lastMeasuredLux);
40 | }
41 | }
42 | }
43 |
44 | @Override
45 | public void onAccuracyChanged(Sensor sensor, int accuracy) {
46 | // Ignore
47 | }
48 |
49 | public static int getScreenBrightnessFromLux(float lux) {
50 | int minBrightness = mSharedPreferences.getInt(SP_MIN_BRIGHTNESS, 48);
51 | if (lux >= 500) return 255;
52 | if (lux <= 30) return minBrightness;
53 |
54 | double slope = (255.0 - minBrightness) / (500.0 - 30.0);
55 | return (int) (minBrightness + slope * (lux - 30));
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/app/src/main/java/me/rapierxbox/shellyelevatev2/helper/MediaHelper.java:
--------------------------------------------------------------------------------
1 | package me.rapierxbox.shellyelevatev2.helper;
2 |
3 | import static me.rapierxbox.shellyelevatev2.ShellyElevateApplication.mApplicationContext;
4 |
5 | import android.content.Context;
6 | import android.media.AudioManager;
7 | import android.media.MediaPlayer;
8 | import android.net.Uri;
9 |
10 | import java.io.IOException;
11 |
12 | public class MediaHelper {
13 | private final MediaPlayer mediaPlayerEffects;
14 | private final MediaPlayer mediaPlayerMusic;
15 | private final AudioManager audioManager;
16 |
17 | public MediaHelper() {
18 | mediaPlayerEffects = new MediaPlayer();
19 | mediaPlayerMusic = new MediaPlayer();
20 | audioManager = (AudioManager) mApplicationContext.getSystemService(Context.AUDIO_SERVICE);
21 |
22 | mediaPlayerEffects.setAudioStreamType(AudioManager.USE_DEFAULT_STREAM_TYPE);
23 | mediaPlayerMusic.setAudioStreamType(AudioManager.STREAM_MUSIC);
24 |
25 | mediaPlayerEffects.setLooping(false);
26 | mediaPlayerMusic.setLooping(true);
27 |
28 | mediaPlayerEffects.setOnPreparedListener(mp -> {
29 | mp.start();
30 | pauseMusic();
31 | });
32 | mediaPlayerMusic.setOnPreparedListener(MediaPlayer::start);
33 |
34 | mediaPlayerEffects.setOnCompletionListener(mp -> resumeMusic());
35 | }
36 |
37 | public void playMusic(Uri uri) throws IOException {
38 | mediaPlayerMusic.reset();
39 | mediaPlayerMusic.setDataSource(mApplicationContext, uri);
40 | mediaPlayerMusic.prepareAsync();
41 | }
42 | public void playEffect(Uri uri) throws IOException {
43 | mediaPlayerEffects.reset();
44 | mediaPlayerEffects.setDataSource(mApplicationContext, uri);
45 | mediaPlayerEffects.prepareAsync();
46 | }
47 | public void pauseMusic() {
48 | mediaPlayerMusic.pause();
49 | }
50 | public void resumeMusic() {
51 | mediaPlayerMusic.start();
52 | }
53 |
54 | public void stopAll() {
55 | mediaPlayerEffects.stop();
56 | mediaPlayerMusic.stop();
57 | }
58 | public void setVolume(double volume) {
59 | audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, (int) (audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC) * volume), 0);
60 | }
61 |
62 | public double getVolume() {
63 | return (double) audioManager.getStreamVolume(AudioManager.STREAM_MUSIC) / (double) audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
64 | }
65 |
66 | public void onDestroy() {
67 | mediaPlayerEffects.release();
68 | mediaPlayerMusic.release();
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/app/src/main/java/me/rapierxbox/shellyelevatev2/helper/ServiceHelper.java:
--------------------------------------------------------------------------------
1 | package me.rapierxbox.shellyelevatev2.helper;
2 |
3 | import static me.rapierxbox.shellyelevatev2.Constants.SP_DEPRECATED_HA_IP;
4 | import static me.rapierxbox.shellyelevatev2.Constants.SP_WEBVIEW_URL;
5 | import static me.rapierxbox.shellyelevatev2.ShellyElevateApplication.mSharedPreferences;
6 |
7 | import android.content.Context;
8 | import android.content.SharedPreferences;
9 | import android.net.nsd.NsdManager;
10 | import android.net.nsd.NsdServiceInfo;
11 | import android.util.Log;
12 |
13 | import java.util.function.Consumer;
14 |
15 | public class ServiceHelper {
16 | public static void getHAURL(Context context, Consumer action) {
17 | NsdManager nsdManager = (NsdManager) context.getSystemService(Context.NSD_SERVICE);
18 | NsdManager.DiscoveryListener discoveryListener = new NsdManager.DiscoveryListener() {
19 | @Override
20 | public void onStartDiscoveryFailed(String serviceType, int errorCode) {
21 | Log.e("discovery", "Discovery failed: Error code: " + errorCode);
22 | }
23 |
24 | @Override
25 | public void onStopDiscoveryFailed(String serviceType, int errorCode) {
26 | Log.e("discovery", "Discovery failed to stop: Error code: " + errorCode);
27 | }
28 |
29 | @Override
30 | public void onDiscoveryStarted(String serviceType) {
31 | Log.i("discovery", "Service discovery started");
32 | }
33 |
34 | @Override
35 | public void onDiscoveryStopped(String serviceType) {
36 | Log.i("discovery", "Service discovery stopped");
37 | }
38 |
39 | @Override
40 | public void onServiceFound(NsdServiceInfo serviceInfo) {
41 | if (serviceInfo.getServiceType().equals("_home-assistant._tcp.")) {
42 | Log.i("discovery", "Found Home Assistant service: " + serviceInfo.getServiceName());
43 |
44 | NsdManager.ResolveListener resolveListener = new NsdManager.ResolveListener() {
45 | @Override
46 | public void onResolveFailed(NsdServiceInfo serviceInfo, int errorCode) {
47 | Log.e("discovery", "Resolve failed: Error code: " + errorCode);
48 | }
49 |
50 | @Override
51 | public void onServiceResolved(NsdServiceInfo serviceInfo) {
52 | Log.i("discovery", "Service resolved: " + serviceInfo);
53 | String url = "http://" + serviceInfo.getHost().getHostAddress() + ":" + serviceInfo.getPort();
54 | Log.i("discovery", "Home Assistant URL: " + url);
55 | action.accept(url);
56 | }
57 | };
58 |
59 | nsdManager.resolveService(serviceInfo, resolveListener);
60 |
61 | nsdManager.stopServiceDiscovery(this);
62 | }
63 | }
64 |
65 | @Override
66 | public void onServiceLost(NsdServiceInfo serviceInfo) {
67 | Log.i("discovery", "Service lost: " + serviceInfo);
68 | }
69 | };
70 |
71 | nsdManager.discoverServices("_home-assistant._tcp.", NsdManager.PROTOCOL_DNS_SD, discoveryListener);
72 | }
73 |
74 | public static String getWebviewUrl() {
75 | if (mSharedPreferences.contains(SP_DEPRECATED_HA_IP)) {
76 | mSharedPreferences.edit()
77 | .putString(SP_WEBVIEW_URL, "http://" + mSharedPreferences.getString(SP_DEPRECATED_HA_IP, "") + ":8123")
78 | .remove(SP_DEPRECATED_HA_IP)
79 | .apply();
80 | }
81 | return mSharedPreferences.getString(SP_WEBVIEW_URL, "");
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/app/src/main/java/me/rapierxbox/shellyelevatev2/helper/SwipeHelper.java:
--------------------------------------------------------------------------------
1 | package me.rapierxbox.shellyelevatev2.helper;
2 |
3 | import static me.rapierxbox.shellyelevatev2.Constants.SP_SWITCH_ON_SWIPE;
4 | import static me.rapierxbox.shellyelevatev2.ShellyElevateApplication.mDeviceHelper;
5 | import static me.rapierxbox.shellyelevatev2.ShellyElevateApplication.mMQTTServer;
6 | import static me.rapierxbox.shellyelevatev2.ShellyElevateApplication.mSharedPreferences;
7 |
8 | import android.util.Log;
9 | import android.view.MotionEvent;
10 |
11 | public class SwipeHelper{
12 | private float touchStartY = 0;
13 | private long touchStartEventTime = 0;
14 |
15 | public float minVel = 2.5F;
16 | public float minDist = 250.0F;
17 |
18 | private boolean doSwitchOnSwipe = true;
19 | public boolean onTouchEvent(MotionEvent event){
20 | switch (event.getAction()){
21 | case MotionEvent.ACTION_DOWN:
22 | touchStartY = event.getY();
23 | touchStartEventTime = event.getEventTime();
24 | return true;
25 | case MotionEvent.ACTION_UP:
26 | float deltaY = Math.abs(touchStartY - event.getY());
27 | float deltaT = Math.abs(touchStartEventTime - event.getEventTime());
28 | float velocity = deltaY / deltaT;
29 | if (velocity > minVel && deltaY > minDist){
30 | if (doSwitchOnSwipe) {
31 | mDeviceHelper.setRelay(!mDeviceHelper.getRelay());
32 | }
33 | if (mMQTTServer.shouldSend()) {
34 | mMQTTServer.publishSwipeEvent();
35 | }
36 | return false;
37 | }
38 | }
39 | return true;
40 | }
41 |
42 | public void updateValues() {
43 | doSwitchOnSwipe = mSharedPreferences.getBoolean(SP_SWITCH_ON_SWIPE, true);
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/app/src/main/java/me/rapierxbox/shellyelevatev2/mqtt/MQTTServer.java:
--------------------------------------------------------------------------------
1 | package me.rapierxbox.shellyelevatev2.mqtt;
2 |
3 | import static me.rapierxbox.shellyelevatev2.Constants.MQTT_TOPIC_CONFIG_DEVICE;
4 | import static me.rapierxbox.shellyelevatev2.Constants.MQTT_TOPIC_HOME_ASSISTANT_STATUS;
5 | import static me.rapierxbox.shellyelevatev2.Constants.MQTT_TOPIC_HUM_SENSOR;
6 | import static me.rapierxbox.shellyelevatev2.Constants.MQTT_TOPIC_LUX_SENSOR;
7 | import static me.rapierxbox.shellyelevatev2.Constants.MQTT_TOPIC_REBOOT_BUTTON;
8 | import static me.rapierxbox.shellyelevatev2.Constants.MQTT_TOPIC_REFRESH_WEBVIEW_BUTTON;
9 | import static me.rapierxbox.shellyelevatev2.Constants.MQTT_TOPIC_RELAY_COMMAND;
10 | import static me.rapierxbox.shellyelevatev2.Constants.MQTT_TOPIC_RELAY_STATE;
11 | import static me.rapierxbox.shellyelevatev2.Constants.MQTT_TOPIC_SLEEPING_BINARY_SENSOR;
12 | import static me.rapierxbox.shellyelevatev2.Constants.MQTT_TOPIC_SLEEP_BUTTON;
13 | import static me.rapierxbox.shellyelevatev2.Constants.MQTT_TOPIC_STATUS;
14 | import static me.rapierxbox.shellyelevatev2.Constants.MQTT_TOPIC_SWIPE_EVENT;
15 | import static me.rapierxbox.shellyelevatev2.Constants.MQTT_TOPIC_TEMP_SENSOR;
16 | import static me.rapierxbox.shellyelevatev2.Constants.MQTT_TOPIC_WAKE_BUTTON;
17 | import static me.rapierxbox.shellyelevatev2.Constants.SP_MQTT_BROKER;
18 | import static me.rapierxbox.shellyelevatev2.Constants.SP_MQTT_DEVICE_ID;
19 | import static me.rapierxbox.shellyelevatev2.Constants.SP_MQTT_ENABLED;
20 | import static me.rapierxbox.shellyelevatev2.Constants.SP_MQTT_PASSWORD;
21 | import static me.rapierxbox.shellyelevatev2.Constants.SP_MQTT_PORT;
22 | import static me.rapierxbox.shellyelevatev2.Constants.SP_MQTT_USERNAME;
23 | import static me.rapierxbox.shellyelevatev2.ShellyElevateApplication.mDeviceHelper;
24 | import static me.rapierxbox.shellyelevatev2.ShellyElevateApplication.mDeviceSensorManager;
25 | import static me.rapierxbox.shellyelevatev2.ShellyElevateApplication.mSharedPreferences;
26 |
27 | import android.util.Log;
28 |
29 | import org.eclipse.paho.mqttv5.client.MqttClient;
30 | import org.eclipse.paho.mqttv5.client.MqttConnectionOptions;
31 | import org.eclipse.paho.mqttv5.client.persist.MemoryPersistence;
32 | import org.eclipse.paho.mqttv5.common.MqttException;
33 | import org.json.JSONArray;
34 | import org.json.JSONException;
35 | import org.json.JSONObject;
36 |
37 | import java.util.UUID;
38 | import java.util.concurrent.Executors;
39 | import java.util.concurrent.ScheduledExecutorService;
40 | import java.util.concurrent.TimeUnit;
41 |
42 | import me.rapierxbox.shellyelevatev2.screensavers.ScreenSaverManagerHolder;
43 |
44 | public class MQTTServer {
45 | private MqttClient mMqttClient;
46 | private final MemoryPersistence mMemoryPersistence;
47 | private final ShellyElevateMQTTCallback mShellyElevateMQTTCallback;
48 | private final MqttConnectionOptions mMqttConnectionsOptions;
49 | private final ScheduledExecutorService scheduler;
50 |
51 | private boolean enabled;
52 | private boolean connected;
53 | private byte[] password;
54 | private String username;
55 | private String broker;
56 | private String clientId;
57 | private int port;
58 |
59 | private boolean validForConnection;
60 |
61 | public MQTTServer() {
62 | mMemoryPersistence = new MemoryPersistence();
63 | mShellyElevateMQTTCallback = new ShellyElevateMQTTCallback();
64 | mMqttConnectionsOptions = new MqttConnectionOptions();
65 |
66 | scheduler = Executors.newScheduledThreadPool(1);
67 | scheduler.scheduleWithFixedDelay(this::publishTempAndHum, 0, 5, TimeUnit.SECONDS);
68 |
69 | connected = false;
70 |
71 | clientId = mSharedPreferences.getString(SP_MQTT_DEVICE_ID, "shellywalldisplay");
72 | if (clientId.equals("shellyelevate") || clientId.equals("shellywalldisplay") || clientId.length() <= 2) {
73 | clientId = "shellyelevate-" + UUID.randomUUID().toString().replaceAll("-", "").substring(2, 6);
74 | mSharedPreferences.edit().putString(SP_MQTT_DEVICE_ID, clientId).apply();
75 | }
76 |
77 | updateValues();
78 | }
79 |
80 | public void updateValues() {
81 | password = mSharedPreferences.getString(SP_MQTT_PASSWORD, "").getBytes();
82 | username = mSharedPreferences.getString(SP_MQTT_USERNAME, "");
83 | broker = mSharedPreferences.getString(SP_MQTT_BROKER, "");
84 | port = mSharedPreferences.getInt(SP_MQTT_PORT, 1883);
85 | clientId = mSharedPreferences.getString(SP_MQTT_DEVICE_ID, "shellywalldisplay");
86 | enabled = mSharedPreferences.getBoolean(SP_MQTT_ENABLED, false);
87 |
88 | validForConnection = password.length > 0 && !username.isEmpty() && !broker.isEmpty();
89 |
90 | connect();
91 | }
92 |
93 | public void disconnect() {
94 | if (mMqttClient != null && mMqttClient.isConnected()) {
95 | try {
96 | deleteConfig();
97 | mMqttClient.publish(parseTopic(MQTT_TOPIC_STATUS), "offline".getBytes(), 1, true);
98 | mMqttClient.disconnect();
99 |
100 | connected = false;
101 | } catch (MqttException e) {
102 | Log.e("MQTT", "Error disconnecting MQTT client", e);
103 | }
104 | }
105 | }
106 |
107 | public void connect() {
108 | if (validForConnection) {
109 | try {
110 | mMqttConnectionsOptions.setUserName(username);
111 | mMqttConnectionsOptions.setPassword(password);
112 |
113 | if (connected) {
114 | disconnect();
115 | }
116 |
117 | mMqttClient = new MqttClient(broker + ":" + port, clientId, mMemoryPersistence);
118 | mMqttClient.setCallback(mShellyElevateMQTTCallback);
119 | mMqttClient.connect(mMqttConnectionsOptions);
120 |
121 | publishConfig();
122 |
123 | mMqttClient.publish(parseTopic(MQTT_TOPIC_STATUS), "online".getBytes(), 1, true);
124 |
125 | mMqttClient.subscribe("shellyelevatev2/#", 0);
126 | mMqttClient.subscribe("shellyelevatev2/#", 1);
127 | mMqttClient.subscribe("shellyelevatev2/#", 2);
128 |
129 | mMqttClient.subscribe(MQTT_TOPIC_HOME_ASSISTANT_STATUS, 0);
130 | mMqttClient.subscribe(MQTT_TOPIC_HOME_ASSISTANT_STATUS, 1);
131 | mMqttClient.subscribe(MQTT_TOPIC_HOME_ASSISTANT_STATUS, 2);
132 |
133 | connected = true;
134 |
135 | publishTempAndHum();
136 | publishRelay(mDeviceHelper.getRelay());
137 | publishLux(mDeviceSensorManager.getLastMeasuredLux());
138 | publishSleeping(ScreenSaverManagerHolder.getInstance().isScreenSaverRunning());
139 |
140 | } catch (MqttException | JSONException e) {
141 | Log.e("MQTT", "Error connecting:", e);
142 | }
143 | }
144 | }
145 |
146 | public boolean isEnabled() {
147 | return enabled;
148 | }
149 |
150 | public boolean shouldSend() {
151 | return connected && enabled;
152 | }
153 |
154 | public void publishTempAndHum() {
155 | if (this.shouldSend()) {
156 | this.publishTemp((float) mDeviceHelper.getTemperature());
157 | this.publishHum((float) mDeviceHelper.getHumidity());
158 | }
159 | }
160 |
161 | public void publishTemp(float temp) {
162 | try {
163 | mMqttClient.publish(parseTopic(MQTT_TOPIC_TEMP_SENSOR), String.valueOf(temp).getBytes(), 1, false);
164 | } catch (MqttException e) {
165 | Log.e("MQTT", "Error publishing temperature", e);
166 | }
167 | }
168 |
169 | public void publishHum(float hum) {
170 | try {
171 | mMqttClient.publish(parseTopic(MQTT_TOPIC_HUM_SENSOR), String.valueOf(hum).getBytes(), 1, false);
172 | } catch (MqttException e) {
173 | Log.e("MQTT", "Error publishing humidity", e);
174 | }
175 | }
176 |
177 | public void publishLux(float lux) {
178 | try {
179 | mMqttClient.publish(parseTopic(MQTT_TOPIC_LUX_SENSOR), String.valueOf(lux).getBytes(), 1, false);
180 | } catch (MqttException e) {
181 | Log.e("MQTT", "Error publishing lux", e);
182 | }
183 | }
184 |
185 | public void publishRelay(boolean state) {
186 | try {
187 | mMqttClient.publish(parseTopic(MQTT_TOPIC_RELAY_STATE), (state ? "ON" : "OFF").getBytes(), 1, false);
188 | } catch (MqttException e) {
189 | Log.e("MQTT", "Error publishing relay state", e);
190 | }
191 | }
192 |
193 | public void publishSleeping(boolean state) {
194 | try {
195 | mMqttClient.publish(parseTopic(MQTT_TOPIC_SLEEPING_BINARY_SENSOR), (state ? "ON" : "OFF").getBytes(), 1, false);
196 | } catch (MqttException e) {
197 | Log.e("MQTT", "Error publishing sleeping state", e);
198 | }
199 | }
200 |
201 | public void publishSwipeEvent() {
202 | try {
203 | mMqttClient.publish(parseTopic(MQTT_TOPIC_SWIPE_EVENT), "{\"event_type\": \"swipe\"}".getBytes(), 1, false);
204 | } catch (MqttException e) {
205 | Log.e("MQTT", "Error publishing swipe event", e);
206 | }
207 | }
208 |
209 | private void deleteConfig() throws MqttException {
210 | mMqttClient.publish(parseTopic(MQTT_TOPIC_CONFIG_DEVICE), "".getBytes(), 1, false);
211 | }
212 |
213 | private void publishConfig() throws JSONException, MqttException {
214 | JSONObject configPayload = new JSONObject();
215 |
216 | JSONObject device = new JSONObject();
217 | device.put("ids", clientId);
218 | device.put("name", "Shelly Wall Display");
219 | device.put("mf", "Shelly");
220 | configPayload.put("dev", device);
221 |
222 | JSONObject origin = new JSONObject();
223 | origin.put("name", "ShellyElevateV2");
224 | origin.put("url", "https://github.com/RapierXbox/ShellyElevate");
225 | configPayload.put("o", origin);
226 |
227 | JSONObject components = new JSONObject();
228 |
229 | JSONObject tempSensorPayload = new JSONObject();
230 | tempSensorPayload.put("p", "sensor");
231 | tempSensorPayload.put("name", "Temperature");
232 | tempSensorPayload.put("state_topic", parseTopic(MQTT_TOPIC_TEMP_SENSOR));
233 | tempSensorPayload.put("device_class", "temperature");
234 | tempSensorPayload.put("unit_of_measurement", "°C");
235 | tempSensorPayload.put("unique_id", clientId + "_temp");
236 | components.put(clientId + "_temp", tempSensorPayload);
237 |
238 | JSONObject humSensorPayload = new JSONObject();
239 | humSensorPayload.put("p", "sensor");
240 | humSensorPayload.put("name", "Humidity");
241 | humSensorPayload.put("state_topic", parseTopic(MQTT_TOPIC_HUM_SENSOR));
242 | humSensorPayload.put("device_class", "humidity");
243 | humSensorPayload.put("unit_of_measurement", "%");
244 | humSensorPayload.put("unique_id", clientId + "_hum");
245 | components.put(clientId + "_hum", humSensorPayload);
246 |
247 | JSONObject luxSensorPayload = new JSONObject();
248 | luxSensorPayload.put("p", "sensor");
249 | luxSensorPayload.put("name", "Light");
250 | luxSensorPayload.put("state_topic", parseTopic(MQTT_TOPIC_LUX_SENSOR));
251 | luxSensorPayload.put("device_class", "illuminance");
252 | luxSensorPayload.put("unit_of_measurement", "lx");
253 | luxSensorPayload.put("unique_id", clientId + "_lux");
254 | components.put(clientId + "_lux", luxSensorPayload);
255 |
256 | JSONObject relaySwitchPayload = new JSONObject();
257 | relaySwitchPayload.put("p", "switch");
258 | relaySwitchPayload.put("name", "Relay");
259 | relaySwitchPayload.put("state_topic", parseTopic(MQTT_TOPIC_RELAY_STATE));
260 | relaySwitchPayload.put("command_topic", parseTopic(MQTT_TOPIC_RELAY_COMMAND));
261 | relaySwitchPayload.put("device_class", "outlet");
262 | relaySwitchPayload.put("unique_id", clientId + "_relay");
263 | components.put(clientId + "_relay", relaySwitchPayload);
264 |
265 | JSONObject sleepButtonPayload = new JSONObject();
266 | sleepButtonPayload.put("p", "button");
267 | sleepButtonPayload.put("name", "Sleep");
268 | sleepButtonPayload.put("command_topic", parseTopic(MQTT_TOPIC_SLEEP_BUTTON));
269 | sleepButtonPayload.put("unique_id", clientId + "_sleep");
270 | components.put(clientId + "_sleep", sleepButtonPayload);
271 |
272 | JSONObject wakeButtonPayload = new JSONObject();
273 | wakeButtonPayload.put("p", "button");
274 | wakeButtonPayload.put("name", "Wake");
275 | wakeButtonPayload.put("command_topic", parseTopic(MQTT_TOPIC_WAKE_BUTTON));
276 | wakeButtonPayload.put("unique_id", clientId + "_wake");
277 | components.put(clientId + "_wake", wakeButtonPayload);
278 |
279 | JSONObject refreshWebviewButtonPayload = new JSONObject();
280 | refreshWebviewButtonPayload.put("p", "button");
281 | refreshWebviewButtonPayload.put("name", "Refresh Webview");
282 | refreshWebviewButtonPayload.put("command_topic", parseTopic(MQTT_TOPIC_REFRESH_WEBVIEW_BUTTON));
283 | refreshWebviewButtonPayload.put("device_class", "restart");
284 | refreshWebviewButtonPayload.put("unique_id", clientId + "_refresh_webview");
285 | components.put(clientId + "_refresh_webview", refreshWebviewButtonPayload);
286 |
287 | JSONObject rebootButtonPayload = new JSONObject();
288 | rebootButtonPayload.put("p", "button");
289 | rebootButtonPayload.put("name", "Reboot");
290 | rebootButtonPayload.put("command_topic", parseTopic(MQTT_TOPIC_REBOOT_BUTTON));
291 | rebootButtonPayload.put("device_class", "restart");
292 | rebootButtonPayload.put("unique_id", clientId + "_reboot");
293 | components.put(clientId + "_reboot", rebootButtonPayload);
294 |
295 | JSONObject swipeEventPayload = new JSONObject();
296 | swipeEventPayload.put("p", "event");
297 | swipeEventPayload.put("name", "Swipe Event");
298 | swipeEventPayload.put("state_topic", parseTopic(MQTT_TOPIC_SWIPE_EVENT));
299 | swipeEventPayload.put("device_class", "button");
300 | swipeEventPayload.put("event_types", new JSONArray().put("swipe"));
301 | swipeEventPayload.put("unique_id", clientId + "_swipe_event");
302 | components.put(clientId + "_swipe_event", swipeEventPayload);
303 |
304 | JSONObject sleepingBinarySensorPayload = new JSONObject();
305 | sleepingBinarySensorPayload.put("p", "binary_sensor");
306 | sleepingBinarySensorPayload.put("name", "Sleeping");
307 | sleepingBinarySensorPayload.put("state_topic", parseTopic(MQTT_TOPIC_SLEEPING_BINARY_SENSOR));
308 | sleepingBinarySensorPayload.put("unique_id", clientId + "_sleeping");
309 | components.put(clientId + "_sleeping", sleepingBinarySensorPayload);
310 |
311 | configPayload.put("cmps", components);
312 |
313 | configPayload.put("state_topic", MQTT_TOPIC_STATUS);
314 |
315 | mMqttClient.publish(parseTopic(MQTT_TOPIC_CONFIG_DEVICE), configPayload.toString().getBytes(), 1, true);
316 | }
317 |
318 | private String parseTopic(String topic) {
319 | return topic.replace("%s", clientId);
320 | }
321 |
322 | public String getClientId() {
323 | return clientId;
324 | }
325 |
326 | public void onDestroy() {
327 | disconnect();
328 |
329 | if (scheduler != null && !scheduler.isShutdown()) {
330 | scheduler.shutdown();
331 | }
332 | }
333 | }
334 |
--------------------------------------------------------------------------------
/app/src/main/java/me/rapierxbox/shellyelevatev2/mqtt/ShellyElevateMQTTCallback.java:
--------------------------------------------------------------------------------
1 | package me.rapierxbox.shellyelevatev2.mqtt;
2 |
3 | import static me.rapierxbox.shellyelevatev2.ShellyElevateApplication.mApplicationContext;
4 | import static me.rapierxbox.shellyelevatev2.Constants.*;
5 | import static me.rapierxbox.shellyelevatev2.ShellyElevateApplication.mDeviceHelper;
6 | import static me.rapierxbox.shellyelevatev2.ShellyElevateApplication.mMQTTServer;
7 |
8 | import android.content.Intent;
9 | import android.util.Log;
10 | import android.widget.Toast;
11 |
12 | import androidx.localbroadcastmanager.content.LocalBroadcastManager;
13 |
14 | import org.eclipse.paho.mqttv5.client.IMqttToken;
15 | import org.eclipse.paho.mqttv5.client.MqttCallback;
16 | import org.eclipse.paho.mqttv5.client.MqttDisconnectResponse;
17 | import org.eclipse.paho.mqttv5.common.MqttException;
18 | import org.eclipse.paho.mqttv5.common.MqttMessage;
19 | import org.eclipse.paho.mqttv5.common.packet.MqttProperties;
20 |
21 | import java.io.IOException;
22 | import java.nio.charset.StandardCharsets;
23 | import java.util.Arrays;
24 |
25 | import me.rapierxbox.shellyelevatev2.ShellyElevateApplication;
26 | import me.rapierxbox.shellyelevatev2.screensavers.ScreenSaverManagerHolder;
27 |
28 | public class ShellyElevateMQTTCallback implements MqttCallback {
29 | @Override
30 | public void disconnected(MqttDisconnectResponse disconnectResponse) {
31 | Log.i("MQTT", "Disconnected");
32 | Toast.makeText(mApplicationContext, "MQTT disconnected", Toast.LENGTH_SHORT).show();
33 | }
34 |
35 | @Override
36 | public void mqttErrorOccurred(MqttException exception) {
37 | Log.e("MQTT", "Error occurred: " + exception);
38 | }
39 |
40 | @Override
41 | public void messageArrived(String topic, MqttMessage message) {
42 | switch (topic.replace(mMQTTServer.getClientId(), "%s")) {
43 | case MQTT_TOPIC_RELAY_COMMAND:
44 | mDeviceHelper.setRelay(new String(message.getPayload(), StandardCharsets.UTF_8).contains("ON"));
45 | break;
46 | case MQTT_TOPIC_REFRESH_WEBVIEW_BUTTON:
47 | Intent intent = new Intent(INTENT_WEBVIEW_REFRESH);
48 | LocalBroadcastManager.getInstance(ShellyElevateApplication.mApplicationContext).sendBroadcast(intent);
49 | break;
50 | case MQTT_TOPIC_SLEEP_BUTTON:
51 | ScreenSaverManagerHolder.getInstance().startScreenSaver();
52 | break;
53 | case MQTT_TOPIC_WAKE_BUTTON:
54 | ScreenSaverManagerHolder.getInstance().stopScreenSaver();
55 | break;
56 | case MQTT_TOPIC_REBOOT_BUTTON:
57 | long deltaTime = System.currentTimeMillis() - ShellyElevateApplication.getApplicationStartTime();
58 | deltaTime /= 1000;
59 | if (deltaTime > 20) {
60 | try {
61 | Runtime.getRuntime().exec("reboot");
62 | } catch (IOException e) {
63 | Log.e("MQTT", "Error rebooting:", e);
64 | }
65 |
66 | } else {
67 | Toast.makeText(mApplicationContext, "Please wait %s seconds before rebooting".replace("%s",String.valueOf(20-deltaTime) ), Toast.LENGTH_LONG).show();
68 | }
69 | }
70 | }
71 |
72 | @Override
73 | public void deliveryComplete(IMqttToken token) {
74 |
75 | }
76 |
77 | @Override
78 | public void connectComplete(boolean reconnect, String serverURI) {
79 | Log.i("MQTT", "Connected to: " + serverURI);
80 | }
81 |
82 | @Override
83 | public void authPacketArrived(int reasonCode, MqttProperties properties) {
84 |
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/app/src/main/java/me/rapierxbox/shellyelevatev2/screensavers/DigitalClockAndDateScreenSaver.java:
--------------------------------------------------------------------------------
1 | package me.rapierxbox.shellyelevatev2.screensavers;
2 |
3 | import android.content.Context;
4 | import android.content.Intent;
5 |
6 | import me.rapierxbox.shellyelevatev2.screensavers.activities.DigitalClockAndDateScreenSaverActivity;
7 |
8 | public class DigitalClockAndDateScreenSaver extends ScreenSaver {
9 | @Override
10 | public void onStart(Context context) {
11 | Intent intent = new Intent(context, DigitalClockAndDateScreenSaverActivity.class);
12 | intent.putExtra("date", true);
13 | intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
14 | context.startActivity(intent);
15 | }
16 |
17 | @Override
18 | public void onEnd(Context context) {}
19 |
20 | @Override
21 | public String getName() {
22 | return "Digital Clock and Date";
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/app/src/main/java/me/rapierxbox/shellyelevatev2/screensavers/DigitalClockScreenSaver.java:
--------------------------------------------------------------------------------
1 | package me.rapierxbox.shellyelevatev2.screensavers;
2 |
3 | import android.content.Context;
4 | import android.content.Intent;
5 |
6 | import me.rapierxbox.shellyelevatev2.screensavers.activities.DigitalClockAndDateScreenSaverActivity;
7 |
8 | public class DigitalClockScreenSaver extends ScreenSaver {
9 |
10 | public void onStart(Context context) {
11 | Intent intent = new Intent(context, DigitalClockAndDateScreenSaverActivity.class);
12 | intent.putExtra("date", false);
13 | intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
14 | context.startActivity(intent);
15 | }
16 |
17 | @Override
18 | public void onEnd(Context context) {
19 | }
20 |
21 | @Override
22 | public String getName() {
23 | return "Digital Clock";
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/app/src/main/java/me/rapierxbox/shellyelevatev2/screensavers/ScreenOffScreenSaver.java:
--------------------------------------------------------------------------------
1 | package me.rapierxbox.shellyelevatev2.screensavers;
2 |
3 | import static me.rapierxbox.shellyelevatev2.ShellyElevateApplication.mDeviceHelper;
4 |
5 | import android.content.Context;
6 |
7 | public class ScreenOffScreenSaver extends ScreenSaver{
8 | @Override
9 | public void onStart(Context context) {
10 | mDeviceHelper.setScreenOn(false);
11 | }
12 |
13 | @Override
14 | public void onEnd(Context context) {
15 | mDeviceHelper.setScreenOn(true);
16 | }
17 |
18 | @Override
19 | public String getName() {
20 | return "Screen Off";
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/app/src/main/java/me/rapierxbox/shellyelevatev2/screensavers/ScreenSaver.java:
--------------------------------------------------------------------------------
1 | package me.rapierxbox.shellyelevatev2.screensavers;
2 |
3 | import android.content.Context;
4 |
5 | public abstract class ScreenSaver {
6 | public abstract void onStart(Context context);
7 | public abstract void onEnd(Context context);
8 | public abstract String getName();
9 | }
10 |
--------------------------------------------------------------------------------
/app/src/main/java/me/rapierxbox/shellyelevatev2/screensavers/ScreenSaverManager.java:
--------------------------------------------------------------------------------
1 | package me.rapierxbox.shellyelevatev2.screensavers;
2 |
3 | import static me.rapierxbox.shellyelevatev2.Constants.SP_SCREEN_SAVER_DELAY;
4 | import static me.rapierxbox.shellyelevatev2.Constants.SP_SCREEN_SAVER_ENABLED;
5 | import static me.rapierxbox.shellyelevatev2.Constants.SP_SCREEN_SAVER_ID;
6 | import static me.rapierxbox.shellyelevatev2.ShellyElevateApplication.mApplicationContext;
7 | import static me.rapierxbox.shellyelevatev2.ShellyElevateApplication.mMQTTServer;
8 | import static me.rapierxbox.shellyelevatev2.ShellyElevateApplication.mSharedPreferences;
9 |
10 | import android.content.Intent;
11 | import android.util.Log;
12 | import android.widget.ArrayAdapter;
13 |
14 | import java.util.concurrent.Executors;
15 | import java.util.concurrent.ScheduledExecutorService;
16 | import java.util.concurrent.TimeUnit;
17 |
18 | import me.rapierxbox.shellyelevatev2.backbutton.FloatingBackButtonService;
19 |
20 | public class ScreenSaverManager {
21 | private long lastTouchEventTime;
22 | private int screenSaverDelay;
23 | private boolean screenSaverRunning;
24 |
25 | private final ScheduledExecutorService scheduler;
26 |
27 | private final ScreenSaver[] screenSavers;
28 | private int currentScreenSaverId;
29 |
30 | private boolean screenSaverEnabled;
31 |
32 | public ScreenSaverManager() {
33 |
34 | scheduler = Executors.newScheduledThreadPool(1);
35 | scheduler.scheduleWithFixedDelay(this::checkLastTouchEventTime, 0, 1, TimeUnit.SECONDS);
36 |
37 | lastTouchEventTime = 0;
38 | screenSaverDelay = 45;
39 | currentScreenSaverId = 0;
40 | screenSaverRunning = false;
41 |
42 | screenSavers = new ScreenSaver[]{
43 | new ScreenOffScreenSaver(),
44 | new DigitalClockScreenSaver(),
45 | new DigitalClockAndDateScreenSaver()
46 | };
47 | }
48 |
49 | public boolean onTouchEvent() {
50 | lastTouchEventTime = System.currentTimeMillis();
51 | if (screenSaverRunning) {
52 | stopScreenSaver();
53 | return true;
54 | }
55 | return false;
56 | }
57 |
58 | public void updateValues() {
59 | stopScreenSaver();
60 |
61 | screenSaverDelay = mSharedPreferences.getInt(SP_SCREEN_SAVER_DELAY, 45);
62 | if (screenSaverDelay < 5) {
63 | screenSaverDelay = 5;
64 | mSharedPreferences.edit().putInt(SP_SCREEN_SAVER_DELAY, 5).apply();
65 | }
66 | currentScreenSaverId = mSharedPreferences.getInt(SP_SCREEN_SAVER_ID, 0);
67 | if (currentScreenSaverId < 0 || currentScreenSaverId >= screenSavers.length) {
68 | currentScreenSaverId = 0;
69 | mSharedPreferences.edit().putInt(SP_SCREEN_SAVER_ID, 0).apply();
70 | }
71 | screenSaverEnabled = mSharedPreferences.getBoolean(SP_SCREEN_SAVER_ENABLED, true);
72 | currentScreenSaverId = Math.min(Math.max(currentScreenSaverId, 0), screenSavers.length - 1);
73 | lastTouchEventTime = System.currentTimeMillis();
74 | }
75 |
76 | public ArrayAdapter getScreenSaverSpinnerAdapter() {
77 | ArrayAdapter adapter = new ArrayAdapter<>(mApplicationContext, android.R.layout.simple_spinner_item);
78 | adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
79 |
80 | for (ScreenSaver screenSaver : screenSavers) {
81 | adapter.add(screenSaver.getName());
82 | }
83 |
84 | return adapter;
85 | }
86 |
87 | public boolean isScreenSaverRunning() {
88 | return screenSaverRunning;
89 | }
90 |
91 | public int getCurrentScreenSaverId() {
92 | return currentScreenSaverId;
93 | }
94 |
95 | public boolean isScreenSaverEnabled() {
96 | return screenSaverEnabled;
97 | }
98 |
99 | public void onDestroy() {
100 | if (scheduler != null && !scheduler.isShutdown()) {
101 | scheduler.shutdown();
102 | }
103 | }
104 |
105 | private void checkLastTouchEventTime() {
106 | if (System.currentTimeMillis() - lastTouchEventTime > screenSaverDelay * 1000L && screenSaverEnabled) {
107 | startScreenSaver();
108 | }
109 | }
110 |
111 | public void startScreenSaver() {
112 | if (!screenSaverRunning) {
113 | screenSaverRunning = true;
114 |
115 | Intent backButtonIntent = new Intent(mApplicationContext, FloatingBackButtonService.class);
116 | backButtonIntent.setAction(FloatingBackButtonService.PAUSE_BUTTON);
117 | mApplicationContext.startService(backButtonIntent);
118 |
119 | screenSavers[currentScreenSaverId].onStart(mApplicationContext);
120 | Log.i("ShellyElevateV2", "Starting screen saver with id: " + currentScreenSaverId);
121 |
122 | if (mMQTTServer.shouldSend()) {
123 | mMQTTServer.publishSleeping(true);
124 | }
125 | }
126 | }
127 |
128 | public void stopScreenSaver() {
129 | if (screenSaverRunning) {
130 | screenSaverRunning = false;
131 | screenSavers[currentScreenSaverId].onEnd(mApplicationContext);
132 | lastTouchEventTime = System.currentTimeMillis();
133 | Log.i("ShellyElevateV2", "Ending screen saver with id: " + currentScreenSaverId);
134 |
135 | Intent backButtonIntent = new Intent(mApplicationContext, FloatingBackButtonService.class);
136 | backButtonIntent.setAction(FloatingBackButtonService.RESUME_BUTTON);
137 | mApplicationContext.startService(backButtonIntent);
138 |
139 | if (mMQTTServer.shouldSend()) {
140 | mMQTTServer.publishSleeping(false);
141 | }
142 | }
143 | }
144 | }
145 |
--------------------------------------------------------------------------------
/app/src/main/java/me/rapierxbox/shellyelevatev2/screensavers/ScreenSaverManagerHolder.kt:
--------------------------------------------------------------------------------
1 | package me.rapierxbox.shellyelevatev2.screensavers
2 |
3 | object ScreenSaverManagerHolder {
4 | private var instance: ScreenSaverManager? = null
5 |
6 | @JvmStatic
7 | fun initialize(): ScreenSaverManager {
8 | if (instance == null) {
9 | instance = ScreenSaverManager()
10 | }
11 | return instance!!
12 | }
13 |
14 | //Currently the two methods are identical, but
15 | //in the future they may be different. For example
16 | //init is probably going to have a context as parameter
17 | @JvmStatic
18 | fun getInstance(): ScreenSaverManager {
19 | if (instance == null) {
20 | instance = ScreenSaverManager()
21 | }
22 | return instance!!
23 | }
24 | }
--------------------------------------------------------------------------------
/app/src/main/java/me/rapierxbox/shellyelevatev2/screensavers/UserInteractionReceiver.kt:
--------------------------------------------------------------------------------
1 | package me.rapierxbox.shellyelevatev2.screensavers
2 |
3 | import android.content.BroadcastReceiver
4 | import android.content.Context
5 | import android.content.Intent
6 | import me.rapierxbox.shellyelevatev2.backbutton.BackAccessibilityService.Companion.ACTION_USER_INTERACTION
7 |
8 | class UserInteractionReceiver : BroadcastReceiver() {
9 | override fun onReceive(context: Context?, intent: Intent?) {
10 | if (intent?.action == ACTION_USER_INTERACTION) {
11 | ScreenSaverManagerHolder.getInstance().onTouchEvent()
12 | }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/app/src/main/java/me/rapierxbox/shellyelevatev2/screensavers/activities/DigitalClockAndDateScreenSaverActivity.kt:
--------------------------------------------------------------------------------
1 | package me.rapierxbox.shellyelevatev2.screensavers.activities
2 |
3 | import android.annotation.SuppressLint
4 | import android.app.Activity
5 | import android.content.BroadcastReceiver
6 | import android.content.Context
7 | import android.content.Intent
8 | import android.content.IntentFilter
9 | import android.os.Bundle
10 | import androidx.core.view.isVisible
11 | import me.rapierxbox.shellyelevatev2.ShellyElevateApplication
12 | import me.rapierxbox.shellyelevatev2.databinding.DigitalClockAndDateScreenSaverBinding
13 | import me.rapierxbox.shellyelevatev2.screensavers.ScreenSaverManagerHolder
14 | import java.text.SimpleDateFormat
15 | import java.util.Date
16 |
17 | class DigitalClockAndDateScreenSaverActivity : Activity() {
18 | private lateinit var binding: DigitalClockAndDateScreenSaverBinding // Declare the binding object
19 |
20 | private val timeFormatter = SimpleDateFormat.getTimeInstance(SimpleDateFormat.SHORT)
21 | private val dateFormatter = SimpleDateFormat.getDateInstance(SimpleDateFormat.MEDIUM)
22 |
23 | private var showDate = false
24 |
25 | private val mTimeTickBroadCastReciver = object : BroadcastReceiver() {
26 | override fun onReceive(context: Context?, intent: Intent?) {
27 | updateTime()
28 | }
29 | }
30 |
31 | private fun updateTime() {
32 | val now = Date()
33 | binding.clockText.text = timeFormatter.format(now)
34 |
35 | if (showDate)
36 | binding.dateText.text = dateFormatter.format(now)
37 | }
38 |
39 | @SuppressLint("ClickableViewAccessibility")
40 | override fun onCreate(savedInstanceState: Bundle?) {
41 | super.onCreate(savedInstanceState)
42 |
43 | showDate = intent.getBooleanExtra("date", false)
44 |
45 | binding = DigitalClockAndDateScreenSaverBinding.inflate(layoutInflater) // Inflate the binding
46 | setContentView(binding.root) // Set the content view using binding.root
47 |
48 | binding.dateText.isVisible = showDate
49 |
50 | updateTime()
51 |
52 | binding.swipeDetectionOverlay.setOnTouchListener { _, event ->
53 | ScreenSaverManagerHolder.getInstance().onTouchEvent()
54 | ShellyElevateApplication.mSwipeHelper.onTouchEvent(event)
55 |
56 | finish()
57 |
58 | return@setOnTouchListener false
59 | }
60 |
61 | registerReceiver(mTimeTickBroadCastReciver, IntentFilter(Intent.ACTION_TIME_TICK))
62 | }
63 |
64 | override fun onDestroy() {
65 | super.onDestroy()
66 |
67 | unregisterReceiver(mTimeTickBroadCastReciver)
68 | }
69 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_back_arrow.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_exit.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
15 |
20 |
25 |
30 |
35 |
40 |
45 |
50 |
55 |
60 |
65 |
70 |
75 |
80 |
85 |
90 |
95 |
100 |
105 |
110 |
115 |
120 |
125 |
130 |
135 |
140 |
145 |
150 |
155 |
160 |
165 |
170 |
171 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
15 |
18 |
21 |
22 |
23 |
24 |
30 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/round_button_bg.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/digital_clock_and_date_screen_saver.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
20 |
21 |
33 |
34 |
44 |
45 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/floating_button_layout.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
15 |
16 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/main_activity.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
17 |
18 |
19 |
29 |
30 |
39 |
40 |
49 |
50 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/settings_activity.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
10 |
14 |
15 |
20 |
21 |
22 |
23 |
29 |
30 |
35 |
36 |
41 |
42 |
48 |
49 |
58 |
59 |
67 |
68 |
75 |
76 |
82 |
83 |
89 |
90 |
97 |
98 |
99 |
100 |
106 |
107 |
113 |
114 |
121 |
122 |
123 |
124 |
132 |
133 |
140 |
141 |
147 |
148 |
156 |
157 |
158 |
165 |
166 |
172 |
173 |
178 |
179 |
180 |
188 |
189 |
196 |
197 |
203 |
204 |
211 |
212 |
213 |
220 |
221 |
228 |
229 |
237 |
238 |
245 |
246 |
247 |
255 |
256 |
264 |
265 |
271 |
272 |
278 |
279 |
287 |
288 |
289 |
295 |
296 |
302 |
303 |
312 |
313 |
314 |
320 |
321 |
327 |
328 |
336 |
337 |
338 |
344 |
345 |
351 |
352 |
360 |
361 |
362 |
370 |
371 |
372 |
373 |
374 |
375 |
376 |
386 |
387 |
388 |
389 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/settings_menu.xml:
--------------------------------------------------------------------------------
1 |
2 |
18 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RapierXbox/ShellyElevate/0c003b61c7604b58679a3fa6a3efe8f38524b554/app/src/main/res/mipmap-hdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RapierXbox/ShellyElevate/0c003b61c7604b58679a3fa6a3efe8f38524b554/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RapierXbox/ShellyElevate/0c003b61c7604b58679a3fa6a3efe8f38524b554/app/src/main/res/mipmap-mdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RapierXbox/ShellyElevate/0c003b61c7604b58679a3fa6a3efe8f38524b554/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RapierXbox/ShellyElevate/0c003b61c7604b58679a3fa6a3efe8f38524b554/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RapierXbox/ShellyElevate/0c003b61c7604b58679a3fa6a3efe8f38524b554/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RapierXbox/ShellyElevate/0c003b61c7604b58679a3fa6a3efe8f38524b554/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RapierXbox/ShellyElevate/0c003b61c7604b58679a3fa6a3efe8f38524b554/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RapierXbox/ShellyElevate/0c003b61c7604b58679a3fa6a3efe8f38524b554/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RapierXbox/ShellyElevate/0c003b61c7604b58679a3fa6a3efe8f38524b554/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/values-night/colors.xml:
--------------------------------------------------------------------------------
1 |
2 | #CFBDFF
3 | #3A0093
4 | #6200EE
5 | #D0BEFF
6 | #CFBDFF
7 | #38197D
8 | #513697
9 | #C2ACFF
10 | #FFACEA
11 | #5D0054
12 | #9A008C
13 | #FFAEEA
14 | #FFB4AB
15 | #690005
16 | #93000A
17 | #FFDAD6
18 | #15121C
19 | #E7E0F0
20 | #15121C
21 | #E7E0F0
22 | #494456
23 | #CBC3D9
24 | #948DA2
25 | #494456
26 | #000000
27 | #E7E0F0
28 | #322F3A
29 | #6D23F9
30 | #E8DDFF
31 | #22005D
32 | #CFBDFF
33 | #5300CD
34 | #E8DDFF
35 | #22005D
36 | #CFBDFF
37 | #4F3494
38 | #FFD7F1
39 | #390033
40 | #FFACEA
41 | #840078
42 | #15121C
43 | #3B3743
44 | #0F0D17
45 | #1D1A25
46 | #211E29
47 | #2C2834
48 | #37333F
49 | #E3D6FF
50 | #2D0077
51 | #9D79FF
52 | #000000
53 | #E3D6FF
54 | #2D0672
55 | #9B80E5
56 | #000000
57 | #FFCEEF
58 | #4B0043
59 | #E657CF
60 | #000000
61 | #FFD2CC
62 | #540003
63 | #FF5449
64 | #000000
65 | #15121C
66 | #E7E0F0
67 | #15121C
68 | #FFFFFF
69 | #494456
70 | #E1D9F0
71 | #B6AEC4
72 | #948DA2
73 | #000000
74 | #E7E0F0
75 | #2C2834
76 | #5500D0
77 | #E8DDFF
78 | #160042
79 | #CFBDFF
80 | #4000A2
81 | #E8DDFF
82 | #160042
83 | #CFBDFF
84 | #3E2082
85 | #FFD7F1
86 | #280023
87 | #FFACEA
88 | #67005D
89 | #15121C
90 | #47424F
91 | #080610
92 | #1F1C27
93 | #292632
94 | #34313D
95 | #403C48
96 | #F5EDFF
97 | #000000
98 | #CBB8FF
99 | #0F0033
100 | #F5EDFF
101 | #000000
102 | #CBB8FF
103 | #0F0033
104 | #FFEAF5
105 | #000000
106 | #FFA5E9
107 | #1D001A
108 | #FFECE9
109 | #000000
110 | #FFAEA4
111 | #220001
112 | #15121C
113 | #E7E0F0
114 | #15121C
115 | #FFFFFF
116 | #494456
117 | #FFFFFF
118 | #F5EDFF
119 | #C7BFD5
120 | #000000
121 | #E7E0F0
122 | #000000
123 | #5500D0
124 | #E8DDFF
125 | #000000
126 | #CFBDFF
127 | #160042
128 | #E8DDFF
129 | #000000
130 | #CFBDFF
131 | #160042
132 | #FFD7F1
133 | #000000
134 | #FFACEA
135 | #280023
136 | #15121C
137 | #524E5B
138 | #000000
139 | #211E29
140 | #322F3A
141 | #3D3946
142 | #494551
143 |
144 |
--------------------------------------------------------------------------------
/app/src/main/res/values-night/theme_overlays.xml:
--------------------------------------------------------------------------------
1 |
2 |
50 |
98 |
99 |
--------------------------------------------------------------------------------
/app/src/main/res/values-night/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
50 |
51 |
--------------------------------------------------------------------------------
/app/src/main/res/values/arrays.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 | #4800B2
3 | #FFFFFF
4 | #6200EE
5 | #D0BEFF
6 | #674DAE
7 | #FFFFFF
8 | #B398FF
9 | #45298A
10 | #720068
11 | #FFFFFF
12 | #9A008C
13 | #FFAEEA
14 | #BA1A1A
15 | #FFFFFF
16 | #FFDAD6
17 | #93000A
18 | #FDF7FF
19 | #1D1A25
20 | #FDF7FF
21 | #1D1A25
22 | #E8DFF6
23 | #494456
24 | #7A7488
25 | #CBC3D9
26 | #322F3A
27 | #F5EEFE
28 | #CFBDFF
29 | #E8DDFF
30 | #22005D
31 | #CFBDFF
32 | #5300CD
33 | #E8DDFF
34 | #22005D
35 | #CFBDFF
36 | #4F3494
37 | #FFD7F1
38 | #390033
39 | #FFACEA
40 | #840078
41 | #DED7E7
42 | #FDF7FF
43 | #FFFFFF
44 | #F8F1FF
45 | #F2EBFB
46 | #EDE5F5
47 | #E7E0F0
48 |
49 |
--------------------------------------------------------------------------------
/app/src/main/res/values/donottranslate.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | http://192.168.1.123:8123
4 | tcp://192.168.1.123
5 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | ShellyElevateV2
3 |
4 |
5 | Search for HA URL
6 | Switch on swipe
7 | Automatic Brightness
8 | Screen Saver
9 | Status:
10 | START
11 | Lite Mode
12 | Delay
13 | 45
14 | Type
15 | HTTP Server enabled:
16 | Address:
17 | Extended JS Interface
18 | Brightness
19 | MQTT
20 | Broker address
21 | Running
22 | Not running
23 | Settings saved
24 | Delay must be bigger then 5s
25 | Settings
26 | Broker port
27 | Username
28 | username
29 | Password
30 | password
31 | http://%1$s:8080
32 | Android settings
33 | Exit
34 | Back
35 | This service will show a \"Back\" button when ShellyElevate is not visible.
36 | Enable accessibility service to continue
37 | Minimum brightness
38 |
--------------------------------------------------------------------------------
/app/src/main/res/values/theme_overlays.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
50 |
51 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/accessibility_service_config.xml:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. For more details, visit
12 | # https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app's APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Enables namespacing of each library's R class so that its R class includes only the
19 | # resources declared in the library itself and none from the library's dependencies,
20 | # thereby reducing the size of the R class for that library
21 | android.nonTransitiveRClass=true
--------------------------------------------------------------------------------
/gradle/libs.versions.toml:
--------------------------------------------------------------------------------
1 | [versions]
2 | agp = "8.8.2"
3 | junit = "4.13.2"
4 | junitVersion = "1.2.1"
5 | espressoCore = "3.6.1"
6 | appcompat = "1.7.0"
7 | material = "1.12.0"
8 | kotlin = "2.1.20"
9 | lifecycleRuntimeKtx = "2.8.7"
10 | orgEclipsePahoMqttv5Client = "1.2.5"
11 | preference = "1.2.1"
12 | nanohttpd = "2.3.1"
13 | okhttp = "5.0.0-alpha.14"
14 |
15 | [libraries]
16 | junit = { group = "junit", name = "junit", version.ref = "junit" }
17 | ext-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
18 | espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
19 | appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
20 | material = { group = "com.google.android.material", name = "material", version.ref = "material" }
21 | lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" }
22 | org-eclipse-paho-mqttv5-client = { module = "org.eclipse.paho:org.eclipse.paho.mqttv5.client", version.ref = "orgEclipsePahoMqttv5Client" }
23 | preference = { group = "androidx.preference", name = "preference", version.ref = "preference" }
24 | nanohttpd = { group = "org.nanohttpd", name = "nanohttpd", version.ref = "nanohttpd" }
25 | okhttpbom = { group = "com.squareup.okhttp3", name = "okhttp-bom", version.ref = "okhttp" }
26 | okhttp = { group = "com.squareup.okhttp3", name = "okhttp" }
27 |
28 |
29 | [plugins]
30 | android-application = { id = "com.android.application", version.ref = "agp" }
31 | jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
32 |
33 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RapierXbox/ShellyElevate/0c003b61c7604b58679a3fa6a3efe8f38524b554/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Sat Feb 15 22:09:25 CET 2025
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
5 | zipStoreBase=GRADLE_USER_HOME
6 | zipStorePath=wrapper/dists
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | #
4 | # Copyright 2015 the original author or authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | ##############################################################################
20 | ##
21 | ## Gradle start up script for UN*X
22 | ##
23 | ##############################################################################
24 |
25 | # Attempt to set APP_HOME
26 | # Resolve links: $0 may be a link
27 | PRG="$0"
28 | # Need this for relative symlinks.
29 | while [ -h "$PRG" ] ; do
30 | ls=`ls -ld "$PRG"`
31 | link=`expr "$ls" : '.*-> \(.*\)$'`
32 | if expr "$link" : '/.*' > /dev/null; then
33 | PRG="$link"
34 | else
35 | PRG=`dirname "$PRG"`"/$link"
36 | fi
37 | done
38 | SAVED="`pwd`"
39 | cd "`dirname \"$PRG\"`/" >/dev/null
40 | APP_HOME="`pwd -P`"
41 | cd "$SAVED" >/dev/null
42 |
43 | APP_NAME="Gradle"
44 | APP_BASE_NAME=`basename "$0"`
45 |
46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
48 |
49 | # Use the maximum available, or set MAX_FD != -1 to use that value.
50 | MAX_FD="maximum"
51 |
52 | warn () {
53 | echo "$*"
54 | }
55 |
56 | die () {
57 | echo
58 | echo "$*"
59 | echo
60 | exit 1
61 | }
62 |
63 | # OS specific support (must be 'true' or 'false').
64 | cygwin=false
65 | msys=false
66 | darwin=false
67 | nonstop=false
68 | case "`uname`" in
69 | CYGWIN* )
70 | cygwin=true
71 | ;;
72 | Darwin* )
73 | darwin=true
74 | ;;
75 | MINGW* )
76 | msys=true
77 | ;;
78 | NONSTOP* )
79 | nonstop=true
80 | ;;
81 | esac
82 |
83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
84 |
85 |
86 | # Determine the Java command to use to start the JVM.
87 | if [ -n "$JAVA_HOME" ] ; then
88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
89 | # IBM's JDK on AIX uses strange locations for the executables
90 | JAVACMD="$JAVA_HOME/jre/sh/java"
91 | else
92 | JAVACMD="$JAVA_HOME/bin/java"
93 | fi
94 | if [ ! -x "$JAVACMD" ] ; then
95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
96 |
97 | Please set the JAVA_HOME variable in your environment to match the
98 | location of your Java installation."
99 | fi
100 | else
101 | JAVACMD="java"
102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
103 |
104 | Please set the JAVA_HOME variable in your environment to match the
105 | location of your Java installation."
106 | fi
107 |
108 | # Increase the maximum file descriptors if we can.
109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
110 | MAX_FD_LIMIT=`ulimit -H -n`
111 | if [ $? -eq 0 ] ; then
112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
113 | MAX_FD="$MAX_FD_LIMIT"
114 | fi
115 | ulimit -n $MAX_FD
116 | if [ $? -ne 0 ] ; then
117 | warn "Could not set maximum file descriptor limit: $MAX_FD"
118 | fi
119 | else
120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
121 | fi
122 | fi
123 |
124 | # For Darwin, add options to specify how the application appears in the dock
125 | if $darwin; then
126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
127 | fi
128 |
129 | # For Cygwin or MSYS, switch paths to Windows format before running java
130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
133 |
134 | JAVACMD=`cygpath --unix "$JAVACMD"`
135 |
136 | # We build the pattern for arguments to be converted via cygpath
137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
138 | SEP=""
139 | for dir in $ROOTDIRSRAW ; do
140 | ROOTDIRS="$ROOTDIRS$SEP$dir"
141 | SEP="|"
142 | done
143 | OURCYGPATTERN="(^($ROOTDIRS))"
144 | # Add a user-defined pattern to the cygpath arguments
145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
147 | fi
148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
149 | i=0
150 | for arg in "$@" ; do
151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
153 |
154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
156 | else
157 | eval `echo args$i`="\"$arg\""
158 | fi
159 | i=`expr $i + 1`
160 | done
161 | case $i in
162 | 0) set -- ;;
163 | 1) set -- "$args0" ;;
164 | 2) set -- "$args0" "$args1" ;;
165 | 3) set -- "$args0" "$args1" "$args2" ;;
166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
172 | esac
173 | fi
174 |
175 | # Escape application args
176 | save () {
177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
178 | echo " "
179 | }
180 | APP_ARGS=`save "$@"`
181 |
182 | # Collect all arguments for the java command, following the shell quoting and substitution rules
183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
184 |
185 | exec "$JAVACMD" "$@"
186 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34 |
35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
37 |
38 | @rem Find java.exe
39 | if defined JAVA_HOME goto findJavaFromJavaHome
40 |
41 | set JAVA_EXE=java.exe
42 | %JAVA_EXE% -version >NUL 2>&1
43 | if "%ERRORLEVEL%" == "0" goto execute
44 |
45 | echo.
46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47 | echo.
48 | echo Please set the JAVA_HOME variable in your environment to match the
49 | echo location of your Java installation.
50 |
51 | goto fail
52 |
53 | :findJavaFromJavaHome
54 | set JAVA_HOME=%JAVA_HOME:"=%
55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
56 |
57 | if exist "%JAVA_EXE%" goto execute
58 |
59 | echo.
60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61 | echo.
62 | echo Please set the JAVA_HOME variable in your environment to match the
63 | echo location of your Java installation.
64 |
65 | goto fail
66 |
67 | :execute
68 | @rem Setup the command line
69 |
70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
71 |
72 |
73 | @rem Execute Gradle
74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
75 |
76 | :end
77 | @rem End local scope for the variables with windows NT shell
78 | if "%ERRORLEVEL%"=="0" goto mainEnd
79 |
80 | :fail
81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
82 | rem the _cmd.exe /c_ return code!
83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
84 | exit /b 1
85 |
86 | :mainEnd
87 | if "%OS%"=="Windows_NT" endlocal
88 |
89 | :omega
90 |
--------------------------------------------------------------------------------
/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | repositories {
3 | google ()
4 | mavenCentral()
5 | gradlePluginPortal()
6 | }
7 | }
8 |
9 | dependencyResolutionManagement {
10 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
11 | repositories {
12 | google()
13 | mavenCentral()
14 | }
15 | }
16 |
17 | rootProject.name = "ShellyElevateV2"
18 | include(":app")
19 |
--------------------------------------------------------------------------------