added check for battery optimization unrestricted

This commit is contained in:
Acid
2026-03-24 18:50:40 -04:00
parent e99df3c979
commit b804d94fff
5 changed files with 125 additions and 11 deletions
+1
View File
@@ -36,6 +36,7 @@ android {
}
buildFeatures {
compose true
buildConfig true
}
composeOptions {
kotlinCompilerExtensionVersion '1.5.8'
@@ -6,21 +6,29 @@ import android.content.Intent
import android.content.SharedPreferences
import android.content.pm.PackageManager
import android.graphics.drawable.Drawable
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.os.PowerManager
import android.os.Process
import android.provider.Settings
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.Image
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material.icons.filled.Search
import androidx.compose.material.icons.filled.Warning
import androidx.compose.material.icons.filled.ArrowForward
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
@@ -44,18 +52,31 @@ data class AppInfo(
class MainActivity : ComponentActivity() {
private lateinit var prefs: SharedPreferences
private var showBatteryWarning by mutableStateOf(false)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
prefs = getSharedPreferences("stims_prefs", Context.MODE_PRIVATE)
setContent {
StimsTheme {
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
AppListScreen(prefs)
AppListScreen(
prefs = prefs,
showBatteryWarning = showBatteryWarning,
isSamsung = Build.MANUFACTURER.equals("samsung", ignoreCase = true),
onOpenBatterySettings = {
startActivity(
Intent(
Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
Uri.fromParts("package", packageName, null)
)
)
}
)
}
}
}
@@ -67,6 +88,9 @@ class MainActivity : ComponentActivity() {
Toast.makeText(this, "Please enable Usage Stats permission", Toast.LENGTH_LONG).show()
startActivity(Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS))
}
val pm = getSystemService(Context.POWER_SERVICE) as PowerManager
val isSamsung = Build.MANUFACTURER.equals("samsung", ignoreCase = true)
showBatteryWarning = isSamsung || !pm.isIgnoringBatteryOptimizations(packageName)
}
private fun hasUsageStatsPermission(context: Context): Boolean {
@@ -83,7 +107,12 @@ class MainActivity : ComponentActivity() {
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun AppListScreen(prefs: SharedPreferences) {
fun AppListScreen(
prefs: SharedPreferences,
showBatteryWarning: Boolean,
isSamsung: Boolean,
onOpenBatterySettings: () -> Unit,
) {
val context = LocalContext.current
val packageManager = context.packageManager
@@ -160,6 +189,9 @@ fun AppListScreen(prefs: SharedPreferences) {
}
} else {
Column(modifier = Modifier.padding(padding)) {
if (showBatteryWarning) {
BatteryWarningBanner(isSamsung = isSamsung, onClick = onOpenBatterySettings)
}
OutlinedTextField(
value = searchQuery,
onValueChange = { searchQuery = it },
@@ -295,3 +327,49 @@ fun AppIcon(icon: Drawable?) {
) {}
}
}
@Composable
fun BatteryWarningBanner(isSamsung: Boolean, onClick: () -> Unit) {
val warningColor = Color(0xFFF59E0B)
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 12.dp, vertical = 6.dp)
.border(width = 1.5.dp, color = warningColor, shape = MaterialTheme.shapes.small)
.clickable { onClick() }
.padding(horizontal = 12.dp, vertical = 10.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(
imageVector = Icons.Default.Warning,
contentDescription = null,
tint = warningColor,
modifier = Modifier.size(20.dp)
)
Spacer(modifier = Modifier.width(10.dp))
Column(modifier = Modifier.weight(1f)) {
Text(
text = "Battery optimization is active",
style = MaterialTheme.typography.labelMedium,
fontWeight = FontWeight.Bold,
color = Color(0xFF92400E)
)
Text(
text = if (isSamsung)
"Tap → App info → Battery → set to Unrestricted"
else
"Tap → App info → Battery → Unrestricted",
style = MaterialTheme.typography.bodySmall,
color = Color(0xFFB45309)
)
}
Spacer(modifier = Modifier.width(6.dp))
Icon(
imageVector = Icons.Default.ArrowForward,
contentDescription = null,
tint = warningColor,
modifier = Modifier.size(16.dp)
)
}
}
@@ -10,6 +10,7 @@ import android.os.Handler
import android.os.IBinder
import android.os.Looper
import android.os.PowerManager
import android.util.Log
import androidx.core.app.NotificationCompat
class StimsService : Service() {
@@ -18,6 +19,7 @@ class StimsService : Service() {
private var selectedPackages = mutableSetOf<String>()
private val handler = Handler(Looper.getMainLooper())
private val checkInterval = 10000L // Check every 10 seconds
private var lastForegroundPackage: String? = null
private val monitorRunnable = object : Runnable {
override fun run() {
@@ -34,17 +36,18 @@ class StimsService : Service() {
if (packages != null) {
selectedPackages = packages.toMutableSet()
if (BuildConfig.DEBUG) Log.d(TAG, "Updated stimmed packages: $selectedPackages")
updateNotification()
}
if (action == ACTION_STOP) {
if (BuildConfig.DEBUG) Log.d(TAG, "Stop action received")
stopSelf()
return START_NOT_STICKY
}
if (BuildConfig.DEBUG) Log.d(TAG, "Service starting")
startForegroundService()
handler.removeCallbacks(monitorRunnable)
handler.post(monitorRunnable)
return START_STICKY
}
@@ -52,6 +55,7 @@ class StimsService : Service() {
private fun startForegroundService() {
createNotificationChannel()
updateNotification()
handler.removeCallbacks(monitorRunnable)
handler.post(monitorRunnable)
}
@@ -68,26 +72,29 @@ class StimsService : Service() {
private fun checkForegroundApp() {
val powerManager = getSystemService(Context.POWER_SERVICE) as PowerManager
if (!powerManager.isInteractive) {
if (BuildConfig.DEBUG) Log.d(TAG, "Screen not interactive, releasing wake lock")
releaseWakeLock()
return
}
val usageStatsManager = getSystemService(Context.USAGE_STATS_SERVICE) as UsageStatsManager
val time = System.currentTimeMillis()
val events = usageStatsManager.queryEvents(time - 15000, time)
val events = usageStatsManager.queryEvents(time - checkInterval * 2, time)
val event = UsageEvents.Event()
var currentPackage: String? = null
while (events.hasNextEvent()) {
events.getNextEvent(event)
if (event.eventType == UsageEvents.Event.MOVE_TO_FOREGROUND) {
currentPackage = event.packageName
lastForegroundPackage = event.packageName
}
}
if (currentPackage != null && selectedPackages.contains(currentPackage)) {
val shouldHold = lastForegroundPackage != null && selectedPackages.contains(lastForegroundPackage)
if (BuildConfig.DEBUG) Log.d(TAG, "Poll: foreground=$lastForegroundPackage shouldHold=$shouldHold wakeLockHeld=${wakeLock?.isHeld}")
if (shouldHold) {
acquireWakeLock()
} else if (currentPackage != null) {
} else {
releaseWakeLock()
}
}
@@ -100,13 +107,16 @@ class StimsService : Service() {
PowerManager.SCREEN_BRIGHT_WAKE_LOCK or PowerManager.ACQUIRE_CAUSES_WAKEUP,
"Stims::WakeLock"
)
wakeLock?.acquire(10 * 60 * 1000L) // 10 min timeout
@Suppress("WakelockTimeout")
wakeLock?.acquire() // service lifecycle manages release
if (BuildConfig.DEBUG) Log.d(TAG, "Wake lock acquired for $lastForegroundPackage")
}
}
private fun releaseWakeLock() {
if (wakeLock?.isHeld == true) {
wakeLock?.release()
if (BuildConfig.DEBUG) Log.d(TAG, "Wake lock released")
}
wakeLock = null
}
@@ -133,5 +143,6 @@ class StimsService : Service() {
const val CHANNEL_ID = "StimsServiceChannel"
const val NOTIFICATION_ID = 1
const val ACTION_STOP = "acidburn.stims.STOP"
private const val TAG = "StimsService"
}
}
+23
View File
@@ -0,0 +1,23 @@
Categories:
- System
License: GPL-3.0-only
SourceCode: https://github.com/acidburnmonkey/stims
IssueTracker: https://github.com/acidburnmonkey/stims/issues
AutoName: Stims
RepoType: git
Repo: https://github.com/acidburnmonkey/stims
Builds:
- versionName: '1.0'
versionCode: 1
commit: v1.0
gradle:
- yes
output: app/build/outputs/apk/release/app-release-unsigned.apk
AutoUpdateMode: Version
UpdateCheckMode: Tags
CurrentVersion: '1.0'
CurrentVersionCode: 1
+1
View File
@@ -1,3 +1,4 @@
android.useAndroidX=true
android.enableJetifier=true
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
org.gradle.configuration-cache=true