-
Notifications
You must be signed in to change notification settings - Fork 17
DRAFT: feat: webview commands for listing and evaluating js #229
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 6 commits
9ce4f38
8dabdb1
3945a67
36729b8
cef8b30
559d39d
a6e7d1e
37687ee
d8109a6
46f452c
e6a0c33
98d936d
7808755
31386a7
4abe35b
f42b53c
581330f
b1cfde6
c6d1eea
49d63ee
c74d7b7
c0e2d01
0391621
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| package agents | ||
|
|
||
| import _ "embed" | ||
|
|
||
| //go:embed android/devicekit.so | ||
| var AndroidDevicekitSO []byte | ||
|
|
||
| //go:embed android/devicekit.dex | ||
| var AndroidDevicekitDEX []byte | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,151 @@ | ||||||||||||||||||||||
| #include <jvmti.h> | ||||||||||||||||||||||
| #include <jni.h> | ||||||||||||||||||||||
| #include <android/log.h> | ||||||||||||||||||||||
| #include <string.h> | ||||||||||||||||||||||
| #include <stdlib.h> | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| #define TAG "devicekit" | ||||||||||||||||||||||
| #define LOG(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__) | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| /* ── load devicekit.dex into the target process ─────────────────────────── */ | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| /* dex_path — full path to devicekit.dex on the device | ||||||||||||||||||||||
| opt_dir — directory used by DexClassLoader for optimised odex output; | ||||||||||||||||||||||
| typically the same directory that contains the dex */ | ||||||||||||||||||||||
| static void bootstrap(JNIEnv *env, const char *dex_path, const char *opt_dir) { | ||||||||||||||||||||||
| jclass cls_CL = (*env)->FindClass(env, "java/lang/ClassLoader"); | ||||||||||||||||||||||
| jclass cls_DCL = (*env)->FindClass(env, "dalvik/system/DexClassLoader"); | ||||||||||||||||||||||
| if (!cls_CL || !cls_DCL || (*env)->ExceptionCheck(env)) { | ||||||||||||||||||||||
| (*env)->ExceptionClear(env); | ||||||||||||||||||||||
| return; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| jmethodID getSys = (*env)->GetStaticMethodID(env, cls_CL, "getSystemClassLoader", "()Ljava/lang/ClassLoader;"); | ||||||||||||||||||||||
| jmethodID init = (*env)->GetMethodID(env, cls_DCL, "<init>", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/ClassLoader;)V"); | ||||||||||||||||||||||
| jmethodID load = (*env)->GetMethodID(env, cls_CL, "loadClass", "(Ljava/lang/String;)Ljava/lang/Class;"); | ||||||||||||||||||||||
| if (!getSys || !init || !load) { | ||||||||||||||||||||||
| (*env)->ExceptionClear(env); | ||||||||||||||||||||||
| return; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| jobject parent = (*env)->CallStaticObjectMethod(env, cls_CL, getSys); | ||||||||||||||||||||||
| jstring j_dex = (*env)->NewStringUTF(env, dex_path); | ||||||||||||||||||||||
| jstring j_opt = (*env)->NewStringUTF(env, opt_dir); | ||||||||||||||||||||||
| jobject loader = (*env)->NewObject(env, cls_DCL, init, j_dex, j_opt, NULL, parent); | ||||||||||||||||||||||
| (*env)->DeleteLocalRef(env, j_dex); | ||||||||||||||||||||||
| (*env)->DeleteLocalRef(env, j_opt); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| if (!loader || (*env)->ExceptionCheck(env)) { | ||||||||||||||||||||||
| jthrowable exc = (*env)->ExceptionOccurred(env); | ||||||||||||||||||||||
| (*env)->ExceptionClear(env); | ||||||||||||||||||||||
| if (exc) { | ||||||||||||||||||||||
| jclass ecls = (*env)->GetObjectClass(env, exc); | ||||||||||||||||||||||
| jmethodID toStr = (*env)->GetMethodID(env, ecls, "toString", "()Ljava/lang/String;"); | ||||||||||||||||||||||
| if (toStr) { | ||||||||||||||||||||||
| jstring msg = (jstring)(*env)->CallObjectMethod(env, exc, toStr); | ||||||||||||||||||||||
| if (msg && !(*env)->ExceptionCheck(env)) { | ||||||||||||||||||||||
| const char *s = (*env)->GetStringUTFChars(env, msg, NULL); | ||||||||||||||||||||||
| LOG("DexClassLoader exception: %s", s ? s : "(null)"); | ||||||||||||||||||||||
| if (s) (*env)->ReleaseStringUTFChars(env, msg, s); | ||||||||||||||||||||||
| } else (*env)->ExceptionClear(env); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| LOG("DexClassLoader failed — push devicekit.dex to %s", dex_path); | ||||||||||||||||||||||
| return; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| jstring j_cls = (*env)->NewStringUTF(env, "com.mobilenext.mobilecli.MobileCliAgent"); | ||||||||||||||||||||||
| jclass agentCls = (jclass)(*env)->CallObjectMethod(env, loader, load, j_cls); | ||||||||||||||||||||||
| (*env)->DeleteLocalRef(env, j_cls); | ||||||||||||||||||||||
| if (!agentCls || (*env)->ExceptionCheck(env)) { | ||||||||||||||||||||||
| jthrowable exc = (*env)->ExceptionOccurred(env); | ||||||||||||||||||||||
| (*env)->ExceptionClear(env); | ||||||||||||||||||||||
| if (exc) { | ||||||||||||||||||||||
| jclass ecls = (*env)->GetObjectClass(env, exc); | ||||||||||||||||||||||
| jmethodID toStr = (*env)->GetMethodID(env, ecls, "toString", "()Ljava/lang/String;"); | ||||||||||||||||||||||
| if (toStr) { | ||||||||||||||||||||||
| jstring msg = (jstring)(*env)->CallObjectMethod(env, exc, toStr); | ||||||||||||||||||||||
| if (msg && !(*env)->ExceptionCheck(env)) { | ||||||||||||||||||||||
| const char *s = (*env)->GetStringUTFChars(env, msg, NULL); | ||||||||||||||||||||||
| LOG("loadClass exception: %s", s ? s : "(null)"); | ||||||||||||||||||||||
| if (s) (*env)->ReleaseStringUTFChars(env, msg, s); | ||||||||||||||||||||||
| } else { | ||||||||||||||||||||||
| (*env)->ExceptionClear(env); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| LOG("loadClass(MobileCliAgent) failed"); | ||||||||||||||||||||||
| return; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| jmethodID start = (*env)->GetStaticMethodID(env, agentCls, "start", "()V"); | ||||||||||||||||||||||
| if (!start || (*env)->ExceptionCheck(env)) { | ||||||||||||||||||||||
| (*env)->ExceptionClear(env); | ||||||||||||||||||||||
| return; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| (*env)->CallStaticVoidMethod(env, agentCls, start); | ||||||||||||||||||||||
| if ((*env)->ExceptionCheck(env)) { | ||||||||||||||||||||||
| jthrowable exc = (*env)->ExceptionOccurred(env); | ||||||||||||||||||||||
| (*env)->ExceptionClear(env); | ||||||||||||||||||||||
| if (exc) { | ||||||||||||||||||||||
| jclass ecls = (*env)->GetObjectClass(env, exc); | ||||||||||||||||||||||
| jmethodID toStr = (*env)->GetMethodID(env, ecls, "toString", "()Ljava/lang/String;"); | ||||||||||||||||||||||
| if (toStr) { | ||||||||||||||||||||||
| jstring msg = (jstring)(*env)->CallObjectMethod(env, exc, toStr); | ||||||||||||||||||||||
| if (msg && !(*env)->ExceptionCheck(env)) { | ||||||||||||||||||||||
| const char *s = (*env)->GetStringUTFChars(env, msg, NULL); | ||||||||||||||||||||||
| LOG("MobileCliAgent.start() threw: %s", s ? s : "(null)"); | ||||||||||||||||||||||
| if (s) (*env)->ReleaseStringUTFChars(env, msg, s); | ||||||||||||||||||||||
| } else { | ||||||||||||||||||||||
| (*env)->ExceptionClear(env); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| LOG("MobileCliAgent started"); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| /* ── agent entry points ──────────────────────────────────────────────────── */ | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| /* opts is the dex path passed via: am attach-agent <pid> agent.so=<dex_path> | ||||||||||||||||||||||
| opt_dir is derived as the directory containing the dex file. */ | ||||||||||||||||||||||
| static jint setup(JavaVM *vm, const char *opts) { | ||||||||||||||||||||||
| if (!opts || opts[0] == '\0') { | ||||||||||||||||||||||
| LOG("no dex path provided — pass it as agent.so=/path/to/devicekit.dex"); | ||||||||||||||||||||||
| return JNI_ERR; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| const char *dex_path = opts; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| /* derive opt_dir as dirname(dex_path) */ | ||||||||||||||||||||||
| char opt_dir[512]; | ||||||||||||||||||||||
| const char *slash = strrchr(dex_path, '/'); | ||||||||||||||||||||||
| if (slash && slash != dex_path) { | ||||||||||||||||||||||
| size_t len = (size_t)(slash - dex_path); | ||||||||||||||||||||||
| if (len >= sizeof(opt_dir)) { len = sizeof(opt_dir) - 1; } | ||||||||||||||||||||||
| memcpy(opt_dir, dex_path, len); | ||||||||||||||||||||||
| opt_dir[len] = '\0'; | ||||||||||||||||||||||
| } else { | ||||||||||||||||||||||
| opt_dir[0] = '.'; | ||||||||||||||||||||||
| opt_dir[1] = '\0'; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| JNIEnv *env = NULL; | ||||||||||||||||||||||
| (*vm)->AttachCurrentThread(vm, (void **) &env, NULL); | ||||||||||||||||||||||
| bootstrap(env, dex_path, opt_dir); | ||||||||||||||||||||||
|
Comment on lines
+136
to
+138
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Check If Suggested fix JNIEnv *env = NULL;
- (*vm)->AttachCurrentThread(vm, (void **) &env, NULL);
+ jint rc = (*vm)->AttachCurrentThread(vm, (void **) &env, NULL);
+ if (rc != JNI_OK || env == NULL) {
+ LOG("AttachCurrentThread failed: %d", rc);
+ return JNI_ERR;
+ }
bootstrap(env, dex_path, opt_dir);📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||
| LOG("agent ready (dex=%s)", dex_path); | ||||||||||||||||||||||
| return JNI_OK; | ||||||||||||||||||||||
|
Comment on lines
+88
to
+140
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Return
Suggested fix-static void bootstrap(JNIEnv *env, const char *dex_path, const char *opt_dir) {
+static jint bootstrap(JNIEnv *env, const char *dex_path, const char *opt_dir) {
jclass cls_CL = (*env)->FindClass(env, "java/lang/ClassLoader");
jclass cls_DCL = (*env)->FindClass(env, "dalvik/system/DexClassLoader");
if (!cls_CL || !cls_DCL || (*env)->ExceptionCheck(env)) {
(*env)->ExceptionClear(env);
- return;
+ return JNI_ERR;
}
@@
if (!getSys || !init || !load) {
(*env)->ExceptionClear(env);
- return;
+ return JNI_ERR;
}
@@
LOG("DexClassLoader failed — push devicekit.dex to %s", dex_path);
- return;
+ return JNI_ERR;
}
@@
LOG("loadClass(MobileCliAgent) failed");
- return;
+ return JNI_ERR;
}
@@
if (!start || (*env)->ExceptionCheck(env)) {
(*env)->ExceptionClear(env);
- return;
+ return JNI_ERR;
}
@@
if ((*env)->ExceptionCheck(env)) {
@@
+ return JNI_ERR;
}
LOG("MobileCliAgent started");
+ return JNI_OK;
}
@@
- bootstrap(env, dex_path, opt_dir);
+ if (bootstrap(env, dex_path, opt_dir) != JNI_OK) {
+ return JNI_ERR;
+ }
LOG("agent ready (dex=%s)", dex_path);
return JNI_OK;🤖 Prompt for AI Agents |
||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| JNIEXPORT jint | ||||||||||||||||||||||
| JNICALL Agent_OnLoad(JavaVM *vm, char *opts, void *reserved) { | ||||||||||||||||||||||
| return setup(vm, opts); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| JNIEXPORT jint | ||||||||||||||||||||||
| JNICALL Agent_OnAttach(JavaVM *vm, char *opts, void *reserved) { | ||||||||||||||||||||||
| return setup(vm, opts); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
Repository: mobile-next/mobilecli
Length of output: 1958
🏁 Script executed:
Repository: mobile-next/mobilecli
Length of output: 82
🏁 Script executed:
Repository: mobile-next/mobilecli
Length of output: 239
🏁 Script executed:
Repository: mobile-next/mobilecli
Length of output: 289
Missing embed targets cause compile failure
Both
android/devicekit.so(line 5) andandroid/devicekit.dex(line 8) are missing from the repository. The//go:embeddirectives will fail at build time until these artifacts are added.🧰 Tools
🪛 GitHub Check: test
[failure] 8-8:
pattern android/devicekit.dex: no matching files found
🤖 Prompt for AI Agents