Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
78 commits
Select commit Hold shift + click to select a range
d5782cc
Community translations by @converdario
converdario Aug 9, 2026
d74d73b
Community translations by @Fuchs23
Fuchs23 Aug 9, 2026
a0ec04e
feat: cts gesture zone width
xeaft Aug 9, 2026
89603f0
Community translations by @dimzhen
dimzhen Aug 9, 2026
4bb8c0d
Community translations by @MetaOxytocin
MetaOxytocin Aug 9, 2026
8135b7d
Community translations by @MetaOxytocin
MetaOxytocin Aug 9, 2026
864ba36
Community translations by @elchumy6960
elchumy6960 Aug 9, 2026
3d30f87
Community translations by @elchumy6960
elchumy6960 Aug 9, 2026
640a856
Community translations by @operaroblox9441
operaroblox9441 Aug 9, 2026
9b02a17
Community translations by @BenceBarens
BenceBarens Aug 9, 2026
9d800a7
Community translations by @BenceBarens
BenceBarens Aug 9, 2026
8abf3b4
Community translations by @manmen2414
manmen2414 Aug 9, 2026
4880268
Community translations by @Lucasbringmann16
Lucasbringmann16 Aug 10, 2026
6983672
Community translations by @Daniibrx
Daniibrx Aug 10, 2026
c867257
Community translations by @Kawaharu869
Kawaharu869 Aug 10, 2026
ff3c2b8
Community translations by @Kawaharu869
Kawaharu869 Aug 10, 2026
5c83093
Community translations by @paul1126
paul1126 Aug 10, 2026
36629a3
Community translations by @productbyPHT
productbyPHT Aug 10, 2026
51e5a6d
Community translations by @productbyPHT
productbyPHT Aug 10, 2026
6fe00f3
Community translations by @ru-tei2
ru-tei2 Aug 10, 2026
2fae82c
Community translations by @paul1126
paul1126 Aug 10, 2026
20df844
Community translations by @paul1126
paul1126 Aug 10, 2026
c67d79d
Community translations by @OrdinaryPerson0
OrdinaryPerson0 Aug 10, 2026
5e992f8
Community translations by @Ndraaa-repo
Ndraaa-repo Aug 10, 2026
10980a7
Community translations by @OrdinaryPerson0
OrdinaryPerson0 Aug 10, 2026
b9b8817
Community translations by @QuaternionDev
QuaternionDev Aug 10, 2026
65d5ac9
feat: Add 'Open App' and Hotspot control automation actions, add test…
thomasborgogno Aug 10, 2026
f1ff6eb
Merge branch 'main' into feature/actions-launch-app-and-hotspot
sameerasw Aug 11, 2026
82cdd16
Upstream (#752)
sameerasw Aug 11, 2026
c82bd81
Merge branch 'develop' into feature/actions-launch-app-and-hotspot
sameerasw Aug 11, 2026
de67204
Merge branch 'develop' into feature/gesture-zone-width
sameerasw Aug 11, 2026
dc778ed
feat: implement WearOS notification synchronization system with app f…
sameerasw Aug 9, 2026
443665d
feat: add functionality to sync app icons to watch launcher via new s…
sameerasw Aug 9, 2026
a82f8f7
feat: improve media notification handling, refine synchronization log…
sameerasw Aug 9, 2026
2f18708
feat: add notification reply support and bulk active notification syn…
sameerasw Aug 9, 2026
6ae8c6b
fix: respect silent notification sync preferences by robustly filteri…
sameerasw Aug 9, 2026
473f41a
feat: add watch notification sound selector and implement silent noti…
sameerasw Aug 9, 2026
3a17ae5
feat: add toggle to control media playback notification syncing to watch
sameerasw Aug 10, 2026
ebddc11
refactor: improve watch notification sync UI strings, haptics, and re…
sameerasw Aug 12, 2026
789050d
feat: implement WearOS phone call sync
sameerasw Aug 12, 2026
6a369e9
feat: update call sync feature with new icon, reordered UI elements, …
sameerasw Aug 12, 2026
4490b3a
feat: move Call Sync settings
sameerasw Aug 12, 2026
74f492d
Watch launcher (#756)
sameerasw Aug 12, 2026
9caec89
Community translations by @AhmedAwad7
AhmedAwad7 Aug 12, 2026
5581600
Community translations by @CapMarkoTo
CapMarkoTo Aug 13, 2026
137af78
feat: implement Smart Pixels quick settings tile
sameerasw Aug 13, 2026
f7aa928
feat: add animated overlay transitions and automated burn-in pattern …
sameerasw Aug 13, 2026
d6b8117
feat: add power saving triggers and state to automation
sameerasw Aug 13, 2026
81a63e7
feat: implement Essentials on Display Quick Settings tile
sameerasw Aug 13, 2026
94f240b
feat: update UsbDebuggingTileService to support simultaneous toggle a…
sameerasw Aug 13, 2026
89d77fe
feat: flashlight effects
sameerasw Aug 13, 2026
3c38c06
Merge branch 'develop' into feature/actions-launch-app-and-hotspot
sameerasw Aug 13, 2026
14af9eb
feat: Add 'Open App' and Hotspot control automation actions, test act…
sameerasw Aug 13, 2026
3e016e2
Merge branch 'develop' into feature/gesture-zone-width
sameerasw Aug 13, 2026
a8cb98c
Feature: add an option for circle to search gesture zone width (#731)
sameerasw Aug 13, 2026
b6a173b
chore: comment out the optimized debug build
sameerasw Aug 13, 2026
87e4653
Community translations by @converdario (#727)
sameerasw Aug 13, 2026
f2ee711
Community translations by @Fuchs23 (#730)
sameerasw Aug 13, 2026
5a9384a
Community translations by @dimzhen (#732)
sameerasw Aug 13, 2026
bfd4712
Community translations by @MetaOxytocin (#733)
sameerasw Aug 13, 2026
9be56df
Community translations by @elchumy6960 (#734)
sameerasw Aug 13, 2026
ad035a3
Community translations by @operaroblox9441 (#736)
sameerasw Aug 13, 2026
00ea63a
Community translations by @BenceBarens (#738)
sameerasw Aug 13, 2026
6ebac81
Community translations by @manmen2414 (#739)
sameerasw Aug 13, 2026
91b0439
Community translations by @Lucasbringmann16 (#740)
sameerasw Aug 13, 2026
5e5b396
Community translations by @Daniibrx (#741)
sameerasw Aug 13, 2026
bb31953
Merge branch 'develop' into translations-Kawaharu869
sameerasw Aug 13, 2026
bb504ad
Community translations by @Kawaharu869 (#742)
sameerasw Aug 13, 2026
7ea226b
Community translations by @paul1126 (#745)
sameerasw Aug 13, 2026
4a7404c
Community translations by @productbyPHT (#746)
sameerasw Aug 13, 2026
e44ac33
Merge branch 'develop' into translations-ru-tei2
sameerasw Aug 13, 2026
9974965
Community translations by @ru-tei2 (#747)
sameerasw Aug 13, 2026
6ab1fd3
Community translations by @OrdinaryPerson0 (#748)
sameerasw Aug 13, 2026
ba307c7
Community translations by @Ndraaa-repo (#749)
sameerasw Aug 13, 2026
c5beaff
Community translations by @QuaternionDev (#750)
sameerasw Aug 13, 2026
2f38233
Community translations by @AhmedAwad7 (#757)
sameerasw Aug 13, 2026
e3346d3
Community translations by @CapMarkoTo (#759)
sameerasw Aug 13, 2026
c2cd092
chore: bump version code to 60 and version name to 17.1
sameerasw Aug 13, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 11 additions & 11 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,8 @@ android {
applicationId = "com.sameerasw.essentials"
minSdk = 26
targetSdk = 37
versionCode = 59
versionName = "17.0"
versionCode = 60
versionName = "17.1"

val whatsNewCounter = 2
buildConfigField("int", "WHATS_NEW_COUNTER", whatsNewCounter.toString())
Expand All @@ -103,15 +103,15 @@ android {

// optimized dev build

// debug {
// isMinifyEnabled = true
// isShrinkResources = true
// isDebuggable = false
// proguardFiles(
// getDefaultProguardFile("proguard-android-optimize.txt"),
// "proguard-rules.pro"
// )
// }
// debug {
// isMinifyEnabled = true
// isShrinkResources = true
// isDebuggable = false
// proguardFiles(
// getDefaultProguardFile("proguard-android-optimize.txt"),
// "proguard-rules.pro"
// )
// }

// end

Expand Down
46 changes: 46 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.ANSWER_PHONE_CALLS" />
<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.READ_CALL_LOG" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT" />
Expand Down Expand Up @@ -367,6 +370,14 @@
android:taskAffinity=""
android:theme="@style/Theme.Essentials.Translucent" />

<activity
android:name=".ui.activities.SmartPixelsIntensityActivity"
android:excludeFromRecents="true"
android:exported="true"
android:noHistory="true"
android:taskAffinity=""
android:theme="@style/Theme.Essentials.Translucent" />

<activity
android:name=".ui.activities.PrivateDnsSettingsActivity"
android:excludeFromRecents="true"
Expand Down Expand Up @@ -705,6 +716,32 @@
android:name="android.service.quicksettings.TILE_CATEGORY"
android:value="android.service.quicksettings.CATEGORY_DISPLAY" />
</service>
<service
android:name=".services.tiles.SmartPixelsTileService"
android:exported="true"
android:icon="@drawable/rounded_grain_24"
android:label="@string/feat_smart_pixels_title"
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
<intent-filter>
<action android:name="android.service.quicksettings.action.QS_TILE" />
</intent-filter>
<meta-data
android:name="android.service.quicksettings.TILE_CATEGORY"
android:value="android.service.quicksettings.CATEGORY_DISPLAY" />
</service>
<service
android:name=".services.tiles.EssentialsOnDisplayTileService"
android:exported="true"
android:icon="@drawable/rounded_live_tv_24"
android:label="@string/tile_essentials_on_display"
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
<intent-filter>
<action android:name="android.service.quicksettings.action.QS_TILE" />
</intent-filter>
<meta-data
android:name="android.service.quicksettings.TILE_CATEGORY"
android:value="android.service.quicksettings.CATEGORY_DISPLAY" />
</service>
<service
android:name=".services.tiles.ScreenLockedSecurityTileService"
android:exported="true"
Expand Down Expand Up @@ -1209,6 +1246,15 @@
</intent-filter>
</receiver>

<receiver
android:name=".services.CallReceiver"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.PHONE_STATE" />
<action android:name="android.intent.action.NEW_OUTGOING_CALL" />
</intent-filter>
</receiver>

<service
android:name=".appfunctions.EssentialsAppFunctionService"
android:exported="true"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ class SettingsRepository(private val context: Context) {
const val KEY_HIDE_GESTURE_BAR_ON_LAUNCHER_ENABLED = "hide_gesture_bar_on_launcher_enabled"
const val KEY_CIRCLE_TO_SEARCH_GESTURE_ENABLED = "circle_to_search_gesture_enabled"
const val KEY_CIRCLE_TO_SEARCH_GESTURE_HEIGHT = "circle_to_search_gesture_height"
const val KEY_CIRCLE_TO_SEARCH_GESTURE_WIDTH = "circle_to_search_gesture_width"
const val KEY_CIRCLE_TO_SEARCH_PREVIEW_ENABLED = "circle_to_search_preview_enabled"
const val KEY_AUTO_UPDATE_ENABLED = "auto_update_enabled"
const val KEY_UPDATE_NOTIFICATION_ENABLED = "update_notification_enabled"
Expand Down
30 changes: 30 additions & 0 deletions app/src/main/java/com/sameerasw/essentials/domain/diy/Action.kt
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,36 @@ sealed interface Action {
}
}

@Keep
data class OpenApp(
@SerializedName("packageName") val packageName: String = ""
) : Action {
override val title: Int get() = R.string.diy_action_open_app
override val icon: Int get() = R.drawable.rounded_open_in_new_24
override val isConfigurable: Boolean = true
}

@Keep
data object TurnOnHotspot : Action {
override val title: Int = R.string.diy_action_hotspot_on
override val icon: Int = R.drawable.rounded_wifi_tethering_24
override val permissions: List<String> = listOf("shizuku", "root")
}

@Keep
data object TurnOffHotspot : Action {
override val title: Int = R.string.diy_action_hotspot_off
override val icon: Int = R.drawable.rounded_wifi_tethering_24
override val permissions: List<String> = listOf("shizuku", "root")
}

@Keep
data object ToggleHotspot : Action {
override val title: Int = R.string.diy_action_hotspot_toggle
override val icon: Int = R.drawable.rounded_wifi_tethering_24
override val permissions: List<String> = listOf("shizuku", "root")
}

@Keep
data class FreezeTag(
@SerializedName("mode") val mode: String = "Freeze", // "Freeze", "Unfreeze"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@ sealed interface State {
override val icon: Int = R.drawable.rounded_mobile_text_2_24
}

@Keep
data object PowerSaving : State {
override val title: Int = R.string.diy_state_power_saving
override val icon: Int = R.drawable.rounded_battery_android_frame_shield_24
}

@Keep
data class TimePeriod(
@SerializedName("startHour") val startHour: Int = 0,
Expand Down
12 changes: 12 additions & 0 deletions app/src/main/java/com/sameerasw/essentials/domain/diy/Trigger.kt
Original file line number Diff line number Diff line change
Expand Up @@ -116,4 +116,16 @@ sealed interface Trigger {
override val permissions: List<String>
get() = listOf(android.Manifest.permission.ACCESS_FINE_LOCATION)
}

@Keep
data object PowerSavingOn : Trigger {
override val title: Int = R.string.diy_trigger_power_saving_on
override val icon: Int = R.drawable.rounded_battery_android_frame_shield_24
}

@Keep
data object PowerSavingOff : Trigger {
override val title: Int = R.string.diy_trigger_power_saving_off
override val icon: Int = R.drawable.rounded_battery_android_frame_shield_24
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ data class AutomationSuggestion(
"DeviceUnlock",
"ChargerConnected",
"ChargerDisconnected",
"PowerSavingOn",
"PowerSavingOff",
"Schedule",
"BluetoothConnected",
"BluetoothDisconnected",
Expand All @@ -44,7 +46,7 @@ data class AutomationSuggestion(

@Guide(
description = "State type if automation is STATE type",
enumValues = ["Charging", "ScreenOn", "TimePeriod"]
enumValues = ["Charging", "ScreenOn", "PowerSaving", "TimePeriod"]
)
val stateType: String? = null,

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,8 @@ object GenAIAutomationService {
{
"type": "TRIGGER" or "STATE" or "APP",
"title": "Short descriptive title",
"triggerType": "ScreenOff" or "ScreenOn" or "DeviceUnlock" or "ChargerConnected" or "ChargerDisconnected" or "Schedule" or "BluetoothConnected" or "BluetoothDisconnected" or "WifiConnected" or "WifiDisconnected",
"stateType": "Charging" or "ScreenOn" or "TimePeriod",
"triggerType": "ScreenOff" or "ScreenOn" or "DeviceUnlock" or "ChargerConnected" or "ChargerDisconnected" or "PowerSavingOn" or "PowerSavingOff" or "Schedule" or "BluetoothConnected" or "BluetoothDisconnected" or "WifiConnected" or "WifiDisconnected",
"stateType": "Charging" or "ScreenOn" or "PowerSaving" or "TimePeriod",
"actionTypes": ["HapticVibration", "ShowNotification", "RemoveNotification", "TurnOnFlashlight", "TurnOffFlashlight", "ToggleFlashlight", "DimWallpaper", "DeviceEffects", "SoundMode", "TurnOnLowPower", "TurnOffLowPower", "ScreenOff", "MediaPlayPause", "MediaNext", "MediaPrevious", "AIAssistant", "TakeScreenshot", "ToggleMediaVolume", "LikeCurrentSong", "CircleToSearch", "PinApp", "SometimesEssentials", "FreezeTag"],
"explanation": "Brief 1-2 sentence description of what this automation does",
"hour": null or integer (0-23 for Schedule trigger or start time of TimePeriod state),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1410,6 +1410,44 @@ object FeatureRegistry {
override fun isEnabled(viewModel: MainViewModel) = true
override fun onToggle(viewModel: MainViewModel, context: Context, enabled: Boolean) {}
},
object : Feature(
id = "Notification Sync",
title = R.string.watch_notif_sync_title,
iconRes = R.drawable.rounded_notification_settings_24,
category = R.string.cat_tools,
description = R.string.watch_notif_sync_desc,
aboutDescription = R.string.watch_notif_sync_desc,
permissionKeys = listOf("NOTIFICATION_LISTENER"),
parentFeatureId = "Watch",
hasMoreSettings = true,
showToggle = true
) {
override fun isEnabled(viewModel: MainViewModel) = viewModel.isNotificationSyncEnabled.value
override fun isToggleEnabled(viewModel: MainViewModel, context: Context) = viewModel.isNotificationListenerEnabled.value
override fun onToggle(viewModel: MainViewModel, context: Context, enabled: Boolean) =
viewModel.setNotificationSyncEnabled(enabled, context)
},
object : Feature(
id = "Call Sync",
title = R.string.watch_call_sync_title,
iconRes = R.drawable.rounded_call_24,
category = R.string.cat_tools,
description = R.string.watch_call_sync_desc,
aboutDescription = R.string.watch_call_sync_desc,
permissionKeys = listOf("READ_PHONE_STATE", "ANSWER_PHONE_CALLS", "READ_CONTACTS", "READ_CALL_LOG"),
parentFeatureId = "Watch",
hasMoreSettings = false,
showToggle = true
) {
override fun isEnabled(viewModel: MainViewModel) =
viewModel.isCallSyncEnabled.value

override fun isToggleEnabled(viewModel: MainViewModel, context: Context) =
com.sameerasw.essentials.utils.PermissionUtils.hasCallPermissions(context)

override fun onToggle(viewModel: MainViewModel, context: Context, enabled: Boolean) =
viewModel.setCallSyncEnabled(enabled, context)
},
object : Feature(
id = "Watch Wireless Debugging",
title = R.string.feat_watch_wireless_debugging_title,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,5 +139,10 @@ fun initPermissionRegistry() {
PermissionRegistry.register("WRITE_SECURE_SETTINGS", R.string.feat_notification_snoozing_title)

// Install unknown packages feature
PermissionRegistry.register("REQUEST_INSTALL_PACKAGES", R.string.tab_app_updates_title)
// Watch Call Sync & Notification Sync
PermissionRegistry.register("READ_PHONE_STATE", R.string.watch_call_sync_title)
PermissionRegistry.register("ANSWER_PHONE_CALLS", R.string.watch_call_sync_title)
PermissionRegistry.register("READ_CONTACTS", R.string.watch_call_sync_title)
PermissionRegistry.register("READ_CALL_LOG", R.string.watch_call_sync_title)
PermissionRegistry.register("NOTIFICATION_LISTENER", R.string.watch_notif_sync_title)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* Copyright (c) 2026 sameerasw.com
* License: MIT License
*
* Feature Module: Background Services & Receivers
* File: CallReceiver.kt
* Description: BroadcastReceiver that listens for telephony state changes and passes call info to WatchCallSyncManager.
*/

package com.sameerasw.essentials.services

import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.telephony.TelephonyManager
import android.util.Log

class CallReceiver : BroadcastReceiver() {
companion object {
private const val TAG = "CallReceiver"
private var savedNumber: String? = null
}

@Suppress("DEPRECATION")
override fun onReceive(context: Context, intent: Intent) {
Log.d(TAG, "Broadcast received: ${intent.action}")

if (intent.action == Intent.ACTION_NEW_OUTGOING_CALL) {
savedNumber = intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER)
Log.d(TAG, "New outgoing call to: $savedNumber")
return
}

val stateStr = intent.getStringExtra(TelephonyManager.EXTRA_STATE)
val incomingNumber = intent.getStringExtra(TelephonyManager.EXTRA_INCOMING_NUMBER)

val state = when (stateStr) {
TelephonyManager.EXTRA_STATE_RINGING -> TelephonyManager.CALL_STATE_RINGING
TelephonyManager.EXTRA_STATE_OFFHOOK -> TelephonyManager.CALL_STATE_OFFHOOK
TelephonyManager.EXTRA_STATE_IDLE -> TelephonyManager.CALL_STATE_IDLE
else -> TelephonyManager.CALL_STATE_IDLE
}

val numberToUse = when {
state == TelephonyManager.CALL_STATE_RINGING -> incomingNumber ?: savedNumber
savedNumber != null -> savedNumber
else -> incomingNumber
}

if (state == TelephonyManager.CALL_STATE_IDLE) {
savedNumber = null
}

WatchCallSyncManager.onCallStateChanged(context, state, numberToUse)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,35 @@ class EssentialsWearableListenerService : WearableListenerService() {
}
}
}
"/dismiss_phone_notification" -> {
val key = String(messageEvent.data ?: byteArrayOf())
if (key.isNotBlank()) {
try {
val instance = NotificationListener.instance
if (instance != null) {
instance.cancelNotification(key)
} else if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
android.service.notification.NotificationListenerService.requestRebind(
android.content.ComponentName(this, NotificationListener::class.java)
)
}
} catch (e: Throwable) {
android.util.Log.e("EssentialsWearable", "Error cancelling notification: $key", e)
}
}
}
"/reply_phone_notification" -> {
val jsonStr = String(messageEvent.data ?: byteArrayOf())
if (jsonStr.isNotBlank()) {
WatchNotificationSyncManager.handleReplyFromWatch(this, jsonStr)
}
}
WatchCallSyncManager.PATH_WATCH_CALL_ACTION -> {
val action = String(messageEvent.data ?: byteArrayOf())
if (action.isNotBlank()) {
WatchCallSyncManager.handleCallAction(this, action)
}
}
}
}
}
Loading
Loading