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