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/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 @@
+
+
+
+
+
+
+
+
+
+
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
}