Slide deck for Gio Android presentation 25 June 2020.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

732 lines
26 KiB

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=1024, user-scalable=no">
<title>Your deck.js Presentation</title>
<!-- Required stylesheet -->
<link rel="stylesheet" media="screen" href="core/deck.core.css">
<!-- Extension CSS files go here. Remove or add as needed. -->
<link rel="stylesheet" media="screen" href="extensions/goto/deck.goto.css">
<link rel="stylesheet" media="screen" href="extensions/menu/deck.menu.css">
<link rel="stylesheet" media="screen" href="extensions/navigation/deck.navigation.css">
<link rel="stylesheet" media="screen" href="extensions/status/deck.status.css">
<!-- <link rel="stylesheet" media="screen" href="extensions/scale/deck.scale.css"> -->
<!-- Style theme. More available in /themes/style/ or create your own. -->
<link rel="stylesheet" media="screen" href="themes/style/swiss.css">
<!-- Transition theme. More available in /themes/transition/ or create your own. -->
<link rel="stylesheet" media="screen" href="themes/transition/horizontal-slide.css">
<!-- Basic black and white print styles -->
<link rel="stylesheet" media="print" href="core/print.css">
<!-- Required Modernizr file -->
<script src="modernizr.custom.js"></script>
</head>
<body style="margin:0px;padding:0px;overflow:hidden">
<div class="deck-container">
<!-- Begin slides. Just make elements with a class of slide. -->
<section class="slide">
<h2>Android for Gio programmers</h2> <br></br>
<h3>Native Development Kit (NDK): direct access from cgo</h3>
<ul><li>limited Android API access</li></ul>
<h3>Java Native Interface: call Java code via cgo</h3>
<ul><li>custom Java classes</li></ul>
<h3>Java fragments: extend the main Gio Activity</h3>
<ul><li>permissions and application lifecycle</li></ul>
</section>
<section class="slide">
<h2>NDK &mdash; sensor.h</h2>
<p>$ANDROID_HOME/ndk-bundle/sysroot/usr/include/android/sensor.h</p>
<pre style="font-size: 90%"><code># android/sensor.h
#if __ANDROID_API__ >= 26
/**
* Get a reference to the sensor manager. ASensorManager is a singleton
* per package as different packages may have access to different sensors.
*
* Example:
*
* ASensorManager* sensorManager = ASensorManager_getInstanceForPackage("foo.bar .baz");
*
*/
ASensorManager* ASensorManager_getInstanceForPackage(const char* packageName) __INTRODUCED_IN(26);
#endif
/**
* Returns the default sensor for the given type, or NULL if no sensor
* of that type exists.
*/
ASensor const* ASensorManager_getDefaultSensor(ASensorManager* manager, int type);
/**
* {@link ASensorManager} is an opaque type to manage sensors and
* events queues.
*
* {@link ASensorManager} is a singleton that can be obtained using
* ASensorManager_getInstance().
*
* This file provides a set of functions that uses {@link
* ASensorManager} to access and list hardware sensors, and
* create and destroy event queues:
* - ASensorManager_getSensorList()
* - ASensorManager_getDefaultSensor()
* - ASensorManager_getDefaultSensorEx()
* - ASensorManager_createEventQueue()
* - ASensorManager_destroyEventQueue()
*/
typedef struct ASensorManager ASensorManager;
</code></pre>
</section>
<section class="slide">
<h2>NDK &mdash; nswrap</h2>
<img src="nswrap-git.png"></img>
</section>
<!--
<section class="slide">
<h2>NDK android27.yaml</h2>
<p>nswrap configuration: android27.yaml</p>
<pre style="font-size: 65%"><code>package: android27
libraries: [android, log, nativewindow, m]
inputfiles:
- $ANDROID_HOME/ndk-bundle/sysroot/usr/include/android/native_window.h
- $ANDROID_HOME/ndk-bundle/sysroot/usr/include/android/log.h
- $ANDROID_HOME/ndk-bundle/sysroot/usr/include/android/sensor.h
- $ANDROID_HOME/ndk-bundle/sysroot/usr/include/jni.h
- $ANDROID_HOME/ndk-bundle/sysroot/usr/include/sys/stat.h
sysimports:
- android/native_window.h
- android/log.h
- android/sensor.h
- jni.h
- stdlib.h
- sys/stat.h
functions:
- .*
functionignore:
- JNI_CreateJavaVM
- JNI_GetCreatedJavaVMs
- JNI_GetDefaultJavaVMInitArgs
- JNI_OnLoad
- JNI_OnUnload
- AHardwareBuffer_lockPlanes
- wcstoimax
- wcstoumax
- imaxdiv
# - __system_property_get
enums:
- .*
typesubs:
Stat: Stat_t
Stat64: Stat64_t
clang: $ANDROID_HOME/ndk-bundle/toolchains/llvm/prebuilt/darwin-x86_64/bin/aarch64-linux-android27-clang
</code></pre>
</section>
<section class = "slide">
<h2>NDK &mdash; Clang AST</h2>
<p>nswrap reads the Clang ast</p>
<pre><code>...
|-TypedefDecl 0x7fac6e838220 &lt;line:480:1, col:31&gt; col:31 referenced ASensorManager 'struct ASensorManager':'struct ASensorManager'
| |-ElaboratedType 0x7fac6e8371d0 'struct ASensorManager' sugar
| | `-RecordType 0x7fac6e837180 'struct ASensorManager'
| | `-Record 0x7fac6e837100 'ASensorManager'
| `-FullComment 0x7fac6e8468a0 &lt;line:465:3, col:10&gt;
| |-ParagraphComment 0x7fac6e8465a0 &lt;col:3, col:4&gt;
| | `-TextComment 0x7fac6e846570 &lt;col:3, col:4&gt; Text=" {"
| `-VerbatimBlockComment 0x7fac6e8465c0 &lt;col:5, col:10&gt; Name="link" CloseName=""
| |-VerbatimBlockLineComment 0x7fac6e846610 &lt;col:10, col:66&gt; Text=" ASensorManager} is an opaque type to manage sensors and"
| |-VerbatimBlockLineComment 0x7fac6e846630 &lt;line:466:3, col:18&gt; Text=" events queues."
...
|-FunctionDecl 0x7fac6e838b28 &lt;line:568:1, versioning.h:19:89&gt; sensor.h:568:17 ASensorManager_getInstanceForPackage 'ASensorManager *(const char *)'
| |-ParmVarDecl 0x7fac6e838a00 &lt;col:54, col:66&gt; col:66 packageName 'const char *'
| |-AnnotateAttr 0x7fac6e838bc8 &lt;versioning.h:19:51, col:87&gt; "introduced_in=26"
</code></pre>
</section>
<section class="slide">
<h2>NDK &mdash; nswrap output</h2>
<p>nswrap generates 1270 lines of Go code</p>
<p>android27/main.go</p>
<pre style="font-size:75%"><code>package android27
...
type ASensorManager = C.struct_ASensorManager
const ASENSOR_TYPE_INVALID = C.ASENSOR_TYPE_INVALID
const ASENSOR_TYPE_ACCELEROMETER = C.ASENSOR_TYPE_ACCELEROMETER
const ASENSOR_TYPE_MAGNETIC_FIELD = C.ASENSOR_TYPE_MAGNETIC_FIELD
const ASENSOR_TYPE_GYROSCOPE = C.ASENSOR_TYPE_GYROSCOPE
...
func ASensorManagerGetInstanceForPackage(packageName *Char) *ASensorManager {
ret := (*ASensorManager)(unsafe.Pointer(C.ASensorManager_getInstanceForPackage((*C.char)(packageName))))
return ret
}
func ASensorManagerGetDefaultSensor(manager *ASensorManager, type_ Int) *ASensor {
ret := (*ASensor)(unsafe.Pointer(C.ASensorManager_getDefaultSensor((*C.ASensorManager)(manager), (C.int)(type_))))
return ret
}
</code></pre>
</section>
-->
<section class="slide">
<h2>NDK &mdash; Android-specific Go code</h2>
<p>os_android.go</p>
<pre style="font-size: 65%"><code>import ndk "git.wow.st/gmp/android-go/android27"
func sensorLoop() {
// the Android Looper is thread-local
runtime.LockOSThread()
pkg := ndk.CharWithGoString("st.wow.git.sensors")
manager := ndk.ASensorManagerGetInstanceForPackage(pkg)
pkg.Free()
sens := ndk.ASensorManagerGetDefaultSensor(manager, ndk.ASENSOR_TYPE_ACCELEROMETER)
stp := ndk.ASensorGetStringType(sens)
labchan <- "sensor" + stp.String()
var looper *ndk.ALooper
var queue *ndk.ASensorEventQueue
looper_id := 1
var rate ndk.Int32_t = 60
setup := func() {
looper = ndk.ALooperForThread()
if (looper == nil) {
labchan <- "no looper for thread"
looper = ndk.ALooperPrepare(ndk.ALOOPER_PREPARE_ALLOW_NON_CALLBACKS)
}
queue = ndk.ASensorManagerCreateEventQueue(manager, looper, looper_id)
ndk.ASensorEventQueueEnableSensor(queue, sens)
ndk.ASensorEventQueueSetEventRate(queue, sens, 1000000/rate) // microseconds
}
setup()
...
</code></pre>
</section>
<section class="slide">
<h2>NDK &mdash; Android main polling loop</h2>
<p>os_android.go (86 total lines)</p>
<pre style="font-size:75%"><code>func sensorLoop() {
...
for {
var zero ndk.Int
id := (int)(ndk.ALooperPollOnce(-1, &zero, &zero, (*unsafe.Pointer)(unsafe.Pointer(nil)))) // poll forever
if (id == ndk.ALOOPER_POLL_ERROR) { // set up a new looper
labchan <- "getting a new looper"
setup()
continue
}
if (id == looper_id) {
var event ndk.ASensorEvent
if (ndk.ASensorEventQueueGetEvents(queue, &event, 1) != 1) {
continue
}
accel := (&event).Acceleration()
senschan <- vector{float64(accel.X()), float64(accel.Y()), float64(accel.Z())}
}
}
</code></pre>
</section>
<section class="slide">
<h2>NDK &mdash; Gio app setup</h2>
<p>main.go</p>
<pre style="font-size: 70%"><code>func eventloop() {
w := app.NewWindow( app.Size(unit.Dp(400), unit.Dp(400)), app.Title("Hello"))
th := material.NewTheme(gofont.Collection())
var ops op.Ops
var accel vector
var cx, cy, cvx, cvy, cs float32 // circle position, velocity and radius
cx = 200
cy = 200
cs = 50 // circle size (radius)
circle := func(gtx C, width, height float32) D {
// clip circle position and bounce off of the edges
if (cx < cs) { cx = cs; cvx = (-0.5) * cvx }
if (cy < cs) { cy = cs; cvy = (-0.5) * cvy }
if (cx > width - cs) { cx = width - cs; cvx = (-0.5) * cvx }
if (cy > height - cs) { cy = height - cs; cvy = (-0.5) * cvy }
blue := color.RGBA{0x3f, 0x51, 0xb5, 0x80}
r1 := f32.Rectangle{f32.Point{cx - cs, cy - cs}, f32.Point{cx + cs, cy + cs}}
clip.Rect{ Rect: r1, NE: cs, NW: cs, SE: cs, SW: cs}.Op(gtx.Ops).Add(gtx.Ops)
paint.ColorOp{Color: blue}.Add(gtx.Ops)
paint.PaintOp{Rect: r1}.Add(gtx.Ops)
var ret D
ret.Size.X = int(cs * 2)
ret.Size.Y = int(cs * 2)
return ret
}
...
</code></pre>
</section>
<section class="slide">
<h2>NDK &mdash; Gio app main loop</h2>
<p>main.go (174 total lines)</p>
<pre style="font-size:65%"><code> ...
ticker := time.NewTicker(time.Second / 60)
var t, told int64
t = time.Now().UnixNano()
for {
select {
case <- ticker.C:
told = t
t = time.Now().UnixNano()
elapsed := float32((t - told) / 1000000)
cvx = cvx - float32(accel.x/1000) * elapsed
cvy = cvy + float32(accel.y/1000) * elapsed
cx = cx + cvx * elapsed
cy = cy + cvy * elapsed
w.Invalidate()
case x := <-senschan:
accel = x
case e := <-w.Events():
switch e := e.(type) {
case system.FrameEvent:
gtx := layout.NewContext(&ops, e)
... // draw everything
e.Frame(gtx.Ops)
}
}
}
</code></pre>
</section>
<section class="slide">
<h2>NDK &mdash; Building an APK</h2>
<pre><code>$ gogio -target android -minsdk 27 .
$ adb install sensors.apk
</code></pre>
<img src="accel.jpg" style="width:60%"></img>
</section>
<section class="slide">
<h2>JNI &mdash; Java Native Interface</h2>
<p>Use JNI from cgo to:</p>
<ul>
<li>Access Android Java APIs (with limitations)</li>
<li>Call methods on your own Java classes</li>
</ul>
<p>To use it:
<ul>
<li>create (or acquire) a Java Virtual Machine (JavaVM)</li>
<li>use the JavaVM to obtain a JNI Native Interface struct (JNIEnv)</li>
<li>call function pointers inside the JNIEnv struct</li>
</ul>
<p>$ANDROID_HOME/ndk-bundle/sysroot/usr/include/jni.h</p>
<pre style="font-size:65%"><code>typedef const struct JNINativeInterface* JNIEnv;
typedef const struct JNIInvokeInterface* JavaVM;
/*
* Table of interface function pointers.
*/
struct JNINativeInterface {
jobject (*NewObject)(JNIEnv*, jclass, jmethodID, ...);
jclass (*GetObjectClass)(JNIEnv*, jobject);
jboolean (*IsInstanceOf)(JNIEnv*, jobject, jclass);
jmethodID (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);
jobject (*CallObjectMethod)(JNIEnv*, jobject, jmethodID, ...);
jboolean (*CallBooleanMethod)(JNIEnv*, jobject, jmethodID, ...);
jbyte (*CallByteMethod)(JNIEnv*, jobject, jmethodID, ...);
jchar (*CallCharMethod)(JNIEnv*, jobject, jmethodID, ...);
jshort (*CallShortMethod)(JNIEnv*, jobject, jmethodID, ...);
jint (*CallIntMethod)(JNIEnv*, jobject, jmethodID, ...);
jlong (*CallLongMethod)(JNIEnv*, jobject, jmethodID, ...);
jfloat (*CallFloatMethod)(JNIEnv*, jobject, jmethodID, ...);
jdouble (*CallDoubleMethod)(JNIEnv*, jobject, jmethodID, ...);
void (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...);
jobject (*GetObjectField)(JNIEnv*, jobject, jfieldID);
jboolean (*GetBooleanField)(JNIEnv*, jobject, jfieldID);
jbyte (*GetByteField)(JNIEnv*, jobject, jfieldID);
jchar (*GetCharField)(JNIEnv*, jobject, jfieldID);
jshort (*GetShortField)(JNIEnv*, jobject, jfieldID);
jint (*GetIntField)(JNIEnv*, jobject, jfieldID);
jlong (*GetLongField)(JNIEnv*, jobject, jfieldID);
jfloat (*GetFloatField)(JNIEnv*, jobject, jfieldID);
jdouble (*GetDoubleField)(JNIEnv*, jobject, jfieldID);
...
</code></pre>
</section>
<section class="slide">
<h2>JNI &mdash; git.wow.st/gmp/jni</h2>
<iframe frameborder="0" src="jni/jni.html" style="transform:scale(1.5); transform-origin: 50% 0%;" width=800 height=13300></iframe>
</section>
<section class="slide">
<h2>JNI &mdash; Basics</h2>
<p>my/java/AClass.java:</p>
<pre style="font-size:60%"><code>package my.java;
public class AClass {
public int Num() {
return 17;
}
}
</code></pre>
<p>main.go:</p>
<pre style="font-size:60%"><code>package main
//go:generate javac my/java/AClass.java
import (
"fmt"
"git.wow.st/gmp/jni"
)
func main() {
vm := jni.CreateJavaVM()
err := jni.Do(vm, func(env jni.Env) error {
cls := jni.FindClass(env, "my/java/AClass")
if cls == 0 {
return fmt.Errorf("Class not found")
}
mid := jni.GetMethodID(env, cls, "<init>", "()V")
if mid == nil {
return fmt.Errorf("Initializer not found")
}
inst, err := jni.NewObject(env, cls, mid)
if err != nil {
return err
}
mid = jni.GetMethodID(env, cls, "Num", "()I")
if mid == nil {
return fmt.Errorf("Method not found")
}
res, err := jni.CallIntMethod(env, inst, mid)
if err != nil {
return err
}
fmt.Printf("Result: %d\n", res)
return nil
})
if err != nil {
fmt.Printf(err.Error())
}
}
</code></pre>
</section>
<section class="slide">
<h2>JNI &mdash; Using JNI on Android</h2>
<p><code>gogio</code> looks for <code>.jar</code> files in your package
directory and across all imports. Every class from these <code>.jar</code> files
will be bundled with your <code>apk</code></p>
<pre style="font-size:65%"><code>$ apktool d hrm.apk
$ find hrm/smali -type f
hrm/smali/org/gioui/GioView$1.smali
hrm/smali/org/gioui/GioView.smali
hrm/smali/org/gioui/GioView$3.smali
hrm/smali/org/gioui/GioView$4.smali
hrm/smali/org/gioui/GioView$2.smali
hrm/smali/org/gioui/Gio.smali
hrm/smali/org/gioui/GioActivity.smali
hrm/smali/org/gioui/Gio$1.smali
hrm/smali/org/gioui/GioView$InputConnection.smali
hrm/smali/st/wow/git/ble/BlessedConnect$3.smali
hrm/smali/st/wow/git/ble/BlessedConnect$1.smali
hrm/smali/st/wow/git/ble/BlessedConnect$2.smali
hrm/smali/st/wow/git/ble/BlessedConnect.smali
hrm/smali/timber/log/Timber.smali
hrm/smali/timber/log/Timber$Tree.smali
hrm/smali/timber/log/Timber$DebugTree.smali
hrm/smali/timber/log/Timber$1.smali
hrm/smali/com/welie/blessed/BluetoothPeripheral$1$9.smali
hrm/smali/com/welie/blessed/BluetoothPeripheral$11.smali
hrm/smali/com/welie/blessed/BluetoothCentral$4.smali
...
</code></pre>
</section>
<section class="slide">
<h2>JNI &mdash; JNI Limitations on Android</h2>
<p>Key limitation: cannot override <code>Activity</code> methods:</p>
<ul>
<li><code>onActivityResult</code></li>
<li><code>onRequestPermissionResult</code></li>
<li>Lifecycle methods: <code>onCreate</code>, <code>onDestroy</code>, etc.</li>
</ul>
</section>
<section class="slide">
<h2>Java Fragments</h2>
<p>There are lots of gaps in the NDK and limitations with JNI.
Fragments can access the rest.</p>
<img src="fragment.png"></img>
</section>
<section class="slide">
<h2>Java Fragments &mdash; Basics</h2>
<p>os_android.go</p>
<pre style="font-size:61%"><code>import (
"gioui.org/app"
"git.wow.st/gmp/jni"
)
func registerFragment(w *app.Window) {
vm := jni.JVMFor(app.JavaVM())
w.Do(func(view uintptr) {
err := jni.Do(vm, func(env jni.Env) error {
cls := jni.FindClass(env, "st/wow/git/fragment/AFrag")
mth := jni.GetMethodID(env, cls, "<init>", "()V");
inst, err := jni.NewObject(env, cls, mth)
mth = jni.GetMethodID(env, cls, "register", "(Landroid/view/View;)V");
jni.CallVoidMethod(env, inst, mth, jni.Value(view))
return nil
})
if err != nil { ... }
})
}
</code></pre>
<p>AFrag.java</p>
<pre style="font-size:61%"><code>package com.afrag.AFrag;
import android.app.Activity;
import android.app.Fragment;
import android.app.FragmentTransaction;
import android.content.Context;
import android.util.Log;
import android.view.View;
public class AFrag extends Fragment {
public void register(View view) {
Activity act = (Activity)view.getContext();
FragmentTransaction ft = act.getFragmentManager().beginTransaction();
ft.add(this, "AFrag");
ft.commitNow();
}
@Override public void onAttach(Context ctx) {
super.onAttach(ctx);
}
}
</code></pre>
</section>
<section class="slide">
<h2>Java Fragments &mdash; Permissions (Java)</h2>
<p>Here we are going to connect to Bluetooth low-energy peripherals using a
library called Blessed (<a href="https://github.com/weliem/blessed-android">https://github.com/weliem/blessed-android</a>)</p>
<p>We need permission to access Bluetooth hardware, which, on Android,
requies <code>ACCESS_FINE_LOCATION</code>, which is a "dangerous" permission. See:
<a href="https://developer.android.com/reference/android/Manifest.permission#ACCESS_FINE_LOCATION">developer.android.com/reference/android/Manifest.permission</a></p>
<p>We need to request access to this permission at runtime.</p>
<p>BlessedConnect.java:</p>
<pre style="font-size:57%"><code>package st.wow.git.ble;
import android.app.Fragment;
import com.welie.blessed.BluetoothCentral;
import com.welie.blessed.BluetoothCentralCallback;
import com.welie.blessed.BluetoothPeripheral;
import com.welie.blessed.BluetoothPeripheralCallback;
public class BlessedConnect extends Fragment {
final int PERMISSION_REQUEST = 1;
final int REQUEST_ENABLE_BT = 2;
@Override public void onAttach(Context ctx) {
super.onAttach(ctx);
if (ctx.checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
requestPermissions(new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, PERMISSION_REQUEST);
}
System.loadLibrary("gio");
installComplete(this);
}
public void onRequestPermissionsResult (int requestCode, String[] permissions, int[] grantResults) {
if (requestCode == PERMISSION_REQUEST) {
boolean granted = true;
for (int x : grantResults) {
if (x == PackageManager.PERMISSION_DENIED) {
granted = false;
break;
}
}
}
}
static private native void installComplete(BlessedConnect p);
}
</code></pre>
</section>
<section class="slide">
<h2>Java Fragments &mdash; Permissions (Go)</h2>
<p>We need to ask <code>gogio</code> to add all required permissions to
AndroidManifest.xml</p>
<p>os_android.go:</p>
<pre style="font-size:70%"><code>import (
"gioui.org/app"
_ "gioui.org/app/permission/bluetooth"
)
</code></pre>
<p>AndroidManifest.xml:</p>
<pre style="font-size:70%"><code>
&lt;?xml version="1.0" encoding="utf-8" standalone="no"?&gt;&lt;manifest xmlns:android="http://schemas.android.com/apk/res/android" android:compileSdkVersion="29" android:compileSdkVersionCodename="10" package="git.wow.st.hrm" platformBuildVersionCode="29" platformBuildVersionName="10"&gt;
&lt;uses-permission android:name="android.permission.BLUETOOTH"/&gt;
&lt;uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/&gt;
&lt;uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/&gt;
&lt;uses-feature android:glEsVersion="0x00030000" android:required="false"/&gt;
&lt;uses-feature android:name="android.hardware.bluetooth" android:required="false"/&gt;
&lt;uses-feature android:name="android.hardware.bluetooth_le" android:required="false"/&gt;
&lt;application android:icon="@mipmap/ic_launcher" android:label="Hrm"&gt;
&lt;activity android:configChanges="keyboardHidden|orientation" android:label="Hrm" android:name="org.gioui.GioActivity" android:theme="@style/Theme.GioApp" android:windowSoftInputMode="adjustResize"&gt;
&lt;intent-filter&gt;
&lt;action android:name="android.intent.action.MAIN"/&gt;
&lt;category android:name="android.intent.category.LAUNCHER"/&gt;
&lt;/intent-filter&gt;
&lt;/activity&gt;
&lt;/application&gt;
&lt;/manifest&gt;
</code></pre>
</section>
<section class ="slide">
<h2>Java Fragments &mdash; Gio app</h2>
<p>main.go is 482 lines. No platform-specific code is required in the Gio app.</p>
<pre style="font-size:65%"><code>import (
"gioui.org/app"
"git.wow.st/gmp/ble"
)
func eventloop()
w := app.NewWindow()
...
b := ble.NewBLE()
b.Enable(w)
select {
case e := <-b.Events():
switch e := e.(type) {
case ble.UpdateStateEvent: ...
case ble.DiscoverPeripheralEvent: ...
case ble.ConnectEvent: ...
case ble.ConnectTimeoutEvent: ...
case ble.DiscoverServiceEvent: ...
case ble.DiscoverCharacteristicEvent: ...
case ble.UpdateValueEvent: ...
}
w.Invalidate()
case e := <-w.Events():
switch e := e.(type) {
case system.DestroyEvent:
return
case system.FrameEvent:
gtx := layout.NewContext(&ops, e)
... // draw everything
e.Frame(gtx.Ops)
}
}
}
</code></pre>
</section>
<section class="slide">
<h2>Java Fragments &mdash; BLE API</h2>
<pre><code>func (b *BLE) Enable(w *app.Window)
func (b *BLE) State() string
func (b *BLE) Events() chan interface{}
func (b *BLE) Scan()
func (b *BLE) StopScan()
func (p Peripheral) DiscoverServices()
func (p Peripheral) DiscoverCharacteristics(serv Service)
func (p Peripheral) SetNotifyValue(c Characteristic)
func (b *BLE) Connect(p Peripheral) bool
func CancelConnection(p Peripheral)
func Disconnect(p Peripheral)
</code></pre>
<img src="ble.png" style="width:70%"></img>
</section>
<section class ="slide">
<h2>Links</h2>
<ol>
<p><a href="https://git.wow.st/gmp/nswrap">git.wow.st/gmp/nswrap</a></p>
<p><a href="https://git.wow.st/gmp/android-go">git.wow.st/gmp/android-go</a></p>
<p><a href="https://git.wow.st/gmp/hrm">git.wow.st/gmp/jni</a></p>
<p><a href="https://git.wow.st/gmp/ble">git.wow.st/gmp/ble</a></p>
<p><a href="https://git.wow.st/gmp/hrm">git.wow.st/gmp/hrm</a></p>
<p><a href="https://git.wow.st/gmp/passgo">git.wow.st/gmp/passgo</a></p>
</ol>
</section>
<!-- End slides. -->
<!-- Begin extension snippets. Add or remove as needed. -->
<!-- deck.navigation snippet -->
<div aria-role="navigation">
<a href="#" class="deck-prev-link" title="Previous">&#8592;</a>
<a href="#" class="deck-next-link" title="Next">&#8594;</a>
</div>
<!-- deck.status snippet -->
<p class="deck-status" aria-role="status">
<span class="deck-status-current"></span>
/
<span class="deck-status-total"></span>
</p>
<!-- deck.goto snippet -->
<form action="." method="get" class="goto-form">
<label for="goto-slide">Go to slide:</label>
<input type="text" name="slidenum" id="goto-slide" list="goto-datalist">
<datalist id="goto-datalist"></datalist>
<input type="submit" value="Go">
</form>
<!-- End extension snippets. -->
</div>
<!-- Required JS files. -->
<script src="jquery.min.js"></script>
<script src="core/deck.core.js"></script>
<!-- Extension JS files. Add or remove as needed. -->
<script src="extensions/menu/deck.menu.js"></script>
<script src="extensions/goto/deck.goto.js"></script>
<script src="extensions/status/deck.status.js"></script>
<script src="extensions/navigation/deck.navigation.js"></script>
<!-- <script src="extensions/scale/deck.scale.js"></script> -->
<!-- Initialize the deck. You can put this in an external file if desired. -->
<script>
$(function() {
$.deck('.slide');
});
</script>
</body>
</html>