From 20e57e9f883eb502b18be19ee458b441e46bc005 Mon Sep 17 00:00:00 2001 From: cogwheel0 <172976095+cogwheel0@users.noreply.github.com> Date: Fri, 21 Nov 2025 19:16:51 +0530 Subject: [PATCH 1/7] feat(android): Add voice interaction service for app launch --- android/app/src/main/AndroidManifest.xml | 12 +++++++++ .../conduit/ConduitVoiceInteractionSession.kt | 26 +++++++++++++++++++ .../ConduitVoiceInteractionSessionService.kt | 11 ++++++++ .../res/xml/voice_interaction_service.xml | 7 +++++ pubspec.lock | 16 ++++++------ 5 files changed, 64 insertions(+), 8 deletions(-) create mode 100644 android/app/src/main/kotlin/app/cogwheel/conduit/ConduitVoiceInteractionSession.kt create mode 100644 android/app/src/main/kotlin/app/cogwheel/conduit/ConduitVoiceInteractionSessionService.kt create mode 100644 android/app/src/main/res/xml/voice_interaction_service.xml diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 7008f09..e56683c 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -32,6 +32,18 @@ android:allowBackup="false" android:fullBackupContent="false" android:usesCleartextTraffic="true"> + + + + + + + + diff --git a/pubspec.lock b/pubspec.lock index 8cfd515..43c4376 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -961,10 +961,10 @@ packages: dependency: transitive description: name: meta - sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c + sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" url: "https://pub.dev" source: hosted - version: "1.16.0" + version: "1.17.0" mime: dependency: transitive description: @@ -1606,26 +1606,26 @@ packages: dependency: transitive description: name: test - sha256: "65e29d831719be0591f7b3b1a32a3cda258ec98c58c7b25f7b84241bc31215bb" + sha256: "75906bf273541b676716d1ca7627a17e4c4070a3a16272b7a3dc7da3b9f3f6b7" url: "https://pub.dev" source: hosted - version: "1.26.2" + version: "1.26.3" test_api: dependency: transitive description: name: test_api - sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00" + sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55 url: "https://pub.dev" source: hosted - version: "0.7.6" + version: "0.7.7" test_core: dependency: transitive description: name: test_core - sha256: "80bf5a02b60af04b09e14f6fe68b921aad119493e26e490deaca5993fef1b05a" + sha256: "0cc24b5ff94b38d2ae73e1eb43cc302b77964fbf67abad1e296025b78deb53d0" url: "https://pub.dev" source: hosted - version: "0.6.11" + version: "0.6.12" timezone: dependency: transitive description: From f18edc7fe00a7d6ad65d3c7fb310399334580848 Mon Sep 17 00:00:00 2001 From: cogwheel0 <172976095+cogwheel0@users.noreply.github.com> Date: Fri, 21 Nov 2025 19:50:39 +0530 Subject: [PATCH 2/7] feat(android): Add screen context capture for voice assistant --- .../conduit/ConduitVoiceInteractionSession.kt | 73 ++++++++++++++++++- .../app/cogwheel/conduit/MainActivity.kt | 20 +++++ .../main/res/drawable/rounded_button_bg.xml | 6 ++ .../main/res/drawable/rounded_overlay_bg.xml | 6 ++ .../src/main/res/layout/assistant_overlay.xml | 67 +++++++++++++++++ lib/core/utils/android_assistant_handler.dart | 35 +++++++++ lib/features/chat/views/chat_page.dart | 25 +++++++ 7 files changed, 228 insertions(+), 4 deletions(-) create mode 100644 android/app/src/main/res/drawable/rounded_button_bg.xml create mode 100644 android/app/src/main/res/drawable/rounded_overlay_bg.xml create mode 100644 android/app/src/main/res/layout/assistant_overlay.xml create mode 100644 lib/core/utils/android_assistant_handler.dart diff --git a/android/app/src/main/kotlin/app/cogwheel/conduit/ConduitVoiceInteractionSession.kt b/android/app/src/main/kotlin/app/cogwheel/conduit/ConduitVoiceInteractionSession.kt index 9e52f5b..987206b 100644 --- a/android/app/src/main/kotlin/app/cogwheel/conduit/ConduitVoiceInteractionSession.kt +++ b/android/app/src/main/kotlin/app/cogwheel/conduit/ConduitVoiceInteractionSession.kt @@ -9,6 +9,24 @@ import android.app.assist.AssistContent class ConduitVoiceInteractionSession(context: Context) : VoiceInteractionSession(context) { + private var capturedContext: String? = null + private var summarizeButton: android.widget.Button? = null + + override fun onCreateContentView(): android.view.View { + val view = layoutInflater.inflate(app.cogwheel.conduit.R.layout.assistant_overlay, null) + summarizeButton = view.findViewById(app.cogwheel.conduit.R.id.btn_summarize) + summarizeButton?.setOnClickListener { + launchAppWithContext() + } + + val closeButton = view.findViewById(app.cogwheel.conduit.R.id.btn_close) + closeButton?.setOnClickListener { + finish() + } + + return view + } + override fun onHandleAssist( data: Bundle?, structure: AssistStructure?, @@ -18,9 +36,56 @@ class ConduitVoiceInteractionSession(context: Context) : VoiceInteractionSession android.util.Log.d("ConduitVoiceSession", "onHandleAssist called") - // Launch the main activity when the assistant is triggered - val intent = Intent(context, MainActivity::class.java) - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - context.startActivity(intent) + val screenContext = StringBuilder() + structure?.let { + val nodes = it.windowNodeCount + for (i in 0 until nodes) { + val windowNode = it.getWindowNodeAt(i) + traverseNode(windowNode.rootViewNode, screenContext) + } + } + + capturedContext = screenContext.toString() + // Ideally, we could update the UI here to say "Context Ready" + } + + private fun launchAppWithContext() { + try { + android.util.Log.d("ConduitVoiceSession", "Attempting to launch app with context") + val intent = Intent(context, MainActivity::class.java) + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP) + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) + + if (capturedContext != null) { + intent.putExtra("screen_context", capturedContext) + android.util.Log.d("ConduitVoiceSession", "Context attached: ${capturedContext?.take(50)}...") + } else { + android.util.Log.d("ConduitVoiceSession", "No context captured") + } + + context.startActivity(intent) + android.util.Log.d("ConduitVoiceSession", "App launch requested") + finish() // Close the overlay + } catch (e: Exception) { + android.util.Log.e("ConduitVoiceSession", "Failed to launch app", e) + } + } + + private fun traverseNode(node: AssistStructure.ViewNode?, builder: StringBuilder) { + if (node == null) return + + if (node.text != null) { + builder.append(node.text).append("\n") + } + + // Also check content description for accessibility text + if (node.contentDescription != null) { + builder.append(node.contentDescription).append("\n") + } + + for (i in 0 until node.childCount) { + traverseNode(node.getChildAt(i), builder) + } } } diff --git a/android/app/src/main/kotlin/app/cogwheel/conduit/MainActivity.kt b/android/app/src/main/kotlin/app/cogwheel/conduit/MainActivity.kt index edecfcf..4343470 100644 --- a/android/app/src/main/kotlin/app/cogwheel/conduit/MainActivity.kt +++ b/android/app/src/main/kotlin/app/cogwheel/conduit/MainActivity.kt @@ -23,12 +23,32 @@ class MainActivity : FlutterActivity() { windowInsetsController.isAppearanceLightNavigationBars = false } + private val CHANNEL = "app.cogwheel.conduit/assistant" + private var methodChannel: io.flutter.plugin.common.MethodChannel? = null + override fun configureFlutterEngine(flutterEngine: FlutterEngine) { super.configureFlutterEngine(flutterEngine) // Initialize background streaming handler backgroundStreamingHandler = BackgroundStreamingHandler(this) backgroundStreamingHandler.setup(flutterEngine) + + methodChannel = io.flutter.plugin.common.MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL) + + // Check if started with context + handleIntent(intent) + } + + override fun onNewIntent(intent: android.content.Intent) { + super.onNewIntent(intent) + handleIntent(intent) + } + + private fun handleIntent(intent: android.content.Intent) { + val screenContext = intent.getStringExtra("screen_context") + if (screenContext != null) { + methodChannel?.invokeMethod("analyzeScreen", screenContext) + } } override fun onDestroy() { diff --git a/android/app/src/main/res/drawable/rounded_button_bg.xml b/android/app/src/main/res/drawable/rounded_button_bg.xml new file mode 100644 index 0000000..ab7b3f1 --- /dev/null +++ b/android/app/src/main/res/drawable/rounded_button_bg.xml @@ -0,0 +1,6 @@ + + + + + diff --git a/android/app/src/main/res/drawable/rounded_overlay_bg.xml b/android/app/src/main/res/drawable/rounded_overlay_bg.xml new file mode 100644 index 0000000..53dea9a --- /dev/null +++ b/android/app/src/main/res/drawable/rounded_overlay_bg.xml @@ -0,0 +1,6 @@ + + + + + diff --git a/android/app/src/main/res/layout/assistant_overlay.xml b/android/app/src/main/res/layout/assistant_overlay.xml new file mode 100644 index 0000000..32ea604 --- /dev/null +++ b/android/app/src/main/res/layout/assistant_overlay.xml @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + +