1 /* 2 * Copyright (C) 2009 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.ant; 18 19 import com.android.apkbuilder.ApkBuilder.ApkCreationException; 20 import com.android.apkbuilder.internal.ApkBuilderImpl; 21 import com.android.apkbuilder.internal.ApkBuilderImpl.ApkFile; 22 import com.android.sdklib.internal.project.ApkConfigurationHelper; 23 import com.android.sdklib.internal.project.ApkSettings; 24 import com.android.sdklib.internal.project.ProjectProperties; 25 import com.android.sdklib.internal.project.ProjectProperties.PropertyType; 26 27 import org.apache.tools.ant.BuildException; 28 import org.apache.tools.ant.Project; 29 import org.apache.tools.ant.ProjectComponent; 30 import org.apache.tools.ant.Task; 31 import org.apache.tools.ant.types.Path; 32 import org.apache.tools.ant.types.Path.PathElement; 33 34 import java.io.File; 35 import java.io.FileInputStream; 36 import java.io.FileNotFoundException; 37 import java.util.ArrayList; 38 import java.util.Map; 39 import java.util.Map.Entry; 40 41 public class ApkBuilderTask extends Task { 42 43 // ref id to the <path> object containing all the boot classpaths. 44 private final static String REF_APK_PATH = "android.apks.path"; 45 46 /** 47 * Class to represent nested elements. Since they all have only one attribute ('path'), the 48 * same class can be used for all the nested elements (zip, file, sourcefolder, jarfolder, 49 * nativefolder). 50 */ 51 public final static class Value extends ProjectComponent { 52 String mPath; 53 54 /** 55 * Sets the value of the "path" attribute. 56 * @param path the value. 57 */ setPath(Path path)58 public void setPath(Path path) { 59 mPath = path.toString(); 60 } 61 } 62 63 private String mOutFolder; 64 private String mBaseName; 65 private boolean mVerbose = false; 66 private boolean mSigned = true; 67 68 private final ArrayList<Value> mZipList = new ArrayList<Value>(); 69 private final ArrayList<Value> mFileList = new ArrayList<Value>(); 70 private final ArrayList<Value> mSourceList = new ArrayList<Value>(); 71 private final ArrayList<Value> mJarfolderList = new ArrayList<Value>(); 72 private final ArrayList<Value> mJarfileList = new ArrayList<Value>(); 73 private final ArrayList<Value> mNativeList = new ArrayList<Value>(); 74 75 private final ArrayList<FileInputStream> mZipArchives = new ArrayList<FileInputStream>(); 76 private final ArrayList<File> mArchiveFiles = new ArrayList<File>(); 77 private final ArrayList<ApkFile> mJavaResources = new ArrayList<ApkFile>(); 78 private final ArrayList<FileInputStream> mResourcesJars = new ArrayList<FileInputStream>(); 79 private final ArrayList<ApkFile> mNativeLibraries = new ArrayList<ApkFile>(); 80 81 /** 82 * Sets the value of the "outfolder" attribute. 83 * @param outFolder the value. 84 */ setOutfolder(Path outFolder)85 public void setOutfolder(Path outFolder) { 86 mOutFolder = outFolder.toString(); 87 } 88 89 /** 90 * Sets the value of the "basename" attribute. 91 * @param baseName the value. 92 */ setBasename(String baseName)93 public void setBasename(String baseName) { 94 mBaseName = baseName; 95 } 96 97 /** 98 * Sets the value of the "verbose" attribute. 99 * @param verbose the value. 100 */ setVerbose(boolean verbose)101 public void setVerbose(boolean verbose) { 102 mVerbose = verbose; 103 } 104 105 /** 106 * Sets the value of the "signed" attribute. 107 * @param signed the value. 108 */ setSigned(boolean signed)109 public void setSigned(boolean signed) { 110 mSigned = signed; 111 } 112 113 /** 114 * Returns an object representing a nested <var>zip</var> element. 115 */ createZip()116 public Object createZip() { 117 Value zip = new Value(); 118 mZipList.add(zip); 119 return zip; 120 } 121 122 /** 123 * Returns an object representing a nested <var>file</var> element. 124 */ createFile()125 public Object createFile() { 126 Value file = new Value(); 127 mFileList.add(file); 128 return file; 129 } 130 131 /** 132 * Returns an object representing a nested <var>sourcefolder</var> element. 133 */ createSourcefolder()134 public Object createSourcefolder() { 135 Value file = new Value(); 136 mSourceList.add(file); 137 return file; 138 } 139 140 /** 141 * Returns an object representing a nested <var>jarfolder</var> element. 142 */ createJarfolder()143 public Object createJarfolder() { 144 Value file = new Value(); 145 mJarfolderList.add(file); 146 return file; 147 } 148 149 /** 150 * Returns an object representing a nested <var>jarfile</var> element. 151 */ createJarfile()152 public Object createJarfile() { 153 Value file = new Value(); 154 mJarfileList.add(file); 155 return file; 156 } 157 158 /** 159 * Returns an object representing a nested <var>nativefolder</var> element. 160 */ createNativefolder()161 public Object createNativefolder() { 162 Value file = new Value(); 163 mNativeList.add(file); 164 return file; 165 } 166 167 @Override execute()168 public void execute() throws BuildException { 169 Project antProject = getProject(); 170 171 ApkBuilderImpl apkBuilder = new ApkBuilderImpl(); 172 apkBuilder.setVerbose(mVerbose); 173 apkBuilder.setSignedPackage(mSigned); 174 175 try { 176 // setup the list of everything that needs to go in the archive. 177 178 // go through the list of zip files to add. This will not include 179 // the resource package, which is handled separaly for each apk to create. 180 for (Value v : mZipList) { 181 FileInputStream input = new FileInputStream(v.mPath); 182 mZipArchives.add(input); 183 } 184 185 // now go through the list of file to directly add the to the list. 186 for (Value v : mFileList) { 187 mArchiveFiles.add(ApkBuilderImpl.getInputFile(v.mPath)); 188 } 189 190 // now go through the list of file to directly add the to the list. 191 for (Value v : mSourceList) { 192 ApkBuilderImpl.processSourceFolderForResource(v.mPath, mJavaResources); 193 } 194 195 // now go through the list of jar folders. 196 for (Value v : mJarfolderList) { 197 ApkBuilderImpl.processJarFolder(v.mPath, mResourcesJars); 198 } 199 200 // now go through the list of jar files. 201 for (Value v : mJarfileList) { 202 ApkBuilderImpl.processJarFile(v.mPath, mResourcesJars); 203 } 204 205 // now the native lib folder. 206 for (Value v : mNativeList) { 207 String parameter = v.mPath; 208 File f = new File(parameter); 209 210 // compute the offset to get the relative path 211 int offset = parameter.length(); 212 if (parameter.endsWith(File.separator) == false) { 213 offset++; 214 } 215 216 ApkBuilderImpl.processNativeFolder(offset, f, mNativeLibraries); 217 } 218 219 // create the Path item that will contain all the generated APKs 220 // for reuse by other targets (signing/zipaligning) 221 Path path = new Path(antProject); 222 223 // The createApk method uses mBaseName for the base name of the packages (resources 224 // and apk files). 225 // The generated apk file name is 226 // debug: {base}[-{config}]-debug-unaligned.apk 227 // release: {base}[-{config}]-unsigned.apk 228 // Unfortunately for 1.5 projects and before the 'install' ant target expects the name 229 // of the default debug package to be {base}-debug.apk 230 // In order to support those package, we look for the 'out.debug.unaligned.package' 231 // property. If this exist, then we generate {base}[-{config}]-debug-unaligned.apk 232 // otherwise we generate {base}[-{config}]-debug.apk 233 // FIXME: Make apkbuilder export the package name used instead of 234 // having to keep apkbuilder and the rules file in sync 235 String debugPackageSuffix = "-debug-unaligned.apk"; 236 if (antProject.getProperty("out.debug.unaligned.package") == null 237 && antProject.getProperty("out-debug-unaligned-package") == null) { 238 debugPackageSuffix = "-debug.apk"; 239 } 240 241 // first do a full resource package 242 createApk(apkBuilder, null /*configName*/, null /*resourceFilter*/, path, 243 debugPackageSuffix); 244 245 // now see if we need to create file with filtered resources. 246 // Get the project base directory. 247 File baseDir = antProject.getBaseDir(); 248 ProjectProperties properties = ProjectProperties.load(baseDir.getAbsolutePath(), 249 PropertyType.DEFAULT); 250 251 ApkSettings apkSettings = ApkConfigurationHelper.getSettings(properties); 252 if (apkSettings != null) { 253 Map<String, String> apkFilters = apkSettings.getResourceFilters(); 254 if (apkFilters.size() > 0) { 255 for (Entry<String, String> entry : apkFilters.entrySet()) { 256 createApk(apkBuilder, entry.getKey(), entry.getValue(), path, 257 debugPackageSuffix); 258 } 259 } 260 } 261 262 // finally sets the path in the project with a reference 263 antProject.addReference(REF_APK_PATH, path); 264 265 } catch (FileNotFoundException e) { 266 throw new BuildException(e); 267 } catch (IllegalArgumentException e) { 268 throw new BuildException(e); 269 } catch (ApkCreationException e) { 270 throw new BuildException(e); 271 } 272 } 273 274 /** 275 * Creates an application package. 276 * @param apkBuilder 277 * @param configName the name of the filter config. Can be null in which case a full resource 278 * package will be generated. 279 * @param resourceFilter the resource configuration filter to pass to aapt (if configName is 280 * non null) 281 * @param path Ant {@link Path} to which add the generated APKs as {@link PathElement} 282 * @param debugPackageSuffix suffix for the debug packages. 283 * @throws FileNotFoundException 284 * @throws ApkCreationException 285 */ createApk(ApkBuilderImpl apkBuilder, String configName, String resourceFilter, Path path, String debugPackageSuffix)286 private void createApk(ApkBuilderImpl apkBuilder, String configName, String resourceFilter, 287 Path path, String debugPackageSuffix) 288 throws FileNotFoundException, ApkCreationException { 289 // All the files to be included in the archive have already been prep'ed up, except 290 // the resource package. 291 // figure out its name. 292 String filename; 293 if (configName != null && resourceFilter != null) { 294 filename = mBaseName + "-" + configName + ".ap_"; 295 } else { 296 filename = mBaseName + ".ap_"; 297 } 298 299 // now we add it to the list of zip archive (it's just a zip file). 300 301 // it's used as a zip archive input 302 FileInputStream resoucePackageZipFile = new FileInputStream(new File(mOutFolder, filename)); 303 mZipArchives.add(resoucePackageZipFile); 304 305 // prepare the filename to generate. Same thing as the resource file. 306 if (configName != null && resourceFilter != null) { 307 filename = mBaseName + "-" + configName; 308 } else { 309 filename = mBaseName; 310 } 311 312 if (mSigned) { 313 filename = filename + debugPackageSuffix; 314 } else { 315 filename = filename + "-unsigned.apk"; 316 } 317 318 if (configName == null || resourceFilter == null) { 319 if (mSigned) { 320 System.out.println(String.format( 321 "Creating %s and signing it with a debug key...", filename)); 322 } else { 323 System.out.println(String.format( 324 "Creating %s for release...", filename)); 325 } 326 } else { 327 if (mSigned) { 328 System.out.println(String.format( 329 "Creating %1$s (with %2$s) and signing it with a debug key...", 330 filename, resourceFilter)); 331 } else { 332 System.out.println(String.format( 333 "Creating %1$s (with %2$s) for release...", 334 filename, resourceFilter)); 335 } 336 } 337 338 // out File 339 File f = new File(mOutFolder, filename); 340 341 // add it to the Path object 342 PathElement element = path.createPathElement(); 343 element.setLocation(f); 344 345 // and generate the apk 346 apkBuilder.createPackage(f.getAbsoluteFile(), mZipArchives, 347 mArchiveFiles, mJavaResources, mResourcesJars, mNativeLibraries); 348 349 // we are done. We need to remove the resource package from the list of zip archives 350 // in case we have another apk to generate. 351 mZipArchives.remove(resoucePackageZipFile); 352 } 353 } 354