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