From b11e317d94723064ab87440a2de129dee518bde3 Mon Sep 17 00:00:00 2001 From: "sameerasw.com" Date: Fri, 27 Feb 2026 19:54:46 +0530 Subject: [PATCH 01/45] New translations strings.xml (German) --- app/src/main/res/values-de/strings.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 7459d2e02..ff6da9a74 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -375,7 +375,7 @@ Text Size Font Size Custom Text - Enter your text... + Gib deinen Text ein... Spacing Border Width Round Corners @@ -391,7 +391,7 @@ Rotate right Next OK - Save Changes + Änderungen Speichern Calendar Sync Settings Sync specific calendars Periodic Sync @@ -1041,7 +1041,7 @@ \?#/ Oi! You can check updates in app settings, No need to add here XD Export - Import + Importieren Repositories exported successfully Failed to export repositories Repositories imported successfully From 820721e6f173730824265befd7d158e4e10920d8 Mon Sep 17 00:00:00 2001 From: "sameerasw.com" Date: Fri, 27 Feb 2026 19:54:47 +0530 Subject: [PATCH 02/45] New translations strings.xml (Dutch) --- app/src/main/res/values-nl/strings.xml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index a209e0738..abd421b8a 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -192,9 +192,9 @@ Automatisch Uit USB-foutopsporing - Color Picker - Are you sure you\'re on Androdi 17? (╯°_°)╯ - Eye Dropper + Kleurenkiezer + Weet je zeker dat je op Android 17 bent? (╯°_°)╯ + Pipet Aan Uit Aangepaste Privé-DNS @@ -599,7 +599,7 @@ Aanpasbare helderheid in-/uitschakelen\n\nHet automatisch aanpassen van de schermhelderheid op basis van de lichtcondities in-/uitschakelen. Privé-DNS in-/uitschakelen.\n\nDoor de Uit/Automatisch/Hostnaam privé-DNS-modi schakelen. USB-foutopsporing in-/uitschakelen.\n\nToegang tot foutopsporing via USB in-/uitschakelen via de snelle instellingen. - Launch the eye dropper tool to pick colors introduced in Android 17 BETA 2 + Open het pipethulpmiddel om kleuren te kiezen, geïntroduceerd in Android 17 Bèta 2 Downloaden Scherm uit From c87a36a959655d3cb9375b32985ab1a3ac1eddfd Mon Sep 17 00:00:00 2001 From: "sameerasw.com" Date: Sat, 28 Feb 2026 00:13:40 +0530 Subject: [PATCH 03/45] Update source file strings.xml --- app/src/main/res/values/strings.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 94708047e..217e34bd6 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -117,6 +117,8 @@ More options Freeze all apps Unfreeze all apps + Export frozen apps list + Import frozen apps list Pick apps to freeze Choose which apps can be frozen Automation From 5e6eae6382be0d7dd4f0bf2c6bed927eebe5785d Mon Sep 17 00:00:00 2001 From: "sameerasw.com" Date: Sat, 28 Feb 2026 00:13:43 +0530 Subject: [PATCH 04/45] New translations strings.xml (Romanian) --- app/src/main/res/values-ro/strings.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/res/values-ro/strings.xml b/app/src/main/res/values-ro/strings.xml index d64dd8821..9c9506408 100644 --- a/app/src/main/res/values-ro/strings.xml +++ b/app/src/main/res/values-ro/strings.xml @@ -113,6 +113,8 @@ More options Freeze all apps Unfreeze all apps + Export frozen apps list + Import frozen apps list Pick apps to freeze Choose which apps can be frozen Automation From fbfa4922fc6d8bb265e13ae683f9bdf417b7950e Mon Sep 17 00:00:00 2001 From: "sameerasw.com" Date: Sat, 28 Feb 2026 00:13:45 +0530 Subject: [PATCH 05/45] New translations strings.xml (French) --- app/src/main/res/values-fr/strings.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 197026acf..01c843c40 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -113,6 +113,8 @@ Plus d\'options Geler toutes les applis Dégeler toutes les applis + Export frozen apps list + Import frozen apps list Choisir les applis à geler Choisir quelles applis peuvent être gelées Automatisation From 8f3b3a24b27ba30b4d7fdefa85caad370a1840d7 Mon Sep 17 00:00:00 2001 From: "sameerasw.com" Date: Sat, 28 Feb 2026 00:13:46 +0530 Subject: [PATCH 06/45] New translations strings.xml (Spanish) --- app/src/main/res/values-es/strings.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index e102d281f..c688bfe01 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -113,6 +113,8 @@ More options Freeze all apps Unfreeze all apps + Export frozen apps list + Import frozen apps list Pick apps to freeze Choose which apps can be frozen Automation From 05fdfeb10962f179d0c3c42e84fd10ad79f2a30a Mon Sep 17 00:00:00 2001 From: "sameerasw.com" Date: Sat, 28 Feb 2026 00:13:47 +0530 Subject: [PATCH 07/45] New translations strings.xml (Afrikaans) --- app/src/main/res/values-af/strings.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/res/values-af/strings.xml b/app/src/main/res/values-af/strings.xml index e75199d39..62aa1d9a1 100644 --- a/app/src/main/res/values-af/strings.xml +++ b/app/src/main/res/values-af/strings.xml @@ -113,6 +113,8 @@ More options Freeze all apps Unfreeze all apps + Export frozen apps list + Import frozen apps list Pick apps to freeze Choose which apps can be frozen Automation From 6b72139a3af6fc8a19ce5e8052f0663fbb390bcb Mon Sep 17 00:00:00 2001 From: "sameerasw.com" Date: Sat, 28 Feb 2026 00:13:48 +0530 Subject: [PATCH 08/45] New translations strings.xml (Arabic) --- app/src/main/res/values-ar/strings.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index 45ef1041d..b14e4b1dd 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -113,6 +113,8 @@ More options Freeze all apps Unfreeze all apps + Export frozen apps list + Import frozen apps list Pick apps to freeze Choose which apps can be frozen Automation From f569af2d067590ff7cfbe7bb0c2c3a809cdb82a7 Mon Sep 17 00:00:00 2001 From: "sameerasw.com" Date: Sat, 28 Feb 2026 00:13:49 +0530 Subject: [PATCH 09/45] New translations strings.xml (Catalan) --- app/src/main/res/values-ca/strings.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/res/values-ca/strings.xml b/app/src/main/res/values-ca/strings.xml index e75199d39..62aa1d9a1 100644 --- a/app/src/main/res/values-ca/strings.xml +++ b/app/src/main/res/values-ca/strings.xml @@ -113,6 +113,8 @@ More options Freeze all apps Unfreeze all apps + Export frozen apps list + Import frozen apps list Pick apps to freeze Choose which apps can be frozen Automation From e983929fa3d809864c8fcbeb4a96101d21d72151 Mon Sep 17 00:00:00 2001 From: "sameerasw.com" Date: Sat, 28 Feb 2026 00:13:50 +0530 Subject: [PATCH 10/45] New translations strings.xml (Czech) --- app/src/main/res/values-cs/strings.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index e75199d39..62aa1d9a1 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -113,6 +113,8 @@ More options Freeze all apps Unfreeze all apps + Export frozen apps list + Import frozen apps list Pick apps to freeze Choose which apps can be frozen Automation From 3ee9af1bbe4ea75d98f40b7aba340c806ad76859 Mon Sep 17 00:00:00 2001 From: "sameerasw.com" Date: Sat, 28 Feb 2026 00:13:51 +0530 Subject: [PATCH 11/45] New translations strings.xml (Danish) --- app/src/main/res/values-da/strings.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/res/values-da/strings.xml b/app/src/main/res/values-da/strings.xml index e75199d39..62aa1d9a1 100644 --- a/app/src/main/res/values-da/strings.xml +++ b/app/src/main/res/values-da/strings.xml @@ -113,6 +113,8 @@ More options Freeze all apps Unfreeze all apps + Export frozen apps list + Import frozen apps list Pick apps to freeze Choose which apps can be frozen Automation From ec4f9d4c657d0ff5786f2d16f47c2db4c3d17e6b Mon Sep 17 00:00:00 2001 From: "sameerasw.com" Date: Sat, 28 Feb 2026 00:13:53 +0530 Subject: [PATCH 12/45] New translations strings.xml (German) --- app/src/main/res/values-de/strings.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index ff6da9a74..4ef814872 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -113,6 +113,8 @@ Weitere Optionen Friere alle Apps ein Taue alle Apps auf + Export frozen apps list + Import frozen apps list Wähle Apps zum Einfrieren Wähle, welche Apps eingefroren werden können Automatisierung From aa41716d13eed78955d20e52b6a16e048c4c0bce Mon Sep 17 00:00:00 2001 From: "sameerasw.com" Date: Sat, 28 Feb 2026 00:13:54 +0530 Subject: [PATCH 13/45] New translations strings.xml (Greek) --- app/src/main/res/values-el/strings.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index e75199d39..62aa1d9a1 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -113,6 +113,8 @@ More options Freeze all apps Unfreeze all apps + Export frozen apps list + Import frozen apps list Pick apps to freeze Choose which apps can be frozen Automation From 24d499a8482dc046a5008deb19c932bfad567f7e Mon Sep 17 00:00:00 2001 From: "sameerasw.com" Date: Sat, 28 Feb 2026 00:13:55 +0530 Subject: [PATCH 14/45] New translations strings.xml (Finnish) --- app/src/main/res/values-fi/strings.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/res/values-fi/strings.xml b/app/src/main/res/values-fi/strings.xml index e75199d39..62aa1d9a1 100644 --- a/app/src/main/res/values-fi/strings.xml +++ b/app/src/main/res/values-fi/strings.xml @@ -113,6 +113,8 @@ More options Freeze all apps Unfreeze all apps + Export frozen apps list + Import frozen apps list Pick apps to freeze Choose which apps can be frozen Automation From 09125ad678de5fa78641b71e41027d2f9e3825e6 Mon Sep 17 00:00:00 2001 From: "sameerasw.com" Date: Sat, 28 Feb 2026 00:13:56 +0530 Subject: [PATCH 15/45] New translations strings.xml (Hebrew) --- app/src/main/res/values-he/strings.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/res/values-he/strings.xml b/app/src/main/res/values-he/strings.xml index e75199d39..62aa1d9a1 100644 --- a/app/src/main/res/values-he/strings.xml +++ b/app/src/main/res/values-he/strings.xml @@ -113,6 +113,8 @@ More options Freeze all apps Unfreeze all apps + Export frozen apps list + Import frozen apps list Pick apps to freeze Choose which apps can be frozen Automation From e501979513f8fab6cd8519302624255d668b8347 Mon Sep 17 00:00:00 2001 From: "sameerasw.com" Date: Sat, 28 Feb 2026 00:13:57 +0530 Subject: [PATCH 16/45] New translations strings.xml (Hungarian) --- app/src/main/res/values-hu/strings.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index e75199d39..62aa1d9a1 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -113,6 +113,8 @@ More options Freeze all apps Unfreeze all apps + Export frozen apps list + Import frozen apps list Pick apps to freeze Choose which apps can be frozen Automation From 3b1c334a86d50c1e69cc228c082c81e4a1bd4994 Mon Sep 17 00:00:00 2001 From: "sameerasw.com" Date: Sat, 28 Feb 2026 00:13:58 +0530 Subject: [PATCH 17/45] New translations strings.xml (Italian) --- app/src/main/res/values-it/strings.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index ac66f2cb5..3fe710f31 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -114,6 +114,8 @@ Le app bloccate richiederanno l\'autenticazione all\'apertura. L\'app rimarrà s Altre opzioni Blocca tutte le app Sblocca tutte le app + Export frozen apps list + Import frozen apps list Scegli le app da bloccare Choose which apps can be frozen Automation From 9a7fad4f416df35ecbcb210d934d7151abd766ec Mon Sep 17 00:00:00 2001 From: "sameerasw.com" Date: Sat, 28 Feb 2026 00:13:59 +0530 Subject: [PATCH 18/45] New translations strings.xml (Japanese) --- app/src/main/res/values-ja/strings.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 066f411b8..2a44038bc 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -113,6 +113,8 @@ その他のオプション すべてのアプリをフリーズ すべてのアプリのフリーズを解除 + Export frozen apps list + Import frozen apps list フリーズさせるアプリを選択 フリーズできるアプリを選択してください 自動化 From d3fb35c35750a02ce0367d89c2a6fe9ae2cd34e3 Mon Sep 17 00:00:00 2001 From: "sameerasw.com" Date: Sat, 28 Feb 2026 00:14:00 +0530 Subject: [PATCH 19/45] New translations strings.xml (Korean) --- app/src/main/res/values-ko/strings.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index e75199d39..62aa1d9a1 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -113,6 +113,8 @@ More options Freeze all apps Unfreeze all apps + Export frozen apps list + Import frozen apps list Pick apps to freeze Choose which apps can be frozen Automation From f92afbba2225df910d210974dd9a4498ed6153f0 Mon Sep 17 00:00:00 2001 From: "sameerasw.com" Date: Sat, 28 Feb 2026 00:14:01 +0530 Subject: [PATCH 20/45] New translations strings.xml (Dutch) --- app/src/main/res/values-nl/strings.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index abd421b8a..a68a093c7 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -113,6 +113,8 @@ Meer opties Bevries alle apps Ontdooi alle apps + Export frozen apps list + Import frozen apps list Kies apps om te bevriezen Kies welke apps bevroren kunnen worden Automatisatie From 5e409f5d9c72c51acae6f03a1a435608f32eb717 Mon Sep 17 00:00:00 2001 From: "sameerasw.com" Date: Sat, 28 Feb 2026 00:14:03 +0530 Subject: [PATCH 21/45] New translations strings.xml (Norwegian) --- app/src/main/res/values-no/strings.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/res/values-no/strings.xml b/app/src/main/res/values-no/strings.xml index e75199d39..62aa1d9a1 100644 --- a/app/src/main/res/values-no/strings.xml +++ b/app/src/main/res/values-no/strings.xml @@ -113,6 +113,8 @@ More options Freeze all apps Unfreeze all apps + Export frozen apps list + Import frozen apps list Pick apps to freeze Choose which apps can be frozen Automation From a07e9563bbba01d20487a21b358487e079d56e2d Mon Sep 17 00:00:00 2001 From: "sameerasw.com" Date: Sat, 28 Feb 2026 00:14:04 +0530 Subject: [PATCH 22/45] New translations strings.xml (Polish) --- app/src/main/res/values-pl/strings.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index e75199d39..62aa1d9a1 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -113,6 +113,8 @@ More options Freeze all apps Unfreeze all apps + Export frozen apps list + Import frozen apps list Pick apps to freeze Choose which apps can be frozen Automation From a24c0f48a414e141d193cbe6dfd957ae5014ae09 Mon Sep 17 00:00:00 2001 From: "sameerasw.com" Date: Sat, 28 Feb 2026 00:14:05 +0530 Subject: [PATCH 23/45] New translations strings.xml (Portuguese) --- app/src/main/res/values-pt/strings.xml | 28 ++++++++++++++------------ 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index ea4a579ba..0c02d6026 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -26,7 +26,7 @@ Another note, the biometric authentication prompt only lets you use STRONG secure class methods. Face unlock security methods in WEAK class in devices such as Pixel 7 will only be able to utilize the available other STRONG auth methods such as fingerprint or pin. Enable Button Remap - Use Shizuku + Use Shizuku or Root or Root Works with screen off (Recommended) Shizuku is not running Detected %1$s @@ -113,6 +113,8 @@ More options Freeze all apps Unfreeze all apps + Export frozen apps list + Import frozen apps list Pick apps to freeze Choose which apps can be frozen Automation @@ -479,15 +481,15 @@ Shizuku Required for advanced commands. Install Shizuku from the Play Store. Install Shizuku - Shizuku permission + Grant Permission Required to run power-saving commands while maps is navigating. Requires Shizuku or Root Root Access Permissions required for system actions using Root privileges. Notification Listener - Required to detect when Maps is navigating. - Required to detect new notifications - Required to detect and snooze notifications + Requires notification listener access to monitor Google Maps navigation status and enable power saving when not navigatiing. + Requires notification listener access to detect new notifications and trigger edge lighting. + Requires notification listener access to monitor and snooze unwanted system notifications. Accessibility Service Required for App Lock, Screen off widget and other features to detect interactions Required to trigger notification lighting on new notifications @@ -834,7 +836,7 @@ You are up to date This is a pre-release version and might be unstable. - Release Notes v%1$s + Release Notes %1$s View on GitHub Download APK @@ -890,7 +892,7 @@ Notification lighting does not work It depends on the OEM. Some like OneUI does not seem to allow overlays above the AOD preventing the lighting effects being shown. In this case, try the ambient display as a workaround. Button remap does not work while display is off - Some OEMs limit the accessibility service reporting once the display is actually off but they may still work while the AOD is on. \nIn this case, you may able to use button remaps with AOD on but not with off. \n\nAs a workaround, you will need to use Shizuku permissions and turn on the \'Use Shizuku\' toggle in button remap settings which identifies and listen to hardware input events.\nThis is not guaranteed to work on all devices and needs testing.\n\nAnd even if it\'s on, Shizuku method only will be used when it\'s needed. Otherwise it will always fallback to Accessibility which also handles the blocking of the actual input during long press. + Some OEMs limit the accessibility service reporting once the display is actually off but they may still work while the AOD is on. \nIn this case, you may able to use button remaps with AOD on but not with off. \n\nAs a workaround, you will need to use Shizuku permissions and turn on the \'Use Shizuku or Root\' toggle in button remap settings which identifies and listen to hardware input events.\nThis is not guaranteed to work on all devices and needs testing.\n\nAnd even if it\'s on, Shizuku method only will be used when it\'s needed. Otherwise it will always fallback to Accessibility which also handles the blocking of the actual input during long press. Flashlight brightness does not work Only a limited number of devices got hardware and software support adjusting the flashlight intensity. \n\n\'The minimum version of Android is 13 (SDK33).\nFlashlight brightness control only supports HAL version 3.8 and higher, so among the supported devices, the latest ones (For example, Pixel 6/7, Samsung S23, etc.)\'\npolodarb/Flashlight-Tiramisu What the hell is this app? @@ -908,10 +910,10 @@ Essentials Bug Report Send via - Are We There Yet? - Prepare for your destination. - Open your map app, pick a location, and share it to Essentials. - Radius: %d m + Are we there yet? + Destination nearby alerts + Open Google Maps, pick a location, and share it to Essentials. + Alert Radius: %d m Location Used to detect arrival at your destination. Background Location @@ -932,8 +934,8 @@ Required to wake your device upon arrival. Tap to grant. %1$d m %1$.1f km - Travel Alarm active - %1$s remaining + Travel alarm active + %1$s remaining (%2$d%%) Travel Progress Shows real-time distance to destination Destination Nearby From 5190790890344833790be9ea38ff54c8676cf021 Mon Sep 17 00:00:00 2001 From: "sameerasw.com" Date: Sat, 28 Feb 2026 00:14:06 +0530 Subject: [PATCH 24/45] New translations strings.xml (Russian) --- app/src/main/res/values-ru/strings.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 0cf11ce42..ee43cd589 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -113,6 +113,8 @@ Больше Настроек Заморозить все приложения Разморозить все приложения + Export frozen apps list + Import frozen apps list Выбор Приложения для Заморозки Выберите, какие приложения будут заморожены Автоматизация From 4a8d5b3c3569a18ae1a1aac4db77d5c1ae5250a5 Mon Sep 17 00:00:00 2001 From: "sameerasw.com" Date: Sat, 28 Feb 2026 00:14:07 +0530 Subject: [PATCH 25/45] New translations strings.xml (Serbian (Cyrillic)) --- app/src/main/res/values-sr/strings.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/res/values-sr/strings.xml b/app/src/main/res/values-sr/strings.xml index e75199d39..62aa1d9a1 100644 --- a/app/src/main/res/values-sr/strings.xml +++ b/app/src/main/res/values-sr/strings.xml @@ -113,6 +113,8 @@ More options Freeze all apps Unfreeze all apps + Export frozen apps list + Import frozen apps list Pick apps to freeze Choose which apps can be frozen Automation From 4132261f603dc8508bb2e95c81d6f337ed07292b Mon Sep 17 00:00:00 2001 From: "sameerasw.com" Date: Sat, 28 Feb 2026 00:14:09 +0530 Subject: [PATCH 26/45] New translations strings.xml (Swedish) --- app/src/main/res/values-sv/strings.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index e75199d39..62aa1d9a1 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -113,6 +113,8 @@ More options Freeze all apps Unfreeze all apps + Export frozen apps list + Import frozen apps list Pick apps to freeze Choose which apps can be frozen Automation From b86dafb6cf74826d916b9efa85d1d4a4c4157bf2 Mon Sep 17 00:00:00 2001 From: "sameerasw.com" Date: Sat, 28 Feb 2026 00:14:10 +0530 Subject: [PATCH 27/45] New translations strings.xml (Turkish) --- app/src/main/res/values-tr/strings.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index d5966867e..ba3556f21 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -113,6 +113,8 @@ More options Freeze all apps Unfreeze all apps + Export frozen apps list + Import frozen apps list Pick apps to freeze Choose which apps can be frozen Automation From 3360240d79f62eef5809c22a0df9aec8b768570a Mon Sep 17 00:00:00 2001 From: "sameerasw.com" Date: Sat, 28 Feb 2026 00:14:11 +0530 Subject: [PATCH 28/45] New translations strings.xml (Ukrainian) --- app/src/main/res/values-uk/strings.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index e75199d39..62aa1d9a1 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -113,6 +113,8 @@ More options Freeze all apps Unfreeze all apps + Export frozen apps list + Import frozen apps list Pick apps to freeze Choose which apps can be frozen Automation From 5ec6db2f6aa935b67b8eb8085d5c3d2971c53bcd Mon Sep 17 00:00:00 2001 From: "sameerasw.com" Date: Sat, 28 Feb 2026 00:14:12 +0530 Subject: [PATCH 29/45] New translations strings.xml (Chinese Simplified) --- app/src/main/res/values-zh/strings.xml | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/app/src/main/res/values-zh/strings.xml b/app/src/main/res/values-zh/strings.xml index 8795b2e4f..c618e79f6 100644 --- a/app/src/main/res/values-zh/strings.xml +++ b/app/src/main/res/values-zh/strings.xml @@ -113,6 +113,8 @@ More options Freeze all apps Unfreeze all apps + Export frozen apps list + Import frozen apps list Pick apps to freeze Choose which apps can be frozen Automation @@ -479,15 +481,15 @@ Shizuku Required for advanced commands. Install Shizuku from the Play Store. Install Shizuku - Shizuku permission + Grant Permission Required to run power-saving commands while maps is navigating. Requires Shizuku or Root Root Access Permissions required for system actions using Root privileges. Notification Listener - Required to detect when Maps is navigating. - Required to detect new notifications - Required to detect and snooze notifications + Requires notification listener access to monitor Google Maps navigation status and enable power saving when not navigatiing. + Requires notification listener access to detect new notifications and trigger edge lighting. + Requires notification listener access to monitor and snooze unwanted system notifications. Accessibility Service Required for App Lock, Screen off widget and other features to detect interactions Required to trigger notification lighting on new notifications @@ -834,7 +836,7 @@ You are up to date This is a pre-release version and might be unstable. - Release Notes v%1$s + Release Notes %1$s View on GitHub Download APK @@ -890,7 +892,7 @@ Notification lighting does not work It depends on the OEM. Some like OneUI does not seem to allow overlays above the AOD preventing the lighting effects being shown. In this case, try the ambient display as a workaround. Button remap does not work while display is off - Some OEMs limit the accessibility service reporting once the display is actually off but they may still work while the AOD is on. \nIn this case, you may able to use button remaps with AOD on but not with off. \n\nAs a workaround, you will need to use Shizuku permissions and turn on the \'Use Shizuku\' toggle in button remap settings which identifies and listen to hardware input events.\nThis is not guaranteed to work on all devices and needs testing.\n\nAnd even if it\'s on, Shizuku method only will be used when it\'s needed. Otherwise it will always fallback to Accessibility which also handles the blocking of the actual input during long press. + Some OEMs limit the accessibility service reporting once the display is actually off but they may still work while the AOD is on. \nIn this case, you may able to use button remaps with AOD on but not with off. \n\nAs a workaround, you will need to use Shizuku permissions and turn on the \'Use Shizuku or Root\' toggle in button remap settings which identifies and listen to hardware input events.\nThis is not guaranteed to work on all devices and needs testing.\n\nAnd even if it\'s on, Shizuku method only will be used when it\'s needed. Otherwise it will always fallback to Accessibility which also handles the blocking of the actual input during long press. Flashlight brightness does not work Only a limited number of devices got hardware and software support adjusting the flashlight intensity. \n\n\'The minimum version of Android is 13 (SDK33).\nFlashlight brightness control only supports HAL version 3.8 and higher, so among the supported devices, the latest ones (For example, Pixel 6/7, Samsung S23, etc.)\'\npolodarb/Flashlight-Tiramisu What the hell is this app? @@ -908,10 +910,10 @@ Essentials Bug Report Send via - Are We There Yet? - Prepare for your destination. - Open your map app, pick a location, and share it to Essentials. - Radius: %d m + Are we there yet? + Destination nearby alerts + Open Google Maps, pick a location, and share it to Essentials. + Alert Radius: %d m Location Used to detect arrival at your destination. Background Location @@ -932,8 +934,8 @@ Required to wake your device upon arrival. Tap to grant. %1$d m %1$.1f km - Travel Alarm active - %1$s remaining + Travel alarm active + %1$s remaining (%2$d%%) Travel Progress Shows real-time distance to destination Destination Nearby From 49b895cbadeac2d300b72afa7af2d5fc93252eb1 Mon Sep 17 00:00:00 2001 From: "sameerasw.com" Date: Sat, 28 Feb 2026 00:14:13 +0530 Subject: [PATCH 30/45] New translations strings.xml (Chinese Traditional) --- app/src/main/res/values-zh/strings.xml | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/app/src/main/res/values-zh/strings.xml b/app/src/main/res/values-zh/strings.xml index c618e79f6..68f178f4e 100644 --- a/app/src/main/res/values-zh/strings.xml +++ b/app/src/main/res/values-zh/strings.xml @@ -481,15 +481,15 @@ Shizuku Required for advanced commands. Install Shizuku from the Play Store. Install Shizuku - Grant Permission + Shizuku permission Required to run power-saving commands while maps is navigating. Requires Shizuku or Root Root Access Permissions required for system actions using Root privileges. Notification Listener - Requires notification listener access to monitor Google Maps navigation status and enable power saving when not navigatiing. - Requires notification listener access to detect new notifications and trigger edge lighting. - Requires notification listener access to monitor and snooze unwanted system notifications. + Required to detect when Maps is navigating. + Required to detect new notifications + Required to detect and snooze notifications Accessibility Service Required for App Lock, Screen off widget and other features to detect interactions Required to trigger notification lighting on new notifications @@ -836,7 +836,7 @@ You are up to date This is a pre-release version and might be unstable. - Release Notes %1$s + Release Notes v%1$s View on GitHub Download APK @@ -892,7 +892,7 @@ Notification lighting does not work It depends on the OEM. Some like OneUI does not seem to allow overlays above the AOD preventing the lighting effects being shown. In this case, try the ambient display as a workaround. Button remap does not work while display is off - Some OEMs limit the accessibility service reporting once the display is actually off but they may still work while the AOD is on. \nIn this case, you may able to use button remaps with AOD on but not with off. \n\nAs a workaround, you will need to use Shizuku permissions and turn on the \'Use Shizuku or Root\' toggle in button remap settings which identifies and listen to hardware input events.\nThis is not guaranteed to work on all devices and needs testing.\n\nAnd even if it\'s on, Shizuku method only will be used when it\'s needed. Otherwise it will always fallback to Accessibility which also handles the blocking of the actual input during long press. + Some OEMs limit the accessibility service reporting once the display is actually off but they may still work while the AOD is on. \nIn this case, you may able to use button remaps with AOD on but not with off. \n\nAs a workaround, you will need to use Shizuku permissions and turn on the \'Use Shizuku\' toggle in button remap settings which identifies and listen to hardware input events.\nThis is not guaranteed to work on all devices and needs testing.\n\nAnd even if it\'s on, Shizuku method only will be used when it\'s needed. Otherwise it will always fallback to Accessibility which also handles the blocking of the actual input during long press. Flashlight brightness does not work Only a limited number of devices got hardware and software support adjusting the flashlight intensity. \n\n\'The minimum version of Android is 13 (SDK33).\nFlashlight brightness control only supports HAL version 3.8 and higher, so among the supported devices, the latest ones (For example, Pixel 6/7, Samsung S23, etc.)\'\npolodarb/Flashlight-Tiramisu What the hell is this app? @@ -910,10 +910,10 @@ Essentials Bug Report Send via - Are we there yet? - Destination nearby alerts - Open Google Maps, pick a location, and share it to Essentials. - Alert Radius: %d m + Are We There Yet? + Prepare for your destination. + Open your map app, pick a location, and share it to Essentials. + Radius: %d m Location Used to detect arrival at your destination. Background Location @@ -934,8 +934,8 @@ Required to wake your device upon arrival. Tap to grant. %1$d m %1$.1f km - Travel alarm active - %1$s remaining (%2$d%%) + Travel Alarm active + %1$s remaining Travel Progress Shows real-time distance to destination Destination Nearby From 8b1630515665958d9a678593a47e2d68d4491658 Mon Sep 17 00:00:00 2001 From: "sameerasw.com" Date: Sat, 28 Feb 2026 00:14:14 +0530 Subject: [PATCH 31/45] New translations strings.xml (English) --- app/src/main/res/values-en/strings.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/res/values-en/strings.xml b/app/src/main/res/values-en/strings.xml index e75199d39..62aa1d9a1 100644 --- a/app/src/main/res/values-en/strings.xml +++ b/app/src/main/res/values-en/strings.xml @@ -113,6 +113,8 @@ More options Freeze all apps Unfreeze all apps + Export frozen apps list + Import frozen apps list Pick apps to freeze Choose which apps can be frozen Automation From eca8497519fee567ec152bbbf3e6b2e88499757a Mon Sep 17 00:00:00 2001 From: "sameerasw.com" Date: Sat, 28 Feb 2026 00:14:16 +0530 Subject: [PATCH 32/45] New translations strings.xml (Vietnamese) --- app/src/main/res/values-vi/strings.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml index e75199d39..62aa1d9a1 100644 --- a/app/src/main/res/values-vi/strings.xml +++ b/app/src/main/res/values-vi/strings.xml @@ -113,6 +113,8 @@ More options Freeze all apps Unfreeze all apps + Export frozen apps list + Import frozen apps list Pick apps to freeze Choose which apps can be frozen Automation From 97322d266f84e19085dffcf15f486ff793fad2b3 Mon Sep 17 00:00:00 2001 From: "sameerasw.com" Date: Sat, 28 Feb 2026 00:14:17 +0530 Subject: [PATCH 33/45] New translations strings.xml (Portuguese, Brazilian) --- app/src/main/res/values-pt/strings.xml | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index 0c02d6026..9b9fe8d0a 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -26,7 +26,7 @@ Another note, the biometric authentication prompt only lets you use STRONG secure class methods. Face unlock security methods in WEAK class in devices such as Pixel 7 will only be able to utilize the available other STRONG auth methods such as fingerprint or pin. Enable Button Remap - Use Shizuku or Root or Root + Use Shizuku Works with screen off (Recommended) Shizuku is not running Detected %1$s @@ -481,15 +481,15 @@ Shizuku Required for advanced commands. Install Shizuku from the Play Store. Install Shizuku - Grant Permission + Shizuku permission Required to run power-saving commands while maps is navigating. Requires Shizuku or Root Root Access Permissions required for system actions using Root privileges. Notification Listener - Requires notification listener access to monitor Google Maps navigation status and enable power saving when not navigatiing. - Requires notification listener access to detect new notifications and trigger edge lighting. - Requires notification listener access to monitor and snooze unwanted system notifications. + Required to detect when Maps is navigating. + Required to detect new notifications + Required to detect and snooze notifications Accessibility Service Required for App Lock, Screen off widget and other features to detect interactions Required to trigger notification lighting on new notifications @@ -836,7 +836,7 @@ You are up to date This is a pre-release version and might be unstable. - Release Notes %1$s + Release Notes v%1$s View on GitHub Download APK @@ -892,7 +892,7 @@ Notification lighting does not work It depends on the OEM. Some like OneUI does not seem to allow overlays above the AOD preventing the lighting effects being shown. In this case, try the ambient display as a workaround. Button remap does not work while display is off - Some OEMs limit the accessibility service reporting once the display is actually off but they may still work while the AOD is on. \nIn this case, you may able to use button remaps with AOD on but not with off. \n\nAs a workaround, you will need to use Shizuku permissions and turn on the \'Use Shizuku or Root\' toggle in button remap settings which identifies and listen to hardware input events.\nThis is not guaranteed to work on all devices and needs testing.\n\nAnd even if it\'s on, Shizuku method only will be used when it\'s needed. Otherwise it will always fallback to Accessibility which also handles the blocking of the actual input during long press. + Some OEMs limit the accessibility service reporting once the display is actually off but they may still work while the AOD is on. \nIn this case, you may able to use button remaps with AOD on but not with off. \n\nAs a workaround, you will need to use Shizuku permissions and turn on the \'Use Shizuku\' toggle in button remap settings which identifies and listen to hardware input events.\nThis is not guaranteed to work on all devices and needs testing.\n\nAnd even if it\'s on, Shizuku method only will be used when it\'s needed. Otherwise it will always fallback to Accessibility which also handles the blocking of the actual input during long press. Flashlight brightness does not work Only a limited number of devices got hardware and software support adjusting the flashlight intensity. \n\n\'The minimum version of Android is 13 (SDK33).\nFlashlight brightness control only supports HAL version 3.8 and higher, so among the supported devices, the latest ones (For example, Pixel 6/7, Samsung S23, etc.)\'\npolodarb/Flashlight-Tiramisu What the hell is this app? @@ -910,10 +910,10 @@ Essentials Bug Report Send via - Are we there yet? - Destination nearby alerts - Open Google Maps, pick a location, and share it to Essentials. - Alert Radius: %d m + Are We There Yet? + Prepare for your destination. + Open your map app, pick a location, and share it to Essentials. + Radius: %d m Location Used to detect arrival at your destination. Background Location @@ -934,8 +934,8 @@ Required to wake your device upon arrival. Tap to grant. %1$d m %1$.1f km - Travel alarm active - %1$s remaining (%2$d%%) + Travel Alarm active + %1$s remaining Travel Progress Shows real-time distance to destination Destination Nearby From 08a3cfcfae51cf022d345d69d54a261415f6da44 Mon Sep 17 00:00:00 2001 From: "sameerasw.com" Date: Sat, 28 Feb 2026 00:14:18 +0530 Subject: [PATCH 34/45] New translations strings.xml (Sinhala) --- app/src/main/res/values-si/strings.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/res/values-si/strings.xml b/app/src/main/res/values-si/strings.xml index e75199d39..62aa1d9a1 100644 --- a/app/src/main/res/values-si/strings.xml +++ b/app/src/main/res/values-si/strings.xml @@ -113,6 +113,8 @@ More options Freeze all apps Unfreeze all apps + Export frozen apps list + Import frozen apps list Pick apps to freeze Choose which apps can be frozen Automation From 077341efa087ff971416fbc1e90407556dcabbd3 Mon Sep 17 00:00:00 2001 From: "sameerasw.com" Date: Sat, 28 Feb 2026 00:14:19 +0530 Subject: [PATCH 35/45] New translations strings.xml (Acholi) --- app/src/main/res/values-ach/strings.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/res/values-ach/strings.xml b/app/src/main/res/values-ach/strings.xml index e75199d39..62aa1d9a1 100644 --- a/app/src/main/res/values-ach/strings.xml +++ b/app/src/main/res/values-ach/strings.xml @@ -113,6 +113,8 @@ More options Freeze all apps Unfreeze all apps + Export frozen apps list + Import frozen apps list Pick apps to freeze Choose which apps can be frozen Automation From 7604d5f7b5025c63b1ad8ab02c350923da4ca7e9 Mon Sep 17 00:00:00 2001 From: sameerasw Date: Sat, 28 Feb 2026 15:41:42 +0530 Subject: [PATCH 36/45] feat: #156 Pixel chargign optimization toggle tile --- .../res/drawable/outline_battery_android_frame_bolt_24.xml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 app/src/main/res/drawable/outline_battery_android_frame_bolt_24.xml diff --git a/app/src/main/res/drawable/outline_battery_android_frame_bolt_24.xml b/app/src/main/res/drawable/outline_battery_android_frame_bolt_24.xml new file mode 100644 index 000000000..56fd38250 --- /dev/null +++ b/app/src/main/res/drawable/outline_battery_android_frame_bolt_24.xml @@ -0,0 +1,5 @@ + + + + + From 208e44916848f114234a4a484f01ce5a6a315b05 Mon Sep 17 00:00:00 2001 From: sameerasw Date: Sat, 28 Feb 2026 15:41:57 +0530 Subject: [PATCH 37/45] feat: #156 Pixel chargign optimization toggle tile --- app/src/main/AndroidManifest.xml | 10 ++ .../domain/registry/FeatureRegistry.kt | 21 ++++ .../domain/registry/PermissionRegistry.kt | 1 + .../services/tiles/ChargeQuickTileService.kt | 99 +++++++++++++++++++ .../ui/activities/QSPreferencesActivity.kt | 2 + .../configs/QuickSettingsTilesSettingsUI.kt | 8 ++ .../drawable/rounded_battery_android_0_24.xml | 9 ++ ...ounded_battery_android_frame_shield_24.xml | 9 ++ app/src/main/res/values/strings.xml | 11 +++ 9 files changed, 170 insertions(+) create mode 100644 app/src/main/java/com/sameerasw/essentials/services/tiles/ChargeQuickTileService.kt create mode 100644 app/src/main/res/drawable/rounded_battery_android_0_24.xml create mode 100644 app/src/main/res/drawable/rounded_battery_android_frame_shield_24.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index d0b233fe8..d5cc14152 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -631,6 +631,16 @@ + + + + + = Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { + val pendingIntent = android.app.PendingIntent.getActivity( + this, + 0, + intent, + android.app.PendingIntent.FLAG_UPDATE_CURRENT or android.app.PendingIntent.FLAG_IMMUTABLE + ) + startActivityAndCollapse(pendingIntent) + } else { + @Suppress("DEPRECATION") + startActivityAndCollapse(intent) + } + return + } + super.onClick() + } + + override fun onTileClick() { + val adaptiveChargingEnabled = Settings.Secure.getInt(contentResolver, ADAPTIVE_CHARGING_SETTING, 0) == 1 + val chargeOptimizationEnabled = Settings.Secure.getInt(contentResolver, CHARGE_OPTIMIZATION_MODE, 0) == 1 + + when { + adaptiveChargingEnabled -> { + Settings.Secure.putInt(contentResolver, CHARGE_OPTIMIZATION_MODE, 1) + Settings.Secure.putInt(contentResolver, ADAPTIVE_CHARGING_SETTING, 0) + } + + chargeOptimizationEnabled -> { + Settings.Secure.putInt(contentResolver, CHARGE_OPTIMIZATION_MODE, 0) + Settings.Secure.putInt(contentResolver, ADAPTIVE_CHARGING_SETTING, 0) + } + + else -> { + Settings.Secure.putInt(contentResolver, CHARGE_OPTIMIZATION_MODE, 0) + Settings.Secure.putInt(contentResolver, ADAPTIVE_CHARGING_SETTING, 1) + } + } + } + + override fun getTileLabel(): String = getString(R.string.tile_charge_optimization) + + override fun getTileSubtitle(): String { + val adaptiveChargingEnabled = Settings.Secure.getInt(contentResolver, ADAPTIVE_CHARGING_SETTING, 0) == 1 + val chargeOptimizationEnabled = Settings.Secure.getInt(contentResolver, CHARGE_OPTIMIZATION_MODE, 0) == 1 + return when { + chargeOptimizationEnabled -> getString(R.string.limit_to_80) + adaptiveChargingEnabled -> getString(R.string.adaptive_charging) + else -> getString(R.string.deactivated) + } + } + + override fun hasFeaturePermission(): Boolean { + return PermissionUtils.canWriteSecureSettings(this) + } + + override fun getTileIcon(): Icon { + val adaptiveChargingEnabled = Settings.Secure.getInt(contentResolver, ADAPTIVE_CHARGING_SETTING, 0) == 1 + val chargeOptimizationEnabled = Settings.Secure.getInt(contentResolver, CHARGE_OPTIMIZATION_MODE, 0) == 1 + val resId = when { + chargeOptimizationEnabled -> R.drawable.rounded_battery_android_frame_shield_24 + adaptiveChargingEnabled -> R.drawable.rounded_battery_android_frame_plus_24 + else -> R.drawable.outline_battery_android_frame_bolt_24 + } + return Icon.createWithResource(this, resId) + } + + override fun getTileState(): Int { + val adaptiveChargingEnabled = Settings.Secure.getInt(contentResolver, ADAPTIVE_CHARGING_SETTING, 0) == 1 + val chargeOptimizationEnabled = Settings.Secure.getInt(contentResolver, CHARGE_OPTIMIZATION_MODE, 0) == 1 + return if (chargeOptimizationEnabled || adaptiveChargingEnabled) Tile.STATE_ACTIVE else Tile.STATE_INACTIVE + } +} diff --git a/app/src/main/java/com/sameerasw/essentials/ui/activities/QSPreferencesActivity.kt b/app/src/main/java/com/sameerasw/essentials/ui/activities/QSPreferencesActivity.kt index f7c4c2822..ec41eb4ab 100644 --- a/app/src/main/java/com/sameerasw/essentials/ui/activities/QSPreferencesActivity.kt +++ b/app/src/main/java/com/sameerasw/essentials/ui/activities/QSPreferencesActivity.kt @@ -75,9 +75,11 @@ class QSPreferencesActivity : ComponentActivity() { "com.sameerasw.essentials.services.tiles.UsbDebuggingTileService" -> "Quick settings tiles" "com.sameerasw.essentials.services.tiles.DeveloperOptionsTileService" -> "Quick settings tiles" "com.sameerasw.essentials.services.tiles.BatteryNotificationTileService" -> "Battery notification" + "com.sameerasw.essentials.services.tiles.ChargeQuickTileService" -> "Quick settings tiles" else -> null } + Log.d("QSPreferences", "Mapping to feature: $feature") if (feature != null) { diff --git a/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/QuickSettingsTilesSettingsUI.kt b/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/QuickSettingsTilesSettingsUI.kt index 1857e14a5..6e6237398 100644 --- a/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/QuickSettingsTilesSettingsUI.kt +++ b/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/QuickSettingsTilesSettingsUI.kt @@ -48,6 +48,7 @@ import com.sameerasw.essentials.services.tiles.AppLockTileService import com.sameerasw.essentials.services.tiles.BatteryNotificationTileService import com.sameerasw.essentials.services.tiles.BubblesTileService import com.sameerasw.essentials.services.tiles.CaffeinateTileService +import com.sameerasw.essentials.services.tiles.ChargeQuickTileService import com.sameerasw.essentials.services.tiles.DeveloperOptionsTileService import com.sameerasw.essentials.services.tiles.DynamicNightLightTileService import com.sameerasw.essentials.services.tiles.FlashlightPulseTileService @@ -269,6 +270,13 @@ fun QuickSettingsTilesSettingsUI( BatteryNotificationTileService::class.java, listOf("POST_NOTIFICATIONS", "BLUETOOTH_CONNECT", "BLUETOOTH_SCAN"), R.string.feat_battery_notification_desc + ), + QSTileInfo( + R.string.tile_charge_optimization, + R.drawable.rounded_battery_android_frame_shield_24, + ChargeQuickTileService::class.java, + listOf("WRITE_SECURE_SETTINGS"), + R.string.about_desc_charge_optimization ) ) diff --git a/app/src/main/res/drawable/rounded_battery_android_0_24.xml b/app/src/main/res/drawable/rounded_battery_android_0_24.xml new file mode 100644 index 000000000..570cf5068 --- /dev/null +++ b/app/src/main/res/drawable/rounded_battery_android_0_24.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/rounded_battery_android_frame_shield_24.xml b/app/src/main/res/drawable/rounded_battery_android_frame_shield_24.xml new file mode 100644 index 000000000..c8b08abd0 --- /dev/null +++ b/app/src/main/res/drawable/rounded_battery_android_frame_shield_24.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 217e34bd6..1d84455c1 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -218,6 +218,10 @@ dns.quad9.net CleanBrowsing adult-filter-dns.cleanbrowsing.org + Charging + Limit to 80% + Adaptive + Not optimized Screen locked security @@ -628,6 +632,7 @@ Toggle Private DNS.\n\nCycle through Off, Automatic, and Private DNS provider modes. Toggle USB Debugging.\n\nEnable or disable ADB debugging access directly from the quick settings. Launch the eye dropper tool to pick colors introduced in Android 17 BETA 2 + Optimize your battery life by limiting the maximum charge or using adaptive charging. This is specially designed for Pixel devices to ensure longevity and healthy charging cycles. Download @@ -872,6 +877,12 @@ vibration feel + + battery + charge + optimization + pixel + Invert selection From 1ecc6bc0a823fb6eebbaaeb3f0a0cc2d041e94c2 Mon Sep 17 00:00:00 2001 From: sameerasw Date: Sat, 28 Feb 2026 15:43:33 +0530 Subject: [PATCH 38/45] feat: Credtis to TebbeUbben/ChargeQuickTile --- app/src/main/res/values/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 1d84455c1..65e77973b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -632,7 +632,7 @@ Toggle Private DNS.\n\nCycle through Off, Automatic, and Private DNS provider modes. Toggle USB Debugging.\n\nEnable or disable ADB debugging access directly from the quick settings. Launch the eye dropper tool to pick colors introduced in Android 17 BETA 2 - Optimize your battery life by limiting the maximum charge or using adaptive charging. This is specially designed for Pixel devices to ensure longevity and healthy charging cycles. + Optimize your battery life by limiting the maximum charge or using adaptive charging. This is specially designed for Pixel devices to ensure longevity and healthy charging cycles.\n\nCredits: TebbeUbben/ChargeQuickTile Download From 3b08ef86f2c9cf1d59a908810e95f7405fb6be95 Mon Sep 17 00:00:00 2001 From: sameerasw Date: Sat, 28 Feb 2026 16:08:10 +0530 Subject: [PATCH 39/45] feat: Delete cliboard history entries --- .../ime/EssentialsInputMethodService.kt | 9 +++ .../essentials/ui/ime/KeyboardInputView.kt | 65 +++++++++++++++---- 2 files changed, 60 insertions(+), 14 deletions(-) diff --git a/app/src/main/java/com/sameerasw/essentials/ime/EssentialsInputMethodService.kt b/app/src/main/java/com/sameerasw/essentials/ime/EssentialsInputMethodService.kt index bb9d8146b..8ce43b36e 100644 --- a/app/src/main/java/com/sameerasw/essentials/ime/EssentialsInputMethodService.kt +++ b/app/src/main/java/com/sameerasw/essentials/ime/EssentialsInputMethodService.kt @@ -445,6 +445,9 @@ class EssentialsInputMethodService : InputMethodService(), LifecycleOwner, ViewM undoRedoManager.recordInsert(text) currentInputConnection?.commitText(text, 1) }, + onDeleteClipboardItem = { text -> + deleteClipboardItem(text) + }, onUndoClick = { val ic = currentInputConnection undoRedoManager.undo(ic) @@ -639,4 +642,10 @@ class EssentialsInputMethodService : InputMethodService(), LifecycleOwner, ViewM updateSuggestions() } } + fun deleteClipboardItem(text: String) { + val current = _clipboardHistory.value.toMutableList() + if (current.remove(text)) { + _clipboardHistory.value = current + } + } } diff --git a/app/src/main/java/com/sameerasw/essentials/ui/ime/KeyboardInputView.kt b/app/src/main/java/com/sameerasw/essentials/ui/ime/KeyboardInputView.kt index 5b7b4b068..5d9cd7745 100644 --- a/app/src/main/java/com/sameerasw/essentials/ui/ime/KeyboardInputView.kt +++ b/app/src/main/java/com/sameerasw/essentials/ui/ime/KeyboardInputView.kt @@ -36,9 +36,8 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width -import androidx.compose.foundation.lazy.grid.GridCells -import androidx.compose.foundation.lazy.grid.LazyVerticalGrid -import androidx.compose.foundation.lazy.grid.items +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.ButtonGroup import androidx.compose.material3.ExperimentalMaterial3Api @@ -289,6 +288,7 @@ fun KeyboardInputView( clipboardHistory: List = emptyList(), onSuggestionClick: (Suggestion) -> Unit = {}, onPasteClick: (String) -> Unit = {}, + onDeleteClipboardItem: (String) -> Unit = {}, onUndoClick: () -> Unit = {}, onType: (String) -> Unit, onKeyPress: (Int) -> Unit, @@ -730,21 +730,58 @@ fun KeyboardInputView( color = MaterialTheme.colorScheme.onSurfaceVariant ) } else { - LazyVerticalGrid( - columns = GridCells.Fixed(2), - horizontalArrangement = Arrangement.spacedBy(8.dp), + LazyColumn( verticalArrangement = Arrangement.spacedBy(8.dp), modifier = Modifier.fillMaxSize() ) { - items(clipboardHistory) { clipText -> - ClipboardItem( - text = clipText, - shape = RoundedCornerShape(keyRoundness), - onClick = { - onPasteClick(clipText) - isClipboardMode = false + items(clipboardHistory, key = { it }) { clipText -> + val dismissState = androidx.compose.material3.rememberSwipeToDismissBoxState( + confirmValueChange = { value -> + if (value == androidx.compose.material3.SwipeToDismissBoxValue.StartToEnd || + value == androidx.compose.material3.SwipeToDismissBoxValue.EndToStart) { + onDeleteClipboardItem(clipText) + performHeavyHaptic() + true + } else false + } + ) + + androidx.compose.material3.SwipeToDismissBox( + state = dismissState, + backgroundContent = { + val color by animateColorAsState( + when (dismissState.targetValue) { + androidx.compose.material3.SwipeToDismissBoxValue.Settled -> MaterialTheme.colorScheme.surfaceContainerLow + else -> MaterialTheme.colorScheme.errorContainer + }, label = "dismissBackground" + ) + Box( + Modifier + .fillMaxSize() + .clip(RoundedCornerShape(keyRoundness)) + .background(color) + .padding(horizontal = 16.dp), + contentAlignment = if (dismissState.dismissDirection == androidx.compose.material3.SwipeToDismissBoxValue.StartToEnd) + Alignment.CenterStart else Alignment.CenterEnd + ) { + Icon( + painter = painterResource(R.drawable.rounded_delete_24), + contentDescription = "Delete", + tint = MaterialTheme.colorScheme.onErrorContainer + ) + } }, - modifier = Modifier.fillMaxWidth() + content = { + ClipboardItem( + text = clipText, + shape = RoundedCornerShape(keyRoundness), + onClick = { + onPasteClick(clipText) + isClipboardMode = false + }, + modifier = Modifier.fillMaxWidth() + ) + } ) } } From 4fc14c570257e58799c772e4449a134ad0d999b4 Mon Sep 17 00:00:00 2001 From: sameerasw Date: Sat, 28 Feb 2026 16:24:56 +0530 Subject: [PATCH 40/45] fix: Emoji loading in keyboard --- app/proguard-rules.pro | 7 ++++++- .../java/com/sameerasw/essentials/ui/ime/EmojiData.kt | 10 ++++++---- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 6c52fb89e..18ac13ce5 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -36,4 +36,9 @@ # Prevent over-minification of settings and registry classes -keep class com.sameerasw.essentials.data.repository.** { *; } --keep class com.sameerasw.essentials.domain.registry.** { *; } \ No newline at end of file +-keep class com.sameerasw.essentials.domain.registry.** { *; } + +# Emoji data classes for Gson +-keep class com.sameerasw.essentials.ui.ime.EmojiObject { *; } +-keep class com.sameerasw.essentials.ui.ime.EmojiCategory { *; } +-keep class com.sameerasw.essentials.ui.ime.EmojiDataResponse { *; } \ No newline at end of file diff --git a/app/src/main/java/com/sameerasw/essentials/ui/ime/EmojiData.kt b/app/src/main/java/com/sameerasw/essentials/ui/ime/EmojiData.kt index 4000058b1..26da55106 100644 --- a/app/src/main/java/com/sameerasw/essentials/ui/ime/EmojiData.kt +++ b/app/src/main/java/com/sameerasw/essentials/ui/ime/EmojiData.kt @@ -1,7 +1,9 @@ package com.sameerasw.essentials.ui.ime import android.content.Context +import android.util.Log import com.google.gson.Gson +import com.google.gson.annotations.SerializedName import com.sameerasw.essentials.R import java.io.InputStreamReader import androidx.compose.runtime.getValue @@ -14,8 +16,8 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.withContext data class EmojiObject( - val emoji: String, - val name: String + @SerializedName("emoji") val emoji: String, + @SerializedName("name") val name: String ) data class EmojiCategory( @@ -25,7 +27,7 @@ data class EmojiCategory( ) data class EmojiDataResponse( - val emojis: Map>> + @SerializedName("emojis") val emojis: Map>> ) object EmojiData { @@ -94,7 +96,7 @@ object EmojiData { reader.close() inputStream.close() } catch (e: Exception) { - e.printStackTrace() + Log.e("EmojiData", "Error loading emojis", e) withContext(Dispatchers.Main) { _isLoading.value = false } From 399432886bea2088ecc7b83f8bbd680095488447 Mon Sep 17 00:00:00 2001 From: sameerasw Date: Sat, 28 Feb 2026 17:22:02 +0530 Subject: [PATCH 41/45] feat: AOD settings --- .../essentials/FeatureSettingsActivity.kt | 12 +++++ .../data/repository/SettingsRepository.kt | 21 ++++++++ .../domain/registry/FeatureRegistry.kt | 17 +++++++ .../configs/AlwaysOnDisplaySettingsUI.kt | 50 +++++++++++++++++++ .../essentials/viewmodels/MainViewModel.kt | 19 +++++++ app/src/main/res/values/strings.xml | 2 + 6 files changed, 121 insertions(+) create mode 100644 app/src/main/java/com/sameerasw/essentials/ui/composables/configs/AlwaysOnDisplaySettingsUI.kt diff --git a/app/src/main/java/com/sameerasw/essentials/FeatureSettingsActivity.kt b/app/src/main/java/com/sameerasw/essentials/FeatureSettingsActivity.kt index c33c91ccf..93ccf7fbe 100644 --- a/app/src/main/java/com/sameerasw/essentials/FeatureSettingsActivity.kt +++ b/app/src/main/java/com/sameerasw/essentials/FeatureSettingsActivity.kt @@ -47,7 +47,9 @@ import com.sameerasw.essentials.ui.components.cards.FeatureCard import com.sameerasw.essentials.ui.components.containers.RoundedCardContainer import com.sameerasw.essentials.ui.components.linkActions.LinkPickerScreen import com.sameerasw.essentials.ui.components.sheets.PermissionsBottomSheet +import com.sameerasw.essentials.ui.composables.configs.AlwaysOnDisplaySettingsUI import com.sameerasw.essentials.ui.composables.configs.AmbientMusicGlanceSettingsUI + import com.sameerasw.essentials.ui.composables.configs.AppLockSettingsUI import com.sameerasw.essentials.ui.composables.configs.BatteriesSettingsUI import com.sameerasw.essentials.ui.composables.configs.BatteryNotificationSettingsUI @@ -245,6 +247,7 @@ class FeatureSettingsActivity : FragmentActivity() { "Caffeinate" -> !viewModel.isPostNotificationsEnabled.value "Battery notification" -> !viewModel.isPostNotificationsEnabled.value || (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && !viewModel.isBluetoothPermissionGranted.value) "Text and animations" -> !viewModel.isWriteSettingsEnabled.value || !isWriteSecureSettingsEnabled + "Always on Display" -> !isWriteSecureSettingsEnabled else -> false } if (hasMissingPermissions) { @@ -656,7 +659,16 @@ class FeatureSettingsActivity : FragmentActivity() { highlightSetting = highlightSetting ) } + + "Always on Display" -> { + AlwaysOnDisplaySettingsUI( + viewModel = viewModel, + modifier = Modifier.padding(top = 16.dp), + highlightSetting = highlightSetting + ) + } } + } } } diff --git a/app/src/main/java/com/sameerasw/essentials/data/repository/SettingsRepository.kt b/app/src/main/java/com/sameerasw/essentials/data/repository/SettingsRepository.kt index f39f0ecee..e2fd5eb81 100644 --- a/app/src/main/java/com/sameerasw/essentials/data/repository/SettingsRepository.kt +++ b/app/src/main/java/com/sameerasw/essentials/data/repository/SettingsRepository.kt @@ -830,4 +830,25 @@ class SettingsRepository(private val context: Context) { e.printStackTrace() } } + + fun isAodEnabled(): Boolean { + return android.provider.Settings.Secure.getInt( + context.contentResolver, + "doze_always_on", + 1 + ) == 1 + } + + fun setAodEnabled(enabled: Boolean) { + try { + android.provider.Settings.Secure.putInt( + context.contentResolver, + "doze_always_on", + if (enabled) 1 else 0 + ) + } catch (e: Exception) { + e.printStackTrace() + } + } } + diff --git a/app/src/main/java/com/sameerasw/essentials/domain/registry/FeatureRegistry.kt b/app/src/main/java/com/sameerasw/essentials/domain/registry/FeatureRegistry.kt index 96f65605b..ffd570a7b 100644 --- a/app/src/main/java/com/sameerasw/essentials/domain/registry/FeatureRegistry.kt +++ b/app/src/main/java/com/sameerasw/essentials/domain/registry/FeatureRegistry.kt @@ -150,6 +150,23 @@ object FeatureRegistry { override fun isEnabled(viewModel: MainViewModel) = true override fun onToggle(viewModel: MainViewModel, context: Context, enabled: Boolean) {} }, + object : Feature( + id = "Always on Display", + title = R.string.feat_always_on_display_title, + iconRes = R.drawable.rounded_mobile_text_2_24, + category = R.string.cat_interface, + description = R.string.feat_always_on_display_desc, + aboutDescription = R.string.about_desc_aod, + permissionKeys = listOf("WRITE_SECURE_SETTINGS"), + showToggle = true, + parentFeatureId = "Display" + ) { + override fun isEnabled(viewModel: MainViewModel) = viewModel.isAodEnabled.value + override fun onToggle(viewModel: MainViewModel, context: Context, enabled: Boolean) { + viewModel.setAodEnabled(enabled) + } + }, + object : Feature( id = "Text and animations", diff --git a/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/AlwaysOnDisplaySettingsUI.kt b/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/AlwaysOnDisplaySettingsUI.kt new file mode 100644 index 000000000..fbffdec8e --- /dev/null +++ b/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/AlwaysOnDisplaySettingsUI.kt @@ -0,0 +1,50 @@ +package com.sameerasw.essentials.ui.composables.configs + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalView +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import com.sameerasw.essentials.R +import com.sameerasw.essentials.ui.components.cards.IconToggleItem +import com.sameerasw.essentials.ui.components.containers.RoundedCardContainer +import com.sameerasw.essentials.ui.modifiers.highlight +import com.sameerasw.essentials.utils.HapticUtil +import com.sameerasw.essentials.viewmodels.MainViewModel + +@Composable +fun AlwaysOnDisplaySettingsUI( + viewModel: MainViewModel, + modifier: Modifier = Modifier, + highlightSetting: String? = null +) { + val view = LocalView.current + + Column( + modifier = modifier + .fillMaxWidth() + .padding(16.dp), + verticalArrangement = Arrangement.spacedBy(16.dp) + ) { + RoundedCardContainer( + modifier = Modifier, + spacing = 2.dp, + cornerRadius = 24.dp + ) { + IconToggleItem( + iconRes = R.drawable.rounded_mobile_text_2_24, + title = stringResource(R.string.feat_always_on_display_title), + isChecked = viewModel.isAodEnabled.value, + onCheckedChange = { checked -> + HapticUtil.performVirtualKeyHaptic(view) + viewModel.setAodEnabled(checked) + }, + modifier = Modifier.highlight(highlightSetting == "aod_toggle") + ) + } + } +} diff --git a/app/src/main/java/com/sameerasw/essentials/viewmodels/MainViewModel.kt b/app/src/main/java/com/sameerasw/essentials/viewmodels/MainViewModel.kt index fee8c7336..850974116 100644 --- a/app/src/main/java/com/sameerasw/essentials/viewmodels/MainViewModel.kt +++ b/app/src/main/java/com/sameerasw/essentials/viewmodels/MainViewModel.kt @@ -108,6 +108,8 @@ class MainViewModel : ViewModel() { val isCalendarSyncEnabled = mutableStateOf(false) val isCalendarSyncPeriodicEnabled = mutableStateOf(false) val isBatteryNotificationEnabled = mutableStateOf(false) + val isAodEnabled = mutableStateOf(false) + data class CalendarAccount( val id: Long, @@ -230,6 +232,9 @@ class MainViewModel : ViewModel() { Settings.Secure.getUriFor("display_density_forced") -> { smallestWidth.intValue = settingsRepository.getSmallestWidth() } + Settings.Secure.getUriFor("doze_always_on") -> { + isAodEnabled.value = settingsRepository.isAodEnabled() + } } } } @@ -463,6 +468,12 @@ class MainViewModel : ViewModel() { contentObserver ) + context.contentResolver.registerContentObserver( + Settings.Secure.getUriFor("doze_always_on"), + false, + contentObserver + ) + settingsRepository.registerOnSharedPreferenceChangeListener(preferenceChangeListener) viewModelScope.launch { @@ -514,6 +525,8 @@ class MainViewModel : ViewModel() { settingsRepository.getFloat(SettingsRepository.KEY_EDGE_LIGHTING_INDICATOR_X, 50f) notificationLightingIndicatorY.value = settingsRepository.getFloat(SettingsRepository.KEY_EDGE_LIGHTING_INDICATOR_Y, 2f) + isAodEnabled.value = settingsRepository.isAodEnabled() + isRootEnabled.value = settingsRepository.getBoolean(SettingsRepository.KEY_USE_ROOT) if (isRootEnabled.value) { @@ -2139,7 +2152,13 @@ class MainViewModel : ViewModel() { return com.sameerasw.essentials.utils.LogManager.generateReport(context, settingsJson) } + fun setAodEnabled(enabled: Boolean) { + settingsRepository.setAodEnabled(enabled) + isAodEnabled.value = enabled + } + override fun onCleared() { + super.onCleared() appContext?.contentResolver?.unregisterContentObserver(contentObserver) if (::settingsRepository.isInitialized) { diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 65e77973b..159a0cdc2 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -376,6 +376,8 @@ Disable rarely used apps Watermark Add EXIF data and logos to photos + Always on Display + Show time and info while screen off Calendar Sync Sync events to your watch Overlay From dad8e44674fd4658c1dfcc93eae85909479b9a73 Mon Sep 17 00:00:00 2001 From: sameerasw Date: Sat, 28 Feb 2026 18:25:14 +0530 Subject: [PATCH 42/45] feat: Dynamic AOD/ Notification glance --- app/src/main/AndroidManifest.xml | 23 ++--- .../data/repository/SettingsRepository.kt | 11 +++ .../services/NotificationListener.kt | 68 +++++++++++++++ .../tiles/AlwaysOnDisplayTileService.kt | 56 +++++++++--- .../ui/activities/QSPreferencesActivity.kt | 1 + .../configs/AlwaysOnDisplaySettingsUI.kt | 85 +++++++++++++++++++ .../essentials/viewmodels/MainViewModel.kt | 30 ++++++- .../res/drawable/outline_mobile_chat_24.xml | 5 ++ app/src/main/res/values/strings.xml | 5 ++ 9 files changed, 263 insertions(+), 21 deletions(-) create mode 100644 app/src/main/res/drawable/outline_mobile_chat_24.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index d5cc14152..859eff45a 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -406,16 +406,6 @@ - - - - - + + + + + + + + ) = + saveAppSelection(KEY_NOTIFICATION_GLANCE_SELECTED_APPS, apps) + + fun updateNotificationGlanceAppSelection(packageName: String, enabled: Boolean) = + updateAppSelection(KEY_NOTIFICATION_GLANCE_SELECTED_APPS, packageName, enabled) + private fun updateAppSelection(key: String, packageName: String, enabled: Boolean) { val current = loadAppSelection(key).toMutableList() val index = current.indexOfFirst { it.packageName == packageName } diff --git a/app/src/main/java/com/sameerasw/essentials/services/NotificationListener.kt b/app/src/main/java/com/sameerasw/essentials/services/NotificationListener.kt index 26d99b8ff..52a451000 100644 --- a/app/src/main/java/com/sameerasw/essentials/services/NotificationListener.kt +++ b/app/src/main/java/com/sameerasw/essentials/services/NotificationListener.kt @@ -27,6 +27,8 @@ class NotificationListener : NotificationListenerService() { "com.sameerasw.essentials.ACTION_REQUEST_AMBIENT_GLANCE" } + private val activeGlanceNotifications = mutableSetOf() + private val likeActionReceiver = object : android.content.BroadcastReceiver() { override fun onReceive(context: Context?, intent: Intent?) { if (intent?.action == ACTION_LIKE_CURRENT_SONG) { @@ -749,6 +751,8 @@ class NotificationListener : NotificationListenerService() { applicationContext.sendBroadcast(pulseIntent) } } + + handleNotificationGlance(sbn, true) } catch (_: Exception) { // ignore failures } @@ -803,6 +807,70 @@ class NotificationListener : NotificationListenerService() { if (sbn.packageName == "com.google.android.apps.maps") { MapsState.hasNavigationNotification = false } + handleNotificationGlance(sbn, false) + } + + private fun handleNotificationGlance(sbn: StatusBarNotification, isPosted: Boolean) { + try { + val prefs = getSharedPreferences("essentials_prefs", MODE_PRIVATE) + val enabled = prefs.getBoolean(SettingsRepository.KEY_NOTIFICATION_GLANCE_ENABLED, false) + if (!enabled) { + if (activeGlanceNotifications.isNotEmpty()) { + activeGlanceNotifications.clear() + updateAodState(false) + } + return + } + + val pkg = sbn.packageName + if (pkg == packageName) return + + if (isPosted) { + if (isAppSelectedForNotificationGlance(pkg)) { + activeGlanceNotifications.add(sbn.key) + } + } else { + activeGlanceNotifications.remove(sbn.key) + } + + updateAodState(activeGlanceNotifications.isNotEmpty()) + + } catch (e: Exception) { + Log.e("NotificationListener", "Error in handleNotificationGlance", e) + } + } + + private fun updateAodState(enable: Boolean) { + try { + val currentValue = Settings.Secure.getInt(contentResolver, "doze_always_on", 0) + val newValue = if (enable) 1 else 0 + if (currentValue != newValue) { + Settings.Secure.putInt(contentResolver, "doze_always_on", newValue) + } + } catch (e: Exception) { + Log.e("NotificationListener", "Failed to update AOD state", e) + } + } + + private fun isAppSelectedForNotificationGlance(packageName: String): Boolean { + try { + val prefs = getSharedPreferences("essentials_prefs", MODE_PRIVATE) + val sameAsLighting = prefs.getBoolean(SettingsRepository.KEY_NOTIFICATION_GLANCE_SAME_AS_LIGHTING, true) + if (sameAsLighting) { + return isAppSelectedForNotificationLighting(packageName) + } + + val json = prefs.getString(SettingsRepository.KEY_NOTIFICATION_GLANCE_SELECTED_APPS, null) + if (json == null) return true + + val selectedApps: List = + com.google.gson.Gson().fromJson(json, Array::class.java).toList() + + val app = selectedApps.find { it.packageName == packageName } + return app?.isEnabled ?: true + } catch (_: Exception) { + return true + } } private fun hasAllRequiredPermissions(): Boolean { diff --git a/app/src/main/java/com/sameerasw/essentials/services/tiles/AlwaysOnDisplayTileService.kt b/app/src/main/java/com/sameerasw/essentials/services/tiles/AlwaysOnDisplayTileService.kt index 853fbd1ee..84df15336 100644 --- a/app/src/main/java/com/sameerasw/essentials/services/tiles/AlwaysOnDisplayTileService.kt +++ b/app/src/main/java/com/sameerasw/essentials/services/tiles/AlwaysOnDisplayTileService.kt @@ -8,14 +8,19 @@ import android.provider.Settings import android.service.quicksettings.Tile import androidx.annotation.RequiresApi import com.sameerasw.essentials.R +import com.sameerasw.essentials.data.repository.SettingsRepository @RequiresApi(Build.VERSION_CODES.N) class AlwaysOnDisplayTileService : BaseTileService() { - override fun getTileLabel(): String = "AOD" + override fun getTileLabel(): String = "Always on Display" override fun getTileSubtitle(): String { - return if (isAodEnabled()) "On" else "Off" + return when { + isGlanceEnabled() -> "Dynamic" + isAodEnabled() -> "On" + else -> "Off" + } } override fun hasFeaturePermission(): Boolean { @@ -23,23 +28,54 @@ class AlwaysOnDisplayTileService : BaseTileService() { } override fun getTileIcon(): Icon? { - return if (isAodEnabled()) { - Icon.createWithResource(this, R.drawable.rounded_mobile_text_2_24) - } else { - Icon.createWithResource(this, R.drawable.rounded_mobile_off_24) + return when { + isGlanceEnabled() -> Icon.createWithResource(this, R.drawable.outline_mobile_chat_24) + isAodEnabled() -> Icon.createWithResource(this, R.drawable.rounded_mobile_text_2_24) + else -> Icon.createWithResource(this, R.drawable.rounded_mobile_off_24) } } override fun getTileState(): Int { - return if (isAodEnabled()) Tile.STATE_ACTIVE else Tile.STATE_INACTIVE + return if (isAodEnabled() || isGlanceEnabled()) Tile.STATE_ACTIVE else Tile.STATE_INACTIVE } override fun onTileClick() { - val newState = if (isAodEnabled()) 0 else 1 - Settings.Secure.putInt(contentResolver, "doze_always_on", newState) + when { + isGlanceEnabled() -> { + // Dynamic -> On + setGlanceEnabled(false) + setAodEnabled(true) + } + isAodEnabled() -> { + // On -> Off + setAodEnabled(false) + setGlanceEnabled(false) + } + else -> { + // Off -> Dynamic + setGlanceEnabled(true) + setAodEnabled(false) + } + } } private fun isAodEnabled(): Boolean { - return Settings.Secure.getInt(contentResolver, "doze_always_on", 1) == 1 + return Settings.Secure.getInt(contentResolver, "doze_always_on", 0) == 1 + } + + private fun setAodEnabled(enabled: Boolean) { + Settings.Secure.putInt(contentResolver, "doze_always_on", if (enabled) 1 else 0) + } + + private fun isGlanceEnabled(): Boolean { + return getSharedPreferences("essentials_prefs", MODE_PRIVATE) + .getBoolean(SettingsRepository.KEY_NOTIFICATION_GLANCE_ENABLED, false) + } + + private fun setGlanceEnabled(enabled: Boolean) { + getSharedPreferences("essentials_prefs", MODE_PRIVATE).edit().apply { + putBoolean(SettingsRepository.KEY_NOTIFICATION_GLANCE_ENABLED, enabled) + apply() + } } } diff --git a/app/src/main/java/com/sameerasw/essentials/ui/activities/QSPreferencesActivity.kt b/app/src/main/java/com/sameerasw/essentials/ui/activities/QSPreferencesActivity.kt index ec41eb4ab..b2667a0b6 100644 --- a/app/src/main/java/com/sameerasw/essentials/ui/activities/QSPreferencesActivity.kt +++ b/app/src/main/java/com/sameerasw/essentials/ui/activities/QSPreferencesActivity.kt @@ -76,6 +76,7 @@ class QSPreferencesActivity : ComponentActivity() { "com.sameerasw.essentials.services.tiles.DeveloperOptionsTileService" -> "Quick settings tiles" "com.sameerasw.essentials.services.tiles.BatteryNotificationTileService" -> "Battery notification" "com.sameerasw.essentials.services.tiles.ChargeQuickTileService" -> "Quick settings tiles" + "com.sameerasw.essentials.services.tiles.AlwaysOnDisplayTileService" -> "Always on Display" else -> null } diff --git a/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/AlwaysOnDisplaySettingsUI.kt b/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/AlwaysOnDisplaySettingsUI.kt index fbffdec8e..6ac4bdb81 100644 --- a/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/AlwaysOnDisplaySettingsUI.kt +++ b/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/AlwaysOnDisplaySettingsUI.kt @@ -2,28 +2,44 @@ package com.sameerasw.essentials.ui.composables.configs import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Button +import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalView import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.sameerasw.essentials.R import com.sameerasw.essentials.ui.components.cards.IconToggleItem import com.sameerasw.essentials.ui.components.containers.RoundedCardContainer +import com.sameerasw.essentials.ui.components.sheets.AppSelectionSheet import com.sameerasw.essentials.ui.modifiers.highlight import com.sameerasw.essentials.utils.HapticUtil import com.sameerasw.essentials.viewmodels.MainViewModel +@OptIn(ExperimentalMaterial3ExpressiveApi::class) @Composable fun AlwaysOnDisplaySettingsUI( viewModel: MainViewModel, modifier: Modifier = Modifier, highlightSetting: String? = null ) { + val context = LocalContext.current val view = LocalView.current + var showAppSelectionSheet by remember { mutableStateOf(false) } + Column( modifier = modifier .fillMaxWidth() @@ -46,5 +62,74 @@ fun AlwaysOnDisplaySettingsUI( modifier = Modifier.highlight(highlightSetting == "aod_toggle") ) } + + Text( + text = stringResource(R.string.feat_notification_glance_title), + style = MaterialTheme.typography.titleMedium, + modifier = Modifier.padding(start = 16.dp, top = 8.dp), + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + + RoundedCardContainer { + IconToggleItem( + iconRes = R.drawable.rounded_notification_settings_24, + title = stringResource(R.string.feat_notification_glance_title), + isChecked = viewModel.isNotificationGlanceEnabled.value, + onCheckedChange = { checked -> + HapticUtil.performVirtualKeyHaptic(view) + viewModel.setNotificationGlanceEnabled(checked) + }, + modifier = Modifier.highlight(highlightSetting == "notification_glance_enabled") + ) + + IconToggleItem( + iconRes = R.drawable.rounded_apps_24, + title = stringResource(R.string.notification_glance_same_as_lighting_title), + isChecked = viewModel.isNotificationGlanceSameAsLightingEnabled.value, + onCheckedChange = { checked -> + HapticUtil.performVirtualKeyHaptic(view) + viewModel.setNotificationGlanceSameAsLightingEnabled(checked) + }, + modifier = Modifier.highlight(highlightSetting == "notification_glance_same_apps") + ) + } + + Text( + text = stringResource(R.string.notification_glance_desc), + modifier = Modifier.padding(horizontal = 16.dp), + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + + if (!viewModel.isNotificationGlanceSameAsLightingEnabled.value) { + Button( + onClick = { + HapticUtil.performVirtualKeyHaptic(view) + showAppSelectionSheet = true + }, + modifier = Modifier.fillMaxWidth(), + enabled = viewModel.isNotificationGlanceEnabled.value + ) { + Text(stringResource(R.string.action_select_apps)) + } + } + + Spacer(modifier = Modifier.height(80.dp)) + + if (showAppSelectionSheet) { + AppSelectionSheet( + onDismissRequest = { showAppSelectionSheet = false }, + onLoadApps = { viewModel.loadNotificationGlanceSelectedApps(it) }, + onSaveApps = { ctx, apps -> viewModel.saveNotificationGlanceSelectedApps(ctx, apps) }, + onAppToggle = { ctx, pkg, enabled -> + viewModel.updateNotificationGlanceAppEnabled( + ctx, + pkg, + enabled + ) + }, + context = context + ) + } } } diff --git a/app/src/main/java/com/sameerasw/essentials/viewmodels/MainViewModel.kt b/app/src/main/java/com/sameerasw/essentials/viewmodels/MainViewModel.kt index 850974116..b7374cd02 100644 --- a/app/src/main/java/com/sameerasw/essentials/viewmodels/MainViewModel.kt +++ b/app/src/main/java/com/sameerasw/essentials/viewmodels/MainViewModel.kt @@ -109,6 +109,8 @@ class MainViewModel : ViewModel() { val isCalendarSyncPeriodicEnabled = mutableStateOf(false) val isBatteryNotificationEnabled = mutableStateOf(false) val isAodEnabled = mutableStateOf(false) + val isNotificationGlanceEnabled = mutableStateOf(false) + val isNotificationGlanceSameAsLightingEnabled = mutableStateOf(true) data class CalendarAccount( @@ -399,6 +401,8 @@ class MainViewModel : ViewModel() { SettingsRepository.KEY_TRANSITION_ANIMATION_SCALE -> transitionAnimationScale.floatValue = settingsRepository.getAnimationScale(android.provider.Settings.Global.TRANSITION_ANIMATION_SCALE) SettingsRepository.KEY_WINDOW_ANIMATION_SCALE -> windowAnimationScale.floatValue = settingsRepository.getAnimationScale(android.provider.Settings.Global.WINDOW_ANIMATION_SCALE) SettingsRepository.KEY_SMALLEST_WIDTH -> smallestWidth.intValue = settingsRepository.getSmallestWidth() + SettingsRepository.KEY_NOTIFICATION_GLANCE_ENABLED -> isNotificationGlanceEnabled.value = settingsRepository.getBoolean(key) + SettingsRepository.KEY_NOTIFICATION_GLANCE_SAME_AS_LIGHTING -> isNotificationGlanceSameAsLightingEnabled.value = settingsRepository.getBoolean(key, true) } } } @@ -730,6 +734,8 @@ class MainViewModel : ViewModel() { isCalendarSyncPeriodicEnabled.value = settingsRepository.isCalendarSyncPeriodicEnabled() isBatteryNotificationEnabled.value = settingsRepository.isBatteryNotificationEnabled() selectedCalendarIds.value = settingsRepository.getCalendarSyncSelectedCalendars() + isNotificationGlanceEnabled.value = settingsRepository.getBoolean(SettingsRepository.KEY_NOTIFICATION_GLANCE_ENABLED) + isNotificationGlanceSameAsLightingEnabled.value = settingsRepository.getBoolean(SettingsRepository.KEY_NOTIFICATION_GLANCE_SAME_AS_LIGHTING, true) refreshTrackedUpdates(context) if (isBatteryNotificationEnabled.value) { @@ -2153,8 +2159,30 @@ class MainViewModel : ViewModel() { } fun setAodEnabled(enabled: Boolean) { - settingsRepository.setAodEnabled(enabled) isAodEnabled.value = enabled + settingsRepository.setAodEnabled(enabled) + } + + fun setNotificationGlanceEnabled(enabled: Boolean) { + isNotificationGlanceEnabled.value = enabled + settingsRepository.putBoolean(SettingsRepository.KEY_NOTIFICATION_GLANCE_ENABLED, enabled) + } + + fun setNotificationGlanceSameAsLightingEnabled(enabled: Boolean) { + isNotificationGlanceSameAsLightingEnabled.value = enabled + settingsRepository.putBoolean(SettingsRepository.KEY_NOTIFICATION_GLANCE_SAME_AS_LIGHTING, enabled) + } + + fun loadNotificationGlanceSelectedApps(context: Context): List { + return settingsRepository.loadNotificationGlanceSelectedApps() + } + + fun saveNotificationGlanceSelectedApps(context: Context, apps: List) { + settingsRepository.saveNotificationGlanceSelectedApps(apps) + } + + fun updateNotificationGlanceAppEnabled(context: Context, packageName: String, enabled: Boolean) { + settingsRepository.updateNotificationGlanceAppSelection(packageName, enabled) } override fun onCleared() { diff --git a/app/src/main/res/drawable/outline_mobile_chat_24.xml b/app/src/main/res/drawable/outline_mobile_chat_24.xml new file mode 100644 index 000000000..ba33a32e7 --- /dev/null +++ b/app/src/main/res/drawable/outline_mobile_chat_24.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 159a0cdc2..8026cd51f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -62,6 +62,10 @@ Glance at media on AOD Docked mode Keep the overlay visible indefinitely while music is playing on AOD + Notification glance + Keep AOD on while notifications are pending + Same apps as notification lighting + This feature will dynamically enable Always on Display when a notification arrives from a selected app, and disable it once all matching notifications are dismissed. Pick apps or use the same selection as notification lighting. Grant notification access Toggle media volume When the screen is off, long-press the selected button to trigger its assigned action. On Pixel devices, this action only gets triggered if the AOD is on due to system limitations. @@ -626,6 +630,7 @@ Hide sensitive content on the lock screen.\n\nToggle whether notification content is shown or hidden when your device is locked. Toggle tap to wake functionality.\n\nEnable or disable the ability to wake your screen with a tap. Toggle Always On Display.\n\nQuickly enable or disable the always-on display to view info at a glance. + Automatically control your Always On Display based on your notifications. When a message or alert arrives from a selected app, AOD will stay on until you dismiss the notification, ensuring you never miss important info without wasting battery when no alerts are present. Combine audio channels into mono.\n\nUseful when using a single earbud or for accessibility purposes. Toggle the flashlight.\n\nA Long pressing opens the controls for intensity adjustment which might need hardware implementation which some devices may lack. Keep the screen awake while charging.\n\nPrevents the screen from sleeping as long as the device is connected to a power source which is suitable for developers during debugging. From 4f93409529cd5e898042e76b0ae65dfa788984e5 Mon Sep 17 00:00:00 2001 From: sameerasw Date: Sat, 28 Feb 2026 18:50:29 +0530 Subject: [PATCH 43/45] feat: Force turn off display when no notifications in dynamic AOD --- .../data/repository/SettingsRepository.kt | 1 + .../services/NotificationListener.kt | 13 ++ .../handlers/AodForceTurnOffHandler.kt | 116 ++++++++++++++++++ .../tiles/ScreenOffAccessibilityService.kt | 11 ++ .../configs/AlwaysOnDisplaySettingsUI.kt | 27 +++- .../essentials/viewmodels/MainViewModel.kt | 11 +- app/src/main/res/values/strings.xml | 2 + 7 files changed, 178 insertions(+), 3 deletions(-) create mode 100644 app/src/main/java/com/sameerasw/essentials/services/handlers/AodForceTurnOffHandler.kt diff --git a/app/src/main/java/com/sameerasw/essentials/data/repository/SettingsRepository.kt b/app/src/main/java/com/sameerasw/essentials/data/repository/SettingsRepository.kt index 21b4ba2db..76a211310 100644 --- a/app/src/main/java/com/sameerasw/essentials/data/repository/SettingsRepository.kt +++ b/app/src/main/java/com/sameerasw/essentials/data/repository/SettingsRepository.kt @@ -161,6 +161,7 @@ class SettingsRepository(private val context: Context) { const val KEY_NOTIFICATION_GLANCE_ENABLED = "notification_glance_enabled" const val KEY_NOTIFICATION_GLANCE_SAME_AS_LIGHTING = "notification_glance_same_as_lighting" const val KEY_NOTIFICATION_GLANCE_SELECTED_APPS = "notification_glance_selected_apps" + const val KEY_AOD_FORCE_TURN_OFF_ENABLED = "aod_force_turn_off_enabled" } // Observe changes diff --git a/app/src/main/java/com/sameerasw/essentials/services/NotificationListener.kt b/app/src/main/java/com/sameerasw/essentials/services/NotificationListener.kt index 52a451000..99acdc58b 100644 --- a/app/src/main/java/com/sameerasw/essentials/services/NotificationListener.kt +++ b/app/src/main/java/com/sameerasw/essentials/services/NotificationListener.kt @@ -4,6 +4,7 @@ import android.app.Notification import android.content.Context import android.content.Intent import android.os.Build +import android.os.PowerManager import android.provider.Settings import android.service.notification.NotificationListenerService import android.service.notification.StatusBarNotification @@ -846,6 +847,18 @@ class NotificationListener : NotificationListenerService() { val newValue = if (enable) 1 else 0 if (currentValue != newValue) { Settings.Secure.putInt(contentResolver, "doze_always_on", newValue) + + // If turning OFF and force turn off workaround is enabled, trigger it + if (!enable) { + val powerManager = getSystemService(Context.POWER_SERVICE) as PowerManager + if (!powerManager.isInteractive) { + val prefs = getSharedPreferences("essentials_prefs", MODE_PRIVATE) + val forceTurnOffEnabled = prefs.getBoolean(SettingsRepository.KEY_AOD_FORCE_TURN_OFF_ENABLED, false) + if (forceTurnOffEnabled) { + sendBroadcast(Intent("FORCE_TURN_OFF_AOD").setPackage(packageName)) + } + } + } } } catch (e: Exception) { Log.e("NotificationListener", "Failed to update AOD state", e) diff --git a/app/src/main/java/com/sameerasw/essentials/services/handlers/AodForceTurnOffHandler.kt b/app/src/main/java/com/sameerasw/essentials/services/handlers/AodForceTurnOffHandler.kt new file mode 100644 index 000000000..f14885049 --- /dev/null +++ b/app/src/main/java/com/sameerasw/essentials/services/handlers/AodForceTurnOffHandler.kt @@ -0,0 +1,116 @@ +package com.sameerasw.essentials.services.handlers + +import android.accessibilityservice.AccessibilityService +import android.content.Context +import android.graphics.Color +import android.graphics.PixelFormat +import android.os.Build +import android.os.Handler +import android.os.Looper +import android.os.PowerManager +import android.util.Log +import android.view.Gravity +import android.view.View +import android.view.WindowManager +import android.widget.FrameLayout + +class AodForceTurnOffHandler(private val service: AccessibilityService) { + + private var windowManager: WindowManager? = null + private var overlayView: View? = null + private val handler = Handler(Looper.getMainLooper()) + + private var isRunning = false + + fun forceTurnOff() { + val powerManager = service.getSystemService(Context.POWER_SERVICE) as PowerManager + // Only run if screen is not interactive (currently in AOD or off) + if (powerManager.isInteractive || isRunning) { + Log.d("AodForceTurnOff", "Skipping forceTurnOff: isInteractive=${powerManager.isInteractive}, isRunning=$isRunning") + return + } + + Log.d("AodForceTurnOff", "Starting forceTurnOff sequence") + isRunning = true + showOverlay() + + // Sequence: Overlay -> Wake -> Lock -> Remove Overlay + // Using slightly longer delays to ensure system registers actions + handler.postDelayed({ + wakeScreen() + + handler.postDelayed({ + lockScreen() + + // Allow time for the lock action to process and screen to turn off + handler.postDelayed({ + removeOverlay() + isRunning = false + Log.d("AodForceTurnOff", "ForceTurnOff sequence completed") + }, 600) + }, 100) + }, 50) + } + + private fun showOverlay() { + if (overlayView != null) return + + windowManager = service.getSystemService(Context.WINDOW_SERVICE) as WindowManager + overlayView = FrameLayout(service).apply { + setBackgroundColor(Color.BLACK) + } + + val type = WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY + + val params = WindowManager.LayoutParams( + WindowManager.LayoutParams.MATCH_PARENT, + WindowManager.LayoutParams.MATCH_PARENT, + type, + WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or + WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE or + WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN or + WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS or + WindowManager.LayoutParams.FLAG_FULLSCREEN or + WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON, + PixelFormat.OPAQUE + ).apply { + gravity = Gravity.CENTER + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES + } + } + + try { + windowManager?.addView(overlayView, params) + } catch (e: Exception) { + overlayView = null + isRunning = false + } + } + + private fun wakeScreen() { + val powerManager = service.getSystemService(Context.POWER_SERVICE) as PowerManager + @Suppress("DEPRECATION") + val wakeLock = powerManager.newWakeLock( + PowerManager.SCREEN_BRIGHT_WAKE_LOCK or PowerManager.ACQUIRE_CAUSES_WAKEUP, + "Essentials:ForceTurnOffWake" + ) + wakeLock.acquire(100) + } + + private fun lockScreen() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + service.performGlobalAction(AccessibilityService.GLOBAL_ACTION_LOCK_SCREEN) + } + } + + fun removeOverlay() { + if (overlayView != null && windowManager != null) { + try { + windowManager?.removeView(overlayView) + } catch (_: Exception) {} + overlayView = null + } + isRunning = false + } +} diff --git a/app/src/main/java/com/sameerasw/essentials/services/tiles/ScreenOffAccessibilityService.kt b/app/src/main/java/com/sameerasw/essentials/services/tiles/ScreenOffAccessibilityService.kt index 743862dec..4ff1d6a0a 100644 --- a/app/src/main/java/com/sameerasw/essentials/services/tiles/ScreenOffAccessibilityService.kt +++ b/app/src/main/java/com/sameerasw/essentials/services/tiles/ScreenOffAccessibilityService.kt @@ -19,6 +19,7 @@ import com.sameerasw.essentials.data.repository.SettingsRepository import com.sameerasw.essentials.domain.HapticFeedbackType import com.sameerasw.essentials.services.InputEventListenerService import com.sameerasw.essentials.services.handlers.AmbientGlanceHandler +import com.sameerasw.essentials.services.handlers.AodForceTurnOffHandler import com.sameerasw.essentials.services.handlers.AppFlowHandler import com.sameerasw.essentials.services.handlers.ButtonRemapHandler import com.sameerasw.essentials.services.handlers.FlashlightHandler @@ -43,6 +44,7 @@ class ScreenOffAccessibilityService : AccessibilityService(), SensorEventListene private lateinit var appFlowHandler: AppFlowHandler private lateinit var securityHandler: SecurityHandler private lateinit var ambientGlanceHandler: AmbientGlanceHandler + private lateinit var aodForceTurnOffHandler: AodForceTurnOffHandler private var screenReceiver: BroadcastReceiver? = null @@ -66,6 +68,7 @@ class ScreenOffAccessibilityService : AccessibilityService(), SensorEventListene appFlowHandler = AppFlowHandler(this) securityHandler = SecurityHandler(this) ambientGlanceHandler = AmbientGlanceHandler(this) + aodForceTurnOffHandler = AodForceTurnOffHandler(this) flashlightHandler.register() @@ -76,6 +79,7 @@ class ScreenOffAccessibilityService : AccessibilityService(), SensorEventListene Intent.ACTION_SCREEN_ON -> { notificationLightingHandler.onScreenOn() ambientGlanceHandler.dismissImmediately() + aodForceTurnOffHandler.removeOverlay() freezeHandler.removeCallbacks(freezeRunnable) stopInputEventListener() } @@ -98,6 +102,10 @@ class ScreenOffAccessibilityService : AccessibilityService(), SensorEventListene "SHOW_AMBIENT_GLANCE" -> { ambientGlanceHandler.handleIntent(intent) } + + "FORCE_TURN_OFF_AOD" -> { + aodForceTurnOffHandler.forceTurnOff() + } } } } @@ -107,6 +115,7 @@ class ScreenOffAccessibilityService : AccessibilityService(), SensorEventListene addAction(Intent.ACTION_USER_PRESENT) addAction(InputEventListenerService.ACTION_VOLUME_LONG_PRESSED) addAction("SHOW_AMBIENT_GLANCE") + addAction("FORCE_TURN_OFF_AOD") } registerReceiver(screenReceiver, filter, RECEIVER_EXPORTED) @@ -155,6 +164,7 @@ class ScreenOffAccessibilityService : AccessibilityService(), SensorEventListene securityHandler.restoreAnimationScale() notificationLightingHandler.removeOverlay() ambientGlanceHandler.removeOverlay() + aodForceTurnOffHandler.removeOverlay() stopInputEventListener() serviceScope.cancel() super.onDestroy() @@ -246,6 +256,7 @@ class ScreenOffAccessibilityService : AccessibilityService(), SensorEventListene "SHOW_NOTIFICATION_LIGHTING" -> notificationLightingHandler.handleIntent(intent) "SHOW_AMBIENT_GLANCE" -> ambientGlanceHandler.handleIntent(intent) + "FORCE_TURN_OFF_AOD" -> aodForceTurnOffHandler.forceTurnOff() "APP_AUTHENTICATED" -> intent.getStringExtra("package_name") ?.let { appFlowHandler.onAuthenticated(it) } diff --git a/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/AlwaysOnDisplaySettingsUI.kt b/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/AlwaysOnDisplaySettingsUI.kt index 6ac4bdb81..c44f0dfe1 100644 --- a/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/AlwaysOnDisplaySettingsUI.kt +++ b/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/AlwaysOnDisplaySettingsUI.kt @@ -77,7 +77,7 @@ fun AlwaysOnDisplaySettingsUI( isChecked = viewModel.isNotificationGlanceEnabled.value, onCheckedChange = { checked -> HapticUtil.performVirtualKeyHaptic(view) - viewModel.setNotificationGlanceEnabled(checked) + viewModel.toggleNotificationGlanceEnabled(checked) }, modifier = Modifier.highlight(highlightSetting == "notification_glance_enabled") ) @@ -92,6 +92,24 @@ fun AlwaysOnDisplaySettingsUI( }, modifier = Modifier.highlight(highlightSetting == "notification_glance_same_apps") ) + + val isAccessibilityEnabled = viewModel.isAccessibilityEnabled.value + IconToggleItem( + iconRes = R.drawable.rounded_power_settings_new_24, + title = stringResource(R.string.feat_aod_force_turn_off_title), + isChecked = viewModel.isAodForceTurnOffEnabled.value, + onCheckedChange = { checked -> + HapticUtil.performVirtualKeyHaptic(view) + // Check latest snapshot inside lambda + val currentlyEnabled = com.sameerasw.essentials.utils.PermissionUtils.isAccessibilityServiceEnabled(context) + if (checked && !currentlyEnabled) { + com.sameerasw.essentials.utils.PermissionUtils.openAccessibilitySettings(context) + } else { + viewModel.toggleAodForceTurnOffEnabled(checked) + } + }, + modifier = Modifier.highlight(highlightSetting == "aod_force_turn_off") + ) } Text( @@ -101,6 +119,13 @@ fun AlwaysOnDisplaySettingsUI( color = MaterialTheme.colorScheme.onSurfaceVariant ) + Text( + text = stringResource(R.string.feat_aod_force_turn_off_desc), + modifier = Modifier.padding(16.dp), + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + if (!viewModel.isNotificationGlanceSameAsLightingEnabled.value) { Button( onClick = { diff --git a/app/src/main/java/com/sameerasw/essentials/viewmodels/MainViewModel.kt b/app/src/main/java/com/sameerasw/essentials/viewmodels/MainViewModel.kt index b7374cd02..ac01faf9e 100644 --- a/app/src/main/java/com/sameerasw/essentials/viewmodels/MainViewModel.kt +++ b/app/src/main/java/com/sameerasw/essentials/viewmodels/MainViewModel.kt @@ -110,6 +110,7 @@ class MainViewModel : ViewModel() { val isBatteryNotificationEnabled = mutableStateOf(false) val isAodEnabled = mutableStateOf(false) val isNotificationGlanceEnabled = mutableStateOf(false) + val isAodForceTurnOffEnabled = mutableStateOf(false) val isNotificationGlanceSameAsLightingEnabled = mutableStateOf(true) @@ -402,6 +403,7 @@ class MainViewModel : ViewModel() { SettingsRepository.KEY_WINDOW_ANIMATION_SCALE -> windowAnimationScale.floatValue = settingsRepository.getAnimationScale(android.provider.Settings.Global.WINDOW_ANIMATION_SCALE) SettingsRepository.KEY_SMALLEST_WIDTH -> smallestWidth.intValue = settingsRepository.getSmallestWidth() SettingsRepository.KEY_NOTIFICATION_GLANCE_ENABLED -> isNotificationGlanceEnabled.value = settingsRepository.getBoolean(key) + SettingsRepository.KEY_AOD_FORCE_TURN_OFF_ENABLED -> isAodForceTurnOffEnabled.value = settingsRepository.getBoolean(key) SettingsRepository.KEY_NOTIFICATION_GLANCE_SAME_AS_LIGHTING -> isNotificationGlanceSameAsLightingEnabled.value = settingsRepository.getBoolean(key, true) } } @@ -735,6 +737,7 @@ class MainViewModel : ViewModel() { isBatteryNotificationEnabled.value = settingsRepository.isBatteryNotificationEnabled() selectedCalendarIds.value = settingsRepository.getCalendarSyncSelectedCalendars() isNotificationGlanceEnabled.value = settingsRepository.getBoolean(SettingsRepository.KEY_NOTIFICATION_GLANCE_ENABLED) + isAodForceTurnOffEnabled.value = settingsRepository.getBoolean(SettingsRepository.KEY_AOD_FORCE_TURN_OFF_ENABLED) isNotificationGlanceSameAsLightingEnabled.value = settingsRepository.getBoolean(SettingsRepository.KEY_NOTIFICATION_GLANCE_SAME_AS_LIGHTING, true) refreshTrackedUpdates(context) @@ -2163,11 +2166,15 @@ class MainViewModel : ViewModel() { settingsRepository.setAodEnabled(enabled) } - fun setNotificationGlanceEnabled(enabled: Boolean) { - isNotificationGlanceEnabled.value = enabled + fun toggleNotificationGlanceEnabled(enabled: Boolean) { settingsRepository.putBoolean(SettingsRepository.KEY_NOTIFICATION_GLANCE_ENABLED, enabled) + isNotificationGlanceEnabled.value = enabled } + fun toggleAodForceTurnOffEnabled(enabled: Boolean) { + settingsRepository.putBoolean(SettingsRepository.KEY_AOD_FORCE_TURN_OFF_ENABLED, enabled) + isAodForceTurnOffEnabled.value = enabled + } fun setNotificationGlanceSameAsLightingEnabled(enabled: Boolean) { isNotificationGlanceSameAsLightingEnabled.value = enabled settingsRepository.putBoolean(SettingsRepository.KEY_NOTIFICATION_GLANCE_SAME_AS_LIGHTING, enabled) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 8026cd51f..f025fa3ba 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1162,4 +1162,6 @@ Transition animation scale Window animation scale Adjust system-wide font scale, weight, and animation speeds. Note that some settings may require advanced permissions or a device reboot for certain apps to reflect changes. \n\nAdditional shizuku or root permission may be necessary for scale adjustments + Force turn off AOD + Force turn off the AOD when no notifications. Requires accessibility permission. \ No newline at end of file From 3cc67d18b8d75df8cb96f1345f22acc7952fc876 Mon Sep 17 00:00:00 2001 From: sameerasw Date: Sat, 28 Feb 2026 19:32:44 +0530 Subject: [PATCH 44/45] feat: Auto grant accessibility --- .../sameerasw/essentials/SettingsActivity.kt | 7 +++ .../data/repository/SettingsRepository.kt | 1 + .../essentials/viewmodels/MainViewModel.kt | 48 ++++++++++++++++++- app/src/main/res/values/strings.xml | 2 + 4 files changed, 56 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/sameerasw/essentials/SettingsActivity.kt b/app/src/main/java/com/sameerasw/essentials/SettingsActivity.kt index 6100d7dfb..c44307609 100644 --- a/app/src/main/java/com/sameerasw/essentials/SettingsActivity.kt +++ b/app/src/main/java/com/sameerasw/essentials/SettingsActivity.kt @@ -717,6 +717,13 @@ fun SettingsContent(viewModel: MainViewModel, modifier: Modifier = Modifier) { } } + IconToggleItem( + iconRes = R.drawable.rounded_settings_accessibility_24, + title = stringResource(R.string.feat_auto_accessibility_title), + description = stringResource(R.string.feat_auto_accessibility_desc), + isChecked = viewModel.isAutoAccessibilityEnabled.value, + onCheckedChange = { viewModel.setAutoAccessibilityEnabled(it, context) } + ) } } diff --git a/app/src/main/java/com/sameerasw/essentials/data/repository/SettingsRepository.kt b/app/src/main/java/com/sameerasw/essentials/data/repository/SettingsRepository.kt index 76a211310..2f1a52011 100644 --- a/app/src/main/java/com/sameerasw/essentials/data/repository/SettingsRepository.kt +++ b/app/src/main/java/com/sameerasw/essentials/data/repository/SettingsRepository.kt @@ -162,6 +162,7 @@ class SettingsRepository(private val context: Context) { const val KEY_NOTIFICATION_GLANCE_SAME_AS_LIGHTING = "notification_glance_same_as_lighting" const val KEY_NOTIFICATION_GLANCE_SELECTED_APPS = "notification_glance_selected_apps" const val KEY_AOD_FORCE_TURN_OFF_ENABLED = "aod_force_turn_off_enabled" + const val KEY_AUTO_ACCESSIBILITY_ENABLED = "auto_accessibility_enabled" } // Observe changes diff --git a/app/src/main/java/com/sameerasw/essentials/viewmodels/MainViewModel.kt b/app/src/main/java/com/sameerasw/essentials/viewmodels/MainViewModel.kt index ac01faf9e..3b43435d4 100644 --- a/app/src/main/java/com/sameerasw/essentials/viewmodels/MainViewModel.kt +++ b/app/src/main/java/com/sameerasw/essentials/viewmodels/MainViewModel.kt @@ -111,6 +111,7 @@ class MainViewModel : ViewModel() { val isAodEnabled = mutableStateOf(false) val isNotificationGlanceEnabled = mutableStateOf(false) val isAodForceTurnOffEnabled = mutableStateOf(false) + val isAutoAccessibilityEnabled = mutableStateOf(false) val isNotificationGlanceSameAsLightingEnabled = mutableStateOf(true) @@ -405,6 +406,7 @@ class MainViewModel : ViewModel() { SettingsRepository.KEY_NOTIFICATION_GLANCE_ENABLED -> isNotificationGlanceEnabled.value = settingsRepository.getBoolean(key) SettingsRepository.KEY_AOD_FORCE_TURN_OFF_ENABLED -> isAodForceTurnOffEnabled.value = settingsRepository.getBoolean(key) SettingsRepository.KEY_NOTIFICATION_GLANCE_SAME_AS_LIGHTING -> isNotificationGlanceSameAsLightingEnabled.value = settingsRepository.getBoolean(key, true) + SettingsRepository.KEY_AUTO_ACCESSIBILITY_ENABLED -> isAutoAccessibilityEnabled.value = settingsRepository.getBoolean(key) } } } @@ -416,13 +418,50 @@ class MainViewModel : ViewModel() { isAccessibilityEnabled.value = PermissionUtils.isAccessibilityServiceEnabled(context) isWriteSecureSettingsEnabled.value = PermissionUtils.canWriteSecureSettings(context) + isShizukuAvailable.value = ShizukuUtils.isShizukuAvailable() + isShizukuPermissionGranted.value = ShizukuUtils.hasPermission() + isAutoAccessibilityEnabled.value = settingsRepository.getBoolean(SettingsRepository.KEY_AUTO_ACCESSIBILITY_ENABLED) + + if (isAutoAccessibilityEnabled.value && !isAccessibilityEnabled.value) { + val serviceName = "${context.packageName}/${ScreenOffAccessibilityService::class.java.name}" + var success = false + + if (isWriteSecureSettingsEnabled.value) { + try { + val enabledServices = Settings.Secure.getString( + context.contentResolver, + Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES + ) ?: "" + val newServices = if (enabledServices.isEmpty()) serviceName else if (!enabledServices.contains(serviceName)) "$enabledServices:$serviceName" else enabledServices + Settings.Secure.putString( + context.contentResolver, + Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, + newServices + ) + Settings.Secure.putString( + context.contentResolver, + Settings.Secure.ACCESSIBILITY_ENABLED, + "1" + ) + success = true + } catch (e: Exception) { + success = false + } + } + + if (success) { + isAccessibilityEnabled.value = PermissionUtils.isAccessibilityServiceEnabled(context) + if (isAccessibilityEnabled.value) { + android.widget.Toast.makeText(context, "Accessibility auto-granted", android.widget.Toast.LENGTH_SHORT).show() + } + } + } + isReadPhoneStateEnabled.value = PermissionUtils.hasReadPhoneStatePermission(context) isPostNotificationsEnabled.value = ContextCompat.checkSelfPermission( context, Manifest.permission.POST_NOTIFICATIONS ) == PackageManager.PERMISSION_GRANTED - isShizukuAvailable.value = ShizukuUtils.isShizukuAvailable() - isShizukuPermissionGranted.value = ShizukuUtils.hasPermission() isNotificationListenerEnabled.value = PermissionUtils.hasNotificationListenerPermission(context) isOverlayPermissionGranted.value = PermissionUtils.canDrawOverlays(context) @@ -2156,6 +2195,11 @@ class MainViewModel : ViewModel() { } } + fun setAutoAccessibilityEnabled(isEnabled: Boolean, context: Context) { + settingsRepository.putBoolean(SettingsRepository.KEY_AUTO_ACCESSIBILITY_ENABLED, isEnabled) + isAutoAccessibilityEnabled.value = isEnabled + } + fun generateBugReport(context: Context): String { val settingsJson = settingsRepository.getAllConfigsAsJsonString() return com.sameerasw.essentials.utils.LogManager.generateReport(context, settingsJson) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f025fa3ba..652f546e8 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1164,4 +1164,6 @@ Adjust system-wide font scale, weight, and animation speeds. Note that some settings may require advanced permissions or a device reboot for certain apps to reflect changes. \n\nAdditional shizuku or root permission may be necessary for scale adjustments Force turn off AOD Force turn off the AOD when no notifications. Requires accessibility permission. + Auto accessibility + Automatically grants the accessibility permission on app launch if missing using WRITE_SECURE_SETTINGS. \ No newline at end of file From 36ed60eb1cf6e831109717c9eba673d1ac1a6716 Mon Sep 17 00:00:00 2001 From: sameerasw Date: Sat, 28 Feb 2026 20:33:06 +0530 Subject: [PATCH 45/45] feat: App lock light theme #231 --- .../sameerasw/essentials/AppLockActivity.kt | 181 +++++++++--------- 1 file changed, 88 insertions(+), 93 deletions(-) diff --git a/app/src/main/java/com/sameerasw/essentials/AppLockActivity.kt b/app/src/main/java/com/sameerasw/essentials/AppLockActivity.kt index 43728ca14..3a8348fb5 100644 --- a/app/src/main/java/com/sameerasw/essentials/AppLockActivity.kt +++ b/app/src/main/java/com/sameerasw/essentials/AppLockActivity.kt @@ -1,20 +1,34 @@ package com.sameerasw.essentials -import android.app.Activity import android.content.Intent +import android.content.pm.PackageManager import android.os.Build import android.os.Bundle import android.util.Log -import android.widget.FrameLayout -import android.widget.ImageView -import android.widget.LinearLayout -import android.widget.TextView -import androidx.activity.SystemBarStyle +import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge import androidx.biometric.BiometricPrompt +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import coil.compose.AsyncImage import androidx.core.content.ContextCompat import androidx.fragment.app.FragmentActivity import com.sameerasw.essentials.services.tiles.ScreenOffAccessibilityService +import com.sameerasw.essentials.ui.theme.EssentialsTheme import java.util.concurrent.Executor class AppLockActivity : FragmentActivity() { @@ -26,90 +40,7 @@ class AppLockActivity : FragmentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - - // Force Dark Theme - enableEdgeToEdge( - statusBarStyle = SystemBarStyle.dark(android.graphics.Color.TRANSPARENT), - navigationBarStyle = SystemBarStyle.dark(android.graphics.Color.TRANSPARENT) - ) - - window.setBackgroundDrawableResource(android.R.color.black) - - // Get accent color (respect Monet on Android 12+) - val primaryColor = - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - ContextCompat.getColor(this, android.R.color.system_accent1_300) - } else { - val typedValue = android.util.TypedValue() - theme.resolveAttribute(android.R.attr.colorPrimary, typedValue, true) - typedValue.data - } - - val root = LinearLayout(this).apply { - orientation = LinearLayout.VERTICAL - setBackgroundColor(android.graphics.Color.BLACK) - gravity = android.view.Gravity.CENTER_HORIZONTAL - setPadding(0, (140 * resources.displayMetrics.density).toInt(), 0, 0) - layoutParams = LinearLayout.LayoutParams( - LinearLayout.LayoutParams.MATCH_PARENT, - LinearLayout.LayoutParams.MATCH_PARENT - ) - } - - // Composite icon layout - val iconContainer = FrameLayout(this).apply { - layoutParams = LinearLayout.LayoutParams( - (96 * resources.displayMetrics.density).toInt(), - (96 * resources.displayMetrics.density).toInt() - ) - } - - val baseIconSize = (80 * resources.displayMetrics.density).toInt() - val appRegistrationIcon = ImageView(this).apply { - setImageResource(R.drawable.rounded_shield_lock_24) - setColorFilter(primaryColor, android.graphics.PorterDuff.Mode.SRC_IN) - layoutParams = FrameLayout.LayoutParams(baseIconSize, baseIconSize).apply { - gravity = android.view.Gravity.CENTER - } - } - - val essentialsIconSize = (32 * resources.displayMetrics.density).toInt() - val essentialsIconView = ImageView(this).apply { - setImageResource(R.mipmap.ic_launcher_round) - layoutParams = FrameLayout.LayoutParams(essentialsIconSize, essentialsIconSize).apply { - gravity = android.view.Gravity.BOTTOM or android.view.Gravity.END - } - } - - iconContainer.addView(appRegistrationIcon) - iconContainer.addView(essentialsIconView) - - val titleView = TextView(this).apply { - text = "App is locked" - setTextColor(android.graphics.Color.WHITE) - textSize = 22f - setPadding(0, (24 * resources.displayMetrics.density).toInt(), 0, 0) - gravity = android.view.Gravity.CENTER - } - - val subtextView = TextView(this).apply { - text = "Please authenticate to unlock or \ngive the phone to the owner \n( -_-)" - setTextColor(android.graphics.Color.WHITE) - textSize = 14f - alpha = 0.6f - setPadding( - (48 * resources.displayMetrics.density).toInt(), - (8 * resources.displayMetrics.density).toInt(), - (48 * resources.displayMetrics.density).toInt(), - 0 - ) - gravity = android.view.Gravity.CENTER - } - - root.addView(iconContainer) - root.addView(titleView) - root.addView(subtextView) - setContentView(root) + enableEdgeToEdge() packageToLock = intent.getStringExtra("package_to_lock") if (packageToLock == null) { @@ -120,10 +51,16 @@ class AppLockActivity : FragmentActivity() { val appLabel = try { val appInfo = packageManager.getApplicationInfo(packageToLock!!, 0) packageManager.getApplicationLabel(appInfo).toString() - } catch (e: Exception) { + } catch (e: PackageManager.NameNotFoundException) { packageToLock } + setContent { + EssentialsTheme { + AppLockScreen() + } + } + executor = ContextCompat.getMainExecutor(this) biometricPrompt = BiometricPrompt( this, executor, @@ -156,13 +93,72 @@ class AppLockActivity : FragmentActivity() { biometricPrompt.authenticate(promptInfo) } + @Composable + private fun AppLockScreen() { + Box( + modifier = Modifier + .fillMaxSize() + .background(MaterialTheme.colorScheme.background) + ) { + Column( + modifier = Modifier + .fillMaxSize() + .padding(top = 140.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Box( + modifier = Modifier.size(96.dp) + ) { + Icon( + painter = painterResource(id = R.drawable.rounded_shield_lock_24), + contentDescription = "Lock Icon", + tint = MaterialTheme.colorScheme.primary, + modifier = Modifier + .size(80.dp) + .align(Alignment.Center) + ) + + AsyncImage( + model = R.mipmap.ic_launcher_round, + contentDescription = "Essentials App Icon", + modifier = Modifier + .size(32.dp) + .align(Alignment.BottomEnd) + .clip(CircleShape) + .background(MaterialTheme.colorScheme.background) + .padding(2.dp) + ) + } + + Spacer(modifier = Modifier.height(24.dp)) + + Text( + text = "App is locked", + style = MaterialTheme.typography.headlineSmall, + color = MaterialTheme.colorScheme.onBackground + ) + + Spacer(modifier = Modifier.height(8.dp)) + + Text( + text = "Please authenticate to unlock or\ngive the phone to the owner\n( -_-)", + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onBackground, + textAlign = TextAlign.Center, + modifier = Modifier + .padding(horizontal = 48.dp) + .alpha(0.6f) + ) + } + } + } + private fun notifySuccessAndFinish() { val intent = Intent("APP_AUTHENTICATED").apply { `package` = packageName putExtra("package_name", packageToLock) } sendBroadcast(intent) - // Also notify via service to be more reliable val serviceIntent = Intent(this, ScreenOffAccessibilityService::class.java).apply { action = "APP_AUTHENTICATED" putExtra("package_name", packageToLock) @@ -193,7 +189,6 @@ class AppLockActivity : FragmentActivity() { @Deprecated("Deprecated in Java") override fun onBackPressed() { - // Prevent going back, treated as cancel/failure notifyFailureAndFinish() @Suppress("DEPRECATION") super.onBackPressed()