github.com/shranet/mobile@v0.0.0-20200814083559-5702cdcd481b/internal/binres/sdk.go (about) 1 package binres 2 3 import ( 4 "archive/zip" 5 "bytes" 6 "compress/gzip" 7 "fmt" 8 "io" 9 "os" 10 "path" 11 ) 12 13 // MinSDK is the targetted sdk version for support by package binres. 14 const MinSDK = 15 15 16 // Requires environment variable ANDROID_HOME to be set. 17 func apiResources() ([]byte, error) { 18 apiResPath, err := apiResourcesPath() 19 if err != nil { 20 return nil, err 21 } 22 zr, err := zip.OpenReader(apiResPath) 23 if err != nil { 24 if os.IsNotExist(err) { 25 return nil, fmt.Errorf(`%v; consider installing with "android update sdk --all --no-ui --filter android-%d"`, err, MinSDK) 26 } 27 return nil, err 28 } 29 defer zr.Close() 30 31 buf := new(bytes.Buffer) 32 for _, f := range zr.File { 33 if f.Name == "resources.arsc" { 34 rc, err := f.Open() 35 if err != nil { 36 return nil, err 37 } 38 _, err = io.Copy(buf, rc) 39 if err != nil { 40 return nil, err 41 } 42 rc.Close() 43 break 44 } 45 } 46 if buf.Len() == 0 { 47 return nil, fmt.Errorf("failed to read resources.arsc") 48 } 49 return buf.Bytes(), nil 50 } 51 52 func apiResourcesPath() (string, error) { 53 // TODO(elias.naur): use the logic from gomobile's androidAPIPath and use the any installed version of the 54 // Android SDK instead. Currently, the binres_test.go tests fail on anything newer than android-15. 55 sdkdir := os.Getenv("ANDROID_HOME") 56 if sdkdir == "" { 57 return "", fmt.Errorf("ANDROID_HOME env var not set") 58 } 59 platform := fmt.Sprintf("android-%v", MinSDK) 60 return path.Join(sdkdir, "platforms", platform, "android.jar"), nil 61 } 62 63 // PackResources produces a stripped down gzip version of the resources.arsc from api jar. 64 func PackResources() ([]byte, error) { 65 tbl, err := OpenSDKTable() 66 if err != nil { 67 return nil, err 68 } 69 70 tbl.pool.strings = []string{} // should not be needed 71 pkg := tbl.pkgs[0] 72 73 // drop language string entries 74 for _, typ := range pkg.specs[3].types { 75 if typ.config.locale.language != 0 { 76 for j, nt := range typ.entries { 77 if nt == nil { // NoEntry 78 continue 79 } 80 pkg.keyPool.strings[nt.key] = "" 81 typ.indices[j] = NoEntry 82 typ.entries[j] = nil 83 } 84 } 85 } 86 87 // drop strings from pool for specs to be dropped 88 for _, spec := range pkg.specs[4:] { 89 for _, typ := range spec.types { 90 for _, nt := range typ.entries { 91 if nt == nil { // NoEntry 92 continue 93 } 94 // don't drop if there's a collision 95 var collision bool 96 for _, xspec := range pkg.specs[:4] { 97 for _, xtyp := range xspec.types { 98 for _, xnt := range xtyp.entries { 99 if xnt == nil { 100 continue 101 } 102 if collision = nt.key == xnt.key; collision { 103 break 104 } 105 } 106 } 107 } 108 if !collision { 109 pkg.keyPool.strings[nt.key] = "" 110 } 111 } 112 } 113 } 114 115 // entries are densely packed but probably safe to drop nil entries off the end 116 for _, spec := range pkg.specs[:4] { 117 for _, typ := range spec.types { 118 var last int 119 for i, nt := range typ.entries { 120 if nt != nil { 121 last = i 122 } 123 } 124 typ.entries = typ.entries[:last+1] 125 typ.indices = typ.indices[:last+1] 126 } 127 } 128 129 // keeping 0:attr, 1:id, 2:style, 3:string 130 pkg.typePool.strings = pkg.typePool.strings[:4] 131 pkg.specs = pkg.specs[:4] 132 133 bin, err := tbl.MarshalBinary() 134 if err != nil { 135 return nil, err 136 } 137 138 buf := new(bytes.Buffer) 139 140 zw := gzip.NewWriter(buf) 141 if _, err := zw.Write(bin); err != nil { 142 return nil, err 143 } 144 if err := zw.Flush(); err != nil { 145 return nil, err 146 } 147 if err := zw.Close(); err != nil { 148 return nil, err 149 } 150 return buf.Bytes(), nil 151 }