diff --git a/apps/android/app/build.gradle.kts b/apps/android/app/build.gradle.kts
index ae9ac6eb1..832c90b72 100644
--- a/apps/android/app/build.gradle.kts
+++ b/apps/android/app/build.gradle.kts
@@ -47,6 +47,10 @@ android {
excludes += "/META-INF/{AL2.0,LGPL2.1}"
}
}
+
+ lint {
+ disable += setOf("IconLauncherShape")
+ }
}
dependencies {
@@ -56,7 +60,7 @@ dependencies {
implementation("androidx.core:core-ktx:1.17.0")
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.10.0")
- implementation("androidx.activity:activity-compose:1.12.1")
+ implementation("androidx.activity:activity-compose:1.12.2")
implementation("androidx.compose.ui:ui")
implementation("androidx.compose.ui:ui-tooling-preview")
diff --git a/apps/android/app/src/main/AndroidManifest.xml b/apps/android/app/src/main/AndroidManifest.xml
index e8547b4eb..93794dd08 100644
--- a/apps/android/app/src/main/AndroidManifest.xml
+++ b/apps/android/app/src/main/AndroidManifest.xml
@@ -12,12 +12,20 @@
+
+
= Build.VERSION_CODES.M) {
- PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
- } else {
- PendingIntent.FLAG_UPDATE_CURRENT
- }
+ val flags = PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
val stopPending = PendingIntent.getService(this, 2, stopIntent, flags)
return NotificationCompat.Builder(this, CHANNEL_ID)
@@ -126,11 +119,6 @@ class NodeForegroundService : Service() {
}
private fun startForegroundWithTypes(notification: Notification, requiresMic: Boolean) {
- if (Build.VERSION.SDK_INT < 29) {
- startForeground(NOTIFICATION_ID, notification)
- return
- }
-
if (didStartForeground && requiresMic == lastRequiresMic) {
updateNotification(notification)
return
@@ -162,11 +150,7 @@ class NodeForegroundService : Service() {
fun start(context: Context) {
val intent = Intent(context, NodeForegroundService::class.java)
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
- context.startForegroundService(intent)
- } else {
- context.startService(intent)
- }
+ context.startForegroundService(intent)
}
fun stop(context: Context) {
diff --git a/apps/android/app/src/main/java/com/steipete/clawdis/node/SecurePrefs.kt b/apps/android/app/src/main/java/com/steipete/clawdis/node/SecurePrefs.kt
index 51545095f..a0be15e13 100644
--- a/apps/android/app/src/main/java/com/steipete/clawdis/node/SecurePrefs.kt
+++ b/apps/android/app/src/main/java/com/steipete/clawdis/node/SecurePrefs.kt
@@ -3,6 +3,7 @@
package com.steipete.clawdis.node
import android.content.Context
+import androidx.core.content.edit
import androidx.security.crypto.EncryptedSharedPreferences
import androidx.security.crypto.MasterKey
import kotlinx.coroutines.flow.MutableStateFlow
@@ -70,39 +71,39 @@ class SecurePrefs(context: Context) {
fun setLastDiscoveredStableId(value: String) {
val trimmed = value.trim()
- prefs.edit().putString("bridge.lastDiscoveredStableId", trimmed).apply()
+ prefs.edit { putString("bridge.lastDiscoveredStableId", trimmed) }
_lastDiscoveredStableId.value = trimmed
}
fun setDisplayName(value: String) {
val trimmed = value.trim()
- prefs.edit().putString(displayNameKey, trimmed).apply()
+ prefs.edit { putString(displayNameKey, trimmed) }
_displayName.value = trimmed
}
fun setCameraEnabled(value: Boolean) {
- prefs.edit().putBoolean("camera.enabled", value).apply()
+ prefs.edit { putBoolean("camera.enabled", value) }
_cameraEnabled.value = value
}
fun setPreventSleep(value: Boolean) {
- prefs.edit().putBoolean("screen.preventSleep", value).apply()
+ prefs.edit { putBoolean("screen.preventSleep", value) }
_preventSleep.value = value
}
fun setManualEnabled(value: Boolean) {
- prefs.edit().putBoolean("bridge.manual.enabled", value).apply()
+ prefs.edit { putBoolean("bridge.manual.enabled", value) }
_manualEnabled.value = value
}
fun setManualHost(value: String) {
val trimmed = value.trim()
- prefs.edit().putString("bridge.manual.host", trimmed).apply()
+ prefs.edit { putString("bridge.manual.host", trimmed) }
_manualHost.value = trimmed
}
fun setManualPort(value: Int) {
- prefs.edit().putInt("bridge.manual.port", value).apply()
+ prefs.edit { putInt("bridge.manual.port", value) }
_manualPort.value = value
}
@@ -113,14 +114,14 @@ class SecurePrefs(context: Context) {
fun saveBridgeToken(token: String) {
val key = "bridge.token.${_instanceId.value}"
- prefs.edit().putString(key, token.trim()).apply()
+ prefs.edit { putString(key, token.trim()) }
}
private fun loadOrCreateInstanceId(): String {
val existing = prefs.getString("node.instanceId", null)?.trim()
if (!existing.isNullOrBlank()) return existing
val fresh = UUID.randomUUID().toString()
- prefs.edit().putString("node.instanceId", fresh).apply()
+ prefs.edit { putString("node.instanceId", fresh) }
return fresh
}
@@ -131,7 +132,7 @@ class SecurePrefs(context: Context) {
val candidate = DeviceNames.bestDefaultNodeName(context).trim()
val resolved = candidate.ifEmpty { "Android Node" }
- prefs.edit().putString(displayNameKey, resolved).apply()
+ prefs.edit { putString(displayNameKey, resolved) }
return resolved
}
@@ -139,12 +140,12 @@ class SecurePrefs(context: Context) {
val sanitized = WakeWords.sanitize(words, defaultWakeWords)
val encoded =
JsonArray(sanitized.map { JsonPrimitive(it) }).toString()
- prefs.edit().putString("voiceWake.triggerWords", encoded).apply()
+ prefs.edit { putString("voiceWake.triggerWords", encoded) }
_wakeWords.value = sanitized
}
fun setVoiceWakeMode(mode: VoiceWakeMode) {
- prefs.edit().putString(voiceWakeModeKey, mode.rawValue).apply()
+ prefs.edit { putString(voiceWakeModeKey, mode.rawValue) }
_voiceWakeMode.value = mode
}
@@ -154,7 +155,7 @@ class SecurePrefs(context: Context) {
// Default ON (foreground) when unset.
if (raw.isNullOrBlank()) {
- prefs.edit().putString(voiceWakeModeKey, resolved.rawValue).apply()
+ prefs.edit { putString(voiceWakeModeKey, resolved.rawValue) }
}
return resolved
diff --git a/apps/android/app/src/main/java/com/steipete/clawdis/node/bridge/BridgeDiscovery.kt b/apps/android/app/src/main/java/com/steipete/clawdis/node/bridge/BridgeDiscovery.kt
index ca9c5ef39..a0e8032bc 100644
--- a/apps/android/app/src/main/java/com/steipete/clawdis/node/bridge/BridgeDiscovery.kt
+++ b/apps/android/app/src/main/java/com/steipete/clawdis/node/bridge/BridgeDiscovery.kt
@@ -6,7 +6,6 @@ import android.net.DnsResolver
import android.net.NetworkCapabilities
import android.net.nsd.NsdManager
import android.net.nsd.NsdServiceInfo
-import android.os.Build
import android.os.CancellationSignal
import android.util.Log
import java.io.IOException
@@ -181,7 +180,6 @@ class BridgeDiscovery(
}
private fun txt(info: NsdServiceInfo, key: String): String? {
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) return null
val bytes = info.attributes[key] ?: return null
return try {
String(bytes, Charsets.UTF_8).trim().ifEmpty { null }
@@ -401,7 +399,7 @@ class BridgeDiscovery(
dns.rawQuery(
network,
wireQuery,
- 0,
+ DnsResolver.FLAG_EMPTY,
dnsExecutor,
signal,
object : DnsResolver.Callback {
diff --git a/apps/android/app/src/main/java/com/steipete/clawdis/node/node/CameraCaptureManager.kt b/apps/android/app/src/main/java/com/steipete/clawdis/node/node/CameraCaptureManager.kt
index b3a11b2bf..4f1501340 100644
--- a/apps/android/app/src/main/java/com/steipete/clawdis/node/node/CameraCaptureManager.kt
+++ b/apps/android/app/src/main/java/com/steipete/clawdis/node/node/CameraCaptureManager.kt
@@ -2,6 +2,7 @@ package com.steipete.clawdis.node.node
import android.Manifest
import android.content.Context
+import android.annotation.SuppressLint
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.util.Base64
@@ -18,6 +19,7 @@ import androidx.camera.video.VideoCapture
import androidx.camera.video.VideoRecordEvent
import androidx.core.content.ContextCompat
import androidx.core.content.ContextCompat.checkSelfPermission
+import androidx.core.graphics.scale
import com.steipete.clawdis.node.PermissionRequester
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.suspendCancellableCoroutine
@@ -92,7 +94,7 @@ class CameraCaptureManager(private val context: Context) {
(decoded.height.toDouble() * (maxWidth.toDouble() / decoded.width.toDouble()))
.toInt()
.coerceAtLeast(1)
- Bitmap.createScaledBitmap(decoded, maxWidth, h, true)
+ decoded.scale(maxWidth, h)
} else {
decoded
}
@@ -108,6 +110,7 @@ class CameraCaptureManager(private val context: Context) {
)
}
+ @SuppressLint("MissingPermission")
suspend fun clip(paramsJson: String?): Payload =
withContext(Dispatchers.Main) {
ensureCameraPermission()
diff --git a/apps/android/app/src/main/java/com/steipete/clawdis/node/node/CanvasController.kt b/apps/android/app/src/main/java/com/steipete/clawdis/node/node/CanvasController.kt
index b0f4d4b25..5c3e90d69 100644
--- a/apps/android/app/src/main/java/com/steipete/clawdis/node/node/CanvasController.kt
+++ b/apps/android/app/src/main/java/com/steipete/clawdis/node/node/CanvasController.kt
@@ -1,10 +1,11 @@
package com.steipete.clawdis.node.node
import android.graphics.Bitmap
-import android.os.Build
import android.graphics.Canvas
import android.os.Looper
import android.webkit.WebView
+import androidx.core.graphics.createBitmap
+import androidx.core.graphics.scale
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.withContext
@@ -80,7 +81,7 @@ class CanvasController {
val scaled =
if (maxWidth != null && maxWidth > 0 && bmp.width > maxWidth) {
val h = (bmp.height.toDouble() * (maxWidth.toDouble() / bmp.width.toDouble())).toInt().coerceAtLeast(1)
- Bitmap.createScaledBitmap(bmp, maxWidth, h, true)
+ bmp.scale(maxWidth, h)
} else {
bmp
}
@@ -97,7 +98,7 @@ class CanvasController {
val scaled =
if (maxWidth != null && maxWidth > 0 && bmp.width > maxWidth) {
val h = (bmp.height.toDouble() * (maxWidth.toDouble() / bmp.width.toDouble())).toInt().coerceAtLeast(1)
- Bitmap.createScaledBitmap(bmp, maxWidth, h, true)
+ bmp.scale(maxWidth, h)
} else {
bmp
}
@@ -116,7 +117,7 @@ class CanvasController {
suspendCancellableCoroutine { cont ->
val width = width.coerceAtLeast(1)
val height = height.coerceAtLeast(1)
- val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
+ val bitmap = createBitmap(width, height, Bitmap.Config.ARGB_8888)
// WebView isn't supported by PixelCopy.request(...) directly; draw() is the most reliable
// cross-version snapshot for this lightweight "canvas" use-case.
diff --git a/apps/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/apps/android/app/src/main/res/mipmap-anydpi/ic_launcher.xml
similarity index 79%
rename from apps/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
rename to apps/android/app/src/main/res/mipmap-anydpi/ic_launcher.xml
index a65c12916..6f379984a 100644
--- a/apps/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
+++ b/apps/android/app/src/main/res/mipmap-anydpi/ic_launcher.xml
@@ -2,5 +2,5 @@
+
-
diff --git a/apps/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/apps/android/app/src/main/res/mipmap-anydpi/ic_launcher_round.xml
similarity index 79%
rename from apps/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
rename to apps/android/app/src/main/res/mipmap-anydpi/ic_launcher_round.xml
index a65c12916..6f379984a 100644
--- a/apps/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
+++ b/apps/android/app/src/main/res/mipmap-anydpi/ic_launcher_round.xml
@@ -2,5 +2,5 @@
+
-
diff --git a/apps/android/app/src/main/res/values/themes.xml b/apps/android/app/src/main/res/values/themes.xml
index 86d2e2f3b..881dbe537 100644
--- a/apps/android/app/src/main/res/values/themes.xml
+++ b/apps/android/app/src/main/res/values/themes.xml
@@ -1,8 +1,7 @@
-
+
-
diff --git a/apps/android/app/src/main/res/xml/backup_rules.xml b/apps/android/app/src/main/res/xml/backup_rules.xml
new file mode 100644
index 000000000..21e592ca4
--- /dev/null
+++ b/apps/android/app/src/main/res/xml/backup_rules.xml
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/apps/android/app/src/main/res/xml/data_extraction_rules.xml b/apps/android/app/src/main/res/xml/data_extraction_rules.xml
new file mode 100644
index 000000000..46e58c54e
--- /dev/null
+++ b/apps/android/app/src/main/res/xml/data_extraction_rules.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/apps/android/app/src/main/res/xml/network_security_config.xml b/apps/android/app/src/main/res/xml/network_security_config.xml
index 68016a00b..136225f61 100644
--- a/apps/android/app/src/main/res/xml/network_security_config.xml
+++ b/apps/android/app/src/main/res/xml/network_security_config.xml
@@ -1,7 +1,7 @@
-
+
-
+
clawdis.internal