From a2c76eab98c5af64963e35f9ae8dc2ea29ddc217 Mon Sep 17 00:00:00 2001 From: Phenix Date: Sat, 7 Feb 2026 02:00:56 +0800 Subject: [PATCH 01/12] auto-claude: subtask-1-1 - Create widget layout XML with status display and action buttons Created widget_adb_status.xml with: - Status header (icon + text + toggle button) - IP:Port display (shown when connected) - Copy button for quick access - Placeholder text when disabled - RemoteViews-compatible views only - Follows Material Design patterns from fragment_control.xml Co-Authored-By: Claude Sonnet 4.5 --- app/src/main/res/layout/widget_adb_status.xml | 119 ++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 app/src/main/res/layout/widget_adb_status.xml diff --git a/app/src/main/res/layout/widget_adb_status.xml b/app/src/main/res/layout/widget_adb_status.xml new file mode 100644 index 0000000..82bb6ec --- /dev/null +++ b/app/src/main/res/layout/widget_adb_status.xml @@ -0,0 +1,119 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 851b8c7e259f3ef005016633b7deb75e6bd53144 Mon Sep 17 00:00:00 2001 From: Phenix Date: Sat, 7 Feb 2026 02:04:14 +0800 Subject: [PATCH 02/12] auto-claude: subtask-1-2 - Create widget info XML metadata file --- .auto-claude-security.json | 185 +++++++++++++++++++++++ .auto-claude-status | 25 +++ .claude_settings.json | 39 +++++ .gitignore | 63 ++++---- app/src/main/res/values/strings.xml | 3 + app/src/main/res/xml/adb_widget_info.xml | 26 ++++ 6 files changed, 311 insertions(+), 30 deletions(-) create mode 100644 .auto-claude-security.json create mode 100644 .auto-claude-status create mode 100644 .claude_settings.json create mode 100644 app/src/main/res/xml/adb_widget_info.xml diff --git a/.auto-claude-security.json b/.auto-claude-security.json new file mode 100644 index 0000000..f740b91 --- /dev/null +++ b/.auto-claude-security.json @@ -0,0 +1,185 @@ +{ + "base_commands": [ + ".", + "[", + "[[", + "ag", + "awk", + "basename", + "bash", + "bc", + "break", + "cat", + "cd", + "chmod", + "clear", + "cmp", + "column", + "comm", + "command", + "continue", + "cp", + "curl", + "cut", + "date", + "df", + "diff", + "dig", + "dirname", + "du", + "echo", + "egrep", + "env", + "eval", + "exec", + "exit", + "expand", + "export", + "expr", + "false", + "fd", + "fgrep", + "file", + "find", + "fmt", + "fold", + "gawk", + "gh", + "git", + "grep", + "gunzip", + "gzip", + "head", + "help", + "host", + "iconv", + "id", + "jobs", + "join", + "jq", + "kill", + "killall", + "less", + "let", + "ln", + "ls", + "lsof", + "man", + "mkdir", + "mktemp", + "more", + "mv", + "nl", + "paste", + "pgrep", + "ping", + "pkill", + "popd", + "printenv", + "printf", + "ps", + "pushd", + "pwd", + "read", + "readlink", + "realpath", + "reset", + "return", + "rev", + "rg", + "rm", + "rmdir", + "sed", + "seq", + "set", + "sh", + "shuf", + "sleep", + "sort", + "source", + "split", + "stat", + "tail", + "tar", + "tee", + "test", + "time", + "timeout", + "touch", + "tr", + "tree", + "true", + "type", + "uname", + "unexpand", + "uniq", + "unset", + "unzip", + "watch", + "wc", + "wget", + "whereis", + "which", + "whoami", + "xargs", + "yes", + "yq", + "zip", + "zsh" + ], + "stack_commands": [ + "ant", + "gradle", + "gradlew", + "ipython", + "jar", + "java", + "javac", + "jupyter", + "kotlin", + "kotlinc", + "maven", + "mvn", + "node", + "notebook", + "npm", + "npx", + "pdb", + "pip", + "pip3", + "pipx", + "pudb", + "python", + "python3" + ], + "script_commands": [], + "custom_commands": [], + "detected_stack": { + "languages": [ + "python", + "javascript", + "java", + "kotlin" + ], + "package_managers": [ + "gradle" + ], + "frameworks": [], + "databases": [], + "infrastructure": [], + "cloud_providers": [], + "code_quality_tools": [], + "version_managers": [] + }, + "custom_scripts": { + "npm_scripts": [], + "make_targets": [], + "poetry_scripts": [], + "cargo_aliases": [], + "shell_scripts": [] + }, + "project_dir": "D:\\AI\\Android\\APK\\Android-Apps\\apps\\wirelessadb", + "created_at": "2026-02-05T12:56:49.824459", + "project_hash": "0d6be561318e22002fe7a184fb411324", + "inherited_from": "D:\\AI\\Android\\APK\\Android-Apps\\apps\\wirelessadb" +} \ No newline at end of file diff --git a/.auto-claude-status b/.auto-claude-status new file mode 100644 index 0000000..40fc799 --- /dev/null +++ b/.auto-claude-status @@ -0,0 +1,25 @@ +{ + "active": true, + "spec": "003-home-screen-widget", + "state": "building", + "subtasks": { + "completed": 1, + "total": 12, + "in_progress": 1, + "failed": 0 + }, + "phase": { + "current": "Widget Foundation", + "id": null, + "total": 5 + }, + "workers": { + "active": 0, + "max": 1 + }, + "session": { + "number": 3, + "started_at": "2026-02-07T01:52:14.093243" + }, + "last_update": "2026-02-07T02:02:04.636777" +} \ No newline at end of file diff --git a/.claude_settings.json b/.claude_settings.json new file mode 100644 index 0000000..dc558db --- /dev/null +++ b/.claude_settings.json @@ -0,0 +1,39 @@ +{ + "sandbox": { + "enabled": true, + "autoAllowBashIfSandboxed": true + }, + "permissions": { + "defaultMode": "acceptEdits", + "allow": [ + "Read(./**)", + "Write(./**)", + "Edit(./**)", + "Glob(./**)", + "Grep(./**)", + "Read(D:\\AI\\Android\\APK\\Android-Apps\\apps\\wirelessadb\\.auto-claude\\worktrees\\tasks\\003-home-screen-widget/**)", + "Write(D:\\AI\\Android\\APK\\Android-Apps\\apps\\wirelessadb\\.auto-claude\\worktrees\\tasks\\003-home-screen-widget/**)", + "Edit(D:\\AI\\Android\\APK\\Android-Apps\\apps\\wirelessadb\\.auto-claude\\worktrees\\tasks\\003-home-screen-widget/**)", + "Glob(D:\\AI\\Android\\APK\\Android-Apps\\apps\\wirelessadb\\.auto-claude\\worktrees\\tasks\\003-home-screen-widget/**)", + "Grep(D:\\AI\\Android\\APK\\Android-Apps\\apps\\wirelessadb\\.auto-claude\\worktrees\\tasks\\003-home-screen-widget/**)", + "Read(D:\\AI\\Android\\APK\\Android-Apps\\apps\\wirelessadb\\.auto-claude\\worktrees\\tasks\\003-home-screen-widget\\.auto-claude\\specs\\003-home-screen-widget/**)", + "Write(D:\\AI\\Android\\APK\\Android-Apps\\apps\\wirelessadb\\.auto-claude\\worktrees\\tasks\\003-home-screen-widget\\.auto-claude\\specs\\003-home-screen-widget/**)", + "Edit(D:\\AI\\Android\\APK\\Android-Apps\\apps\\wirelessadb\\.auto-claude\\worktrees\\tasks\\003-home-screen-widget\\.auto-claude\\specs\\003-home-screen-widget/**)", + "Read(D:\\AI\\Android\\APK\\Android-Apps\\apps\\wirelessadb\\.auto-claude/**)", + "Write(D:\\AI\\Android\\APK\\Android-Apps\\apps\\wirelessadb\\.auto-claude/**)", + "Edit(D:\\AI\\Android\\APK\\Android-Apps\\apps\\wirelessadb\\.auto-claude/**)", + "Glob(D:\\AI\\Android\\APK\\Android-Apps\\apps\\wirelessadb\\.auto-claude/**)", + "Grep(D:\\AI\\Android\\APK\\Android-Apps\\apps\\wirelessadb\\.auto-claude/**)", + "Bash(*)", + "WebFetch(*)", + "WebSearch(*)", + "mcp__context7__resolve-library-id(*)", + "mcp__context7__get-library-docs(*)", + "mcp__graphiti-memory__search_nodes(*)", + "mcp__graphiti-memory__search_facts(*)", + "mcp__graphiti-memory__add_episode(*)", + "mcp__graphiti-memory__get_episodes(*)", + "mcp__graphiti-memory__get_entity_edge(*)" + ] + } +} \ No newline at end of file diff --git a/.gitignore b/.gitignore index f0afa5e..d3992d1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,30 +1,33 @@ -# Gradle -.gradle/ -build/ -!gradle/wrapper/gradle-wrapper.jar - -# Android -*.apk -*.aab -*.ap_ -*.dex -local.properties - -# IDE -.idea/ -*.iml -.DS_Store - -# Research (local notes, not for repo) -research/ - -# Signing secrets (NEVER commit!) -*.keystore -*.jks -.env - -# Screenshots and temp files -screenshot*.png -temp/ -nul -.serena/ +# Gradle +.gradle/ +build/ +!gradle/wrapper/gradle-wrapper.jar + +# Android +*.apk +*.aab +*.ap_ +*.dex +local.properties + +# IDE +.idea/ +*.iml +.DS_Store + +# Research (local notes, not for repo) +research/ + +# Signing secrets (NEVER commit!) +*.keystore +*.jks +.env + +# Screenshots and temp files +screenshot*.png +temp/ +nul +.serena/ + +# Auto Claude data directory +.auto-claude/ diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e87d4ad..5bb801e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -328,4 +328,7 @@ Added: %1$s Requested: Now Remove device + + + Quick ADB status and toggle control diff --git a/app/src/main/res/xml/adb_widget_info.xml b/app/src/main/res/xml/adb_widget_info.xml new file mode 100644 index 0000000..0db9ab2 --- /dev/null +++ b/app/src/main/res/xml/adb_widget_info.xml @@ -0,0 +1,26 @@ + + + From 98a7fd0a6bb2369118ba517348fee7bf1eed6a06 Mon Sep 17 00:00:00 2001 From: Phenix Date: Sat, 7 Feb 2026 02:06:58 +0800 Subject: [PATCH 03/12] auto-claude: subtask-1-3 - Create widget icon drawable resource - Created ic_widget_adb.xml following app's icon style - Uses ADB-style design with circle and connectors - 20dp x 20dp dimensions matching other indicator icons - White fill color for consistency Co-Authored-By: Claude Sonnet 4.5 --- app/src/main/res/drawable/ic_widget_adb.xml | 24 +++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 app/src/main/res/drawable/ic_widget_adb.xml diff --git a/app/src/main/res/drawable/ic_widget_adb.xml b/app/src/main/res/drawable/ic_widget_adb.xml new file mode 100644 index 0000000..bfaa019 --- /dev/null +++ b/app/src/main/res/drawable/ic_widget_adb.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + From 1a0c2ca58486167074c49d7c41927dc68d0be3f5 Mon Sep 17 00:00:00 2001 From: Phenix Date: Sat, 7 Feb 2026 02:10:07 +0800 Subject: [PATCH 04/12] auto-claude: subtask-1-4 - Create AdbWidgetProvider class extending AppWidget - Created AdbWidgetProvider extending AppWidgetProvider - Implemented widget lifecycle methods (onUpdate, onEnabled, onDisabled, onDeleted) - Uses RemoteViews to update widget UI with ADB status - Displays IP:Port when ADB is enabled - Shows disabled state when ADB is off - Uses coroutines pattern from BootReceiver - Prepared for button click handlers (Phase 2) Co-Authored-By: Claude Sonnet 4.5 --- .auto-claude-status | 6 +- .../wirelessadb/widget/AdbWidgetProvider.kt | 119 ++++++++++++++++++ 2 files changed, 122 insertions(+), 3 deletions(-) create mode 100644 app/src/main/java/com/phenix/wirelessadb/widget/AdbWidgetProvider.kt diff --git a/.auto-claude-status b/.auto-claude-status index 40fc799..74e5a20 100644 --- a/.auto-claude-status +++ b/.auto-claude-status @@ -3,7 +3,7 @@ "spec": "003-home-screen-widget", "state": "building", "subtasks": { - "completed": 1, + "completed": 3, "total": 12, "in_progress": 1, "failed": 0 @@ -18,8 +18,8 @@ "max": 1 }, "session": { - "number": 3, + "number": 5, "started_at": "2026-02-07T01:52:14.093243" }, - "last_update": "2026-02-07T02:02:04.636777" + "last_update": "2026-02-07T02:07:29.802946" } \ No newline at end of file diff --git a/app/src/main/java/com/phenix/wirelessadb/widget/AdbWidgetProvider.kt b/app/src/main/java/com/phenix/wirelessadb/widget/AdbWidgetProvider.kt new file mode 100644 index 0000000..9111659 --- /dev/null +++ b/app/src/main/java/com/phenix/wirelessadb/widget/AdbWidgetProvider.kt @@ -0,0 +1,119 @@ +package com.phenix.wirelessadb.widget + +import android.appwidget.AppWidgetManager +import android.appwidget.AppWidgetProvider +import android.content.Context +import android.view.View +import android.widget.RemoteViews +import com.phenix.wirelessadb.AdbManager +import com.phenix.wirelessadb.R +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch + +/** + * Home screen widget provider for ADB status display and control. + * + * Features: + * - Display current ADB status (enabled/disabled) + * - Show IP:Port when ADB is enabled + * - Toggle button to enable/disable ADB + * - Copy button to copy connection command to clipboard + */ +class AdbWidgetProvider : AppWidgetProvider() { + + companion object { + const val ACTION_TOGGLE_ADB = "com.phenix.wirelessadb.widget.TOGGLE_ADB" + const val ACTION_COPY_COMMAND = "com.phenix.wirelessadb.widget.COPY_COMMAND" + } + + /** + * Called when widget needs to be updated. + * Updates all instances with current ADB status. + */ + override fun onUpdate( + context: Context, + appWidgetManager: AppWidgetManager, + appWidgetIds: IntArray + ) { + // Update each widget instance + for (appWidgetId in appWidgetIds) { + updateWidget(context, appWidgetManager, appWidgetId) + } + } + + /** + * Called when the first widget instance is added. + */ + override fun onEnabled(context: Context) { + super.onEnabled(context) + // Widget is now active - future: register broadcast receivers for real-time updates + } + + /** + * Called when the last widget instance is removed. + */ + override fun onDisabled(context: Context) { + super.onDisabled(context) + // No more widgets - future: unregister broadcast receivers + } + + /** + * Called when a widget instance is removed. + */ + override fun onDeleted(context: Context, appWidgetIds: IntArray) { + super.onDeleted(context, appWidgetIds) + // Widget instance removed - cleanup if needed + } + + /** + * Updates a single widget instance with current ADB status. + */ + private fun updateWidget( + context: Context, + appWidgetManager: AppWidgetManager, + appWidgetId: Int + ) { + CoroutineScope(Dispatchers.IO).launch { + val status = AdbManager.getStatus(context) + + val views = RemoteViews(context.packageName, R.layout.widget_adb_status) + + // Update status icon and text + if (status.enabled && status.ip != null) { + // ADB is enabled and connected + views.setImageViewResource(R.id.widgetStatusIcon, R.drawable.ic_indicator_wifi) + views.setInt(R.id.widgetStatusIcon, "setColorFilter", context.getColor(R.color.status_active)) + views.setTextViewText(R.id.widgetStatusText, context.getString(R.string.status_connected)) + + // Show IP:Port + views.setViewVisibility(R.id.widgetConnectionInfo, View.VISIBLE) + views.setViewVisibility(R.id.widgetPlaceholder, View.GONE) + views.setTextViewText(R.id.widgetIpPortText, "${status.ip}:${status.port}") + } else { + // ADB is disabled + views.setImageViewResource(R.id.widgetStatusIcon, R.drawable.ic_indicator_wifi) + views.setInt(R.id.widgetStatusIcon, "setColorFilter", context.getColor(R.color.status_inactive)) + views.setTextViewText(R.id.widgetStatusText, context.getString(R.string.status_disabled)) + + // Hide IP:Port, show placeholder + views.setViewVisibility(R.id.widgetConnectionInfo, View.GONE) + views.setViewVisibility(R.id.widgetPlaceholder, View.VISIBLE) + } + + // Update toggle button icon + if (status.enabled) { + views.setImageViewResource(R.id.widgetToggleButton, R.drawable.ic_check) + } else { + views.setImageViewResource(R.id.widgetToggleButton, R.drawable.ic_check) + } + + // TODO: Set up PendingIntents for button clicks (Phase 2) + // - Toggle button should trigger ACTION_TOGGLE_ADB + // - Copy button should trigger ACTION_COPY_COMMAND + + // Update widget + appWidgetManager.updateAppWidget(appWidgetId, views) + } + } +} From 9e7b597d67292f6f94d4e76756686fac03c5811c Mon Sep 17 00:00:00 2001 From: Phenix Date: Sat, 7 Feb 2026 02:12:15 +0800 Subject: [PATCH 05/12] auto-claude: subtask-1-5 - Register widget provider in AndroidManifest.xml --- app/src/main/AndroidManifest.xml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index c5209b8..6186ae5 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -47,6 +47,18 @@ + + + + + + + + Date: Sat, 7 Feb 2026 02:17:18 +0800 Subject: [PATCH 06/12] auto-claude: subtask-2-1 - Implement widget update mechanism to fetch and display ADB status Implemented comprehensive widget update mechanism with: - Asynchronous ADB status fetching using AdbManager.getStatus() - Proper thread management (IO for data, Main for UI updates) - Three status states: connected (WiFi + ADB), enabled (ADB only), disabled - Different toggle button icons (check/close) based on state - Error handling with fallback error display - Comprehensive debug logging following AdbManager patterns - Added missing string resources (status_enabled, status_error) The widget now correctly displays: - Status icon with color-coded states (green/orange/gray/red) - IP:Port when connected via WiFi - Port-only when ADB enabled but no WiFi - Placeholder when disabled - Error state with warning icon on failure Co-Authored-By: Claude Sonnet 4.5 --- .auto-claude-status | 10 +- .../wirelessadb/widget/AdbWidgetProvider.kt | 119 ++++++++++++------ app/src/main/res/values/strings.xml | 2 + 3 files changed, 90 insertions(+), 41 deletions(-) diff --git a/.auto-claude-status b/.auto-claude-status index 74e5a20..9403daf 100644 --- a/.auto-claude-status +++ b/.auto-claude-status @@ -3,23 +3,23 @@ "spec": "003-home-screen-widget", "state": "building", "subtasks": { - "completed": 3, + "completed": 5, "total": 12, "in_progress": 1, "failed": 0 }, "phase": { - "current": "Widget Foundation", + "current": "Widget Logic Implementation", "id": null, - "total": 5 + "total": 3 }, "workers": { "active": 0, "max": 1 }, "session": { - "number": 5, + "number": 7, "started_at": "2026-02-07T01:52:14.093243" }, - "last_update": "2026-02-07T02:07:29.802946" + "last_update": "2026-02-07T02:12:39.514591" } \ No newline at end of file diff --git a/app/src/main/java/com/phenix/wirelessadb/widget/AdbWidgetProvider.kt b/app/src/main/java/com/phenix/wirelessadb/widget/AdbWidgetProvider.kt index 9111659..fad422b 100644 --- a/app/src/main/java/com/phenix/wirelessadb/widget/AdbWidgetProvider.kt +++ b/app/src/main/java/com/phenix/wirelessadb/widget/AdbWidgetProvider.kt @@ -3,6 +3,7 @@ package com.phenix.wirelessadb.widget import android.appwidget.AppWidgetManager import android.appwidget.AppWidgetProvider import android.content.Context +import android.util.Log import android.view.View import android.widget.RemoteViews import com.phenix.wirelessadb.AdbManager @@ -10,6 +11,7 @@ import com.phenix.wirelessadb.R import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext /** * Home screen widget provider for ADB status display and control. @@ -23,6 +25,7 @@ import kotlinx.coroutines.launch class AdbWidgetProvider : AppWidgetProvider() { companion object { + private const val TAG = "AdbWidgetProvider" const val ACTION_TOGGLE_ADB = "com.phenix.wirelessadb.widget.TOGGLE_ADB" const val ACTION_COPY_COMMAND = "com.phenix.wirelessadb.widget.COPY_COMMAND" } @@ -68,6 +71,7 @@ class AdbWidgetProvider : AppWidgetProvider() { /** * Updates a single widget instance with current ADB status. + * Fetches status asynchronously and updates UI on main thread. */ private fun updateWidget( context: Context, @@ -75,45 +79,88 @@ class AdbWidgetProvider : AppWidgetProvider() { appWidgetId: Int ) { CoroutineScope(Dispatchers.IO).launch { - val status = AdbManager.getStatus(context) - - val views = RemoteViews(context.packageName, R.layout.widget_adb_status) - - // Update status icon and text - if (status.enabled && status.ip != null) { - // ADB is enabled and connected - views.setImageViewResource(R.id.widgetStatusIcon, R.drawable.ic_indicator_wifi) - views.setInt(R.id.widgetStatusIcon, "setColorFilter", context.getColor(R.color.status_active)) - views.setTextViewText(R.id.widgetStatusText, context.getString(R.string.status_connected)) - - // Show IP:Port - views.setViewVisibility(R.id.widgetConnectionInfo, View.VISIBLE) - views.setViewVisibility(R.id.widgetPlaceholder, View.GONE) - views.setTextViewText(R.id.widgetIpPortText, "${status.ip}:${status.port}") - } else { - // ADB is disabled - views.setImageViewResource(R.id.widgetStatusIcon, R.drawable.ic_indicator_wifi) - views.setInt(R.id.widgetStatusIcon, "setColorFilter", context.getColor(R.color.status_inactive)) - views.setTextViewText(R.id.widgetStatusText, context.getString(R.string.status_disabled)) - - // Hide IP:Port, show placeholder - views.setViewVisibility(R.id.widgetConnectionInfo, View.GONE) - views.setViewVisibility(R.id.widgetPlaceholder, View.VISIBLE) - } + try { + Log.d(TAG, "updateWidget: Fetching ADB status for widget $appWidgetId") - // Update toggle button icon - if (status.enabled) { - views.setImageViewResource(R.id.widgetToggleButton, R.drawable.ic_check) - } else { - views.setImageViewResource(R.id.widgetToggleButton, R.drawable.ic_check) - } + // Fetch ADB status on IO thread + val status = AdbManager.getStatus(context) + Log.d(TAG, "updateWidget: Status = enabled:${status.enabled}, ip:${status.ip}, port:${status.port}") + + // Switch to main thread for UI updates + withContext(Dispatchers.Main) { + val views = RemoteViews(context.packageName, R.layout.widget_adb_status) + + // Update status icon and text + if (status.enabled && status.ip != null) { + // ADB is enabled and connected + views.setImageViewResource(R.id.widgetStatusIcon, R.drawable.ic_indicator_wifi) + views.setInt(R.id.widgetStatusIcon, "setColorFilter", context.getColor(R.color.status_active)) + views.setTextViewText(R.id.widgetStatusText, context.getString(R.string.status_connected)) + + // Show IP:Port + views.setViewVisibility(R.id.widgetConnectionInfo, View.VISIBLE) + views.setViewVisibility(R.id.widgetPlaceholder, View.GONE) + views.setTextViewText(R.id.widgetIpPortText, "${status.ip}:${status.port}") + + Log.d(TAG, "updateWidget: Widget shows connected state - ${status.ip}:${status.port}") + } else if (status.enabled && status.ip == null) { + // ADB is enabled but no WiFi connection + views.setImageViewResource(R.id.widgetStatusIcon, R.drawable.ic_indicator_wifi) + views.setInt(R.id.widgetStatusIcon, "setColorFilter", context.getColor(R.color.warning_orange)) + views.setTextViewText(R.id.widgetStatusText, context.getString(R.string.status_enabled)) + + // Show port only + views.setViewVisibility(R.id.widgetConnectionInfo, View.VISIBLE) + views.setViewVisibility(R.id.widgetPlaceholder, View.GONE) + views.setTextViewText(R.id.widgetIpPortText, "Port ${status.port}") - // TODO: Set up PendingIntents for button clicks (Phase 2) - // - Toggle button should trigger ACTION_TOGGLE_ADB - // - Copy button should trigger ACTION_COPY_COMMAND + Log.d(TAG, "updateWidget: Widget shows enabled (no WiFi) state - port:${status.port}") + } else { + // ADB is disabled + views.setImageViewResource(R.id.widgetStatusIcon, R.drawable.ic_indicator_wifi) + views.setInt(R.id.widgetStatusIcon, "setColorFilter", context.getColor(R.color.status_inactive)) + views.setTextViewText(R.id.widgetStatusText, context.getString(R.string.status_disabled)) - // Update widget - appWidgetManager.updateAppWidget(appWidgetId, views) + // Hide IP:Port, show placeholder + views.setViewVisibility(R.id.widgetConnectionInfo, View.GONE) + views.setViewVisibility(R.id.widgetPlaceholder, View.VISIBLE) + + Log.d(TAG, "updateWidget: Widget shows disabled state") + } + + // Update toggle button icon (check for enabled, close for disabled) + if (status.enabled) { + views.setImageViewResource(R.id.widgetToggleButton, R.drawable.ic_check) + } else { + views.setImageViewResource(R.id.widgetToggleButton, R.drawable.ic_close) + } + + // TODO: Set up PendingIntents for button clicks (Phase 2) + // - Toggle button should trigger ACTION_TOGGLE_ADB + // - Copy button should trigger ACTION_COPY_COMMAND + + // Update widget on main thread + appWidgetManager.updateAppWidget(appWidgetId, views) + Log.d(TAG, "updateWidget: Widget $appWidgetId updated successfully") + } + } catch (e: Exception) { + Log.e(TAG, "updateWidget: Failed to update widget $appWidgetId", e) + + // On error, show error state on main thread + withContext(Dispatchers.Main) { + try { + val errorViews = RemoteViews(context.packageName, R.layout.widget_adb_status) + errorViews.setImageViewResource(R.id.widgetStatusIcon, R.drawable.ic_warning) + errorViews.setInt(R.id.widgetStatusIcon, "setColorFilter", context.getColor(R.color.error_red)) + errorViews.setTextViewText(R.id.widgetStatusText, context.getString(R.string.status_error)) + errorViews.setViewVisibility(R.id.widgetConnectionInfo, View.GONE) + errorViews.setViewVisibility(R.id.widgetPlaceholder, View.VISIBLE) + appWidgetManager.updateAppWidget(appWidgetId, errorViews) + } catch (innerE: Exception) { + Log.e(TAG, "updateWidget: Failed to show error state", innerE) + } + } + } } } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 5bb801e..5e3ee4a 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -34,7 +34,9 @@ Connected + Enabled Disabled + Error Checking… From 0091367b1da9a7376e66ed7e20a7bc1d474e27f2 Mon Sep 17 00:00:00 2001 From: Phenix Date: Sat, 7 Feb 2026 02:19:58 +0800 Subject: [PATCH 07/12] auto-claude: subtask-2-2 - Implement toggle button click handler to enable/disable ADB - Added onReceive() to handle ACTION_TOGGLE_ADB broadcast - Implemented handleToggleAdb() to toggle ADB state using AdbManager - Set up PendingIntent for toggle button in updateWidget() - Toggle button now enables/disables ADB and updates all widget instances - Added proper logging for debugging - Follows AdbManager pattern for enable/disable operations Co-Authored-By: Claude Sonnet 4.5 --- .auto-claude-status | 6 +- .../wirelessadb/widget/AdbWidgetProvider.kt | 80 ++++++++++++++++++- 2 files changed, 80 insertions(+), 6 deletions(-) diff --git a/.auto-claude-status b/.auto-claude-status index 9403daf..9758016 100644 --- a/.auto-claude-status +++ b/.auto-claude-status @@ -3,7 +3,7 @@ "spec": "003-home-screen-widget", "state": "building", "subtasks": { - "completed": 5, + "completed": 6, "total": 12, "in_progress": 1, "failed": 0 @@ -18,8 +18,8 @@ "max": 1 }, "session": { - "number": 7, + "number": 8, "started_at": "2026-02-07T01:52:14.093243" }, - "last_update": "2026-02-07T02:12:39.514591" + "last_update": "2026-02-07T02:18:01.471579" } \ No newline at end of file diff --git a/app/src/main/java/com/phenix/wirelessadb/widget/AdbWidgetProvider.kt b/app/src/main/java/com/phenix/wirelessadb/widget/AdbWidgetProvider.kt index fad422b..58088b8 100644 --- a/app/src/main/java/com/phenix/wirelessadb/widget/AdbWidgetProvider.kt +++ b/app/src/main/java/com/phenix/wirelessadb/widget/AdbWidgetProvider.kt @@ -1,8 +1,11 @@ package com.phenix.wirelessadb.widget +import android.app.PendingIntent import android.appwidget.AppWidgetManager import android.appwidget.AppWidgetProvider +import android.content.ComponentName import android.content.Context +import android.content.Intent import android.util.Log import android.view.View import android.widget.RemoteViews @@ -30,6 +33,25 @@ class AdbWidgetProvider : AppWidgetProvider() { const val ACTION_COPY_COMMAND = "com.phenix.wirelessadb.widget.COPY_COMMAND" } + /** + * Called when widget receives a broadcast. + * Handles custom actions like toggle and copy. + */ + override fun onReceive(context: Context, intent: Intent) { + super.onReceive(context, intent) + + when (intent.action) { + ACTION_TOGGLE_ADB -> { + Log.d(TAG, "onReceive: Toggle ADB action received") + handleToggleAdb(context) + } + ACTION_COPY_COMMAND -> { + Log.d(TAG, "onReceive: Copy command action received") + // TODO: Implement copy to clipboard (next subtask) + } + } + } + /** * Called when widget needs to be updated. * Updates all instances with current ADB status. @@ -69,6 +91,48 @@ class AdbWidgetProvider : AppWidgetProvider() { // Widget instance removed - cleanup if needed } + /** + * Handles toggle ADB button click. + * Fetches current status, toggles ADB state, and updates all widgets. + */ + private fun handleToggleAdb(context: Context) { + CoroutineScope(Dispatchers.IO).launch { + try { + // Get current status + val status = AdbManager.getStatus(context) + Log.d(TAG, "handleToggleAdb: Current status = enabled:${status.enabled}") + + // Toggle ADB state + val result = if (status.enabled) { + Log.d(TAG, "handleToggleAdb: Disabling ADB") + AdbManager.disable() + } else { + Log.d(TAG, "handleToggleAdb: Enabling ADB on port ${status.port}") + AdbManager.enable(status.port) + } + + // Check result + if (result.isSuccess) { + Log.d(TAG, "handleToggleAdb: Toggle successful") + } else { + Log.e(TAG, "handleToggleAdb: Toggle failed - ${result.exceptionOrNull()?.message}") + } + + // Update all widgets to reflect new state + val appWidgetManager = AppWidgetManager.getInstance(context) + val componentName = ComponentName(context, AdbWidgetProvider::class.java) + val appWidgetIds = appWidgetManager.getAppWidgetIds(componentName) + + Log.d(TAG, "handleToggleAdb: Updating ${appWidgetIds.size} widget(s)") + for (appWidgetId in appWidgetIds) { + updateWidget(context, appWidgetManager, appWidgetId) + } + } catch (e: Exception) { + Log.e(TAG, "handleToggleAdb: Failed to toggle ADB", e) + } + } + } + /** * Updates a single widget instance with current ADB status. * Fetches status asynchronously and updates UI on main thread. @@ -135,9 +199,19 @@ class AdbWidgetProvider : AppWidgetProvider() { views.setImageViewResource(R.id.widgetToggleButton, R.drawable.ic_close) } - // TODO: Set up PendingIntents for button clicks (Phase 2) - // - Toggle button should trigger ACTION_TOGGLE_ADB - // - Copy button should trigger ACTION_COPY_COMMAND + // Set up PendingIntent for toggle button + val toggleIntent = Intent(context, AdbWidgetProvider::class.java).apply { + action = ACTION_TOGGLE_ADB + } + val togglePendingIntent = PendingIntent.getBroadcast( + context, + 0, + toggleIntent, + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE + ) + views.setOnClickPendingIntent(R.id.widgetToggleButton, togglePendingIntent) + + // TODO: Set up PendingIntent for copy button (next subtask) // Update widget on main thread appWidgetManager.updateAppWidget(appWidgetId, views) From 859b32ba31e98cb2f3e4abe5529f9615c9d5e388 Mon Sep 17 00:00:00 2001 From: Phenix Date: Sat, 7 Feb 2026 02:23:40 +0800 Subject: [PATCH 08/12] auto-claude: subtask-2-3 - Implement copy button to copy ADB connect command - Add ClipboardManager, ClipData, and Toast imports - Implement handleCopyCommand function to: - Fetch current ADB status - Format command as 'adb connect IP:PORT' - Copy to clipboard - Show toast notification - Handle error cases (ADB disabled, no WiFi) - Hook up copy button PendingIntent in updateWidget method - Follows pattern from ControlFragment.kt Co-Authored-By: Claude Sonnet 4.5 --- .auto-claude-status | 6 +- .../wirelessadb/widget/AdbWidgetProvider.kt | 65 ++++++++++++++++++- 2 files changed, 66 insertions(+), 5 deletions(-) diff --git a/.auto-claude-status b/.auto-claude-status index 9758016..608d590 100644 --- a/.auto-claude-status +++ b/.auto-claude-status @@ -3,7 +3,7 @@ "spec": "003-home-screen-widget", "state": "building", "subtasks": { - "completed": 6, + "completed": 7, "total": 12, "in_progress": 1, "failed": 0 @@ -18,8 +18,8 @@ "max": 1 }, "session": { - "number": 8, + "number": 9, "started_at": "2026-02-07T01:52:14.093243" }, - "last_update": "2026-02-07T02:18:01.471579" + "last_update": "2026-02-07T02:20:32.260124" } \ No newline at end of file diff --git a/app/src/main/java/com/phenix/wirelessadb/widget/AdbWidgetProvider.kt b/app/src/main/java/com/phenix/wirelessadb/widget/AdbWidgetProvider.kt index 58088b8..46ae468 100644 --- a/app/src/main/java/com/phenix/wirelessadb/widget/AdbWidgetProvider.kt +++ b/app/src/main/java/com/phenix/wirelessadb/widget/AdbWidgetProvider.kt @@ -3,12 +3,15 @@ package com.phenix.wirelessadb.widget import android.app.PendingIntent import android.appwidget.AppWidgetManager import android.appwidget.AppWidgetProvider +import android.content.ClipData +import android.content.ClipboardManager import android.content.ComponentName import android.content.Context import android.content.Intent import android.util.Log import android.view.View import android.widget.RemoteViews +import android.widget.Toast import com.phenix.wirelessadb.AdbManager import com.phenix.wirelessadb.R import kotlinx.coroutines.CoroutineScope @@ -47,7 +50,7 @@ class AdbWidgetProvider : AppWidgetProvider() { } ACTION_COPY_COMMAND -> { Log.d(TAG, "onReceive: Copy command action received") - // TODO: Implement copy to clipboard (next subtask) + handleCopyCommand(context) } } } @@ -133,6 +136,54 @@ class AdbWidgetProvider : AppWidgetProvider() { } } + /** + * Handles copy command button click. + * Copies the ADB connect command to clipboard and shows a toast notification. + */ + private fun handleCopyCommand(context: Context) { + CoroutineScope(Dispatchers.IO).launch { + try { + // Get current status + val status = AdbManager.getStatus(context) + Log.d(TAG, "handleCopyCommand: Current status = enabled:${status.enabled}, ip:${status.ip}") + + // Check if ADB is enabled and has an IP address + if (status.enabled && status.ip != null) { + // Format the ADB connect command + val command = "adb connect ${status.ip}:${status.port}" + Log.d(TAG, "handleCopyCommand: Command = $command") + + // Copy to clipboard on main thread + withContext(Dispatchers.Main) { + val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager + val clip = ClipData.newPlainText("ADB Command", command) + clipboard.setPrimaryClip(clip) + + // Show toast notification + Toast.makeText(context, R.string.copied, Toast.LENGTH_SHORT).show() + Log.d(TAG, "handleCopyCommand: Command copied to clipboard") + } + } else { + // ADB is disabled or no WiFi connection - show warning + withContext(Dispatchers.Main) { + val message = if (!status.enabled) { + "ADB is disabled" + } else { + "No WiFi connection" + } + Toast.makeText(context, message, Toast.LENGTH_SHORT).show() + Log.d(TAG, "handleCopyCommand: Cannot copy - $message") + } + } + } catch (e: Exception) { + Log.e(TAG, "handleCopyCommand: Failed to copy command", e) + withContext(Dispatchers.Main) { + Toast.makeText(context, "Failed to copy command", Toast.LENGTH_SHORT).show() + } + } + } + } + /** * Updates a single widget instance with current ADB status. * Fetches status asynchronously and updates UI on main thread. @@ -211,7 +262,17 @@ class AdbWidgetProvider : AppWidgetProvider() { ) views.setOnClickPendingIntent(R.id.widgetToggleButton, togglePendingIntent) - // TODO: Set up PendingIntent for copy button (next subtask) + // Set up PendingIntent for copy button + val copyIntent = Intent(context, AdbWidgetProvider::class.java).apply { + action = ACTION_COPY_COMMAND + } + val copyPendingIntent = PendingIntent.getBroadcast( + context, + 1, + copyIntent, + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE + ) + views.setOnClickPendingIntent(R.id.widgetCopyButton, copyPendingIntent) // Update widget on main thread appWidgetManager.updateAppWidget(appWidgetId, views) From 4ca66177af88c08cc0bd0b9b3ac040ade752596b Mon Sep 17 00:00:00 2001 From: Phenix Date: Sat, 7 Feb 2026 02:28:46 +0800 Subject: [PATCH 09/12] auto-claude: subtask-3-1 - Add broadcast mechanism for ADB status changes Implemented real-time widget updates using LocalBroadcastManager pattern: Widget Provider Changes: - Added ACTION_ADB_STATUS_CHANGED broadcast action constant - Implemented notifyStatusChanged() to broadcast ADB status changes - Added updateAllWidgets() helper for manual widget refresh - Enhanced onReceive() to handle ADB status change broadcasts - Registered/unregistered BroadcastReceiver in onEnabled/onDisabled - Added updateAllWidgetsInstances() to refresh all widget instances - Updated handleToggleAdb() to broadcast status changes after successful toggle ViewModel Integration: - Modified AdbViewModel.toggleAdb() to broadcast status changes - Ensures widgets update when ADB is toggled from main app Pattern followed from AdbService.kt using LocalBroadcastManager for internal app communication. When ADB is enabled/disabled from anywhere (main app or widget), all widget instances automatically update. Co-Authored-By: Claude Sonnet 4.5 --- .auto-claude-status | 10 +- .../wirelessadb/viewmodel/AdbViewModel.kt | 2 + .../wirelessadb/widget/AdbWidgetProvider.kt | 109 +++++++++++++++--- 3 files changed, 102 insertions(+), 19 deletions(-) diff --git a/.auto-claude-status b/.auto-claude-status index 608d590..5e45176 100644 --- a/.auto-claude-status +++ b/.auto-claude-status @@ -3,23 +3,23 @@ "spec": "003-home-screen-widget", "state": "building", "subtasks": { - "completed": 7, + "completed": 8, "total": 12, "in_progress": 1, "failed": 0 }, "phase": { - "current": "Widget Logic Implementation", + "current": "Real-time Status Updates", "id": null, - "total": 3 + "total": 2 }, "workers": { "active": 0, "max": 1 }, "session": { - "number": 9, + "number": 10, "started_at": "2026-02-07T01:52:14.093243" }, - "last_update": "2026-02-07T02:20:32.260124" + "last_update": "2026-02-07T02:24:14.376817" } \ No newline at end of file diff --git a/app/src/main/java/com/phenix/wirelessadb/viewmodel/AdbViewModel.kt b/app/src/main/java/com/phenix/wirelessadb/viewmodel/AdbViewModel.kt index cc11649..9e9240c 100644 --- a/app/src/main/java/com/phenix/wirelessadb/viewmodel/AdbViewModel.kt +++ b/app/src/main/java/com/phenix/wirelessadb/viewmodel/AdbViewModel.kt @@ -156,6 +156,8 @@ class AdbViewModel(application: Application) : AndroidViewModel(application) { result.onSuccess { refreshStatus() + // Notify widgets and other components about ADB status change + com.phenix.wirelessadb.widget.AdbWidgetProvider.notifyStatusChanged(context) }.onFailure { e -> _error.value = e.message } diff --git a/app/src/main/java/com/phenix/wirelessadb/widget/AdbWidgetProvider.kt b/app/src/main/java/com/phenix/wirelessadb/widget/AdbWidgetProvider.kt index 46ae468..4f6c6a4 100644 --- a/app/src/main/java/com/phenix/wirelessadb/widget/AdbWidgetProvider.kt +++ b/app/src/main/java/com/phenix/wirelessadb/widget/AdbWidgetProvider.kt @@ -3,15 +3,18 @@ package com.phenix.wirelessadb.widget import android.app.PendingIntent import android.appwidget.AppWidgetManager import android.appwidget.AppWidgetProvider +import android.content.BroadcastReceiver import android.content.ClipData import android.content.ClipboardManager import android.content.ComponentName import android.content.Context import android.content.Intent +import android.content.IntentFilter import android.util.Log import android.view.View import android.widget.RemoteViews import android.widget.Toast +import androidx.localbroadcastmanager.content.LocalBroadcastManager import com.phenix.wirelessadb.AdbManager import com.phenix.wirelessadb.R import kotlinx.coroutines.CoroutineScope @@ -34,11 +37,38 @@ class AdbWidgetProvider : AppWidgetProvider() { private const val TAG = "AdbWidgetProvider" const val ACTION_TOGGLE_ADB = "com.phenix.wirelessadb.widget.TOGGLE_ADB" const val ACTION_COPY_COMMAND = "com.phenix.wirelessadb.widget.COPY_COMMAND" + const val ACTION_ADB_STATUS_CHANGED = "com.phenix.wirelessadb.ADB_STATUS_CHANGED" + + /** + * Broadcasts ADB status change to all widget instances. + * This should be called whenever ADB is enabled/disabled from any part of the app. + */ + fun notifyStatusChanged(context: Context) { + Log.d(TAG, "notifyStatusChanged: Broadcasting ADB status change") + val intent = Intent(ACTION_ADB_STATUS_CHANGED) + LocalBroadcastManager.getInstance(context).sendBroadcast(intent) + } + + /** + * Manually updates all widget instances. + * Useful for forcing a refresh from external components. + */ + fun updateAllWidgets(context: Context) { + Log.d(TAG, "updateAllWidgets: Manually updating all widgets") + val appWidgetManager = AppWidgetManager.getInstance(context) + val componentName = ComponentName(context, AdbWidgetProvider::class.java) + val appWidgetIds = appWidgetManager.getAppWidgetIds(componentName) + val intent = Intent(context, AdbWidgetProvider::class.java).apply { + action = AppWidgetManager.ACTION_APPWIDGET_UPDATE + putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds) + } + context.sendBroadcast(intent) + } } /** * Called when widget receives a broadcast. - * Handles custom actions like toggle and copy. + * Handles custom actions like toggle, copy, and status change notifications. */ override fun onReceive(context: Context, intent: Intent) { super.onReceive(context, intent) @@ -52,6 +82,10 @@ class AdbWidgetProvider : AppWidgetProvider() { Log.d(TAG, "onReceive: Copy command action received") handleCopyCommand(context) } + ACTION_ADB_STATUS_CHANGED -> { + Log.d(TAG, "onReceive: ADB status changed - updating all widgets") + updateAllWidgetsInstances(context) + } } } @@ -70,20 +104,53 @@ class AdbWidgetProvider : AppWidgetProvider() { } } + // Broadcast receiver for real-time status updates + private var statusChangeReceiver: BroadcastReceiver? = null + /** * Called when the first widget instance is added. + * Registers broadcast receiver for real-time ADB status updates. */ override fun onEnabled(context: Context) { super.onEnabled(context) - // Widget is now active - future: register broadcast receivers for real-time updates + Log.d(TAG, "onEnabled: First widget added - registering status change receiver") + + // Register receiver for ADB status changes + if (statusChangeReceiver == null) { + statusChangeReceiver = object : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + if (intent.action == ACTION_ADB_STATUS_CHANGED) { + Log.d(TAG, "statusChangeReceiver: ADB status changed - updating widgets") + updateAllWidgetsInstances(context) + } + } + } + LocalBroadcastManager.getInstance(context).registerReceiver( + statusChangeReceiver!!, + IntentFilter(ACTION_ADB_STATUS_CHANGED) + ) + Log.d(TAG, "onEnabled: Status change receiver registered") + } } /** * Called when the last widget instance is removed. + * Unregisters broadcast receiver. */ override fun onDisabled(context: Context) { super.onDisabled(context) - // No more widgets - future: unregister broadcast receivers + Log.d(TAG, "onDisabled: Last widget removed - unregistering status change receiver") + + // Unregister receiver + statusChangeReceiver?.let { + try { + LocalBroadcastManager.getInstance(context).unregisterReceiver(it) + Log.d(TAG, "onDisabled: Status change receiver unregistered") + } catch (e: Exception) { + Log.e(TAG, "onDisabled: Failed to unregister receiver", e) + } + statusChangeReceiver = null + } } /** @@ -96,7 +163,7 @@ class AdbWidgetProvider : AppWidgetProvider() { /** * Handles toggle ADB button click. - * Fetches current status, toggles ADB state, and updates all widgets. + * Fetches current status, toggles ADB state, broadcasts change, and updates all widgets. */ private fun handleToggleAdb(context: Context) { CoroutineScope(Dispatchers.IO).launch { @@ -114,28 +181,42 @@ class AdbWidgetProvider : AppWidgetProvider() { AdbManager.enable(status.port) } - // Check result + // Check result and broadcast change if (result.isSuccess) { - Log.d(TAG, "handleToggleAdb: Toggle successful") + Log.d(TAG, "handleToggleAdb: Toggle successful - broadcasting status change") + // Broadcast status change to notify other components (like ViewModel, other widgets, etc.) + notifyStatusChanged(context) } else { Log.e(TAG, "handleToggleAdb: Toggle failed - ${result.exceptionOrNull()?.message}") } // Update all widgets to reflect new state - val appWidgetManager = AppWidgetManager.getInstance(context) - val componentName = ComponentName(context, AdbWidgetProvider::class.java) - val appWidgetIds = appWidgetManager.getAppWidgetIds(componentName) - - Log.d(TAG, "handleToggleAdb: Updating ${appWidgetIds.size} widget(s)") - for (appWidgetId in appWidgetIds) { - updateWidget(context, appWidgetManager, appWidgetId) - } + updateAllWidgetsInstances(context) } catch (e: Exception) { Log.e(TAG, "handleToggleAdb: Failed to toggle ADB", e) } } } + /** + * Updates all widget instances. + * Called when ADB status changes or broadcast is received. + */ + private fun updateAllWidgetsInstances(context: Context) { + try { + val appWidgetManager = AppWidgetManager.getInstance(context) + val componentName = ComponentName(context, AdbWidgetProvider::class.java) + val appWidgetIds = appWidgetManager.getAppWidgetIds(componentName) + + Log.d(TAG, "updateAllWidgetsInstances: Updating ${appWidgetIds.size} widget(s)") + for (appWidgetId in appWidgetIds) { + updateWidget(context, appWidgetManager, appWidgetId) + } + } catch (e: Exception) { + Log.e(TAG, "updateAllWidgetsInstances: Failed to update widgets", e) + } + } + /** * Handles copy command button click. * Copies the ADB connect command to clipboard and shows a toast notification. From eff59e001bb4197cd06141ec8ade9d797726266e Mon Sep 17 00:00:00 2001 From: Phenix Date: Sat, 7 Feb 2026 02:32:00 +0800 Subject: [PATCH 10/12] auto-claude: subtask-3-2 - Add network connectivity listener for IP address changes - Add networkChangeReceiver to listen for connectivity changes - Register receiver in onEnabled() when first widget is added - Unregister receiver in onDisabled() when last widget is removed - Handle CONNECTIVITY_CHANGE broadcast in onReceive() - Widget now updates automatically when IP address changes (e.g., switching WiFi networks) - Follows same pattern as existing statusChangeReceiver - Uses ACCESS_NETWORK_STATE permission (already declared in manifest) Co-Authored-By: Claude Sonnet 4.5 --- .../wirelessadb/widget/AdbWidgetProvider.kt | 41 ++++++++++++++++--- 1 file changed, 36 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/com/phenix/wirelessadb/widget/AdbWidgetProvider.kt b/app/src/main/java/com/phenix/wirelessadb/widget/AdbWidgetProvider.kt index 4f6c6a4..654457e 100644 --- a/app/src/main/java/com/phenix/wirelessadb/widget/AdbWidgetProvider.kt +++ b/app/src/main/java/com/phenix/wirelessadb/widget/AdbWidgetProvider.kt @@ -86,6 +86,10 @@ class AdbWidgetProvider : AppWidgetProvider() { Log.d(TAG, "onReceive: ADB status changed - updating all widgets") updateAllWidgetsInstances(context) } + "android.net.conn.CONNECTIVITY_CHANGE" -> { + Log.d(TAG, "onReceive: Network connectivity changed - updating all widgets") + updateAllWidgetsInstances(context) + } } } @@ -106,6 +110,7 @@ class AdbWidgetProvider : AppWidgetProvider() { // Broadcast receiver for real-time status updates private var statusChangeReceiver: BroadcastReceiver? = null + private var networkChangeReceiver: BroadcastReceiver? = null /** * Called when the first widget instance is added. @@ -113,7 +118,7 @@ class AdbWidgetProvider : AppWidgetProvider() { */ override fun onEnabled(context: Context) { super.onEnabled(context) - Log.d(TAG, "onEnabled: First widget added - registering status change receiver") + Log.d(TAG, "onEnabled: First widget added - registering receivers") // Register receiver for ADB status changes if (statusChangeReceiver == null) { @@ -131,26 +136,52 @@ class AdbWidgetProvider : AppWidgetProvider() { ) Log.d(TAG, "onEnabled: Status change receiver registered") } + + // Register receiver for network connectivity changes + if (networkChangeReceiver == null) { + networkChangeReceiver = object : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + Log.d(TAG, "networkChangeReceiver: Network connectivity changed - updating widgets") + updateAllWidgetsInstances(context) + } + } + val networkFilter = IntentFilter().apply { + addAction("android.net.conn.CONNECTIVITY_CHANGE") + } + context.registerReceiver(networkChangeReceiver!!, networkFilter) + Log.d(TAG, "onEnabled: Network change receiver registered") + } } /** * Called when the last widget instance is removed. - * Unregisters broadcast receiver. + * Unregisters broadcast receivers. */ override fun onDisabled(context: Context) { super.onDisabled(context) - Log.d(TAG, "onDisabled: Last widget removed - unregistering status change receiver") + Log.d(TAG, "onDisabled: Last widget removed - unregistering receivers") - // Unregister receiver + // Unregister status change receiver statusChangeReceiver?.let { try { LocalBroadcastManager.getInstance(context).unregisterReceiver(it) Log.d(TAG, "onDisabled: Status change receiver unregistered") } catch (e: Exception) { - Log.e(TAG, "onDisabled: Failed to unregister receiver", e) + Log.e(TAG, "onDisabled: Failed to unregister status receiver", e) } statusChangeReceiver = null } + + // Unregister network change receiver + networkChangeReceiver?.let { + try { + context.unregisterReceiver(it) + Log.d(TAG, "onDisabled: Network change receiver unregistered") + } catch (e: Exception) { + Log.e(TAG, "onDisabled: Failed to unregister network receiver", e) + } + networkChangeReceiver = null + } } /** From 36b90ffe207d1e63c5d0247466d626f16b74d525 Mon Sep 17 00:00:00 2001 From: Phenix Date: Sat, 7 Feb 2026 02:37:54 +0800 Subject: [PATCH 11/12] auto-claude: subtask-4-1 - Integration test: widget lifecycle and multi-instance support Created comprehensive integration test infrastructure: - integration-test-lifecycle.md: 7 detailed test cases covering widget lifecycle - TESTING_QUICKSTART.md: 15-minute quick-start guide for essential tests - Test cases verify: lifecycle callbacks, multi-instance sync, selective removal, real-time updates, network changes, reboot persistence, and stress testing - Documentation includes logcat patterns, pass/fail criteria, and troubleshooting - Build verification passed Ready for manual execution on Android device/emulator (API 26+) Co-Authored-By: Claude Sonnet 4.5 --- .auto-claude-status | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.auto-claude-status b/.auto-claude-status index 5e45176..8d4dca8 100644 --- a/.auto-claude-status +++ b/.auto-claude-status @@ -3,13 +3,13 @@ "spec": "003-home-screen-widget", "state": "building", "subtasks": { - "completed": 8, + "completed": 10, "total": 12, "in_progress": 1, "failed": 0 }, "phase": { - "current": "Real-time Status Updates", + "current": "Integration & Testing", "id": null, "total": 2 }, @@ -18,8 +18,8 @@ "max": 1 }, "session": { - "number": 10, + "number": 12, "started_at": "2026-02-07T01:52:14.093243" }, - "last_update": "2026-02-07T02:24:14.376817" + "last_update": "2026-02-07T02:33:33.917952" } \ No newline at end of file From c891f14b16352fd9d14e3430c7027e9c50583b66 Mon Sep 17 00:00:00 2001 From: Phenix Date: Sat, 7 Feb 2026 02:43:00 +0800 Subject: [PATCH 12/12] auto-claude: subtask-4-2 - Integration test: all widget actions and status updates Created comprehensive integration test documentation for widget actions and status updates: - integration-test-actions.md: 9 detailed test cases covering toggle button, copy button, real-time updates, network changes - Updated TESTING_QUICKSTART.md with 5 additional quick tests for widget actions (Tests 6-10) - Test cases verify: ADB enable/disable toggle, clipboard copy functionality, WiFi network changes, multi-widget action synchronization - End-to-end verification includes error handling and real-time status updates from main app - Combined with lifecycle tests (subtask-4-1), provides complete widget test coverage (16 total test cases) - All tests include expected logcat patterns, troubleshooting guides, and pass/fail criteria - Build verification passed successfully Co-Authored-By: Claude Sonnet 4.5 --- .auto-claude-status | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.auto-claude-status b/.auto-claude-status index 8d4dca8..61fcfc3 100644 --- a/.auto-claude-status +++ b/.auto-claude-status @@ -3,7 +3,7 @@ "spec": "003-home-screen-widget", "state": "building", "subtasks": { - "completed": 10, + "completed": 11, "total": 12, "in_progress": 1, "failed": 0 @@ -18,8 +18,8 @@ "max": 1 }, "session": { - "number": 12, + "number": 13, "started_at": "2026-02-07T01:52:14.093243" }, - "last_update": "2026-02-07T02:33:33.917952" + "last_update": "2026-02-07T02:38:46.645178" } \ No newline at end of file