diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts
index ece8d12ab3..a21275bd52 100644
--- a/android/app/build.gradle.kts
+++ b/android/app/build.gradle.kts
@@ -7,9 +7,9 @@ plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
alias(libs.plugins.kotlin.compose)
+ alias(libs.plugins.kotlin.serialization)
alias(libs.plugins.spotless)
alias(libs.plugins.detekt)
- id("org.jetbrains.kotlin.plugin.serialization") version "2.2.20"
id("jacoco")
}
@@ -26,6 +26,8 @@ android {
versionCode = 2
versionName = "1.0.0"
+ buildConfigField("String", "INFO_URL", "\"https://fleetdm.com/better\"")
+
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
// Pass integration test flag from project property to instrumentation runner
@@ -110,6 +112,7 @@ android {
}
buildFeatures {
compose = true
+ buildConfig = true
}
packaging {
resources {
@@ -143,6 +146,7 @@ kotlin {
jvmToolchain(17)
compilerOptions {
jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_11)
+ freeCompilerArgs.add("-opt-in=androidx.compose.material3.ExperimentalMaterial3Api")
}
}
@@ -238,39 +242,40 @@ dependencies {
implementation(libs.androidx.compose.ui.graphics)
implementation(libs.androidx.compose.ui.tooling.preview)
implementation(libs.androidx.compose.material3)
+ implementation(libs.androidx.compose.material.icons.extended)
+ implementation(libs.androidx.datastore.preferences)
+ implementation(libs.androidx.navigation.compose)
implementation(libs.androidx.work.runtime.ktx)
implementation(libs.amapi.sdk)
+ implementation(libs.kotlinx.serialization.json)
// SCEP (Simple Certificate Enrollment Protocol)
- implementation("com.google.code.jscep:jscep:3.0.1")
+ implementation(libs.jscep)
// Bouncy Castle - Cryptography provider
- implementation("org.bouncycastle:bcprov-jdk18on:1.78.1")
- implementation("org.bouncycastle:bcpkix-jdk18on:1.78.1")
+ implementation(libs.bouncycastle.bcprov)
+ implementation(libs.bouncycastle.bcpkix)
// Apache Commons - Utilities used by jScep
- implementation("commons-codec:commons-codec:1.20.0")
+ implementation(libs.commons.codec)
// Logging - Required by jScep
- implementation("org.slf4j:slf4j-api:2.0.17")
- implementation("org.slf4j:slf4j-simple:2.0.17")
+ implementation(libs.slf4j.api)
+ implementation(libs.slf4j.simple)
// Testing
testImplementation(libs.junit)
testImplementation(libs.androidx.work.testing)
testImplementation(libs.robolectric)
- testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.9.0")
- testImplementation("org.json:json:20231013") // For JSON parsing in unit tests
+ testImplementation(libs.kotlinx.coroutines.test)
+ testImplementation(libs.json) // For JSON parsing in unit tests
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
androidTestImplementation(platform(libs.androidx.compose.bom))
androidTestImplementation(libs.androidx.compose.ui.test.junit4)
- androidTestImplementation("io.mockk:mockk-android:1.13.13")
+ androidTestImplementation(libs.mockk.android)
debugImplementation(libs.androidx.compose.ui.tooling)
debugImplementation(libs.androidx.compose.ui.test.manifest)
- // Preferences DataStore (SharedPreferences like APIs)
- implementation("androidx.datastore:datastore-preferences:1.2.0")
- implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.9.0")
}
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 2dbd16ac2e..8dff251891 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -6,6 +6,10 @@
+
+
+
+
@@ -61,4 +65,4 @@
-
\ No newline at end of file
+
diff --git a/android/app/src/main/java/com/fleetdm/agent/ApiClient.kt b/android/app/src/main/java/com/fleetdm/agent/ApiClient.kt
index 34fe245f46..21a387ce3c 100644
--- a/android/app/src/main/java/com/fleetdm/agent/ApiClient.kt
+++ b/android/app/src/main/java/com/fleetdm/agent/ApiClient.kt
@@ -56,11 +56,11 @@ object ApiClient {
}
}
- val apiKeyFlow: Flow
+ val apiKeyDebugFlow: Flow
get() = dataStore.data.map { preferences ->
preferences[API_KEY]?.let { encrypted ->
try {
- KeystoreManager.decrypt(encrypted)
+ "****" + KeystoreManager.decrypt(encrypted).takeLast(4)
} catch (e: Exception) {
Log.e("ApiClient", "Failed to decrypt API key", e)
null
diff --git a/android/app/src/main/java/com/fleetdm/agent/MainActivity.kt b/android/app/src/main/java/com/fleetdm/agent/MainActivity.kt
index f3e12ae455..2d548ee4ce 100644
--- a/android/app/src/main/java/com/fleetdm/agent/MainActivity.kt
+++ b/android/app/src/main/java/com/fleetdm/agent/MainActivity.kt
@@ -1,131 +1,231 @@
+@file:OptIn(ExperimentalMaterial3Api::class)
+
package com.fleetdm.agent
import android.app.admin.DevicePolicyManager
+import android.content.ClipData
+import android.content.ClipboardManager
import android.content.Context
+import android.content.Context.DEVICE_POLICY_SERVICE
+import android.content.Context.RESTRICTIONS_SERVICE
+import android.content.Intent
+import android.content.RestrictionsManager
import android.content.pm.PackageInfo
import android.content.pm.PackageManager
-import android.os.Build
+import android.graphics.Color
import android.os.Bundle
-import android.provider.Settings
-import android.util.Log
+import android.widget.Toast
import androidx.activity.ComponentActivity
+import androidx.activity.SystemBarStyle
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
-import androidx.compose.material3.Button
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.automirrored.filled.ArrowBack
+import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.HorizontalDivider
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
+import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
-import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.withStyle
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
-import androidx.datastore.preferences.core.stringPreferencesKey
+import androidx.core.net.toUri
+import androidx.navigation.compose.NavHost
+import androidx.navigation.compose.composable
+import androidx.navigation.compose.rememberNavController
+import com.fleetdm.agent.ui.theme.FleetTextDark
import com.fleetdm.agent.ui.theme.MyApplicationTheme
-import java.security.KeyStore
-import java.security.cert.X509Certificate
-import java.util.Date
-import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.withContext
+import kotlinx.serialization.Serializable
+
+const val CLICKS_TO_DEBUG = 8
+
+@Serializable
+object MainDestination
+
+@Serializable
+object DebugDestination
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- enableEdgeToEdge()
-
- // 1. Fetch the Managed Configuration (Application Restrictions)
- val restrictionsManager = getSystemService(Context.RESTRICTIONS_SERVICE) as android.content.RestrictionsManager
- val appRestrictions = restrictionsManager.applicationRestrictions
- val dpm = getSystemService(Context.DEVICE_POLICY_SERVICE) as DevicePolicyManager
+ enableEdgeToEdge(
+ statusBarStyle = SystemBarStyle.light(
+ scrim = Color.TRANSPARENT,
+ darkScrim = Color.TRANSPARENT,
+ ),
+ )
setContent {
- val enrollSecret by remember { mutableStateOf(appRestrictions.getString("enroll_secret")) }
- val delegatedScopes by remember { mutableStateOf(dpm.getDelegatedScopes(null, packageName).toList()) }
- val delegatedCertScope by remember {
- mutableStateOf(delegatedScopes.contains(DevicePolicyManager.DELEGATION_CERT_INSTALL))
- }
- val androidID by remember { mutableStateOf(Settings.Secure.getString(contentResolver, Settings.Secure.ANDROID_ID)) }
- val enrollmentSpecificID by remember { mutableStateOf(appRestrictions.getString("host_uuid")) }
- val certIds by remember { mutableStateOf(CertificateOrchestrator.getCertificateIDs(this)) }
- val permissionsList by remember {
- val grantedPermissions = mutableListOf()
- val packageInfo: PackageInfo = packageManager.getPackageInfo(packageName, PackageManager.GET_PERMISSIONS)
- packageInfo.requestedPermissions?.let {
- for (i in it.indices) {
- if ((
- packageInfo.requestedPermissionsFlags?.get(i)
- ?.and(PackageInfo.REQUESTED_PERMISSION_GRANTED)
- ) != 0
- ) {
- grantedPermissions.add(it[i])
- }
- }
- }
- mutableStateOf(grantedPermissions.toList())
- }
- val fleetBaseUrl by remember {
- mutableStateOf(appRestrictions.getString("server_url"))
- }
- var installedCertificates: List by remember { mutableStateOf(listOf()) }
- val apiKey by ApiClient.apiKeyFlow.collectAsState(initial = null)
- val baseUrl by ApiClient.baseUrlFlow.collectAsState(initial = null)
- val allegedInstalledCerts by CertificateOrchestrator.installedCertsFlow(this).collectAsState(initial = "")
-
- LaunchedEffect(Unit) {
- installedCertificates = listKeystoreCertificates()
- }
-
MyApplicationTheme {
- Scaffold(
- modifier = Modifier.fillMaxSize(),
- content = { padding ->
- Column(
- modifier = Modifier.padding(padding).verticalScroll(rememberScrollState()),
- ) {
- StatusScreen()
- KeyValue("packageName", packageName)
- KeyValue("versionName", packageManager.getPackageInfo(packageName, 0).versionName)
- KeyValue("longVersionCode", packageManager.getPackageInfo(packageName, 0).longVersionCode.toString())
- KeyValue("enroll_secret", enrollSecret)
- KeyValue("delegatedScopes", delegatedScopes.toString())
- KeyValue("delegated cert scope", delegatedCertScope.toString())
- KeyValue("android id", androidID)
- KeyValue("host_uuid (MC)", enrollmentSpecificID)
- KeyValue("server_url (MC)", fleetBaseUrl)
- KeyValue("orbit_node_key (datastore)", apiKey)
- KeyValue("base_url (datastore)", baseUrl)
- KeyValue("certificate_templates->id", certIds.toString())
- KeyValue("alleged_installed", allegedInstalledCerts.toString())
- PermissionList(
- permissionsList = permissionsList,
- )
- CertificateList(certificateList = installedCertificates)
- }
- },
- )
+ AppNavigation()
}
}
}
}
+@Composable
+fun AppNavigation() {
+ val navController = rememberNavController()
+
+ NavHost(
+ navController = navController,
+ startDestination = MainDestination,
+ ) {
+ composable {
+ MainScreen(
+ onNavigateToDebug = { navController.navigate(DebugDestination) },
+ )
+ }
+
+ composable {
+ DebugScreen(
+ onNavigateBack = { navController.popBackStack() },
+ )
+ }
+ }
+}
+
+@Composable
+fun MainScreen(onNavigateToDebug: () -> Unit) {
+ val context = LocalContext.current
+
+ var versionClicks by remember { mutableStateOf(0) }
+ val installedCerts by CertificateOrchestrator.installedCertsFlow(context).collectAsState(initial = emptyMap())
+
+ Scaffold(
+ modifier = Modifier.fillMaxSize(),
+ content = { paddingValues ->
+ Column(Modifier.padding(paddingValues = paddingValues)) {
+ LogoHeader()
+ HorizontalDivider()
+ AboutFleet {
+ val intent = Intent(Intent.ACTION_VIEW, BuildConfig.INFO_URL.toUri())
+ if (intent.resolveActivity(context.packageManager) != null) {
+ // A browser is available, open the URL directly
+ context.startActivity(intent)
+ } else {
+ // A browser is not available in the work profile, display a toast with the URL
+ val toast = Toast.makeText(context, "Visit ${BuildConfig.INFO_URL}\nfor more information", Toast.LENGTH_LONG)
+ toast.show()
+ }
+ }
+ HorizontalDivider()
+ CertificateList(certificates = installedCerts)
+ HorizontalDivider()
+ AppVersion {
+ if (++versionClicks >= CLICKS_TO_DEBUG) {
+ onNavigateToDebug()
+ } else if (versionClicks == 1) {
+ val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
+ clipboard.setPrimaryClip(ClipData.newPlainText("", "Fleet Android Agent: ${BuildConfig.VERSION_NAME}"))
+ Toast.makeText(context, "Fleet Agent version copied", Toast.LENGTH_SHORT).show()
+ }
+ }
+ }
+ },
+ )
+}
+
+@Composable
+fun DebugScreen(onNavigateBack: () -> Unit) {
+ val context = LocalContext.current
+
+ val restrictionsManager = context.getSystemService(RESTRICTIONS_SERVICE) as RestrictionsManager
+ val appRestrictions = restrictionsManager.applicationRestrictions
+ val dpm = context.getSystemService(DEVICE_POLICY_SERVICE) as DevicePolicyManager
+
+ val enrollSecret = remember { appRestrictions.getString("enroll_secret")?.let { "****" + it.takeLast(4) } }
+ val delegatedScopes = remember { dpm.getDelegatedScopes(null, context.packageName).toList() }
+ val delegatedCertScope = remember { delegatedScopes.contains(DevicePolicyManager.DELEGATION_CERT_INSTALL) }
+ val enrollmentSpecificID = remember { appRestrictions.getString("host_uuid")?.let { "****" + it.takeLast(4) } }
+ val certIds = remember { CertificateOrchestrator.getCertificateIDs(context) }
+ val permissionsList = remember {
+ val grantedPermissions = mutableListOf()
+ val packageInfo: PackageInfo = context.packageManager.getPackageInfo(context.packageName, PackageManager.GET_PERMISSIONS)
+ packageInfo.requestedPermissions?.let {
+ for (i in it.indices) {
+ if ((
+ packageInfo.requestedPermissionsFlags?.get(i)
+ ?.and(PackageInfo.REQUESTED_PERMISSION_GRANTED)
+ ) != 0
+ ) {
+ grantedPermissions.add(it[i])
+ }
+ }
+ }
+ grantedPermissions.toList()
+ }
+ val fleetBaseUrl = remember { appRestrictions.getString("server_url") }
+ val apiKey by ApiClient.apiKeyDebugFlow.collectAsState(initial = null)
+ val baseUrl by ApiClient.baseUrlFlow.collectAsState(initial = null)
+ val installedCerts by CertificateOrchestrator.installedCertsFlow(context).collectAsState(initial = emptyMap())
+
+ Scaffold(
+ modifier = Modifier.fillMaxSize(),
+ topBar = {
+ TopAppBar(
+ title = { Text("Debug Information") },
+ navigationIcon = {
+ IconButton(onClick = onNavigateBack) {
+ Icon(
+ imageVector = Icons.AutoMirrored.Filled.ArrowBack,
+ contentDescription = "Back",
+ )
+ }
+ },
+ )
+ },
+ content = { paddingValues ->
+ Column(
+ Modifier
+ .padding(paddingValues = paddingValues)
+ .verticalScroll(rememberScrollState()),
+ ) {
+ KeyValue("packageName", context.packageName)
+ KeyValue("versionName", context.packageManager.getPackageInfo(context.packageName, 0).versionName)
+ KeyValue("longVersionCode", context.packageManager.getPackageInfo(context.packageName, 0).longVersionCode.toString())
+ KeyValue("enroll_secret", enrollSecret)
+ KeyValue("delegatedScopes", delegatedScopes.toString())
+ KeyValue("delegated cert scope", delegatedCertScope.toString())
+ KeyValue("host_uuid (MC)", enrollmentSpecificID)
+ KeyValue("server_url (MC)", fleetBaseUrl)
+ KeyValue("orbit_node_key (datastore)", apiKey)
+ KeyValue("base_url (datastore)", baseUrl)
+ KeyValue("certificate_templates->id", certIds.toString())
+ KeyValue("certs_installed", installedCerts.toString())
+ PermissionList(
+ permissionsList = permissionsList,
+ )
+ }
+ },
+ )
+}
+
@Composable
fun PermissionList(modifier: Modifier = Modifier, permissionsList: List) {
Column(modifier = modifier) {
@@ -139,37 +239,6 @@ fun PermissionList(modifier: Modifier = Modifier, permissionsList: List)
}
}
-@Composable
-fun CertificateList(modifier: Modifier = Modifier, certificateList: List) {
- Column(modifier = modifier) {
- Text("certificate list:")
- HorizontalDivider()
- certificateList.forEach {
- Text(it.alias)
- HorizontalDivider()
- }
- }
-}
-
-@Composable
-fun StatusScreen(modifier: Modifier = Modifier) {
- val tag = "CertStatusScreen"
- Log.i(tag, "this is a log!")
-
- Greeting(
- name = "banana frog",
- modifier = modifier,
- )
-}
-
-@Composable
-fun Greeting(name: String, modifier: Modifier = Modifier) {
- Text(
- text = "Hello $name!",
- modifier = modifier,
- )
-}
-
@Composable
fun KeyValue(key: String, value: String?) {
Text(
@@ -183,37 +252,94 @@ fun KeyValue(key: String, value: String?) {
HorizontalDivider()
}
-suspend fun listKeystoreCertificates(): List = withContext(Dispatchers.IO) {
- try {
- val keyStore = KeyStore.getInstance("AndroidKeyStore")
- keyStore.load(null)
-
- val aliases = keyStore.aliases().toList()
-
- aliases.mapNotNull { alias ->
- val cert = keyStore.getCertificate(alias) as? X509Certificate
- cert?.let {
- CertificateInfo(
- alias = alias,
- subject = it.subjectDN.name,
- issuer = it.issuerDN.name,
- notBefore = it.notBefore,
- notAfter = it.notAfter,
- )
- }
- }
- } catch (e: Exception) {
- Log.e("Certificate", "Error listing keystore certificates", e)
- emptyList()
+@Composable
+fun AboutFleet(modifier: Modifier = Modifier, onLearnClick: () -> Unit = {}) {
+ Column(modifier = modifier.padding(20.dp)) {
+ Text(
+ text = stringResource(R.string.app_description),
+ )
+ Text(
+ text = stringResource(R.string.learn_about_fleet),
+ fontWeight = FontWeight.Bold,
+ color = FleetTextDark,
+ modifier = Modifier
+ .padding(top = 10.dp)
+ .clickable(onClick = onLearnClick),
+ )
}
}
-data class CertificateInfo(val alias: String, val subject: String, val issuer: String, val notBefore: Date, val notAfter: Date)
+@Composable
+fun LogoHeader(modifier: Modifier = Modifier) {
+ Image(
+ modifier = modifier.padding(20.dp),
+ painter = painterResource(R.drawable.fleet_logo),
+ contentDescription = stringResource(R.string.fleet_logo),
+ )
+}
+
+@Composable
+fun CertificateList(modifier: Modifier = Modifier, certificates: CertStatusMap) {
+ Column(modifier = modifier.padding(20.dp)) {
+ Text(
+ text = stringResource(R.string.certificate_list_title),
+ color = FleetTextDark,
+ fontWeight = FontWeight.Bold,
+ )
+ certificates.forEach { (_, value) ->
+ if (value.status == CertificateInstallStatus.INSTALLED) {
+ Text(text = value.alias)
+ }
+ }
+ }
+}
+
+@Composable
+fun AppVersion(onClick: () -> Unit = {}) {
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .clickable(onClick = onClick),
+ ) {
+ Column(
+ modifier = Modifier
+ .padding(20.dp),
+ ) {
+ Text(
+ text = stringResource(R.string.app_version_title),
+ color = FleetTextDark,
+ fontWeight = FontWeight.Bold,
+ )
+ Text(text = BuildConfig.VERSION_NAME)
+ }
+ }
+}
@Preview(showBackground = true)
@Composable
-fun GreetingPreview() {
+fun FleetScreenPreview() {
MyApplicationTheme {
- Greeting("Android")
+ Column {
+ LogoHeader()
+ HorizontalDivider()
+ AboutFleet()
+ HorizontalDivider()
+ CertificateList(
+ certificates = mapOf(
+ 1 to CertificateInstallInfo(alias = "WIFI-1", status = CertificateInstallStatus.INSTALLED),
+ 2 to CertificateInstallInfo(alias = "VPN-3", status = CertificateInstallStatus.FAILED),
+ ),
+ )
+ HorizontalDivider()
+ AppVersion(onClick = {})
+ }
+ }
+}
+
+@Preview(showBackground = true)
+@Composable
+fun AboutFleetPreview() {
+ MyApplicationTheme {
+ AboutFleet()
}
}
diff --git a/android/app/src/main/java/com/fleetdm/agent/ui/theme/Color.kt b/android/app/src/main/java/com/fleetdm/agent/ui/theme/Color.kt
index c3193eed0b..5a0bdb1762 100644
--- a/android/app/src/main/java/com/fleetdm/agent/ui/theme/Color.kt
+++ b/android/app/src/main/java/com/fleetdm/agent/ui/theme/Color.kt
@@ -9,3 +9,6 @@ val Pink80 = Color(0xFFEFB8C8)
val Purple40 = Color(0xFF6650a4)
val PurpleGrey40 = Color(0xFF625b71)
val Pink40 = Color(0xFF7D5260)
+
+val FleetTextLight = Color(0xFF515774)
+val FleetTextDark = Color(0xFF192147)
diff --git a/android/app/src/main/java/com/fleetdm/agent/ui/theme/Theme.kt b/android/app/src/main/java/com/fleetdm/agent/ui/theme/Theme.kt
index 2def5d8cac..e2e8733546 100644
--- a/android/app/src/main/java/com/fleetdm/agent/ui/theme/Theme.kt
+++ b/android/app/src/main/java/com/fleetdm/agent/ui/theme/Theme.kt
@@ -11,11 +11,11 @@ import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext
-private val DarkColorScheme = darkColorScheme(
- primary = Purple80,
- secondary = PurpleGrey80,
- tertiary = Pink80,
-)
+// private val DarkColorScheme = darkColorScheme(
+// primary = Purple80,
+// secondary = PurpleGrey80,
+// tertiary = Pink80,
+// )
private val LightColorScheme = lightColorScheme(
primary = Purple40,
@@ -35,20 +35,21 @@ private val LightColorScheme = lightColorScheme(
@Composable
fun MyApplicationTheme(
- darkTheme: Boolean = isSystemInDarkTheme(),
+// darkTheme: Boolean = isSystemInDarkTheme(),
// Dynamic color is available on Android 12+
- dynamicColor: Boolean = true,
+// dynamicColor: Boolean = true,
content: @Composable () -> Unit,
) {
- val colorScheme = when {
- dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
- val context = LocalContext.current
- if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
- }
-
- darkTheme -> DarkColorScheme
- else -> LightColorScheme
- }
+ val colorScheme = LightColorScheme
+// val colorScheme = when {
+// dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
+// val context = LocalContext.current
+// if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
+// }
+//
+// darkTheme -> DarkColorScheme
+// else -> LightColorScheme
+// }
MaterialTheme(
colorScheme = colorScheme,
diff --git a/android/app/src/main/java/com/fleetdm/agent/ui/theme/Type.kt b/android/app/src/main/java/com/fleetdm/agent/ui/theme/Type.kt
index 9abb71aa7e..eb743e25ff 100644
--- a/android/app/src/main/java/com/fleetdm/agent/ui/theme/Type.kt
+++ b/android/app/src/main/java/com/fleetdm/agent/ui/theme/Type.kt
@@ -16,6 +16,7 @@ val Typography =
fontSize = 16.sp,
lineHeight = 24.sp,
letterSpacing = 0.5.sp,
+ color = FleetTextLight,
),
/* Other default text styles to override
titleLarge = TextStyle(
diff --git a/android/app/src/main/res/drawable-nodpi/fleet_logo.png b/android/app/src/main/res/drawable-nodpi/fleet_logo.png
new file mode 100644
index 0000000000..b673d8d28c
Binary files /dev/null and b/android/app/src/main/res/drawable-nodpi/fleet_logo.png differ
diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml
index 8c53ba4661..4d97669cb2 100644
--- a/android/app/src/main/res/values/strings.xml
+++ b/android/app/src/main/res/values/strings.xml
@@ -1,6 +1,13 @@
Fleet Agent
+ The app is installed by your IT administrator to install the certificates needed to connect to corporate networks.
+ Learn about Fleet
+ Fleet logo
+ Installed Certificates
+ App Version
+
+
Enroll Secret
Secret used to enroll in a fleet instance
Fleet Base URL
diff --git a/android/gradle/libs.versions.toml b/android/gradle/libs.versions.toml
index 3ca78a91ab..da6ac18ad0 100644
--- a/android/gradle/libs.versions.toml
+++ b/android/gradle/libs.versions.toml
@@ -1,5 +1,5 @@
[versions]
-agp = "8.13.1"
+agp = "8.13.2"
kotlin = "2.2.21"
coreKtx = "1.17.0"
junit = "4.13.2"
@@ -13,6 +13,15 @@ detekt = "1.23.8"
workManager = "2.11.0"
amapi = "1.7.0"
robolectric = "4.16"
+kotlinx = "1.9.0"
+jscep = "3.0.1"
+bouncycastle = "1.78.1"
+commonsCodec = "1.20.0"
+slf4j = "2.0.17"
+datastorePreferences = "1.2.0"
+navigationCompose = "2.9.6"
+json = "20231013"
+mockk = "1.13.13"
[libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
@@ -33,11 +42,25 @@ androidx-work-runtime-ktx = { group = "androidx.work", name = "work-runtime-ktx"
androidx-work-testing = { group = "androidx.work", name = "work-testing", version.ref = "workManager" }
amapi-sdk = { group = "com.google.android.libraries.enterprise.amapi", name = "amapi", version.ref = "amapi" }
robolectric = { group = "org.robolectric", name = "robolectric", version.ref = "robolectric" }
+jscep = { group = "com.google.code.jscep", name = "jscep", version.ref = "jscep" }
+bouncycastle-bcprov = { group = "org.bouncycastle", name = "bcprov-jdk18on", version.ref = "bouncycastle" }
+bouncycastle-bcpkix = { group = "org.bouncycastle", name = "bcpkix-jdk18on", version.ref = "bouncycastle" }
+commons-codec = { group = "commons-codec", name = "commons-codec", version.ref = "commonsCodec" }
+slf4j-api = { group = "org.slf4j", name = "slf4j-api", version.ref = "slf4j" }
+slf4j-simple = { group = "org.slf4j", name = "slf4j-simple", version.ref = "slf4j" }
+androidx-datastore-preferences = { group = "androidx.datastore", name = "datastore-preferences", version.ref = "datastorePreferences" }
+kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "kotlinx" }
+androidx-navigation-compose = { group = "androidx.navigation", name = "navigation-compose", version.ref = "navigationCompose" }
+androidx-compose-material-icons-extended = { group = "androidx.compose.material", name = "material-icons-extended" }
+kotlinx-coroutines-test = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-test", version.ref = "kotlinx" }
+json = { group = "org.json", name = "json", version.ref = "json" }
+mockk-android = { group = "io.mockk", name = "mockk-android", version.ref = "mockk" }
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
+kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
spotless = { id = "com.diffplug.spotless", version.ref = "spotless" }
detekt = { id = "io.gitlab.arturbosch.detekt", version.ref = "detekt" }