• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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