diff --git a/android/.gitignore b/android/.gitignore index aa724b7..bc41352 100644 --- a/android/.gitignore +++ b/android/.gitignore @@ -7,6 +7,8 @@ /.idea/workspace.xml /.idea/navEditor.xml /.idea/assetWizardSettings.xml +/.idea/misc.xml +/.idea/junie.xml .DS_Store /build /captures diff --git a/android/.idea/misc.xml b/android/.idea/misc.xml deleted file mode 100644 index 991a888..0000000 --- a/android/.idea/misc.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index 7eec416..3150bcd 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -64,6 +64,7 @@ dependencies { // Lifecycle service implementation(libs.lifecycle.service) + implementation(libs.lifecycle.runtime.compose) // Navigation implementation(libs.navigation.compose) diff --git a/android/app/src/main/java/com/thisux/droidclaw/capture/ScreenCaptureManager.kt b/android/app/src/main/java/com/thisux/droidclaw/capture/ScreenCaptureManager.kt index b88360f..d006d42 100644 --- a/android/app/src/main/java/com/thisux/droidclaw/capture/ScreenCaptureManager.kt +++ b/android/app/src/main/java/com/thisux/droidclaw/capture/ScreenCaptureManager.kt @@ -1,9 +1,12 @@ package com.thisux.droidclaw.capture +import android.app.Activity import android.content.Context import android.content.Intent import android.graphics.Bitmap import android.graphics.PixelFormat +import androidx.core.graphics.createBitmap +import androidx.core.graphics.get import android.hardware.display.DisplayManager import android.hardware.display.VirtualDisplay import android.media.ImageReader @@ -19,16 +22,19 @@ class ScreenCaptureManager(private val context: Context) { companion object { private const val TAG = "ScreenCapture" - const val REQUEST_CODE = 1001 val isAvailable = MutableStateFlow(false) // Stores MediaProjection consent for use by ConnectionService var consentResultCode: Int? = null var consentData: Intent? = null + // Expose consent as state so UI can react immediately + val hasConsentState = MutableStateFlow(false) + fun storeConsent(resultCode: Int, data: Intent?) { consentResultCode = resultCode consentData = data + hasConsentState.value = (resultCode == Activity.RESULT_OK && data != null) } fun hasConsent(): Boolean = consentResultCode != null && consentData != null @@ -86,7 +92,7 @@ class ScreenCaptureManager(private val context: Context) { val rowStride = planes[0].rowStride val rowPadding = rowStride - pixelStride * image.width - val bitmap = Bitmap.createBitmap( + val bitmap = createBitmap( image.width + rowPadding / pixelStride, image.height, Bitmap.Config.ARGB_8888 @@ -119,7 +125,7 @@ class ScreenCaptureManager(private val context: Context) { bitmap.width - 1 to bitmap.height - 1, bitmap.width / 2 to bitmap.height / 2 ) - return points.all { (x, y) -> bitmap.getPixel(x, y) == android.graphics.Color.BLACK } + return points.all { (x, y) -> bitmap[x, y] == android.graphics.Color.BLACK } } fun release() { diff --git a/android/app/src/main/java/com/thisux/droidclaw/ui/screens/SettingsScreen.kt b/android/app/src/main/java/com/thisux/droidclaw/ui/screens/SettingsScreen.kt index b9edcb0..bd0edda 100644 --- a/android/app/src/main/java/com/thisux/droidclaw/ui/screens/SettingsScreen.kt +++ b/android/app/src/main/java/com/thisux/droidclaw/ui/screens/SettingsScreen.kt @@ -26,6 +26,7 @@ import androidx.compose.material3.OutlinedButton import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -36,8 +37,11 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext +import androidx.lifecycle.compose.LocalLifecycleOwner import androidx.compose.ui.text.input.PasswordVisualTransformation import androidx.compose.ui.unit.dp +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleEventObserver import com.thisux.droidclaw.DroidClawApp import com.thisux.droidclaw.accessibility.DroidClawAccessibilityService import com.thisux.droidclaw.capture.ScreenCaptureManager @@ -58,8 +62,21 @@ fun SettingsScreen() { val isAccessibilityEnabled by DroidClawAccessibilityService.isRunning.collectAsState() val isCaptureAvailable by ScreenCaptureManager.isAvailable.collectAsState() - val hasCaptureConsent = isCaptureAvailable || ScreenCaptureManager.hasConsent() - val isBatteryExempt = remember { BatteryOptimization.isIgnoringBatteryOptimizations(context) } + val hasConsent by ScreenCaptureManager.hasConsentState.collectAsState() + val hasCaptureConsent = isCaptureAvailable || hasConsent + + var isBatteryExempt by remember { mutableStateOf(BatteryOptimization.isIgnoringBatteryOptimizations(context)) } + + val lifecycleOwner = LocalLifecycleOwner.current + DisposableEffect(lifecycleOwner) { + val observer = LifecycleEventObserver { _, event -> + if (event == Lifecycle.Event.ON_RESUME) { + isBatteryExempt = BatteryOptimization.isIgnoringBatteryOptimizations(context) + } + } + lifecycleOwner.lifecycle.addObserver(observer) + onDispose { lifecycleOwner.lifecycle.removeObserver(observer) } + } val projectionLauncher = rememberLauncherForActivityResult( ActivityResultContracts.StartActivityForResult() diff --git a/android/gradle/libs.versions.toml b/android/gradle/libs.versions.toml index 6f42528..3295033 100644 --- a/android/gradle/libs.versions.toml +++ b/android/gradle/libs.versions.toml @@ -39,6 +39,7 @@ kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx- kotlinx-coroutines-android = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-android", version.ref = "kotlinxCoroutines" } datastore-preferences = { group = "androidx.datastore", name = "datastore-preferences", version.ref = "datastore" } lifecycle-service = { group = "androidx.lifecycle", name = "lifecycle-service", version.ref = "lifecycleService" } +lifecycle-runtime-compose = { group = "androidx.lifecycle", name = "lifecycle-runtime-compose", version.ref = "lifecycleService" } navigation-compose = { group = "androidx.navigation", name = "navigation-compose", version.ref = "navigationCompose" } compose-icons-extended = { group = "androidx.compose.material", name = "material-icons-extended", version.ref = "composeIconsExtended" }