added check for battery optimization unrestricted
This commit is contained in:
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,3 +1,4 @@
|
||||
android.useAndroidX=true
|
||||
android.enableJetifier=true
|
||||
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
|
||||
org.gradle.configuration-cache=true
|
||||
|
||||
Reference in New Issue
Block a user