Skip to content
Draft
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
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ Essential tools, mods and workarounds for Pixels and other Androids
<a href="https://github.com/sameerasw/essentials/issues/new?template=bug_report.md"><img alt="GitHub Issues or Pull Requests by label" src="https://img.shields.io/github/issues/sameerasw/essentials/bug?style=for-the-badge&logo=openbugbounty&logoColor=%23fff&label=bug%3F&labelColor=%232a6&color=%232a6">
</a>
<a href="https://github.com/sameerasw/essentials/issues/new?template=feature_request.md"><img alt="GitHub Issues or Pull Requests by label" src="https://img.shields.io/github/issues/sameerasw/essentials/enhancement?style=for-the-badge&logo=apachespark&logoColor=%23fff&label=Feature%20request&labelColor=%23a26&color=%23a26">
</a>
</a>
<a href="https://sameerasw.com"><img src="https://img.shields.io/badge/My%20website-orange?style=for-the-badge&logo=googlechrome&logoColor=%23000&labelColor=%233AFFB8&color=%233AFFB8" alt="My website" /></a>
<a href="https://t.me/tidwib"><img src="https://img.shields.io/badge/Community-2CA5E0?style=for-the-badge&logo=telegram&logoColor=white" alt="Community" /></a>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ import com.sameerasw.essentials.ui.composables.configs.NotificationLightingSetti
import com.sameerasw.essentials.ui.composables.configs.OtherCustomizationsSettingsUI
import com.sameerasw.essentials.ui.composables.configs.QuickSettingsTilesSettingsUI
import com.sameerasw.essentials.ui.composables.configs.RefreshRateSettingsUI
import com.sameerasw.essentials.ui.composables.configs.PerAppRefreshRateSettingsUI
import com.sameerasw.essentials.ui.composables.configs.RemoteLockSettingsUI
import com.sameerasw.essentials.ui.composables.configs.ScreenLockedSecuritySettingsUI
import com.sameerasw.essentials.ui.composables.configs.ScreenOffWidgetSettingsUI
Expand Down Expand Up @@ -264,6 +265,7 @@ class FeatureSettingsActivity : AppCompatActivity() {
"Location reached" -> !viewModel.isLocationPermissionGranted.value || !viewModel.isBackgroundLocationPermissionGranted.value
"Quick settings tiles" -> !viewModel.isWriteSettingsEnabled.value
"Screen refresh rate" -> !viewModel.isShizukuPermissionGranted.value
"Per app refresh rate" -> (if (viewModel.isUseUsageAccess.value) !viewModel.isUsageStatsPermissionGranted.value else !isAccessibilityEnabled) || !isShizukuPermissionGranted
// Top level checks for other features (rarely hit if they are children, but safe to add)
"Essentials On Display" -> !isAccessibilityEnabled || !isNotificationListenerEnabled
"Call vibrations" -> !isReadPhoneStateEnabled || !isNotificationListenerEnabled
Expand Down Expand Up @@ -497,6 +499,7 @@ class FeatureSettingsActivity : AppCompatActivity() {
"Text and animations" -> !viewModel.isWriteSettingsEnabled.value || !isWriteSecureSettingsEnabled
"Lock screen clock" -> !isWriteSecureSettingsEnabled
"Screen refresh rate" -> !viewModel.isShizukuPermissionGranted.value
"Per app refresh rate" -> (if (viewModel.isUseUsageAccess.value) !viewModel.isUsageStatsPermissionGranted.value else !isAccessibilityEnabled) || !viewModel.isShizukuPermissionGranted.value
"Shut-Up!" -> !isWriteSecureSettingsEnabled || !viewModel.isUsageStatsPermissionGranted.value
else -> false
}
Expand Down Expand Up @@ -743,6 +746,13 @@ class FeatureSettingsActivity : AppCompatActivity() {
)
}

