~eliasnaur/gio-patches

app/permission: add documentation and storage permissions v1 PROPOSED

Greg Pomerantz
Greg Pomerantz: 2
 app/permission: add documentation and storage permissions
 app: add RegisterFragment method on *Window for Android

 14 files changed, 202 insertions(+), 54 deletions(-)
Export patchset (mbox)
How do I use this?

Copy & paste the following snippet into your terminal to import this patchset into git:

curl -s https://lists.sr.ht/~eliasnaur/gio-patches/patches/9151/mbox | git am -3
Learn more about email & git

[PATCH 1/2] app/permission: add documentation and storage permissions Export this patch

Greg Pomerantz
Storage permissions enables the Android permissions
READ_EXTERNAL_STORAGE and WRITE_EXTERNAL_STORAGE.
---
 app/doc.go                          | 32 ++------------------------
 app/permission/bluetooth/main.go    | 23 +++++++++++++++++++
 app/permission/bluetooth_le/main.go |  1 -
 app/permission/doc.go               | 46 +++++++++++++++++++++++++++++++++++++
 app/permission/storage/main.go      | 17 ++++++++++++++
 cmd/gogio/permission.go             | 12 +++++-----
 6 files changed, 94 insertions(+), 37 deletions(-)
 delete mode 100644 app/permission/bluetooth_le/main.go
 create mode 100644 app/permission/doc.go
 create mode 100644 app/permission/storage/main.go

diff --git a/app/doc.go b/app/doc.go
index fc034b8..24eef87 100644
--- a/app/doc.go
+++ b/app/doc.go
@@ -67,35 +67,7 @@ Permissions
 
 The packages under gioui.org/app/permission should be imported
 by a Gio program or by one of its dependencies to indicate that specific
-operating-system permissions are required. For example, if a Gio
-program requires access to a device's Bluetooth interface, it
-should import "gioui.org/app/permission/bluetooth" as follows:
-
-	package main
-
-	import (
-		"gioui.org/app"
-		_ "gioui.org/app/permission/bluetooth"
-	)
-
-	func main() {
-		...
-	}
-
-Since there are no exported identifiers in the app/permission/bluetooth
-package, the import uses the anonymous identifier (_) as the imported
-package name.
-
-As a special case, the gogio tool detects when a program directly or
-indirectly depends on the "net" package from the Go standard library as an
-indication that the program requires network access permissions. If a program
-requires network permissions but does not directly or indirectly import
-"net", it will be necessary to add the following code somewhere in the
-program's source code:
-
-	import (
-		...
-		_ "net"
-	)
+operating-system permissions are required.  Please see documentation for
+package gioui.org/app/permission for more information.
 */
 package app
diff --git a/app/permission/bluetooth/main.go b/app/permission/bluetooth/main.go
index b3b971b..5ef16d5 100644
--- a/app/permission/bluetooth/main.go
+++ b/app/permission/bluetooth/main.go
@@ -1 +1,24 @@
+// SPDX-License-Identifier: Unlicense OR MIT
+
+/*
+Package bluetooth implements permissions to access Bluetooth and Bluetooth
+Low Energy hardware, including the ability to discover and pair devices.
+
+Android
+
+The following entries will be added to AndroidManifest.xml:
+
+    <uses-permission android:name="android.permission.BLUETOOTH"/>
+    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
+    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
+    <uses-feature android:name="android.hardware.bluetooth" android:required="false"/>
+    <uses-feature android:name="android.hardware.bluetooth_le" android:required="false"/>
+
+Note that ACCESS_FINE_LOCATION is required on Android before the Bluetooth
+device may be used.
+See https://developer.android.com/guide/topics/connectivity/bluetooth.
+
+ACCESS_FINE_LOCATION is a "dangerous" permission. See documentation for
+package gioui.org/app/permission for more information.
+*/
 package bluetooth
