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  }