Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
b11e317
New translations strings.xml (German)
sameerasw Feb 27, 2026
820721e
New translations strings.xml (Dutch)
sameerasw Feb 27, 2026
c87a36a
Update source file strings.xml
sameerasw Feb 27, 2026
5e6eae6
New translations strings.xml (Romanian)
sameerasw Feb 27, 2026
fbfa492
New translations strings.xml (French)
sameerasw Feb 27, 2026
8f3b3a2
New translations strings.xml (Spanish)
sameerasw Feb 27, 2026
05fdfeb
New translations strings.xml (Afrikaans)
sameerasw Feb 27, 2026
6b72139
New translations strings.xml (Arabic)
sameerasw Feb 27, 2026
f569af2
New translations strings.xml (Catalan)
sameerasw Feb 27, 2026
e983929
New translations strings.xml (Czech)
sameerasw Feb 27, 2026
3ee9af1
New translations strings.xml (Danish)
sameerasw Feb 27, 2026
ec4f9d4
New translations strings.xml (German)
sameerasw Feb 27, 2026
aa41716
New translations strings.xml (Greek)
sameerasw Feb 27, 2026
24d499a
New translations strings.xml (Finnish)
sameerasw Feb 27, 2026
09125ad
New translations strings.xml (Hebrew)
sameerasw Feb 27, 2026
e501979
New translations strings.xml (Hungarian)
sameerasw Feb 27, 2026
3b1c334
New translations strings.xml (Italian)
sameerasw Feb 27, 2026
9a7fad4
New translations strings.xml (Japanese)
sameerasw Feb 27, 2026
d3fb35c
New translations strings.xml (Korean)
sameerasw Feb 27, 2026
f92afbb
New translations strings.xml (Dutch)
sameerasw Feb 27, 2026
5e409f5
New translations strings.xml (Norwegian)
sameerasw Feb 27, 2026
a07e956
New translations strings.xml (Polish)
sameerasw Feb 27, 2026
a24c0f4
New translations strings.xml (Portuguese)
sameerasw Feb 27, 2026
5190790
New translations strings.xml (Russian)
sameerasw Feb 27, 2026
4a8d5b3
New translations strings.xml (Serbian (Cyrillic))
sameerasw Feb 27, 2026
4132261
New translations strings.xml (Swedish)
sameerasw Feb 27, 2026
b86dafb
New translations strings.xml (Turkish)
sameerasw Feb 27, 2026
3360240
New translations strings.xml (Ukrainian)
sameerasw Feb 27, 2026
5ec6db2
New translations strings.xml (Chinese Simplified)
sameerasw Feb 27, 2026
49b895c
New translations strings.xml (Chinese Traditional)
sameerasw Feb 27, 2026
8b16305
New translations strings.xml (English)
sameerasw Feb 27, 2026
eca8497
New translations strings.xml (Vietnamese)
sameerasw Feb 27, 2026
97322d2
New translations strings.xml (Portuguese, Brazilian)
sameerasw Feb 27, 2026
08a3cfc
New translations strings.xml (Sinhala)
sameerasw Feb 27, 2026
077341e
New translations strings.xml (Acholi)
sameerasw Feb 27, 2026
e414011
Merge pull request #240 from sameerasw/l10n_develop
sameerasw Feb 27, 2026
7604d5f
feat: #156 Pixel chargign optimization toggle tile
sameerasw Feb 28, 2026
208e449
feat: #156 Pixel chargign optimization toggle tile
sameerasw Feb 28, 2026
1ecc6bc
feat: Credtis to TebbeUbben/ChargeQuickTile
sameerasw Feb 28, 2026
3b08ef8
feat: Delete cliboard history entries
sameerasw Feb 28, 2026
4fc14c5
fix: Emoji loading in keyboard
sameerasw Feb 28, 2026
3994328
feat: AOD settings
sameerasw Feb 28, 2026
dad8e44
feat: Dynamic AOD/ Notification glance
sameerasw Feb 28, 2026
4f93409
feat: Force turn off display when no notifications in dynamic AOD
sameerasw Feb 28, 2026
3cc67d1
feat: Auto grant accessibility
sameerasw Feb 28, 2026
36ed60e
feat: App lock light theme #231
sameerasw Feb 28, 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
7 changes: 6 additions & 1 deletion app/proguard-rules.pro
Original file line number Diff line number Diff line change
Expand Up @@ -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.** { *; }
-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 { *; }
33 changes: 23 additions & 10 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -406,16 +406,6 @@
</intent-filter>
</service>

<service
android:name=".services.tiles.AlwaysOnDisplayTileService"
android:exported="true"
android:icon="@drawable/rounded_mobile_text_2_24"
android:label="AOD"
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
<intent-filter>
<action android:name="android.service.quicksettings.action.QS_TILE" />
</intent-filter>
</service>
<service
android:name=".services.tiles.NotificationLightingTileService"
android:exported="true"
Expand Down Expand Up @@ -632,6 +622,29 @@
</intent-filter>
</service>

