1// Copyright 2018 The Bazel Authors. All rights reserved. 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15// Package shellapk creates a minimal shell apk. 16package shellapk 17 18import ( 19 "archive/zip" 20 "flag" 21 "fmt" 22 "io" 23 "io/ioutil" 24 "log" 25 "os" 26 "path/filepath" 27 "strings" 28 "sync" 29 30 "src/common/golang/fileutils" 31 "src/common/golang/flags" 32 "src/common/golang/ziputils" 33 "src/tools/ak/types" 34) 35 36const ( 37 swigdepsFileName = "nativedeps.txt" 38 jarManifestFileName = "META-INF/MANIFEST.MF" 39 40 // see https://docs.oracle.com/javase/tutorial/deployment/jar/defman.html 41 defJarManifestContents = "Manifest-Version: 1.0\nCreated-By: ak" 42) 43 44var ( 45 // Cmd defines the command to run shellapk. 46 Cmd = types.Command{ 47 Init: Init, 48 Run: Run, 49 Desc: desc, 50 Flags: []string{"java_resources", "dummy_native_libs", "jdk", "dex_file", "manifest_package_name", "in_application_class_name", "shell_app", "android_manifest", "arsc_path", "android_resources_zip", "linked_native_library"}, 51 } 52 53 // Variables to hold flag values. 54 javaRes flags.StringList 55 dummyNativeLibs string 56 jdk string 57 dexFile string 58 manifestPkgName string 59 inAppClassName string 60 out string 61 manifest string 62 arsc string 63 resZip string 64 linkedNativeLib string 65 66 initOnce sync.Once 67) 68 69// Init initializes shellapk. 70func Init() { 71 initOnce.Do(func() { 72 flag.Var(&javaRes, "java_resources", "Path to java resource file.") 73 flag.StringVar(&dummyNativeLibs, "dummy_native_libs", "", "Native libraries files.") 74 flag.StringVar(&jdk, "jdk", "", "JDK path.") 75 flag.StringVar(&dexFile, "dex_file", "", "Dex file with stubby classes for apk.") 76 flag.StringVar(&manifestPkgName, "manifest_package_name", "", "Manifest package name.") 77 flag.StringVar(&inAppClassName, "in_application_class_name", "", "Path for the application class name.") 78 flag.StringVar(&out, "shell_app", "", "Output path for the shell apk.") 79 flag.StringVar(&manifest, "android_manifest", "", "Binary AndroidManifest.xml artifact.") 80 flag.StringVar(&arsc, "arsc_path", "", "Path to the arsc table.") 81 flag.StringVar(&resZip, "android_resources_zip", "", "Android resources files.") 82 flag.StringVar(&linkedNativeLib, "linked_native_library", "", "Linked native lib name that needs to be outputed on the swigdeps file.") 83 }) 84} 85 86func desc() string { 87 return "Shellapk creates a minimal shell apk." 88} 89 90// Run is the entry point for shellapk. 91func Run() { 92 if err := doWork( 93 out, 94 javaRes, 95 dummyNativeLibs, 96 jdk, 97 dexFile, 98 manifestPkgName, 99 inAppClassName, 100 manifest, 101 arsc, 102 resZip, 103 linkedNativeLib); err != nil { 104 log.Fatalf("Error creating shellapk: %v", err) 105 } 106} 107 108func doWork(out string, javaRes []string, dummyNativeLibs, jdk, dexFile, manifestPkgName, inAppClassName, manifest, arsc, resZip, linkedNativeLib string) error { 109 // Create temp dir for all apk files 110 apkDir, err := ioutil.TempDir("", "shellapk") 111 if err != nil { 112 return err 113 } 114 defer os.RemoveAll(apkDir) 115 116 // Exctract dexes 117 if err := extractDexes(apkDir, dexFile); err != nil { 118 return err 119 } 120 121 // Extract java resources 122 for _, f := range javaRes { 123 if err := ziputils.Unzip(f, apkDir); err != nil { 124 return err 125 } 126 } 127 128 // Copy manifest 129 if err := fileutils.Copy(manifest, filepath.Join(apkDir, "AndroidManifest.xml")); err != nil { 130 return err 131 } 132 133 // Write output zip 134 w, err := os.Create(out) 135 if err != nil { 136 return err 137 } 138 defer w.Close() 139 zipOut := zip.NewWriter(w) 140 defer zipOut.Close() 141 142 // Write all files in apkDir 143 err = filepath.Walk(apkDir, func(path string, f os.FileInfo, err error) error { 144 if err != nil { 145 return err 146 } 147 148 if !f.IsDir() { 149 // Filter out META-INF/MANIFEST.MF. If this file is not correct 150 // the apk signer will refuse to touch the apk. 151 if path[len(apkDir)+1:] == jarManifestFileName { 152 return nil 153 } 154 155 n := strings.TrimLeft(strings.TrimPrefix(path, apkDir), "/") 156 if err := ziputils.WriteFile(zipOut, path, n); err != nil { 157 return err 158 } 159 } 160 return nil 161 }) 162 if err != nil { 163 return err 164 } 165 166 // Copy all files from android resources zip 167 r, err := zip.OpenReader(resZip) 168 if err != nil { 169 return err 170 } 171 for _, f := range r.File { 172 if strings.HasSuffix(f.Name, "AndroidManifest.xml") { 173 continue 174 } 175 fR, err := f.Open() 176 if err != nil { 177 return err 178 } 179 if err := ziputils.WriteReader(zipOut, fR, f.Name); err != nil { 180 return err 181 } 182 } 183 r.Close() 184 185 // Copy all native files from dummy native libs 186 if dummyNativeLibs != "" { 187 r, err := zip.OpenReader(dummyNativeLibs) 188 if err != nil { 189 return err 190 } 191 for _, f := range r.File { 192 if !strings.HasSuffix(f.Name, ".so") { 193 continue 194 } 195 fR, err := f.Open() 196 if err != nil { 197 return err 198 } 199 ziputils.WriteReader(zipOut, fR, f.Name) 200 } 201 r.Close() 202 } 203 204 if err := ziputils.WriteReader(zipOut, strings.NewReader(defJarManifestContents), jarManifestFileName); err != nil { 205 return err 206 } 207 208 if err := ziputils.WriteFile(zipOut, manifestPkgName, "package_name.txt"); err != nil { 209 return err 210 } 211 if err := ziputils.WriteFile(zipOut, inAppClassName, "app_name.txt"); err != nil { 212 return err 213 } 214 if linkedNativeLib != "" { 215 if err = ziputils.WriteFile(zipOut, linkedNativeLib, swigdepsFileName); err != nil { 216 return err 217 } 218 } 219 if arsc != "" { 220 if err = ziputils.WriteFile(zipOut, arsc, "resources.arsc"); err != nil { 221 return err 222 } 223 } 224 225 return nil 226} 227 228func extractDexes(dir, zipFile string) error { 229 r, err := zip.OpenReader(zipFile) 230 if err != nil { 231 return err 232 } 233 defer r.Close() 234 235 dexOut, err := os.Create(filepath.Join(dir, "classes.dex")) 236 if err != nil { 237 return err 238 } 239 defer dexOut.Close() 240 dexCount := 0 241 for _, f := range r.File { 242 if strings.HasSuffix(f.Name, ".dex") { 243 dexCount++ 244 dR, err := f.Open() 245 if err != nil { 246 return err 247 } 248 io.Copy(dexOut, dR) 249 } 250 } 251 if dexCount != 1 { 252 return fmt.Errorf("expected 1 dex in %s, actually %d", dexFile, dexCount) 253 } 254 return nil 255} 256