Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
f1fab32
chore(deps): update actions/checkout action to v6
renovate[bot] Dec 5, 2025
9e72718
refactor: Polish setup code and add analytics
xLexip Dec 5, 2025
6bc4572
fix(broadcasts): Listen for MY_PACKAGE_REPLACED to restart service af…
xLexip Dec 6, 2025
d2296ab
feat: Add a "Star on GitHub" action
xLexip Dec 6, 2025
1156bc4
chore: Require telephony to support foldables but not tablets
xLexip Dec 6, 2025
428ca41
refactor: Use the new InstallSourceChecker util where applicable
xLexip Dec 6, 2025
3b193f6
chore(analytics): Log successful in-app updates
xLexip Dec 6, 2025
fecf527
fix: Enable in-app updates only when the installer is Google Play
xLexip Dec 6, 2025
0893e7a
feat(ui): Show a warning when the device is covered
xLexip Dec 6, 2025
74971a6
feat: Use SENSOR_DELAY_UI in UI use cases
xLexip Dec 6, 2025
f3023a2
refactor(analytics): Update setup step log events
xLexip Dec 7, 2025
36887a3
refactor: Outsource three-dot menu code
xLexip Dec 6, 2025
7caaf9c
fix: Pause sensor usage when the UI is in background
xLexip Dec 6, 2025
55de853
refactor(ui): Increase hysteresis delay for device-covered checks
xLexip Dec 6, 2025
6c7e5c5
feat(ui): Implement a setup card on the main screen
xLexip Dec 7, 2025
b290203
feat(ui): Add a safety statement to the setup wizard
xLexip Dec 7, 2025
82d0055
feat(ui): Allow to skip setup step 2 to see what step 3 is
xLexip Dec 7, 2025
11090bd
refactor(service): Change the notification action to pause rather tha…
xLexip Dec 7, 2025
87542e2
refactor(res): Optimize the app strings and translations
xLexip Dec 7, 2025
4a22561
chore(ui): Hide the pause action in the service notification for now
xLexip Dec 7, 2025
4b90430
fix: Always restart the service onResume if adaptive theme is enabled
xLexip Dec 7, 2025
47e83de
chore: Implement flexible update prompts and easy prompt intrusiveness
xLexip Dec 8, 2025
dad261d
feat(ui): Show the live lux measurement when the service is enabled
xLexip Dec 8, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ jobs:
contents: read
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
with:
fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis

Expand Down
13 changes: 11 additions & 2 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,16 @@
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
<uses-permission android:name="android.permission.VIBRATE" />

<!-- One safeguard to support foldables but not tablets -->
<uses-feature
android:name="android.hardware.telephony"
android:required="true"
tools:ignore="UnnecessaryRequiredFeature" />

<supports-screens
android:smallScreens="true"
android:normalScreens="true"
android:largeScreens="false"
android:largeScreens="true"
android:xlargeScreens="false"
android:resizeable="true"
android:anyDensity="true" />
Expand Down Expand Up @@ -66,11 +72,14 @@
</activity>

<receiver
android:name=".broadcasts.BootCompletedReceiver"
android:name=".broadcasts.AppLifecycleReceiver"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.MY_PACKAGE_REPLACED" />
</intent-filter>
</receiver>

<service
Expand Down
17 changes: 8 additions & 9 deletions app/src/main/java/dev/lexip/hecate/analytics/AnalyticsGate.kt
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ package dev.lexip.hecate.analytics

import android.content.Context
import com.google.firebase.analytics.FirebaseAnalytics
import dev.lexip.hecate.util.InstallSourceChecker