<service
android:name=".services.tiles.ChargeQuickTileService"
android:exported="true"
android:icon="@drawable/rounded_battery_android_frame_shield_24"
android:label="@string/tile_charge_optimization"
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
<intent-filter>
<action android:name="android.service.quicksettings.action.QS_TILE" />
</intent-filter>
</service>

<service
android:name=".services.tiles.AlwaysOnDisplayTileService"
android:exported="true"
android:icon="@drawable/rounded_mobile_text_2_24"
android:label="@string/feat_always_on_display_title"
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
<intent-filter>
<action android:name="android.service.quicksettings.action.QS_TILE" />
<action android:name="android.service.quicksettings.action.QS_TILE_PREFERENCES" />
</intent-filter>
</service>

<receiver
android:name=".services.receivers.SecurityDeviceAdminReceiver"
android:permission="android.permission.BIND_DEVICE_ADMIN"
Expand Down
181 changes: 88 additions & 93 deletions app/src/main/java/com/sameerasw/essentials/AppLockActivity.kt
Original file line number Diff line number Diff line change
@@ -1,20 +1,34 @@
package com.sameerasw.essentials

import android.app.Activity
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle
import android.util.Log
import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView
import androidx.activity.SystemBarStyle
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.biometric.BiometricPrompt
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import coil.compose.AsyncImage
import androidx.core.content.ContextCompat
import androidx.fragment.app.FragmentActivity
import com.sameerasw.essentials.services.tiles.ScreenOffAccessibilityService
import com.sameerasw.essentials.ui.theme.EssentialsTheme
import java.util.concurrent.Executor

class AppLockActivity : FragmentActivity() {
Expand All @@ -26,90 +40,7 @@ class AppLockActivity : FragmentActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

// Force Dark Theme
enableEdgeToEdge(
statusBarStyle = SystemBarStyle.dark(android.graphics.Color.TRANSPARENT),
navigationBarStyle = SystemBarStyle.dark(android.graphics.Color.TRANSPARENT)
)

window.setBackgroundDrawableResource(android.R.color.black)

// Get accent color (respect Monet on Android 12+)
val primaryColor =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
ContextCompat.getColor(this, android.R.color.system_accent1_300)
} else {
val typedValue = android.util.TypedValue()
theme.resolveAttribute(android.R.attr.colorPrimary, typedValue, true)
typedValue.data
}

val root = LinearLayout(this).apply {
orientation = LinearLayout.VERTICAL
setBackgroundColor(android.graphics.Color.BLACK)
gravity = android.view.Gravity.CENTER_HORIZONTAL
setPadding(0, (140 * resources.displayMetrics.density).toInt(), 0, 0)
layoutParams = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.MATCH_PARENT
)
}

// Composite icon layout
val iconContainer = FrameLayout(this).apply {
layoutParams = LinearLayout.LayoutParams(
(96 * resources.displayMetrics.density).toInt(),
(96 * resources.displayMetrics.density).toInt()
)
}

val baseIconSize = (80 * resources.displayMetrics.density).toInt()
val appRegistrationIcon = ImageView(this).apply {
setImageResource(R.drawable.rounded_shield_lock_24)
setColorFilter(primaryColor, android.graphics.PorterDuff.Mode.SRC_IN)
layoutParams = FrameLayout.LayoutParams(baseIconSize, baseIconSize).apply {
gravity = android.view.Gravity.CENTER
}
}

val essentialsIconSize = (32 * resources.displayMetrics.density).toInt()
val essentialsIconView = ImageView(this).apply {
setImageResource(R.mipmap.ic_launcher_round)
layoutParams = FrameLayout.LayoutParams(essentialsIconSize, essentialsIconSize).apply {
gravity = android.view.Gravity.BOTTOM or android.view.Gravity.END
}
}

iconContainer.addView(appRegistrationIcon)
iconContainer.addView(essentialsIconView)

val titleView = TextView(this).apply {
text = "App is locked"
setTextColor(android.graphics.Color.WHITE)
textSize = 22f
setPadding(0, (24 * resources.displayMetrics.density).toInt(), 0, 0)
gravity = android.view.Gravity.CENTER
}

val subtextView = TextView(this).apply {
text = "Please authenticate to unlock or \ngive the phone to the owner \n( -_-)"
setTextColor(android.graphics.Color.WHITE)
textSize = 14f
alpha = 0.6f
setPadding(
(48 * resources.displayMetrics.density).toInt(),
(8 * resources.displayMetrics.density).toInt(),
(48 * resources.displayMetrics.density).toInt(),
0
)
gravity = android.view.Gravity.CENTER
}

root.addView(iconContainer)
root.addView(titleView)
root.addView(subtextView)
setContentView(root)
enableEdgeToEdge()

packageToLock = intent.getStringExtra("package_to_lock")
if (packageToLock == null) {
Expand All @@ -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,
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -656,7 +659,16 @@ class FeatureSettingsActivity : FragmentActivity() {
highlightSetting = highlightSetting
)
}

"Always on Display" -> {
AlwaysOnDisplaySettingsUI(
viewModel = viewModel,
modifier = Modifier.padding(top = 16.dp),
highlightSetting = highlightSetting
)
}
}

}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) }
)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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<AppSelection>) =
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 }
Expand Down Expand Up @@ -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()
}
}
}

Loading