diff --git a/.gitignore b/.gitignore index 363698e..551803b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,9 @@ *.ast *.apk +*.jar +*.dex +*.class examples/sensors/sensors +examples/jni/jni +examples/dex/dex +examples/permissions/permissions diff --git a/examples/dex/bindata.go b/examples/dex/bindata.go new file mode 100644 index 0000000..91a8cd7 --- /dev/null +++ b/examples/dex/bindata.go @@ -0,0 +1,237 @@ +// Code generated by go-bindata. +// sources: +// java/classes.dex +// DO NOT EDIT! + +package main + +import ( + "bytes" + "compress/gzip" + "fmt" + "io" + "io/ioutil" + "os" + "path/filepath" + "strings" + "time" +) + +func bindataRead(data []byte, name string) ([]byte, error) { + gz, err := gzip.NewReader(bytes.NewBuffer(data)) + if err != nil { + return nil, fmt.Errorf("Read %q: %v", name, err) + } + + var buf bytes.Buffer + _, err = io.Copy(&buf, gz) + clErr := gz.Close() + + if err != nil { + return nil, fmt.Errorf("Read %q: %v", name, err) + } + if clErr != nil { + return nil, err + } + + return buf.Bytes(), nil +} + +type asset struct { + bytes []byte + info os.FileInfo +} + +type bindataFileInfo struct { + name string + size int64 + mode os.FileMode + modTime time.Time +} + +func (fi bindataFileInfo) Name() string { + return fi.name +} +func (fi bindataFileInfo) Size() int64 { + return fi.size +} +func (fi bindataFileInfo) Mode() os.FileMode { + return fi.mode +} +func (fi bindataFileInfo) ModTime() time.Time { + return fi.modTime +} +func (fi bindataFileInfo) IsDir() bool { + return false +} +func (fi bindataFileInfo) Sys() interface{} { + return nil +} + +var _javaClassesDex = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x5c\x91\xbf\x4e\xc2\x50\x14\xc6\x7f\xa7\xe5\x4f\x22\x06\x11\x4d\x5c\x88\xe9\xa0\xab\x57\x23\x4e\x18\x13\xe3\x22\x09\xd1\x8d\xa5\x53\x15\x24\x25\x88\xc4\x56\x61\xd4\x84\x91\x47\xd0\xcd\x47\xf0\x11\x1c\x1c\x1c\xf4\x21\xdc\x58\xdc\x1d\xcc\xed\xbd\x40\xe3\x49\xda\xef\xe4\xf4\x9e\xef\x9c\xfe\x6e\xab\x3d\x5a\xda\xdd\x3f\xa0\x5e\xfd\x2c\xbf\xff\x3e\x7f\x7f\xbd\x6c\xbe\xf9\xcd\x1f\xff\xd6\x5f\x9f\xf8\x8d\xca\xf6\xb2\x03\x03\x60\xd4\xac\x96\xb1\x31\x16\xc8\x63\xea\x19\x60\x02\x38\xc0\x13\x8b\x70\x81\x57\x40\x80\x0f\xc0\x13\x98\x02\x15\x81\x2d\x81\x3d\x81\xaa\xc0\xa9\x40\x20\x70\x25\xc6\xc3\xb5\x9e\x39\xeb\x39\x8b\x9c\xfd\x46\xe2\x29\x89\x3a\x73\x85\xac\x55\xb1\x0f\x29\x8d\x64\xd1\xa7\xd3\x50\xcc\x8c\x41\x49\x57\x8b\x73\x9f\xbe\x98\x19\x6b\xac\xb2\xa2\x67\x1e\x86\xfd\x30\x3e\xa2\x70\x7c\xd2\x0b\xa2\x68\xa7\x1b\xdc\x07\x48\x9d\x72\x43\x67\xaa\x17\xf4\x3b\xea\xfc\xa2\xdb\xbe\x8c\x6b\x6c\x34\xa2\x58\x0d\x6f\x86\xaa\x13\xc6\xaa\xd5\x1e\x29\xd3\x53\xc3\x3d\xbb\xbb\x46\x9a\xb8\xe4\x8b\x64\xf5\x0b\x44\xe4\xf1\x21\x33\x15\x47\xc6\x7a\xe7\xc2\xbf\x7d\xb5\xce\xf8\x3a\x29\xc6\x6e\x8a\x73\x36\xc5\x38\x97\xe2\x2c\x9e\x39\xa3\x59\x3b\x9e\xf1\xd1\xcc\x5d\x5b\xd7\xff\x8e\x67\xce\x27\x5c\x4a\x26\xd7\x77\xfa\x17\x00\x00\xff\xff\xe4\x6b\xad\x5e\x0c\x02\x00\x00") + +func javaClassesDexBytes() ([]byte, error) { + return bindataRead( + _javaClassesDex, + "java/classes.dex", + ) +} + +func javaClassesDex() (*asset, error) { + bytes, err := javaClassesDexBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "java/classes.dex", size: 524, mode: os.FileMode(420), modTime: time.Unix(1572550715, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +// Asset loads and returns the asset for the given name. +// It returns an error if the asset could not be found or +// could not be loaded. +func Asset(name string) ([]byte, error) { + cannonicalName := strings.Replace(name, "\\", "/", -1) + if f, ok := _bindata[cannonicalName]; ok { + a, err := f() + if err != nil { + return nil, fmt.Errorf("Asset %s can't read by error: %v", name, err) + } + return a.bytes, nil + } + return nil, fmt.Errorf("Asset %s not found", name) +} + +// MustAsset is like Asset but panics when Asset would return an error. +// It simplifies safe initialization of global variables. +func MustAsset(name string) []byte { + a, err := Asset(name) + if err != nil { + panic("asset: Asset(" + name + "): " + err.Error()) + } + + return a +} + +// AssetInfo loads and returns the asset info for the given name. +// It returns an error if the asset could not be found or +// could not be loaded. +func AssetInfo(name string) (os.FileInfo, error) { + cannonicalName := strings.Replace(name, "\\", "/", -1) + if f, ok := _bindata[cannonicalName]; ok { + a, err := f() + if err != nil { + return nil, fmt.Errorf("AssetInfo %s can't read by error: %v", name, err) + } + return a.info, nil + } + return nil, fmt.Errorf("AssetInfo %s not found", name) +} + +// AssetNames returns the names of the assets. +func AssetNames() []string { + names := make([]string, 0, len(_bindata)) + for name := range _bindata { + names = append(names, name) + } + return names +} + +// _bindata is a table, holding each asset generator, mapped to its name. +var _bindata = map[string]func() (*asset, error){ + "java/classes.dex": javaClassesDex, +} + +// AssetDir returns the file names below a certain +// directory embedded in the file by go-bindata. +// For example if you run go-bindata on data/... and data contains the +// following hierarchy: +// data/ +// foo.txt +// img/ +// a.png +// b.png +// then AssetDir("data") would return []string{"foo.txt", "img"} +// AssetDir("data/img") would return []string{"a.png", "b.png"} +// AssetDir("foo.txt") and AssetDir("notexist") would return an error +// AssetDir("") will return []string{"data"}. +func AssetDir(name string) ([]string, error) { + node := _bintree + if len(name) != 0 { + cannonicalName := strings.Replace(name, "\\", "/", -1) + pathList := strings.Split(cannonicalName, "/") + for _, p := range pathList { + node = node.Children[p] + if node == nil { + return nil, fmt.Errorf("Asset %s not found", name) + } + } + } + if node.Func != nil { + return nil, fmt.Errorf("Asset %s not found", name) + } + rv := make([]string, 0, len(node.Children)) + for childName := range node.Children { + rv = append(rv, childName) + } + return rv, nil +} + +type bintree struct { + Func func() (*asset, error) + Children map[string]*bintree +} +var _bintree = &bintree{nil, map[string]*bintree{ + "java": &bintree{nil, map[string]*bintree{ + "classes.dex": &bintree{javaClassesDex, map[string]*bintree{}}, + }}, +}} + +// RestoreAsset restores an asset under the given directory +func RestoreAsset(dir, name string) error { + data, err := Asset(name) + if err != nil { + return err + } + info, err := AssetInfo(name) + if err != nil { + return err + } + err = os.MkdirAll(_filePath(dir, filepath.Dir(name)), os.FileMode(0755)) + if err != nil { + return err + } + err = ioutil.WriteFile(_filePath(dir, name), data, info.Mode()) + if err != nil { + return err + } + err = os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime()) + if err != nil { + return err + } + return nil +} + +// RestoreAssets restores an asset under the given directory recursively +func RestoreAssets(dir, name string) error { + children, err := AssetDir(name) + // File + if err != nil { + return RestoreAsset(dir, name) + } + // Dir + for _, child := range children { + err = RestoreAssets(dir, filepath.Join(name, child)) + if err != nil { + return err + } + } + return nil +} + +func _filePath(dir, name string) string { + cannonicalName := strings.Replace(name, "\\", "/", -1) + return filepath.Join(append([]string{dir}, strings.Split(cannonicalName, "/")...)...) +} + diff --git a/examples/dex/dex_android.go b/examples/dex/dex_android.go new file mode 100644 index 0000000..822e5b8 --- /dev/null +++ b/examples/dex/dex_android.go @@ -0,0 +1,206 @@ +package main + +/* +#cgo LDFLAGS: -landroid -llog + +#include +#include +#include + +static jobject gClassLoader; +static jmethodID gFindClassMethod; + +jobject +CreateObject(JNIEnv* env, jclass cls) { + jmethodID init = (*env)->GetMethodID(env, cls, "", "()V"); + return (*env)->NewObject(env, cls, init); +} + +jmethodID GetMethodID(JNIEnv *env, jclass cls, const char *name, const char *sig) { + return (*env)->GetMethodID(env, cls, name, sig); +} + +jclass +FindClass(JNIEnv* env, char* name) { + jstring strClassName = (*env)->NewStringUTF(env, name); + return (*env)->CallObjectMethod(env, gClassLoader, gFindClassMethod, strClassName); +} + +void +SetupJVM(JNIEnv* env, jobject context, void* buf, int len) { + jclass cls = (*env)->GetObjectClass(env, context); + jmethodID mid = (*env)->GetMethodID(env, cls, "getClassLoader", "()Ljava/lang/ClassLoader;"); + jobject loader = (*env)->CallObjectMethod(env, context, mid); + gClassLoader = (*env)->NewGlobalRef(env, loader); + cls = (*env)->GetObjectClass(env, loader); + gFindClassMethod = (*env)->GetMethodID(env, cls, "findClass", "(Ljava/lang/String;)Ljava/lang/Class;"); + + __android_log_write(ANDROID_LOG_DEBUG, "gio", "Looking for InMemoryDexClassLoader"); + cls = (*env)->FindClass(env, "dalvik/system/InMemoryDexClassLoader"); + if (cls == NULL) { + __android_log_write(ANDROID_LOG_DEBUG, "gio", "not found"); + return; + } + __android_log_write(ANDROID_LOG_DEBUG, "gio", "NewDirectByteBuffer"); + jobject bbuf = (*env)->NewDirectByteBuffer(env, buf, (jlong)len); + if (bbuf == NULL) { + __android_log_write(ANDROID_LOG_DEBUG, "gio", "failed"); + return; + } + + __android_log_write(ANDROID_LOG_DEBUG, "gio", "looking for "); + mid = (*env)->GetMethodID(env, cls, "", "(Ljava/nio/ByteBuffer;Ljava/lang/ClassLoader;)V"); + if (mid == NULL) { + __android_log_write(ANDROID_LOG_DEBUG, "gio", "not found"); + return; + } + __android_log_write(ANDROID_LOG_DEBUG, "gio", "calling "); + jobject dexLoader = (*env)->NewObject(env, cls, mid, bbuf, gClassLoader); + // make the dexLoader our new class loader + gClassLoader = (*env)->NewGlobalRef(env, dexLoader); +} + +// __android_log_write(ANDROID_LOG_DEBUG, "gio", "looking for AClass"); +// cls = FindClass(env, "st/wow/git/dex/AClass"); +// if (cls == NULL) { +// __android_log_write(ANDROID_LOG_DEBUG, "gio", "not found"); +// return; +// } +// +// __android_log_write(ANDROID_LOG_DEBUG, "gio", "calling CreateObject"); +// jobject obj = CreateObject(env, cls); +// if (obj == NULL) { +// __android_log_write(ANDROID_LOG_DEBUG, "gio", "failed"); +// return; +// } +// +// __android_log_write(ANDROID_LOG_DEBUG, "gio", "looking for "); +// mid = (*env)->GetMethodID(env, cls, "", "()V"); +// if (mid == NULL) { +// __android_log_write(ANDROID_LOG_DEBUG, "gio", "not found"); +// return; +// } +// __android_log_write(ANDROID_LOG_DEBUG, "gio", "creating object"); +// obj = (*env)->NewObject(env, cls, mid); +// if (obj == NULL) { +// __android_log_write(ANDROID_LOG_DEBUG, "gio", "failed"); +// return; +// } +// +// __android_log_write(ANDROID_LOG_DEBUG, "gio", "looking for Num"); +// mid = (*env)->GetMethodID(env, cls, "Num", "()I"); +// if (mid == NULL) { +// __android_log_write(ANDROID_LOG_DEBUG, "gio", "not found"); +// return; +// } +// jint val = (*env)->CallIntMethod(env, obj, mid); +// __android_log_print(ANDROID_LOG_DEBUG, "gio", "value = %d", val); + + +jint +CallIntMethod(JNIEnv *env, jobject obj, jmethodID methodID) { + return (*env)->CallIntMethod(env, obj, methodID); +} + +jint +GetEnv(JavaVM *vm, JNIEnv **env, jint version) { + return (*vm)->GetEnv(vm, (void **)env, version); +} + +jint +AttachCurrentThread(JavaVM *vm, JNIEnv **p_env, void *thr_args) { + return (*vm)->AttachCurrentThread(vm, p_env, thr_args); +} + +jint +DetachCurrentThread(JavaVM *vm) { + return (*vm)->DetachCurrentThread(vm); +} + +*/ +import "C" + +import ( + "errors" + "fmt" + "runtime" + "unsafe" + + "gioui.org/app" +) + +var theJVM *C.JavaVM + +func SetJVM() { + h := app.PlatformHandle() + theJVM = (*C.JavaVM)(unsafe.Pointer(h.JVM)) + dex := MustAsset("java/classes.dex") + RunInJVM(func(env *C.JNIEnv) { + labchan <- "Set up JVM" + C.SetupJVM( + env, + (C.jobject)(h.Context), + unsafe.Pointer(&dex[0]), + C.int(len(dex)), + ) + }) +} + +func CallJNI() { + labchan <- "Call JNI" + RunInJVM(func(env *C.JNIEnv) { + labchan <- "FindClass" + cls := JniFindClass(env, "st/wow/git/dex/AClass") + labchan <- "GetMethodID" + mid := JniGetMethodID(env, cls, "Num", "()I") + labchan <- "CreateObject" + obj := JniCreateObject(env, cls) + val := JniCallIntMethod(env, obj, mid) + labchan <- fmt.Sprintf("val = %d", val) + }) +} + +func JniFindClass(env *C.JNIEnv, name string) C.jclass { + cname := C.CString(name) + defer C.free(unsafe.Pointer(cname)) + return C.FindClass((*C.JNIEnv)(env), cname) +} + +func JniCreateObject(env *C.JNIEnv, cls C.jclass) C.jobject { + return C.CreateObject(env, (C.jclass)(cls)) +} + +func JniCallIntMethod(env *C.JNIEnv, obj C.jobject, methodID C.jmethodID) int { + return (int)(C.CallIntMethod(env, obj, methodID)) +} + +func JniGetMethodID(env *C.JNIEnv, cls C.jclass, name, sig string) C.jmethodID { + cname := C.CString(name) + defer C.free(unsafe.Pointer(cname)) + csig := C.CString(sig) + defer C.free(unsafe.Pointer(csig)) + return C.GetMethodID(env, cls, cname, csig) +} + +func RunInJVM(f func(env *C.JNIEnv)) { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + var env *C.JNIEnv + var detach bool + if res := C.GetEnv(theJVM, &env, C.JNI_VERSION_1_6); res != C.JNI_OK { + if res != C.JNI_EDETACHED { + panic(fmt.Errorf("JNI GetEnv failed with error %d", res)) + } + if C.AttachCurrentThread(theJVM, &env, nil) != C.JNI_OK { + panic(errors.New("runInJVM: AttachCurrentThread failed")) + } + detach = true + } + + if detach { + defer func() { + C.DetachCurrentThread(theJVM) + }() + } + f(env) +} diff --git a/examples/dex/dex_other.go b/examples/dex/dex_other.go new file mode 100644 index 0000000..c02f383 --- /dev/null +++ b/examples/dex/dex_other.go @@ -0,0 +1,9 @@ +package main + +func SetJVM() { + dex := MustAsset("java/classes.dex") + _ = dex +} + +func CallJNI() { +} diff --git a/examples/dex/java/AClass.java b/examples/dex/java/AClass.java new file mode 100644 index 0000000..3c85cc0 --- /dev/null +++ b/examples/dex/java/AClass.java @@ -0,0 +1,9 @@ +package st.wow.git.dex; + +public class AClass { + public int Num() { + return 17; + } +} + + diff --git a/examples/dex/java/gen.go b/examples/dex/java/gen.go new file mode 100644 index 0000000..591189c --- /dev/null +++ b/examples/dex/java/gen.go @@ -0,0 +1,5 @@ +//go:generate javac -bootclasspath $ANDROID_HOME/platforms/android-29/android.jar AClass.java +//go:generate $ANDROID_HOME/build-tools/28.0.3/dx --dex --output=classes.dex --no-strict AClass.class +//go:generate rm AClass.class + +package java diff --git a/examples/dex/main.go b/examples/dex/main.go new file mode 100644 index 0000000..0ffa0a0 --- /dev/null +++ b/examples/dex/main.go @@ -0,0 +1,70 @@ +//go:generate go generate ./java +//go:generate go-bindata java/classes.dex + +package main + +import ( + "gioui.org/app" + "gioui.org/layout" + "gioui.org/io/system" + "gioui.org/unit" + "gioui.org/widget/material" + + "gioui.org/font/gofont" +) + +var ( + labchan chan string +) + +func main() { + labchan = make(chan string) + go eventloop() + app.Main() +} + +func eventloop() { + gofont.Register() + w := app.NewWindow(app.Title("Dex")) + th := material.NewTheme() + gtx := &layout.Context{Queue: w.Queue()} + + sysinset := &layout.Inset{} + margin := layout.UniformInset(unit.Dp(10)) + labels := []material.Label{} + list := &layout.List{Axis: layout.Vertical} + + go func() { + labchan <- "Starting" + SetJVM() + CallJNI() + }() + + resetSysinset := func(x system.Insets) { + sysinset.Top = x.Top + sysinset.Bottom = x.Bottom + sysinset.Left = x.Left + sysinset.Right = x.Right + } + + for { select { + case x := <-labchan: + labels = append(labels, th.Body1(x)) + case e := <-w.Events(): + switch e := e.(type) { + case system.DestroyEvent: + return + case system.FrameEvent: + gtx.Reset(e.Config, e.Size) + resetSysinset(e.Insets) + sysinset.Layout(gtx, func() { + margin.Layout(gtx, func() { + list.Layout(gtx, len(labels), func(i int) { + labels[i].Layout(gtx) + }) + }) + }) + e.Frame(gtx.Ops) + } + }} +} diff --git a/examples/jni/main.go b/examples/jni/main.go index c5e8977..84c5162 100644 --- a/examples/jni/main.go +++ b/examples/jni/main.go @@ -26,13 +26,6 @@ func main() { log.Print("app.Main() returned") } -func diffInsets(x, y system.Insets) bool { - return x.Top != y.Top || - x.Bottom != y.Bottom || - x.Left != y.Left || - x.Right != y.Right -} - func eventloop() { w := app.NewWindow( app.Size(unit.Dp(400), unit.Dp(400)), diff --git a/examples/sensors/main.go b/examples/sensors/main.go index 12e4650..2c5374b 100644 --- a/examples/sensors/main.go +++ b/examples/sensors/main.go @@ -11,7 +11,7 @@ import ( "gioui.org/unit" "gioui.org/widget/material" - _ "gioui.org/font/gofont" + "gioui.org/font/gofont" ) var ( @@ -34,6 +34,7 @@ func diffInsets(x, y system.Insets) bool { } func eventloop() { + gofont.Register() w := app.NewWindow( app.Size(unit.Dp(400), unit.Dp(400)), app.Title("Hello")) diff --git a/go.mod b/go.mod index 0c15508..28b0897 100644 --- a/go.mod +++ b/go.mod @@ -1,8 +1,3 @@ module git.wow.st/gmp/android-go go 1.13 - -require ( - gioui.org v0.0.0-20191018181617-5ef176af8145 - golang.org/x/image v0.0.0-20190910094157-69e4b8554b2a -)