From c41bca5b80d611d2494806e7ca0d90b488e2371a Mon Sep 17 00:00:00 2001 From: ganzvu Date: Mon, 1 Jun 2026 19:15:27 -0500 Subject: [PATCH 1/2] fix(release): align native library packaging Co-authored-by: Cursor --- app/build.gradle.kts | 3 +++ build.gradle.kts | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 96b2786..e5ebc09 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -67,6 +67,9 @@ android { noCompress += "task" } packaging { + jniLibs { + useLegacyPackaging = false + } resources { excludes += "/META-INF/{AL2.0,LGPL2.1}" } diff --git a/build.gradle.kts b/build.gradle.kts index 95cbdb3..4120fa6 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,5 +1,5 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id("com.android.application") version "8.1.1" apply false + id("com.android.application") version "8.6.1" apply false id("org.jetbrains.kotlin.android") version "1.9.0" apply false } From 79214a746c13e97854bcc0b7750ff96dfa174959 Mon Sep 17 00:00:00 2001 From: ganzvu Date: Mon, 1 Jun 2026 19:19:27 -0500 Subject: [PATCH 2/2] Add Samsung Routine autostart hooks Co-authored-by: Cursor --- app/src/main/AndroidManifest.xml | 19 +++++ .../com/example/driveswipe/DriveSwipeApp.kt | 24 ++++++ .../driveswipe/GestureServiceController.kt | 68 ++++++++++++++++ .../com/example/driveswipe/MainActivity.kt | 30 ++----- .../driveswipe/RoutineTriggerActivity.kt | 80 +++++++++++++++++++ .../com/example/driveswipe/ServiceContract.kt | 2 + app/src/main/res/values/strings.xml | 4 + app/src/main/res/xml/shortcuts.xml | 26 ++++++ 8 files changed, 230 insertions(+), 23 deletions(-) create mode 100644 app/src/main/java/com/example/driveswipe/GestureServiceController.kt create mode 100644 app/src/main/java/com/example/driveswipe/RoutineTriggerActivity.kt create mode 100644 app/src/main/res/xml/shortcuts.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index a10e386..99eeb05 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -30,12 +30,31 @@ android:name=".MainActivity" android:exported="true" android:theme="@style/Theme.DriveSwipe"> + + + + + + + + + + + + = Build.VERSION_CODES.TIRAMISU) { + ContextCompat.checkSelfPermission( + context, + Manifest.permission.POST_NOTIFICATIONS + ) == PackageManager.PERMISSION_GRANTED + } else { + true + } + return hasCamera && hasNotifications + } + + fun start(context: Context, settings: AppSettings) { + val intent = buildStartIntent(context, settings) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + ContextCompat.startForegroundService(context, intent) + } else { + context.startService(intent) + } + } + + fun stop(context: Context) { + context.stopService(Intent(context, GestureService::class.java)) + } + + @Suppress("DEPRECATION") + fun isRunning(context: Context): Boolean { + val activityManager = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager + return activityManager.getRunningServices(Int.MAX_VALUE).any { serviceInfo -> + serviceInfo.service.className == GestureService::class.java.name + } + } +} diff --git a/app/src/main/java/com/example/driveswipe/MainActivity.kt b/app/src/main/java/com/example/driveswipe/MainActivity.kt index 16bc817..fc351c0 100644 --- a/app/src/main/java/com/example/driveswipe/MainActivity.kt +++ b/app/src/main/java/com/example/driveswipe/MainActivity.kt @@ -133,40 +133,23 @@ class MainActivity : ComponentActivity() { private fun startGestureService() { val settings = viewModel.uiState.value.settings - val intent = Intent(this, GestureService::class.java).apply { - putExtra(ServiceContract.EXTRA_NIGHT_MODE, settings.isNightMode) - putExtra(ServiceContract.EXTRA_ACTION_COOLDOWN_MS, settings.tuning.actionCooldownMs) - putExtra(ServiceContract.EXTRA_VOLUME_TICK_MS, settings.tuning.volumeTickMs) - putExtra(ServiceContract.EXTRA_PINCH_THRESHOLD, settings.tuning.pinchThreshold) - putExtra(ServiceContract.EXTRA_PINCH_RELEASE_THRESHOLD, settings.tuning.pinchReleaseThreshold) - putExtra(ServiceContract.EXTRA_SWIPE_THRESHOLD, settings.tuning.swipeThreshold) - putExtra(ServiceContract.EXTRA_SWIPE_TIMEOUT_MS, settings.tuning.swipeTimeoutMs) - putExtra(ServiceContract.EXTRA_ALERTING_BURST_MS, settings.tuning.alertingBurstMs) - putExtra(ServiceContract.EXTRA_ACTIVE_TIMEOUT_MS, settings.tuning.activeTimeoutMs) - putExtra(ServiceContract.EXTRA_IDLE_INFERENCE_INTERVAL_MS, settings.tuning.idleInferenceIntervalMs) - putExtra(ServiceContract.EXTRA_MAP_PINCH_RIGHT, settings.mappings.pinchDragRight.name) - putExtra(ServiceContract.EXTRA_MAP_PINCH_LEFT, settings.mappings.pinchDragLeft.name) - putExtra(ServiceContract.EXTRA_MAP_TWO_FINGER, settings.mappings.twoFingerPoint.name) - putExtra(ServiceContract.EXTRA_MAP_VOLUME_UP, settings.mappings.volumeUp.name) - putExtra(ServiceContract.EXTRA_MAP_VOLUME_DOWN, settings.mappings.volumeDown.name) - } - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - ContextCompat.startForegroundService(this, intent) - } else { - startService(intent) + if (!GestureServiceController.hasRequiredStartPermissions(this)) { + checkPermissions() + return } + GestureServiceController.start(this, settings) viewModel.setServiceRunning(true) } private fun stopGestureService() { - val intent = Intent(this, GestureService::class.java) - stopService(intent) + GestureServiceController.stop(this) viewModel.setServiceRunning(false) } override fun onResume() { super.onResume() refreshPermissionState() + viewModel.setServiceRunning(GestureServiceController.isRunning(this)) } override fun onStart() { @@ -180,6 +163,7 @@ class MainActivity : ComponentActivity() { } else { registerReceiver(serviceEventReceiver, filter) } + viewModel.setServiceRunning(GestureServiceController.isRunning(this)) } override fun onStop() { diff --git a/app/src/main/java/com/example/driveswipe/RoutineTriggerActivity.kt b/app/src/main/java/com/example/driveswipe/RoutineTriggerActivity.kt new file mode 100644 index 0000000..615ed5d --- /dev/null +++ b/app/src/main/java/com/example/driveswipe/RoutineTriggerActivity.kt @@ -0,0 +1,80 @@ +package com.example.driveswipe + +import android.content.Intent +import android.os.Bundle +import android.widget.Toast +import androidx.activity.ComponentActivity +import androidx.lifecycle.lifecycleScope +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.launch + +class RoutineTriggerActivity : ComponentActivity() { + private val settingsRepository by lazy { SettingsRepository(applicationContext) } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + handleRoutineIntent(intent) + } + + override fun onNewIntent(intent: Intent) { + super.onNewIntent(intent) + setIntent(intent) + handleRoutineIntent(intent) + } + + private fun handleRoutineIntent(intent: Intent?) { + when (intent?.action) { + ServiceContract.ACTION_ROUTINE_START -> startFromRoutine() + ServiceContract.ACTION_ROUTINE_STOP -> stopFromRoutine() + else -> openMainActivityAndFinish() + } + } + + private fun startFromRoutine() { + if (!GestureServiceController.hasRequiredStartPermissions(this)) { + Toast.makeText( + this, + "DriveSwipe needs camera and notification permissions before Routine start.", + Toast.LENGTH_LONG + ).show() + openMainActivityAndFinish() + return + } + + lifecycleScope.launch { + val settings = settingsRepository.settingsFlow.first() + runCatching { + GestureServiceController.start(this@RoutineTriggerActivity, settings) + }.onSuccess { + Toast.makeText( + this@RoutineTriggerActivity, + "DriveSwipe listening started.", + Toast.LENGTH_SHORT + ).show() + finish() + }.onFailure { + Toast.makeText( + this@RoutineTriggerActivity, + "DriveSwipe could not start from Routine. Opening app.", + Toast.LENGTH_LONG + ).show() + openMainActivityAndFinish() + } + } + } + + private fun stopFromRoutine() { + GestureServiceController.stop(this) + Toast.makeText(this, "DriveSwipe listening stopped.", Toast.LENGTH_SHORT).show() + finish() + } + + private fun openMainActivityAndFinish() { + startActivity( + Intent(this, MainActivity::class.java).apply { + addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP) + } + ) + finish() + } +} diff --git a/app/src/main/java/com/example/driveswipe/ServiceContract.kt b/app/src/main/java/com/example/driveswipe/ServiceContract.kt index 8d4e5e7..66ad53b 100644 --- a/app/src/main/java/com/example/driveswipe/ServiceContract.kt +++ b/app/src/main/java/com/example/driveswipe/ServiceContract.kt @@ -19,6 +19,8 @@ object ServiceContract { const val ACTION_GESTURE_EVENT = "com.example.driveswipe.ACTION_GESTURE_EVENT" const val ACTION_ENGINE_STATE_EVENT = "com.example.driveswipe.ACTION_ENGINE_STATE_EVENT" + const val ACTION_ROUTINE_START = "com.example.driveswipe.action.ROUTINE_START" + const val ACTION_ROUTINE_STOP = "com.example.driveswipe.action.ROUTINE_STOP" const val EXTRA_EVENT_GESTURE = "EVENT_GESTURE" const val EXTRA_EVENT_ACTION = "EVENT_ACTION" const val EXTRA_ENGINE_STATE = "ENGINE_STATE" diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 33debe5..6564d9c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,3 +1,7 @@ DriveSwipe + Start DriveSwipe + Start DriveSwipe listening + Stop DriveSwipe + Stop DriveSwipe listening diff --git a/app/src/main/res/xml/shortcuts.xml b/app/src/main/res/xml/shortcuts.xml new file mode 100644 index 0000000..4f3c777 --- /dev/null +++ b/app/src/main/res/xml/shortcuts.xml @@ -0,0 +1,26 @@ + + + + + + + + + +