Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
8 changes: 8 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,14 @@
android:taskAffinity=""
android:theme="@style/Theme.Essentials.Translucent" />

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

<activity
android:name=".ui.activities.TileAuthActivity"
android:excludeFromRecents="true"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import com.sameerasw.essentials.domain.diy.Action
import com.sameerasw.essentials.domain.diy.ActionGsonAdapter
import com.sameerasw.essentials.domain.model.AppSelection
import com.sameerasw.essentials.domain.model.AppTag
import com.sameerasw.essentials.domain.model.ConsciousGateCountdownStyle
import com.sameerasw.essentials.domain.model.DnsPreset
import com.sameerasw.essentials.domain.model.NotificationLightingColorMode
import com.sameerasw.essentials.domain.model.NotificationLightingSide
Expand Down Expand Up @@ -222,6 +223,15 @@ class SettingsRepository(
const val KEY_APP_LOCK_ENABLED = "app_lock_enabled"
const val KEY_APP_LOCK_SELECTED_APPS = "app_lock_selected_apps"
const val KEY_APP_LOCK_AUTO_LOCK_DELAY_INDEX = "app_lock_auto_lock_delay_index"

const val KEY_CONSCIOUS_GATE_ENABLED = "conscious_gate_enabled"
const val KEY_CONSCIOUS_GATE_SELECTED_APPS = "conscious_gate_selected_apps"
const val KEY_CONSCIOUS_GATE_DELAY_SECONDS = "conscious_gate_delay_seconds"
const val KEY_CONSCIOUS_GATE_REAPPEAR_MINUTES = "conscious_gate_reappear_minutes"
const val KEY_CONSCIOUS_GATE_ICON_NAME = "conscious_gate_icon_name"
const val KEY_CONSCIOUS_GATE_TITLE = "conscious_gate_title"
const val KEY_CONSCIOUS_GATE_MESSAGE = "conscious_gate_message"
const val KEY_CONSCIOUS_GATE_COUNTDOWN_STYLE = "conscious_gate_countdown_style"
const val KEY_USE_USAGE_ACCESS = "use_usage_access"

const val KEY_FREEZE_WHEN_LOCKED_ENABLED = "freeze_when_locked_enabled"
Expand Down Expand Up @@ -940,6 +950,63 @@ class SettingsRepository(
enabled: Boolean,
) = updateAppSelection(KEY_APP_LOCK_SELECTED_APPS, packageName, enabled)

/**
* Executes the load conscious gate selected apps operation.
*/
fun loadConsciousGateSelectedApps() = loadAppSelection(KEY_CONSCIOUS_GATE_SELECTED_APPS)

/**
* Executes the save conscious gate selected apps operation.
*
* @param apps [List<AppSelection>] Target apps.
*/
fun saveConsciousGateSelectedApps(apps: List<AppSelection>) = saveAppSelection(KEY_CONSCIOUS_GATE_SELECTED_APPS, apps)

/**
* Executes the update conscious gate app selection operation.
*
* @param packageName [String] Target package name.
* @param enabled [Boolean] Target enabled.
*/
fun updateConsciousGateAppSelection(
packageName: String,
enabled: Boolean,
) = updateAppSelection(KEY_CONSCIOUS_GATE_SELECTED_APPS, packageName, enabled)

fun getConsciousGateDelaySeconds(): Int = prefs.getInt(KEY_CONSCIOUS_GATE_DELAY_SECONDS, 5)

fun setConsciousGateDelaySeconds(seconds: Int) = putInt(KEY_CONSCIOUS_GATE_DELAY_SECONDS, seconds)

fun getConsciousGateReappearMinutes(): Int = prefs.getInt(KEY_CONSCIOUS_GATE_REAPPEAR_MINUTES, 0)

fun setConsciousGateReappearMinutes(minutes: Int) = putInt(KEY_CONSCIOUS_GATE_REAPPEAR_MINUTES, minutes)

fun getConsciousGateIconName(): String = prefs.getString(KEY_CONSCIOUS_GATE_ICON_NAME, "rounded_pause_24") ?: "rounded_pause_24"

fun setConsciousGateIconName(iconName: String) = putString(KEY_CONSCIOUS_GATE_ICON_NAME, iconName)

fun getConsciousGateTitle(context: Context = this.context): String =
prefs.getString(KEY_CONSCIOUS_GATE_TITLE, null) ?: context.getString(com.sameerasw.essentials.R.string.conscious_gate_default_title)

fun setConsciousGateTitle(title: String) = putString(KEY_CONSCIOUS_GATE_TITLE, title)

fun getConsciousGateMessage(context: Context = this.context): String =
prefs.getString(KEY_CONSCIOUS_GATE_MESSAGE, null) ?: context.getString(com.sameerasw.essentials.R.string.conscious_gate_default_message)

fun setConsciousGateMessage(message: String) = putString(KEY_CONSCIOUS_GATE_MESSAGE, message)

fun getConsciousGateCountdownStyle(): ConsciousGateCountdownStyle {
val styleName =
prefs.getString(KEY_CONSCIOUS_GATE_COUNTDOWN_STYLE, ConsciousGateCountdownStyle.CIRCULAR_WAVY.name)
return try {
ConsciousGateCountdownStyle.valueOf(styleName ?: ConsciousGateCountdownStyle.CIRCULAR_WAVY.name)
} catch (e: Exception) {
ConsciousGateCountdownStyle.CIRCULAR_WAVY
}
}

fun setConsciousGateCountdownStyle(style: ConsciousGateCountdownStyle) = putString(KEY_CONSCIOUS_GATE_COUNTDOWN_STYLE, style.name)

/**
* Executes the load freeze selected apps operation.
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/*
* Copyright (c) 2026 sameerasw.com
* License: MIT License
*
* Feature Module: Domain Layer Models & Registries
* File: ConsciousGateCountdownStyle.kt
* Description: Domain model and business logic entry for ConsciousGateCountdownStyle.kt.
*/

package com.sameerasw.essentials.domain.model

enum class ConsciousGateCountdownStyle {
CIRCULAR_WAVY,
LINEAR_WAVY,
LOADING_BLOB,
BREATHING_DOT,
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import com.sameerasw.essentials.domain.model.Feature
import com.sameerasw.essentials.domain.model.SearchSetting
import com.sameerasw.essentials.ui.activities.PixelSearchbarSettingsActivity
import com.sameerasw.essentials.ui.activities.WatermarkActivity
import com.sameerasw.essentials.ui.features.consciousgate.CONSCIOUS_GATE_FEATURE_ID
import com.sameerasw.essentials.utils.DeviceUtils
import com.sameerasw.essentials.utils.ShellUtils
import com.sameerasw.essentials.viewmodels.MainViewModel
Expand Down Expand Up @@ -1454,6 +1455,66 @@ object FeatureRegistry {
enabled: Boolean,
) = viewModel.setAppLockEnabled(enabled, context)
},
object : Feature(
id = CONSCIOUS_GATE_FEATURE_ID,
title = R.string.feat_conscious_gate_title,
iconRes = R.drawable.rounded_pause_24,
category = R.string.cat_interaction,
description = R.string.feat_conscious_gate_desc,
aboutDescription = R.string.about_desc_conscious_gate,
parentFeatureId = "Input",
searchableSettings =
listOf(
SearchSetting(
R.string.search_conscious_gate_enable_title,
R.string.search_conscious_gate_enable_desc,
"conscious_gate_enabled",
R.array.keywords_privacy,
),
SearchSetting(
R.string.search_conscious_gate_pick_title,
R.string.search_conscious_gate_pick_desc,
"conscious_gate_selected_apps",
R.array.keywords_selection,
),
SearchSetting(
R.string.search_conscious_gate_delay_title,
R.string.search_conscious_gate_delay_desc,
"conscious_gate_delay_seconds",
),
),
) {
override val permissionKeys: List<String>
get() =
if (com.sameerasw.essentials.data.repository
.SettingsRepository(
EssentialsApp.context,
).getBoolean(com.sameerasw.essentials.data.repository.SettingsRepository.KEY_USE_USAGE_ACCESS)
) {
listOf("USAGE_STATS", "ACCESSIBILITY")
} else {
listOf("ACCESSIBILITY")
}

override fun isEnabled(viewModel: MainViewModel) = viewModel.isConsciousGateEnabled.value

override fun isToggleEnabled(
viewModel: MainViewModel,
context: Context,
) = (
if (viewModel.isUseUsageAccess.value) {
viewModel.isUsageStatsPermissionGranted.value
} else {
viewModel.isAccessibilityEnabled.value
}
)

override fun onToggle(
viewModel: MainViewModel,
context: Context,
enabled: Boolean,
) = viewModel.setConsciousGateEnabled(enabled, context)
},
object : Feature(
id = "Shut-Up!",
title = R.string.feat_shut_up_title,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ fun initPermissionRegistry() {
PermissionRegistry.register("ACCESSIBILITY", R.string.feat_dynamic_night_light_title)
PermissionRegistry.register("ACCESSIBILITY", R.string.feat_app_lock_title)
PermissionRegistry.register("ACCESSIBILITY", R.string.feat_essentials_on_display_title)
PermissionRegistry.register("ACCESSIBILITY", R.string.feat_conscious_gate_title)

// Write secure settings permission
PermissionRegistry.register("WRITE_SECURE_SETTINGS", R.string.feat_statusbar_icons_title)
Expand Down Expand Up @@ -70,6 +71,7 @@ fun initPermissionRegistry() {
PermissionRegistry.register("USAGE_STATS", R.string.feat_freeze_title)
PermissionRegistry.register("USAGE_STATS", R.string.feat_app_lock_title)
PermissionRegistry.register("USAGE_STATS", R.string.feat_dynamic_night_light_title)
PermissionRegistry.register("USAGE_STATS", R.string.feat_conscious_gate_title)
PermissionRegistry.register("NOTIFICATION_LISTENER", R.string.feat_freeze_title)

// Root permission
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,17 @@ class AppDetectionService : Service() {
"APP_AUTHENTICATION_FAILED" -> {
goHome()
}

"CONSCIOUS_GATE_CONFIRMED" -> {
val packageName = intent.getStringExtra("package_name")
if (packageName != null) {
appFlowHandler.onConsciousGateConfirmed(packageName)
}
}

"CONSCIOUS_GATE_CLOSED" -> {
goHome()
}
}
}
}
Expand All @@ -72,6 +83,8 @@ class AppDetectionService : Service() {
IntentFilter().apply {
addAction("APP_AUTHENTICATED")
addAction("APP_AUTHENTICATION_FAILED")
addAction("CONSCIOUS_GATE_CONFIRMED")
addAction("CONSCIOUS_GATE_CLOSED")
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
registerReceiver(authReceiver, filter, RECEIVER_EXPORTED)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,12 @@ class AppFlowHandler(
private set
private var currentUsageStatsPackage: String? = null

// Conscious Gate State
private var gatingPackage: String? = null
private var lastGateRequestTime: Long = 0
private val confirmedGatePackages = mutableMapOf<String, Long>()
private val pendingReappearRunnables = mutableMapOf<String, Runnable>()

// App Automation State
private val activeAppAutomationIds = mutableSetOf<String>()

Expand Down Expand Up @@ -142,11 +148,17 @@ class AppFlowHandler(
if (oldPackage != null && oldPackage != packageName) {
lastLeaveTimes[oldPackage] = System.currentTimeMillis()
checkShutUpRestore(oldPackage, packageName)
confirmedGatePackages.remove(oldPackage)
pendingReappearRunnables.remove(oldPackage)?.let { handler.removeCallbacks(it) }
}
if (packageName != context.packageName && packageName != lockingPackage) {
lockingPackage = null
}
if (packageName != context.packageName && packageName != gatingPackage) {
gatingPackage = null
}
checkAppLock(packageName)
checkConsciousGate(packageName)
checkHighlightNightLight(packageName)
checkAppAutomations(packageName)
checkGestureBarAutomation(packageName)
Expand Down Expand Up @@ -235,6 +247,91 @@ class AppFlowHandler(
}
}

private fun checkConsciousGate(packageName: String) {
val prefs = context.getSharedPreferences("essentials_prefs", Context.MODE_PRIVATE)
val isEnabled = prefs.getBoolean("conscious_gate_enabled", false)
if (!isEnabled) return

if (packageName == context.packageName) {
return
}

val json = prefs.getString("conscious_gate_selected_apps", null)
val selectedApps: List<AppSelection> =
if (json != null) {
try {
Gson().fromJson(json, Array<AppSelection>::class.java).toList()
} catch (_: Exception) {
emptyList()
}
} else {
emptyList()
}

val isGated = selectedApps.find { it.packageName == packageName }?.isEnabled ?: false
if (!isGated) return

if (confirmedGatePackages.containsKey(packageName)) {
// Already confirmed for this continuous session; the reappear timer (if any)
// is scheduled separately from onConsciousGateConfirmed.
return
}

val now = System.currentTimeMillis()
if (packageName == gatingPackage && now - lastGateRequestTime < 1500) {
return
}

gatingPackage = packageName
lastGateRequestTime = now

val delaySeconds = prefs.getInt("conscious_gate_delay_seconds", 5)
val iconName = prefs.getString("conscious_gate_icon_name", null) ?: "rounded_pause_24"
val title = prefs.getString("conscious_gate_title", null)
val message = prefs.getString("conscious_gate_message", null)
val countdownStyle = prefs.getString("conscious_gate_countdown_style", null) ?: "CIRCULAR_WAVY"

Log.d("ConsciousGate", "App $packageName is gated and not confirmed. Showing pause screen.")
val intent =
Intent().apply {
component = ComponentName(context, "com.sameerasw.essentials.ui.activities.ConsciousGateActivity")
putExtra("package_to_gate", packageName)
putExtra("delay_seconds", delaySeconds)
putExtra("icon_name", iconName)
title?.let { putExtra("title", it) }
message?.let { putExtra("message", it) }
putExtra("countdown_style", countdownStyle)
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_NO_ANIMATION
}
context.startActivity(intent)
}

fun onConsciousGateConfirmed(packageName: String) {
confirmedGatePackages[packageName] = System.currentTimeMillis()
if (packageName == gatingPackage) {
gatingPackage = null
}

pendingReappearRunnables.remove(packageName)?.let { handler.removeCallbacks(it) }

val prefs = context.getSharedPreferences("essentials_prefs", Context.MODE_PRIVATE)
val reappearMinutes = prefs.getInt("conscious_gate_reappear_minutes", 0)
if (reappearMinutes > 0) {
val runnable =
Runnable {
pendingReappearRunnables.remove(packageName)
if (currentPackage == packageName) {
confirmedGatePackages.remove(packageName)
checkConsciousGate(packageName)
} else {
confirmedGatePackages.remove(packageName)
}
}
pendingReappearRunnables[packageName] = runnable
handler.postDelayed(runnable, reappearMinutes * 60 * 1000L)
}
}

private fun checkHighlightNightLight(packageName: String) {
val prefs = context.getSharedPreferences("essentials_prefs", Context.MODE_PRIVATE)
val isEnabled = prefs.getBoolean("dynamic_night_light_enabled", false)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -672,6 +672,13 @@ class ScreenOffAccessibilityService :

"APP_AUTHENTICATION_FAILED" -> performGlobalAction(GLOBAL_ACTION_HOME)

"CONSCIOUS_GATE_CONFIRMED" ->
intent
.getStringExtra("package_name")
?.let { appFlowHandler.onConsciousGateConfirmed(it) }

"CONSCIOUS_GATE_CLOSED" -> performGlobalAction(GLOBAL_ACTION_HOME)

FlashlightActionReceiver.ACTION_INCREASE,
FlashlightActionReceiver.ACTION_DECREASE,
FlashlightActionReceiver.ACTION_OFF,
Expand Down
Loading
Loading