diff --git a/gogio/androidbuild.go b/gogio/androidbuild.go
index 77136a1..cb686cf 100644
--- a/gogio/androidbuild.go
+++ b/gogio/androidbuild.go
@@ -50,6 +50,8 @@ type manifestData struct {
AppName string
Schemes []string
PackageQueries []string
+ ManifestSnip string
+ AppSnip string
}
const (
@@ -123,7 +125,7 @@ func buildAndroid(tmpDir string, bi *buildInfo) error {
return nil
}
dir := filepath.Dir(p.GoFiles[0])
- jars, err := filepath.Glob(filepath.Join(dir, "*.jar"))
+ jars, err := androidExtraJars(dir)
if err != nil {
return err
}
@@ -422,6 +424,9 @@ func exeAndroid(tmpDir string, tools *androidTools, bi *buildInfo, extraJars, pe
if err != nil {
return err
}
+ if err := copyTree(filepath.Join(bi.pkgDir, "android", "res"), resDir); err != nil {
+ return err
+ }
resZip := filepath.Join(tmpDir, "resources.zip")
aapt2 := filepath.Join(tools.buildtools, "aapt2")
_, err = runCmd(exec.Command(
@@ -447,52 +452,15 @@ func exeAndroid(tmpDir string, tools *androidTools, bi *buildInfo, extraJars, pe
AppName: appName,
Schemes: bi.schemes,
PackageQueries: bi.packageQueries,
+ ManifestSnip: readOptionalText(filepath.Join(bi.pkgDir, "android", "manifest_snippets.xml")),
+ AppSnip: readOptionalText(filepath.Join(bi.pkgDir, "android", "application_snippets.xml")),
}
- tmpl, err := template.New("test").Parse(
- `
-
- {{if .PackageQueries}}
-
- {{range .PackageQueries}}
-
- {{end}}
-
- {{end}}
-
-{{range .Permissions}}
-{{end}}{{range .Features}}
-{{end}}
-
-
-
-
-
- {{range .Schemes}}
-
-
-
-
-
-
- {{end}}
-
-
-`)
- var manifestBuffer bytes.Buffer
- if err := tmpl.Execute(&manifestBuffer, manifestSrc); err != nil {
+ manifestBuffer, err := renderAndroidManifest(manifestSrc)
+ if err != nil {
return err
}
manifest := filepath.Join(tmpDir, "AndroidManifest.xml")
- if err := os.WriteFile(manifest, manifestBuffer.Bytes(), 0o660); err != nil {
+ if err := os.WriteFile(manifest, manifestBuffer, 0o660); err != nil {
return err
}
@@ -757,6 +725,120 @@ func defaultAndroidKeystore(tmpDir string, bi *buildInfo) error {
return err
}
+func renderAndroidManifest(data manifestData) ([]byte, error) {
+ tmpl, err := template.New("test").Parse(
+ `
+
+ {{if .PackageQueries}}
+
+ {{range .PackageQueries}}
+
+ {{end}}
+
+ {{end}}
+
+{{range .Permissions}}
+{{end}}{{range .Features}}
+{{end}}{{.ManifestSnip}}
+{{.AppSnip}}
+
+
+
+
+
+ {{range .Schemes}}
+
+
+
+
+
+
+ {{end}}
+
+
+`)
+ if err != nil {
+ return nil, err
+ }
+ var manifestBuffer bytes.Buffer
+ if err := tmpl.Execute(&manifestBuffer, data); err != nil {
+ return nil, err
+ }
+ return manifestBuffer.Bytes(), nil
+}
+
+func readOptionalText(path string) string {
+ data, err := os.ReadFile(path)
+ if err != nil {
+ return ""
+ }
+ if len(data) == 0 {
+ return ""
+ }
+ return "\n" + string(data) + "\n"
+}
+
+func copyTree(src, dst string) error {
+ info, err := os.Stat(src)
+ if err != nil {
+ if os.IsNotExist(err) {
+ return nil
+ }
+ return err
+ }
+ if !info.IsDir() {
+ return fmt.Errorf("extra Android resources path is not a directory: %s", src)
+ }
+ return filepath.Walk(src, func(path string, entry os.FileInfo, walkErr error) error {
+ if walkErr != nil {
+ return walkErr
+ }
+ rel, err := filepath.Rel(src, path)
+ if err != nil {
+ return err
+ }
+ if rel == "." {
+ return nil
+ }
+ target := filepath.Join(dst, rel)
+ if entry.IsDir() {
+ return os.MkdirAll(target, 0o755)
+ }
+ if err := os.MkdirAll(filepath.Dir(target), 0o755); err != nil {
+ return err
+ }
+ data, err := os.ReadFile(path)
+ if err != nil {
+ return err
+ }
+ return os.WriteFile(target, data, 0o660)
+ })
+}
+
+func androidExtraJars(dir string) ([]string, error) {
+ var jars []string
+ for _, pattern := range []string{
+ filepath.Join(dir, "*.jar"),
+ filepath.Join(dir, "android", "*.jar"),
+ } {
+ matches, err := filepath.Glob(pattern)
+ if err != nil {
+ return nil, err
+ }
+ jars = append(jars, matches...)
+ }
+ return jars, nil
+}
+
func findNDK(androidHome string) (string, error) {
ndks, err := filepath.Glob(filepath.Join(androidHome, "ndk", "*"))
if err != nil {
diff --git a/gogio/androidbuild_test.go b/gogio/androidbuild_test.go
new file mode 100644
index 0000000..f72cded
--- /dev/null
+++ b/gogio/androidbuild_test.go
@@ -0,0 +1,103 @@
+// SPDX-License-Identifier: Unlicense OR MIT
+
+package main
+
+import (
+ "os"
+ "path/filepath"
+ "strings"
+ "testing"
+)
+
+func TestAndroidExtraJarsIncludesAndroidSubdirectory(t *testing.T) {
+ t.Parallel()
+
+ dir := t.TempDir()
+ rootJar := filepath.Join(dir, "crew.jar")
+ androidJar := filepath.Join(dir, "android", "vault.jar")
+ writeTestFile(t, rootJar, "root")
+ writeTestFile(t, androidJar, "android")
+
+ got, err := androidExtraJars(dir)
+ if err != nil {
+ t.Fatalf("androidExtraJars() error = %v", err)
+ }
+ want := []string{rootJar, androidJar}
+ for _, jar := range want {
+ if !containsString(got, jar) {
+ t.Fatalf("androidExtraJars() = %v, want %q included", got, jar)
+ }
+ }
+}
+
+func TestRenderAndroidManifestIncludesOptionalSnippets(t *testing.T) {
+ t.Parallel()
+
+ manifest, err := renderAndroidManifest(manifestData{
+ AppID: "org.example.heist",
+ Version: Semver{Major: 1, Minor: 2, Patch: 3, VersionCode: 4},
+ MinSDK: 28,
+ TargetSDK: 35,
+ Permissions: []string{"android.permission.INTERNET"},
+ Features: []string{`name="android.hardware.fingerprint"`},
+ IconSnip: `android:icon="@mipmap/ic_launcher"`,
+ AppName: "Bellagio Crew",
+ ManifestSnip: "\n\t\n",
+ AppSnip: "\n\t\t\n",
+ })
+ if err != nil {
+ t.Fatalf("renderAndroidManifest() error = %v", err)
+ }
+
+ got := string(manifest)
+ for _, want := range []string{
+ `android.permission.POST_NOTIFICATIONS`,
+ ``,
+ `android.permission.INTERNET`,
+ `android.hardware.fingerprint`,
+ } {
+ if !strings.Contains(got, want) {
+ t.Fatalf("renderAndroidManifest() missing %q in %s", want, got)
+ }
+ }
+}
+
+func TestCopyTreeCopiesNestedResources(t *testing.T) {
+ t.Parallel()
+
+ src := filepath.Join(t.TempDir(), "android", "res")
+ dst := filepath.Join(t.TempDir(), "merged")
+ resourcePath := filepath.Join(src, "xml", "heist_service.xml")
+ writeTestFile(t, resourcePath, "")
+
+ if err := copyTree(src, dst); err != nil {
+ t.Fatalf("copyTree() error = %v", err)
+ }
+
+ got, err := os.ReadFile(filepath.Join(dst, "xml", "heist_service.xml"))
+ if err != nil {
+ t.Fatalf("ReadFile() error = %v", err)
+ }
+ if string(got) != "" {
+ t.Fatalf("copyTree() copied %q, want %q", string(got), "")
+ }
+}
+
+func writeTestFile(t *testing.T, path, contents string) {
+ t.Helper()
+ if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
+ t.Fatalf("MkdirAll(%q) error = %v", path, err)
+ }
+ if err := os.WriteFile(path, []byte(contents), 0o644); err != nil {
+ t.Fatalf("WriteFile(%q) error = %v", path, err)
+ }
+}
+
+func containsString(values []string, want string) bool {
+ for _, value := range values {
+ if value == want {
+ return true
+ }
+ }
+ return false
+}