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/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index d0b233fe8..859eff45a 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -406,16 +406,6 @@
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
= 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()
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/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 f39f0ecee..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
@@ -158,6 +158,11 @@ class SettingsRepository(private val context: Context) {
const val KEY_TRANSITION_ANIMATION_SCALE = "transition_animation_scale"
const val KEY_WINDOW_ANIMATION_SCALE = "window_animation_scale"
const val KEY_SMALLEST_WIDTH = "smallest_width"
+ 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"
+ const val KEY_AUTO_ACCESSIBILITY_ENABLED = "auto_accessibility_enabled"
}
// Observe changes
@@ -383,6 +388,14 @@ class SettingsRepository(private val context: Context) {
fun updateFlashlightPulseAppSelection(packageName: String, enabled: Boolean) =
updateAppSelection(KEY_FLASHLIGHT_PULSE_SELECTED_APPS, packageName, enabled)
+ fun loadNotificationGlanceSelectedApps() = loadAppSelection(KEY_NOTIFICATION_GLANCE_SELECTED_APPS)
+
+ fun saveNotificationGlanceSelectedApps(apps: List) =
+ 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 }
@@ -830,4 +843,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 e9a00e9b5..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",
@@ -565,6 +582,13 @@ object FeatureRegistry {
R.array.keywords_network_visibility,
R.string.feat_qs_tiles_title
),
+ SearchSetting(
+ R.string.tile_charge_optimization,
+ R.string.about_desc_charge_optimization,
+ "Charge optimization",
+ R.array.keywords_battery,
+ R.string.feat_qs_tiles_title
+ ),
SearchSetting(
R.string.search_qs_usb_debugging_title,
R.string.search_qs_usb_debugging_desc,
@@ -992,6 +1016,20 @@ object FeatureRegistry {
) {
override fun isEnabled(viewModel: MainViewModel) = false
override fun onToggle(viewModel: MainViewModel, context: Context, enabled: Boolean) {}
+ },
+ object : Feature(
+ id = "Charge optimization tile",
+ title = R.string.tile_charge_optimization,
+ iconRes = R.drawable.rounded_battery_android_frame_shield_24,
+ category = R.string.cat_system,
+ description = R.string.feat_qs_tiles_desc,
+ aboutDescription = R.string.about_desc_charge_optimization,
+ permissionKeys = listOf("WRITE_SECURE_SETTINGS"),
+ showToggle = false,
+ isVisibleInMain = false
+ ) {
+ override fun isEnabled(viewModel: MainViewModel) = false
+ override fun onToggle(viewModel: MainViewModel, context: Context, enabled: Boolean) {}
}
)
}
\ No newline at end of file
diff --git a/app/src/main/java/com/sameerasw/essentials/domain/registry/PermissionRegistry.kt b/app/src/main/java/com/sameerasw/essentials/domain/registry/PermissionRegistry.kt
index f687dacba..d42075f3f 100644
--- a/app/src/main/java/com/sameerasw/essentials/domain/registry/PermissionRegistry.kt
+++ b/app/src/main/java/com/sameerasw/essentials/domain/registry/PermissionRegistry.kt
@@ -30,6 +30,7 @@ fun initPermissionRegistry() {
PermissionRegistry.register("WRITE_SECURE_SETTINGS", R.string.feat_dynamic_night_light_title)
PermissionRegistry.register("WRITE_SECURE_SETTINGS", R.string.feat_screen_locked_security_title)
PermissionRegistry.register("WRITE_SECURE_SETTINGS", R.string.tile_developer_options)
+ PermissionRegistry.register("WRITE_SECURE_SETTINGS", R.string.tile_charge_optimization)
// Shizuku permission
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/services/NotificationListener.kt b/app/src/main/java/com/sameerasw/essentials/services/NotificationListener.kt
index 26d99b8ff..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
@@ -27,6 +28,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 +752,8 @@ class NotificationListener : NotificationListenerService() {
applicationContext.sendBroadcast(pulseIntent)
}
}
+
+ handleNotificationGlance(sbn, true)
} catch (_: Exception) {
// ignore failures
}
@@ -803,6 +808,82 @@ 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)
+
+ // 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)
+ }
+ }
+
+ 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/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/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/services/tiles/ChargeQuickTileService.kt b/app/src/main/java/com/sameerasw/essentials/services/tiles/ChargeQuickTileService.kt
new file mode 100644
index 000000000..f9615e937
--- /dev/null
+++ b/app/src/main/java/com/sameerasw/essentials/services/tiles/ChargeQuickTileService.kt
@@ -0,0 +1,99 @@
+package com.sameerasw.essentials.services.tiles
+
+import android.content.Intent
+import android.graphics.drawable.Icon
+import android.os.Build
+import android.provider.Settings
+import android.service.quicksettings.Tile
+import androidx.annotation.RequiresApi
+import com.sameerasw.essentials.FeatureSettingsActivity
+import com.sameerasw.essentials.R
+import com.sameerasw.essentials.utils.PermissionUtils
+
+@RequiresApi(Build.VERSION_CODES.N)
+class ChargeQuickTileService : BaseTileService() {
+
+ companion object {
+ private const val ADAPTIVE_CHARGING_SETTING = "adaptive_charging_enabled"
+ private const val CHARGE_OPTIMIZATION_MODE = "charge_optimization_mode"
+ }
+
+ override fun onClick() {
+ if (!hasFeaturePermission()) {
+ com.sameerasw.essentials.utils.HapticUtil.performHapticForService(this)
+ val intent = Intent(this, FeatureSettingsActivity::class.java).apply {
+ flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
+ putExtra("feature", "Quick settings tiles")
+ }
+ if (Build.VERSION.SDK_INT >= 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/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/activities/QSPreferencesActivity.kt b/app/src/main/java/com/sameerasw/essentials/ui/activities/QSPreferencesActivity.kt
index f7c4c2822..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
@@ -75,9 +75,12 @@ 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"
+ "com.sameerasw.essentials.services.tiles.AlwaysOnDisplayTileService" -> "Always on Display"
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/AlwaysOnDisplaySettingsUI.kt b/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/AlwaysOnDisplaySettingsUI.kt
new file mode 100644
index 000000000..c44f0dfe1
--- /dev/null
+++ b/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/AlwaysOnDisplaySettingsUI.kt
@@ -0,0 +1,160 @@
+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()
+ .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")
+ )
+ }
+
+ 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.toggleNotificationGlanceEnabled(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")
+ )
+
+ 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(
+ text = stringResource(R.string.notification_glance_desc),
+ modifier = Modifier.padding(horizontal = 16.dp),
+ style = MaterialTheme.typography.bodySmall,
+ 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 = {
+ 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/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/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
}
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()
+ )
+ }
)
}
}
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..3b43435d4 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,12 @@ class MainViewModel : ViewModel() {
val isCalendarSyncEnabled = mutableStateOf(false)
val isCalendarSyncPeriodicEnabled = mutableStateOf(false)
val isBatteryNotificationEnabled = mutableStateOf(false)
+ val isAodEnabled = mutableStateOf(false)
+ val isNotificationGlanceEnabled = mutableStateOf(false)
+ val isAodForceTurnOffEnabled = mutableStateOf(false)
+ val isAutoAccessibilityEnabled = mutableStateOf(false)
+ val isNotificationGlanceSameAsLightingEnabled = mutableStateOf(true)
+
data class CalendarAccount(
val id: Long,
@@ -230,6 +236,9 @@ class MainViewModel : ViewModel() {
Settings.Secure.getUriFor("display_density_forced") -> {
smallestWidth.intValue = settingsRepository.getSmallestWidth()
}
+ Settings.Secure.getUriFor("doze_always_on") -> {
+ isAodEnabled.value = settingsRepository.isAodEnabled()
+ }
}
}
}
@@ -394,6 +403,10 @@ 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_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)
}
}
}
@@ -405,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)
@@ -463,6 +513,12 @@ class MainViewModel : ViewModel() {
contentObserver
)
+ context.contentResolver.registerContentObserver(
+ Settings.Secure.getUriFor("doze_always_on"),
+ false,
+ contentObserver
+ )
+
settingsRepository.registerOnSharedPreferenceChangeListener(preferenceChangeListener)
viewModelScope.launch {
@@ -514,6 +570,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) {
@@ -717,6 +775,9 @@ class MainViewModel : ViewModel() {
isCalendarSyncPeriodicEnabled.value = settingsRepository.isCalendarSyncPeriodicEnabled()
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)
if (isBatteryNotificationEnabled.value) {
@@ -2134,12 +2195,49 @@ 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)
}
+ fun setAodEnabled(enabled: Boolean) {
+ isAodEnabled.value = enabled
+ settingsRepository.setAodEnabled(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)
+ }
+
+ 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() {
+
super.onCleared()
appContext?.contentResolver?.unregisterContentObserver(contentObserver)
if (::settingsRepository.isInitialized) {
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 @@
+
+
+
+
+
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/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-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
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
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
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
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
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
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
index 7459d2e02..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
@@ -375,7 +377,7 @@
Text Size
Font Size
Custom Text
- Enter your text...
+ Gib deinen Text ein...
Spacing
Border Width
Round Corners
@@ -391,7 +393,7 @@
Rotate right
Next
OK
- Save Changes
+ Änderungen Speichern
Calendar Sync Settings
Sync specific calendars
Periodic Sync
@@ -1041,7 +1043,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
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
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
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
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
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
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
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
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
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
フリーズさせるアプリを選択
フリーズできるアプリを選択してください
自動化
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
diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml
index a209e0738..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
@@ -192,9 +194,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 +601,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
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
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
diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml
index ea4a579ba..9b9fe8d0a 100644
--- a/app/src/main/res/values-pt/strings.xml
+++ b/app/src/main/res/values-pt/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
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
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
Выбор Приложения для Заморозки
Выберите, какие приложения будут заморожены
Автоматизация
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
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
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
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
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
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
diff --git a/app/src/main/res/values-zh/strings.xml b/app/src/main/res/values-zh/strings.xml
index 8795b2e4f..68f178f4e 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
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 217e34bd6..652f546e8 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.
@@ -218,6 +222,10 @@
dns.quad9.net
CleanBrowsing
adult-filter-dns.cleanbrowsing.org
+ Charging
+ Limit to 80%
+ Adaptive
+ Not optimized
Screen locked security
@@ -372,6 +380,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
@@ -620,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.
@@ -628,6 +639,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.\n\nCredits: TebbeUbben/ChargeQuickTile
Download
@@ -872,6 +884,12 @@
- vibration
- feel
+
+ - battery
+ - charge
+ - optimization
+ - pixel
+
Invert selection
@@ -1144,4 +1162,8 @@
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.
+ Auto accessibility
+ Automatically grants the accessibility permission on app launch if missing using WRITE_SECURE_SETTINGS.
\ No newline at end of file