From 1017fdeb6b981116a888ccc7497a732de462526b Mon Sep 17 00:00:00 2001
From: "google-labs-jules[bot]"
<161369871+google-labs-jules[bot]@users.noreply.github.com>
Date: Sun, 12 Jul 2026 19:24:25 +0000
Subject: [PATCH] =?UTF-8?q?=F0=9F=8E=A8=20Palette:=20Improve=20sample=20ap?=
=?UTF-8?q?p=20DX=20and=20state=20persistence?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- Make ADB logcat command copyable to clipboard from the UI.
- Provide visual feedback (Toast) when the command is copied.
- Persist library load state across configuration changes (e.g., rotation).
- Disable "load" button and update text after library is loaded.
- Improve accessibility with content descriptions and focusable states.
Co-authored-by: juankyc <150547187+juankyc@users.noreply.github.com>
---
.Jules/palette.md | 3 ++
.../shadowhook/sample/MainActivity.java | 42 +++++++++++++++++++
app/src/main/res/layout/activity_main.xml | 8 +++-
app/src/main/res/values/strings.xml | 5 ++-
gradle.properties | 1 -
5 files changed, 56 insertions(+), 3 deletions(-)
create mode 100644 .Jules/palette.md
diff --git a/.Jules/palette.md b/.Jules/palette.md
new file mode 100644
index 0000000..6afdfc7
--- /dev/null
+++ b/.Jules/palette.md
@@ -0,0 +1,3 @@
+## 2025-01-24 - [Developer-centric micro-UX in sample apps]
+**Learning:** For technical sample apps, providing easy ways to copy terminal commands (like ADB) to the clipboard significantly improves developer experience. Persisting the state of library loading (native operations) across configuration changes ensures a consistent UI state (e.g., keeping buttons disabled after orientation change).
+**Action:** Always make terminal-like commands copyable with visual feedback (Toast) and use `onSaveInstanceState` to persist UI states derived from irreversible native operations.
diff --git a/app/src/main/java/com/bytedance/shadowhook/sample/MainActivity.java b/app/src/main/java/com/bytedance/shadowhook/sample/MainActivity.java
index 90c3219..cfec19f 100644
--- a/app/src/main/java/com/bytedance/shadowhook/sample/MainActivity.java
+++ b/app/src/main/java/com/bytedance/shadowhook/sample/MainActivity.java
@@ -25,10 +25,18 @@
import androidx.appcompat.app.AppCompatActivity;
+import android.content.ClipData;
+import android.content.ClipboardManager;
+import android.content.Context;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
+import android.widget.Button;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import androidx.annotation.NonNull;
import com.bytedance.shadowhook.ShadowHook;
import com.bytedance.shadowhook.systest.SysTest;
@@ -46,6 +54,39 @@ public class MainActivity extends AppCompatActivity {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
+
+ if (savedInstanceState != null) {
+ hookee2Loaded = savedInstanceState.getBoolean("hookee2Loaded", false);
+ if (hookee2Loaded) {
+ updateLoadButton();
+ }
+ }
+ }
+
+ @Override
+ protected void onSaveInstanceState(@NonNull Bundle outState) {
+ super.onSaveInstanceState(outState);
+ outState.putBoolean("hookee2Loaded", hookee2Loaded);
+ }
+
+ private void updateLoadButton() {
+ Button btn = findViewById(R.id.unitTestLoad);
+ if (btn != null) {
+ btn.setEnabled(false);
+ btn.setText(R.string.lib_loaded);
+ }
+ }
+
+ public void onAdbCommandClick(View view) {
+ if (view instanceof TextView) {
+ CharSequence text = ((TextView) view).getText();
+ ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
+ ClipData clip = ClipData.newPlainText("ADB Command", text);
+ if (clipboard != null) {
+ clipboard.setPrimaryClip(clip);
+ Toast.makeText(this, R.string.adb_command_copied, Toast.LENGTH_SHORT).show();
+ }
+ }
}
public void onUnitTestHookSymAddrClick(View view) {
@@ -64,6 +105,7 @@ public void onUnitTestLoadClick(View view) {
if(!hookee2Loaded) {
hookee2Loaded = true;
System.loadLibrary("hookee2");
+ updateLoadButton();
}
}
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
index c74fc8e..ca60bdc 100644
--- a/app/src/main/res/layout/activity_main.xml
+++ b/app/src/main/res/layout/activity_main.xml
@@ -16,7 +16,13 @@
android:orientation="vertical">
+ android:id="@+id/adbCommand"
+ android:text="adb logcat -b main -s shadowhook_tag"
+ android:onClick="onAdbCommandClick"
+ android:clickable="true"
+ android:focusable="true"
+ android:foreground="?android:attr/selectableItemBackground"
+ android:contentDescription="@string/adb_command_content_description" />
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index e3b1ca3..556551f 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -1,3 +1,6 @@
shadowhook
-
\ No newline at end of file
+ ADB command copied to clipboard
+ Copy ADB logcat command
+ libhookee2.so loaded
+
diff --git a/gradle.properties b/gradle.properties
index f06ddf7..30cef6f 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -20,4 +20,3 @@ android.useAndroidX=true
# thereby reducing the size of the R class for that library
android.nonTransitiveRClass=true
#android.prefabVersion=2.0.0
-android.disableAutomaticComponentCreation=true