diff --git a/app/permission/bluetooth_le/main.go b/app/permission/bluetooth_le/main.go
deleted file mode 100644
index 6fcf968..0000000
--- a/app/permission/bluetooth_le/main.go
@@ -1 +0,0 @@
-package bluetooth_le
diff --git a/app/permission/doc.go b/app/permission/doc.go
new file mode 100644
index 0000000..4aa59ef
--- /dev/null
+++ b/app/permission/doc.go
@@ -0,0 +1,46 @@
+// SPDX-License-Identifier: Unlicense OR MIT
+
+/*
+Package permission includes sub-packages that should be imported
+by a Gio program or by one of its dependencies to indicate that specific
+operating-system permissions are required. For example, if a Gio
+program requires access to a device's Bluetooth interface, it
+should import "gioui.org/app/permission/bluetooth" as follows:
+
+	package main
+
+	import (
+		"gioui.org/app"
+		_ "gioui.org/app/permission/bluetooth"
+	)
+
+	func main() {
+		...
+	}
+
+Since there are no exported identifiers in the app/permission/bluetooth
+package, the import uses the anonymous identifier (_) as the imported
+package name.
+
+As a special case, the gogio tool detects when a program directly or
+indirectly depends on the "net" package from the Go standard library as an
+indication that the program requires network access permissions. If a program
+requires network permissions but does not directly or indirectly import
+"net", it will be necessary to add the following code somewhere in the
+program's source code:
+
+	import (
+		...
+		_ "net"
+	)
+
+Android -- Dangerous Permissions
+
+Certain permissions on Android are marked with a protection level of
+"dangerous". This means that, in addition to including the relevant Gio
+permission packages, your app will need to prompt the user specifically
+to request access. This can be done with a java Fragment, installed using
+(*app.Window).RegisterFragment(). For more information on dangerous
+permissions, see: https://developer.android.com/guide/topics/permissions/overview#dangerous_permissions
+*/
+package permission
diff --git a/app/permission/storage/main.go b/app/permission/storage/main.go
new file mode 100644
index 0000000..ec7f630
--- /dev/null
+++ b/app/permission/storage/main.go
@@ -0,0 +1,17 @@
+// SPDX-License-Identifier: Unlicense OR MIT
+
+/*
+Package storage implements read and write storage permissions
+on mobile devices.
+
+Android
+
+The following entries will be added to AndroidManifest.xml:
+
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
+
+READ_EXTERNAL_STORAGE and WRITE_EXTERNAL_STORAGE are "dangerous" permissions.
+See documentation for package gioui.org/app/permission for more information.
+*/
+package storage
diff --git a/cmd/gogio/permission.go b/cmd/gogio/permission.go
index eabbb91..196eb12 100644
--- a/cmd/gogio/permission.go
+++ b/cmd/gogio/permission.go
@@ -9,15 +9,15 @@ var AndroidPermissions = map[string][]string{
 		"android.permission.BLUETOOTH_ADMIN",
 		"android.permission.ACCESS_FINE_LOCATION",
 	},
-	"bluetooth_le": {
-		"android.permission.BLUETOOTH",
-		"android.permission.BLUETOOTH_ADMIN",
-		"android.permission.ACCESS_FINE_LOCATION",
+	"storage": {
+		"android.permission.READ_EXTERNAL_STORAGE",
+		"android.permission.WRITE_EXTERNAL_STORAGE",
 	},
 }
 
 var AndroidFeatures = map[string][]string{
 	"default":      {`glEsVersion="0x00030000"`},
-	"bluetooth":    {`name="android.hardware.bluetooth"`},
-	"bluetooth_le": {`name="android.hardware.bluetooth_le"`},
+	"bluetooth":    {`name="android.hardware.bluetooth"`,
+			 `name="android.hardware.bluetooth_le"`,
+			},
 }
-- 
2.16.2
Thank you!

To git.sr.ht:~eliasnaur/gio
   99d97d2..8321745  master -> master

On Mon Nov 25, 2019 at 1:39 PM Greg Pomerantz wrote:

[PATCH 2/2] app: add RegisterFragment method on *Window for Android Export this patch

Greg Pomerantz
RegisterFragment creates an instance of a Java class and attempts
to register it as a Fragment in the window's Context.

Signed-off-by: Greg Pomerantz <gmp.gio@wow.st>
---
 app/app.go                        |  7 -------
 app/app_android.go                | 33 ++++++++++++++++++++++++++++++++
 app/internal/window/GioView.java  | 40 ++++++++++++++++++++++++++++++++++++++-
 app/internal/window/handle.go     |  7 -------
 app/internal/window/os_android.c  |  5 +++++
 app/internal/window/os_android.go | 10 ++++++++++
 app/internal/window/os_android.h  |  1 +
 app/window.go                     | 22 +++++++++++++++++++--
 8 files changed, 108 insertions(+), 17 deletions(-)
 create mode 100644 app/app_android.go
 delete mode 100644 app/internal/window/handle.go

diff --git a/app/app.go b/app/app.go
index 9a18ec4..71bc57b 100644
--- a/app/app.go
+++ b/app/app.go
@@ -9,13 +9,6 @@ import (
 	"gioui.org/app/internal/window"
 )
 
-type Handle window.Handle
-
-// PlatformHandle returns the platform specific Handle.
-func PlatformHandle() *Handle {
-	return (*Handle)(window.PlatformHandle)
-}
-
 // extraArgs contains extra arguments to append to
 // os.Args. The arguments are separated with |.
 // Useful for running programs on mobiles where the
