aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtPlugin.java115
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AndroidPrintStream.java88
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/actions/MultiApkExportAction.java122
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/AaptParser.java35
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/PostCompilerBuilder.java175
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/PostCompilerHelper.java441
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/pages/OverviewExportPart.java16
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/ExportHelper.java246
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/Sdk.java1
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/actions/ExportAction.java2
-rw-r--r--sdkmanager/libs/sdklib/src/com/android/sdklib/build/ApkBuilder.java263
-rw-r--r--testapps/.gitignore3
-rw-r--r--testapps/README.txt6
-rw-r--r--testapps/basicProject/.classpath7
-rw-r--r--testapps/basicProject/.project33
-rw-r--r--testapps/basicProject/AndroidManifest.xml16
-rw-r--r--testapps/basicProject/build.properties17
-rw-r--r--testapps/basicProject/build.xml84
-rw-r--r--testapps/basicProject/default.properties11
-rw-r--r--testapps/basicProject/res/drawable-hdpi/icon.pngbin0 -> 4147 bytes
-rw-r--r--testapps/basicProject/res/drawable-ldpi/icon.pngbin0 -> 1723 bytes
-rw-r--r--testapps/basicProject/res/drawable-mdpi/icon.pngbin0 -> 2574 bytes
-rw-r--r--testapps/basicProject/res/layout/main.xml13
-rw-r--r--testapps/basicProject/res/values/strings.xml4
-rw-r--r--testapps/basicProject/src/com/android/tests/basicproject/Main.java15
25 files changed, 1119 insertions, 594 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtPlugin.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtPlugin.java
index 41771f1..77ba50e 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtPlugin.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtPlugin.java
@@ -31,9 +31,7 @@ import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs;
import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs.BuildVerbosity;
import com.android.ide.eclipse.adt.internal.project.AndroidClasspathContainerInitializer;
import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper;
-import com.android.ide.eclipse.adt.internal.project.ExportHelper;
import com.android.ide.eclipse.adt.internal.project.ProjectHelper;
-import com.android.ide.eclipse.adt.internal.project.ExportHelper.IExportCallback;
import com.android.ide.eclipse.adt.internal.resources.manager.GlobalProjectMonitor;
import com.android.ide.eclipse.adt.internal.resources.manager.ProjectResources;
import com.android.ide.eclipse.adt.internal.resources.manager.ResourceFolder;
@@ -44,7 +42,6 @@ import com.android.ide.eclipse.adt.internal.sdk.LoadStatus;
import com.android.ide.eclipse.adt.internal.sdk.Sdk;
import com.android.ide.eclipse.adt.internal.sdk.Sdk.ITargetChangeListener;
import com.android.ide.eclipse.adt.internal.ui.EclipseUiHelper;
-import com.android.ide.eclipse.adt.internal.wizards.export.ExportWizard;
import com.android.ide.eclipse.ddms.DdmsPlugin;
import com.android.ide.eclipse.hierarchyviewer.HierarchyViewerPlugin;
import com.android.sdklib.IAndroidTarget;
@@ -73,8 +70,6 @@ import org.eclipse.core.runtime.jobs.JobChangeAdapter;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.resource.ImageDescriptor;
-import org.eclipse.jface.viewers.StructuredSelection;
-import org.eclipse.jface.wizard.WizardDialog;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.widgets.Display;
@@ -105,12 +100,10 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
-import java.io.PrintStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Calendar;
import java.util.List;
/**
@@ -156,44 +149,6 @@ public class AdtPlugin extends AbstractUIPlugin {
protected boolean mSdkIsLoading;
/**
- * Custom PrintStream for Dx output. This class overrides the method
- * <code>println()</code> and adds the standard output tag with the
- * date and the project name in front of every messages.
- */
- private static final class AndroidPrintStream extends PrintStream {
- private IProject mProject;
- private String mPrefix;
-
- /**
- * Default constructor with project and output stream.
- * The project is used to get the project name for the output tag.
- *
- * @param project The Project
- * @param prefix A prefix to be printed before the actual message. Can be null
- * @param stream The Stream
- */
- public AndroidPrintStream(IProject project, String prefix, OutputStream stream) {
- super(stream);
- mProject = project;
- }
-
- @Override
- public void println(String message) {
- // write the date/project tag first.
- String tag = getMessageTag(mProject != null ? mProject.getName() : null);
-
- print(tag);
- print(' ');
- if (mPrefix != null) {
- print(mPrefix);
- }
-
- // then write the regular message
- super.println(message);
- }
- }
-
- /**
* An error handler for checkSdkLocationAndId() that will handle the generated error
* or warning message. Each method must return a boolean that will in turn be returned by
* checkSdkLocationAndId.
@@ -346,19 +301,6 @@ public class AdtPlugin extends AbstractUIPlugin {
}
});
- // setup export callback for editors
- ExportHelper.setCallback(new IExportCallback() {
- public void startExportWizard(IProject project) {
- StructuredSelection selection = new StructuredSelection(project);
-
- ExportWizard wizard = new ExportWizard();
- wizard.init(PlatformUI.getWorkbench(), selection);
- WizardDialog dialog = new WizardDialog(getDisplay().getActiveShell(),
- wizard);
- dialog.open();
- }
- });
-
// initialize editors
startEditors();
@@ -805,40 +747,6 @@ public class AdtPlugin extends AbstractUIPlugin {
}
/**
- * Returns an standard PrintStream object for a specific project.<br>
- * This PrintStream will add a date/project at the beginning of every
- * <code>println()</code> output.
- *
- * @param project The project object
- * @param prefix The prefix to be added to the message. Can be null.
- * @return a new PrintStream
- */
- public static synchronized PrintStream getOutPrintStream(IProject project, String prefix) {
- if (sPlugin != null) {
- return new AndroidPrintStream(project, prefix, sPlugin.mAndroidConsoleStream);
- }
-
- return null;
- }
-
- /**
- * Returns an error PrintStream object for a specific project.<br>
- * This PrintStream will add a date/project at the beginning of every
- * <code>println()</code> output.
- *
- * @param project The project object
- * @param prefix The prefix to be added to the message. Can be null.
- * @return a new PrintStream
- */
- public static synchronized PrintStream getErrPrintStream(IProject project, String prefix) {
- if (sPlugin != null) {
- return new AndroidPrintStream(project, prefix, sPlugin.mAndroidConsoleErrorStream);
- }
-
- return null;
- }
-
- /**
* Returns whether the {@link IAndroidTarget}s have been loaded from the SDK.
*/
public final LoadStatus getSdkLoadStatus() {
@@ -1365,6 +1273,10 @@ public class AdtPlugin extends AbstractUIPlugin {
});
}
+ public static synchronized OutputStream getOutStream() {
+ return sPlugin.mAndroidConsoleStream;
+ }
+
public static synchronized OutputStream getErrorStream() {
return sPlugin.mAndroidConsoleErrorStream;
}
@@ -1409,7 +1321,7 @@ public class AdtPlugin extends AbstractUIPlugin {
*/
public static synchronized void printToStream(MessageConsoleStream stream, String tag,
Object... objects) {
- String dateTag = getMessageTag(tag);
+ String dateTag = AndroidPrintStream.getMessageTag(tag);
for (Object obj : objects) {
stream.print(dateTag);
@@ -1423,21 +1335,4 @@ public class AdtPlugin extends AbstractUIPlugin {
}
}
}
-
- /**
- * Creates a string containing the current date/time, and the tag.
- * The tag does not end with a whitespace.
- * @param tag The tag associated to the message. Can be null
- * @return The dateTag
- */
- public static String getMessageTag(String tag) {
- Calendar c = Calendar.getInstance();
-
- if (tag == null) {
- return String.format(Messages.Console_Date_Tag, c);
- }
-
- return String.format(Messages.Console_Data_Project_Tag, c, tag);
- }
-
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AndroidPrintStream.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AndroidPrintStream.java
new file mode 100644
index 0000000..5323742
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AndroidPrintStream.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
+ *
+ * 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.ide.eclipse.adt;
+
+import org.eclipse.core.resources.IProject;
+
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.util.Calendar;
+
+/**
+ * Custom PrintStream allowing to precede the message with a tag containing data/project info.
+ *
+ * Additionally, a prefix can be set (and removed) at runtime.
+ *
+ * Only {@link #println()} is supported.
+ */
+public class AndroidPrintStream extends PrintStream {
+ private IProject mProject;
+ private String mPrefix;
+
+ /**
+ * Default constructor with project and output stream.
+ * The project is used to get the project name for the output tag.
+ *
+ * @param project The Project
+ * @param prefix A prefix to be printed before the actual message. Can be null
+ * @param stream The Stream
+ */
+ public AndroidPrintStream(IProject project, String prefix, OutputStream stream) {
+ super(stream);
+ mProject = project;
+ }
+
+ /**
+ * Updates the value of the prefix.
+ * @param prefix
+ */
+ public void setPrefix(String prefix) {
+ mPrefix = prefix;
+ }
+
+ @Override
+ public void println(String message) {
+ // write the date/project tag first.
+ String tag = getMessageTag(mProject != null ? mProject.getName() : null);
+
+ print(tag);
+ print(": ");
+ if (mPrefix != null) {
+ print(mPrefix);
+ }
+
+ // then write the regular message
+ super.println(message);
+ }
+
+ /**
+ * Creates a string containing the current date/time, and the tag.
+ * The tag does not end with a whitespace.
+ * @param tag The tag associated to the message. Can be null
+ * @return The dateTag
+ */
+ public static String getMessageTag(String tag) {
+ Calendar c = Calendar.getInstance();
+
+ if (tag == null) {
+ return String.format(Messages.Console_Date_Tag, c);
+ }
+
+ return String.format(Messages.Console_Data_Project_Tag, c, tag);
+ }
+
+}
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 6ff6d0b..70e886d 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,6 +17,7 @@
package com.android.ide.eclipse.adt.internal.actions;
import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.AndroidPrintStream;
import com.android.ide.eclipse.adt.internal.build.PostCompilerHelper;
import com.android.ide.eclipse.adt.internal.project.ProjectHelper;
import com.android.ide.eclipse.adt.internal.sdk.ProjectState;
@@ -43,6 +44,8 @@ import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jface.action.IAction;
@@ -175,6 +178,11 @@ public class MultiApkExportAction implements IObjectActionDelegate {
binFolder.create(true, true, monitor);
}
+ AndroidPrintStream stdout = new AndroidPrintStream(exportProject, null /*prefix*/,
+ System.out);
+ AndroidPrintStream stderr = new AndroidPrintStream(exportProject, null /*prefix*/,
+ System.err);
+
for (ApkData apk : apks) {
// find the IProject object for this apk.
ProjectConfig projectConfig = apk.getProjectConfig();
@@ -218,11 +226,11 @@ public class MultiApkExportAction implements IObjectActionDelegate {
// if there are soft variants, only export those.
for (Entry<String, String> entry : variantMap.entrySet()) {
buildVariant(wsRoot, projectState, appPackage, versionCode, apk, entry,
- binFolder);
+ binFolder, stdout, stderr);
}
} else {
buildVariant(wsRoot, projectState, appPackage, versionCode, apk,
- null /*soft variant*/, binFolder);
+ null /*soft variant*/, binFolder, stdout, stderr);
}
}
@@ -241,63 +249,71 @@ public class MultiApkExportAction implements IObjectActionDelegate {
* @throws CoreException
*/
private void buildVariant(IWorkspaceRoot wsRoot, ProjectState projectState, String appPackage,
- int versionCode, ApkData apk, Entry<String, String> softVariant, IFolder binFolder)
+ int versionCode, ApkData apk, Entry<String, String> softVariant, IFolder binFolder,
+ AndroidPrintStream stdout, AndroidPrintStream stderr)
throws CoreException {
- // get the libraries for this project
- IProject[] libProjects = projectState.getFullLibraryProjects();
-
- IProject project = projectState.getProject();
- IJavaProject javaProject = JavaCore.create(project);
-
- int compositeVersionCode = apk.getCompositeVersionCode(versionCode);
-
- // figure out the file names
- String pkgName = project.getName() + "-" + apk.getBuildInfo();
- String finalNameRoot = appPackage + "-" + compositeVersionCode;
- if (softVariant != null) {
- String tmp = "-" + softVariant.getKey();
- pkgName += tmp;
- finalNameRoot += tmp;
- }
+ try {
+ // get the libraries for this project
+ IProject[] libProjects = projectState.getFullLibraryProjects();
- pkgName += ".ap_";
- String outputName = finalNameRoot + "-unsigned.apk";
+ IProject project = projectState.getProject();
+ IJavaProject javaProject = JavaCore.create(project);
- PostCompilerHelper helper = new PostCompilerHelper(project, System.out, System.err);
+ int compositeVersionCode = apk.getCompositeVersionCode(versionCode);
- // get the manifest file
- IFile manifestFile = project.getFile(SdkConstants.FN_ANDROID_MANIFEST_XML);
- // get the project bin folder
- IFolder projectBinFolder = wsRoot.getFolder(javaProject.getOutputLocation());
- String projectBinFolderPath = projectBinFolder.getLocation().toOSString();
+ // figure out the file names
+ String pkgName = project.getName() + "-" + apk.getBuildInfo();
+ String finalNameRoot = appPackage + "-" + compositeVersionCode;
+ if (softVariant != null) {
+ String tmp = "-" + softVariant.getKey();
+ pkgName += tmp;
+ finalNameRoot += tmp;
+ }
- // package the resources
- if (helper.packageResources(manifestFile, libProjects,
- softVariant != null ? softVariant.getValue() : null, compositeVersionCode,
- projectBinFolderPath, pkgName) == false) {
- return;
+ pkgName += ".ap_";
+ String outputName = finalNameRoot + "-unsigned.apk";
+
+ PostCompilerHelper helper = new PostCompilerHelper(project, stdout, stderr,
+ false /*debugMode*/, false/*verbose*/);
+
+ // get the manifest file
+ IFile manifestFile = project.getFile(SdkConstants.FN_ANDROID_MANIFEST_XML);
+ // get the project bin folder
+ IFolder projectBinFolder = wsRoot.getFolder(javaProject.getOutputLocation());
+ String projectBinFolderPath = projectBinFolder.getLocation().toOSString();
+
+ // package the resources
+ helper.packageResources(manifestFile, libProjects,
+ softVariant != null ? softVariant.getValue() : null,
+ compositeVersionCode, projectBinFolderPath, pkgName);
+
+ apk.setOutputName(softVariant != null ? softVariant.getKey() : null, outputName);
+
+ // do the final export.
+ 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 = PostCompilerHelper.getJavaProjects(javaRefs);
+
+ helper.finalPackage(
+ new File(projectBinFolderPath, pkgName).getAbsolutePath(),
+ dexFile.getLocation().toOSString(),
+ outputFile,
+ javaProject,
+ libProjects,
+ referencedJavaProjects,
+ apk.getAbi(),
+ null, //key
+ null, //certificate
+ null); //ResourceMarker
+ } catch (CoreException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new CoreException(new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
+ e.getMessage(), e));
}
- apk.setOutputName(softVariant != null ? softVariant.getKey() : null, outputName);
-
- // do the final export.
- 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 = PostCompilerHelper.getJavaProjects(javaRefs);
-
- helper.finalPackage(
- new File(projectBinFolderPath, pkgName).getAbsolutePath(),
- dexFile.getLocation().toOSString(),
- 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/AaptParser.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/AaptParser.java
index 3b1c413..fd458de 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/AaptParser.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/AaptParser.java
@@ -25,7 +25,7 @@ import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import java.io.File;
-import java.util.ArrayList;
+import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -126,6 +126,16 @@ public class AaptParser {
private final static Pattern sPattern9Line1 = Pattern.compile(
"^Invalid configuration: (.+)$"); //$NON-NLS-1$
+ /**
+ * Parse the output of aapt and mark the incorrect file with error markers
+ *
+ * @param results the output of aapt
+ * @param project the project containing the file to mark
+ * @return true if the parsing failed, false if success.
+ */
+ static final boolean parseOutput(List<String> results, IProject project) {
+ return parseOutput(results.toArray(new String[results.size()]), project);
+ }
/**
* Parse the output of aapt and mark the incorrect file with error markers
@@ -134,10 +144,9 @@ public class AaptParser {
* @param project the project containing the file to mark
* @return true if the parsing failed, false if success.
*/
- static final boolean parseOutput(ArrayList<String> results,
- IProject project) {
+ static final boolean parseOutput(String[] results, IProject project) {
// nothing to parse? just return false;
- if (results.size() == 0) {
+ if (results.length == 0) {
return false;
}
@@ -147,8 +156,8 @@ public class AaptParser {
Matcher m;
- for (int i = 0; i < results.size(); i++) {
- String p = results.get(i);
+ for (int i = 0; i < results.length ; i++) {
+ String p = results[i];
m = sPattern0Line1.matcher(p);
if (m.matches()) {
@@ -183,10 +192,10 @@ public class AaptParser {
String location = m.group(1);
String msg = p; // default msg is the line in case we don't find anything else
- if (++i < results.size()) {
- msg = results.get(i).trim();
- if (++i < results.size()) {
- msg = msg + " - " + results.get(i).trim(); //$NON-NLS-1$
+ if (++i < results.length) {
+ msg = results[i].trim();
+ if (++i < results.length) {
+ msg = msg + " - " + results[i].trim(); //$NON-NLS-1$
// skip the next line
i++;
@@ -429,16 +438,16 @@ public class AaptParser {
* @param pattern The pattern to match
* @return null if error or no match, the matcher otherwise.
*/
- private static final Matcher getNextLineMatcher(ArrayList<String> lines,
+ private static final Matcher getNextLineMatcher(String[] lines,
int nextIndex, Pattern pattern) {
// unless we can't, because we reached the last line
- if (nextIndex == lines.size()) {
+ if (nextIndex == lines.length) {
// we expected a 2nd line, so we flag as error
// and we bail
return null;
}
- Matcher m = pattern.matcher(lines.get(nextIndex));
+ Matcher m = pattern.matcher(lines[nextIndex]);
if (m.matches()) {
return m;
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/PostCompilerBuilder.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/PostCompilerBuilder.java
index 8c8a15d..cdea980 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/PostCompilerBuilder.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/PostCompilerBuilder.java
@@ -18,15 +18,24 @@ package com.android.ide.eclipse.adt.internal.build;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.AndroidConstants;
+import com.android.ide.eclipse.adt.AndroidPrintStream;
+import com.android.ide.eclipse.adt.internal.build.PostCompilerHelper.AaptExecException;
+import com.android.ide.eclipse.adt.internal.build.PostCompilerHelper.AaptResultException;
+import com.android.ide.eclipse.adt.internal.build.PostCompilerHelper.DexException;
+import com.android.ide.eclipse.adt.internal.build.PostCompilerHelper.NativeLibInJarException;
+import com.android.ide.eclipse.adt.internal.build.PostCompilerHelper.ResourceMarker;
+import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs;
import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs.BuildVerbosity;
import com.android.ide.eclipse.adt.internal.project.ApkInstallManager;
import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper;
import com.android.ide.eclipse.adt.internal.project.ProjectHelper;
import com.android.ide.eclipse.adt.internal.sdk.ProjectState;
import com.android.ide.eclipse.adt.internal.sdk.Sdk;
+import com.android.prefs.AndroidLocation.AndroidLocationException;
import com.android.sdklib.SdkConstants;
-import com.android.sdklib.xml.AndroidManifest;
-import com.android.sdklib.xml.AndroidXPathFactory;
+import com.android.sdklib.build.ApkCreationException;
+import com.android.sdklib.build.DuplicateFileException;
+import com.android.sdklib.internal.build.DebugKeyProvider.KeytoolException;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
@@ -43,19 +52,13 @@ import org.eclipse.core.runtime.IStatus;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
-import org.xml.sax.InputSource;
import java.io.File;
-import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Map;
-import javax.xml.xpath.XPath;
-
public class PostCompilerBuilder extends BaseBuilder {
- private static final String CONSOLE_PREFIX_DX = "Dx"; //$NON-NLS-1$
-
/** This ID is used in plugin.xml and in each project's .project file.
* It cannot be changed even if the class is renamed/moved */
public static final String ID = "com.android.ide.eclipse.adt.ApkBuilder"; //$NON-NLS-1$
@@ -83,8 +86,8 @@ public class PostCompilerBuilder extends BaseBuilder {
*/
private boolean mBuildFinalPackage = false;
- private PrintStream mDxOutStream = null;
- private PrintStream mDxErrStream = null;
+ private AndroidPrintStream mOutStream = null;
+ private AndroidPrintStream mErrStream = null;
/**
* Basic Resource Delta Visitor class to check if a referenced project had a change in its
@@ -168,6 +171,14 @@ public class PostCompilerBuilder extends BaseBuilder {
}
}
+ private ResourceMarker mResourceMarker = new ResourceMarker() {
+ public void setWarning(IResource resource, String message) {
+ BaseProjectHelper.markResource(resource, AndroidConstants.MARKER_PACKAGING,
+ message, IMarker.SEVERITY_WARNING);
+ }
+ };
+
+
public PostCompilerBuilder() {
super();
}
@@ -215,7 +226,8 @@ public class PostCompilerBuilder extends BaseBuilder {
// get the list of referenced projects.
javaProjects = ProjectHelper.getReferencedProjects(project);
- IJavaProject[] referencedJavaProjects = PostCompilerHelper.getJavaProjects(javaProjects);
+ IJavaProject[] referencedJavaProjects = PostCompilerHelper.getJavaProjects(
+ javaProjects);
// mix the java project and the library projects
final int libCount = libProjects.length;
@@ -292,8 +304,9 @@ public class PostCompilerBuilder extends BaseBuilder {
IJavaProject referencedJavaProject = referencedJavaProjects[i];
delta = getDelta(referencedJavaProject.getProject());
if (delta != null) {
- ReferencedProjectDeltaVisitor refProjectDv = new ReferencedProjectDeltaVisitor(
- referencedJavaProject);
+ ReferencedProjectDeltaVisitor refProjectDv =
+ new ReferencedProjectDeltaVisitor(referencedJavaProject);
+
delta.accept(refProjectDv);
// save the state
@@ -383,15 +396,20 @@ public class PostCompilerBuilder extends BaseBuilder {
// Get the DX output stream. Since the builder is created for the life of the
// project, they can be kept around.
- if (mDxOutStream == null) {
- mDxOutStream = AdtPlugin.getOutPrintStream(project, CONSOLE_PREFIX_DX);
- mDxErrStream = AdtPlugin.getErrPrintStream(project, CONSOLE_PREFIX_DX);
+ if (mOutStream == null) {
+ mOutStream = new AndroidPrintStream(project, null /*prefix*/,
+ AdtPlugin.getOutStream());
+ mErrStream = new AndroidPrintStream(project, null /*prefix*/,
+ AdtPlugin.getOutStream());
}
// we need to test all three, as we may need to make the final package
// but not the intermediary ones.
if (mPackageResources || mConvertToDex || mBuildFinalPackage) {
- PostCompilerHelper helper = new PostCompilerHelper(project, mDxOutStream, mDxErrStream);
+ PostCompilerHelper helper = new PostCompilerHelper(project,
+ mOutStream, mErrStream,
+ true /*debugMode*/,
+ AdtPrefs.getPrefs().getBuildVerbosity() == BuildVerbosity.VERBOSE);
// resource to the AndroidManifest.xml file
IFile manifestFile = project.getFile(SdkConstants.FN_ANDROID_MANIFEST_XML);
@@ -429,12 +447,31 @@ public class PostCompilerBuilder extends BaseBuilder {
removeMarkersFromContainer(project, AndroidConstants.MARKER_AAPT_PACKAGE);
// need to figure out some path before we can execute aapt;
- if (helper.packageResources( manifestFile, libProjects, null /*resfilter*/,
- 0 /*versionCode */, osBinPath,
- AndroidConstants.FN_RESOURCES_AP_) == false) {
- // aapt failed. Whatever files that needed to be marked
- // have already been marked. We just return.
+ try {
+ helper.packageResources(manifestFile, libProjects, null /*resfilter*/,
+ 0 /*versionCode */, osBinPath,
+ AndroidConstants.FN_RESOURCES_AP_);
+ } catch (AaptExecException e) {
+ BaseProjectHelper.markResource(project, AndroidConstants.MARKER_PACKAGING,
+ e.getMessage(), IMarker.SEVERITY_ERROR);
return allRefProjects;
+ } catch (AaptResultException e) {
+ // attempt to parse the error output
+ String[] aaptOutput = e.getOutput();
+ boolean parsingError = AaptParser.parseOutput(aaptOutput, project);
+
+ // if we couldn't parse the output we display it in the console.
+ if (parsingError) {
+ AdtPlugin.printErrorToConsole(project, (Object[]) aaptOutput);
+
+ // if the exec failed, and we couldn't parse the error output (and
+ // therefore not all files that should have been marked, were marked),
+ // we put a generic marker on the project and abort.
+ BaseProjectHelper.markResource(project,
+ AndroidConstants.MARKER_PACKAGING,
+ Messages.Unparsed_AAPT_Errors,
+ IMarker.SEVERITY_ERROR);
+ }
}
// build has been done. reset the state of the builder
@@ -446,8 +483,25 @@ public class PostCompilerBuilder 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 +
- SdkConstants.FN_APK_CLASSES_DEX, referencedJavaProjects) == false) {
+ try {
+ helper.executeDx(javaProject, osBinPath, osBinPath + File.separator +
+ SdkConstants.FN_APK_CLASSES_DEX, referencedJavaProjects,
+ mResourceMarker);
+ } catch (DexException e) {
+ String message = e.getMessage();
+
+ AdtPlugin.printErrorToConsole(project, message);
+ BaseProjectHelper.markResource(project, AndroidConstants.MARKER_PACKAGING,
+ message, IMarker.SEVERITY_ERROR);
+
+ Throwable cause = e.getCause();
+
+ if (cause instanceof NoClassDefFoundError
+ || cause instanceof NoSuchMethodError) {
+ AdtPlugin.printErrorToConsole(project, Messages.Incompatible_VM_Warning,
+ Messages.Requires_1_5_Error);
+ }
+
// dx failed, we return
return allRefProjects;
}
@@ -459,33 +513,68 @@ public class PostCompilerBuilder extends BaseBuilder {
saveProjectBooleanProperty(PROPERTY_CONVERT_TO_DEX, mConvertToDex);
}
- // figure out whether the application is debuggable.
- // It is considered debuggable if the attribute debuggable is set to true
- // in the manifest
- boolean debuggable = false;
- XPath xpath = AndroidXPathFactory.newXPath();
- String result = xpath.evaluate(
- "/" + AndroidManifest.NODE_MANIFEST + //$NON-NLS-1$
- "/" + AndroidManifest.NODE_APPLICATION + //$NON-NLS-1$
- "/@" + AndroidXPathFactory.DEFAULT_NS_PREFIX + //$NON-NLS-1$
- ":" + AndroidManifest.ATTRIBUTE_DEBUGGABLE, //$NON-NLS-1$
- new InputSource(manifestFile.getContents()));
- if (result.length() > 0) {
- debuggable = Boolean.valueOf(result);
- }
-
// now we need to make the final package from the intermediary apk
// and classes.dex.
// This is the default package with all the resources.
String classesDexPath = osBinPath + File.separator +
SdkConstants.FN_APK_CLASSES_DEX;
- if (helper.finalPackage(
+ try {
+ helper.finalDebugPackage(
osBinPath + File.separator + AndroidConstants.FN_RESOURCES_AP_,
- classesDexPath, osFinalPackagePath, true /*debugSign*/,
- javaProject, libProjects,
- referencedJavaProjects, null /*abiFilter*/, debuggable) == false) {
+ classesDexPath, osFinalPackagePath,
+ javaProject, libProjects, referencedJavaProjects, mResourceMarker);
+ } 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(project, AndroidConstants.MARKER_PACKAGING, msg,
+ IMarker.SEVERITY_ERROR);
+
+ // output more info in the console
+ AdtPlugin.printErrorToConsole(project,
+ msg,
+ String.format(Messages.ApkBuilder_JAVA_HOME_is_s, e.getJavaHome()),
+ Messages.ApkBuilder_Update_or_Execute_manually_s,
+ e.getCommandLine());
+
return allRefProjects;
+ } catch (ApkCreationException e) {
+ String eMessage = e.getMessage();
+
+ // mark the project with the standard message
+ String msg = String.format(Messages.Final_Archive_Error_s, eMessage);
+ BaseProjectHelper.markResource(project, AndroidConstants.MARKER_PACKAGING, msg,
+ IMarker.SEVERITY_ERROR);
+ } 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(project, AndroidConstants.MARKER_PACKAGING, msg,
+ IMarker.SEVERITY_ERROR);
+ } catch (NativeLibInJarException e) {
+ String msg = e.getMessage();
+
+ BaseProjectHelper.markResource(project, AndroidConstants.MARKER_PACKAGING,
+ msg, IMarker.SEVERITY_ERROR);
+
+ AdtPlugin.printErrorToConsole(project, (Object[]) e.getConsoleMsgs());
+ } catch (CoreException e) {
+ // mark project and return
+ String msg = String.format(Messages.Final_Archive_Error_s, e.getMessage());
+ AdtPlugin.printErrorToConsole(project, msg);
+ BaseProjectHelper.markResource(project, AndroidConstants.MARKER_PACKAGING, msg,
+ IMarker.SEVERITY_ERROR);
+ } 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(project, msg2);
+ BaseProjectHelper.markResource(project, AndroidConstants.MARKER_PACKAGING, msg2,
+ IMarker.SEVERITY_ERROR);
}
// we are done.
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/PostCompilerHelper.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/PostCompilerHelper.java
index 9bcc7d4..928ffac 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/PostCompilerHelper.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/PostCompilerHelper.java
@@ -18,6 +18,7 @@ package com.android.ide.eclipse.adt.internal.build;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.AndroidConstants;
+import com.android.ide.eclipse.adt.AndroidPrintStream;
import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs;
import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs.BuildVerbosity;
import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper;
@@ -32,13 +33,13 @@ import com.android.sdklib.build.ApkCreationException;
import com.android.sdklib.build.DuplicateFileException;
import com.android.sdklib.build.SealedApkException;
import com.android.sdklib.build.ApkBuilder.JarStatus;
+import com.android.sdklib.build.ApkBuilder.SigningInfo;
import com.android.sdklib.internal.build.DebugKeyProvider;
import com.android.sdklib.internal.build.SignedJarBuilder;
import com.android.sdklib.internal.build.DebugKeyProvider.KeytoolException;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
-import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspace;
@@ -57,6 +58,8 @@ import org.eclipse.jface.preference.IPreferenceStore;
import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
+import java.security.PrivateKey;
+import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;
@@ -72,17 +75,115 @@ import java.util.List;
* {@link #finalPackage(String, String, String, boolean, IJavaProject, IProject[], IJavaProject[], String, boolean)}
* will make the apk from all the previous components.
*
+ * This class only executes the 3 above actions. It does not handle the errors, and simply sends
+ * them back as custom exceptions.
+ *
+ * Warnings are handled by the {@link ResourceMarker} interface.
+ *
+ * Console output (verbose and non verbose) is handled through the {@link AndroidPrintStream} passed
+ * to the constructor.
+ *
*/
public class PostCompilerHelper {
+ private static final String CONSOLE_PREFIX_DX = "Dx"; //$NON-NLS-1$
+
private final IProject mProject;
- private final PrintStream mOutStream;
- private final PrintStream mErrStream;
+ private final AndroidPrintStream mOutStream;
+ private final AndroidPrintStream mErrStream;
+ private final boolean mVerbose;
+ private final boolean mDebugMode;
+
+ public static final class AaptExecException extends Exception {
+ private static final long serialVersionUID = 1L;
+
+ AaptExecException(String message, Throwable cause) {
+ super(message, cause);
+ }
+ }
+
+ public static final class AaptResultException extends Exception {
+ private static final long serialVersionUID = 1L;
+
+ private final int mErrorCode;
+ private final String[] mOutput;
+
+ AaptResultException(int errorCode, String[] output) {
+ mErrorCode = errorCode;
+ mOutput = output;
+ }
+
+ public String[] getOutput() {
+ return mOutput;
+ }
+
+ public int getErrorCode() {
+ return mErrorCode;
+ }
+ }
+
+ public static final class DexException extends Exception {
+ private static final long serialVersionUID = 1L;
+
+ DexException(String message) {
+ super(message);
+ }
+
+ DexException(String message, Throwable cause) {
+ super(message, cause);
+ }
+ }
+
+ public static final class NativeLibInJarException extends Exception {
+ private static final long serialVersionUID = 1L;
+
+ private final JarStatus mStatus;
+ private final String mLibName;
+ private final String[] mConsoleMsgs;
- public PostCompilerHelper(IProject project, PrintStream outStream, PrintStream errStream) {
+ NativeLibInJarException(JarStatus status, String message, String libName,
+ String[] consoleMsgs) {
+ super(message);
+ mStatus = status;
+ mLibName = libName;
+ mConsoleMsgs = consoleMsgs;
+ }
+
+ public JarStatus getStatus() {
+ return mStatus;
+ }
+
+ public String getLibName() {
+ return mLibName;
+ }
+
+ public String[] getConsoleMsgs() {
+ return mConsoleMsgs;
+ }
+ }
+
+ /**
+ * An object able to put a marker on a resource.
+ */
+ public interface ResourceMarker {
+ void setWarning(IResource resource, String message);
+ }
+
+ /**
+ * Creates a new post-compiler helper
+ * @param project
+ * @param outStream
+ * @param errStream
+ * @param debugMode whether this is a debug build
+ * @param verbose
+ */
+ public PostCompilerHelper(IProject project, AndroidPrintStream outStream,
+ AndroidPrintStream errStream, boolean debugMode, boolean verbose) {
mProject = project;
mOutStream = outStream;
mErrStream = errStream;
+ mDebugMode = debugMode;
+ mVerbose = verbose;
}
/**
@@ -95,10 +196,12 @@ public class PostCompilerHelper {
* If the value is <=0, no values are inserted.
* @param outputFolder where to write the resource ap_ file.
* @param outputFilename the name of the resource ap_ file.
- * @return true if success.
+ * @throws AaptExecException
+ * @throws AaptResultException
*/
- public boolean packageResources(IFile manifestFile, IProject[] libProjects, String resFilter,
- int versionCode, String outputFolder, String outputFilename) {
+ public void packageResources(IFile manifestFile, IProject[] libProjects, String resFilter,
+ int versionCode, String outputFolder, String outputFilename)
+ throws AaptExecException, AaptResultException {
// need to figure out some path before we can execute aapt;
// get the resource folder
@@ -138,21 +241,68 @@ public class PostCompilerHelper {
}
// build the default resource package
- if (executeAapt(osManifestPath, osResPaths, osAssetsPath,
+ executeAapt(osManifestPath, osResPaths, osAssetsPath,
outputFolder + File.separator + outputFilename, resFilter,
- versionCode) == false) {
- // aapt failed. Whatever files that needed to be marked
- // have already been marked. We just return.
- return false;
- }
+ versionCode);
+ }
+ }
+
+ /**
+ * Makes a final package signed with the debug key.
+ *
+ * Packages the dex files, the temporary resource file into the final package file.
+ *
+ * Whether the package is a debug package is controlled with the <var>debugMode</var> parameter
+ * in {@link #PostCompilerHelper(IProject, PrintStream, PrintStream, boolean, boolean)}
+ *
+ * @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 javaProject the java project being compiled
+ * @param libProjects an optional list of library projects (can be null)
+ * @param referencedJavaProjects referenced projects.
+ * @return true if success, false otherwise.
+ * @throws ApkCreationException
+ * @throws AndroidLocationException
+ * @throws KeytoolException
+ * @throws NativeLibInJarException
+ * @throws CoreException
+ * @throws DuplicateFileException
+ */
+ public void finalDebugPackage(String intermediateApk, String dex, String output,
+ final IJavaProject javaProject, IProject[] libProjects,
+ IJavaProject[] referencedJavaProjects, ResourceMarker resMarker)
+ throws ApkCreationException, KeytoolException, AndroidLocationException,
+ NativeLibInJarException, DuplicateFileException, CoreException {
+
+ // get the debug keystore to use.
+ IPreferenceStore store = AdtPlugin.getDefault().getPreferenceStore();
+ String keystoreOsPath = store.getString(AdtPrefs.PREFS_CUSTOM_DEBUG_KEYSTORE);
+ if (keystoreOsPath == null || new File(keystoreOsPath).isFile() == false) {
+ keystoreOsPath = 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, keystoreOsPath));
}
- return true;
+ // from the keystore, get the signing info
+ SigningInfo info = ApkBuilder.getDebugKey(keystoreOsPath, mVerbose ? mOutStream : null);
+
+ finalPackage(intermediateApk, dex, output, javaProject, libProjects,
+ referencedJavaProjects, null /*abiFilter*/,
+ info != null ? info.key : null, info != null ? info.certificate : null, resMarker);
}
/**
- * Makes the final package. Package the dex files, the temporary resource file into the final
- * package file.
+ * Makes the final package.
+ *
+ * Packages the dex files, the temporary resource file into the final package file.
+ *
+ * Whether the package is a debug package is controlled with the <var>debugMode</var> parameter
+ * in {@link #PostCompilerHelper(IProject, PrintStream, PrintStream, boolean, boolean)}
+ *
* @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.
@@ -162,101 +312,75 @@ public class PostCompilerHelper {
* @param referencedJavaProjects referenced projects.
* @param abiFilter an optional filter. If not null, then only the matching ABI is included in
* the final archive
- * @param debuggable whether the project manifest has debuggable==true. If true, any gdbserver
- * executables will be packaged with the native libraries.
* @return true if success, false otherwise.
+ * @throws NativeLibInJarException
+ * @throws ApkCreationException
+ * @throws CoreException
+ * @throws DuplicateFileException
*/
- public boolean finalPackage(String intermediateApk, String dex, String output,
- boolean debugSign, final IJavaProject javaProject, IProject[] libProjects,
- IJavaProject[] referencedJavaProjects, String abiFilter, boolean debuggable) {
-
- IProject project = javaProject.getProject();
-
- String keystoreOsPath = null;
- if (debugSign) {
- IPreferenceStore store = AdtPlugin.getDefault().getPreferenceStore();
- 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);
-
- // 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());
-
- return false;
- } 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);
-
- return false;
- }
- } else {
- AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, mProject,
- String.format(Messages.ApkBuilder_Using_s_To_Sign, keystoreOsPath));
- }
- }
-
+ public void finalPackage(String intermediateApk, String dex, String output,
+ final IJavaProject javaProject, IProject[] libProjects,
+ IJavaProject[] referencedJavaProjects, String abiFilter, PrivateKey key,
+ X509Certificate certificate, ResourceMarker resMarker)
+ throws NativeLibInJarException, ApkCreationException, DuplicateFileException,
+ CoreException {
try {
- ApkBuilder apkBuilder = new ApkBuilder(output, intermediateApk, dex, keystoreOsPath,
- AdtPrefs.getPrefs().getBuildVerbosity() == BuildVerbosity.VERBOSE ?
- AdtPlugin.getOutPrintStream(project, null): null);
- apkBuilder.setDebugMode(debuggable);
+ ApkBuilder apkBuilder = new ApkBuilder(output, intermediateApk, dex,
+ key, certificate,
+ mVerbose ? mOutStream: null);
+ apkBuilder.setDebugMode(mDebugMode);
// Now we write the standard resources from the project and the referenced projects.
writeStandardResources(apkBuilder, javaProject, referencedJavaProjects);
// Now we write the standard resources from the external jars
- for (String libraryOsPath : getExternalJars()) {
- JarStatus status = apkBuilder.addResourcesFromJar(new File(libraryOsPath));
+ for (String libraryOsPath : getExternalJars(resMarker)) {
+ JarStatus jarStatus = 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) {
+ if (jarStatus.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);
- 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()) {
+
+ if (jarStatus.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()) {
+
+ for (String lib : jarStatus.getNativeLibs()) {
consoleMsgs.add(" - " + lib);
}
- AdtPlugin.printErrorToConsole(mProject,
- consoleMsgs.toArray());
- return false;
+ String[] consoleStrings = consoleMsgs.toArray(new String[consoleMsgs.size()]);
+
+ // if there's a conflict or if the prefs force error on any native code in jar
+ // files, throw an exception
+ if (jarStatus.hasNativeLibsConflicts() ||
+ AdtPrefs.getPrefs().getBuildForceErrorOnNativeLibInJar()) {
+ throw new NativeLibInJarException(jarStatus, msg, libName, consoleStrings);
+ } else {
+ // otherwise, put a warning, and output to the console also.
+ if (resMarker != null) {
+ resMarker.setWarning(mProject, msg);
+ }
+
+ for (String string : consoleStrings) {
+ mOutStream.println(string);
+ }
+ }
}
}
@@ -282,46 +406,9 @@ public class PostCompilerHelper {
// seal the APK.
apkBuilder.sealApk();
- return true;
- } catch (CoreException 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);
- } 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);
- } 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
- // went wrong (otherwise the build appears to succeed but the zip archive is not closed
- // and therefore invalid.
- String msg = e.getMessage();
- if (msg == null) {
- msg = e.getClass().getCanonicalName();
- }
-
- msg = String.format("Unknown error: %1$s", msg);
- AdtPlugin.printErrorToConsole(mProject, msg);
- BaseProjectHelper.markResource(mProject, AndroidConstants.MARKER_PACKAGING, msg,
- IMarker.SEVERITY_ERROR);
}
-
- return false;
}
/**
@@ -332,9 +419,11 @@ public class PostCompilerHelper {
* @param referencedJavaProjects the list of referenced projects for this project.
*
* @throws CoreException
+ * @throws DexException
*/
- boolean executeDx(IJavaProject javaProject, String osBinPath, String osOutFilePath,
- IJavaProject[] referencedJavaProjects) throws CoreException {
+ public void executeDx(IJavaProject javaProject, String osBinPath, String osOutFilePath,
+ IJavaProject[] referencedJavaProjects, ResourceMarker resMarker) throws CoreException,
+ DexException {
IAndroidTarget target = Sdk.getCurrent().getTarget(mProject);
AndroidTargetData targetData = Sdk.getCurrent().getTargetData(target);
@@ -353,7 +442,7 @@ public class PostCompilerHelper {
try {
// get the list of libraries to include with the source code
- String[] libraries = getExternalJars();
+ String[] libraries = getExternalJars(resMarker);
// get the list of referenced projects output to add
String[] projectOutputs = getProjectOutputs(referencedJavaProjects);
@@ -369,40 +458,35 @@ public class PostCompilerHelper {
// then external jars.
System.arraycopy(libraries, 0, fileNames, 1 + projectOutputs.length, libraries.length);
+ // set a temporary prefix on the print streams.
+ mOutStream.setPrefix(CONSOLE_PREFIX_DX);
+ mErrStream.setPrefix(CONSOLE_PREFIX_DX);
+
int res = wrapper.run(osOutFilePath, fileNames,
- AdtPrefs.getPrefs().getBuildVerbosity() == BuildVerbosity.VERBOSE,
+ mVerbose,
mOutStream, mErrStream);
+ mOutStream.setPrefix(null);
+ mErrStream.setPrefix(null);
+
if (res != 0) {
// output error message and marker the project.
- String message = String.format(Messages.Dalvik_Error_d,
- res);
- AdtPlugin.printErrorToConsole(mProject, message);
- BaseProjectHelper.markResource(mProject, AndroidConstants.MARKER_PACKAGING,
- message, IMarker.SEVERITY_ERROR);
- return false;
+ String message = String.format(Messages.Dalvik_Error_d, res);
+ throw new DexException(message);
}
- } catch (Throwable ex) {
- String message = ex.getMessage();
+ } catch (DexException e) {
+ throw e;
+ } catch (Throwable t) {
+ String message = t.getMessage();
if (message == null) {
- message = ex.getClass().getCanonicalName();
+ message = t.getClass().getCanonicalName();
}
message = String.format(Messages.Dalvik_Error_s, message);
- AdtPlugin.printErrorToConsole(mProject, message);
- BaseProjectHelper.markResource(mProject, AndroidConstants.MARKER_PACKAGING,
- message, IMarker.SEVERITY_ERROR);
- if ((ex instanceof NoClassDefFoundError)
- || (ex instanceof NoSuchMethodError)) {
- AdtPlugin.printErrorToConsole(mProject, Messages.Incompatible_VM_Warning,
- Messages.Requires_1_5_Error);
- }
- return false;
- }
- return true;
+ throw new DexException(message, t);
+ }
}
-
/**
* Executes aapt. If any error happen, files or the project will be marked.
* @param osManifestPath The path to the manifest file
@@ -414,11 +498,12 @@ public class PostCompilerHelper {
* resources.)
* @param versionCode optional version code to insert in the manifest during packaging. If <=0
* then no value is inserted
- * @return true if success, false otherwise.
+ * @throws AaptExecException
+ * @throws AaptResultException
*/
- private boolean executeAapt(String osManifestPath,
+ private void executeAapt(String osManifestPath,
List<String> osResPaths, String osAssetsPath, String osOutFilePath,
- String configFilter, int versionCode) {
+ String configFilter, int versionCode) throws AaptExecException, AaptResultException {
IAndroidTarget target = Sdk.getCurrent().getTarget(mProject);
// Create the command line.
@@ -436,6 +521,10 @@ public class PostCompilerHelper {
commandArray.add("--auto-add-overlay"); //$NON-NLS-1$
}
+ if (mDebugMode) {
+ commandArray.add("--debug-mode"); //$NON-NLS-1$
+ }
+
if (versionCode > 0) {
commandArray.add("--version-code"); //$NON-NLS-1$
commandArray.add(Integer.toString(versionCode));
@@ -489,49 +578,25 @@ public class PostCompilerHelper {
// get the output and return code from the process
execError = BaseBuilder.grabProcessOutput(mProject, process, results);
- // attempt to parse the error output
- boolean parsingError = AaptParser.parseOutput(results, mProject);
-
- // if we couldn't parse the output we display it in the console.
- if (parsingError) {
- if (execError != 0) {
- AdtPlugin.printErrorToConsole(mProject, results.toArray());
- } else {
- AdtPlugin.printBuildToConsole(BuildVerbosity.ALWAYS, mProject,
- results.toArray());
+ if (execError != 0) {
+ throw new AaptResultException(execError,
+ results.toArray(new String[results.size()]));
+ } else if (mVerbose) {
+ for (String resultString : results) {
+ mOutStream.println(resultString);
}
}
- // We need to abort if the exec failed.
- if (execError != 0) {
- // if the exec failed, and we couldn't parse the error output (and therefore
- // not all files that should have been marked, were marked), we put a generic
- // marker on the project and abort.
- if (parsingError) {
- BaseProjectHelper.markResource(mProject, AndroidConstants.MARKER_PACKAGING,
- Messages.Unparsed_AAPT_Errors,
- IMarker.SEVERITY_ERROR);
- }
- // abort if exec failed.
- return false;
- }
- } catch (IOException e1) {
+ } catch (IOException e) {
String msg = String.format(Messages.AAPT_Exec_Error, command[0]);
- BaseProjectHelper.markResource(mProject, AndroidConstants.MARKER_PACKAGING, msg,
- IMarker.SEVERITY_ERROR);
- return false;
+ throw new AaptExecException(msg, e);
} catch (InterruptedException e) {
String msg = String.format(Messages.AAPT_Exec_Error, command[0]);
- BaseProjectHelper.markResource(mProject, AndroidConstants.MARKER_PACKAGING, msg,
- IMarker.SEVERITY_ERROR);
- return false;
+ throw new AaptExecException(msg, e);
}
-
- return true;
}
-
/**
* Writes the standard resources of a project and its referenced projects
* into a {@link SignedJarBuilder}.
@@ -600,7 +665,7 @@ public class PostCompilerHelper {
* Returns an array of external jar files used by the project.
* @return an array of OS-specific absolute file paths
*/
- private final String[] getExternalJars() {
+ private final String[] getExternalJars(ResourceMarker resMarker) {
// get a java project from it
IJavaProject javaProject = JavaCore.create(mProject);
@@ -637,13 +702,13 @@ public class PostCompilerHelper {
} else {
String message = String.format( Messages.Couldnt_Locate_s_Error,
path);
- AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE,
- mProject, message);
+ // always output to the console
+ mOutStream.println(message);
- // Also put a warning marker on the project
- BaseProjectHelper.markResource(mProject,
- AndroidConstants.MARKER_PACKAGING, message,
- IMarker.SEVERITY_WARNING);
+ // put a marker
+ if (resMarker != null) {
+ resMarker.setWarning(mProject, message);
+ }
}
}
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/pages/OverviewExportPart.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/pages/OverviewExportPart.java
index 138f0b1..b0eb75a 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/pages/OverviewExportPart.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/pages/OverviewExportPart.java
@@ -21,11 +21,15 @@ import com.android.ide.eclipse.adt.internal.editors.ui.SectionHelper.ManifestSec
import com.android.ide.eclipse.adt.internal.project.ExportHelper;
import com.android.ide.eclipse.adt.internal.sdk.ProjectState;
import com.android.ide.eclipse.adt.internal.sdk.Sdk;
+import com.android.ide.eclipse.adt.internal.wizards.export.ExportWizard;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
+import org.eclipse.jface.viewers.StructuredSelection;
+import org.eclipse.jface.wizard.WizardDialog;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.ui.IEditorInput;
+import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.forms.events.HyperlinkAdapter;
import org.eclipse.ui.forms.events.HyperlinkEvent;
import org.eclipse.ui.forms.widgets.FormText;
@@ -40,7 +44,8 @@ final class OverviewExportPart extends ManifestSectionPart {
private final OverviewPage mOverviewPage;
- public OverviewExportPart(OverviewPage overviewPage, Composite body, FormToolkit toolkit, ManifestEditor editor) {
+ public OverviewExportPart(OverviewPage overviewPage, final Composite body, FormToolkit toolkit,
+ ManifestEditor editor) {
super(body, toolkit, Section.TWISTIE | Section.EXPANDED, true /* description */);
mOverviewPage = overviewPage;
Section section = getSection();
@@ -84,10 +89,15 @@ final class OverviewExportPart extends ManifestSectionPart {
if (project != null) {
if ("manual".equals(e.data)) { //$NON-NLS-1$
// now we can export an unsigned apk for the project.
- ExportHelper.exportProject(project);
+ ExportHelper.exportUnsignedReleaseApk(project);
} else {
// call the export wizard
- ExportHelper.startExportWizard(project);
+ StructuredSelection selection = new StructuredSelection(project);
+
+ ExportWizard wizard = new ExportWizard();
+ wizard.init(PlatformUI.getWorkbench(), selection);
+ WizardDialog dialog = new WizardDialog(body.getShell(), wizard);
+ dialog.open();
}
}
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/ExportHelper.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/ExportHelper.java
index 235ba7c0..2a10fb8 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/ExportHelper.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/ExportHelper.java
@@ -16,11 +16,25 @@
package com.android.ide.eclipse.adt.internal.project;
+import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.AndroidConstants;
+import com.android.ide.eclipse.adt.AndroidPrintStream;
+import com.android.ide.eclipse.adt.internal.build.PostCompilerHelper;
+import com.android.ide.eclipse.adt.internal.sdk.ProjectState;
+import com.android.ide.eclipse.adt.internal.sdk.Sdk;
+import com.android.sdklib.SdkConstants;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IncrementalProjectBuilder;
+import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.core.runtime.jobs.Job;
+import org.eclipse.jdt.core.IJavaProject;
+import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Display;
@@ -28,41 +42,116 @@ import org.eclipse.swt.widgets.FileDialog;
import org.eclipse.swt.widgets.Shell;
import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
import java.io.IOException;
-import java.util.jar.JarEntry;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipInputStream;
-import java.util.zip.ZipOutputStream;
+import java.io.OutputStream;
+import java.security.PrivateKey;
+import java.security.cert.X509Certificate;
/**
* Export helper for project.
*/
public final class ExportHelper {
- private static IExportCallback sCallback;
-
- public interface IExportCallback {
- void startExportWizard(IProject project);
- }
-
- public static void setCallback(IExportCallback callback) {
- sCallback = callback;
- }
+ /**
+ * Exports a release version of the application created by the given project.
+ * @param project the project to export
+ * @param outputFile the file to write
+ * @param key the key to used for signing. Can be null.
+ * @param certificate the certificate used for signing. Can be null.
+ * @param monitor
+ */
+ public static void export(IProject project, File outputFile, PrivateKey key,
+ X509Certificate certificate, IProgressMonitor monitor) throws CoreException {
+
+ // the export, takes the output of the precompiler & Java builders so it's
+ // important to call build in case the auto-build option of the workspace is disabled.
+ project.build(IncrementalProjectBuilder.INCREMENTAL_BUILD, monitor);
+
+ // if either key or certificate is null, ensure the other is null.
+ if (key == null) {
+ certificate = null;
+ } else if (certificate == null) {
+ key = null;
+ }
- public static void startExportWizard(IProject project) {
- if (sCallback != null) {
- sCallback.startExportWizard(project);
+ try {
+ AndroidPrintStream fakeStream = new AndroidPrintStream(null, null, new OutputStream() {
+ @Override
+ public void write(int b) throws IOException {
+ // do nothing
+ }
+ });
+
+ PostCompilerHelper helper = new PostCompilerHelper(project,
+ fakeStream, fakeStream,
+ false /*debugMode*/, false /*verbose*/);
+
+ // get the list of library projects
+ ProjectState projectState = Sdk.getProjectState(project);
+ IProject[] libProjects = projectState.getFullLibraryProjects();
+
+ // Step 1. Package the resources.
+
+ // tmp file for the packaged resource file. To not disturb the incremental builders
+ // output, all intermediary files are created in tmp files.
+ File resourceFile = File.createTempFile("android_", ".ap_");
+
+ // package the resources.
+ helper.packageResources(
+ project.getFile(SdkConstants.FN_ANDROID_MANIFEST_XML),
+ libProjects,
+ null, // res filter
+ 0, // versionCode
+ resourceFile.getParent(),
+ resourceFile.getName());
+
+ // Step 2. Convert the byte code to Dalvik bytecode
+
+ // tmp file for the packaged resource file.
+ File dexFile = File.createTempFile("android_", ".dex");
+
+ IFolder outputFolder = BaseProjectHelper.getOutputFolder(project);
+
+ IJavaProject javaProject = JavaCore.create(project);
+ IProject[] javaProjects = ProjectHelper.getReferencedProjects(project);
+ IJavaProject[] referencedJavaProjects = PostCompilerHelper.getJavaProjects(
+ javaProjects);
+
+ helper.executeDx(
+ javaProject,
+ outputFolder.getLocation().toOSString(),
+ dexFile.getAbsolutePath(),
+ referencedJavaProjects,
+ null /*resourceMarker*/);
+
+ // Step 3. Final package
+
+ helper.finalPackage(
+ resourceFile.getAbsolutePath(),
+ dexFile.getAbsolutePath(),
+ outputFile.getAbsolutePath(),
+ javaProject,
+ libProjects,
+ referencedJavaProjects,
+ null /*abiFilter*/,
+ key,
+ certificate,
+ null); //resourceMarker
+
+ // success!
+ } catch (Exception e) {
+ //?
}
}
/**
- * Exports an <b>unsigned</b> version of the application created by the given project.
+ * Exports an unsigned release APK after prompting the user for a location.
+ *
+ * <strong>Must be called from the UI thread.</strong>
+ *
* @param project the project to export
*/
- public static void exportProject(IProject project) {
+ public static void exportUnsignedReleaseApk(final IProject project) {
Shell shell = Display.getCurrent().getActiveShell();
// get the java project to get the output directory
@@ -89,103 +178,34 @@ public final class ExportHelper {
fileDialog.setText("Export Project");
fileDialog.setFileName(fileName);
- String saveLocation = fileDialog.open();
+ final String saveLocation = fileDialog.open();
if (saveLocation != null) {
- // get the stream from the original file
-
- ZipInputStream zis = null;
- ZipOutputStream zos = null;
- FileInputStream input = null;
- FileOutputStream output = null;
-
- try {
- input = new FileInputStream(file);
- zis = new ZipInputStream(input);
-
- // get an output stream into the new file
- File saveFile = new File(saveLocation);
- output = new FileOutputStream(saveFile);
- zos = new ZipOutputStream(output);
- } catch (FileNotFoundException e) {
- // only the input/output stream are throwing this exception.
- // so we only have to close zis if output is the one that threw.
- if (zis != null) {
+ new Job("Android Release Export") {
+ @Override
+ protected IStatus run(IProgressMonitor monitor) {
try {
- zis.close();
- } catch (IOException e1) {
- // pass
- }
- }
-
- MessageDialog.openError(shell, "Android IDE Plug-in",
- String.format("Failed to export %1$s: %2$s doesn't exist!",
- project.getName(), file.getPath()));
- return;
- }
-
- try {
- ZipEntry entry;
-
- byte[] buffer = new byte[4096];
-
- while ((entry = zis.getNextEntry()) != null) {
- String name = entry.getName();
-
- // do not take directories or anything inside the META-INF folder since
- // we want to strip the signature.
- if (entry.isDirectory() || name.startsWith("META-INF/")) { //$NON-NL1$
- continue;
- }
-
- ZipEntry newEntry;
-
- // Preserve the STORED method of the input entry.
- if (entry.getMethod() == JarEntry.STORED) {
- newEntry = new JarEntry(entry);
- } else {
- // Create a new entry so that the compressed len is recomputed.
- newEntry = new JarEntry(name);
- }
-
- // add the entry to the jar archive
- zos.putNextEntry(newEntry);
-
- // read the content of the entry from the input stream, and write it into the archive.
- int count;
- while ((count = zis.read(buffer)) != -1) {
- zos.write(buffer, 0, count);
+ export(project,
+ new File(saveLocation),
+ null, //key
+ null, //certificate
+ monitor);
+
+ // this is unsigned export. Let's tell the developers to run zip align
+ AdtPlugin.displayWarning("Android IDE Plug-in", String.format(
+ "An unsigned package of the application was saved at\n%1$s\n\n" +
+ "Before publishing the application you will need to:\n" +
+ "- Sign the application with your release key,\n" +
+ "- run zipalign on the signed package. ZipAlign is located in <SDK>/tools/\n\n" +
+ "Aligning applications allows Android to use application resources\n" +
+ "more efficiently.", saveLocation));
+
+ return Status.OK_STATUS;
+ } catch (CoreException e) {
+ return e.getStatus();
}
-
- // close the entry for this file
- zos.closeEntry();
- zis.closeEntry();
}
+ }.schedule();
- } catch (IOException e) {
- MessageDialog.openError(shell, "Android IDE Plug-in",
- String.format("Failed to export %1$s: %2$s",
- project.getName(), e.getMessage()));
- } finally {
- try {
- zos.close();
- } catch (IOException e) {
- // pass
- }
- try {
- zis.close();
- } catch (IOException e) {
- // pass
- }
- }
-
- // this is unsigned export. Let's tell the developers to run zip align
- MessageDialog.openWarning(shell, "Android IDE Plug-in", String.format(
- "An unsigned package of the application was saved at\n%1$s\n\n" +
- "Before publishing the application you will need to:\n" +
- "- Sign the application with your release key,\n" +
- "- run zipalign on the signed package. ZipAlign is located in <SDK>/tools/\n\n" +
- "Aligning applications allows Android to use application resources\n" +
- "more efficiently.", saveLocation));
}
} else {
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/Sdk.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/Sdk.java
index db71d2c..b7de225 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/Sdk.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/Sdk.java
@@ -20,7 +20,6 @@ import com.android.ddmlib.IDevice;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.AndroidConstants;
import com.android.ide.eclipse.adt.internal.project.AndroidClasspathContainerInitializer;
-import com.android.ide.eclipse.adt.internal.project.AndroidNature;
import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper;
import com.android.ide.eclipse.adt.internal.project.ProjectHelper;
import com.android.ide.eclipse.adt.internal.resources.manager.GlobalProjectMonitor;
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/actions/ExportAction.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/actions/ExportAction.java
index 40edc5e..f9ef11b 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/actions/ExportAction.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/actions/ExportAction.java
@@ -64,7 +64,7 @@ public class ExportAction implements IObjectActionDelegate {
MessageDialog.openError(mShell, "Android Export",
"Android library projects cannot be exported.");
} else {
- ExportHelper.exportProject(project);
+ ExportHelper.exportUnsignedReleaseApk(project);
}
}
}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/build/ApkBuilder.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/build/ApkBuilder.java
index 2bc65c4..8c18c3a 100644
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/build/ApkBuilder.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/build/ApkBuilder.java
@@ -153,11 +153,11 @@ public final class ApkBuilder {
}
}
- private final File mApkFile;
- private final File mResFile;
- private final File mDexFile;
- private final PrintStream mVerboseStream;
- private final SignedJarBuilder mBuilder;
+ private File mApkFile;
+ private File mResFile;
+ private File mDexFile;
+ private PrintStream mVerboseStream;
+ private SignedJarBuilder mBuilder;
private boolean mDebugMode = false;
private boolean mIsSealed = false;
@@ -203,10 +203,121 @@ public final class ApkBuilder {
}
/**
+ * Signing information.
+ *
+ * Both the {@link PrivateKey} and the {@link X509Certificate} are guaranteed to be non-null.
+ *
+ */
+ public final static class SigningInfo {
+ public final PrivateKey key;
+ public final X509Certificate certificate;
+
+ private SigningInfo(PrivateKey key, X509Certificate certificate) {
+ if (key == null || certificate == null) {
+ throw new IllegalArgumentException("key and certificate cannot be null");
+ }
+ this.key = key;
+ this.certificate = certificate;
+ }
+ }
+
+ /**
+ * Returns the key and certificate from a given debug store.
+ *
+ * It is expected that the store password is 'android' and the key alias and password are
+ * 'androiddebugkey' and 'android' respectively.
+ *
+ * @param storeOsPath the OS path to the debug store.
+ * @param verboseStream an option {@link PrintStream} to display verbose information
+ * @return they key and certificate in a {@link SigningInfo} object or null.
+ * @throws ApkCreationException
+ */
+ public static SigningInfo getDebugKey(String storeOsPath, final PrintStream verboseStream)
+ throws ApkCreationException {
+ try {
+ if (storeOsPath != null) {
+ File storeFile = new File(storeOsPath);
+ try {
+ checkInputFile(storeFile);
+ } catch (FileNotFoundException e) {
+ // ignore these since the debug store can be created on the fly anyway.
+ }
+
+ // get the debug key
+ if (verboseStream != null) {
+ verboseStream.println(String.format("Using keystore: %s", storeOsPath));
+ }
+
+ IKeyGenOutput keygenOutput = null;
+ if (verboseStream != null) {
+ keygenOutput = new IKeyGenOutput() {
+ public void out(String message) {
+ verboseStream.println(message);
+ }
+
+ public void err(String message) {
+ verboseStream.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()));
+ }
+
+ return new SigningInfo(key, certificate);
+ } else {
+ return null;
+ }
+ } 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(), e);
+ } 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(), e);
+ }
+ } catch (ApkCreationException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new ApkCreationException(e);
+ }
+ }
+
+ /**
* 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 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.
+ * @param verboseStream the stream to which verbose output should go. If null, verbose mode
+ * is not enabled.
* @throws ApkCreationException
*/
public ApkBuilder(String apkOsPath, String resOsPath, String dexOsPath, String storeOsPath,
@@ -224,6 +335,35 @@ public final class ApkBuilder {
* This creates a new builder that will create the specified output file, using the two
* mandatory given input files.
*
+ * Optional {@link PrivateKey} and {@link X509Certificate} can be provided to sign the APK.
+ *
+ * An optional {@link PrintStream} can also be provided for verbose output. If null, there will
+ * be no output.
+ *
+ * @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.
+ * @param key the private key used to sign the package. Can be null.
+ * @param certificate the certificate used to sign the package. Can be null.
+ * @param verboseStream the stream to which verbose output should go. If null, verbose mode
+ * is not enabled.
+ * @throws ApkCreationException
+ */
+ public ApkBuilder(String apkOsPath, String resOsPath, String dexOsPath, PrivateKey key,
+ X509Certificate certificate, PrintStream verboseStream) throws ApkCreationException {
+ this(new File(apkOsPath),
+ new File(resOsPath),
+ dexOsPath != null ? new File(dexOsPath) : null,
+ key, certificate,
+ 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'.
*
@@ -239,7 +379,51 @@ public final class ApkBuilder {
* @throws ApkCreationException
*/
public ApkBuilder(File apkFile, File resFile, File dexFile, String debugStoreOsPath,
- PrintStream verboseStream) throws ApkCreationException {
+ final PrintStream verboseStream) throws ApkCreationException {
+
+ SigningInfo info = getDebugKey(debugStoreOsPath, verboseStream);
+ if (info != null) {
+ init(apkFile, resFile, dexFile, info.key, info.certificate, verboseStream);
+ } else {
+ init(apkFile, resFile, dexFile, null /*key*/, null/*certificate*/, verboseStream);
+ }
+ }
+
+ /**
+ * Creates a new instance.
+ *
+ * This creates a new builder that will create the specified output file, using the two
+ * mandatory given input files.
+ *
+ * Optional {@link PrivateKey} and {@link X509Certificate} can be provided to sign the APK.
+ *
+ * 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 key the private key used to sign the package. Can be null.
+ * @param certificate the certificate used to sign the package. Can be 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, PrivateKey key,
+ X509Certificate certificate, PrintStream verboseStream) throws ApkCreationException {
+ init(apkFile, resFile, dexFile, key, certificate, verboseStream);
+ }
+
+
+ /**
+ * Constructor init method.
+ *
+ * @see #ApkBuilder(File, File, File, String, PrintStream)
+ * @see #ApkBuilder(String, String, String, String, PrintStream)
+ * @see #ApkBuilder(File, File, File, PrivateKey, X509Certificate, PrintStream)
+ */
+ private void init(File apkFile, File resFile, File dexFile, PrivateKey key,
+ X509Certificate certificate, PrintStream verboseStream) throws ApkCreationException {
try {
checkOutputFile(mApkFile = apkFile);
@@ -251,56 +435,9 @@ public final class ApkBuilder {
}
mVerboseStream = verboseStream;
- if (debugStoreOsPath != null) {
- File storeFile = new File(debugStoreOsPath);
- try {
- checkInputFile(storeFile);
- } catch (FileNotFoundException e) {
- // ignore these since the debug store can be created on the fly anyway.
- }
-
- // get the debug key
- verbosePrintln("Using keystore: %s", debugStoreOsPath);
-
- 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(
- debugStoreOsPath, 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 {
- // no debug keystore? build without signing.
- mBuilder = new SignedJarBuilder(
- new FileOutputStream(mApkFile, false /* append */),
- null /* key */, null /* certificate */);
- }
+ mBuilder = new SignedJarBuilder(
+ new FileOutputStream(mApkFile, false /* append */), key,
+ certificate);
verbosePrintln("Packaging %s", mApkFile.getName());
@@ -312,18 +449,6 @@ public final class ApkBuilder {
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 (ApkCreationException e) {
throw e;
} catch (Exception e) {
@@ -690,7 +815,7 @@ public final class ApkBuilder {
* @throws FileNotFoundException if the file is not here.
* @throws ApkCreationException If the file is a folder or a file that cannot be read.
*/
- private void checkInputFile(File file) throws FileNotFoundException, ApkCreationException {
+ private static void checkInputFile(File file) throws FileNotFoundException, ApkCreationException {
if (file.isDirectory()) {
throw new ApkCreationException("%s is a directory!", file);
}
diff --git a/testapps/.gitignore b/testapps/.gitignore
new file mode 100644
index 0000000..0c75cfd
--- /dev/null
+++ b/testapps/.gitignore
@@ -0,0 +1,3 @@
+*/bin
+*/local.properties
+*/gen
diff --git a/testapps/README.txt b/testapps/README.txt
new file mode 100644
index 0000000..e76bc4d
--- /dev/null
+++ b/testapps/README.txt
@@ -0,0 +1,6 @@
+This repository contains test applications used by the SDK automated tests.
+
+These are not meant to be packaged with the SDK.
+
+Each project represents a different test case. For more information, read
+the readme file located in the project directory. \ No newline at end of file
diff --git a/testapps/basicProject/.classpath b/testapps/basicProject/.classpath
new file mode 100644
index 0000000..609aa00
--- /dev/null
+++ b/testapps/basicProject/.classpath
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+ <classpathentry kind="src" path="src"/>
+ <classpathentry kind="src" path="gen"/>
+ <classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/>
+ <classpathentry kind="output" path="bin"/>
+</classpath>
diff --git a/testapps/basicProject/.project b/testapps/basicProject/.project
new file mode 100644
index 0000000..2bf35ce
--- /dev/null
+++ b/testapps/basicProject/.project
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>BasicProject</name>
+ <comment></comment>
+ <projects>
+ </projects>
+ <buildSpec>
+ <buildCommand>
+ <name>com.android.ide.eclipse.adt.ResourceManagerBuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>com.android.ide.eclipse.adt.PreCompilerBuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>org.eclipse.jdt.core.javabuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>com.android.ide.eclipse.adt.ApkBuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ </buildSpec>
+ <natures>
+ <nature>com.android.ide.eclipse.adt.AndroidNature</nature>
+ <nature>org.eclipse.jdt.core.javanature</nature>
+ </natures>
+</projectDescription>
diff --git a/testapps/basicProject/AndroidManifest.xml b/testapps/basicProject/AndroidManifest.xml
new file mode 100644
index 0000000..978c71e
--- /dev/null
+++ b/testapps/basicProject/AndroidManifest.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.tests.basicproject"
+ android:versionCode="1"
+ android:versionName="1.0">
+ <application android:label="@string/app_name" android:icon="@drawable/icon">
+ <activity android:name="Main"
+ android:label="@string/app_name">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+ <uses-sdk android:minSdkVersion="AOSP" />
+</manifest>
diff --git a/testapps/basicProject/build.properties b/testapps/basicProject/build.properties
new file mode 100644
index 0000000..edc7f23
--- /dev/null
+++ b/testapps/basicProject/build.properties
@@ -0,0 +1,17 @@
+# This file is used to override default values used by the Ant build system.
+#
+# This file must be checked in Version Control Systems, as it is
+# integral to the build system of your project.
+
+# This file is only used by the Ant script.
+
+# You can use this to override default values such as
+# 'source.dir' for the location of your java source folder and
+# 'out.dir' for the location of your output folder.
+
+# You can also use it define how the release builds are signed by declaring
+# the following properties:
+# 'key.store' for the location of your keystore and
+# 'key.alias' for the name of the key to use.
+# The password will be asked during the build when you use the 'release' target.
+
diff --git a/testapps/basicProject/build.xml b/testapps/basicProject/build.xml
new file mode 100644
index 0000000..9ac802c
--- /dev/null
+++ b/testapps/basicProject/build.xml
@@ -0,0 +1,84 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project name="BasicProject" default="help">
+
+<!-- The local.properties file is created and updated by the 'android'
+ tool.
+ It contains the path to the SDK. It should *NOT* be checked into
+ Version Control Systems. -->
+ <property file="local.properties" />
+
+ <!-- The build.properties file can be created by you and is never touched
+ by the 'android' tool. This is the place to change some of the
+ default property values used by the Ant rules.
+ Here are some properties you may want to change/update:
+
+ source.dir
+ The name of the source directory. Default is 'src'.
+ out.dir
+ The name of the output directory. Default is 'bin'.
+
+ Properties related to the SDK location or the project target should
+ be updated using the 'android' tool with the 'update' action.
+
+ This file is an integral part of the build system for your
+ application and should be checked into Version Control Systems.
+
+ -->
+ <property file="build.properties" />
+
+ <!-- The default.properties file is created and updated by the 'android'
+ tool, as well as ADT.
+ This file is an integral part of the build system for your
+ application and should be checked into Version Control Systems. -->
+ <property file="default.properties" />
+
+ <!-- Custom Android task to deal with the project target, and import the
+ proper rules.
+ This requires ant 1.6.0 or above. -->
+ <path id="android.antlibs">
+ <pathelement path="${sdk.dir}/tools/lib/anttasks.jar" />
+ <pathelement path="${sdk.dir}/tools/lib/sdklib.jar" />
+ <pathelement path="${sdk.dir}/tools/lib/androidprefs.jar" />
+ </path>
+
+ <taskdef name="setup"
+ classname="com.android.ant.SetupTask"
+ classpathref="android.antlibs" />
+
+<!-- extension targets. Uncomment the ones where you want to do custom work
+ in between standard targets -->
+<!--
+ <target name="-pre-build">
+ </target>
+ <target name="-pre-compile">
+ </target>
+
+ [This is typically used for code obfuscation.
+ Compiled code location: ${out.classes.absolute.dir}
+ If this is not done in place, override ${out.dex.input.absolute.dir}]
+ <target name="-post-compile">
+ </target>
+-->
+
+
+ <!-- Execute the Android Setup task that will setup some properties
+ specific to the target, and import the build rules files.
+
+ The rules file is imported from
+ <SDK>/platforms/<target_platform>/ant/ant_rules_r#.xml
+
+ To customize existing targets, there are two options:
+ - Customize only one target:
+ - copy/paste the target into this file, *before* the
+ <setup> task.
+ - customize it to your needs.
+ - Customize the whole script.
+ - copy/paste the content of the rules files (minus the top node)
+ into this file, *after* the <setup> task
+ - disable the import of the rules by changing the setup task
+ below to <setup import="false" />.
+ - customize to your needs.
+ -->
+ <setup />
+
+</project>
diff --git a/testapps/basicProject/default.properties b/testapps/basicProject/default.properties
new file mode 100644
index 0000000..640d5d4
--- /dev/null
+++ b/testapps/basicProject/default.properties
@@ -0,0 +1,11 @@
+# This file is automatically generated by Android Tools.
+# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
+#
+# This file must be checked in Version Control Systems.
+#
+# To customize properties used by the Ant build system use,
+# "build.properties", and override values to adapt the script to your
+# project structure.
+
+# Project target.
+target=android-AOSP
diff --git a/testapps/basicProject/res/drawable-hdpi/icon.png b/testapps/basicProject/res/drawable-hdpi/icon.png
new file mode 100644
index 0000000..8074c4c
--- /dev/null
+++ b/testapps/basicProject/res/drawable-hdpi/icon.png
Binary files differ
diff --git a/testapps/basicProject/res/drawable-ldpi/icon.png b/testapps/basicProject/res/drawable-ldpi/icon.png
new file mode 100644
index 0000000..1095584
--- /dev/null
+++ b/testapps/basicProject/res/drawable-ldpi/icon.png
Binary files differ
diff --git a/testapps/basicProject/res/drawable-mdpi/icon.png b/testapps/basicProject/res/drawable-mdpi/icon.png
new file mode 100644
index 0000000..a07c69f
--- /dev/null
+++ b/testapps/basicProject/res/drawable-mdpi/icon.png
Binary files differ
diff --git a/testapps/basicProject/res/layout/main.xml b/testapps/basicProject/res/layout/main.xml
new file mode 100644
index 0000000..829994c
--- /dev/null
+++ b/testapps/basicProject/res/layout/main.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ >
+<TextView
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:text="Hello World, Main"
+ />
+</LinearLayout>
+
diff --git a/testapps/basicProject/res/values/strings.xml b/testapps/basicProject/res/values/strings.xml
new file mode 100644
index 0000000..549e4ea
--- /dev/null
+++ b/testapps/basicProject/res/values/strings.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <string name="app_name">Main</string>
+</resources>
diff --git a/testapps/basicProject/src/com/android/tests/basicproject/Main.java b/testapps/basicProject/src/com/android/tests/basicproject/Main.java
new file mode 100644
index 0000000..1240556
--- /dev/null
+++ b/testapps/basicProject/src/com/android/tests/basicproject/Main.java
@@ -0,0 +1,15 @@
+package com.android.tests.basicproject;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+public class Main extends Activity
+{
+ /** Called when the activity is first created. */
+ @Override
+ public void onCreate(Bundle savedInstanceState)
+ {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.main);
+ }
+}