/**
* Controls whether analytics collection is enabled.
Expand All @@ -24,18 +25,16 @@ object AnalyticsGate {
@Volatile
private var enabled = false

@Volatile
private var playStoreInstall = false

fun init(context: Context) {
val pm = context.packageManager
val installer = try {
pm.getInstallSourceInfo(context.packageName).installingPackageName
?: pm.getInstallSourceInfo(context.packageName).initiatingPackageName
} catch (_: Exception) {
null
}
val isGooglePlayInstall = installer == "com.android.vending"
enabled = isGooglePlayInstall
playStoreInstall = InstallSourceChecker.isInstalledFromPlayStore(context)
enabled = playStoreInstall
FirebaseAnalytics.getInstance(context).setAnalyticsCollectionEnabled(enabled)
}

fun allowed(): Boolean = enabled

fun isPlayStoreInstall(): Boolean = playStoreInstall
}
30 changes: 30 additions & 0 deletions app/src/main/java/dev/lexip/hecate/analytics/AnalyticsLogger.kt
Original file line number Diff line number Diff line change
Expand Up @@ -106,4 +106,34 @@ object AnalyticsLogger {
}
}
}

fun logSetupStarted(context: Context) {
ifAllowed {
analytics(context).logEvent("setup_started") { }
}
}

fun logSetupStepOneCompleted(context: Context) {
ifAllowed {
analytics(context).logEvent("setup_step_one_completed") { }
}
}

fun logSetupStepTwoCompleted(context: Context) {
ifAllowed {
analytics(context).logEvent("setup_step_two_completed") { }
}
}

fun logSetupFinished(context: Context) {
ifAllowed {
analytics(context).logEvent("setup_finished") { }
}
}

fun logInAppUpdateInstalled(context: Context) {
ifAllowed {
analytics(context).logEvent("in_app_update_installed") { }
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,25 @@ import dev.lexip.hecate.services.BroadcastReceiverService

private const val TAG = "BootCompletedReceiver"

class BootCompletedReceiver : BroadcastReceiver() {
class AppLifecycleReceiver : BroadcastReceiver() {

override fun onReceive(context: Context, intent: Intent) {
if (intent.action == Intent.ACTION_BOOT_COMPLETED) {
Log.i(TAG, "Boot completed, starting broadcast receiver service...")
val serviceIntent = Intent(context, BroadcastReceiverService::class.java)
ContextCompat.startForegroundService(context, serviceIntent)
when (intent.action) {
Intent.ACTION_BOOT_COMPLETED -> {
Log.i(TAG, "Boot completed, starting broadcast receiver service...")
val serviceIntent = Intent(context, BroadcastReceiverService::class.java)
ContextCompat.startForegroundService(context, serviceIntent)
}

Intent.ACTION_MY_PACKAGE_REPLACED -> {
Log.i(TAG, "App package replaced, starting broadcast receiver service if needed...")
val serviceIntent = Intent(context, BroadcastReceiverService::class.java)
ContextCompat.startForegroundService(context, serviceIntent)
}

else -> {
Log.d(TAG, "Received unrelated intent action: ${intent.action}")
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,18 +38,17 @@ class ScreenOnReceiver(
Log.d(TAG, "Screen turned on, checking adaptive theme conditions...")

// Check if the device is covered using the proximity sensor
proximitySensorManager.startListening { distance ->
proximitySensorManager.startListening({ distance: Float ->
proximitySensorManager.stopListening()

// If the device is not covered, change the device theme based on the environment
if (distance >= 5f) {
lightSensorManager.startListening { lightValue ->
lightSensorManager.startListening({ lightValue: Float ->
lightSensorManager.stopListening()
darkThemeHandler.setDarkTheme(lightValue < adaptiveThemeThresholdLux)
}
})
} else Log.d(TAG, "Device is covered, skipping adaptive theme checks.")

}
})
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ import android.util.Log
import androidx.core.app.NotificationCompat
import dev.lexip.hecate.HecateApplication
import dev.lexip.hecate.R
import dev.lexip.hecate.analytics.AnalyticsLogger
import dev.lexip.hecate.broadcasts.ScreenOnReceiver
import dev.lexip.hecate.data.UserPreferencesRepository
import dev.lexip.hecate.util.DarkThemeHandler
Expand All @@ -39,7 +38,7 @@ import kotlinx.coroutines.launch

private const val TAG = "BroadcastReceiverService"
private const val NOTIFICATION_CHANNEL_ID = "ForegroundServiceChannel"
private const val ACTION_STOP_SERVICE = "dev.lexip.hecate.action.STOP_SERVICE"
private const val ACTION_PAUSE_SERVICE = "dev.lexip.hecate.action.STOP_SERVICE"

private var screenOnReceiver: ScreenOnReceiver? = null

Expand Down Expand Up @@ -67,23 +66,10 @@ class BroadcastReceiverService : Service() {
val dataStore = (this.applicationContext as HecateApplication).userPreferencesDataStore

// Handle stop action from notification
if (intent?.action == ACTION_STOP_SERVICE) {
Log.i(
TAG,
"Disable action received from notification - disabling adaptive theme and stopping service..."
)
if (intent?.action == ACTION_PAUSE_SERVICE) {
Log.i(TAG, "Pause action received from notification.")
serviceScope.launch {
try {
val userPreferencesRepository = UserPreferencesRepository(dataStore)
userPreferencesRepository.updateAdaptiveThemeEnabled(false)
Log.i(TAG, "Adaptive theme disabled via notification action.")
AnalyticsLogger.logServiceDisabled(
applicationContext,
source = "notification_action"
)
} catch (e: Exception) {
Log.e(TAG, "Failed to update adaptive theme preference", e)
}
Log.i(TAG, "Adaptive theme paused/killed via notification action.")
stopForeground(STOP_FOREGROUND_REMOVE)
stopSelf()
}
Expand Down Expand Up @@ -161,22 +147,6 @@ class BroadcastReceiverService : Service() {
pendingIntent
).build()

// Create action to stop the service
val stopIntent = Intent(this, BroadcastReceiverService::class.java).apply {
action = ACTION_STOP_SERVICE
}
val stopPendingIntent = PendingIntent.getService(
this,
0,
stopIntent,
PendingIntent.FLAG_IMMUTABLE
)
val stopAction = NotificationCompat.Action.Builder(
0,
getString(R.string.action_stop_service),
stopPendingIntent
).build()

// Build notification
val builder = NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID)
.setContentTitle(getString(R.string.app_name))
Expand All @@ -186,7 +156,6 @@ class BroadcastReceiverService : Service() {
.setOnlyAlertOnce(true)
.setContentIntent(pendingIntent)
.addAction(disableAction)
.addAction(stopAction)
.setOngoing(true)


Expand Down
Loading