~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
View this thread in the archives

[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