cmd/gomobile: icon support for android
Provides support for resources.arsc generation enabling the setting of an application icon. If an asset/icon.png is encountered during build, then the resources.arsc is generated to identify a single xxxhdpi resource and the manifest will be updated to reference resource as app icon. References golang/go#9985 Change-Id: I9ef59fff45dcd612a41c479b2c679d22c094ab36 Reviewed-on: https://go-review.googlesource.com/30019 Reviewed-by: Hyang-Ah Hana Kim <hyangah@gmail.com>
This commit is contained in:
parent
3ef91fec25
commit
eed0461ac2
@ -9,6 +9,7 @@ import (
|
|||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
|
"encoding/xml"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"go/build"
|
"go/build"
|
||||||
@ -19,6 +20,8 @@ import (
|
|||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"golang.org/x/mobile/internal/binres"
|
||||||
)
|
)
|
||||||
|
|
||||||
func goAndroidBuild(pkg *build.Package, androidArchs []string) (map[string]bool, error) {
|
func goAndroidBuild(pkg *build.Package, androidArchs []string) (map[string]bool, error) {
|
||||||
@ -143,15 +146,7 @@ func goAndroidBuild(pkg *build.Package, androidArchs []string) (map[string]bool,
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
w, err := apkwCreate("AndroidManifest.xml")
|
w, err := apkwCreate("classes.dex")
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if _, err := w.Write(manifestData); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
w, err = apkwCreate("classes.dex")
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -184,6 +179,9 @@ func goAndroidBuild(pkg *build.Package, androidArchs []string) (map[string]bool,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add any assets.
|
// Add any assets.
|
||||||
|
var arsc struct {
|
||||||
|
iconPath string
|
||||||
|
}
|
||||||
assetsDir := filepath.Join(pkg.Dir, "assets")
|
assetsDir := filepath.Join(pkg.Dir, "assets")
|
||||||
assetsDirExists := true
|
assetsDirExists := true
|
||||||
fi, err := os.Stat(assetsDir)
|
fi, err := os.Stat(assetsDir)
|
||||||
@ -213,6 +211,15 @@ func goAndroidBuild(pkg *build.Package, androidArchs []string) (map[string]bool,
|
|||||||
if info.IsDir() {
|
if info.IsDir() {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if rel, err := filepath.Rel(assetsDir, path); rel == "icon.png" && err == nil {
|
||||||
|
arsc.iconPath = path
|
||||||
|
// TODO returning here does not write the assets/icon.png to the final assets output,
|
||||||
|
// making it unavailable via the assets API. Should the file be duplicated into assets
|
||||||
|
// or should assets API be able to retrieve files from the generated resource table?
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
name := "assets/" + path[len(assetsDir)+1:]
|
name := "assets/" + path[len(assetsDir)+1:]
|
||||||
return apkwWriteFile(name, path)
|
return apkwWriteFile(name, path)
|
||||||
})
|
})
|
||||||
@ -221,6 +228,46 @@ func goAndroidBuild(pkg *build.Package, androidArchs []string) (map[string]bool,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bxml, err := binres.UnmarshalXML(bytes.NewReader(manifestData), arsc.iconPath != "")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// generate resources.arsc identifying single xxxhdpi icon resource.
|
||||||
|
if arsc.iconPath != "" {
|
||||||
|
pkgname, err := bxml.RawValueByName("manifest", xml.Name{Local: "package"})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
tbl, name := binres.NewMipmapTable(pkgname)
|
||||||
|
if err := apkwWriteFile(name, arsc.iconPath); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
w, err := apkwCreate("resources.arsc")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
bin, err := tbl.MarshalBinary()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if _, err := w.Write(bin); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
w, err = apkwCreate("AndroidManifest.xml")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
bin, err := bxml.MarshalBinary()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if _, err := w.Write(bin); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: add gdbserver to apk?
|
// TODO: add gdbserver to apk?
|
||||||
|
|
||||||
if !buildN {
|
if !buildN {
|
||||||
|
@ -74,8 +74,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"hash"
|
"hash"
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
"golang.org/x/mobile/internal/binres"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewWriter returns a new Writer writing an APK file to w.
|
// NewWriter returns a new Writer writing an APK file to w.
|
||||||
@ -103,14 +101,6 @@ func (w *Writer) Create(name string) (io.Writer, error) {
|
|||||||
if err := w.clearCur(); err != nil {
|
if err := w.clearCur(); err != nil {
|
||||||
return nil, fmt.Errorf("apk: Create(%s): %v", name, err)
|
return nil, fmt.Errorf("apk: Create(%s): %v", name, err)
|
||||||
}
|
}
|
||||||
if name == "AndroidManifest.xml" {
|
|
||||||
w.cur = &fileWriter{
|
|
||||||
name: name,
|
|
||||||
w: new(bytes.Buffer),
|
|
||||||
sha1: sha1.New(),
|
|
||||||
}
|
|
||||||
return w.cur, nil
|
|
||||||
}
|
|
||||||
res, err := w.create(name)
|
res, err := w.create(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("apk: Create(%s): %v", name, err)
|
return nil, fmt.Errorf("apk: Create(%s): %v", name, err)
|
||||||
@ -234,24 +224,6 @@ func (w *Writer) clearCur() error {
|
|||||||
if w.cur == nil {
|
if w.cur == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if w.cur.name == "AndroidManifest.xml" {
|
|
||||||
buf := w.cur.w.(*bytes.Buffer)
|
|
||||||
bxml, err := binres.UnmarshalXML(buf)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
b, err := bxml.MarshalBinary()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
f, err := w.create("AndroidManifest.xml")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if _, err := f.Write(b); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
w.manifest = append(w.manifest, manifestEntry{
|
w.manifest = append(w.manifest, manifestEntry{
|
||||||
name: w.cur.name,
|
name: w.cur.name,
|
||||||
sha1: w.cur.sha1,
|
sha1: w.cur.sha1,
|
||||||
|
@ -160,6 +160,38 @@ type XML struct {
|
|||||||
stack []*Element
|
stack []*Element
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RawValueByName returns the original raw string value of first matching element attribute, or error if not exists.
|
||||||
|
// Given <manifest package="VAL" ...> then RawValueByName("manifest", xml.Name{Local: "package"}) returns "VAL".
|
||||||
|
func (bx *XML) RawValueByName(elname string, attrname xml.Name) (string, error) {
|
||||||
|
elref, err := bx.Pool.RefByName(elname)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
nref, err := bx.Pool.RefByName(attrname.Local)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
nsref := PoolRef(NoEntry)
|
||||||
|
if attrname.Space != "" {
|
||||||
|
nsref, err = bx.Pool.RefByName(attrname.Space)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for el := range bx.iterElements() {
|
||||||
|
if el.Name == elref {
|
||||||
|
for _, attr := range el.attrs {
|
||||||
|
// TODO enforce TypedValue DataString constraint?
|
||||||
|
if nsref == attr.NS && nref == attr.Name {
|
||||||
|
return bx.Pool.strings[int(attr.RawValue)], nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", fmt.Errorf("no matching element %q for attribute %+v found", elname, attrname)
|
||||||
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
androidSchema = "http://schemas.android.com/apk/res/android"
|
androidSchema = "http://schemas.android.com/apk/res/android"
|
||||||
toolsSchema = "http://schemas.android.com/tools"
|
toolsSchema = "http://schemas.android.com/tools"
|
||||||
@ -170,7 +202,7 @@ var skipSynthesize bool
|
|||||||
|
|
||||||
// UnmarshalXML decodes an AndroidManifest.xml document returning type XML
|
// UnmarshalXML decodes an AndroidManifest.xml document returning type XML
|
||||||
// containing decoded resources.
|
// containing decoded resources.
|
||||||
func UnmarshalXML(r io.Reader) (*XML, error) {
|
func UnmarshalXML(r io.Reader, withIcon bool) (*XML, error) {
|
||||||
tbl, err := OpenTable()
|
tbl, err := OpenTable()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -247,6 +279,25 @@ func UnmarshalXML(r io.Reader) (*XML, error) {
|
|||||||
|
|
||||||
q = append(q, ltoken{s, line}, ltoken{e, line})
|
q = append(q, ltoken{s, line}, ltoken{e, line})
|
||||||
}
|
}
|
||||||
|
case "application":
|
||||||
|
if !skipSynthesize {
|
||||||
|
for _, attr := range tkn.Attr {
|
||||||
|
if attr.Name.Space == androidSchema && attr.Name.Local == "icon" {
|
||||||
|
return nil, fmt.Errorf("manual declaration of android:icon in AndroidManifest.xml not supported")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if withIcon {
|
||||||
|
tkn.Attr = append(tkn.Attr,
|
||||||
|
xml.Attr{
|
||||||
|
Name: xml.Name{
|
||||||
|
Space: androidSchema,
|
||||||
|
Local: "icon",
|
||||||
|
},
|
||||||
|
Value: "@mipmap/icon",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
q = append(q, ltoken{tkn, line})
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
q = append(q, ltoken{tkn, line})
|
q = append(q, ltoken{tkn, line})
|
||||||
@ -371,6 +422,14 @@ func UnmarshalXML(r io.Reader) (*XML, error) {
|
|||||||
nattr.TypedValue.Type = DataReference
|
nattr.TypedValue.Type = DataReference
|
||||||
dref, err := tbl.RefByName(attr.Value)
|
dref, err := tbl.RefByName(attr.Value)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if strings.HasPrefix(attr.Value, "@mipmap") {
|
||||||
|
// firstDrawableId is a TableRef matching first entry of mipmap spec initialized by NewMipmapTable.
|
||||||
|
// 7f is default package, 02 is mipmap spec, 0000 is first entry; e.g. R.drawable.icon
|
||||||
|
// TODO resource table should generate ids as required.
|
||||||
|
const firstDrawableId = 0x7f020000
|
||||||
|
nattr.TypedValue.Value = firstDrawableId
|
||||||
|
continue
|
||||||
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
nattr.TypedValue.Value = uint32(dref)
|
nattr.TypedValue.Value = uint32(dref)
|
||||||
|
@ -7,6 +7,7 @@ package binres
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding"
|
"encoding"
|
||||||
|
"encoding/xml"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
@ -209,7 +210,7 @@ func TestEncode(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
bx, err := UnmarshalXML(f)
|
bx, err := UnmarshalXML(f, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -236,7 +237,7 @@ func TestEncode(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := compareElements(bx, bxml); err != nil {
|
if err := compareElements(bx, bxml); err != nil {
|
||||||
t.Fatal(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Current output byte-for-byte of pkg binres is close, but not exact, to output of aapt.
|
// Current output byte-for-byte of pkg binres is close, but not exact, to output of aapt.
|
||||||
@ -253,6 +254,23 @@ func TestEncode(t *testing.T) {
|
|||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRawValueByName(t *testing.T) {
|
||||||
|
f, err := os.Open("testdata/bootstrap.xml")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
bx, err := UnmarshalXML(f, false)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
pkgname, err := bx.RawValueByName("manifest", xml.Name{Local: "package"})
|
||||||
|
if want := "com.zentus.balloon"; err != nil || pkgname != want {
|
||||||
|
t.Fatalf("have (%q, %v), want (%q, nil)", pkgname, err, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type byAttrName []*Attribute
|
type byAttrName []*Attribute
|
||||||
|
|
||||||
func (a byAttrName) Len() int { return len(a) }
|
func (a byAttrName) Len() int { return len(a) }
|
||||||
|
@ -65,6 +65,16 @@ func (pl *Pool) ref(s string) PoolRef {
|
|||||||
return PoolRef(len(pl.strings) - 1)
|
return PoolRef(len(pl.strings) - 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RefByName returns the PoolRef of s, or error if not exists.
|
||||||
|
func (pl *Pool) RefByName(s string) (PoolRef, error) {
|
||||||
|
for i, x := range pl.strings {
|
||||||
|
if s == x {
|
||||||
|
return PoolRef(i), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0, fmt.Errorf("PoolRef by name %q does not exist", s)
|
||||||
|
}
|
||||||
|
|
||||||
func (pl *Pool) IsSorted() bool { return pl.flags&SortedFlag == SortedFlag }
|
func (pl *Pool) IsSorted() bool { return pl.flags&SortedFlag == SortedFlag }
|
||||||
func (pl *Pool) IsUTF8() bool { return pl.flags&UTF8Flag == UTF8Flag }
|
func (pl *Pool) IsUTF8() bool { return pl.flags&UTF8Flag == UTF8Flag }
|
||||||
|
|
||||||
|
@ -56,6 +56,40 @@ type Table struct {
|
|||||||
pkgs []*Package
|
pkgs []*Package
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewMipmapTable returns a resource table initialized for a single xxxhdpi mipmap resource
|
||||||
|
// and the path to write resource data to.
|
||||||
|
func NewMipmapTable(pkgname string) (*Table, string) {
|
||||||
|
pkg := &Package{id: 127, name: pkgname, typePool: &Pool{}, keyPool: &Pool{}}
|
||||||
|
|
||||||
|
attr := pkg.typePool.ref("attr")
|
||||||
|
mipmap := pkg.typePool.ref("mipmap")
|
||||||
|
icon := pkg.keyPool.ref("icon")
|
||||||
|
|
||||||
|
nt := &Entry{values: []*Value{{data: &Data{Type: DataString}}}}
|
||||||
|
typ := &Type{id: 2, indices: []uint32{0}, entries: []*Entry{nt}}
|
||||||
|
typ.config.screenType.density = 640
|
||||||
|
typ.config.version.sdk = 4
|
||||||
|
|
||||||
|
pkg.specs = append(pkg.specs,
|
||||||
|
&TypeSpec{
|
||||||
|
id: uint8(attr) + 1, //1,
|
||||||
|
},
|
||||||
|
&TypeSpec{
|
||||||
|
id: uint8(mipmap) + 1, //2,
|
||||||
|
entryCount: 1,
|
||||||
|
entries: []uint32{uint32(icon)}, // {0}
|
||||||
|
types: []*Type{typ},
|
||||||
|
})
|
||||||
|
|
||||||
|
pkg.lastPublicType = uint32(len(pkg.typePool.strings)) // 2
|
||||||
|
pkg.lastPublicKey = uint32(len(pkg.keyPool.strings)) // 1
|
||||||
|
|
||||||
|
name := "res/mipmap-xxxhdpi-v4/icon.png"
|
||||||
|
tbl := &Table{pool: &Pool{}, pkgs: []*Package{pkg}}
|
||||||
|
tbl.pool.ref(name)
|
||||||
|
return tbl, name
|
||||||
|
}
|
||||||
|
|
||||||
// OpenSDKTable decodes resources.arsc from sdk platform jar.
|
// OpenSDKTable decodes resources.arsc from sdk platform jar.
|
||||||
func OpenSDKTable() (*Table, error) {
|
func OpenSDKTable() (*Table, error) {
|
||||||
bin, err := apiResources()
|
bin, err := apiResources()
|
||||||
@ -89,7 +123,8 @@ func OpenTable() (*Table, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SpecByName parses the spec name from an entry string if necessary and returns
|
// SpecByName parses the spec name from an entry string if necessary and returns
|
||||||
// the Package and TypeSpec associated with that name.
|
// the Package and TypeSpec associated with that name along with their respective
|
||||||
|
// indices.
|
||||||
//
|
//
|
||||||
// For example:
|
// For example:
|
||||||
// tbl.SpecByName("@android:style/Theme.NoTitleBar")
|
// tbl.SpecByName("@android:style/Theme.NoTitleBar")
|
||||||
@ -261,7 +296,7 @@ func (pkg *Package) UnmarshalBinary(bin []byte) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
buf := bin[idOffset:]
|
buf := bin[idOffset:]
|
||||||
for len(pkg.specs) < len(pkg.typePool.strings) {
|
for len(buf) > 0 {
|
||||||
t := ResType(btou16(buf))
|
t := ResType(btou16(buf))
|
||||||
switch t {
|
switch t {
|
||||||
case ResTableTypeSpec:
|
case ResTableTypeSpec:
|
||||||
@ -288,9 +323,11 @@ func (pkg *Package) UnmarshalBinary(bin []byte) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (pkg *Package) MarshalBinary() ([]byte, error) {
|
func (pkg *Package) MarshalBinary() ([]byte, error) {
|
||||||
bin := make([]byte, 284)
|
// Package header size is determined by C++ struct ResTable_package
|
||||||
|
// see frameworks/base/include/ResourceTypes.h
|
||||||
|
bin := make([]byte, 288)
|
||||||
putu16(bin, uint16(ResTablePackage))
|
putu16(bin, uint16(ResTablePackage))
|
||||||
putu16(bin[2:], 284)
|
putu16(bin[2:], 288)
|
||||||
|
|
||||||
putu32(bin[8:], pkg.id)
|
putu32(bin[8:], pkg.id)
|
||||||
p := utf16.Encode([]rune(pkg.name))
|
p := utf16.Encode([]rune(pkg.name))
|
||||||
@ -532,6 +569,31 @@ func (typ *Type) MarshalBinary() ([]byte, error) {
|
|||||||
putu32(bin[12:], uint32(len(typ.entries)))
|
putu32(bin[12:], uint32(len(typ.entries)))
|
||||||
putu32(bin[16:], uint32(56+len(typ.entries)*4))
|
putu32(bin[16:], uint32(56+len(typ.entries)*4))
|
||||||
|
|
||||||
|
// assure typ.config.size is always written as 52; extended configuration beyond supported
|
||||||
|
// API level is not supported by this marshal implementation but will be forward-compatible.
|
||||||
|
putu32(bin[20:], 52)
|
||||||
|
|
||||||
|
putu16(bin[24:], typ.config.imsi.mcc)
|
||||||
|
putu16(bin[26:], typ.config.imsi.mnc)
|
||||||
|
putu16(bin[28:], typ.config.locale.language)
|
||||||
|
putu16(bin[30:], typ.config.locale.country)
|
||||||
|
bin[32] = typ.config.screenType.orientation
|
||||||
|
bin[33] = typ.config.screenType.touchscreen
|
||||||
|
putu16(bin[34:], typ.config.screenType.density)
|
||||||
|
bin[36] = typ.config.input.keyboard
|
||||||
|
bin[37] = typ.config.input.navigation
|
||||||
|
bin[38] = typ.config.input.inputFlags
|
||||||
|
bin[39] = typ.config.input.inputPad0
|
||||||
|
putu16(bin[40:], typ.config.screenSize.width)
|
||||||
|
putu16(bin[42:], typ.config.screenSize.height)
|
||||||
|
putu16(bin[44:], typ.config.version.sdk)
|
||||||
|
putu16(bin[46:], typ.config.version.minor)
|
||||||
|
bin[48] = typ.config.screenConfig.layout
|
||||||
|
bin[49] = typ.config.screenConfig.uiMode
|
||||||
|
putu16(bin[50:], typ.config.screenConfig.smallestWidthDP)
|
||||||
|
putu16(bin[52:], typ.config.screenSizeDP.width)
|
||||||
|
putu16(bin[54:], typ.config.screenSizeDP.height)
|
||||||
|
|
||||||
var ntbin []byte
|
var ntbin []byte
|
||||||
for i, nt := range typ.entries {
|
for i, nt := range typ.entries {
|
||||||
if nt == nil { // NoEntry
|
if nt == nil { // NoEntry
|
||||||
@ -594,11 +656,15 @@ func (nt *Entry) UnmarshalBinary(bin []byte) error {
|
|||||||
|
|
||||||
func (nt *Entry) MarshalBinary() ([]byte, error) {
|
func (nt *Entry) MarshalBinary() ([]byte, error) {
|
||||||
bin := make([]byte, 8)
|
bin := make([]byte, 8)
|
||||||
putu16(bin, nt.size)
|
sz := nt.size
|
||||||
|
if sz == 0 {
|
||||||
|
sz = 8
|
||||||
|
}
|
||||||
|
putu16(bin, sz)
|
||||||
putu16(bin[2:], nt.flags)
|
putu16(bin[2:], nt.flags)
|
||||||
putu32(bin[4:], uint32(nt.key))
|
putu32(bin[4:], uint32(nt.key))
|
||||||
|
|
||||||
if nt.size == 16 {
|
if sz == 16 {
|
||||||
bin = append(bin, make([]byte, 8+len(nt.values)*12)...)
|
bin = append(bin, make([]byte, 8+len(nt.values)*12)...)
|
||||||
putu32(bin[8:], uint32(nt.parent))
|
putu32(bin[8:], uint32(nt.parent))
|
||||||
putu32(bin[12:], uint32(len(nt.values)))
|
putu32(bin[12:], uint32(len(nt.values)))
|
||||||
|
BIN
internal/binres/testdata/bootstrap-res/mipmap-xxxhdpi/icon.png
vendored
Normal file
BIN
internal/binres/testdata/bootstrap-res/mipmap-xxxhdpi/icon.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 486 B |
BIN
internal/binres/testdata/bootstrap.arsc
vendored
Normal file
BIN
internal/binres/testdata/bootstrap.arsc
vendored
Normal file
Binary file not shown.
BIN
internal/binres/testdata/bootstrap.bin
vendored
BIN
internal/binres/testdata/bootstrap.bin
vendored
Binary file not shown.
1
internal/binres/testdata/bootstrap.xml
vendored
1
internal/binres/testdata/bootstrap.xml
vendored
@ -17,6 +17,7 @@ license that can be found in the LICENSE file.
|
|||||||
android:label="Balloon世界"
|
android:label="Balloon世界"
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:hasCode="false"
|
android:hasCode="false"
|
||||||
|
android:icon="@mipmap/icon"
|
||||||
foo="bar"
|
foo="bar"
|
||||||
android:debuggable="true"
|
android:debuggable="true"
|
||||||
baz="bar"
|
baz="bar"
|
||||||
|
11
internal/binres/testdata/gen.sh
vendored
11
internal/binres/testdata/gen.sh
vendored
@ -1,4 +1,4 @@
|
|||||||
#! /usr/bin/sh
|
#! /usr/bin/env bash
|
||||||
|
|
||||||
# version of build-tools tests run against
|
# version of build-tools tests run against
|
||||||
AAPT=${ANDROID_HOME}/build-tools/23.0.1/aapt
|
AAPT=${ANDROID_HOME}/build-tools/23.0.1/aapt
|
||||||
@ -7,9 +7,14 @@ AAPT=${ANDROID_HOME}/build-tools/23.0.1/aapt
|
|||||||
APIJAR=${ANDROID_HOME}/platforms/android-15/android.jar
|
APIJAR=${ANDROID_HOME}/platforms/android-15/android.jar
|
||||||
|
|
||||||
for f in *.xml; do
|
for f in *.xml; do
|
||||||
|
RES=""
|
||||||
|
if [ -d "${f:0:-4}-res" ]; then
|
||||||
|
RES="-S ${f:0:-4}-res"
|
||||||
|
fi
|
||||||
cp "$f" AndroidManifest.xml
|
cp "$f" AndroidManifest.xml
|
||||||
"$AAPT" p -M AndroidManifest.xml -I "$APIJAR" -F tmp.apk
|
"$AAPT" p -M AndroidManifest.xml $RES -I "$APIJAR" -F tmp.apk
|
||||||
unzip -qq -o tmp.apk
|
unzip -qq -o tmp.apk AndroidManifest.xml resources.arsc
|
||||||
mv AndroidManifest.xml "${f:0:-3}bin"
|
mv AndroidManifest.xml "${f:0:-3}bin"
|
||||||
|
mv resources.arsc "${f:0:-3}arsc"
|
||||||
rm tmp.apk
|
rm tmp.apk
|
||||||
done
|
done
|
||||||
|
Loading…
x
Reference in New Issue
Block a user