diff --git a/app/app_android.go b/app/app_android.go
new file mode 100644
index 0000000..59d6a60
--- /dev/null
+++ b/app/app_android.go
@@ -0,0 +1,33 @@
+package app
+
+import (
+	"gioui.org/app/internal/window"
+)
+
+type Handle window.Handle
+
+// PlatformHandle returns the Android platform-specific Handle.
+func PlatformHandle() *Handle {
+	return (*Handle)(window.PlatformHandle)
+}
+
+// androidDriver is an interface that allows the Window's run method
+// to call the RegisterFragment method of the Android window driver.
+type androidDriver interface {
+	RegisterFragment(string)
+}
+
+// RegisterFragment constructs a Java instance of the specified class
+// and attempts to register it as a Fragment in the Context in which
+// the View was created.
+func (w *Window) RegisterFragment(del string) {
+	go func() {
+		<-w.driverWait
+		w.callbacks.Event(funcEvent{
+			func() {
+				d := w.driver.(androidDriver)
+				d.RegisterFragment(del)
+			},
+		})
+	}()
+}
diff --git a/app/internal/window/GioView.java b/app/internal/window/GioView.java
index 82bc36f..eb6f09e 100644
--- a/app/internal/window/GioView.java
+++ b/app/internal/window/GioView.java
@@ -2,12 +2,21 @@
 
 package org.gioui;
 
+import java.lang.Class;
+import java.lang.IllegalAccessException;
+import java.lang.InstantiationException;
+import java.lang.ExceptionInInitializerError;
+import java.lang.SecurityException;
+import android.app.Activity;
+import android.app.Fragment;
+import android.app.FragmentManager;
+import android.app.FragmentTransaction;
 import android.content.Context;
 import android.graphics.Rect;
 import android.os.Build;
 import android.os.Handler;
-import android.util.AttributeSet;
 import android.text.Editable;
+import android.util.AttributeSet;
 import android.view.Choreographer;
 import android.view.KeyCharacterMap;
 import android.view.KeyEvent;
@@ -207,6 +216,35 @@ public class GioView extends SurfaceView implements Choreographer.FrameCallback
 		return onBack(nhandle);
 	}
 
+	public void registerFragment(String del) {
+		final Class cls;
+		try {
+			cls = getContext().getClassLoader().loadClass(del);
+		} catch (ClassNotFoundException e) {
+			throw new RuntimeException("RegisterFragment: fragment class not found: " + e.getMessage());
+		}
+
+		handler.post(new Runnable() {
+			public void run() {
+				final Fragment frag;
+				try {
+					frag = (Fragment)cls.newInstance();
+				} catch (IllegalAccessException | InstantiationException | ExceptionInInitializerError | SecurityException | ClassCastException e) {
+					throw new RuntimeException("RegisterFragment: error instantiating fragment: " + e.getMessage());
+				}
+				final FragmentManager fm;
+				try {
+					fm = (FragmentManager)((Activity)(getContext())).getFragmentManager();
+				} catch (ClassCastException e) {
+					throw new RuntimeException("RegisterFragment: Cannot get fragment manager from View Context: " + e.getMessage());
+				}
+				FragmentTransaction ft = fm.beginTransaction();
+				ft.add(frag, del);
+				ft.commitNow();
+			}
+		});
+	}
+
 	static private native long onCreateView(GioView view);
 	static private native void onDestroyView(long handle);
 	static private native void onStartView(long handle);
diff --git a/app/internal/window/handle.go b/app/internal/window/handle.go
deleted file mode 100644
index 5d7b1a1..0000000
--- a/app/internal/window/handle.go
@@ -1,7 +0,0 @@
-// +build !android
-
-package window
-
-var PlatformHandle *Handle
-
-type Handle struct{}
diff --git a/app/internal/window/os_android.c b/app/internal/window/os_android.c
index 5150ab6..3dba313 100644
--- a/app/internal/window/os_android.c
+++ b/app/internal/window/os_android.c
@@ -166,3 +166,8 @@ void gio_jni_ReleaseByteArrayElements(JNIEnv *env, jbyteArray arr, jbyte *bytes)
 jsize gio_jni_GetArrayLength(JNIEnv *env, jbyteArray arr) {
 	return (*env)->GetArrayLength(env, arr);
 }
