From 136fa9bbbb9db02f54f22084acf58d98e88e5be4 Mon Sep 17 00:00:00 2001 From: "Hyang-Ah (Hana) Kim" Date: Thu, 16 Jul 2015 00:23:53 -0400 Subject: [PATCH] bind/java: reenable asset access. This is done by moving app.Context to internal/mobileinit, introducing mobileinit.SetCurrentContext and, making bind/java depend on it. TODO: check gomobile bind's proguard rule - context lookup was implemented through reflection on android.app.AppGlobals class. Change-Id: Ieb6ad503eeef8c2c1c5836a21c667938c5a701a2 Reviewed-on: https://go-review.googlesource.com/12279 Reviewed-by: David Crawshaw --- app/android.c | 8 ++-- app/android.go | 36 +++--------------- asset/asset_android.go | 4 +- bind/java/Seq.java | 14 +++++++ bind/java/SeqTest.java | 6 +++ bind/java/seq_android.c | 9 +++++ bind/java/seq_android.go | 7 ++++ bind/java/testpkg/assets/hello.txt | 1 + bind/java/testpkg/testpkg.go | 18 +++++++++ exp/audio/al/al_android.go | 4 +- internal/mobileinit/ctx_android.go | 61 ++++++++++++++++++++++++++++++ internal/mobileinit/mobileinit.go | 2 +- 12 files changed, 131 insertions(+), 39 deletions(-) create mode 100644 bind/java/testpkg/assets/hello.txt create mode 100644 internal/mobileinit/ctx_android.go diff --git a/app/android.c b/app/android.c index cd4dfd0..9cf940c 100644 --- a/app/android.c +++ b/app/android.c @@ -45,8 +45,6 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved) { } // Load classes here, which uses the correct ClassLoader. - current_vm = vm; - current_ctx = NULL; current_ctx_clazz = find_class(env, "org/golang/app/GoNativeActivity"); current_ctx_clazz = (jclass)(*env)->NewGlobalRef(env, current_ctx_clazz); @@ -61,8 +59,10 @@ void ANativeActivity_onCreate(ANativeActivity *activity, void* savedState, size_ JNIEnv* env = activity->env; // Note that activity->clazz is mis-named. - current_vm = activity->vm; - current_ctx = (*env)->NewGlobalRef(env, activity->clazz); + JavaVM* current_vm = activity->vm; + jobject current_ctx = activity->clazz; + + setCurrentContext(current_vm, (*env)->NewGlobalRef(env, current_ctx)); // Set TMPDIR. jmethodID gettmpdir = find_method(env, current_ctx_clazz, "getTmpdir", "()Ljava/lang/String;"); diff --git a/app/android.go b/app/android.go index 4950ec6..68a3a2d 100644 --- a/app/android.go +++ b/app/android.go @@ -34,17 +34,6 @@ package app #include #include -// current_vm is stored to initialize other cgo packages. -// -// As all the Go packages in a program form a single shared library, -// there can only be one JNI_OnLoad function for iniitialization. In -// OpenJDK there is JNI_GetCreatedJavaVMs, but this is not available -// on android. -JavaVM* current_vm; - -// current_ctx is Android's android.context.Context. May be NULL. -jobject current_ctx; - jclass current_ctx_clazz; jclass app_find_class(JNIEnv* env, const char* name); @@ -58,8 +47,14 @@ import ( "unsafe" "golang.org/x/mobile/app/internal/callfn" + "golang.org/x/mobile/internal/mobileinit" ) +//export setCurrentContext +func setCurrentContext(vm *C.JavaVM, ctx C.jobject) { + mobileinit.SetCurrentContext(unsafe.Pointer(vm), unsafe.Pointer(ctx)) +} + //export callMain func callMain(mainPC uintptr) { for _, name := range []string{"TMPDIR", "PATH", "LD_LIBRARY_PATH"} { @@ -186,25 +181,6 @@ func onConfigurationChanged(activity *C.ANativeActivity) { func onLowMemory(activity *C.ANativeActivity) { } -// Context holds global OS-specific context. -// -// Its extra methods are deliberately difficult to access because they must be -// used with care. Their use implies the use of cgo, which probably requires -// you understand the initialization process in the app package. Also care must -// be taken to write both Android, iOS, and desktop-testing versions to -// maintain portability. -type Context struct{} - -// AndroidContext returns a jobject for the app android.context.Context. -func (Context) AndroidContext() unsafe.Pointer { - return unsafe.Pointer(C.current_ctx) -} - -// JavaVM returns a JNI *JavaVM. -func (Context) JavaVM() unsafe.Pointer { - return unsafe.Pointer(C.current_vm) -} - var ( windowDestroyed = make(chan bool) windowCreated = make(chan *C.ANativeWindow) diff --git a/asset/asset_android.go b/asset/asset_android.go index a790532..0476baf 100644 --- a/asset/asset_android.go +++ b/asset/asset_android.go @@ -61,13 +61,13 @@ import ( "sync" "unsafe" - "golang.org/x/mobile/app" + "golang.org/x/mobile/internal/mobileinit" ) var assetOnce sync.Once func assetInit() { - ctx := app.Context{} + ctx := mobileinit.Context{} C.asset_manager_init(ctx.JavaVM(), ctx.AndroidContext()) } diff --git a/bind/java/Seq.java b/bind/java/Seq.java index 3c92d1c..0fc78f4 100644 --- a/bind/java/Seq.java +++ b/bind/java/Seq.java @@ -4,6 +4,8 @@ package go; +import android.app.Application; +import android.content.Context; import android.util.Log; import android.util.SparseArray; import android.util.SparseIntArray; @@ -24,6 +26,16 @@ public class Seq { Log.w("GoSeq", "LoadJNI class not found"); } + try { + // TODO(hyangah): check proguard rule. + Application appl = (Application)Class.forName("android.app.AppGlobals").getMethod("getInitialApplication").invoke(null, (Object[]) null); + Context ctx = appl.getApplicationContext(); + setContext(ctx); + + } catch (Exception e) { + Log.w("GoSeq", "Global context not found:" + e); + } + initSeq(); new Thread("GoSeq") { public void run() { Seq.receive(); } @@ -37,6 +49,8 @@ public class Seq { ensure(64); } + static native void setContext(Context ctx); + // Ensure that at least size bytes can be written to the Seq. // Any existing data in the buffer is preserved. public native void ensure(int size); diff --git a/bind/java/SeqTest.java b/bind/java/SeqTest.java index fee922f..786a037 100644 --- a/bind/java/SeqTest.java +++ b/bind/java/SeqTest.java @@ -17,6 +17,12 @@ public class SeqTest extends AndroidTestCase { public SeqTest() { } + public void testAssets() { + String want = "Hello, Assets.\n"; + String got = Testpkg.ReadAsset(); + assertEquals("Asset read", want, got); + } + public void testAdd() { long res = Testpkg.Add(3, 4); assertEquals("Unexpected arithmetic failure", 7, res); diff --git a/bind/java/seq_android.c b/bind/java/seq_android.c index b22667f..9792652 100644 --- a/bind/java/seq_android.c +++ b/bind/java/seq_android.c @@ -435,3 +435,12 @@ Java_go_Seq_recvRes(JNIEnv *env, jclass clazz, jint handle, jobject out_obj) { } RecvRes((int32_t)handle, out->buf, out->len); } + +JNIEXPORT void JNICALL +Java_go_Seq_setContext(JNIEnv* env, jclass clazz, jobject ctx) { + JavaVM* vm; + if ((*env)->GetJavaVM(env, &vm) != 0) { + LOG_FATAL("failed to get JavaVM"); + } + setContext(vm, (*env)->NewGlobalRef(env, ctx)); +} diff --git a/bind/java/seq_android.go b/bind/java/seq_android.go index e24d360..01a915f 100644 --- a/bind/java/seq_android.go +++ b/bind/java/seq_android.go @@ -6,6 +6,7 @@ package java // import "golang.org/x/mobile/bind/java" //#cgo LDFLAGS: -llog //#include +//#include //#include //#include //#include "seq_android.h" @@ -16,6 +17,7 @@ import ( "unsafe" "golang.org/x/mobile/bind/seq" + "golang.org/x/mobile/internal/mobileinit" ) const maxSliceLen = 1<<31 - 1 @@ -178,3 +180,8 @@ func init() { seq.EncString = encodeString seq.DecString = decodeString } + +//export setContext +func setContext(vm *C.JavaVM, ctx C.jobject) { + mobileinit.SetCurrentContext(unsafe.Pointer(vm), unsafe.Pointer(ctx)) +} diff --git a/bind/java/testpkg/assets/hello.txt b/bind/java/testpkg/assets/hello.txt new file mode 100644 index 0000000..0c44083 --- /dev/null +++ b/bind/java/testpkg/assets/hello.txt @@ -0,0 +1 @@ +Hello, Assets. diff --git a/bind/java/testpkg/testpkg.go b/bind/java/testpkg/testpkg.go index 51313bb..fd795e4 100644 --- a/bind/java/testpkg/testpkg.go +++ b/bind/java/testpkg/testpkg.go @@ -11,8 +11,12 @@ package testpkg import ( "errors" "fmt" + "io/ioutil" + "log" "runtime" "time" + + "golang.org/x/mobile/asset" ) type I interface { @@ -156,3 +160,17 @@ func Hello(r Receiver, name string) { func GarbageCollect() { runtime.GC() } + +func ReadAsset() string { + rc, err := asset.Open("hello.txt") + if err != nil { + log.Fatal(err) + } + defer rc.Close() + + b, err := ioutil.ReadAll(rc) + if err != nil { + log.Fatal(err) + } + return string(b) +} diff --git a/exp/audio/al/al_android.go b/exp/audio/al/al_android.go index 979ebdd..a076a9c 100644 --- a/exp/audio/al/al_android.go +++ b/exp/audio/al/al_android.go @@ -198,7 +198,7 @@ import ( "log" "unsafe" - "golang.org/x/mobile/app" + "golang.org/x/mobile/internal/mobileinit" ) var ( @@ -247,7 +247,7 @@ var ( ) func initAL() { - ctx := app.Context{} + ctx := mobileinit.Context{} switch C.al_init(ctx.JavaVM(), ctx.AndroidContext(), &alHandle) { case C.AL_INIT_RESULT_OK: // No-op. diff --git a/internal/mobileinit/ctx_android.go b/internal/mobileinit/ctx_android.go new file mode 100644 index 0000000..753a2c8 --- /dev/null +++ b/internal/mobileinit/ctx_android.go @@ -0,0 +1,61 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package mobileinit + +/* +#include +#include + +// current_vm is stored to initialize other cgo packages. +// +// As all the Go packages in a program form a single shared library, +// there can only be one JNI_OnLoad function for initialization. In +// OpenJDK there is JNI_GetCreatedJavaVMs, but this is not available +// on android. +JavaVM* current_vm; + +// current_ctx is Android's android.context.Context. May be NULL. +jobject current_ctx; + +// Set current_vm and current_ctx. The ctx passed in must be a global +// reference instance. +void set_vm_ctx(JavaVM* vm, jobject ctx) { + current_vm = vm; + current_ctx = ctx; + // TODO: check leak +} +*/ +import "C" + +import "unsafe" + +// SetCurrentContext populates the global Context object with the specified +// current JavaVM instance (vm) and android.context.Context object (ctx). +// The android.context.Context object must be a global reference. +func SetCurrentContext(vm, ctx unsafe.Pointer) { + C.set_vm_ctx((*C.JavaVM)(vm), (C.jobject)(ctx)) +} + +// TODO(hyangah): should the app package have Context? It may be useful for +// external packages that need to access android context and vm. + +// Context holds global OS-specific context. +// +// Its extra methods are deliberately difficult to access because they must be +// used with care. Their use implies the use of cgo, which probably requires +// you understand the initialization process in the app package. Also care must +// be taken to write both Android, iOS, and desktop-testing versions to +// maintain portability. +type Context struct{} + +// AndroidContext returns a jobject for the app android.context.Context. +func (Context) AndroidContext() unsafe.Pointer { + return unsafe.Pointer(C.current_ctx) +} + +// JavaVM returns a JNI *JavaVM. +func (Context) JavaVM() unsafe.Pointer { + return unsafe.Pointer(C.current_vm) +} diff --git a/internal/mobileinit/mobileinit.go b/internal/mobileinit/mobileinit.go index 9f0b3bc..767943b 100644 --- a/internal/mobileinit/mobileinit.go +++ b/internal/mobileinit/mobileinit.go @@ -5,5 +5,5 @@ // Package mobileinit contains common initialization logic for mobile platforms // that is relevant to both all-Go apps and gobind-based apps. // -// Long-term, any code in this package should consider moving into Go stdlib. +// Long-term, some code in this package should consider moving into Go stdlib. package mobileinit