aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--anttasks/src/com/android/ant/ApkBuilderTask.java197
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AndroidConstants.java2
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/actions/MultiApkExportAction.java15
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/ApkBuilder.java9
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/ApkBuilderHelper.java494
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/ApkDeltaVisitor.java4
-rw-r--r--sdkmanager/libs/sdklib/src/com/android/sdklib/SdkConstants.java8
-rw-r--r--sdkmanager/libs/sdklib/src/com/android/sdklib/build/ApkBuilder.java831
-rw-r--r--sdkmanager/libs/sdklib/src/com/android/sdklib/build/ApkBuilderMain.java129
-rw-r--r--sdkmanager/libs/sdklib/src/com/android/sdklib/internal/build/ApkBuilderHelper.java433
-rw-r--r--sdkmanager/libs/sdklib/src/com/android/sdklib/internal/build/JavaResourceFilter.java103
-rw-r--r--sdkmanager/libs/sdklib/src/com/android/sdklib/internal/build/SignedJarBuilder.java79
12 files changed, 1230 insertions, 1074 deletions
diff --git a/anttasks/src/com/android/ant/ApkBuilderTask.java b/anttasks/src/com/android/ant/ApkBuilderTask.java
index bec0417..bc5f53c 100644
--- a/anttasks/src/com/android/ant/ApkBuilderTask.java
+++ b/anttasks/src/com/android/ant/ApkBuilderTask.java
@@ -16,9 +16,10 @@
package com.android.ant;
-import com.android.sdklib.internal.build.ApkBuilderHelper;
-import com.android.sdklib.internal.build.ApkBuilderHelper.ApkCreationException;
-import com.android.sdklib.internal.build.ApkBuilderHelper.ApkFile;
+import com.android.sdklib.build.ApkBuilder;
+import com.android.sdklib.build.ApkBuilder.ApkCreationException;
+import com.android.sdklib.build.ApkBuilder.DuplicateFileException;
+import com.android.sdklib.build.ApkBuilder.SealedApkException;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Project;
@@ -26,12 +27,15 @@ import org.apache.tools.ant.Task;
import org.apache.tools.ant.types.Path;
import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
+import java.io.FilenameFilter;
import java.util.ArrayList;
+import java.util.regex.Pattern;
public class ApkBuilderTask extends Task {
+ private final static Pattern PATTERN_JAR_EXT = Pattern.compile("^.+\\.jar$",
+ Pattern.CASE_INSENSITIVE);
+
private String mOutFolder;
@Deprecated private String mBaseName;
private String mApkFilepath;
@@ -42,20 +46,15 @@ public class ApkBuilderTask extends Task {
private boolean mHasCode = true;
private String mAbiFilter = null;
+ private Path mDexPath;
+
private final ArrayList<Path> mZipList = new ArrayList<Path>();
- private final ArrayList<Path> mDexList = new ArrayList<Path>();
private final ArrayList<Path> mFileList = new ArrayList<Path>();
private final ArrayList<Path> mSourceList = new ArrayList<Path>();
private final ArrayList<Path> mJarfolderList = new ArrayList<Path>();
private final ArrayList<Path> mJarfileList = new ArrayList<Path>();
private final ArrayList<Path> mNativeList = new ArrayList<Path>();
- private final ArrayList<FileInputStream> mZipArchives = new ArrayList<FileInputStream>();
- private final ArrayList<File> mArchiveFiles = new ArrayList<File>();
- private final ArrayList<ApkFile> mJavaResources = new ArrayList<ApkFile>();
- private final ArrayList<FileInputStream> mResourcesJars = new ArrayList<FileInputStream>();
- private final ArrayList<ApkFile> mNativeLibraries = new ArrayList<ApkFile>();
-
/**
* Sets the value of the "outfolder" attribute.
* @param outFolder the value.
@@ -70,7 +69,7 @@ public class ApkBuilderTask extends Task {
* @deprecated
*/
public void setBasename(String baseName) {
- System.out.println("WARNNG: Using deprecated 'basename' attribute in ApkBuilderTask." +
+ System.out.println("WARNING: Using deprecated 'basename' attribute in ApkBuilderTask." +
"Use 'apkfilepath' (path) instead.");
mBaseName = baseName;
}
@@ -154,15 +153,19 @@ public class ApkBuilderTask extends Task {
* is <code>false</code> in which case it's ignored.
*/
public Object createDex() {
- Path path = new Path(getProject());
- mDexList.add(path);
- return path;
+ if (mDexPath == null) {
+ return mDexPath = new Path(getProject());
+ } else {
+ throw new BuildException("Only one <dex> inner element can be provided");
+ }
}
/**
* Returns an object representing a nested <var>file</var> element.
*/
public Object createFile() {
+ System.out.println("WARNING: Using deprecated <file> inner element in ApkBuilderTask." +
+ "Use <dex path=...> instead.");
Path path = new Path(getProject());
mFileList.add(path);
return path;
@@ -208,36 +211,70 @@ public class ApkBuilderTask extends Task {
public void execute() throws BuildException {
Project antProject = getProject();
- ApkBuilderHelper apkBuilder = new ApkBuilderHelper();
- apkBuilder.setVerbose(mVerbose);
- apkBuilder.setSignedPackage(mSigned);
- apkBuilder.setDebugMode(mDebug);
+ // get the rules revision to figure out how to build the output file.
+ String rulesRevStr = antProject.getProperty(TaskHelper.PROP_RULES_REV);
+ int rulesRev = 1;
+ try {
+ rulesRev = Integer.parseInt(rulesRevStr);
+ } catch (NumberFormatException e) {
+ // this shouldn't happen since setup task is the one setting up every time.
+ }
+
+ File outputFile;
+ if (mApkFilepath != null) {
+ outputFile = new File(mApkFilepath);
+ } else if (rulesRev == 2) {
+ if (mSigned) {
+ outputFile = new File(mOutFolder, mBaseName + "-debug-unaligned.apk");
+ } else {
+ outputFile = new File(mOutFolder, mBaseName + "-unsigned.apk");
+ }
+ } else {
+ throw new BuildException("missing attribute 'apkFilepath'");
+ }
+
+ // check dexPath is only one file.
+ File dexFile = null;
+ if (mHasCode) {
+ String[] dexFiles = mDexPath.list();
+ if (dexFiles.length != 1) {
+ throw new BuildException(String.format(
+ "Expected one dex file but path value resolve to %d files.",
+ dexFiles.length));
+ }
+ dexFile = new File(dexFiles[0]);
+ }
try {
- // setup the list of everything that needs to go in the archive.
+ if (mSigned) {
+ System.out.println(String.format(
+ "Creating %s and signing it with a debug key...", outputFile.getName()));
+ } else {
+ System.out.println(String.format(
+ "Creating %s for release...", outputFile.getName()));
+ }
- // go through the list of zip files to add. This will not include
- // the resource package, which is handled separaly for each apk to create.
+ ApkBuilder apkBuilder = new ApkBuilder(
+ outputFile,
+ new File(mOutFolder, mResourceFile),
+ dexFile,
+ mSigned ? ApkBuilder.getDebugKeystore() : null,
+ mVerbose ? System.out : null);
+ apkBuilder.setDebugMode(mDebug);
+
+
+ // add the content of the zip files.
for (Path pathList : mZipList) {
for (String path : pathList.list()) {
- FileInputStream input = new FileInputStream(path);
- mZipArchives.add(input);
+ apkBuilder.addZipFile(new File(path));
}
}
- // now go through the list of file to directly add the to the list.
+ // add the files that go to the root of the archive (this is deprecated)
for (Path pathList : mFileList) {
for (String path : pathList.list()) {
- mArchiveFiles.add(ApkBuilderHelper.getInputFile(path));
- }
- }
-
- // only attempt to add Dex files if hasCode is true.
- if (mHasCode) {
- for (Path pathList : mDexList) {
- for (String path : pathList.list()) {
- mArchiveFiles.add(ApkBuilderHelper.getInputFile(path));
- }
+ File f = new File(path);
+ apkBuilder.addFile(f, f.getName());
}
}
@@ -245,8 +282,7 @@ public class ApkBuilderTask extends Task {
if (mHasCode) {
for (Path pathList : mSourceList) {
for (String path : pathList.list()) {
- ApkBuilderHelper.processSourceFolderForResource(new File(path),
- mJavaResources);
+ apkBuilder.addSourceFolder(new File(path));
}
}
}
@@ -257,7 +293,15 @@ public class ApkBuilderTask extends Task {
// it's ok if top level folders are missing
File folder = new File(path);
if (folder.isDirectory()) {
- ApkBuilderHelper.processJar(folder, mResourcesJars);
+ String[] filenames = folder.list(new FilenameFilter() {
+ public boolean accept(File dir, String name) {
+ return PATTERN_JAR_EXT.matcher(name).matches();
+ }
+ });
+
+ for (String filename : filenames) {
+ apkBuilder.addResourcesFromJar(new File(folder, filename));
+ }
}
}
}
@@ -265,7 +309,7 @@ public class ApkBuilderTask extends Task {
// now go through the list of jar files.
for (Path pathList : mJarfileList) {
for (String path : pathList.list()) {
- ApkBuilderHelper.processJar(new File(path), mResourcesJars);
+ apkBuilder.addResourcesFromJar(new File(path));
}
}
@@ -275,77 +319,26 @@ public class ApkBuilderTask extends Task {
// it's ok if top level folders are missing
File folder = new File(path);
if (folder.isDirectory()) {
- ApkBuilderHelper.processNativeFolder(folder, mDebug,
- mNativeLibraries, mVerbose, mAbiFilter);
+ apkBuilder.addNativeLibraries(folder, mAbiFilter);
}
}
}
- // get the rules revision
- String rulesRevStr = antProject.getProperty(TaskHelper.PROP_RULES_REV);
- int rulesRev = 1;
- try {
- rulesRev = Integer.parseInt(rulesRevStr);
- } catch (NumberFormatException e) {
- // this shouldn't happen since setup task is the one setting up every time.
- }
-
-
- File file;
- if (mApkFilepath != null) {
- file = new File(mApkFilepath);
- } else if (rulesRev == 2) {
- if (mSigned) {
- file = new File(mOutFolder, mBaseName + "-debug-unaligned.apk");
- } else {
- file = new File(mOutFolder, mBaseName + "-unsigned.apk");
- }
- } else {
- throw new BuildException("missing attribute 'apkFilepath'");
- }
- // create the package.
- createApk(apkBuilder, file);
+ // close the archive
+ apkBuilder.sealApk();
- } catch (FileNotFoundException e) {
- throw new BuildException(e);
- } catch (IllegalArgumentException e) {
+ } catch (DuplicateFileException e) {
+ System.err.println(String.format(
+ "Found duplicate file for APK: %1$s\nOrigin 1: %2$s\nOrigin 2: %3$s",
+ e.getArchivePath(), e.getFile1(), e.getFile2()));
throw new BuildException(e);
} catch (ApkCreationException e) {
- e.printStackTrace();
+ throw new BuildException(e);
+ } catch (SealedApkException e) {
+ throw new BuildException(e);
+ } catch (IllegalArgumentException e) {
throw new BuildException(e);
}
}
-
- /**
- * Creates an application package.
- * @param apkBuilder
- * @param outputfile the file to generate
- * @throws FileNotFoundException
- * @throws ApkCreationException
- */
- private void createApk(ApkBuilderHelper apkBuilder, File outputfile)
- throws FileNotFoundException, ApkCreationException {
-
- // add the resource pack file as a zip archive input.
- FileInputStream resoucePackageZipFile = new FileInputStream(
- new File(mOutFolder, mResourceFile));
- mZipArchives.add(resoucePackageZipFile);
-
- if (mSigned) {
- System.out.println(String.format(
- "Creating %s and signing it with a debug key...", outputfile.getName()));
- } else {
- System.out.println(String.format(
- "Creating %s for release...", outputfile.getName()));
- }
-
- // and generate the apk
- apkBuilder.createPackage(outputfile.getAbsoluteFile(), mZipArchives,
- mArchiveFiles, mJavaResources, mResourcesJars, mNativeLibraries);
-
- // we are done. We need to remove the resource package from the list of zip archives
- // in case we have another apk to generate.
- mZipArchives.remove(resoucePackageZipFile);
- }
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AndroidConstants.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AndroidConstants.java
index d5d2185..73f6fcb 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AndroidConstants.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AndroidConstants.java
@@ -101,8 +101,6 @@ public class AndroidConstants {
public final static String FN_COMPILED_RESOURCE_CLASS = FN_RESOURCE_BASE + DOT_CLASS;
/** Manifest java class filename, i.e. "Manifest.java" */
public final static String FN_MANIFEST_CLASS = "Manifest.java"; //$NON-NLS-1$
- /** Dex conversion output filname, i.e. "classes.dex" */
- public final static String FN_CLASSES_DEX = "classes.dex"; //$NON-NLS-1$
/** Temporary packaged resources file name, i.e. "resources.ap_" */
public final static String FN_RESOURCES_AP_ = "resources.ap_"; //$NON-NLS-1$
/** Temporary packaged resources file name for a specific set of configuration */
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/actions/MultiApkExportAction.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/actions/MultiApkExportAction.java
index fe05b1d..aa4b700 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/actions/MultiApkExportAction.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/actions/MultiApkExportAction.java
@@ -17,7 +17,6 @@
package com.android.ide.eclipse.adt.internal.actions;
import com.android.ide.eclipse.adt.AdtPlugin;
-import com.android.ide.eclipse.adt.AndroidConstants;
import com.android.ide.eclipse.adt.internal.build.ApkBuilderHelper;
import com.android.ide.eclipse.adt.internal.project.ProjectHelper;
import com.android.ide.eclipse.adt.internal.project.ProjectState;
@@ -282,17 +281,23 @@ public class MultiApkExportAction implements IObjectActionDelegate {
apk.setOutputName(softVariant != null ? softVariant.getKey() : null, outputName);
// do the final export.
- IFile dexFile = projectBinFolder.getFile(AndroidConstants.FN_CLASSES_DEX);
+ IFile dexFile = projectBinFolder.getFile(SdkConstants.FN_APK_CLASSES_DEX);
String outputFile = binFolder.getFile(outputName).getLocation().toOSString();
// get the list of referenced projects.
IProject[] javaRefs = ProjectHelper.getReferencedProjects(project);
IJavaProject[] referencedJavaProjects = ApkBuilderHelper.getJavaProjects(javaRefs);
- helper.finalPackage(new File(projectBinFolderPath, pkgName).getAbsolutePath(),
+ helper.finalPackage(
+ new File(projectBinFolderPath, pkgName).getAbsolutePath(),
dexFile.getLocation().toOSString(),
- outputFile, javaProject, libProjects, referencedJavaProjects,
- apk.getAbi(), false /*debuggable*/);
+ outputFile,
+ false /*debugSign */,
+ javaProject,
+ libProjects,
+ referencedJavaProjects,
+ apk.getAbi(),
+ false /*debuggable*/);
}
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/ApkBuilder.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/ApkBuilder.java
index 9cd3036..8360feb 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/ApkBuilder.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/ApkBuilder.java
@@ -350,7 +350,7 @@ public class ApkBuilder extends BaseBuilder {
// check classes.dex is present. If not we force to recreate it.
if (mConvertToDex == false) {
- tmp = outputFolder.findMember(AndroidConstants.FN_CLASSES_DEX);
+ tmp = outputFolder.findMember(SdkConstants.FN_APK_CLASSES_DEX);
if (tmp == null || tmp.exists() == false) {
mConvertToDex = true;
mBuildFinalPackage = true;
@@ -445,7 +445,7 @@ public class ApkBuilder extends BaseBuilder {
// then we check if we need to package the .class into classes.dex
if (mConvertToDex) {
if (helper.executeDx(javaProject, osBinPath, osBinPath + File.separator +
- AndroidConstants.FN_CLASSES_DEX, referencedJavaProjects) == false) {
+ SdkConstants.FN_APK_CLASSES_DEX, referencedJavaProjects) == false) {
// dx failed, we return
return allRefProjects;
}
@@ -477,10 +477,11 @@ public class ApkBuilder extends BaseBuilder {
// This is the default package with all the resources.
String classesDexPath = osBinPath + File.separator +
- AndroidConstants.FN_CLASSES_DEX;
+ SdkConstants.FN_APK_CLASSES_DEX;
if (helper.finalPackage(
osBinPath + File.separator + AndroidConstants.FN_RESOURCES_AP_,
- classesDexPath, osFinalPackagePath, javaProject, libProjects,
+ classesDexPath, osFinalPackagePath, true /*debugSign*/,
+ javaProject, libProjects,
referencedJavaProjects, null /*abiFilter*/, debuggable) == false) {
return allRefProjects;
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/ApkBuilderHelper.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/ApkBuilderHelper.java
index 04acf44..3c2d86a 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/ApkBuilderHelper.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/ApkBuilderHelper.java
@@ -27,12 +27,14 @@ import com.android.ide.eclipse.adt.internal.sdk.Sdk;
import com.android.prefs.AndroidLocation.AndroidLocationException;
import com.android.sdklib.IAndroidTarget;
import com.android.sdklib.SdkConstants;
+import com.android.sdklib.build.ApkBuilder;
+import com.android.sdklib.build.ApkBuilder.ApkCreationException;
+import com.android.sdklib.build.ApkBuilder.DuplicateFileException;
+import com.android.sdklib.build.ApkBuilder.JarStatus;
+import com.android.sdklib.build.ApkBuilder.SealedApkException;
import com.android.sdklib.internal.build.DebugKeyProvider;
-import com.android.sdklib.internal.build.JavaResourceFilter;
import com.android.sdklib.internal.build.SignedJarBuilder;
-import com.android.sdklib.internal.build.DebugKeyProvider.IKeyGenOutput;
import com.android.sdklib.internal.build.DebugKeyProvider.KeytoolException;
-import com.android.sdklib.internal.build.SignedJarBuilder.IZipEntryFilter;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
@@ -45,7 +47,6 @@ import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IStatus;
-import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Status;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.IJavaProject;
@@ -54,73 +55,30 @@ import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jface.preference.IPreferenceStore;
import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;
-import java.security.GeneralSecurityException;
-import java.security.PrivateKey;
-import java.security.cert.X509Certificate;
-import java.text.DateFormat;
import java.util.ArrayList;
-import java.util.Date;
import java.util.List;
+/**
+ * Helper with methods for the last 3 steps of the generation of an APK.
+ *
+ * {@link #packageResources(IFile, IProject[], String, int, String, String)} packages the
+ * application resources using aapt into a zip file that is ready to be integrated into the apk.
+ *
+ * {@link #executeDx(IJavaProject, String, String, IJavaProject[])} will convert the Java byte
+ * code into the Dalvik bytecode.
+ *
+ * {@link #finalPackage(String, String, String, boolean, IJavaProject, IProject[], IJavaProject[], String, boolean)}
+ * will make the apk from all the previous components.
+ *
+ */
public class ApkBuilderHelper {
- final static String GDBSERVER_NAME = "gdbserver"; //$NON-NLS-1$
-
private final IProject mProject;
private final PrintStream mOutStream;
private final PrintStream mErrStream;
- /**
- * Custom {@link IZipEntryFilter} to filter out everything that is not a standard java
- * resources, and also record whether the zip file contains native libraries.
- * <p/>Used in {@link SignedJarBuilder#writeZip(java.io.InputStream, IZipEntryFilter)} when
- * we only want the java resources from external jars.
- */
- private final static class JavaAndNativeResourceFilter extends JavaResourceFilter {
- private final List<String> mNativeLibs = new ArrayList<String>();
- private boolean mNativeLibInteference = false;
-
- @Override
- public boolean checkEntry(String name) {
- boolean value = super.checkEntry(name);
-
- // only do additional checks if the file passes the default checks.
- if (value) {
- if (name.endsWith(".so")) {
- mNativeLibs.add(name);
-
- // only .so located in lib/ will interfer with the installation
- if (name.startsWith("lib/")) {
- mNativeLibInteference = true;
- }
- } else if (name.endsWith(".jnilib")) {
- mNativeLibs.add(name);
- }
- }
-
- return value;
- }
-
- List<String> getNativeLibs() {
- return mNativeLibs;
- }
-
- boolean getNativeLibInterefence() {
- return mNativeLibInteference;
- }
-
- void clear() {
- mNativeLibs.clear();
- mNativeLibInteference = false;
- }
- }
-
- private final JavaAndNativeResourceFilter mResourceFilter = new JavaAndNativeResourceFilter();
-
public ApkBuilderHelper(IProject project, PrintStream outStream, PrintStream errStream) {
mProject = project;
mOutStream = outStream;
@@ -190,7 +148,6 @@ public class ApkBuilderHelper {
}
return true;
-
}
/**
@@ -199,6 +156,7 @@ public class ApkBuilderHelper {
* @param intermediateApk The path to the temporary resource file.
* @param dex The path to the dex file.
* @param output The path to the final package file to create.
+ * @param debugSign whether the apk must be signed with the debug key.
* @param javaProject the java project being compiled
* @param libProjects an optional list of library projects (can be null)
* @param referencedJavaProjects referenced projects.
@@ -209,128 +167,96 @@ public class ApkBuilderHelper {
* @return true if success, false otherwise.
*/
public boolean finalPackage(String intermediateApk, String dex, String output,
- final IJavaProject javaProject, IProject[] libProjects,
+ boolean debugSign, final IJavaProject javaProject, IProject[] libProjects,
IJavaProject[] referencedJavaProjects, String abiFilter, boolean debuggable) {
- FileOutputStream fos = null;
- try {
+ IProject project = javaProject.getProject();
+
+ String keystoreOsPath = null;
+ if (debugSign) {
IPreferenceStore store = AdtPlugin.getDefault().getPreferenceStore();
- String osKeyPath = store.getString(AdtPrefs.PREFS_CUSTOM_DEBUG_KEYSTORE);
- if (osKeyPath == null || new File(osKeyPath).exists() == false) {
- osKeyPath = DebugKeyProvider.getDefaultKeyStoreOsPath();
- AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, mProject,
- Messages.ApkBuilder_Using_Default_Key);
- } else {
- AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, mProject,
- String.format(Messages.ApkBuilder_Using_s_To_Sign, osKeyPath));
- }
+ keystoreOsPath = store.getString(AdtPrefs.PREFS_CUSTOM_DEBUG_KEYSTORE);
+ if (keystoreOsPath == null || new File(keystoreOsPath).isFile() == false) {
+ try {
+ keystoreOsPath = DebugKeyProvider.getDefaultKeyStoreOsPath();
+ AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, mProject,
+ Messages.ApkBuilder_Using_Default_Key);
+ } catch (KeytoolException e) {
+ String eMessage = e.getMessage();
+
+ // mark the project with the standard message
+ String msg = String.format(Messages.Final_Archive_Error_s, eMessage);
+ BaseProjectHelper.markResource(mProject, AndroidConstants.MARKER_PACKAGING, msg,
+ IMarker.SEVERITY_ERROR);
- // TODO: get the store type from somewhere else.
- DebugKeyProvider provider = new DebugKeyProvider(osKeyPath, null /* storeType */,
- new IKeyGenOutput() {
- public void err(String message) {
- AdtPlugin.printErrorToConsole(mProject,
- Messages.ApkBuilder_Signing_Key_Creation_s + message);
- }
+ // output more info in the console
+ AdtPlugin.printErrorToConsole(mProject,
+ msg,
+ String.format(Messages.ApkBuilder_JAVA_HOME_is_s, e.getJavaHome()),
+ Messages.ApkBuilder_Update_or_Execute_manually_s,
+ e.getCommandLine());
- public void out(String message) {
- AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE,
- mProject,
- Messages.ApkBuilder_Signing_Key_Creation_s + message);
- }
- });
- PrivateKey key = provider.getDebugKey();
- X509Certificate certificate = (X509Certificate)provider.getCertificate();
-
- if (key == null) {
- String msg = String.format(Messages.Final_Archive_Error_s,
- Messages.ApkBuilder_Unable_To_Gey_Key);
- AdtPlugin.printErrorToConsole(mProject, msg);
- BaseProjectHelper.markResource(mProject, AndroidConstants.MARKER_PACKAGING, msg,
- IMarker.SEVERITY_ERROR);
- return false;
- }
+ return false;
+ } catch (AndroidLocationException e) {
+ String eMessage = e.getMessage();
- // compare the certificate expiration date
- if (certificate != null && certificate.getNotAfter().compareTo(new Date()) < 0) {
- // TODO, regenerate a new one.
- String msg = String.format(Messages.Final_Archive_Error_s,
- String.format(Messages.ApkBuilder_Certificate_Expired_on_s,
- DateFormat.getInstance().format(certificate.getNotAfter())));
- AdtPlugin.printErrorToConsole(mProject, msg);
- BaseProjectHelper.markResource(mProject, AndroidConstants.MARKER_PACKAGING, msg,
- IMarker.SEVERITY_ERROR);
- return false;
- }
+ // mark the project with the standard message
+ String msg = String.format(Messages.Final_Archive_Error_s, eMessage);
+ BaseProjectHelper.markResource(mProject, AndroidConstants.MARKER_PACKAGING, msg,
+ IMarker.SEVERITY_ERROR);
- // create the jar builder.
- fos = new FileOutputStream(output);
- SignedJarBuilder builder = new SignedJarBuilder(fos, key, certificate);
-
- // add the intermediate file containing the compiled resources.
- AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, mProject,
- String.format(Messages.ApkBuilder_Packaging_s, intermediateApk));
- FileInputStream fis = new FileInputStream(intermediateApk);
- try {
- builder.writeZip(fis, null /* filter */);
- } finally {
- fis.close();
+ return false;
+ }
+ } else {
+ AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, mProject,
+ String.format(Messages.ApkBuilder_Using_s_To_Sign, keystoreOsPath));
}
+ }
- // Now we add the new file to the zip archive for the classes.dex file.
- AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, mProject,
- String.format(Messages.ApkBuilder_Packaging_s,
- AndroidConstants.FN_CLASSES_DEX));
- File entryFile = new File(dex);
- builder.writeFile(entryFile, AndroidConstants.FN_CLASSES_DEX);
+
+ try {
+ ApkBuilder apkBuilder = new ApkBuilder(output, intermediateApk, dex, keystoreOsPath,
+ AdtPrefs.getPrefs().getBuildVerbosity() == BuildVerbosity.VERBOSE ?
+ AdtPlugin.getOutPrintStream(project, null): null);
+ apkBuilder.setDebugMode(debuggable);
// Now we write the standard resources from the project and the referenced projects.
- writeStandardResources(builder, javaProject, referencedJavaProjects);
+ writeStandardResources(apkBuilder, javaProject, referencedJavaProjects);
- // Now we write the standard resources from the external libraries
+ // Now we write the standard resources from the external jars
for (String libraryOsPath : getExternalJars()) {
- AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, mProject,
- String.format(Messages.ApkBuilder_Packaging_s, libraryOsPath));
- try {
- fis = new FileInputStream(libraryOsPath);
- mResourceFilter.clear();
- builder.writeZip(fis, mResourceFilter);
-
- // check if we found native libraries in the external library. This
- // constitutes an error or warning depending on if they are in lib/
- List<String> nativeLibs = mResourceFilter.getNativeLibs();
- boolean nativeInterference = mResourceFilter.getNativeLibInterefence();
- if (nativeLibs.size() > 0) {
- String libName = new File(libraryOsPath).getName();
- String msg = String.format("Native libraries detected in '%1$s'. See console for more information.",
- libName);
-
-
- BaseProjectHelper.markResource(mProject, AndroidConstants.MARKER_PACKAGING,
- msg,
- nativeInterference ||
- AdtPrefs.getPrefs().getBuildForceErrorOnNativeLibInJar() ?
- IMarker.SEVERITY_ERROR : IMarker.SEVERITY_WARNING);
-
- ArrayList<String> consoleMsgs = new ArrayList<String>();
- consoleMsgs.add(String.format(
- "The library '%1$s' contains native libraries that will not run on the device.",
- libName));
- if (nativeInterference) {
- consoleMsgs.add("Additionally some of those libraries will interfer with the installation of the application because of their location in lib/");
- consoleMsgs.add("lib/ is reserved for NDK libraries.");
- }
- consoleMsgs.add("The following libraries were found:");
- for (String lib : nativeLibs) {
- consoleMsgs.add(" - " + lib);
- }
- AdtPlugin.printErrorToConsole(mProject,
- consoleMsgs.toArray());
+ JarStatus status = apkBuilder.addResourcesFromJar(new File(libraryOsPath));
+
+ // check if we found native libraries in the external library. This
+ // constitutes an error or warning depending on if they are in lib/
+ if (status.getNativeLibs().size() > 0) {
+ String libName = new File(libraryOsPath).getName();
+ String msg = String.format(
+ "Native libraries detected in '%1$s'. See console for more information.",
+ libName);
- return false;
+ BaseProjectHelper.markResource(mProject, AndroidConstants.MARKER_PACKAGING,
+ msg,
+ status.hasNativeLibsConflicts() ||
+ AdtPrefs.getPrefs().getBuildForceErrorOnNativeLibInJar() ?
+ IMarker.SEVERITY_ERROR : IMarker.SEVERITY_WARNING);
+
+ ArrayList<String> consoleMsgs = new ArrayList<String>();
+ consoleMsgs.add(String.format(
+ "The library '%1$s' contains native libraries that will not run on the device.",
+ libName));
+ if (status.hasNativeLibsConflicts()) {
+ consoleMsgs.add("Additionally some of those libraries will interfer with the installation of the application because of their location in lib/");
+ consoleMsgs.add("lib/ is reserved for NDK libraries.");
+ }
+ consoleMsgs.add("The following libraries were found:");
+ for (String lib : status.getNativeLibs()) {
+ consoleMsgs.add(" - " + lib);
}
- } finally {
- fis.close();
+ AdtPlugin.printErrorToConsole(mProject,
+ consoleMsgs.toArray());
+
+ return false;
}
}
@@ -339,8 +265,8 @@ public class ApkBuilderHelper {
IResource libFolder = mProject.findMember(SdkConstants.FD_NATIVE_LIBS);
if (libFolder != null && libFolder.exists() &&
libFolder.getType() == IResource.FOLDER) {
- // look inside and put .so in lib/* by keeping the relative folder path.
- writeNativeLibraries((IFolder) libFolder, builder, abiFilter, debuggable);
+ // get a File for the folder.
+ apkBuilder.addNativeLibraries(libFolder.getLocation().toFile(), abiFilter);
}
// write the native libraries for the library projects.
@@ -349,59 +275,36 @@ public class ApkBuilderHelper {
libFolder = lib.findMember(SdkConstants.FD_NATIVE_LIBS);
if (libFolder != null && libFolder.exists() &&
libFolder.getType() == IResource.FOLDER) {
- // look inside and put .so in lib/* by keeping the relative folder path.
- writeNativeLibraries((IFolder) libFolder, builder, abiFilter, debuggable);
+ apkBuilder.addNativeLibraries(libFolder.getLocation().toFile(), abiFilter);
}
}
}
- // close the jar file and write the manifest and sign it.
- builder.close();
- } catch (GeneralSecurityException e1) {
- // mark project and return
- String msg = String.format(Messages.Final_Archive_Error_s, e1.getMessage());
- AdtPlugin.printErrorToConsole(mProject, msg);
- BaseProjectHelper.markResource(mProject, AndroidConstants.MARKER_PACKAGING, msg,
- IMarker.SEVERITY_ERROR);
- return false;
- } catch (IOException e1) {
+ // seal the APK.
+ apkBuilder.sealApk();
+ return true;
+ } catch (CoreException e) {
// mark project and return
- String msg = String.format(Messages.Final_Archive_Error_s, e1.getMessage());
+ String msg = String.format(Messages.Final_Archive_Error_s, e.getMessage());
AdtPlugin.printErrorToConsole(mProject, msg);
BaseProjectHelper.markResource(mProject, AndroidConstants.MARKER_PACKAGING, msg,
IMarker.SEVERITY_ERROR);
- return false;
- } catch (KeytoolException e) {
- String eMessage = e.getMessage();
-
- // mark the project with the standard message
- String msg = String.format(Messages.Final_Archive_Error_s, eMessage);
- BaseProjectHelper.markResource(mProject, AndroidConstants.MARKER_PACKAGING, msg,
- IMarker.SEVERITY_ERROR);
-
- // output more info in the console
- AdtPlugin.printErrorToConsole(mProject,
- msg,
- String.format(Messages.ApkBuilder_JAVA_HOME_is_s, e.getJavaHome()),
- Messages.ApkBuilder_Update_or_Execute_manually_s,
- e.getCommandLine());
- } catch (AndroidLocationException e) {
- String eMessage = e.getMessage();
-
- // mark the project with the standard message
- String msg = String.format(Messages.Final_Archive_Error_s, eMessage);
- BaseProjectHelper.markResource(mProject, AndroidConstants.MARKER_PACKAGING, msg,
- IMarker.SEVERITY_ERROR);
-
- // and also output it in the console
- AdtPlugin.printErrorToConsole(mProject, msg);
- } catch (CoreException e) {
+ } catch (ApkCreationException e) {
// mark project and return
String msg = String.format(Messages.Final_Archive_Error_s, e.getMessage());
AdtPlugin.printErrorToConsole(mProject, msg);
BaseProjectHelper.markResource(mProject, AndroidConstants.MARKER_PACKAGING, msg,
IMarker.SEVERITY_ERROR);
- return false;
+ } catch (DuplicateFileException e) {
+ String msg1 = String.format(
+ "Found duplicate file for APK: %1$s\nOrigin 1: %2$s\nOrigin 2: %3$s",
+ e.getArchivePath(), e.getFile1(), e.getFile2());
+ String msg2 = String.format(Messages.Final_Archive_Error_s, msg1);
+ AdtPlugin.printErrorToConsole(mProject, msg2);
+ BaseProjectHelper.markResource(mProject, AndroidConstants.MARKER_PACKAGING, msg2,
+ IMarker.SEVERITY_ERROR);
+ } catch (SealedApkException e) {
+ // this won't happen as we control when the apk is sealed.
} catch (Exception e) {
// try to catch other exception to actually display an error. This will be useful
// if we get an NPE or something so that we can at least notify the user that something
@@ -416,18 +319,9 @@ public class ApkBuilderHelper {
AdtPlugin.printErrorToConsole(mProject, msg);
BaseProjectHelper.markResource(mProject, AndroidConstants.MARKER_PACKAGING, msg,
IMarker.SEVERITY_ERROR);
- return false;
- } finally {
- if (fos != null) {
- try {
- fos.close();
- } catch (IOException e) {
- // pass.
- }
- }
}
- return true;
+ return false;
}
/**
@@ -639,80 +533,29 @@ public class ApkBuilderHelper {
/**
- * Writes native libraries into a {@link SignedJarBuilder}.
- * <p/>The native libraries must be located in a given main folder. Under this folder, it is
- * expected that the libraries are under a sub-folder that represents the ABI of the library.
- *
- * The path in the archive is based on the ABI folder name, and located under a main
- * folder called "lib".
- *
- * This method also packages any "gdbserver" executable it finds in the ABI folders, if
- * <var>debuggable</var> is set to true.
- *
- * @param rootFolder The folder containing the native libraries.
- * @param jarBuilder the {@link SignedJarBuilder} used to create the archive.
- * @param abiFilter an optional filter. If not null, then only the matching ABI is included in
- * the final archive
- * @param debuggable whether the application is debuggable. If <code>true</code> then gdbserver
- * executables will be packaged as well.
- * @throws CoreException
- * @throws IOException
- */
- private void writeNativeLibraries(IFolder rootFolder, SignedJarBuilder jarBuilder,
- String abiFilter, boolean debuggable) throws CoreException, IOException {
- // the native files must be under a single sub-folder under the main root folder.
- // the sub-folder represents the abi for the native libs
- IResource[] abis = rootFolder.members();
- for (IResource abi : abis) {
- if (abi.getType() == IResource.FOLDER) { // ignore non folders.
-
- // check the abi filter and reject all other ABIs
- if (abiFilter != null && abiFilter.equals(abi.getName()) == false) {
- continue;
- }
-
- IResource[] libs = ((IFolder)abi).members();
-
- for (IResource lib : libs) {
- if (lib.getType() == IResource.FILE) { // ignore non files.
- IPath path = lib.getFullPath();
-
- // check the extension.
- String ext = path.getFileExtension();
- if (AndroidConstants.EXT_NATIVE_LIB.equalsIgnoreCase(ext) ||
- (debuggable && GDBSERVER_NAME.equals(lib.getName()))) {
- // compute the path inside the archive.
- IPath apkPath = new Path(SdkConstants.FD_APK_NATIVE_LIBS);
- apkPath = apkPath.append(abi.getName()).append(lib.getName());
-
- // writes the file in the apk.
- jarBuilder.writeFile(lib.getLocation().toFile(), apkPath.toString());
- }
- }
- }
- }
- }
- }
-
- /**
* Writes the standard resources of a project and its referenced projects
* into a {@link SignedJarBuilder}.
* Standard resources are non java/aidl files placed in the java package folders.
- * @param jarBuilder the {@link SignedJarBuilder}.
+ * @param apkBuilder the {@link ApkBuilder}.
* @param javaProject the javaProject object.
* @param referencedJavaProjects the java projects that this project references.
- * @throws IOException
+ * @throws ApkCreationException if an error occurred
+ * @throws SealedApkException if the APK is already sealed.
+ * @throws DuplicateFileException if a file conflicts with another already added to the APK
+ * at the same location inside the APK archive.
* @throws CoreException
*/
- private void writeStandardResources(SignedJarBuilder jarBuilder, IJavaProject javaProject,
- IJavaProject[] referencedJavaProjects) throws IOException, CoreException {
+ private void writeStandardResources(ApkBuilder apkBuilder, IJavaProject javaProject,
+ IJavaProject[] referencedJavaProjects)
+ throws DuplicateFileException, ApkCreationException, SealedApkException,
+ CoreException {
IWorkspace ws = ResourcesPlugin.getWorkspace();
IWorkspaceRoot wsRoot = ws.getRoot();
// create a list of path already put into the archive, in order to detect conflict
ArrayList<String> list = new ArrayList<String>();
- writeStandardProjectResources(jarBuilder, javaProject, wsRoot, list);
+ writeStandardProjectResources(apkBuilder, javaProject, wsRoot, list);
for (IJavaProject referencedJavaProject : referencedJavaProjects) {
// only include output from non android referenced project
@@ -720,7 +563,7 @@ public class ApkBuilderHelper {
// instrumentation projects that need to reference the projects to be tested).
if (referencedJavaProject.getProject().hasNature(
AndroidConstants.NATURE_DEFAULT) == false) {
- writeStandardProjectResources(jarBuilder, referencedJavaProject, wsRoot, list);
+ writeStandardProjectResources(apkBuilder, referencedJavaProject, wsRoot, list);
}
}
}
@@ -728,15 +571,18 @@ public class ApkBuilderHelper {
/**
* Writes the standard resources of a {@link IJavaProject} into a {@link SignedJarBuilder}.
* Standard resources are non java/aidl files placed in the java package folders.
- * @param jarBuilder the {@link SignedJarBuilder}.
+ * @param jarBuilder the {@link ApkBuilder}.
* @param javaProject the javaProject object.
* @param wsRoot the {@link IWorkspaceRoot}.
* @param list a list of files already added to the archive, to detect conflicts.
- * @throws IOException
+ * @throws ApkCreationException if an error occurred
+ * @throws SealedApkException if the APK is already sealed.
+ * @throws DuplicateFileException if a file conflicts with another already added to the APK
+ * at the same location inside the APK archive.
*/
- private void writeStandardProjectResources(SignedJarBuilder jarBuilder,
+ private void writeStandardProjectResources(ApkBuilder apkBuilder,
IJavaProject javaProject, IWorkspaceRoot wsRoot, ArrayList<String> list)
- throws IOException {
+ throws DuplicateFileException, ApkCreationException, SealedApkException {
// get the source pathes
ArrayList<IPath> sourceFolders = BaseProjectHelper.getSourceClasspaths(javaProject);
@@ -744,74 +590,12 @@ public class ApkBuilderHelper {
for (IPath sourcePath : sourceFolders) {
IResource sourceResource = wsRoot.findMember(sourcePath);
if (sourceResource != null && sourceResource.getType() == IResource.FOLDER) {
- writeStandardSourceFolderResources(jarBuilder, sourcePath, (IFolder)sourceResource,
- list);
- }
- }
- }
-
- /**
- * Recursively writes the standard resources of a source folder into a {@link SignedJarBuilder}.
- * Standard resources are non java/aidl files placed in the java package folders.
- * @param jarBuilder the {@link SignedJarBuilder}.
- * @param sourceFolder the {@link IPath} of the source folder.
- * @param currentFolder The current folder we're recursively processing.
- * @param list a list of files already added to the archive, to detect conflicts.
- * @throws IOException
- */
- private void writeStandardSourceFolderResources(SignedJarBuilder jarBuilder, IPath sourceFolder,
- IFolder currentFolder, ArrayList<String> list) throws IOException {
- try {
- IResource[] members = currentFolder.members();
-
- for (IResource member : members) {
- int type = member.getType();
- if (type == IResource.FILE && member.exists()) {
- if (checkFileForPackaging((IFile)member)) {
- // this files must be added to the archive.
- IPath fullPath = member.getFullPath();
-
- // We need to create its path inside the archive.
- // This path is relative to the source folder.
- IPath relativePath = fullPath.removeFirstSegments(
- sourceFolder.segmentCount());
- String zipPath = relativePath.toString();
-
- // lets check it's not already in the list of path added to the archive
- if (list.indexOf(zipPath) != -1) {
- AdtPlugin.printErrorToConsole(mProject,
- String.format(
- Messages.ApkBuilder_s_Conflict_with_file_s,
- fullPath, zipPath));
- } else {
- // get the File object
- File entryFile = member.getLocation().toFile();
-
- AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, mProject,
- String.format(
- Messages.ApkBuilder_Packaging_s_into_s,
- fullPath, zipPath));
-
- // write it in the zip archive
- jarBuilder.writeFile(entryFile, zipPath);
-
- // and add it to the list of entries
- list.add(zipPath);
- }
- }
- } else if (type == IResource.FOLDER) {
- if (checkFolderForPackaging((IFolder)member)) {
- writeStandardSourceFolderResources(jarBuilder, sourceFolder,
- (IFolder)member, list);
- }
- }
+ // get a File from the IResource
+ apkBuilder.addSourceFolder(sourceResource.getLocation().toFile());
}
- } catch (CoreException e) {
- // if we can't get the members of the folder, we just don't do anything.
}
}
-
/**
* Returns an array of external jar files used by the project.
* @return an array of OS-specific absolute file paths
@@ -838,15 +622,11 @@ public class ApkBuilderHelper {
// check the name ends with .jar
if (AndroidConstants.EXT_JAR.equalsIgnoreCase(path.getFileExtension())) {
- boolean local = false;
IResource resource = wsRoot.findMember(path);
if (resource != null && resource.exists() &&
resource.getType() == IResource.FILE) {
- local = true;
oslibraryList.add(resource.getLocation().toOSString());
- }
-
- if (local == false) {
+ } else {
// if the jar path doesn't match a workspace resource,
// then we get an OSString and check if this links to a valid file.
String osFullPath = path.toOSString();
@@ -922,7 +702,7 @@ public class ApkBuilderHelper {
String name = file.getName();
String ext = file.getFileExtension();
- return JavaResourceFilter.checkFileForPackaging(name, ext);
+ return ApkBuilder.checkFileForPackaging(name, ext);
}
/**
@@ -932,7 +712,7 @@ public class ApkBuilderHelper {
*/
static boolean checkFolderForPackaging(IFolder folder) {
String name = folder.getName();
- return JavaResourceFilter.checkFolderForPackaging(name);
+ return ApkBuilder.checkFolderForPackaging(name);
}
/**
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/ApkDeltaVisitor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/ApkDeltaVisitor.java
index 643fee5..81f06c1 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/ApkDeltaVisitor.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/ApkDeltaVisitor.java
@@ -193,7 +193,7 @@ public class ApkDeltaVisitor extends BaseDeltaVisitor
if (mOutputPath.equals(parentPath)) {
String resourceName = resource.getName();
// check if classes.dex was removed
- if (resourceName.equalsIgnoreCase(AndroidConstants.FN_CLASSES_DEX)) {
+ if (resourceName.equalsIgnoreCase(SdkConstants.FN_APK_CLASSES_DEX)) {
mConvertToDex = true;
mMakeFinalPackage = true;
} else if (resourceName.equalsIgnoreCase(
@@ -237,7 +237,7 @@ public class ApkDeltaVisitor extends BaseDeltaVisitor
// inside the native library folder. Test if the changed resource is a .so file.
if (type == IResource.FILE &&
(AndroidConstants.EXT_NATIVE_LIB.equalsIgnoreCase(path.getFileExtension())
- || ApkBuilderHelper.GDBSERVER_NAME.equals(resource.getName()))) {
+ || SdkConstants.FN_GDBSERVER.equals(resource.getName()))) {
mMakeFinalPackage = true;
return false; // return false for file.
}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/SdkConstants.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/SdkConstants.java
index 36afc5b..824719a 100644
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/SdkConstants.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/SdkConstants.java
@@ -48,6 +48,9 @@ public final class SdkConstants {
/** An SDK Project's AndroidManifest.xml file */
public static final String FN_ANDROID_MANIFEST_XML= "AndroidManifest.xml";
+ /** Dex filename inside the APK. i.e. "classes.dex" */
+ public final static String FN_APK_CLASSES_DEX = "classes.dex"; //$NON-NLS-1$
+
/** An SDK Project's build.xml file */
public final static String FN_BUILD_XML = "build.xml";
@@ -132,6 +135,11 @@ public final class SdkConstants {
/** properties file for the SDK */
public final static String FN_SDK_PROP = "sdk.properties"; //$NON-NLS-1$
+ /**
+ * filename for gdbserver.
+ */
+ public final static String FN_GDBSERVER = "gdbserver";
+
/* Folder Names for Android Projects . */
/** Resources folder name, i.e. "res". */
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/build/ApkBuilder.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/build/ApkBuilder.java
new file mode 100644
index 0000000..9f1fc84
--- /dev/null
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/build/ApkBuilder.java
@@ -0,0 +1,831 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.sdklib.build;
+
+import com.android.sdklib.SdkConstants;
+import com.android.sdklib.internal.build.DebugKeyProvider;
+import com.android.sdklib.internal.build.SignedJarBuilder;
+import com.android.sdklib.internal.build.DebugKeyProvider.IKeyGenOutput;
+import com.android.sdklib.internal.build.DebugKeyProvider.KeytoolException;
+import com.android.sdklib.internal.build.SignedJarBuilder.IZipEntryFilter;
+import com.android.sdklib.internal.build.SignedJarBuilder.IZipEntryFilter.ZipAbortException;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.security.PrivateKey;
+import java.security.cert.X509Certificate;
+import java.text.DateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.regex.Pattern;
+
+/**
+ * Class making the final apk packaging.
+ * The inputs are:
+ * - packaged resources (output of aapt)
+ * - code file (ouput of dx)
+ * - Java resources coming from the project, its libraries, and its jar files
+ * - Native libraries from the project or its library.
+ *
+ */
+public final class ApkBuilder {
+
+ private final static Pattern PATTERN_NATIVELIB_EXT = Pattern.compile("^.+\\.so$",
+ Pattern.CASE_INSENSITIVE);
+
+ /**
+ * A No-op zip filter. It's used to detect conflicts.
+ *
+ */
+ private final class NullZipFilter implements IZipEntryFilter {
+ private File mInputFile;
+
+ void reset(File inputFile) {
+ mInputFile = inputFile;
+ }
+
+ public boolean checkEntry(String archivePath) throws ZipAbortException {
+ verbosePrintln("=> %s", archivePath);
+
+ File duplicate = checkFileForDuplicate(archivePath);
+ if (duplicate != null) {
+ throw new DuplicateFileException(archivePath, duplicate, mInputFile);
+ } else {
+ mAddedFiles.put(archivePath, mInputFile);
+ }
+
+ return true;
+ }
+ }
+
+ /**
+ * Custom {@link IZipEntryFilter} to filter out everything that is not a standard java
+ * resources, and also record whether the zip file contains native libraries.
+ * <p/>Used in {@link SignedJarBuilder#writeZip(java.io.InputStream, IZipEntryFilter)} when
+ * we only want the java resources from external jars.
+ */
+ private final class JavaAndNativeResourceFilter implements IZipEntryFilter {
+ private final List<String> mNativeLibs = new ArrayList<String>();
+ private boolean mNativeLibsConflict = false;
+ private File mInputFile;
+
+ public boolean checkEntry(String archivePath) throws ZipAbortException {
+ // split the path into segments.
+ String[] segments = archivePath.split("/");
+
+ // empty path? skip to next entry.
+ if (segments.length == 0) {
+ return false;
+ }
+
+ // Check each folders to make sure they should be included.
+ // Folders like CVS, .svn, etc.. should already have been excluded from the
+ // jar file, but we need to exclude some other folder (like /META-INF) so
+ // we check anyway.
+ for (int i = 0 ; i < segments.length - 1; i++) {
+ if (checkFolderForPackaging(segments[i]) == false) {
+ return false;
+ }
+ }
+
+ // get the file name from the path
+ String fileName = segments[segments.length-1];
+
+ boolean check = checkFileForPackaging(fileName);
+
+ // only do additional checks if the file passes the default checks.
+ if (check) {
+ verbosePrintln("=> %s", archivePath);
+
+ File duplicate = checkFileForDuplicate(archivePath);
+ if (duplicate != null) {
+ throw new DuplicateFileException(archivePath, duplicate, mInputFile);
+ } else {
+ mAddedFiles.put(archivePath, mInputFile);
+ }
+
+ if (archivePath.endsWith(".so")) {
+ mNativeLibs.add(archivePath);
+
+ // only .so located in lib/ will interfere with the installation
+ if (archivePath.startsWith(SdkConstants.FD_APK_NATIVE_LIBS + "/")) {
+ mNativeLibsConflict = true;
+ }
+ } else if (archivePath.endsWith(".jnilib")) {
+ mNativeLibs.add(archivePath);
+ }
+ }
+
+ return check;
+ }
+
+ List<String> getNativeLibs() {
+ return mNativeLibs;
+ }
+
+ boolean getNativeLibsConflict() {
+ return mNativeLibsConflict;
+ }
+
+ void reset(File inputFile) {
+ mInputFile = inputFile;
+ mNativeLibs.clear();
+ mNativeLibsConflict = false;
+ }
+ }
+
+ private final File mApkFile;
+ private final File mResFile;
+ private final File mDexFile;
+ private final PrintStream mVerboseStream;
+ private final SignedJarBuilder mBuilder;
+ private boolean mDebugMode = false;
+ private boolean mIsSealed = false;
+
+ private final NullZipFilter mNullFilter = new NullZipFilter();
+ private final JavaAndNativeResourceFilter mFilter = new JavaAndNativeResourceFilter();
+ private final HashMap<String, File> mAddedFiles = new HashMap<String, File>();
+
+ /**
+ * An exception thrown during packaging of an APK file.
+ */
+ public final static class ApkCreationException extends Exception {
+ private static final long serialVersionUID = 1L;
+
+ public ApkCreationException(String format, Object... args) {
+ super(String.format(format, args));
+ }
+
+ public ApkCreationException(Throwable cause, String format, Object... args) {
+ super(String.format(format, args), cause);
+ }
+
+ public ApkCreationException(Throwable cause) {
+ super(cause);
+ }
+ }
+
+ /**
+ * An exception thrown during packaging of an APK file.
+ */
+ public final static class DuplicateFileException extends ZipAbortException {
+ private static final long serialVersionUID = 1L;
+ private final String mArchivePath;
+ private final File mFile1;
+ private final File mFile2;
+
+ public DuplicateFileException(String archivePath, File file1, File file2) {
+ super();
+ mArchivePath = archivePath;
+ mFile1 = file1;
+ mFile2 = file2;
+ }
+
+ public String getArchivePath() {
+ return mArchivePath;
+ }
+
+ public File getFile1() {
+ return mFile1;
+ }
+
+ public File getFile2() {
+ return mFile2;
+ }
+
+ @Override
+ public String getMessage() {
+ return "Duplicate files at the same path inside the APK";
+ }
+ }
+
+ /**
+ * An exception thrown when trying to add files to a sealed APK.
+ */
+ public final static class SealedApkException extends Exception {
+ private static final long serialVersionUID = 1L;
+
+ public SealedApkException(String format, Object... args) {
+ super(String.format(format, args));
+ }
+
+ public SealedApkException(Throwable cause, String format, Object... args) {
+ super(String.format(format, args), cause);
+ }
+
+ public SealedApkException(Throwable cause) {
+ super(cause);
+ }
+ }
+
+ /**
+ * Status for the addition of a jar file resources into the APK.
+ * This indicates possible issues with native library inside the jar file.
+ */
+ public interface JarStatus {
+ /**
+ * Returns the list of native libraries found in the jar file.
+ */
+ List<String> getNativeLibs();
+
+ /**
+ * Returns whether some of those libraries were located in the location that Android
+ * expects its native libraries.
+ */
+ boolean hasNativeLibsConflicts();
+
+ }
+
+ /** Internal implementation of {@link JarStatus}. */
+ private final static class JarStatusImpl implements JarStatus {
+ public final List<String> mLibs;
+ public final boolean mNativeLibsConflict;
+
+ private JarStatusImpl(List<String> libs, boolean nativeLibsConflict) {
+ mLibs = libs;
+ mNativeLibsConflict = nativeLibsConflict;
+ }
+
+ public List<String> getNativeLibs() {
+ return mLibs;
+ }
+
+ public boolean hasNativeLibsConflicts() {
+ return mNativeLibsConflict;
+ }
+ }
+
+ /**
+ * Creates a new instance.
+ * @param apkOsPath the OS path of the file to create.
+ * @param resOsPath the OS path of the packaged resource file.
+ * @param dexOsPath the OS path of the dex file. This can be null for apk with no code.
+ * @throws ApkCreationException
+ */
+ public ApkBuilder(String apkOsPath, String resOsPath, String dexOsPath, String storeOsPath,
+ PrintStream verboseStream) throws ApkCreationException {
+ this(new File(apkOsPath),
+ new File(resOsPath),
+ dexOsPath != null ? new File(dexOsPath) : null,
+ storeOsPath,
+ verboseStream);
+ }
+
+ /**
+ * Creates a new instance.
+ *
+ * This creates a new builder that will create the specified output file, using the two
+ * mandatory given input files.
+ *
+ * An optional debug keystore can be provided. If set, it is expected that the store password
+ * is 'android' and the key alias and password are 'androiddebugkey' and 'android'.
+ *
+ * An optional {@link PrintStream} can also be provided for verbose output. If null, there will
+ * be no output.
+ *
+ * @param apkFile the file to create
+ * @param resFile the file representing the packaged resource file.
+ * @param dexFile the file representing the dex file. This can be null for apk with no code.
+ * @param storeOsPath the OS path to the debug keystore, if needed or null.
+ * @param verboseStream the stream to which verbose output should go. If null, verbose mode
+ * is not enabled.
+ * @throws ApkCreationException
+ */
+ public ApkBuilder(File apkFile, File resFile, File dexFile, String storeOsPath,
+ PrintStream verboseStream) throws ApkCreationException {
+ checkOutputFile(mApkFile = apkFile);
+ checkInputFile(mResFile = resFile);
+ if (dexFile != null) {
+ checkInputFile(mDexFile = dexFile);
+ } else {
+ mDexFile = null;
+ }
+ mVerboseStream = verboseStream;
+
+ try {
+ File storeFile = null;
+ if (storeOsPath != null) {
+ storeFile = new File(storeOsPath);
+ checkInputFile(storeFile);
+ }
+
+ if (storeFile != null) {
+ // get the debug key
+ verbosePrintln("Using keystore: %s", storeOsPath);
+
+ IKeyGenOutput keygenOutput = null;
+ if (mVerboseStream != null) {
+ keygenOutput = new IKeyGenOutput() {
+ public void out(String message) {
+ mVerboseStream.println(message);
+ }
+
+ public void err(String message) {
+ mVerboseStream.println(message);
+ }
+ };
+ }
+
+ DebugKeyProvider keyProvider = new DebugKeyProvider(
+ storeOsPath, null /*store type*/, keygenOutput);
+
+ PrivateKey key = keyProvider.getDebugKey();
+ X509Certificate certificate = (X509Certificate)keyProvider.getCertificate();
+
+ if (key == null) {
+ throw new ApkCreationException("Unable to get debug signature key");
+ }
+
+ // compare the certificate expiration date
+ if (certificate != null && certificate.getNotAfter().compareTo(new Date()) < 0) {
+ // TODO, regenerate a new one.
+ throw new ApkCreationException("Debug Certificate expired on " +
+ DateFormat.getInstance().format(certificate.getNotAfter()));
+ }
+
+ mBuilder = new SignedJarBuilder(
+ new FileOutputStream(mApkFile, false /* append */), key,
+ certificate);
+ } else {
+ mBuilder = new SignedJarBuilder(
+ new FileOutputStream(mApkFile, false /* append */),
+ null /* key */, null /* certificate */);
+ }
+
+ verbosePrintln("Packaging %s", mApkFile.getName());
+
+ // add the resources
+ addZipFile(mResFile);
+
+ // add the class dex file at the root of the apk
+ if (mDexFile != null) {
+ addFile(mDexFile, SdkConstants.FN_APK_CLASSES_DEX);
+ }
+
+ } catch (KeytoolException e) {
+ if (e.getJavaHome() == null) {
+ throw new ApkCreationException(e.getMessage() +
+ "\nJAVA_HOME seems undefined, setting it will help locating keytool automatically\n" +
+ "You can also manually execute the following command\n:" +
+ e.getCommandLine());
+ } else {
+ throw new ApkCreationException(e.getMessage() +
+ "\nJAVA_HOME is set to: " + e.getJavaHome() +
+ "\nUpdate it if necessary, or manually execute the following command:\n" +
+ e.getCommandLine());
+ }
+ } catch (Exception e) {
+ throw new ApkCreationException(e);
+ }
+ }
+
+ /**
+ * Sets the debug mode. In debug mode, when native libraries are present, the packaging
+ * will also include one or more copies of gdbserver in the final APK file.
+ *
+ * These are used for debugging native code, to ensure that gdbserver is accessible to the
+ * application.
+ *
+ * There will be one version of gdbserver for each ABI supported by the application.
+ *
+ * the gbdserver files are placed in the libs/abi/ folders automatically by the NDK.
+ *
+ * @param debugMode the debug mode flag.
+ */
+ public void setDebugMode(boolean debugMode) {
+ mDebugMode = debugMode;
+ }
+
+ /**
+ * Adds a file to the APK at a given path
+ * @param file the file to add
+ * @param archivePath the path of the file inside the APK archive.
+ * @throws ApkCreationException if an error occurred
+ * @throws SealedApkException if the APK is already sealed.
+ * @throws DuplicateFileException if a file conflicts with another already added to the APK
+ * at the same location inside the APK archive.
+ */
+ public void addFile(File file, String archivePath) throws ApkCreationException,
+ SealedApkException, DuplicateFileException {
+ if (mIsSealed) {
+ throw new SealedApkException("APK is already sealed");
+ }
+
+ try {
+ doAddFile(file, archivePath);
+ } catch (DuplicateFileException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new ApkCreationException(e, "Failed to add %s", file);
+ }
+ }
+
+ /**
+ * Adds the content from a zip file.
+ * All file keep the same path inside the archive.
+ * @param zipFile the zip File.
+ * @throws ApkCreationException if an error occurred
+ * @throws SealedApkException if the APK is already sealed.
+ * @throws DuplicateFileException if a file conflicts with another already added to the APK
+ * at the same location inside the APK archive.
+ */
+ public void addZipFile(File zipFile) throws ApkCreationException, SealedApkException,
+ DuplicateFileException {
+ if (mIsSealed) {
+ throw new SealedApkException("APK is already sealed");
+ }
+
+ try {
+ verbosePrintln("%s:", zipFile);
+
+ // reset the filter with this input.
+ mNullFilter.reset(zipFile);
+
+ // ask the builder to add the content of the file.
+ FileInputStream fis = new FileInputStream(zipFile);
+ mBuilder.writeZip(fis, mNullFilter);
+ } catch (DuplicateFileException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new ApkCreationException(e, "Failed to add %s", zipFile);
+ }
+ }
+
+ /**
+ * Adds the resources from a jar file.
+ * @param jarFile the jar File.
+ * @return a {@link JarStatus} object indicating if native libraries where found in
+ * the jar file.
+ * @throws ApkCreationException if an error occurred
+ * @throws SealedApkException if the APK is already sealed.
+ * @throws DuplicateFileException if a file conflicts with another already added to the APK
+ * at the same location inside the APK archive.
+ */
+ public JarStatus addResourcesFromJar(File jarFile) throws ApkCreationException,
+ SealedApkException, DuplicateFileException {
+ if (mIsSealed) {
+ throw new SealedApkException("APK is already sealed");
+ }
+
+ try {
+ verbosePrintln("%s:", jarFile);
+
+ // reset the filter with this input.
+ mFilter.reset(jarFile);
+
+ // ask the builder to add the content of the file, filtered to only let through
+ // the java resources.
+ FileInputStream fis = new FileInputStream(jarFile);
+ mBuilder.writeZip(fis, mFilter);
+
+ // check if native libraries were found in the external library. This should
+ // constitutes an error or warning depending on if they are in lib/
+ return new JarStatusImpl(mFilter.getNativeLibs(), mFilter.getNativeLibsConflict());
+ } catch (DuplicateFileException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new ApkCreationException(e, "Failed to add %s", jarFile);
+ }
+ }
+
+ /**
+ * Adds the resources from a source folder.
+ * @param sourceFolder the source folder.
+ * @throws ApkCreationException if an error occurred
+ * @throws SealedApkException if the APK is already sealed.
+ * @throws DuplicateFileException if a file conflicts with another already added to the APK
+ * at the same location inside the APK archive.
+ */
+ public void addSourceFolder(File sourceFolder) throws ApkCreationException, SealedApkException,
+ DuplicateFileException {
+ if (mIsSealed) {
+ throw new SealedApkException("APK is already sealed");
+ }
+
+ if (sourceFolder.isDirectory()) {
+ try {
+ // file is a directory, process its content.
+ File[] files = sourceFolder.listFiles();
+ for (File file : files) {
+ processFileForResource(file, null);
+ }
+ } catch (DuplicateFileException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new ApkCreationException(e, "Failed to add %s", sourceFolder);
+ }
+ } else {
+ // not a directory? check if it's a file or doesn't exist
+ if (sourceFolder.exists()) {
+ throw new ApkCreationException("%s is not a folder", sourceFolder);
+ } else {
+ throw new ApkCreationException("%s does not exist", sourceFolder);
+ }
+ }
+ }
+
+ /**
+ * Adds the native libraries from the top native folder.
+ * The content of this folder must be the various ABI folders.
+ *
+ * This may or may not copy gdbserver into the apk based on whether the debug mode is set.
+ *
+ * @param nativeFolder the native folder.
+ * @param abiFilter an optional filter. If not null, then only the matching ABI is included in
+ * the final archive
+ * @throws ApkCreationException if an error occurred
+ * @throws SealedApkException if the APK is already sealed.
+ * @throws DuplicateFileException if a file conflicts with another already added to the APK
+ * at the same location inside the APK archive.
+ *
+ * @see #setDebugMode(boolean)
+ */
+ public void addNativeLibraries(File nativeFolder, String abiFilter)
+ throws ApkCreationException, SealedApkException, DuplicateFileException {
+ if (mIsSealed) {
+ throw new SealedApkException("APK is already sealed");
+ }
+
+ if (nativeFolder.isDirectory() == false) {
+ // not a directory? check if it's a file or doesn't exist
+ if (nativeFolder.exists()) {
+ throw new ApkCreationException("%s is not a folder", nativeFolder);
+ } else {
+ throw new ApkCreationException("%s does not exist", nativeFolder);
+ }
+ }
+
+ File[] abiList = nativeFolder.listFiles();
+
+ if (abiFilter != null) {
+ verbosePrintln("Native folder: %1$s with filter %2$ss", nativeFolder, abiFilter);
+ } else {
+ verbosePrintln("Native folder: %s", nativeFolder);
+ }
+
+ if (abiList != null) {
+ for (File abi : abiList) {
+ if (abi.isDirectory()) { // ignore files
+
+ // check the abi filter and reject all other ABIs
+ if (abiFilter != null && abiFilter.equals(abi.getName()) == false) {
+ continue;
+ }
+
+ File[] libs = abi.listFiles();
+ if (libs != null) {
+ for (File lib : libs) {
+ // only consider files that are .so or, if in debug mode, that
+ // are gdbserver executables
+ if (lib.isFile() &&
+ (PATTERN_NATIVELIB_EXT.matcher(lib.getName()).matches() ||
+ (mDebugMode &&
+ SdkConstants.FN_GDBSERVER.equals(
+ lib.getName())))) {
+ String path =
+ SdkConstants.FD_APK_NATIVE_LIBS + "/" +
+ abi.getName() + "/" + lib.getName();
+
+ try {
+ doAddFile(lib, path);
+ } catch (IOException e) {
+ throw new ApkCreationException(e, "Failed to add %s", lib);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Seals the APK, and signs it if necessary.
+ * @throws ApkCreationException
+ * @throws ApkCreationException if an error occurred
+ * @throws SealedApkException if the APK is already sealed.
+ */
+ public void sealApk() throws ApkCreationException, SealedApkException {
+ if (mIsSealed) {
+ throw new SealedApkException("APK is already sealed");
+ }
+
+ // close and sign the application package.
+ try {
+ mBuilder.close();
+ mIsSealed = true;
+ } catch (Exception e) {
+ throw new ApkCreationException(e, "Failed to seal APK");
+ }
+ }
+
+ /**
+ * Output a given message if the verbose mode is enabled.
+ * @param format the format string for {@link String#format(String, Object...)}
+ * @param args the string arguments
+ */
+ private void verbosePrintln(String format, Object... args) {
+ if (mVerboseStream != null) {
+ mVerboseStream.println(String.format(format, args));
+ }
+ }
+
+ private void doAddFile(File file, String archivePath) throws DuplicateFileException,
+ IOException {
+ verbosePrintln("%1$s => %2$s", file, archivePath);
+
+ File duplicate = checkFileForDuplicate(archivePath);
+ if (duplicate != null) {
+ throw new DuplicateFileException(archivePath, duplicate, file);
+ }
+
+ mAddedFiles.put(archivePath, file);
+ mBuilder.writeFile(file, archivePath);
+ }
+
+ /**
+ * Processes a {@link File} that could be a {@link ApkFile}, or a folder containing
+ * java resources.
+ * @param file the {@link File} to process.
+ * @param path the relative path of this file to the source folder. Can be <code>null</code> to
+ * identify a root file.
+ * @throws IOException
+ * @throws DuplicateFileException if a file conflicts with another already added to the APK
+ * at the same location inside the APK archive.
+ */
+ private void processFileForResource(File file, String path)
+ throws IOException, DuplicateFileException {
+ if (file.isDirectory()) {
+ // a directory? we check it
+ if (checkFolderForPackaging(file.getName())) {
+ // if it's valid, we append its name to the current path.
+ if (path == null) {
+ path = file.getName();
+ } else {
+ path = path + "/" + file.getName();
+ }
+
+ // and process its content.
+ File[] files = file.listFiles();
+ for (File contentFile : files) {
+ processFileForResource(contentFile, path);
+ }
+ }
+ } else {
+ // a file? we check it to make sure it should be added
+ if (checkFileForPackaging(file.getName())) {
+ // we append its name to the current path
+ if (path == null) {
+ path = file.getName();
+ } else {
+ path = path + "/" + file.getName();
+ }
+
+ // and add it to the apk
+ doAddFile(file, path);
+ }
+ }
+ }
+
+ /**
+ * Checks if the given path in the APK archive has not already been used and if it has been,
+ * then returns a {@link File} object for the source of the duplicate
+ * @param archivePath the archive path to test.
+ * @return A File object of either a file at the same location or an archive that contains a
+ * file that was put at the same location.
+ */
+ private File checkFileForDuplicate(String archivePath) {
+ return mAddedFiles.get(archivePath);
+ }
+
+ /**
+ * Checks an output {@link File} object.
+ * This checks the following:
+ * - the file is not an existing directory.
+ * - if the file exists, that it can be modified.
+ * - if it doesn't exists, that a new file can be created.
+ * @param file the File to check
+ * @throws ApkCreationException If the check fails
+ */
+ private void checkOutputFile(File file) throws ApkCreationException {
+ if (file.isDirectory()) {
+ throw new ApkCreationException("%s is a directory!", file);
+ }
+
+ if (file.exists()) { // will be a file in this case.
+ if (file.canWrite() == false) {
+ throw new ApkCreationException("Cannot write %s", file);
+ }
+ } else {
+ try {
+ if (file.createNewFile() == false) {
+ throw new ApkCreationException("Failed to create %s", file);
+ }
+ } catch (IOException e) {
+ throw new ApkCreationException(
+ "Failed to create '%1$ss': %2$s", file, e.getMessage());
+ }
+ }
+ }
+
+ /**
+ * Checks an input {@link File} object.
+ * This checks the following:
+ * - the file is not an existing directory.
+ * - that the file exists and can be read.
+ * @param file the File to check
+ * @throws ApkCreationException If the check fails
+ */
+ private void checkInputFile(File file) throws ApkCreationException {
+ if (file.isDirectory()) {
+ throw new ApkCreationException("%s is a directory!", file);
+ }
+
+ if (file.exists()) {
+ if (file.canRead() == false) {
+ throw new ApkCreationException("Cannot read %s", file);
+ }
+ } else {
+ throw new ApkCreationException("%s does not exist", file);
+ }
+ }
+
+ public static String getDebugKeystore() throws ApkCreationException {
+ try {
+ return DebugKeyProvider.getDefaultKeyStoreOsPath();
+ } catch (Exception e) {
+ throw new ApkCreationException(e, e.getMessage());
+ }
+ }
+
+ /**
+ * Checks whether a folder and its content is valid for packaging into the .apk as
+ * standard Java resource.
+ * @param folderName the name of the folder.
+ */
+ public static boolean checkFolderForPackaging(String folderName) {
+ return folderName.equalsIgnoreCase("CVS") == false &&
+ folderName.equalsIgnoreCase(".svn") == false &&
+ folderName.equalsIgnoreCase("SCCS") == false &&
+ folderName.equalsIgnoreCase("META-INF") == false &&
+ folderName.startsWith("_") == false;
+ }
+
+ /**
+ * Checks a file to make sure it should be packaged as standard resources.
+ * @param fileName the name of the file (including extension)
+ * @return true if the file should be packaged as standard java resources.
+ */
+ public static boolean checkFileForPackaging(String fileName) {
+ String[] fileSegments = fileName.split("\\.");
+ String fileExt = "";
+ if (fileSegments.length > 1) {
+ fileExt = fileSegments[fileSegments.length-1];
+ }
+
+ return checkFileForPackaging(fileName, fileExt);
+ }
+
+ /**
+ * Checks a file to make sure it should be packaged as standard resources.
+ * @param fileName the name of the file (including extension)
+ * @param extension the extension of the file (excluding '.')
+ * @return true if the file should be packaged as standard java resources.
+ */
+ public static boolean checkFileForPackaging(String fileName, String extension) {
+ // Note: this method is used by com.android.ide.eclipse.adt.internal.build.ApkBuilder
+ if (fileName.charAt(0) == '.') { // ignore hidden files.
+ return false;
+ }
+
+ return "aidl".equalsIgnoreCase(extension) == false && // Aidl files
+ "java".equalsIgnoreCase(extension) == false && // Java files
+ "class".equalsIgnoreCase(extension) == false && // Java class files
+ "scc".equalsIgnoreCase(extension) == false && // VisualSourceSafe
+ "swp".equalsIgnoreCase(extension) == false && // vi swap file
+ "package.html".equalsIgnoreCase(fileName) == false && // Javadoc
+ "overview.html".equalsIgnoreCase(fileName) == false && // Javadoc
+ ".cvsignore".equalsIgnoreCase(fileName) == false && // CVS
+ ".DS_Store".equals(fileName) == false && // Mac resources
+ fileName.charAt(fileName.length()-1) != '~'; // Backup files
+ }
+}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/build/ApkBuilderMain.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/build/ApkBuilderMain.java
index fb6ee6a..626e285 100644
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/build/ApkBuilderMain.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/build/ApkBuilderMain.java
@@ -16,14 +16,14 @@
package com.android.sdklib.build;
-import com.android.sdklib.internal.build.ApkBuilderHelper;
-import com.android.sdklib.internal.build.ApkBuilderHelper.ApkCreationException;
-import com.android.sdklib.internal.build.ApkBuilderHelper.ApkFile;
+import com.android.sdklib.build.ApkBuilder.ApkCreationException;
+import com.android.sdklib.build.ApkBuilder.DuplicateFileException;
+import com.android.sdklib.build.ApkBuilder.SealedApkException;
import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
+import java.io.FilenameFilter;
import java.util.ArrayList;
+import java.util.regex.Pattern;
/**
@@ -31,13 +31,8 @@ import java.util.ArrayList;
*/
public final class ApkBuilderMain {
- public final static class WrongOptionException extends Exception {
- private static final long serialVersionUID = 1L;
-
- public WrongOptionException(String message) {
- super(message);
- }
- }
+ private final static Pattern PATTERN_JAR_EXT = Pattern.compile("^.+\\.jar$",
+ Pattern.CASE_INSENSITIVE);
/**
* Main method. This is meant to be called from the command line through an exec.
@@ -49,98 +44,146 @@ public final class ApkBuilderMain {
printUsageAndQuit();
}
- try {
- ApkBuilderHelper helper = new ApkBuilderHelper();
+ System.err.println("\nTHIS TOOL IS DEPRECATED. See --help for more information.\n");
+ try {
+ File outApk = new File(args[0]);
- // read the first args that should be a file path
- File outFile = helper.getOutFile(args[0]);
+ File dexFile = null;
+ ArrayList<File> zipArchives = new ArrayList<File>();
+ ArrayList<File> sourceFolders = new ArrayList<File>();
+ ArrayList<File> jarFiles = new ArrayList<File>();
+ ArrayList<File> nativeFolders = new ArrayList<File>();
- ArrayList<FileInputStream> zipArchives = new ArrayList<FileInputStream>();
- ArrayList<File> archiveFiles = new ArrayList<File>();
- ArrayList<ApkFile> javaResources = new ArrayList<ApkFile>();
- ArrayList<FileInputStream> resourcesJars = new ArrayList<FileInputStream>();
- ArrayList<ApkFile> nativeLibraries = new ArrayList<ApkFile>();
+ boolean verbose = false;
+ boolean signed = true;
+ boolean debug = false;
int index = 1;
do {
String argument = args[index++];
if ("-v".equals(argument)) {
- helper.setVerbose(true);
+ verbose = true;
+
} else if ("-d".equals(argument)) {
- helper.setDebugMode(true);
+ debug = true;
+
} else if ("-u".equals(argument)) {
- helper.setSignedPackage(false);
+ signed = false;
+
} else if ("-z".equals(argument)) {
// quick check on the next argument.
if (index == args.length) {
printAndExit("Missing value for -z");
}
- try {
- FileInputStream input = new FileInputStream(args[index++]);
- zipArchives.add(input);
- } catch (FileNotFoundException e) {
- throw new ApkCreationException("-z file is not found");
- }
+ zipArchives.add(new File(args[index++]));
} else if ("-f". equals(argument)) {
+ if (dexFile != null) {
+ // can't have more than one dex file.
+ printAndExit("Can't have more than one dex file (-f)");
+ }
// quick check on the next argument.
if (index == args.length) {
printAndExit("Missing value for -f");
}
- archiveFiles.add(ApkBuilderHelper.getInputFile(args[index++]));
+ dexFile = new File(args[index++]);
} else if ("-rf". equals(argument)) {
// quick check on the next argument.
if (index == args.length) {
printAndExit("Missing value for -rf");
}
- ApkBuilderHelper.processSourceFolderForResource(
- new File(args[index++]), javaResources);
+ sourceFolders.add(new File(args[index++]));
} else if ("-rj". equals(argument)) {
// quick check on the next argument.
if (index == args.length) {
printAndExit("Missing value for -rj");
}
- ApkBuilderHelper.processJar(new File(args[index++]), resourcesJars);
+ jarFiles.add(new File(args[index++]));
} else if ("-nf".equals(argument)) {
// quick check on the next argument.
if (index == args.length) {
printAndExit("Missing value for -nf");
}
- ApkBuilderHelper.processNativeFolder(new File(args[index++]),
- helper.getDebugMode(), nativeLibraries,
- helper.isVerbose(), null /*abiFilter*/);
+ nativeFolders.add(new File(args[index++]));
} else if ("-storetype".equals(argument)) {
// quick check on the next argument.
if (index == args.length) {
printAndExit("Missing value for -storetype");
}
- helper.setStoreType(args[index++]);
+ // FIXME
} else {
printAndExit("Unknown argument: " + argument);
}
} while (index < args.length);
- helper.createPackage(outFile, zipArchives, archiveFiles, javaResources, resourcesJars,
- nativeLibraries);
+ if (zipArchives.size() == 0) {
+ printAndExit("No zip archive, there must be one for the resources");
+ }
+
+ // create the builder with the basic files.
+ ApkBuilder builder = new ApkBuilder(outApk, zipArchives.get(0), dexFile,
+ signed ? ApkBuilder.getDebugKeystore() : null,
+ verbose ? System.out : null);
+ builder.setDebugMode(debug);
+
+ // add the rest of the files.
+ // first zip Archive was used in the constructor.
+ for (int i = 1 ; i < zipArchives.size() ; i++) {
+ builder.addZipFile(zipArchives.get(i));
+ }
+
+ for (File sourceFolder : sourceFolders) {
+ builder.addSourceFolder(sourceFolder);
+ }
+
+ for (File jarFile : jarFiles) {
+ if (jarFile.isDirectory()) {
+ String[] filenames = jarFile.list(new FilenameFilter() {
+ public boolean accept(File dir, String name) {
+ return PATTERN_JAR_EXT.matcher(name).matches();
+ }
+ });
+
+ for (String filename : filenames) {
+ builder.addResourcesFromJar(new File(jarFile, filename));
+ }
+ } else {
+ builder.addResourcesFromJar(jarFile);
+ }
+ }
+
+ for (File nativeFolder : nativeFolders) {
+ builder.addNativeLibraries(nativeFolder, null /*abiFilter*/);
+ }
+
+ // seal the apk
+ builder.sealApk();
+
- } catch (FileNotFoundException e) {
- printAndExit(e.getMessage());
} catch (ApkCreationException e) {
printAndExit(e.getMessage());
+ } catch (DuplicateFileException e) {
+ printAndExit(String.format(
+ "Found duplicate file for APK: %1$s\nOrigin 1: %2$s\nOrigin 2: %3$s",
+ e.getArchivePath(), e.getFile1(), e.getFile2()));
+ } catch (SealedApkException e) {
+ printAndExit(e.getMessage());
+ } catch (Exception e) {
+ e.printStackTrace();
}
}
private static void printUsageAndQuit() {
// 80 cols marker: 01234567890123456789012345678901234567890123456789012345678901234567890123456789
System.err.println("\n\n<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<");
- System.err.println("THIS TOOL IS DEPRECATED!\n");
+ System.err.println("THIS TOOL IS DEPRECATED and may stop working at any time!\n");
System.err.println("If you wish to use apkbuilder for a custom build system, please look at the");
System.err.println("com.android.sdklib.build.ApkBuilder which provides support for");
System.err.println("recent build improvements including library projects.");
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/build/ApkBuilderHelper.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/build/ApkBuilderHelper.java
deleted file mode 100644
index ba1c878..0000000
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/build/ApkBuilderHelper.java
+++ /dev/null
@@ -1,433 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.sdklib.internal.build;
-
-import com.android.prefs.AndroidLocation.AndroidLocationException;
-import com.android.sdklib.internal.build.DebugKeyProvider.KeytoolException;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.FilenameFilter;
-import java.io.IOException;
-import java.security.PrivateKey;
-import java.security.cert.X509Certificate;
-import java.text.DateFormat;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Date;
-import java.util.regex.Pattern;
-
-/**
- * Command line APK builder with signing support.
- */
-public final class ApkBuilderHelper {
-
- private final static Pattern PATTERN_JAR_EXT = Pattern.compile("^.+\\.jar$",
- Pattern.CASE_INSENSITIVE);
- private final static Pattern PATTERN_NATIVELIB_EXT = Pattern.compile("^.+\\.so$",
- Pattern.CASE_INSENSITIVE);
-
- private final static String NATIVE_LIB_ROOT = "lib/";
- private final static String GDBSERVER_NAME = "gdbserver";
-
- public final static class ApkCreationException extends Exception {
- private static final long serialVersionUID = 1L;
-
- public ApkCreationException(String message) {
- super(message);
- }
-
- public ApkCreationException(Throwable throwable) {
- super(throwable);
- }
- }
-
-
- /**
- * A File to be added to the APK archive.
- * <p/>This includes the {@link File} representing the file and its path in the archive.
- */
- public final static class ApkFile {
- String archivePath;
- File file;
-
- ApkFile(File file, String path) {
- this.file = file;
- this.archivePath = path;
- }
- }
-
- private JavaResourceFilter mResourceFilter = new JavaResourceFilter();
- private boolean mVerbose = false;
- private boolean mSignedPackage = true;
- private boolean mDebugMode = false;
- /** the optional type of the debug keystore. If <code>null</code>, the default */
- private String mStoreType = null;
-
- public void setVerbose(boolean verbose) {
- mVerbose = verbose;
- }
-
- public boolean isVerbose() {
- return mVerbose;
- }
-
- public void setSignedPackage(boolean signedPackage) {
- mSignedPackage = signedPackage;
- }
-
- public void setDebugMode(boolean debugMode) {
- mDebugMode = debugMode;
- }
-
- public boolean getDebugMode() {
- return mDebugMode;
- }
-
- /**
- * Set the type of the keystore. <code>null</code> means the default.
- * @param storeType
- */
- public void setStoreType(String storeType) {
- mStoreType = storeType;
- }
-
- /**
- * Returns the store type. <code>null</code> means default.
- * @return
- */
- public String getStoreType() {
- return mStoreType;
- }
-
- public File getOutFile(String filepath) throws ApkCreationException {
- File f = new File(filepath);
-
- if (f.isDirectory()) {
- throw new ApkCreationException(filepath + " is a directory!");
- }
-
- if (f.exists()) { // will be a file in this case.
- if (f.canWrite() == false) {
- throw new ApkCreationException("Cannot write " + filepath);
- }
- } else {
- try {
- if (f.createNewFile() == false) {
- throw new ApkCreationException("Failed to create " + filepath);
- }
- } catch (IOException e) {
- throw new ApkCreationException(
- "Failed to create '" + filepath + "' : " + e.getMessage());
- }
- }
-
- return f;
- }
-
- /**
- * Returns a {@link File} representing a given file path. The path must represent
- * an actual existing file (not a directory). The path may be relative.
- * @param filepath the path to a file.
- * @return the File representing the path.
- * @throws ApkCreationException if the path represents a directory or if the file does not
- * exist, or cannot be read.
- */
- public static File getInputFile(String filepath) throws ApkCreationException {
- File f = new File(filepath);
-
- if (f.isDirectory()) {
- throw new ApkCreationException(filepath + " is a directory!");
- }
-
- if (f.exists()) {
- if (f.canRead() == false) {
- throw new ApkCreationException("Cannot read " + filepath);
- }
- } else {
- throw new ApkCreationException(filepath + " does not exists!");
- }
-
- return f;
- }
-
- /**
- * Processes a source folder and adds its java resources to a given list of {@link ApkFile}.
- * @param folder the folder representing the source folder.
- * @param javaResources the list of {@link ApkFile} to fill.
- * @throws ApkCreationException
- */
- public static void processSourceFolderForResource(File folder,
- ArrayList<ApkFile> javaResources) throws ApkCreationException {
- if (folder.isDirectory()) {
- // file is a directory, process its content.
- File[] files = folder.listFiles();
- for (File file : files) {
- processFileForResource(file, null, javaResources);
- }
- } else {
- // not a directory? output error and quit.
- if (folder.exists()) {
- throw new ApkCreationException(folder.getAbsolutePath() + " is not a folder!");
- } else {
- throw new ApkCreationException(folder.getAbsolutePath() + " does not exist!");
- }
- }
- }
-
- /**
- * Process a jar file or a jar folder
- * @param file the {@link File} to process
- * @param resourcesJars the collection of FileInputStream to fill up with jar files.
- * @throws FileNotFoundException
- * @throws ApkCreationException
- */
- public static void processJar(File file, Collection<FileInputStream> resourcesJars)
- throws FileNotFoundException, ApkCreationException {
- if (file.isDirectory()) {
- String[] filenames = file.list(new FilenameFilter() {
- public boolean accept(File dir, String name) {
- return PATTERN_JAR_EXT.matcher(name).matches();
- }
- });
-
- for (String filename : filenames) {
- File f = new File(file, filename);
- processJarFile(f, resourcesJars);
- }
- } else if (file.isFile()) {
- processJarFile(file, resourcesJars);
- } else {
- throw new ApkCreationException(file.getAbsolutePath()+ " does not exist!");
- }
- }
-
- public static void processJarFile(File file, Collection<FileInputStream> resourcesJars)
- throws FileNotFoundException {
- FileInputStream input = new FileInputStream(file);
- resourcesJars.add(input);
- }
-
- /**
- * Processes a {@link File} that could be a {@link ApkFile}, or a folder containing
- * java resources.
- * @param file the {@link File} to process.
- * @param path the relative path of this file to the source folder. Can be <code>null</code> to
- * identify a root file.
- * @param javaResources the Collection of {@link ApkFile} object to fill.
- */
- private static void processFileForResource(File file, String path,
- Collection<ApkFile> javaResources) {
- if (file.isDirectory()) {
- // a directory? we check it
- if (JavaResourceFilter.checkFolderForPackaging(file.getName())) {
- // if it's valid, we append its name to the current path.
- if (path == null) {
- path = file.getName();
- } else {
- path = path + "/" + file.getName();
- }
-
- // and process its content.
- File[] files = file.listFiles();
- for (File contentFile : files) {
- processFileForResource(contentFile, path, javaResources);
- }
- }
- } else {
- // a file? we check it
- if (JavaResourceFilter.checkFileForPackaging(file.getName())) {
- // we append its name to the current path
- if (path == null) {
- path = file.getName();
- } else {
- path = path + "/" + file.getName();
- }
-
- // and add it to the list.
- javaResources.add(new ApkFile(file, path));
- }
- }
- }
-
- /**
- * Process a {@link File} for native library inclusion.
- * <p/>The root folder must include folders that include .so files.
- * @param root the native root folder.
- * @param nativeLibraries the collection to add native libraries to.
- * @param verbose verbose mode.
- * @param abiFilter optional ABI filter. If non-null only the given ABI is included.
- * @throws ApkCreationException
- */
- public static void processNativeFolder(File root, boolean debugMode,
- Collection<ApkFile> nativeLibraries, boolean verbose, String abiFilter)
- throws ApkCreationException {
- if (root.isDirectory() == false) {
- throw new ApkCreationException(root.getAbsolutePath() + " is not a folder!");
- }
-
- File[] abiList = root.listFiles();
-
- if (verbose) {
- System.out.println("Processing native folder: " + root.getAbsolutePath());
- if (abiFilter != null) {
- System.out.println("ABI Filter: " + abiFilter);
- }
- }
-
- if (abiList != null) {
- for (File abi : abiList) {
- if (abi.isDirectory()) { // ignore files
-
- // check the abi filter and reject all other ABIs
- if (abiFilter != null && abiFilter.equals(abi.getName()) == false) {
- if (verbose) {
- System.out.println("Rejecting ABI " + abi.getName());
- }
- continue;
- }
-
- File[] libs = abi.listFiles();
- if (libs != null) {
- for (File lib : libs) {
- // only consider files that are .so or, if in debug mode, that
- // are gdbserver executables
- if (lib.isFile() &&
- (PATTERN_NATIVELIB_EXT.matcher(lib.getName()).matches() ||
- (debugMode && GDBSERVER_NAME.equals(lib.getName())))) {
- String path =
- NATIVE_LIB_ROOT + abi.getName() + "/" + lib.getName();
-
- nativeLibraries.add(new ApkFile(lib, path));
- }
- }
- }
- }
- }
- }
- }
-
- /**
- * Creates the application package
- * @param outFile the package file to create
- * @param zipArchives the list of zip archive
- * @param files the list of files to include in the archive
- * @param javaResources the list of java resources from the source folders.
- * @param resourcesJars the list of jar files from which to take java resources
- * @throws ApkCreationException
- */
- public void createPackage(File outFile, Iterable<? extends FileInputStream> zipArchives,
- Iterable<? extends File> files, Iterable<? extends ApkFile> javaResources,
- Iterable<? extends FileInputStream> resourcesJars,
- Iterable<? extends ApkFile> nativeLibraries) throws ApkCreationException {
-
- // get the debug key
- try {
- SignedJarBuilder builder;
-
- if (mSignedPackage) {
- System.err.println(String.format("Using keystore: %s",
- DebugKeyProvider.getDefaultKeyStoreOsPath()));
-
-
- DebugKeyProvider keyProvider = new DebugKeyProvider(
- null /* osKeyPath: use default */,
- mStoreType, null /* IKeyGenOutput */);
- PrivateKey key = keyProvider.getDebugKey();
- X509Certificate certificate = (X509Certificate)keyProvider.getCertificate();
-
- if (key == null) {
- throw new ApkCreationException("Unable to get debug signature key");
- }
-
- // compare the certificate expiration date
- if (certificate != null && certificate.getNotAfter().compareTo(new Date()) < 0) {
- // TODO, regenerate a new one.
- throw new ApkCreationException("Debug Certificate expired on " +
- DateFormat.getInstance().format(certificate.getNotAfter()));
- }
-
- builder = new SignedJarBuilder(
- new FileOutputStream(outFile.getAbsolutePath(), false /* append */), key,
- certificate);
- } else {
- builder = new SignedJarBuilder(
- new FileOutputStream(outFile.getAbsolutePath(), false /* append */),
- null /* key */, null /* certificate */);
- }
-
- // add the archives
- for (FileInputStream input : zipArchives) {
- builder.writeZip(input, null /* filter */);
- }
-
- // add the single files
- for (File input : files) {
- // always put the file at the root of the archive in this case
- builder.writeFile(input, input.getName());
- if (mVerbose) {
- System.err.println(String.format("%1$s => %2$s", input.getAbsolutePath(),
- input.getName()));
- }
- }
-
- // add the java resource from the source folders.
- for (ApkFile resource : javaResources) {
- builder.writeFile(resource.file, resource.archivePath);
- if (mVerbose) {
- System.err.println(String.format("%1$s => %2$s",
- resource.file.getAbsolutePath(), resource.archivePath));
- }
- }
-
- // add the java resource from jar files.
- for (FileInputStream input : resourcesJars) {
- builder.writeZip(input, mResourceFilter);
- }
-
- // add the native files
- for (ApkFile file : nativeLibraries) {
- builder.writeFile(file.file, file.archivePath);
- if (mVerbose) {
- System.err.println(String.format("%1$s => %2$s", file.file.getAbsolutePath(),
- file.archivePath));
- }
- }
-
- // close and sign the application package.
- builder.close();
- } catch (KeytoolException e) {
- if (e.getJavaHome() == null) {
- throw new ApkCreationException(e.getMessage() +
- "\nJAVA_HOME seems undefined, setting it will help locating keytool automatically\n" +
- "You can also manually execute the following command\n:" +
- e.getCommandLine());
- } else {
- throw new ApkCreationException(e.getMessage() +
- "\nJAVA_HOME is set to: " + e.getJavaHome() +
- "\nUpdate it if necessary, or manually execute the following command:\n" +
- e.getCommandLine());
- }
- } catch (AndroidLocationException e) {
- throw new ApkCreationException(e);
- } catch (Exception e) {
- throw new ApkCreationException(e);
- }
- }
-}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/build/JavaResourceFilter.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/build/JavaResourceFilter.java
deleted file mode 100644
index aac1e55..0000000
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/build/JavaResourceFilter.java
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.sdklib.internal.build;
-
-import com.android.sdklib.internal.build.SignedJarBuilder.IZipEntryFilter;
-
-/**
- * A basic implementation of {@link IZipEntryFilter} to filter out anything that is not a
- * java resource.
- */
-public class JavaResourceFilter implements IZipEntryFilter {
-
- public boolean checkEntry(String name) {
- // split the path into segments.
- String[] segments = name.split("/");
-
- // empty path? skip to next entry.
- if (segments.length == 0) {
- return false;
- }
-
- // Check each folders to make sure they should be included.
- // Folders like CVS, .svn, etc.. should already have been excluded from the
- // jar file, but we need to exclude some other folder (like /META-INF) so
- // we check anyway.
- for (int i = 0 ; i < segments.length - 1; i++) {
- if (checkFolderForPackaging(segments[i]) == false) {
- return false;
- }
- }
-
- // get the file name from the path
- String fileName = segments[segments.length-1];
-
- return checkFileForPackaging(fileName);
- }
-
- /**
- * Checks whether a folder and its content is valid for packaging into the .apk as
- * standard Java resource.
- * @param folderName the name of the folder.
- */
- public static boolean checkFolderForPackaging(String folderName) {
- return folderName.equals("CVS") == false &&
- folderName.equals(".svn") == false &&
- folderName.equals("SCCS") == false &&
- folderName.equals("META-INF") == false &&
- folderName.startsWith("_") == false;
- }
-
- /**
- * Checks a file to make sure it should be packaged as standard resources.
- * @param fileName the name of the file (including extension)
- * @return true if the file should be packaged as standard java resources.
- */
- public static boolean checkFileForPackaging(String fileName) {
- String[] fileSegments = fileName.split("\\.");
- String fileExt = "";
- if (fileSegments.length > 1) {
- fileExt = fileSegments[fileSegments.length-1];
- }
-
- return checkFileForPackaging(fileName, fileExt);
- }
-
- /**
- * Checks a file to make sure it should be packaged as standard resources.
- * @param fileName the name of the file (including extension)
- * @param extension the extension of the file (excluding '.')
- * @return true if the file should be packaged as standard java resources.
- */
- public static boolean checkFileForPackaging(String fileName, String extension) {
- // Note: this method is used by com.android.ide.eclipse.adt.internal.build.ApkBuilder
- if (fileName.charAt(0) == '.') { // ignore hidden files.
- return false;
- }
-
- return "aidl".equalsIgnoreCase(extension) == false && // Aidl files
- "java".equalsIgnoreCase(extension) == false && // Java files
- "class".equalsIgnoreCase(extension) == false && // Java class files
- "scc".equalsIgnoreCase(extension) == false && // VisualSourceSafe
- "swp".equalsIgnoreCase(extension) == false && // vi swap file
- "package.html".equalsIgnoreCase(fileName) == false && // Javadoc
- "overview.html".equalsIgnoreCase(fileName) == false && // Javadoc
- ".cvsignore".equalsIgnoreCase(fileName) == false && // CVS
- ".DS_Store".equals(fileName) == false && // Mac resources
- fileName.charAt(fileName.length()-1) != '~'; // Backup files
- }
-}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/build/SignedJarBuilder.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/build/SignedJarBuilder.java
index 79e4be2..81131bc 100644
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/build/SignedJarBuilder.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/build/SignedJarBuilder.java
@@ -16,6 +16,8 @@
package com.android.sdklib.internal.build;
+import com.android.sdklib.internal.build.SignedJarBuilder.IZipEntryFilter.ZipAbortException;
+
import sun.misc.BASE64Encoder;
import sun.security.pkcs.ContentInfo;
import sun.security.pkcs.PKCS7;
@@ -100,14 +102,42 @@ public class SignedJarBuilder {
* be added to a Jar file.
*/
public interface IZipEntryFilter {
+
+ /**
+ * An exception thrown during packaging of a zip file into APK file.
+ * This is typically thrown by implementations of
+ * {@link IZipEntryFilter#checkEntry(String)}.
+ */
+ public static class ZipAbortException extends Exception {
+ private static final long serialVersionUID = 1L;
+
+ public ZipAbortException() {
+ super();
+ }
+
+ public ZipAbortException(String format, Object... args) {
+ super(String.format(format, args));
+ }
+
+ public ZipAbortException(Throwable cause, String format, Object... args) {
+ super(String.format(format, args), cause);
+ }
+
+ public ZipAbortException(Throwable cause) {
+ super(cause);
+ }
+ }
+
+
/**
* Checks a file for inclusion in a Jar archive.
- * @param name the archive file path of the entry
+ * @param archivePath the archive file path of the entry
* @return <code>true</code> if the file should be included.
+ * @throws ZipAbortException if writing the file should be aborted.
*/
- public boolean checkEntry(String name);
+ public boolean checkEntry(String archivePath) throws ZipAbortException;
}
-
+
/**
* Creates a {@link SignedJarBuilder} with a given output stream, and signing information.
* <p/>If either <code>key</code> or <code>certificate</code> is <code>null</code> then
@@ -125,18 +155,18 @@ public class SignedJarBuilder {
mOutputJar.setLevel(9);
mKey = key;
mCertificate = certificate;
-
+
if (mKey != null && mCertificate != null) {
mManifest = new Manifest();
Attributes main = mManifest.getMainAttributes();
main.putValue("Manifest-Version", "1.0");
main.putValue("Created-By", "1.0 (Android)");
-
+
mBase64Encoder = new BASE64Encoder();
mMessageDigest = MessageDigest.getInstance(DIGEST_ALGORITHM);
}
}
-
+
/**
* Writes a new {@link File} into the archive.
* @param inputFile the {@link File} to write.
@@ -147,7 +177,7 @@ public class SignedJarBuilder {
// Get an input stream on the file.
FileInputStream fis = new FileInputStream(inputFile);
try {
-
+
// create the zip entry
JarEntry entry = new JarEntry(jarPath);
entry.setTime(inputFile.lastModified());
@@ -166,8 +196,11 @@ public class SignedJarBuilder {
* @param input the {@link InputStream} for the Jar/Zip to copy.
* @param filter the filter or <code>null</code>
* @throws IOException
+ * @throws ZipAbortException if the {@link IZipEntryFilter} filter indicated that the write
+ * must be aborted.
*/
- public void writeZip(InputStream input, IZipEntryFilter filter) throws IOException {
+ public void writeZip(InputStream input, IZipEntryFilter filter)
+ throws IOException, ZipAbortException {
ZipInputStream zis = new ZipInputStream(input);
try {
@@ -175,19 +208,19 @@ public class SignedJarBuilder {
ZipEntry entry;
while ((entry = zis.getNextEntry()) != null) {
String name = entry.getName();
-
+
// do not take directories or anything inside a potential META-INF folder.
if (entry.isDirectory() || name.startsWith("META-INF/")) {
continue;
}
-
+
// if we have a filter, we check the entry against it
if (filter != null && filter.checkEntry(name) == false) {
continue;
}
-
+
JarEntry newEntry;
-
+
// Preserve the STORED method of the input entry.
if (entry.getMethod() == JarEntry.STORED) {
newEntry = new JarEntry(entry);
@@ -195,9 +228,9 @@ public class SignedJarBuilder {
// Create a new entry so that the compressed len is recomputed.
newEntry = new JarEntry(name);
}
-
+
writeEntry(zis, newEntry);
-
+
zis.closeEntry();
}
} finally {
@@ -206,7 +239,7 @@ public class SignedJarBuilder {
}
/**
- * Closes the Jar archive by creating the manifest, and signing the archive.
+ * Closes the Jar archive by creating the manifest, and signing the archive.
* @throws IOException
* @throws GeneralSecurityException
*/
@@ -215,21 +248,21 @@ public class SignedJarBuilder {
// write the manifest to the jar file
mOutputJar.putNextEntry(new JarEntry(JarFile.MANIFEST_NAME));
mManifest.write(mOutputJar);
-
+
// CERT.SF
Signature signature = Signature.getInstance("SHA1with" + mKey.getAlgorithm());
signature.initSign(mKey);
mOutputJar.putNextEntry(new JarEntry("META-INF/CERT.SF"));
writeSignatureFile(new SignatureOutputStream(mOutputJar, signature));
-
+
// CERT.*
mOutputJar.putNextEntry(new JarEntry("META-INF/CERT." + mKey.getAlgorithm()));
writeSignatureBlock(signature, mCertificate, mKey);
}
-
+
mOutputJar.close();
}
-
+
/**
* Adds an entry to the output jar, and write its content from the {@link InputStream}
* @param input The input stream from where to write the entry content.
@@ -241,10 +274,10 @@ public class SignedJarBuilder {
mOutputJar.putNextEntry(entry);
// read the content of the entry from the input stream, and write it into the archive.
- int count;
+ int count;
while ((count = input.read(mBuffer)) != -1) {
mOutputJar.write(mBuffer, 0, count);
-
+
// update the digest
if (mMessageDigest != null) {
mMessageDigest.update(mBuffer, 0, count);
@@ -253,7 +286,7 @@ public class SignedJarBuilder {
// close the entry for this file
mOutputJar.closeEntry();
-
+
if (mManifest != null) {
// update the manifest for this entry.
Attributes attr = mManifest.getAttributes(entry.getName());
@@ -264,7 +297,7 @@ public class SignedJarBuilder {
attr.putValue(DIGEST_ATTR, mBase64Encoder.encode(mMessageDigest.digest()));
}
}
-
+
/** Writes a .SF file with a digest to the manifest. */
private void writeSignatureFile(OutputStream out)
throws IOException, GeneralSecurityException {