"Per app refresh rate" -> {
PerAppRefreshRateSettingsUI(
viewModel = viewModel,
modifier = Modifier.padding(top = 16.dp),
highlightSetting = highlightSetting
)
}
"Always on Display" -> {
AlwaysOnDisplaySettingsUI(
viewModel = viewModel,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import com.sameerasw.essentials.domain.model.NotificationLightingSide
import com.sameerasw.essentials.domain.model.NotificationLightingStyle
import com.sameerasw.essentials.domain.model.NotificationLightingSweepPosition
import com.sameerasw.essentials.domain.model.ScaleAnimationsProfile
import com.sameerasw.essentials.domain.model.AppRefreshRateConfig
import com.sameerasw.essentials.domain.model.TrackedRepo
import com.sameerasw.essentials.domain.model.github.GitHubUser
import com.sameerasw.essentials.utils.RootUtils
Expand Down Expand Up @@ -240,6 +241,9 @@ class SettingsRepository(private val context: Context) {
const val KEY_EDGE_LIGHTING_SWEEP_SELECTED_SHAPES = "edge_lighting_sweep_selected_shapes"
const val KEY_DISABLE_ROTATION_SUGGESTION = "disable_rotation_suggestion"

const val KEY_PER_APP_REFRESH_RATE_ENABLED = "per_app_refresh_rate_enabled"
const val KEY_PER_APP_REFRESH_RATE_CONFIGS = "per_app_refresh_rate_configs"

const val KEY_LOCK_SCREEN_CLOCK_WEIGHT = "lock_screen_clock_weight"
const val KEY_LOCK_SCREEN_CLOCK_WIDTH = "lock_screen_clock_width"
const val KEY_LOCK_SCREEN_CLOCK_GRADE = "lock_screen_clock_grade"
Expand Down Expand Up @@ -554,6 +558,38 @@ class SettingsRepository(private val context: Context) {
}
}

fun loadPerAppRefreshRateConfigs(): List<AppRefreshRateConfig> {
val json = prefs.getString(KEY_PER_APP_REFRESH_RATE_CONFIGS, null)
return if (json != null) {
try {
gson.fromJson(
json,
Array<AppRefreshRateConfig>::class.java
).toList()
} catch (e: Exception) {
emptyList()
}
} else {
emptyList()
}
}

fun savePerAppRefreshRateConfigs(configs: List<AppRefreshRateConfig>) {
val json = gson.toJson(configs)
putString(KEY_PER_APP_REFRESH_RATE_CONFIGS, json)
}

fun updatePerAppRefreshRateConfig(config: AppRefreshRateConfig) {
val current = loadPerAppRefreshRateConfigs().toMutableList()
val index = current.indexOfFirst { it.packageName == config.packageName }
if (index != -1) {
current[index] = config
} else {
current.add(config)
}
savePerAppRefreshRateConfigs(current)
}

private fun updateAppSelection(key: String, packageName: String, enabled: Boolean) {
val current = loadAppSelection(key).toMutableList()
val index = current.indexOfFirst { it.packageName == packageName }
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.sameerasw.essentials.domain.model

data class AppRefreshRateConfig(
val packageName: String,
val refreshRate: Float,
val isFixed: Boolean = false,
val isEnabled: Boolean = true,
val landscapeRefreshRate: Float? = null,
val onlyOnMediaPlaying: Boolean = false
)
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,30 @@ object FeatureRegistry {
override fun isEnabled(viewModel: MainViewModel) = true
override fun onToggle(viewModel: MainViewModel, context: Context, enabled: Boolean) {}
},
object : Feature(
id = "Per app refresh rate",
title = R.string.refresh_rate_per_app_enable_title,
iconRes = R.drawable.ic_per_app_refresh_rate,
category = R.string.cat_interface,
description = R.string.refresh_rate_per_app_enable_desc,
aboutDescription = R.string.refresh_rate_per_app_enable_desc,
showToggle = false,
parentFeatureId = "Display",
) {
override val permissionKeys: List<String>
get() = (if (com.sameerasw.essentials.data.repository.SettingsRepository(com.sameerasw.essentials.EssentialsApp.context)
.getBoolean(com.sameerasw.essentials.data.repository.SettingsRepository.KEY_USE_USAGE_ACCESS))
listOf("USAGE_STATS") else listOf("ACCESSIBILITY")) + listOf("SHIZUKU")

override fun isEnabled(viewModel: MainViewModel): Boolean = viewModel.isPerAppRefreshRateEnabled.value

override fun isToggleEnabled(viewModel: MainViewModel, context: Context): Boolean =
(if (viewModel.isUseUsageAccess.value) viewModel.isUsageStatsPermissionGranted.value else viewModel.isAccessibilityEnabled.value) && viewModel.isShizukuPermissionGranted.value

override fun onToggle(viewModel: MainViewModel, context: Context, enabled: Boolean) {
viewModel.setPerAppRefreshRateEnabled(enabled, context)
}
},
object : Feature(
id = "Lock screen clock",
title = R.string.feat_lock_screen_clock_title,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.Service
import android.app.usage.UsageEvents
import android.app.usage.UsageStats
import android.app.usage.UsageStatsManager
import android.content.BroadcastReceiver
Expand Down Expand Up @@ -101,6 +102,26 @@ class AppDetectionService : Service() {
private fun getForegroundPackage(): String? {
val usageStatsManager = getSystemService(USAGE_STATS_SERVICE) as UsageStatsManager
val time = System.currentTimeMillis()

// 1. Try to find the last resumed activity using queryEvents (real-time & accurate)
try {
val events = usageStatsManager.queryEvents(time - 1000 * 15, time)
val event = UsageEvents.Event()
var lastResumedPackage: String? = null
while (events.hasNextEvent()) {
events.getNextEvent(event)
if (event.eventType == UsageEvents.Event.ACTIVITY_RESUMED) {
lastResumedPackage = event.packageName
}
}
if (lastResumedPackage != null) {
return lastResumedPackage
}
} catch (e: Exception) {
android.util.Log.e("AppDetectionService", "Failed to query usage events", e)
}

// 2. Fallback to queryUsageStats if no events found in the window
val stats = usageStatsManager.queryUsageStats(
UsageStatsManager.INTERVAL_DAILY,
time - 1000 * 10,
Expand Down Expand Up @@ -129,6 +150,7 @@ class AppDetectionService : Service() {
override fun onDestroy() {
isRunning = false
isPolling = false
appFlowHandler.destroy()
handler.removeCallbacksAndMessages(null)
try {
unregisterReceiver(authReceiver)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -669,6 +669,13 @@ class NotificationListener : NotificationListenerService() {
if (eventType != null) {
triggerAmbientGlance(controller, eventType, isLiked, sbn = sbn)
}

val playStateIntent = Intent("com.sameerasw.essentials.MEDIA_PLAYBACK_CHANGED").apply {
putExtra("package_name", sbn.packageName)
putExtra("is_playing", isPlaying)
setPackage(packageName)
}
sendBroadcast(playStateIntent)
}
} catch (e: Exception) {
e.printStackTrace()
Expand Down
Loading