cmd/gogio: add permissions system for Android

Search for imports of the form gioui.org/app/permission/* and add
required permissions to AndroidManifest.xml.

Signed-off-by: Greg Pomerantz <gmp.gio@wow.st>
This commit is contained in:
Greg Pomerantz
2019-11-01 13:53:01 -04:00
committed by Elias Naur
parent f418684c0e
commit af353822fa
5 changed files with 153 additions and 21 deletions
+35 -1
View File
@@ -36,7 +36,7 @@ DestroyEvent is received.
Main Main
The Main function must be called from a programs main function, to hand over The Main function must be called from a program's main function, to hand over
control of the main thread to operating systems that need it. control of the main thread to operating systems that need it.
Because Main is also blocking, the event loop of a Window must run in a goroutine. Because Main is also blocking, the event loop of a Window must run in a goroutine.
@@ -63,5 +63,39 @@ A Window's Queue method returns an event.Queue implementation that distributes
incoming events to the event handlers declared in the latest frame. incoming events to the event handlers declared in the latest frame.
See the gioui.org/ui package for more information about event handlers. See the gioui.org/ui package for more information about event handlers.
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"
)
*/ */
package app package app
+1
View File
@@ -0,0 +1 @@
package bluetooth
+1
View File
@@ -0,0 +1 @@
package bluetooth_le
+93 -20
View File
@@ -4,6 +4,7 @@ package main
import ( import (
"archive/zip" "archive/zip"
"bytes"
"errors" "errors"
"fmt" "fmt"
"io" "io"
@@ -15,6 +16,7 @@ import (
"runtime" "runtime"
"strconv" "strconv"
"strings" "strings"
"text/template"
"golang.org/x/sync/errgroup" "golang.org/x/sync/errgroup"
"golang.org/x/tools/go/packages" "golang.org/x/tools/go/packages"
@@ -39,6 +41,17 @@ type errWriter struct {
var exeSuffix string var exeSuffix string
type manifestData struct {
AppID string
Version int
MinSDK int
TargetSDK int
Permissions []string
Features []string
IconSnip string
AppName string
}
func init() { func init() {
if runtime.GOOS == "windows" { if runtime.GOOS == "windows" {
exeSuffix = ".exe" exeSuffix = ".exe"
@@ -67,6 +80,8 @@ func buildAndroid(tmpDir string, bi *buildInfo) error {
androidjar: filepath.Join(platform, "android.jar"), androidjar: filepath.Join(platform, "android.jar"),
} }
perms := []string{"default"}
const permPref = "gioui.org/app/permission/"
cfg := &packages.Config{ cfg := &packages.Config{
Mode: packages.NeedName + Mode: packages.NeedName +
packages.NeedFiles + packages.NeedFiles +
@@ -95,6 +110,12 @@ func buildAndroid(tmpDir string, bi *buildInfo) error {
return err return err
} }
extraJars = append(extraJars, jars...) extraJars = append(extraJars, jars...)
switch {
case p.PkgPath == "net":
perms = append(perms, "network")
case strings.HasPrefix(p.PkgPath, permPref):
perms = append(perms, p.PkgPath[len(permPref):])
}
for _, imp := range p.Imports { for _, imp := range p.Imports {
if !visitedPkgs[imp.ID] { if !visitedPkgs[imp.ID] {
@@ -113,9 +134,9 @@ func buildAndroid(tmpDir string, bi *buildInfo) error {
} }
switch *buildMode { switch *buildMode {
case "archive": case "archive":
return archiveAndroid(tmpDir, bi) return archiveAndroid(tmpDir, bi, perms)
case "exe": case "exe":
if err := exeAndroid(tmpDir, tools, bi, extraJars); err != nil { if err := exeAndroid(tmpDir, tools, bi, extraJars, perms); err != nil {
return err return err
} }
return signAPK(tmpDir, tools, bi) return signAPK(tmpDir, tools, bi)
@@ -206,7 +227,7 @@ func compileAndroid(tmpDir string, tools *androidTools, bi *buildInfo) (err erro
return builds.Wait() return builds.Wait()
} }
func archiveAndroid(tmpDir string, bi *buildInfo) (err error) { func archiveAndroid(tmpDir string, bi *buildInfo, perms []string) (err error) {
aarFile := *destPath aarFile := *destPath
if aarFile == "" { if aarFile == "" {
aarFile = fmt.Sprintf("%s.aar", bi.name) aarFile = fmt.Sprintf("%s.aar", bi.name)
@@ -227,11 +248,25 @@ func archiveAndroid(tmpDir string, bi *buildInfo) (err error) {
defer aarw.Close() defer aarw.Close()
aarw.Create("R.txt") aarw.Create("R.txt")
aarw.Create("res/") aarw.Create("res/")
permissions, features := getPermissions(perms)
manifest := aarw.Create("AndroidManifest.xml") manifest := aarw.Create("AndroidManifest.xml")
manifest.Write([]byte(fmt.Sprintf(`<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="%s"> manifestSrc := manifestData{
<uses-sdk android:minSdkVersion="%d"/> AppID: bi.appID,
<uses-feature android:glEsVersion="0x00030000" android:required="true" /> MinSDK: bi.minsdk,
</manifest>`, bi.appID, bi.minsdk))) Permissions: permissions,
Features: features,
}
tmpl, err := template.New("manifest").Parse(
`<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="{{.AppID}}">
<uses-sdk android:minSdkVersion="{{.MinSDK}}"/>
{{range .Permissions}} <uses-permission android:name="{{.}}"/>
{{end}}{{range .Features}} <uses-feature android:{{.}} android:required="false"/>
{{end}}</manifest>
`)
if err != nil {
panic(err)
}
err = tmpl.Execute(manifest, manifestSrc)
proguard := aarw.Create("proguard.txt") proguard := aarw.Create("proguard.txt")
proguard.Write([]byte(`-keep class org.gioui.** { *; }`)) proguard.Write([]byte(`-keep class org.gioui.** { *; }`))
@@ -251,7 +286,7 @@ func archiveAndroid(tmpDir string, bi *buildInfo) (err error) {
return aarw.Close() return aarw.Close()
} }
func exeAndroid(tmpDir string, tools *androidTools, bi *buildInfo, extraJars []string) (err error) { func exeAndroid(tmpDir string, tools *androidTools, bi *buildInfo, extraJars, perms []string) (err error) {
classes := filepath.Join(tmpDir, "classes") classes := filepath.Join(tmpDir, "classes")
var classFiles []string var classFiles []string
err = filepath.Walk(classes, func(path string, f os.FileInfo, err error) error { err = filepath.Walk(classes, func(path string, f os.FileInfo, err error) error {
@@ -342,18 +377,30 @@ func exeAndroid(tmpDir string, tools *androidTools, bi *buildInfo, extraJars []s
if bi.minsdk > targetSDK { if bi.minsdk > targetSDK {
targetSDK = bi.minsdk targetSDK = bi.minsdk
} }
permissions, features := getPermissions(perms)
appName := strings.Title(bi.name) appName := strings.Title(bi.name)
manifestSrc := fmt.Sprintf(`<?xml version="1.0" encoding="utf-8"?> manifestSrc := manifestData{
AppID: bi.appID,
Version: bi.version,
MinSDK: bi.minsdk,
TargetSDK: targetSDK,
Permissions: permissions,
Features: features,
IconSnip: iconSnip,
AppName: appName,
}
tmpl, err := template.New("test").Parse(
`<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="%s" package="{{.AppID}}"
android:versionCode="%d" android:versionCode="{{.Version}}"
android:versionName="1.0.%d"> android:versionName="1.0.{{.Version}}">
<uses-sdk android:minSdkVersion="%d" android:targetSdkVersion="%d" /> <uses-sdk android:minSdkVersion="{{.MinSDK}}" android:targetSdkVersion="{{.TargetSDK}}" />
<uses-permission android:name="android.permission.INTERNET" /> {{range .Permissions}} <uses-permission android:name="{{.}}"/>
<uses-feature android:glEsVersion="0x00030000"/> {{end}}{{range .Features}} <uses-feature android:{{.}} android:required="false"/>
<application %s android:label="%s"> {{end}} <application {{.IconSnip}} android:label="{{.AppName}}">
<activity android:name="org.gioui.GioActivity" <activity android:name="org.gioui.GioActivity"
android:label="%s" android:label="{{.AppName}}"
android:theme="@style/Theme.GioApp" android:theme="@style/Theme.GioApp"
android:configChanges="orientation|keyboardHidden" android:configChanges="orientation|keyboardHidden"
android:windowSoftInputMode="adjustResize"> android:windowSoftInputMode="adjustResize">
@@ -363,11 +410,16 @@ func exeAndroid(tmpDir string, tools *androidTools, bi *buildInfo, extraJars []s
</intent-filter> </intent-filter>
</activity> </activity>
</application> </application>
</manifest>`, bi.appID, bi.version, bi.version, bi.minsdk, targetSDK, iconSnip, appName, appName) </manifest>`)
manifest := filepath.Join(tmpDir, "AndroidManifest.xml") var manifestBuffer bytes.Buffer
if err := ioutil.WriteFile(manifest, []byte(manifestSrc), 0660); err != nil { if err := tmpl.Execute(&manifestBuffer, manifestSrc); err != nil {
return err return err
} }
manifest := filepath.Join(tmpDir, "AndroidManifest.xml")
if err := ioutil.WriteFile(manifest, manifestBuffer.Bytes(), 0660); err != nil {
return err
}
tmpapk := filepath.Join(tmpDir, "link.apk") tmpapk := filepath.Join(tmpDir, "link.apk")
link := exec.Command( link := exec.Command(
aapt2, aapt2,
@@ -597,6 +649,27 @@ func archNDK() string {
} }
} }
func getPermissions(ps []string) ([]string, []string) {
var permissions, features []string
seenPermissions := make(map[string]bool)
seenFeatures := make(map[string]bool)
for _, perm := range ps {
for _, x := range AndroidPermissions[perm] {
if !seenPermissions[x] {
permissions = append(permissions, x)
seenPermissions[x] = true
}
}
for _, x := range AndroidFeatures[perm] {
if !seenFeatures[x] {
features = append(features, x)
seenFeatures[x] = true
}
}
}
return permissions, features
}
func latestPlatform(sdk string) (string, error) { func latestPlatform(sdk string) (string, error) {
allPlats, err := filepath.Glob(filepath.Join(sdk, "platforms", "android-*")) allPlats, err := filepath.Glob(filepath.Join(sdk, "platforms", "android-*"))
if err != nil { if err != nil {
+23
View File
@@ -0,0 +1,23 @@
package main
var AndroidPermissions = map[string][]string{
"network": {
"android.permission.INTERNET",
},
"bluetooth": {
"android.permission.BLUETOOTH",
"android.permission.BLUETOOTH_ADMIN",
"android.permission.ACCESS_FINE_LOCATION",
},
"bluetooth_le": {
"android.permission.BLUETOOTH",
"android.permission.BLUETOOTH_ADMIN",
"android.permission.ACCESS_FINE_LOCATION",
},
}
var AndroidFeatures = map[string][]string{
"default": {`glEsVersion="0x00030000"`},
"bluetooth": {`name="android.hardware.bluetooth"`},
"bluetooth_le": {`name="android.hardware.bluetooth_le"`},
}