mirror of
https://git.sr.ht/~eliasnaur/gio
synced 2026-07-07 18:35:34 +00:00
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:
committed by
Elias Naur
parent
f418684c0e
commit
af353822fa
+35
-1
@@ -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
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
package bluetooth
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
package bluetooth_le
|
||||||
+93
-20
@@ -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 {
|
||||||
|
|||||||
@@ -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"`},
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user