+
+void gio_jni_RegisterFragment(JNIEnv *env, jobject view, jmethodID mid, char* del) {
+	jstring jdel = (*env)->NewStringUTF(env, del);
+	(*env)->CallObjectMethod(env, view, mid, jdel);
+}
diff --git a/app/internal/window/os_android.go b/app/internal/window/os_android.go
index 5cce88d..3a7d52c 100644
--- a/app/internal/window/os_android.go
+++ b/app/internal/window/os_android.go
@@ -54,6 +54,7 @@ type window struct {
 	mhideTextInput                 C.jmethodID
 	mpostFrameCallback             C.jmethodID
 	mpostFrameCallbackOnMainThread C.jmethodID
+	mRegisterFragment              C.jmethodID
 }
 
 var dataDirChan = make(chan string, 1)
@@ -119,6 +120,7 @@ func onCreateView(env *C.JNIEnv, class C.jclass, view C.jobject) C.jlong {
 		mhideTextInput:                 jniGetMethodID(env, class, "hideTextInput", "()V"),
 		mpostFrameCallback:             jniGetMethodID(env, class, "postFrameCallback", "()V"),
 		mpostFrameCallbackOnMainThread: jniGetMethodID(env, class, "postFrameCallbackOnMainThread", "()V"),
+		mRegisterFragment:              jniGetMethodID(env, class, "registerFragment", "(Ljava/lang/String;)V"),
 	}
 	wopts := <-mainWindow.out
 	w.callbacks = wopts.window
@@ -443,6 +445,14 @@ func (w *window) ShowTextInput(show bool) {
 	})
 }
 
+func (w *window) RegisterFragment(del string) {
+	runInJVM(func(env *C.JNIEnv) {
+		cdel := C.CString(del)
+		defer C.free(unsafe.Pointer(cdel))
+		C.gio_jni_RegisterFragment(env, w.view, w.mRegisterFragment, cdel)
+	})
+}
+
 func Main() {
 }
 
diff --git a/app/internal/window/os_android.h b/app/internal/window/os_android.h
index 288d633..e19d28b 100644
--- a/app/internal/window/os_android.h
+++ b/app/internal/window/os_android.h
@@ -17,3 +17,4 @@ __attribute__ ((visibility ("hidden"))) void gio_jni_CallVoidMethod_J(JNIEnv *en
 __attribute__ ((visibility ("hidden"))) jbyte *gio_jni_GetByteArrayElements(JNIEnv *env, jbyteArray arr);
 __attribute__ ((visibility ("hidden"))) void gio_jni_ReleaseByteArrayElements(JNIEnv *env, jbyteArray arr, jbyte *bytes);
 __attribute__ ((visibility ("hidden"))) jsize gio_jni_GetArrayLength(JNIEnv *env, jbyteArray arr);
+__attribute__ ((visibility ("hidden"))) void gio_jni_RegisterFragment(JNIEnv *env, jobject view, jmethodID mid, char* del);
diff --git a/app/window.go b/app/window.go
index 8b41d21..72b7bd8 100644
--- a/app/window.go
+++ b/app/window.go
@@ -26,8 +26,9 @@ type Option func(opts *window.Options)
 
 // Window represents an operating system window.
 type Window struct {
-	driver window.Driver
-	gpu    *gpu.GPU
+	driver     window.Driver
+	driverWait chan struct{}
+	gpu        *gpu.GPU
 
 	out         chan event.Event
 	in          chan event.Event
@@ -95,6 +96,7 @@ func NewWindow(options ...Option) *Window {
 		invalidates: make(chan struct{}, 1),
 		frames:      make(chan *op.Ops),
 		frameAck:    make(chan struct{}),
+		driverWait:  make(chan struct{}),
 	}
 	w.callbacks.w = w
 	go w.run(opts)
@@ -191,6 +193,11 @@ func (c *callbacks) Event(e event.Event) {
 	<-c.w.ack
 }
 
+// funcEvent is sent to run a closure in the Window's run loop.
+type funcEvent struct {
+	f func()
+}
+
 func (w *Window) waitAck() {
 	// Send a dummy event; when it gets through we
 	// know the application has processed the previous event.
@@ -315,7 +322,16 @@ func (w *Window) run(opts *window.Options) {
 				w.out <- e
 				w.waitAck()
 			case driverEvent:
+				oldDriver := w.driver
 				w.driver = e2.driver
+				switch {
+				case oldDriver == nil && w.driver != nil:
+					close(w.driverWait)
+				case oldDriver != nil && w.driver == nil:
+					w.driverWait = make(chan struct{})
+				}
+			case funcEvent:
+				e2.f()
 			case system.DestroyEvent:
 				w.destroyGPU()
 				w.out <- e2
@@ -359,3 +375,5 @@ func Size(w, h unit.Value) Option {
 }
 
 func (driverEvent) ImplementsEvent() {}
+
+func (funcEvent) ImplementsEvent() {}
-- 
2.16.2
View this thread in the archives