diff --git a/.Jules/palette.md b/.Jules/palette.md
new file mode 100644
index 0000000..2373265
--- /dev/null
+++ b/.Jules/palette.md
@@ -0,0 +1,3 @@
+## 2025-05-14 - [State Persistence and Accessibility in Sample Apps]
+**Learning:** In Android sample apps, binary UI states (like library loading) must be persisted across configuration changes (e.g., rotation) using `onSaveInstanceState` to maintain a consistent UI. For interactive elements like copyable text, the `contentDescription` should describe the action (e.g., "Copy command") rather than repeating the text itself.
+**Action:** Always implement state persistence for UI-critical flags and provide action-oriented accessibility descriptions for interactive elements.
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..2b7a87d 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,17 @@
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 androidx.annotation.NonNull;
import android.util.Log;
import android.view.View;
+import android.widget.Button;
+import android.widget.TextView;
+import android.widget.Toast;
import com.bytedance.shadowhook.ShadowHook;
import com.bytedance.shadowhook.systest.SysTest;
@@ -46,6 +53,29 @@ 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) {
+ Button btn = findViewById(R.id.unitTestLoad);
+ btn.setEnabled(false);
+ btn.setText(R.string.lib_hookee2_loaded);
+ }
+
+ TextView adbCommand = findViewById(R.id.adb_command);
+ adbCommand.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
+ ClipData clip = ClipData.newPlainText("ADB command", getString(R.string.adb_log_command));
+ if (clipboard != null) {
+ clipboard.setPrimaryClip(clip);
+ Toast.makeText(MainActivity.this, R.string.adb_log_copied, Toast.LENGTH_SHORT).show();
+ }
+ }
+ });
}
public void onUnitTestHookSymAddrClick(View view) {
@@ -64,6 +94,10 @@ public void onUnitTestLoadClick(View view) {
if(!hookee2Loaded) {
hookee2Loaded = true;
System.loadLibrary("hookee2");
+
+ Button btn = (Button) view;
+ btn.setEnabled(false);
+ btn.setText(R.string.lib_hookee2_loaded);
}
}
@@ -93,6 +127,12 @@ public void onGetRecordsClick(View view) {
}
}
+ @Override
+ protected void onSaveInstanceState(@NonNull Bundle outState) {
+ super.onSaveInstanceState(outState);
+ outState.putBoolean("hookee2Loaded", hookee2Loaded);
+ }
+
public void onDumpRecordsClick(View view) {
String pathname = getApplicationContext().getFilesDir() + "/shadowhook_records.txt";
NativeHandler.nativeDumpRecords(pathname);
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
index c74fc8e..386c6d5 100644
--- a/app/src/main/res/layout/activity_main.xml
+++ b/app/src/main/res/layout/activity_main.xml
@@ -16,7 +16,12 @@
android:orientation="vertical">
+ android:id="@+id/adb_command"
+ android:clickable="true"
+ android:focusable="true"
+ android:foreground="?android:attr/selectableItemBackground"
+ android:contentDescription="@string/copy_adb_log_command"
+ android:text="@string/adb_log_command" />
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index e3b1ca3..6dc708a 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -1,3 +1,7 @@
shadowhook
+ adb logcat -b main -s shadowhook_tag
+ Copy ADB logcat command
+ ADB command copied to clipboard
+ libhookee2.so loaded
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
index 606ebdd..940fa6c 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,10 +1,10 @@
plugins {
- id 'com.android.application' version '7.4.2' apply false
- id 'com.android.library' version '7.4.2' apply false
+ id 'com.android.application' version '8.2.2' apply false
+ id 'com.android.library' version '8.2.2' apply false
}
task clean(type: Delete) {
- delete rootProject.buildDir
+ delete rootProject.layout.buildDirectory
}
ext {
@@ -12,7 +12,7 @@ ext {
compileSdkVersion = 34
targetSdkVersion = 34
buildToolsVersion = '34.0.0'
- javaVersion = JavaVersion.VERSION_1_7
+ javaVersion = JavaVersion.VERSION_1_8
ndkVersion = "23.2.8568313"
cmakeVersion = "3.22.1"
abiFilters = "armeabi-v7a,arm64-v8a"
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
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 927b535..2fdb227 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
#Thu Dec 30 21:41:31 CST 2021
distributionBase=GRADLE_USER_HOME
-distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME