| package binres |
| |
| import ( |
| "archive/zip" |
| "bytes" |
| "compress/gzip" |
| "fmt" |
| "io" |
| "os" |
| "path" |
| ) |
| |
| // MinSDK is the targetted sdk version for support by package binres. |
| const MinSDK = 15 |
| |
| // Requires environment variable ANDROID_HOME to be set. |
| func apiResources() ([]byte, error) { |
| apiResPath, err := apiResourcesPath() |
| if err != nil { |
| return nil, err |
| } |
| zr, err := zip.OpenReader(apiResPath) |
| if err != nil { |
| if os.IsNotExist(err) { |
| return nil, fmt.Errorf(`%v; consider installing with "android update sdk --all --no-ui --filter android-%d"`, err, MinSDK) |
| } |
| return nil, err |
| } |
| defer zr.Close() |
| |
| buf := new(bytes.Buffer) |
| for _, f := range zr.File { |
| if f.Name == "resources.arsc" { |
| rc, err := f.Open() |
| if err != nil { |
| return nil, err |
| } |
| _, err = io.Copy(buf, rc) |
| if err != nil { |
| return nil, err |
| } |
| rc.Close() |
| break |
| } |
| } |
| if buf.Len() == 0 { |
| return nil, fmt.Errorf("failed to read resources.arsc") |
| } |
| return buf.Bytes(), nil |
| } |
| |
| func apiResourcesPath() (string, error) { |
| // TODO(elias.naur): use the logic from gomobile's androidAPIPath and use the any installed version of the |
| // Android SDK instead. Currently, the binres_test.go tests fail on anything newer than android-15. |
| sdkdir := os.Getenv("ANDROID_HOME") |
| if sdkdir == "" { |
| return "", fmt.Errorf("ANDROID_HOME env var not set") |
| } |
| platform := fmt.Sprintf("android-%v", MinSDK) |
| return path.Join(sdkdir, "platforms", platform, "android.jar"), nil |
| } |
| |
| // PackResources produces a stripped down gzip version of the resources.arsc from api jar. |
| func PackResources() ([]byte, error) { |
| tbl, err := OpenSDKTable() |
| if err != nil { |
| return nil, err |
| } |
| |
| tbl.pool.strings = []string{} // should not be needed |
| pkg := tbl.pkgs[0] |
| |
| // drop language string entries |
| for _, typ := range pkg.specs[3].types { |
| if typ.config.locale.language != 0 { |
| for j, nt := range typ.entries { |
| if nt == nil { // NoEntry |
| continue |
| } |
| pkg.keyPool.strings[nt.key] = "" |
| typ.indices[j] = NoEntry |
| typ.entries[j] = nil |
| } |
| } |
| } |
| |
| // drop strings from pool for specs to be dropped |
| for _, spec := range pkg.specs[4:] { |
| for _, typ := range spec.types { |
| for _, nt := range typ.entries { |
| if nt == nil { // NoEntry |
| continue |
| } |
| // don't drop if there's a collision |
| var collision bool |
| for _, xspec := range pkg.specs[:4] { |
| for _, xtyp := range xspec.types { |
| for _, xnt := range xtyp.entries { |
| if xnt == nil { |
| continue |
| } |
| if collision = nt.key == xnt.key; collision { |
| break |
| } |
| } |
| } |
| } |
| if !collision { |
| pkg.keyPool.strings[nt.key] = "" |
| } |
| } |
| } |
| } |
| |
| // entries are densely packed but probably safe to drop nil entries off the end |
| for _, spec := range pkg.specs[:4] { |
| for _, typ := range spec.types { |
| var last int |
| for i, nt := range typ.entries { |
| if nt != nil { |
| last = i |
| } |
| } |
| typ.entries = typ.entries[:last+1] |
| typ.indices = typ.indices[:last+1] |
| } |
| } |
| |
| // keeping 0:attr, 1:id, 2:style, 3:string |
| pkg.typePool.strings = pkg.typePool.strings[:4] |
| pkg.specs = pkg.specs[:4] |
| |
| bin, err := tbl.MarshalBinary() |
| if err != nil { |
| return nil, err |
| } |
| |
| buf := new(bytes.Buffer) |
| |
| zw := gzip.NewWriter(buf) |
| if _, err := zw.Write(bin); err != nil { |
| return nil, err |
| } |
| if err := zw.Flush(); err != nil { |
| return nil, err |
| } |
| if err := zw.Close(); err != nil { |
| return nil, err |
| } |
| return buf.Bytes(), nil |
| } |