From 67dc23a61d888ba1b39fe6ed23d9f4d1a3df80ee Mon Sep 17 00:00:00 2001 From: The Android Open Source Project Date: Mon, 9 Mar 2009 11:52:11 -0700 Subject: auto import from //branches/cupcake/...@137197 --- .../android/draw9patch/ui/ImageEditorPanel.java | 54 +- .../draw9patch/ui/ImageTransferHandler.java | 37 +- .../com.android.ide.eclipse.adt/feature.xml | 1 + .../plugins/com.android.ide.eclipse.adt/.classpath | 4 +- .../META-INF/MANIFEST.MF | 4 +- .../plugins/com.android.ide.eclipse.adt/plugin.xml | 33 +- .../src/com/android/ide/eclipse/adt/AdtPlugin.java | 2 +- .../android/ide/eclipse/adt/build/BaseBuilder.java | 54 +- .../ide/eclipse/adt/build/PreCompilerBuilder.java | 1194 ++++++------- .../eclipse/adt/build/PreCompilerDeltaVisitor.java | 285 +-- .../eclipse/adt/build/ResourceManagerBuilder.java | 103 +- .../eclipse/adt/debug/launching/AndroidLaunch.java | 57 - .../debug/launching/AndroidLaunchController.java | 1838 -------------------- .../adt/debug/launching/DeviceChooserDialog.java | 705 -------- .../adt/debug/launching/LaunchConfigDelegate.java | 431 ----- .../adt/debug/launching/LaunchShortcut.java | 87 - .../eclipse/adt/debug/ui/EmulatorConfigTab.java | 459 ----- .../eclipse/adt/debug/ui/LaunchConfigTabGroup.java | 40 - .../eclipse/adt/debug/ui/MainLaunchConfigTab.java | 489 ------ .../ide/eclipse/adt/launch/AndroidLaunch.java | 57 + .../adt/launch/AndroidLaunchController.java | 1837 +++++++++++++++++++ .../eclipse/adt/launch/DeviceChooserDialog.java | 705 ++++++++ .../ide/eclipse/adt/launch/EmulatorConfigTab.java | 458 +++++ .../adt/launch/JUnitLaunchConfigDelegate.java | 155 ++ .../eclipse/adt/launch/LaunchConfigDelegate.java | 431 +++++ .../eclipse/adt/launch/LaunchConfigTabGroup.java | 40 + .../ide/eclipse/adt/launch/LaunchShortcut.java | 87 + .../eclipse/adt/launch/MainLaunchConfigTab.java | 487 ++++++ .../adt/project/CreateAidlImportAction.java | 210 --- .../ide/eclipse/adt/project/FixLaunchConfig.java | 2 +- .../ide/eclipse/adt/project/FolderDecorator.java | 40 +- .../ide/eclipse/common/AndroidConstants.java | 1 - .../META-INF/MANIFEST.MF | 2 +- .../adt/launch/JUnitLaunchConfigDelegateTest.java | 110 ++ .../ide/eclipse/tests/EclipseTestCollector.java | 21 +- .../com/android/ide/eclipse/tests/UnitTests.java | 2 +- .../editors/resources/manager/ConfigMatchTest.java | 2 - .../resources/manager/QualifierListTest.java | 2 - .../com/android/ide/eclipse/mock/FileMock.java | 6 +- .../com/android/ide/eclipse/mock/FolderMock.java | 4 +- .../com/android/ide/eclipse/mock/ProjectMock.java | 8 +- ninepatch/src/com/android/ninepatch/NinePatch.java | 96 +- 42 files changed, 5335 insertions(+), 5305 deletions(-) delete mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/debug/launching/AndroidLaunch.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/debug/launching/AndroidLaunchController.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/debug/launching/DeviceChooserDialog.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/debug/launching/LaunchConfigDelegate.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/debug/launching/LaunchShortcut.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/debug/ui/EmulatorConfigTab.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/debug/ui/LaunchConfigTabGroup.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/debug/ui/MainLaunchConfigTab.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/AndroidLaunch.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/AndroidLaunchController.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/DeviceChooserDialog.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/EmulatorConfigTab.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/JUnitLaunchConfigDelegate.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/LaunchConfigDelegate.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/LaunchConfigTabGroup.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/LaunchShortcut.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/MainLaunchConfigTab.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/CreateAidlImportAction.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/launch/JUnitLaunchConfigDelegateTest.java diff --git a/draw9patch/src/com/android/draw9patch/ui/ImageEditorPanel.java b/draw9patch/src/com/android/draw9patch/ui/ImageEditorPanel.java index 6901c98..84b96a5 100644 --- a/draw9patch/src/com/android/draw9patch/ui/ImageEditorPanel.java +++ b/draw9patch/src/com/android/draw9patch/ui/ImageEditorPanel.java @@ -478,6 +478,14 @@ class ImageEditorPanel extends JPanel { start = rect.x; } } + } else { + int start = -1; + for (Rectangle rect : patches) { + if (rect.x > start) { + horizontalPatchesSum += rect.width; + start = rect.x; + } + } } verticalPatchesSum = 0; @@ -489,6 +497,14 @@ class ImageEditorPanel extends JPanel { start = rect.y; } } + } else { + int start = -1; + for (Rectangle rect : patches) { + if (rect.y > start) { + verticalPatchesSum += rect.height; + start = rect.y; + } + } } setSize(size); @@ -528,8 +544,7 @@ class ImageEditorPanel extends JPanel { x = 0; y = 0; - if (patches.size() == 0 || horizontalPatches.size() == 0 || - verticalPatches.size() == 0) { + if (patches.size() == 0) { g.drawImage(image, 0, 0, scaledWidth, scaledHeight, null); g2.dispose(); return; @@ -1028,7 +1043,15 @@ class ImageEditorPanel extends JPanel { horizontalPatches = getRectangles(left.first, top.second); verticalPatches = getRectangles(left.second, top.first); } else { - horizontalPatches = verticalPatches = new ArrayList(0); + if (top.first.size() > 0) { + horizontalPatches = new ArrayList(0); + verticalPatches = getVerticalRectangles(top.first); + } else if (left.first.size() > 0) { + horizontalPatches = getHorizontalRectangles(left.first); + verticalPatches = new ArrayList(0); + } else { + horizontalPatches = verticalPatches = new ArrayList(0); + } } row = GraphicsUtilities.getPixels(image, 0, height - 1, width, 1, row); @@ -1041,6 +1064,28 @@ class ImageEditorPanel extends JPanel { verticalPadding = getPadding(left.first); } + private List getVerticalRectangles(List> topPairs) { + List rectangles = new ArrayList(); + for (Pair top : topPairs) { + int x = top.first; + int width = top.second - top.first; + + rectangles.add(new Rectangle(x, 1, width, image.getHeight() - 2)); + } + return rectangles; + } + + private List getHorizontalRectangles(List> leftPairs) { + List rectangles = new ArrayList(); + for (Pair left : leftPairs) { + int y = left.first; + int height = left.second - left.first; + + rectangles.add(new Rectangle(1, y, image.getWidth() - 2, height)); + } + return rectangles; + } + private Pair getPadding(List> pairs) { if (pairs.size() == 0) { return new Pair(0, 0); @@ -1063,7 +1108,7 @@ class ImageEditorPanel extends JPanel { for (Pair left : leftPairs) { int y = left.first; int height = left.second - left.first; - for (Pair top: topPairs) { + for (Pair top : topPairs) { int x = top.first; int width = top.second - top.first; @@ -1108,6 +1153,7 @@ class ImageEditorPanel extends JPanel { startWithPatch[0] = true; fixed.clear(); } + return new Pair>>(fixed, patches); } diff --git a/draw9patch/src/com/android/draw9patch/ui/ImageTransferHandler.java b/draw9patch/src/com/android/draw9patch/ui/ImageTransferHandler.java index a62884f..f14cd77 100644 --- a/draw9patch/src/com/android/draw9patch/ui/ImageTransferHandler.java +++ b/draw9patch/src/com/android/draw9patch/ui/ImageTransferHandler.java @@ -36,25 +36,48 @@ class ImageTransferHandler extends TransferHandler { @Override public boolean importData(JComponent component, Transferable transferable) { try { - Object data = transferable.getTransferData(DataFlavor.javaFileListFlavor); - //noinspection unchecked - final File file = ((List) data).get(0); - mainFrame.open(file).execute(); + for (DataFlavor flavor : transferable.getTransferDataFlavors()) { + if (flavor.isFlavorJavaFileListType()) { + Object data = transferable.getTransferData(DataFlavor.javaFileListFlavor); + //noinspection unchecked + final File file = ((List) data).get(0); + mainFrame.open(file).execute(); + return true; + } else if (flavor.isFlavorTextType()) { + if (flavor.getRepresentationClass() == String.class) { + String mime = flavor.getMimeType(); + DataFlavor flave = new DataFlavor(mime); + Object data = transferable.getTransferData(flave); + final String path = convertPath(data.toString()); + mainFrame.open(new File(path)).execute(); + return true; + } + } + } } catch (UnsupportedFlavorException e) { - return false; + // Ignore } catch (MalformedURLException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); + } catch (Exception e) { + e.printStackTrace(); } - return true; + return false; + } + + private static String convertPath(String path) { + if (path.startsWith("file://")) path = path.substring("file://".length()); + if (path.indexOf('\n') != -1) path = path.substring(0, path.indexOf('\n')); + if (path.indexOf('\r') != -1) path = path.substring(0, path.indexOf('\r')); + return path; } @Override public boolean canImport(JComponent component, DataFlavor[] dataFlavors) { for (DataFlavor flavor : dataFlavors) { - if (flavor.isFlavorJavaFileListType()) { + if (flavor.isFlavorJavaFileListType() || flavor.isFlavorTextType()) { return true; } } diff --git a/eclipse/features/com.android.ide.eclipse.adt/feature.xml b/eclipse/features/com.android.ide.eclipse.adt/feature.xml index b9e2c7f..97bc8b1 100644 --- a/eclipse/features/com.android.ide.eclipse.adt/feature.xml +++ b/eclipse/features/com.android.ide.eclipse.adt/feature.xml @@ -134,6 +134,7 @@ This Agreement is governed by the laws of the State of New York and the intellec + - - + + diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/META-INF/MANIFEST.MF b/eclipse/plugins/com.android.ide.eclipse.adt/META-INF/MANIFEST.MF index a464d5c..3750f66 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/META-INF/MANIFEST.MF +++ b/eclipse/plugins/com.android.ide.eclipse.adt/META-INF/MANIFEST.MF @@ -39,10 +39,12 @@ Require-Bundle: com.android.ide.eclipse.ddms, org.eclipse.wst.sse.core, org.eclipse.wst.sse.ui, org.eclipse.wst.xml.core, - org.eclipse.wst.xml.ui + org.eclipse.wst.xml.ui, + org.eclipse.jdt.junit Eclipse-LazyStart: true Export-Package: com.android.ide.eclipse.adt, com.android.ide.eclipse.adt.build;x-friends:="com.android.ide.eclipse.tests", + com.android.ide.eclipse.adt.launch;x-friends:="com.android.ide.eclipse.tests", com.android.ide.eclipse.adt.project;x-friends:="com.android.ide.eclipse.tests", com.android.ide.eclipse.adt.project.internal;x-friends:="com.android.ide.eclipse.tests", com.android.ide.eclipse.adt.sdk;x-friends:="com.android.ide.eclipse.tests", diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/plugin.xml b/eclipse/plugins/com.android.ide.eclipse.adt/plugin.xml index d6c9ac1..4dfcc07 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/plugin.xml +++ b/eclipse/plugins/com.android.ide.eclipse.adt/plugin.xml @@ -111,7 +111,7 @@ @@ -140,8 +140,8 @@ - + id="com.android.ide.eclipse.adt.launch.LaunchShortcut.debug"> + id="com.android.ide.eclipse.adt.launch.LaunchShortcut.run"> @@ -499,4 +493,15 @@ + + + + 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 61be3e5..48a21d1 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 @@ -20,7 +20,7 @@ import com.android.ddmuilib.StackTracePanel; import com.android.ddmuilib.StackTracePanel.ISourceRevealer; import com.android.ddmuilib.console.DdmConsole; import com.android.ddmuilib.console.IDdmConsole; -import com.android.ide.eclipse.adt.debug.launching.AndroidLaunchController; +import com.android.ide.eclipse.adt.launch.AndroidLaunchController; import com.android.ide.eclipse.adt.preferences.BuildPreferencePage; import com.android.ide.eclipse.adt.project.ProjectHelper; import com.android.ide.eclipse.adt.project.export.ExportWizard; diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/BaseBuilder.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/BaseBuilder.java index e2e9728..c3d5ba6 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/BaseBuilder.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/BaseBuilder.java @@ -20,15 +20,14 @@ import com.android.ide.eclipse.adt.AdtConstants; import com.android.ide.eclipse.adt.AdtPlugin; import com.android.ide.eclipse.adt.project.ProjectHelper; import com.android.ide.eclipse.adt.sdk.LoadStatus; -import com.android.ide.eclipse.adt.sdk.Sdk; import com.android.ide.eclipse.common.AndroidConstants; import com.android.ide.eclipse.common.project.BaseProjectHelper; import com.android.ide.eclipse.common.project.XmlErrorHandler; import com.android.ide.eclipse.common.project.XmlErrorHandler.XmlErrorListener; -import com.android.sdklib.IAndroidTarget; import org.eclipse.core.resources.IContainer; 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; @@ -37,8 +36,10 @@ import org.eclipse.core.resources.IncrementalProjectBuilder; import org.eclipse.core.resources.ResourcesPlugin; 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.SubProgressMonitor; import org.eclipse.jdt.core.IClasspathEntry; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.JavaCore; @@ -892,25 +893,19 @@ abstract class BaseBuilder extends IncrementalProjectBuilder { stopBuild("SDK is not loaded yet"); } - // check the compiler compliance level. - if (ProjectHelper.checkCompilerCompliance(project) != - ProjectHelper.COMPILER_COMPLIANCE_OK) { - // we exit silently - stopBuild(Messages.Compiler_Compliance_Error); - } - - // Check that the SDK directory has been setup. - String osSdkFolder = AdtPlugin.getOsSdkFolder(); - - if (osSdkFolder == null || osSdkFolder.length() == 0) { - stopBuild(Messages.No_SDK_Setup_Error); + // abort if there are TARGET or ADT type markers + IMarker[] markers = project.findMarkers(AdtConstants.MARKER_TARGET, + false /*includeSubtypes*/, IResource.DEPTH_ZERO); + + if (markers.length > 0) { + stopBuild(""); } - - IAndroidTarget projectTarget = Sdk.getCurrent().getTarget(project); - if (projectTarget == null) { - // no target. error has been output by the container initializer: - // exit silently. - stopBuild("Project has no target"); + + markers = project.findMarkers(AdtConstants.MARKER_ADT, false /*includeSubtypes*/, + IResource.DEPTH_ZERO); + + if (markers.length > 0) { + stopBuild(""); } } @@ -925,5 +920,22 @@ abstract class BaseBuilder extends IncrementalProjectBuilder { throw new CoreException(new Status(IStatus.CANCEL, AdtPlugin.PLUGIN_ID, String.format(error, args))); } - + + /** + * Recursively delete all the derived resources. + */ + protected void removeDerivedResources(IResource resource, IProgressMonitor monitor) + throws CoreException { + if (resource.exists()) { + if (resource.isDerived()) { + resource.delete(true, new SubProgressMonitor(monitor, 10)); + } else if (resource.getType() == IResource.FOLDER) { + IFolder folder = (IFolder)resource; + IResource[] members = folder.members(); + for (IResource member : members) { + removeDerivedResources(member, monitor); + } + } + } + } } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/PreCompilerBuilder.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/PreCompilerBuilder.java index a0e446c..fb1608c 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/PreCompilerBuilder.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/PreCompilerBuilder.java @@ -19,7 +19,6 @@ package com.android.ide.eclipse.adt.build; import com.android.ide.eclipse.adt.AdtConstants; import com.android.ide.eclipse.adt.AdtPlugin; import com.android.ide.eclipse.adt.project.FixLaunchConfig; -import com.android.ide.eclipse.adt.project.ProjectHelper; import com.android.ide.eclipse.adt.sdk.Sdk; import com.android.ide.eclipse.common.AndroidConstants; import com.android.ide.eclipse.common.project.AndroidManifestHelper; @@ -27,8 +26,8 @@ import com.android.ide.eclipse.common.project.AndroidManifestParser; import com.android.ide.eclipse.common.project.BaseProjectHelper; import com.android.ide.eclipse.common.project.XmlErrorHandler.BasicXmlErrorListener; import com.android.sdklib.IAndroidTarget; +import com.android.sdklib.SdkConstants; -import org.eclipse.core.resources.IContainer; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IFolder; import org.eclipse.core.resources.IMarker; @@ -36,13 +35,13 @@ import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.IResourceDelta; import org.eclipse.core.resources.IWorkspaceRoot; -import org.eclipse.core.resources.ResourceAttributes; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.core.runtime.Path; +import org.eclipse.core.runtime.SubProgressMonitor; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.JavaCore; @@ -68,13 +67,8 @@ public class PreCompilerBuilder extends BaseBuilder { private static final String PROPERTY_PACKAGE = "manifestPackage"; //$NON-NLS-1$ - private static final String PROPERTY_SOURCE_FOLDER = - "manifestPackageSourceFolder"; //$NON-NLS-1$ - private static final String PROPERTY_COMPILE_RESOURCES = "compileResources"; //$NON-NLS-1$ - - static final String PROPERTY_ANDROID_GENERATED = "androidGenerated"; //$NON-NLS-1$ - static final String PROPERTY_ANDROID_CONFLICT = "androidConflict"; //$NON-NLS-1$ + private static final String PROPERTY_COMPILE_AIDL = "compileAidl"; //$NON-NLS-1$ /** * Single line aidl error
@@ -83,24 +77,58 @@ public class PreCompilerBuilder extends BaseBuilder { private static Pattern sAidlPattern1 = Pattern.compile("^(.+?):(\\d+):\\s(.+)$"); //$NON-NLS-1$ /** - * Compile flag. This is set to true if one of the changed/added/removed - * file is a resource file. Upon visiting all the delta resources, if - * this flag is true, then we know we'll have to compile the resources - * into R.java + * Data to temporarly store aidl source file information + */ + static class AidlData { + IFile aidlFile; + IFolder sourceFolder; + + AidlData(IFolder sourceFolder, IFile aidlFile) { + this.sourceFolder = sourceFolder; + this.aidlFile = aidlFile; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + + if (obj instanceof AidlData) { + AidlData file = (AidlData)obj; + return aidlFile.equals(file.aidlFile) && sourceFolder.equals(file.sourceFolder); + } + + return false; + } + } + + /** + * Resource Compile flag. This flag is reset to false after each successful compilation, and + * stored in the project persistent properties. This allows the builder to remember its state + * when the project is closed/opened. */ - private boolean mCompileResources = false; + private boolean mMustCompileResources = false; /** List of .aidl files found that are modified or new. */ - private final ArrayList mAidlToCompile = new ArrayList(); + private final ArrayList mAidlToCompile = new ArrayList(); /** List of .aidl files that have been removed. */ - private final ArrayList mAidlToRemove = new ArrayList(); + private final ArrayList mAidlToRemove = new ArrayList(); /** cache of the java package defined in the manifest */ private String mManifestPackage; + + /** Output folder for generated Java File. Created on the Builder init + * @see #startupOnInitialize() + */ + private IFolder mGenFolder; - /** Source folder containing the java package defined in the manifest. */ - private IFolder mManifestPackageSourceFolder; + /** + * Progress monitor used at the end of every build to refresh the content of the 'gen' folder + * and set the generated files as derived. + */ + private DerivedProgressMonitor mDerivedProgressMonitor; /** * Progress monitor waiting the end of the process to set a persistent value @@ -109,91 +137,36 @@ public class PreCompilerBuilder extends BaseBuilder { * to be known by eclipse, before we can call resource.setPersistentProperty on * a new file. */ - private static class RefreshProgressMonitor implements IProgressMonitor { + private static class DerivedProgressMonitor implements IProgressMonitor { private boolean mCancelled = false; - private IFile mNewFile; - private IFile mSource; - private boolean mDoneExecuted = false; - public RefreshProgressMonitor(IFile newFile, IFile source) { - mNewFile = newFile; - mSource = source; - } - - public void beginTask(String name, int totalWork) { - } - - public void done() { - if (mDoneExecuted == false) { - mDoneExecuted = true; - if (mNewFile.exists()) { - ProjectHelper.saveResourceProperty(mNewFile, PROPERTY_ANDROID_GENERATED, - mSource); - try { - mNewFile.setDerived(true); - } catch (CoreException e) { - // This really shouldn't happen since we check that the resource exist. - // Worst case scenario, the resource isn't marked as derived. - } - } - } - } - - public void internalWorked(double work) { - } - - public boolean isCanceled() { - return mCancelled; - } - - public void setCanceled(boolean value) { - mCancelled = value; - } - - public void setTaskName(String name) { + private final ArrayList mFileList = new ArrayList(); + private boolean mDone = false; + public DerivedProgressMonitor() { } - - public void subTask(String name) { - } - - public void worked(int work) { + + void addFile(IFile file) { + mFileList.add(file); } - } - - /** - * Progress Monitor setting up to two files as derived once their parent is refreshed. - * This is used as ProgressMonitor to refresh the R.java/Manifest.java parent (to display - * the newly created files in the package explorer). - */ - private static class DerivedProgressMonitor implements IProgressMonitor { - private boolean mCancelled = false; - private IFile mFile1; - private IFile mFile2; - private boolean mDoneExecuted = false; - public DerivedProgressMonitor(IFile file1, IFile file2) { - mFile1 = file1; - mFile2 = file2; + + void reset() { + mFileList.clear(); + mDone = false; } public void beginTask(String name, int totalWork) { } public void done() { - if (mDoneExecuted == false) { - if (mFile1 != null && mFile1.exists()) { - mDoneExecuted = true; - try { - mFile1.setDerived(true); - } catch (CoreException e) { - // This really shouldn't happen since we check that the resource edit. - // Worst case scenario, the resource isn't marked as derived. - } - } - if (mFile2 != null && mFile2.exists()) { - try { - mFile2.setDerived(true); - } catch (CoreException e) { - // This really shouldn't happen since we check that the resource edit. - // Worst case scenario, the resource isn't marked as derived. + if (mDone == false) { + mDone = true; + for (IFile file : mFileList) { + if (file.exists()) { + try { + file.setDerived(true); + } catch (CoreException e) { + // This really shouldn't happen since we check that the resource exist. + // Worst case scenario, the resource isn't marked as derived. + } } } } @@ -229,322 +202,306 @@ public class PreCompilerBuilder extends BaseBuilder { @Override protected IProject[] build(int kind, Map args, IProgressMonitor monitor) throws CoreException { - // First thing we do is go through the resource delta to not - // lose it if we have to abort the build for any reason. + try { + mDerivedProgressMonitor.reset(); - // get the project objects - IProject project = getProject(); - - // Top level check to make sure the build can move forward. - abortOnBadSetup(project); - - IJavaProject javaProject = JavaCore.create(project); - IAndroidTarget projectTarget = Sdk.getCurrent().getTarget(project); - - // now we need to get the classpath list - ArrayList sourceList = BaseProjectHelper.getSourceClasspaths(javaProject); - - PreCompilerDeltaVisitor dv = null; - String javaPackage = null; - - if (kind == FULL_BUILD) { - AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, project, - Messages.Start_Full_Pre_Compiler); - mCompileResources = true; - buildAidlCompilationList(project, sourceList); - } else { - AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, project, - Messages.Start_Inc_Pre_Compiler); - - // Go through the resources and see if something changed. - // Even if the mCompileResources flag is true from a previously aborted - // build, we need to go through the Resource delta to get a possible - // list of aidl files to compile/remove. - IResourceDelta delta = getDelta(project); - if (delta == null) { - mCompileResources = true; - buildAidlCompilationList(project, sourceList); + // First thing we do is go through the resource delta to not + // lose it if we have to abort the build for any reason. + + // get the project objects + IProject project = getProject(); + + // Top level check to make sure the build can move forward. + abortOnBadSetup(project); + + IJavaProject javaProject = JavaCore.create(project); + IAndroidTarget projectTarget = Sdk.getCurrent().getTarget(project); + + // now we need to get the classpath list + ArrayList sourceFolderPathList = BaseProjectHelper.getSourceClasspaths( + javaProject); + + PreCompilerDeltaVisitor dv = null; + String javaPackage = null; + + if (kind == FULL_BUILD) { + AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, project, + Messages.Start_Full_Pre_Compiler); + mMustCompileResources = true; + buildAidlCompilationList(project, sourceFolderPathList); } else { - dv = new PreCompilerDeltaVisitor(this, sourceList); - delta.accept(dv); - - // record the state - mCompileResources |= dv.getCompileResources(); - - // handle aidl modification - if (dv.getFullAidlRecompilation()) { - buildAidlCompilationList(project, sourceList); + AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, project, + Messages.Start_Inc_Pre_Compiler); + + // Go through the resources and see if something changed. + // Even if the mCompileResources flag is true from a previously aborted + // build, we need to go through the Resource delta to get a possible + // list of aidl files to compile/remove. + IResourceDelta delta = getDelta(project); + if (delta == null) { + mMustCompileResources = true; + buildAidlCompilationList(project, sourceFolderPathList); } else { + dv = new PreCompilerDeltaVisitor(this, sourceFolderPathList); + delta.accept(dv); + + // record the state + mMustCompileResources |= dv.getCompileResources(); + + // handle aidl modification, and update mMustCompileAidl mergeAidlFileModifications(dv.getAidlToCompile(), dv.getAidlToRemove()); + + // get the java package from the visitor + javaPackage = dv.getManifestPackage(); } - - // get the java package from the visitor - javaPackage = dv.getManifestPackage(); } - } - - // store the build status in the persistent storage - saveProjectBooleanProperty(PROPERTY_COMPILE_RESOURCES , mCompileResources); - // TODO also needs to store the list of aidl to compile/remove - - // if there was some XML errors, we just return w/o doing - // anything since we've put some markers in the files anyway. - if (dv != null && dv.mXmlError) { - AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, project, - Messages.Xml_Error); - - // This interrupts the build. The next builders will not run. - stopBuild(Messages.Xml_Error); - } - - - // get the manifest file - IFile manifest = AndroidManifestHelper.getManifest(project); - - if (manifest == null) { - String msg = String.format(Messages.s_File_Missing, - AndroidConstants.FN_ANDROID_MANIFEST); - AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, project, msg); - markProject(AdtConstants.MARKER_ADT, msg, IMarker.SEVERITY_ERROR); - - // This interrupts the build. The next builders will not run. - stopBuild(msg); - } - - // lets check the XML of the manifest first, if that hasn't been done by the - // resource delta visitor yet. - if (dv == null || dv.getCheckedManifestXml() == false) { - BasicXmlErrorListener errorListener = new BasicXmlErrorListener(); - AndroidManifestParser parser = BaseProjectHelper.parseManifestForError(manifest, - errorListener); - - if (errorListener.mHasXmlError == true) { - // there was an error in the manifest, its file has been marked, - // by the XmlErrorHandler. - // We return; - String msg = String.format(Messages.s_Contains_Xml_Error, + + // store the build status in the persistent storage + saveProjectBooleanProperty(PROPERTY_COMPILE_RESOURCES , mMustCompileResources); + + // if there was some XML errors, we just return w/o doing + // anything since we've put some markers in the files anyway. + if (dv != null && dv.mXmlError) { + AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, project, + Messages.Xml_Error); + + // This interrupts the build. The next builders will not run. + stopBuild(Messages.Xml_Error); + } + + + // get the manifest file + IFile manifest = AndroidManifestHelper.getManifest(project); + + if (manifest == null) { + String msg = String.format(Messages.s_File_Missing, AndroidConstants.FN_ANDROID_MANIFEST); AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, project, msg); - + markProject(AdtConstants.MARKER_ADT, msg, IMarker.SEVERITY_ERROR); + // This interrupts the build. The next builders will not run. stopBuild(msg); } - - // get the java package from the parser - javaPackage = parser.getPackage(); - } - - if (javaPackage == null || javaPackage.length() == 0) { - // looks like the AndroidManifest file isn't valid. - String msg = String.format(Messages.s_Doesnt_Declare_Package_Error, - AndroidConstants.FN_ANDROID_MANIFEST); - AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, project, - msg); - - // This interrupts the build. The next builders will not run. - stopBuild(msg); - } - - // at this point we have the java package. We need to make sure it's not a different package - // than the previous one that were built. - if (javaPackage.equals(mManifestPackage) == false) { - // The manifest package has changed, the user may want to update - // the launch configuration - if (mManifestPackage != null) { - AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, project, - Messages.Checking_Package_Change); - - FixLaunchConfig flc = new FixLaunchConfig(project, mManifestPackage, javaPackage); - flc.start(); + + // lets check the XML of the manifest first, if that hasn't been done by the + // resource delta visitor yet. + if (dv == null || dv.getCheckedManifestXml() == false) { + BasicXmlErrorListener errorListener = new BasicXmlErrorListener(); + AndroidManifestParser parser = BaseProjectHelper.parseManifestForError(manifest, + errorListener); + + if (errorListener.mHasXmlError == true) { + // there was an error in the manifest, its file has been marked, + // by the XmlErrorHandler. + // We return; + String msg = String.format(Messages.s_Contains_Xml_Error, + AndroidConstants.FN_ANDROID_MANIFEST); + AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, project, msg); + + // This interrupts the build. The next builders will not run. + stopBuild(msg); + } + + // get the java package from the parser + javaPackage = parser.getPackage(); } - - // now we delete the generated classes from their previous location - deleteObsoleteGeneratedClass(AndroidConstants.FN_RESOURCE_CLASS, - mManifestPackageSourceFolder, mManifestPackage); - deleteObsoleteGeneratedClass(AndroidConstants.FN_MANIFEST_CLASS, - mManifestPackageSourceFolder, mManifestPackage); - - // record the new manifest package, and save it. - mManifestPackage = javaPackage; - saveProjectStringProperty(PROPERTY_PACKAGE, mManifestPackage); - } - - if (mCompileResources) { - // we need to figure out where to store the R class. - // get the parent folder for R.java and update mManifestPackageSourceFolder - IFolder packageFolder = getManifestPackageFolder(project, sourceList); - - // at this point, either we have found the package or not. - // if we haven't well it's time to tell the user and abort - if (mManifestPackageSourceFolder == null) { - // mark the manifest file - String message = String.format(Messages.Package_s_Doesnt_Exist_Error, - mManifestPackage); - BaseProjectHelper.addMarker(manifest, AndroidConstants.MARKER_AAPT_COMPILE, message, - IMarker.SEVERITY_ERROR); - AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, project, message); - - // abort + + if (javaPackage == null || javaPackage.length() == 0) { + // looks like the AndroidManifest file isn't valid. + String msg = String.format(Messages.s_Doesnt_Declare_Package_Error, + AndroidConstants.FN_ANDROID_MANIFEST); + AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, project, + msg); + // This interrupts the build. The next builders will not run. - stopBuild(message); + stopBuild(msg); } - - - // found the folder in which to write the stuff - - // get the resource folder - IFolder resFolder = project.getFolder(AndroidConstants.WS_RESOURCES); - - // get the file system path - IPath outputLocation = mManifestPackageSourceFolder.getLocation(); - IPath resLocation = resFolder.getLocation(); - IPath manifestLocation = manifest.getLocation(); - - // those locations have to exist for us to do something! - if (outputLocation != null && resLocation != null - && manifestLocation != null) { - String osOutputPath = outputLocation.toOSString(); - String osResPath = resLocation.toOSString(); - String osManifestPath = manifestLocation.toOSString(); - - // remove the aapt markers - removeMarkersFromFile(manifest, AndroidConstants.MARKER_AAPT_COMPILE); - removeMarkersFromContainer(resFolder, AndroidConstants.MARKER_AAPT_COMPILE); - - AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, project, - Messages.Preparing_Generated_Files); - - // since the R.java file may be already existing in read-only - // mode we need to make it readable so that aapt can overwrite - // it - IFile rJavaFile = packageFolder.getFile(AndroidConstants.FN_RESOURCE_CLASS); - prepareFileForExternalModification(rJavaFile); - - // do the same for the Manifest.java class - IFile manifestJavaFile = packageFolder.getFile(AndroidConstants.FN_MANIFEST_CLASS); - prepareFileForExternalModification(manifestJavaFile); - - // we actually need to delete the manifest.java as it may become empty and in this - // case aapt doesn't generate an empty one, but instead doesn't touch it. - manifestJavaFile.delete(true, null); - - // launch aapt: create the command line - ArrayList array = new ArrayList(); - array.add(projectTarget.getPath(IAndroidTarget.AAPT)); - array.add("package"); //$NON-NLS-1$ - array.add("-m"); //$NON-NLS-1$ - if (AdtPlugin.getBuildVerbosity() == AdtConstants.BUILD_VERBOSE) { - array.add("-v"); //$NON-NLS-1$ + + // at this point we have the java package. We need to make sure it's not a different + // package than the previous one that were built. + if (javaPackage.equals(mManifestPackage) == false) { + // The manifest package has changed, the user may want to update + // the launch configuration + if (mManifestPackage != null) { + AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, project, + Messages.Checking_Package_Change); + + FixLaunchConfig flc = new FixLaunchConfig(project, mManifestPackage, + javaPackage); + flc.start(); } - array.add("-J"); //$NON-NLS-1$ - array.add(osOutputPath); - array.add("-M"); //$NON-NLS-1$ - array.add(osManifestPath); - array.add("-S"); //$NON-NLS-1$ - array.add(osResPath); - array.add("-I"); //$NON-NLS-1$ - array.add(projectTarget.getPath(IAndroidTarget.ANDROID_JAR)); - - if (AdtPlugin.getBuildVerbosity() == AdtConstants.BUILD_VERBOSE) { - StringBuilder sb = new StringBuilder(); - for (String c : array) { - sb.append(c); - sb.append(' '); + + // now we delete the generated classes from their previous location + deleteObsoleteGeneratedClass(AndroidConstants.FN_RESOURCE_CLASS, + mManifestPackage); + deleteObsoleteGeneratedClass(AndroidConstants.FN_MANIFEST_CLASS, + mManifestPackage); + + // record the new manifest package, and save it. + mManifestPackage = javaPackage; + saveProjectStringProperty(PROPERTY_PACKAGE, mManifestPackage); + } + + if (mMustCompileResources) { + // we need to figure out where to store the R class. + // get the parent folder for R.java and update mManifestPackageSourceFolder + IFolder packageFolder = getGenManifestPackageFolder(project); + + // get the resource folder + IFolder resFolder = project.getFolder(AndroidConstants.WS_RESOURCES); + + // get the file system path + IPath outputLocation = mGenFolder.getLocation(); + IPath resLocation = resFolder.getLocation(); + IPath manifestLocation = manifest.getLocation(); + + // those locations have to exist for us to do something! + if (outputLocation != null && resLocation != null + && manifestLocation != null) { + String osOutputPath = outputLocation.toOSString(); + String osResPath = resLocation.toOSString(); + String osManifestPath = manifestLocation.toOSString(); + + // remove the aapt markers + removeMarkersFromFile(manifest, AndroidConstants.MARKER_AAPT_COMPILE); + removeMarkersFromContainer(resFolder, AndroidConstants.MARKER_AAPT_COMPILE); + + AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, project, + Messages.Preparing_Generated_Files); + + // since the R.java file may be already existing in read-only + // mode we need to make it readable so that aapt can overwrite + // it + IFile rJavaFile = packageFolder.getFile(AndroidConstants.FN_RESOURCE_CLASS); + + // do the same for the Manifest.java class + IFile manifestJavaFile = packageFolder.getFile( + AndroidConstants.FN_MANIFEST_CLASS); + + // we actually need to delete the manifest.java as it may become empty and + // in this case aapt doesn't generate an empty one, but instead doesn't + // touch it. + manifestJavaFile.delete(true, null); + + // launch aapt: create the command line + ArrayList array = new ArrayList(); + array.add(projectTarget.getPath(IAndroidTarget.AAPT)); + array.add("package"); //$NON-NLS-1$ + array.add("-m"); //$NON-NLS-1$ + if (AdtPlugin.getBuildVerbosity() == AdtConstants.BUILD_VERBOSE) { + array.add("-v"); //$NON-NLS-1$ } - String cmd_line = sb.toString(); - AdtPlugin.printToConsole(project, cmd_line); - } - - // launch - int execError = 1; - try { - // launch the command line process - Process process = Runtime.getRuntime().exec( - array.toArray(new String[array.size()])); - - // list to store each line of stderr - ArrayList results = new ArrayList(); - - // get the output and return code from the process - execError = grabProcessOutput(process, results); - - // attempt to parse the error output - boolean parsingError = parseAaptOutput(results, project); - - // if we couldn't parse the output we display it in the console. - if (parsingError) { - if (execError != 0) { - AdtPlugin.printErrorToConsole(project, results.toArray()); - } else { - AdtPlugin.printBuildToConsole(AdtConstants.BUILD_NORMAL, - project, results.toArray()); + array.add("-J"); //$NON-NLS-1$ + array.add(osOutputPath); + array.add("-M"); //$NON-NLS-1$ + array.add(osManifestPath); + array.add("-S"); //$NON-NLS-1$ + array.add(osResPath); + array.add("-I"); //$NON-NLS-1$ + array.add(projectTarget.getPath(IAndroidTarget.ANDROID_JAR)); + + if (AdtPlugin.getBuildVerbosity() == AdtConstants.BUILD_VERBOSE) { + StringBuilder sb = new StringBuilder(); + for (String c : array) { + sb.append(c); + sb.append(' '); } + String cmd_line = sb.toString(); + AdtPlugin.printToConsole(project, cmd_line); } - - 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. + + // launch + int execError = 1; + try { + // launch the command line process + Process process = Runtime.getRuntime().exec( + array.toArray(new String[array.size()])); + + // list to store each line of stderr + ArrayList results = new ArrayList(); + + // get the output and return code from the process + execError = grabProcessOutput(process, results); + + // attempt to parse the error output + boolean parsingError = parseAaptOutput(results, project); + + // if we couldn't parse the output we display it in the console. if (parsingError) { - markProject(AdtConstants.MARKER_ADT, Messages.Unparsed_AAPT_Errors, - IMarker.SEVERITY_ERROR); + if (execError != 0) { + AdtPlugin.printErrorToConsole(project, results.toArray()); + } else { + AdtPlugin.printBuildToConsole(AdtConstants.BUILD_NORMAL, + project, results.toArray()); + } } - - AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, project, - Messages.AAPT_Error); - - // abort if 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) { + markProject(AdtConstants.MARKER_ADT, Messages.Unparsed_AAPT_Errors, + IMarker.SEVERITY_ERROR); + } + + AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, project, + Messages.AAPT_Error); + + // abort if exec failed. + // This interrupts the build. The next builders will not run. + stopBuild(Messages.AAPT_Error); + } + } catch (IOException e1) { + // something happen while executing the process, + // mark the project and exit + String msg = String.format(Messages.AAPT_Exec_Error, array.get(0)); + markProject(AdtConstants.MARKER_ADT, msg, IMarker.SEVERITY_ERROR); + // This interrupts the build. The next builders will not run. - stopBuild(Messages.AAPT_Error); + stopBuild(msg); + } catch (InterruptedException e) { + // we got interrupted waiting for the process to end... + // mark the project and exit + String msg = String.format(Messages.AAPT_Exec_Error, array.get(0)); + markProject(AdtConstants.MARKER_ADT, msg, IMarker.SEVERITY_ERROR); + + // This interrupts the build. The next builders will not run. + stopBuild(msg); + } + + // if the return code was OK, we refresh the folder that + // contains R.java to force a java recompile. + if (execError == 0) { + // now add the R.java/Manifest.java to the list of file to be marked + // as derived. + mDerivedProgressMonitor.addFile(rJavaFile); + mDerivedProgressMonitor.addFile(manifestJavaFile); + + // build has been done. reset the state of the builder + mMustCompileResources = false; + + // and store it + saveProjectBooleanProperty(PROPERTY_COMPILE_RESOURCES, + mMustCompileResources); } - } catch (IOException e1) { - // something happen while executing the process, - // mark the project and exit - String msg = String.format(Messages.AAPT_Exec_Error, array.get(0)); - markProject(AdtConstants.MARKER_ADT, msg, IMarker.SEVERITY_ERROR); - - // This interrupts the build. The next builders will not run. - stopBuild(msg); - } catch (InterruptedException e) { - // we got interrupted waiting for the process to end... - // mark the project and exit - String msg = String.format(Messages.AAPT_Exec_Error, array.get(0)); - markProject(AdtConstants.MARKER_ADT, msg, IMarker.SEVERITY_ERROR); - - // This interrupts the build. The next builders will not run. - stopBuild(msg); - } - - // if the return code was OK, we refresh the folder that - // contains R.java to force a java recompile. - if (execError == 0) { - // now set the R.java/Manifest.java file as read only. - finishJavaFilesAfterExternalModification(rJavaFile, manifestJavaFile); - - // build has been done. reset the state of the builder - mCompileResources = false; - - // and store it - saveProjectBooleanProperty(PROPERTY_COMPILE_RESOURCES, mCompileResources); } + } else { + // nothing to do } - } else { - // nothing to do - } - - // now handle the aidl stuff. - // look for a preprocessed aidl file - IResource projectAidl = project.findMember("project.aidl"); //$NON-NLS-1$ - String folderAidlPath = null; - if (projectAidl != null && projectAidl.exists()) { - folderAidlPath = projectAidl.getLocation().toOSString(); - } - boolean aidlStatus = handleAidl(projectTarget, sourceList, folderAidlPath, monitor); - - if (aidlStatus == false && mCompileResources == false) { - AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, project, - Messages.Nothing_To_Compile); + + // now handle the aidl stuff. + boolean aidlStatus = handleAidl(projectTarget, sourceFolderPathList, monitor); + + if (aidlStatus == false && mMustCompileResources == false) { + AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, project, + Messages.Nothing_To_Compile); + } + } finally { + // refresh the 'gen' source folder. Once this is done with the custom progress + // monitor to mark all new files as derived + mGenFolder.refreshLocal(IResource.DEPTH_INFINITE, mDerivedProgressMonitor); } return null; @@ -557,67 +514,57 @@ public class PreCompilerBuilder extends BaseBuilder { AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, getProject(), Messages.Removing_Generated_Classes); - // check if we have the R.java info already. - if (mManifestPackageSourceFolder != null && mManifestPackage != null) { - deleteObsoleteGeneratedClass(AndroidConstants.FN_RESOURCE_CLASS, - mManifestPackageSourceFolder, mManifestPackage); - deleteObsoleteGeneratedClass(AndroidConstants.FN_MANIFEST_CLASS, - mManifestPackageSourceFolder, mManifestPackage); - } - - // FIXME: delete all java generated from aidl. + // remove all the derived resources from the 'gen' source folder. + removeDerivedResources(mGenFolder, monitor); } @Override protected void startupOnInitialize() { super.startupOnInitialize(); + + mDerivedProgressMonitor = new DerivedProgressMonitor(); + + IProject project = getProject(); // load the previous IFolder and java package. mManifestPackage = loadProjectStringProperty(PROPERTY_PACKAGE); - IResource resource = loadProjectResourceProperty(PROPERTY_SOURCE_FOLDER); - if (resource instanceof IFolder) { - mManifestPackageSourceFolder = (IFolder)resource; - } + + // get the source folder in which all the Java files are created + mGenFolder = project.getFolder(SdkConstants.FD_GEN_SOURCES); - // Load the current compile flag. We ask for true if not found to force a + // Load the current compile flags. We ask for true if not found to force a // recompile. - mCompileResources = loadProjectBooleanProperty(PROPERTY_COMPILE_RESOURCES, true); + mMustCompileResources = loadProjectBooleanProperty(PROPERTY_COMPILE_RESOURCES, true); + boolean mustCompileAidl = loadProjectBooleanProperty(PROPERTY_COMPILE_AIDL, true); + + // if we stored that we have to compile some aidl, we build the list that will compile them + // all + if (mustCompileAidl) { + IJavaProject javaProject = JavaCore.create(project); + ArrayList sourceFolderPathList = BaseProjectHelper.getSourceClasspaths( + javaProject); + + buildAidlCompilationList(project, sourceFolderPathList); + } } /** * Delete the a generated java class associated with the specified java package. * @param filename Name of the generated file to remove. - * @param sourceFolder The source Folder containing the old java package. * @param javaPackage the old java package */ - private void deleteObsoleteGeneratedClass(String filename, IFolder sourceFolder, - String javaPackage) { - if (sourceFolder == null || javaPackage == null) { + private void deleteObsoleteGeneratedClass(String filename, String javaPackage) { + if (javaPackage == null) { return; } - - // convert the java package into path - String[] segments = javaPackage.split(AndroidConstants.RE_DOT); - - StringBuilder path = new StringBuilder(); - for (String s : segments) { - path.append(AndroidConstants.WS_SEP_CHAR); - path.append(s); - } - - // appends the name of the generated file - path.append(AndroidConstants.WS_SEP_CHAR); - path.append(filename); - - Path iPath = new Path(path.toString()); + + IPath packagePath = getJavaPackagePath(javaPackage); + IPath iPath = packagePath.append(filename); // Find a matching resource object. - IResource javaFile = sourceFolder.findMember(iPath); + IResource javaFile = mGenFolder.findMember(iPath); if (javaFile != null && javaFile.exists() && javaFile.getType() == IResource.FILE) { try { - // remove the read-only tag - prepareFileForExternalModification((IFile)javaFile); - // delete javaFile.delete(true, null); @@ -626,7 +573,8 @@ public class PreCompilerBuilder extends BaseBuilder { } catch (CoreException e) { // failed to delete it, the user will have to delete it manually. - String message = String.format(Messages.Delete_Obsolete_Error, path); + String message = String.format(Messages.Delete_Obsolete_Error, + javaFile.getFullPath()); IProject project = getProject(); AdtPlugin.printErrorToConsole(project, message); AdtPlugin.printErrorToConsole(project, e.getMessage()); @@ -635,94 +583,38 @@ public class PreCompilerBuilder extends BaseBuilder { } /** - * Looks for the folder containing the package defined in the manifest. It looks in the - * list of source folders for the one containing folders matching the package defined in the - * manifest (from the field mManifestPackage). It returns the final folder, which - * will contain the R class, and update the field mManifestPackageSourceFolder - * to be the source folder containing the full package. + * Creates a relative {@link IPath} from a java package. + * @param javaPackageName the java package. + */ + private IPath getJavaPackagePath(String javaPackageName) { + // convert the java package into path + String[] segments = javaPackageName.split(AndroidConstants.RE_DOT); + + StringBuilder path = new StringBuilder(); + for (String s : segments) { + path.append(AndroidConstants.WS_SEP_CHAR); + path.append(s); + } + + return new Path(path.toString()); + } + + /** + * Returns an {@link IFolder} (located inside the 'gen' source folder), that matches the + * package defined in the manifest. This {@link IFolder} may not actually exist + * (aapt will create it anyway). * @param project The project. - * @param sourceList The list of source folders for the project. - * @return the package that will contain the R class or null if the folder was not found. + * @return the {@link IFolder} that will contain the R class or null if the folder was not found. * @throws CoreException */ - private IFolder getManifestPackageFolder(IProject project, ArrayList sourceList) + private IFolder getGenManifestPackageFolder(IProject project) throws CoreException { - // split the package in segments - String[] packageSegments = mManifestPackage.split(AndroidConstants.RE_DOT); - - // we look for 2 folders. - // 1. The source folder that contains the full java package. - // we will store the folder in the field mJavaSourceFolder, for reuse during - IFolder manifestPackageSourceFolder = null; - // subsequent builds. This is the folder we will give to aapt. - // 2. The folder actually containing the R.java files. We need this one to do a refresh - IFolder packageFolder = null; - - for (IPath iPath : sourceList) { - int packageSegmentIndex = 0; - - // the path is relative to the workspace. We ignore the first segment, - // when getting the resource from the IProject object. - IResource classpathEntry = project.getFolder(iPath.removeFirstSegments(1)); - - if (classpathEntry instanceof IFolder) { - IFolder classpathFolder = (IFolder)classpathEntry; - IFolder folder = classpathFolder; - - boolean failed = false; - while (failed == false - && packageSegmentIndex < packageSegments.length) { - - // loop on that folder content looking for folders - // that match the package - // defined in AndroidManifest.xml - - // get the folder content - IResource[] content = folder.members(); - - // this is the segment we look for - String segment = packageSegments[packageSegmentIndex]; - - // did we find it at this level - boolean found = false; - - for (IResource r : content) { - // look for the java package segment - if (r instanceof IFolder) { - if (r.getName().equals(segment)) { - // we need to skip to the next one - folder = (IFolder)r; - packageSegmentIndex++; - found = true; - break; - } - } - } - - // if we didn't find it at this level we just fail. - if (found == false) { - failed = true; - } - } - - // if we didn't fail then we found it. no point in - // looping through the rest - // or the classpathEntry - if (failed == false) { - // save the target folder reference - manifestPackageSourceFolder = classpathFolder; - packageFolder = folder; - break; - } - } - } - - // save the location of the folder into the persistent storage - if (manifestPackageSourceFolder != mManifestPackageSourceFolder) { - mManifestPackageSourceFolder = manifestPackageSourceFolder; - saveProjectResourceProperty(PROPERTY_SOURCE_FOLDER, mManifestPackageSourceFolder); - } - return packageFolder; + // get the path for the package + IPath packagePath = getJavaPackagePath(mManifestPackage); + + // get a folder for this path under the 'gen' source folder, and return it. + // This IFolder may not reference an actual existing folder. + return mGenFolder.getFolder(packagePath); } /** @@ -730,27 +622,22 @@ public class PreCompilerBuilder extends BaseBuilder { * created from aidl files that are now gone. * @param projectTarget Target of the project * @param sourceFolders the list of source folders, relative to the workspace. - * @param folderAidlPath * @param monitor the projess monitor * @returns true if it did something * @throws CoreException */ private boolean handleAidl(IAndroidTarget projectTarget, ArrayList sourceFolders, - String folderAidlPath, IProgressMonitor monitor) throws CoreException { + IProgressMonitor monitor) throws CoreException { if (mAidlToCompile.size() == 0 && mAidlToRemove.size() == 0) { return false; } - // create the command line - String[] command = new String[4 + sourceFolders.size() + (folderAidlPath != null ? 1 : 0)]; + String[] command = new String[4 + sourceFolders.size()]; int index = 0; - int aidlIndex; command[index++] = projectTarget.getPath(IAndroidTarget.AIDL); - command[aidlIndex = index++] = "-p"; //$NON-NLS-1$ - if (folderAidlPath != null) { - command[index++] = "-p" + folderAidlPath; //$NON-NLS-1$ - } + command[index++] = "-p" + Sdk.getCurrent().getTarget(getProject()).getPath( //$NON-NLS-1$ + IAndroidTarget.ANDROID_AIDL); // since the path are relative to the workspace and not the project itself, we need // the workspace root. @@ -761,81 +648,44 @@ public class PreCompilerBuilder extends BaseBuilder { } // list of files that have failed compilation. - ArrayList stillNeedCompilation = new ArrayList(); + ArrayList stillNeedCompilation = new ArrayList(); // if an aidl file is being removed before we managed to compile it, it'll be in // both list. We *need* to remove it from the compile list or it'll never go away. - for (IFile aidlFile : mAidlToRemove) { + for (AidlData aidlFile : mAidlToRemove) { int pos = mAidlToCompile.indexOf(aidlFile); if (pos != -1) { mAidlToCompile.remove(pos); } } - + // loop until we've compile them all - for (IFile aidlFile : mAidlToCompile) { + for (AidlData aidlData : mAidlToCompile) { // Remove the AIDL error markers from the aidl file - removeMarkersFromFile(aidlFile, AndroidConstants.MARKER_AIDL); - - // get the path - IPath iPath = aidlFile.getLocation(); - String osPath = iPath.toOSString(); - - // get the parent container - IContainer parentContainer = aidlFile.getParent(); - - // replace the extension in both the full path and the - // last segment - String osJavaPath = osPath.replaceAll(AndroidConstants.RE_AIDL_EXT, - AndroidConstants.DOT_JAVA); - String javaName = aidlFile.getName().replaceAll(AndroidConstants.RE_AIDL_EXT, - AndroidConstants.DOT_JAVA); - - // check if we can compile it, or if there is a conflict with a java file - boolean conflict = ProjectHelper.loadBooleanProperty(aidlFile, - PROPERTY_ANDROID_CONFLICT, false); - if (conflict) { - String msg = String.format(Messages.AIDL_Java_Conflict, javaName, - aidlFile.getName()); - - // put a marker - BaseProjectHelper.addMarker(aidlFile, AndroidConstants.MARKER_AIDL, msg, - IMarker.SEVERITY_ERROR); - - // output an error - AdtPlugin.printErrorToConsole(getProject(), msg); - - stillNeedCompilation.add(aidlFile); + removeMarkersFromFile(aidlData.aidlFile, AndroidConstants.MARKER_AIDL); - // move on to next file - continue; - } - - // get the resource for the java file. - Path javaIPath = new Path(javaName); - IFile javaFile = parentContainer.getFile(javaIPath); - - // if the file was read-only, this will make it readable. - prepareFileForExternalModification(javaFile); + // get the path of the source file. + IPath sourcePath = aidlData.aidlFile.getLocation(); + String osSourcePath = sourcePath.toOSString(); + + IFile javaFile = getGenDestinationFile(aidlData, true /*createFolders*/, monitor); // finish to set the command line. - command[aidlIndex] = "-p" + Sdk.getCurrent().getTarget(aidlFile.getProject()).getPath( - IAndroidTarget.ANDROID_AIDL); //$NON-NLS-1$ - command[index] = osPath; - command[index + 1] = osJavaPath; + command[index] = osSourcePath; + command[index + 1] = javaFile.getLocation().toOSString(); // launch the process - if (execAidl(command, aidlFile) == false) { + if (execAidl(command, aidlData.aidlFile) == false) { // aidl failed. File should be marked. We add the file to the list // of file that will need compilation again. - stillNeedCompilation.add(aidlFile); + stillNeedCompilation.add(aidlData); // and we move on to the next one. continue; } else { - // since the exec worked, we refresh the parent, and set the - // file as read only. - finishFileAfterExternalModification(javaFile, aidlFile); + // make sure the file will be marked as derived once we refresh the 'gen' source + // folder. + mDerivedProgressMonitor.addFile(javaFile); } } @@ -844,44 +694,68 @@ public class PreCompilerBuilder extends BaseBuilder { mAidlToCompile.addAll(stillNeedCompilation); // Remove the java files created from aidl files that have been removed. - for (IFile aidlFile : mAidlToRemove) { - // make the java filename - String javaName = aidlFile.getName().replaceAll( - AndroidConstants.RE_AIDL_EXT, - AndroidConstants.DOT_JAVA); - - // get the parent container - IContainer ic = aidlFile.getParent(); - - // and get the IFile corresponding to the java file. - IFile javaFile = ic.getFile(new Path(javaName)); - if (javaFile != null && javaFile.exists() ) { - // check if this java file has a persistent data marking it as generated by - // the builder. - // While we put the aidl path as a resource, internally it's all string anyway. - // We use loadStringProperty, because loadResourceProperty tries to match - // the string value (a path in this case) with an existing resource, but - // the aidl file was deleted, so it would return null, even though the property - // existed. - String aidlPath = ProjectHelper.loadStringProperty(javaFile, - PROPERTY_ANDROID_GENERATED); - - if (aidlPath != null) { - // This confirms the java file was generated by the builder, - // we can delete the aidlFile. - javaFile.delete(true, null); - - // Refresh parent. - ic.refreshLocal(IResource.DEPTH_ONE, monitor); - } + for (AidlData aidlData : mAidlToRemove) { + IFile javaFile = getGenDestinationFile(aidlData, false /*createFolders*/, monitor); + if (javaFile.exists()) { + // This confirms the java file was generated by the builder, + // we can delete the aidlFile. + javaFile.delete(true, null); + + // Refresh parent. + javaFile.getParent().refreshLocal(IResource.DEPTH_ONE, monitor); } } + mAidlToRemove.clear(); + // store the build state. If there are any files that failed to compile, we will + // force a full aidl compile on the next project open. (unless a full compilation succeed + // before the project is closed/re-opened.) + // TODO: Optimize by saving only the files that need compilation + saveProjectBooleanProperty(PROPERTY_COMPILE_AIDL , mAidlToCompile.size() > 0); + return true; } /** + * Returns the {@link IFile} handle to the destination file for a given aild source file + * ({@link AidlData}). + * @param aidlData the data for the aidl source file. + * @param createFolders whether or not the parent folder of the destination should be created + * if it does not exist. + * @param monitor the progress monitor + * @return the handle to the destination file. + * @throws CoreException + */ + private IFile getGenDestinationFile(AidlData aidlData, boolean createFolders, + IProgressMonitor monitor) throws CoreException { + // build the destination folder path. + // Use the path of the source file, except for the path leading to its source folder, + // and for the last segment which is the filename. + int segmentToSourceFolderCount = aidlData.sourceFolder.getFullPath().segmentCount(); + IPath packagePath = aidlData.aidlFile.getFullPath().removeFirstSegments( + segmentToSourceFolderCount).removeLastSegments(1); + Path destinationPath = new Path(packagePath.toString()); + + // get an IFolder for this path. It's relative to the 'gen' folder already + IFolder destinationFolder = mGenFolder.getFolder(destinationPath); + + // create it if needed + if (destinationFolder.exists() == false && createFolders) { + destinationFolder.create(true /*force*/, true /*local*/, + new SubProgressMonitor(monitor, 10));; + } + + // Build the Java file name from the aidl name. + String javaName = aidlData.aidlFile.getName().replaceAll(AndroidConstants.RE_AIDL_EXT, + AndroidConstants.DOT_JAVA); + + // get the resource for the java file. + IFile javaFile = destinationFolder.getFile(javaName); + return javaFile; + } + + /** * Execute the aidl command line, parse the output, and mark the aidl file * with any reported errors. * @param command the String array containing the command line to execute. @@ -933,43 +807,28 @@ public class PreCompilerBuilder extends BaseBuilder { * Goes through the build paths and fills the list of aidl files to compile * ({@link #mAidlToCompile}). * @param project The project. - * @param buildPaths The list of build paths. + * @param sourceFolderPathList The list of source folder paths. */ private void buildAidlCompilationList(IProject project, - ArrayList buildPaths) { - for (IPath p : buildPaths) { - // Because the path contains the name of the project as well, we - // need to remove it, to access the final folder. - String[] segments = p.segments(); - IContainer folder = project; - for (int i = 1; i < segments.length; i++) { - IResource r = folder.findMember(segments[i]); - if (r != null && r.exists() && - r.getType() == IResource.FOLDER) { - folder = (IContainer)r; - } else { - // hmm looks like the build path is corrupted/wrong. - // reset and break - folder = project; - break; - } - } - - // did we ge a folder? - if (folder != project) { - // then we scan! - scanContainerForAidl(folder); + ArrayList sourceFolderPathList) { + IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot(); + for (IPath sourceFolderPath : sourceFolderPathList) { + IFolder sourceFolder = root.getFolder(sourceFolderPath); + // we don't look in the 'gen' source folder as there will be no source in there. + if (sourceFolder.exists() && sourceFolder.equals(mGenFolder) == false) { + scanFolderForAidl(sourceFolder, sourceFolder); } } } /** - * Scans a container and fills the list of aidl files to compile. - * @param container The container to scan. + * Scans a folder and fills the list of aidl files to compile. + * @param sourceFolder the root source folder. + * @param container The folder to scan. */ - private void scanContainerForAidl(IContainer container) { + private void scanFolderForAidl(IFolder sourceFolder, IFolder folder) { try { - IResource[] members = container.members(); + IResource[] members = folder.members(); for (IResource r : members) { // get the type of the resource switch (r.getType()) { @@ -978,12 +837,12 @@ public class PreCompilerBuilder extends BaseBuilder { // and that it's an aidl file if (r.exists() && AndroidConstants.EXT_AIDL.equalsIgnoreCase(r.getFileExtension())) { - mAidlToCompile.add((IFile)r); + mAidlToCompile.add(new AidlData(sourceFolder, (IFile)r)); } break; case IResource.FOLDER: // recursively go through children - scanContainerForAidl((IFolder)r); + scanFolderForAidl(sourceFolder, (IFolder)r); break; default: // this would mean it's a project or the workspace root @@ -1051,13 +910,12 @@ public class PreCompilerBuilder extends BaseBuilder { * @param toCompile List of file to compile * @param toRemove List of file to remove */ - private void mergeAidlFileModifications(ArrayList toCompile, - ArrayList toRemove) { - + private void mergeAidlFileModifications(ArrayList toCompile, + ArrayList toRemove) { // loop through the new toRemove list, and add it to the old one, // plus remove any file that was still to compile and that are now // removed - for (IFile r : toRemove) { + for (AidlData r : toRemove) { if (mAidlToRemove.indexOf(r) == -1) { mAidlToRemove.add(r); } @@ -1072,7 +930,7 @@ public class PreCompilerBuilder extends BaseBuilder { // Also look for them in the remove list, this would mean that they // were removed, then added back, and we shouldn't remove them, just // recompile them. - for (IFile r : toCompile) { + for (AidlData r : toCompile) { if (mAidlToCompile.indexOf(r) == -1) { mAidlToCompile.add(r); } @@ -1083,68 +941,4 @@ public class PreCompilerBuilder extends BaseBuilder { } } } - - /** - * Prepare an already existing file for modification. File generated from - * command line processed are marked as read-only. This method prepares - * them (mark them as read-write) before the command line process is - * started. A check is made to be sure the file exists. - * @param file The IResource object for the file to prepare. - * @throws CoreException - */ - private void prepareFileForExternalModification(IFile file) - throws CoreException { - // file may not exist yet, so we check that. - if (file != null && file.exists()) { - // get the attributes. - ResourceAttributes ra = file.getResourceAttributes(); - if (ra != null) { - // change the attributes - ra.setReadOnly(false); - - // set the new attributes in the file. - file.setResourceAttributes(ra); - } - } - } - - /** - * Finish a file created/modified by an outside command line process. - * The file is marked as modified by Android, and the parent folder is refreshed, so that, - * in case the file didn't exist beforehand, the file appears in the package explorer. - * @param rFile The R file to "finish". - * @param manifestFile The manifest file to "finish". - * @throws CoreException - */ - private void finishJavaFilesAfterExternalModification(IFile rFile, IFile manifestFile) - throws CoreException { - IContainer parent = rFile.getParent(); - - IProgressMonitor monitor = new DerivedProgressMonitor(rFile, manifestFile); - - // refresh the parent node in the package explorer. Once this is done the custom progress - // monitor will mark them as derived. - parent.refreshLocal(IResource.DEPTH_ONE, monitor); - } - - /** - * Finish a file created/modified by an outside command line process. - * The file is marked as modified by Android, and the parent folder is refreshed, so that, - * in case the file didn't exist beforehand, the file appears in the package explorer. - * @param file The file to "finish". - * @param aidlFile The AIDL file to "finish". - * @throws CoreException - */ - private void finishFileAfterExternalModification(IFile file, IFile aidlFile) - throws CoreException { - IContainer parent = file.getParent(); - - // we need to add a link to the aidl file. - // We need to wait for the refresh of the parent to be done, so we'll do - // it in the monitor. This will also set the file as derived. - IProgressMonitor monitor = new RefreshProgressMonitor(file, aidlFile); - - // refresh the parent node in the package explorer. - parent.refreshLocal(IResource.DEPTH_ONE, monitor); - } } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/PreCompilerDeltaVisitor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/PreCompilerDeltaVisitor.java index f4778d7..6841830 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/PreCompilerDeltaVisitor.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/PreCompilerDeltaVisitor.java @@ -19,7 +19,7 @@ package com.android.ide.eclipse.adt.build; import com.android.ide.eclipse.adt.AdtConstants; import com.android.ide.eclipse.adt.AdtPlugin; import com.android.ide.eclipse.adt.build.BaseBuilder.BaseDeltaVisitor; -import com.android.ide.eclipse.adt.project.ProjectHelper; +import com.android.ide.eclipse.adt.build.PreCompilerBuilder.AidlData; import com.android.ide.eclipse.common.AndroidConstants; import com.android.ide.eclipse.common.project.AndroidManifestParser; import com.android.ide.eclipse.common.project.BaseProjectHelper; @@ -31,14 +31,25 @@ import org.eclipse.core.resources.IFolder; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.IResourceDelta; import org.eclipse.core.resources.IResourceDeltaVisitor; +import org.eclipse.core.resources.IWorkspaceRoot; +import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; -import org.eclipse.core.runtime.Path; import java.util.ArrayList; /** * Resource Delta visitor for the pre-compiler. + *

This delta visitor only cares about files that are the source or the result of actions of the + * {@link PreCompilerBuilder}: + *

  • R.java/Manifest.java generated by compiling the resources
  • + *
  • Any Java files generated by aidl
. + * + * Therefore it looks for the following: + *
  • Any modification in the resource folder
  • + *
  • Removed files from the source folder receiving generated Java files
  • + *
  • Any modification to aidl files.
  • + * */ class PreCompilerDeltaVisitor extends BaseDeltaVisitor implements IResourceDeltaVisitor { @@ -53,14 +64,11 @@ class PreCompilerDeltaVisitor extends BaseDeltaVisitor implements private boolean mCompileResources = false; /** List of .aidl files found that are modified or new. */ - private final ArrayList mAidlToCompile = new ArrayList(); + private final ArrayList mAidlToCompile = new ArrayList(); /** List of .aidl files that have been removed. */ - private final ArrayList mAidlToRemove = new ArrayList(); + private final ArrayList mAidlToRemove = new ArrayList(); - /** Aidl forced recompilation flag. This is set to true if project.aidl is modified. */ - private boolean mFullAidlCompilation = false; - /** Manifest check/parsing flag. */ private boolean mCheckedManifestXml = false; @@ -75,36 +83,36 @@ class PreCompilerDeltaVisitor extends BaseDeltaVisitor implements private boolean mInRes = false; /** - * In Source folder flag. This allows us to know if we're in a source - * folder. + * Current Source folder. This allows us to know if we're in a source + * folder, and which folder. */ - private boolean mInSrc = false; + private IFolder mSourceFolder = null; /** List of source folders. */ private ArrayList mSourceFolders; + private boolean mIsGenSourceFolder = false; + + private IWorkspaceRoot mRoot; public PreCompilerDeltaVisitor(BaseBuilder builder, ArrayList sourceFolders) { super(builder); mSourceFolders = sourceFolders; + mRoot = ResourcesPlugin.getWorkspace().getRoot(); } public boolean getCompileResources() { return mCompileResources; } - public ArrayList getAidlToCompile() { + public ArrayList getAidlToCompile() { return mAidlToCompile; } - public ArrayList getAidlToRemove() { + public ArrayList getAidlToRemove() { return mAidlToRemove; } - public boolean getFullAidlRecompilation() { - return mFullAidlCompilation; - } - /** * Returns whether the manifest file was parsed/checked for error during the resource delta * visiting. @@ -149,11 +157,13 @@ class PreCompilerDeltaVisitor extends BaseDeltaVisitor implements // since the delta visitor also visits the root we return true if // segments.length = 1 if (segments.length == 1) { + // FIXME: check this is an Android project. return true; } else if (segments.length == 2) { // if we are at an item directly under the root directory, // then we are not yet in a source or resource folder - mInRes = mInSrc = false; + mInRes = false; + mSourceFolder = null; if (SdkConstants.FD_RESOURCES.equalsIgnoreCase(segments[1])) { // this is the resource folder that was modified. we want to @@ -162,7 +172,7 @@ class PreCompilerDeltaVisitor extends BaseDeltaVisitor implements // since we're going to visit its children next, we set the // flag mInRes = true; - mInSrc = false; + mSourceFolder = null; return true; } else if (AndroidConstants.FN_ANDROID_MANIFEST.equalsIgnoreCase(segments[1])) { // any change in the manifest could trigger a new R.java @@ -183,9 +193,6 @@ class PreCompilerDeltaVisitor extends BaseDeltaVisitor implements // we don't want to go to the children, not like they are // any for this resource anyway. return false; - } else if (AndroidConstants.FN_PROJECT_AIDL.equalsIgnoreCase(segments[1])) { - // need to force recompilation of all the aidl files - mFullAidlCompilation = true; } } @@ -198,7 +205,7 @@ class PreCompilerDeltaVisitor extends BaseDeltaVisitor implements // so first we test if we already know we are in a source or // resource folder. - if (mInSrc) { + if (mSourceFolder != null) { // if we are in the res folder, we are looking for the following changes: // - added/removed/modified aidl files. // - missing R.java file @@ -216,130 +223,84 @@ class PreCompilerDeltaVisitor extends BaseDeltaVisitor implements // get the modification kind int kind = delta.getKind(); - if (kind == IResourceDelta.ADDED) { - // we only care about added files (inside the source folders), if they - // are aidl files. + // we process normal source folder and the 'gen' source folder differently. + if (mIsGenSourceFolder) { + // this is the generated java file source folder. + // - if R.java/Manifest.java are removed/modified, we recompile the resources + // - if aidl files are removed/modified, we recompile them. - // get the extension of the resource - String ext = resource.getFileExtension(); + boolean outputWarning = false; - if (AndroidConstants.EXT_AIDL.equalsIgnoreCase(ext)) { - // look for an already existing matching java file - String javaName = resource.getName().replaceAll( - AndroidConstants.RE_AIDL_EXT, - AndroidConstants.DOT_JAVA); - - // get the parent container - IContainer ic = resource.getParent(); - - IFile javaFile = ic.getFile(new Path(javaName)); - if (javaFile != null && javaFile.exists()) { - // check if that file was generated by the plugin. Normally those files are - // deleted automatically, but it's better to check. - String aidlPath = ProjectHelper.loadStringProperty(javaFile, - PreCompilerBuilder.PROPERTY_ANDROID_GENERATED); - if (aidlPath == null) { - // mark the aidl file that it cannot be compile just yet - ProjectHelper.saveBooleanProperty(file, - PreCompilerBuilder.PROPERTY_ANDROID_CONFLICT, true); - } + String fileName = resource.getName(); - // we add it anyway so that we can try to compile it at every compilation - // until the conflict is fixed. - mAidlToCompile.add(file); + // Special case of R.java/Manifest.java. + if (AndroidConstants.FN_RESOURCE_CLASS.equals(fileName) || + AndroidConstants.FN_MANIFEST_CLASS.equals(fileName)) { + // if it was removed, there's a possibility that it was removed due to a + // package change, or an aidl that was removed, but the only thing + // that will happen is that we'll have an extra build. Not much of a problem. + mCompileResources = true; - } else { - // the java file doesn't exist, we can safely add the file to the list - // of files to compile. - mAidlToCompile.add(file); + // we want a warning + outputWarning = true; + } else { + // this has to be a Java file created from an aidl file. + // Look for the source aidl file in all the source folders. + String aidlFileName = fileName.replaceAll(AndroidConstants.RE_JAVA_EXT, + AndroidConstants.DOT_AIDL); + + for (IPath sourceFolderPath : mSourceFolders) { + // do not search in the current source folder as it is the 'gen' folder. + if (sourceFolderPath.equals(mSourceFolder.getFullPath())) { + continue; + } + + IFolder sourceFolder = getFolder(sourceFolderPath); + if (sourceFolder != null) { + // go recursively, segment by segment. + // index starts at 2 (0 is project, 1 is 'gen' + IFile sourceFile = findFile(sourceFolder, segments, 2, aidlFileName); + + if (sourceFile != null) { + // found the source. add it to the list of files to compile + mAidlToCompile.add(new AidlData(sourceFolder, sourceFile)); + outputWarning = true; + break; + } + } } } - return false; - } - - // get the filename - String fileName = segments[segments.length - 1]; - - boolean outputMessage = false; - - // Special case of R.java/Manifest.java. - // FIXME: This does not check the package. Any modification of R.java/Manifest.java in another project will trigger a new recompilation of the resources. - if (AndroidConstants.FN_RESOURCE_CLASS.equals(fileName) || - AndroidConstants.FN_MANIFEST_CLASS.equals(fileName)) { - // if it was removed, there's a possibility that it was removed due to a - // package change, or an aidl that was removed, but the only thing - // that will happen is that we'll have an extra build. Not much of a problem. - mCompileResources = true; + if (outputWarning) { + if (kind == IResourceDelta.REMOVED) { + // We pring an error just so that it's red, but it's just a warning really. + String msg = String.format(Messages.s_Removed_Recreating_s, fileName); + AdtPlugin.printErrorToConsole(mBuilder.getProject(), msg); + } else if (kind == IResourceDelta.CHANGED) { + // the file was modified manually! we can't allow it. + String msg = String.format(Messages.s_Modified_Manually_Recreating_s, + fileName); + AdtPlugin.printErrorToConsole(mBuilder.getProject(), msg); + } + } - // we want a warning - outputMessage = true; } else { + // this is another source folder. + // We only care about aidl files being added/modified/removed. // get the extension of the resource String ext = resource.getFileExtension(); - if (AndroidConstants.EXT_AIDL.equalsIgnoreCase(ext)) { if (kind == IResourceDelta.REMOVED) { - mAidlToRemove.add(file); + // we'll have to remove the generated file. + mAidlToRemove.add(new AidlData(mSourceFolder, file)); } else { - mAidlToCompile.add(file); - } - } else { - if (kind == IResourceDelta.REMOVED) { - // the file has been removed. we need to check it's a java file and that - // there's a matching aidl file. We can't check its persistent storage - // anymore. - if (AndroidConstants.EXT_JAVA.equalsIgnoreCase(ext)) { - String aidlFile = resource.getName().replaceAll( - AndroidConstants.RE_JAVA_EXT, - AndroidConstants.DOT_AIDL); - - // get the parent container - IContainer ic = resource.getParent(); - - IFile f = ic.getFile(new Path(aidlFile)); - if (f != null && f.exists() ) { - // make sure that the aidl file is not in conflict anymore, in - // case the java file was not generated by us. - if (ProjectHelper.loadBooleanProperty(f, - PreCompilerBuilder.PROPERTY_ANDROID_CONFLICT, false)) { - ProjectHelper.saveBooleanProperty(f, - PreCompilerBuilder.PROPERTY_ANDROID_CONFLICT, false); - } else { - outputMessage = true; - } - mAidlToCompile.add(f); - } - } - } else { - // check if it's an android generated java file. - IResource aidlSource = ProjectHelper.loadResourceProperty( - file, PreCompilerBuilder.PROPERTY_ANDROID_GENERATED); - - if (aidlSource != null && aidlSource.exists() && - aidlSource.getType() == IResource.FILE) { - // it looks like this was a java file created from an aidl file. - // we need to add the aidl file to the list of aidl file to compile - mAidlToCompile.add((IFile)aidlSource); - outputMessage = true; - } + // add the aidl file to the list of file to (re)compile + mAidlToCompile.add(new AidlData(mSourceFolder, file)); } } } - if (outputMessage) { - if (kind == IResourceDelta.REMOVED) { - // We pring an error just so that it's red, but it's just a warning really. - String msg = String.format(Messages.s_Removed_Recreating_s, fileName); - AdtPlugin.printErrorToConsole(mBuilder.getProject(), msg); - } else if (kind == IResourceDelta.CHANGED) { - // the file was modified manually! we can't allow it. - String msg = String.format(Messages.s_Modified_Manually_Recreating_s, fileName); - AdtPlugin.printErrorToConsole(mBuilder.getProject(), msg); - } - } - // no children. return false; } else if (mInRes) { @@ -403,19 +364,25 @@ class PreCompilerDeltaVisitor extends BaseDeltaVisitor implements } } else if (resource instanceof IFolder) { // in this case we may be inside a folder that contains a source - // folder. - String[] sourceFolderSegments = findMatchingSourceFolder(mSourceFolders, segments); - if (sourceFolderSegments != null) { - // we have a match! - mInRes = false; - - // Check if the current folder is actually a source folder - if (sourceFolderSegments.length == segments.length) { - mInSrc = true; + // folder, go through the list of known source folders + + for (IPath sourceFolderPath : mSourceFolders) { + // first check if they match exactly. + if (sourceFolderPath.equals(path)) { + // this is a source folder! + mInRes = false; + mSourceFolder = getFolder(sourceFolderPath); // all non null due to test above + mIsGenSourceFolder = path.segmentCount() == 2 && + path.segment(1).equals(SdkConstants.FD_GEN_SOURCES); + return true; } - // and return true to visit the content, no matter what - return true; + // check if we are on the way to a source folder. + int count = sourceFolderPath.matchingFirstSegments(path); + if (count == path.segmentCount()) { + mInRes = false; + return true; + } } // if we're here, we are visiting another folder @@ -429,4 +396,46 @@ class PreCompilerDeltaVisitor extends BaseDeltaVisitor implements return false; } + + /** + * Searches for and return a file in a folder. The file is defined by its segments, and a new + * name (replacing the last segment). + * @param folder the folder we are searching + * @param segments the segments of the file to search. + * @param index the index of the current segment we are looking for + * @param filename the new name to replace the last segment. + * @return the {@link IFile} representing the searched file, or null if not found + */ + private IFile findFile(IFolder folder, String[] segments, int index, String filename) { + boolean lastSegment = index == segments.length - 1; + IResource resource = folder.findMember(lastSegment ? filename : segments[index]); + if (resource != null && resource.exists()) { + if (lastSegment) { + if (resource.getType() == IResource.FILE) { + return (IFile)resource; + } + } else { + if (resource.getType() == IResource.FOLDER) { + return findFile((IFolder)resource, segments, index+1, filename); + } + } + } + return null; + } + + /** + * Returns a handle to the folder identified by the given path in this container. + *

    The different with {@link IContainer#getFolder(IPath)} is that this returns a non + * null object only if the resource actually exists and is a folder (and not a file) + * @param path the path of the folder to return. + * @return a handle to the folder if it exists, or null otherwise. + */ + private IFolder getFolder(IPath path) { + IResource resource = mRoot.findMember(path); + if (resource != null && resource.exists() && resource.getType() == IResource.FOLDER) { + return (IFolder)resource; + } + + return null; + } } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/ResourceManagerBuilder.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/ResourceManagerBuilder.java index 19d7185..0255f9f 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/ResourceManagerBuilder.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/ResourceManagerBuilder.java @@ -19,19 +19,27 @@ package com.android.ide.eclipse.adt.build; import com.android.ide.eclipse.adt.AdtConstants; import com.android.ide.eclipse.adt.AdtPlugin; import com.android.ide.eclipse.adt.project.ProjectHelper; -import com.android.ide.eclipse.adt.sdk.LoadStatus; import com.android.ide.eclipse.adt.sdk.Sdk; import com.android.ide.eclipse.common.AndroidConstants; import com.android.ide.eclipse.common.project.BaseProjectHelper; import com.android.sdklib.IAndroidTarget; +import com.android.sdklib.SdkConstants; 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.IWorkspaceRoot; +import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.SubProgressMonitor; +import org.eclipse.jdt.core.IClasspathEntry; +import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jdt.core.JavaCore; +import java.util.ArrayList; import java.util.Map; /** @@ -56,6 +64,10 @@ public class ResourceManagerBuilder extends BaseBuilder { // Clear the project of the generic markers BaseBuilder.removeMarkersFromProject(project, AdtConstants.MARKER_ADT); + + // check for existing target marker, in which case we abort. + // (this means: no SDK, no target, or unresolvable target.) + abortOnBadSetup(project); // Check the compiler compliance level, displaying the error message // since this is the first builder. @@ -83,8 +95,7 @@ public class ResourceManagerBuilder extends BaseBuilder { String osSdkFolder = AdtPlugin.getOsSdkFolder(); if (osSdkFolder == null || osSdkFolder.length() == 0) { - AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, project, - Messages.No_SDK_Setup_Error); + AdtPlugin.printErrorToConsole(project, Messages.No_SDK_Setup_Error); markProject(AdtConstants.MARKER_ADT, Messages.No_SDK_Setup_Error, IMarker.SEVERITY_ERROR); @@ -92,13 +103,6 @@ public class ResourceManagerBuilder extends BaseBuilder { stopBuild(Messages.No_SDK_Setup_Error); } - // check if we have finished loading the SDK. - if (AdtPlugin.getDefault().getSdkLoadStatus() != LoadStatus.LOADED) { - // we exit silently - // This interrupts the build. The next builders will not run. - stopBuild("SDK is not loaded yet"); - } - // check the project has a target IAndroidTarget projectTarget = Sdk.getCurrent().getTarget(project); if (projectTarget == null) { @@ -106,6 +110,85 @@ public class ResourceManagerBuilder extends BaseBuilder { // This interrupts the build. The next builders will not run. stopBuild("Project has no target"); } + + // check the 'gen' source folder is present + boolean hasGenSrcFolder = false; // whether the project has a 'gen' source folder setup + IJavaProject javaProject = JavaCore.create(project); + + IClasspathEntry[] classpaths = javaProject.readRawClasspath(); + if (classpaths != null) { + for (IClasspathEntry e : classpaths) { + if (e.getEntryKind() == IClasspathEntry.CPE_SOURCE) { + IPath path = e.getPath(); + if (path.segmentCount() == 2 && + path.segment(1).equals(SdkConstants.FD_GEN_SOURCES)) { + hasGenSrcFolder = true; + break; + } + } + } + } + + boolean genFolderPresent = false; // whether the gen folder actually exists + IResource resource = project.findMember(SdkConstants.FD_GEN_SOURCES); + genFolderPresent = resource != null && resource.exists(); + + if (hasGenSrcFolder == false && genFolderPresent) { + // No source folder setup for 'gen' in the project, but there's already a + // 'gen' resource (file or folder). + String message; + if (resource.getType() == IResource.FOLDER) { + // folder exists already! This is an error. If the folder had been created + // by the NewProjectWizard, it'd be a source folder. + message = String.format("%1$s already exists but is not a source folder. Convert to a source folder or rename it.", + resource.getFullPath().toString()); + } else { + // resource exists but is not a folder. + message = String.format( + "Resource %1$s is in the way. ADT needs a source folder called 'gen' to work. Rename or delete resource.", + resource.getFullPath().toString()); + } + + AdtPlugin.printErrorToConsole(project, message); + markProject(AdtConstants.MARKER_ADT, message, IMarker.SEVERITY_ERROR); + + // This interrupts the build. The next builders will not run. + stopBuild(message); + } else if (hasGenSrcFolder == false || genFolderPresent == false) { + // either there is no 'gen' source folder in the project (older SDK), + // or the folder does not exist (was deleted, or was a fresh svn checkout maybe.) + + // In case we are migrating from an older SDK, we go through the current source + // folders and delete the generated Java files. + ArrayList sourceFolders = BaseProjectHelper.getSourceClasspaths(javaProject); + IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot(); + for (IPath path : sourceFolders) { + IResource member = root.findMember(path); + if (member != null) { + removeDerivedResources(member, monitor); + } + } + + // create the new source folder, if needed + IFolder genFolder = project.getFolder(SdkConstants.FD_GEN_SOURCES); + if (genFolderPresent == false) { + AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, project, + "Creating 'gen' source folder for generated Java files"); + genFolder.create(true /* force */, true /* local */, + new SubProgressMonitor(monitor, 10)); + } + + // add it to the source folder list, if needed only (or it will throw) + if (hasGenSrcFolder == false) { + IClasspathEntry[] entries = javaProject.getRawClasspath(); + entries = ProjectHelper.addEntryToClasspath(entries, + JavaCore.newSourceEntry(genFolder.getFullPath())); + javaProject.setRawClasspath(entries, new SubProgressMonitor(monitor, 10)); + } + + // refresh the whole project + project.refreshLocal(IResource.DEPTH_INFINITE, new SubProgressMonitor(monitor, 10)); + } // Check the preference to be sure we are supposed to refresh // the folders. diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/debug/launching/AndroidLaunch.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/debug/launching/AndroidLaunch.java deleted file mode 100644 index 3d60401..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/debug/launching/AndroidLaunch.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (C) 2007 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.debug.launching; - -import org.eclipse.debug.core.DebugException; -import org.eclipse.debug.core.ILaunchConfiguration; -import org.eclipse.debug.core.ILaunchManager; -import org.eclipse.debug.core.Launch; -import org.eclipse.debug.core.model.ISourceLocator; - -/** - * Custom implementation of Launch to allow access to the LaunchManager - * - */ -class AndroidLaunch extends Launch { - - /** - * Basic constructor does nothing special - * @param launchConfiguration - * @param mode - * @param locator - */ - public AndroidLaunch(ILaunchConfiguration launchConfiguration, String mode, - ISourceLocator locator) { - super(launchConfiguration, mode, locator); - } - - /** Stops the launch, and removes it from the launch manager */ - public void stopLaunch() { - ILaunchManager mgr = getLaunchManager(); - - if (canTerminate()) { - try { - terminate(); - } catch (DebugException e) { - // well looks like we couldn't stop it. nothing else to be - // done really - } - } - // remove the launch - mgr.removeLaunch(this); - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/debug/launching/AndroidLaunchController.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/debug/launching/AndroidLaunchController.java deleted file mode 100644 index ac003df..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/debug/launching/AndroidLaunchController.java +++ /dev/null @@ -1,1838 +0,0 @@ -/* - * Copyright (C) 2007 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.debug.launching; - -import com.android.ddmlib.AndroidDebugBridge; -import com.android.ddmlib.Client; -import com.android.ddmlib.ClientData; -import com.android.ddmlib.Device; -import com.android.ddmlib.Log; -import com.android.ddmlib.MultiLineReceiver; -import com.android.ddmlib.SyncService; -import com.android.ddmlib.AndroidDebugBridge.IClientChangeListener; -import com.android.ddmlib.AndroidDebugBridge.IDebugBridgeChangeListener; -import com.android.ddmlib.AndroidDebugBridge.IDeviceChangeListener; -import com.android.ddmlib.SyncService.SyncResult; -import com.android.ide.eclipse.adt.AdtPlugin; -import com.android.ide.eclipse.adt.debug.launching.DeviceChooserDialog.DeviceChooserResponse; -import com.android.ide.eclipse.adt.debug.ui.EmulatorConfigTab; -import com.android.ide.eclipse.adt.project.ProjectHelper; -import com.android.ide.eclipse.adt.sdk.Sdk; -import com.android.ide.eclipse.common.project.AndroidManifestHelper; -import com.android.sdklib.IAndroidTarget; -import com.android.sdklib.SdkManager; -import com.android.sdklib.avd.AvdManager; -import com.android.sdklib.avd.AvdManager.AvdInfo; - -import org.eclipse.core.resources.IFile; -import org.eclipse.core.resources.IProject; -import org.eclipse.core.resources.IResource; -import org.eclipse.core.runtime.CoreException; -import org.eclipse.core.runtime.IPath; -import org.eclipse.core.runtime.IProgressMonitor; -import org.eclipse.debug.core.DebugPlugin; -import org.eclipse.debug.core.ILaunchConfiguration; -import org.eclipse.debug.core.ILaunchConfigurationType; -import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy; -import org.eclipse.debug.core.ILaunchManager; -import org.eclipse.debug.core.model.IDebugTarget; -import org.eclipse.debug.ui.DebugUITools; -import org.eclipse.jdt.launching.IJavaLaunchConfigurationConstants; -import org.eclipse.jdt.launching.IVMConnector; -import org.eclipse.jdt.launching.JavaRuntime; -import org.eclipse.jface.dialogs.Dialog; -import org.eclipse.jface.dialogs.MessageDialog; -import org.eclipse.jface.preference.IPreferenceStore; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Map.Entry; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * Controls the launch of Android application either on a device or on the - * emulator. If an emulator is already running, this class will attempt to reuse - * it. - */ -public final class AndroidLaunchController implements IDebugBridgeChangeListener, - IDeviceChangeListener, IClientChangeListener { - - private static final String FLAG_AVD = "-avd"; //$NON-NLS-1$ - private static final String FLAG_NETDELAY = "-netdelay"; //$NON-NLS-1$ - private static final String FLAG_NETSPEED = "-netspeed"; //$NON-NLS-1$ - private static final String FLAG_WIPE_DATA = "-wipe-data"; //$NON-NLS-1$ - private static final String FLAG_NO_BOOT_ANIM = "-no-boot-anim"; //$NON-NLS-1$ - - private static final int MAX_ATTEMPT_COUNT = 5; - - private final static Pattern sAmErrorType = Pattern.compile("Error type (\\d+)"); //$NON-NLS-1$ - - /** - * A delayed launch waiting for a device to be present or ready before the - * application is launched. - */ - static final class DelayedLaunchInfo { - /** The device on which to launch the app */ - Device mDevice = null; - - /** The eclipse project */ - IProject mProject; - - /** Package name */ - String mPackageName; - - /** fully qualified name of the activity */ - String mActivity; - - /** IFile to the package (.apk) file */ - IFile mPackageFile; - - /** Debuggable attribute of the manifest file. */ - Boolean mDebuggable = null; - - /** Required ApiVersionNumber by the app. 0 means no requirements */ - int mRequiredApiVersionNumber = 0; - - InstallRetryMode mRetryMode = InstallRetryMode.NEVER; - - /** - * Launch action. See {@link LaunchConfigDelegate#ACTION_DEFAULT}, - * {@link LaunchConfigDelegate#ACTION_ACTIVITY}, - * {@link LaunchConfigDelegate#ACTION_DO_NOTHING} - */ - int mLaunchAction; - - /** the launch object */ - AndroidLaunch mLaunch; - - /** the monitor object */ - IProgressMonitor mMonitor; - - /** debug mode flag */ - boolean mDebugMode; - - int mAttemptCount = 0; - - boolean mCancelled = false; - - /** Basic constructor with activity and package info. */ - private DelayedLaunchInfo(IProject project, String packageName, String activity, - IFile pack, Boolean debuggable, int requiredApiVersionNumber, int launchAction, - AndroidLaunch launch, IProgressMonitor monitor) { - mProject = project; - mPackageName = packageName; - mActivity = activity; - mPackageFile = pack; - mLaunchAction = launchAction; - mLaunch = launch; - mMonitor = monitor; - mDebuggable = debuggable; - mRequiredApiVersionNumber = requiredApiVersionNumber; - } - } - - /** - * Map to store {@link ILaunchConfiguration} objects that must be launched as simple connection - * to running application. The integer is the port on which to connect. - * ALL ACCESS MUST BE INSIDE A synchronized (sListLock) block! - */ - private final static HashMap sRunningAppMap = - new HashMap(); - - private final static Object sListLock = sRunningAppMap; - - /** - * List of {@link DelayedLaunchInfo} waiting for an emulator to connect. - *

    Once an emulator has connected, {@link DelayedLaunchInfo#mDevice} is set and the - * DelayedLaunchInfo object is moved to {@link AndroidLaunchController#mWaitingForReadyEmulatorList}. - * ALL ACCESS MUST BE INSIDE A synchronized (sListLock) block! - */ - private final ArrayList mWaitingForEmulatorLaunches = - new ArrayList(); - - /** - * List of application waiting to be launched on a device/emulator.
    - * ALL ACCESS MUST BE INSIDE A synchronized (sListLock) block! - * */ - private final ArrayList mWaitingForReadyEmulatorList = - new ArrayList(); - - /** - * Application waiting to show up as waiting for debugger. - * ALL ACCESS MUST BE INSIDE A synchronized (sListLock) block! - */ - private final ArrayList mWaitingForDebuggerApplications = - new ArrayList(); - - /** - * List of clients that have appeared as waiting for debugger before their name was available. - * ALL ACCESS MUST BE INSIDE A synchronized (sListLock) block! - */ - private final ArrayList mUnknownClientsWaitingForDebugger = new ArrayList(); - - /** static instance for singleton */ - private static AndroidLaunchController sThis = new AndroidLaunchController(); - - enum InstallRetryMode { - NEVER, ALWAYS, PROMPT; - } - - /** - * Launch configuration data. This stores the result of querying the - * {@link ILaunchConfiguration} so that it's only done once. - */ - static final class AndroidLaunchConfiguration { - - /** - * Launch action. See {@link LaunchConfigDelegate#ACTION_DEFAULT}, - * {@link LaunchConfigDelegate#ACTION_ACTIVITY}, - * {@link LaunchConfigDelegate#ACTION_DO_NOTHING} - */ - public int mLaunchAction = LaunchConfigDelegate.DEFAULT_LAUNCH_ACTION; - - public static final boolean AUTO_TARGET_MODE = true; - - /** - * Target selection mode. - *

      - *
    • true: automatic mode, see {@link #AUTO_TARGET_MODE}
    • - *
    • false: manual mode
    • - *
    - */ - public boolean mTargetMode = LaunchConfigDelegate.DEFAULT_TARGET_MODE; - - /** - * Indicates whether the emulator should be called with -wipe-data - */ - public boolean mWipeData = LaunchConfigDelegate.DEFAULT_WIPE_DATA; - - /** - * Indicates whether the emulator should be called with -no-boot-anim - */ - public boolean mNoBootAnim = LaunchConfigDelegate.DEFAULT_NO_BOOT_ANIM; - - /** - * AVD Name. - */ - public String mAvdName = null; - - public String mNetworkSpeed = EmulatorConfigTab.getSpeed( - LaunchConfigDelegate.DEFAULT_SPEED); - public String mNetworkDelay = EmulatorConfigTab.getDelay( - LaunchConfigDelegate.DEFAULT_DELAY); - - /** - * Optional custom command line parameter to launch the emulator - */ - public String mEmulatorCommandLine; - - /** - * Initialized the structure from an ILaunchConfiguration object. - * @param config - */ - public void set(ILaunchConfiguration config) { - try { - mLaunchAction = config.getAttribute(LaunchConfigDelegate.ATTR_LAUNCH_ACTION, - mLaunchAction); - } catch (CoreException e1) { - // nothing to be done here, we'll use the default value - } - - try { - mTargetMode = config.getAttribute(LaunchConfigDelegate.ATTR_TARGET_MODE, - mTargetMode); - } catch (CoreException e) { - // nothing to be done here, we'll use the default value - } - - try { - mAvdName = config.getAttribute(LaunchConfigDelegate.ATTR_AVD_NAME, mAvdName); - } catch (CoreException e) { - } - - int index = LaunchConfigDelegate.DEFAULT_SPEED; - try { - index = config.getAttribute(LaunchConfigDelegate.ATTR_SPEED, index); - } catch (CoreException e) { - // nothing to be done here, we'll use the default value - } - mNetworkSpeed = EmulatorConfigTab.getSpeed(index); - - index = LaunchConfigDelegate.DEFAULT_DELAY; - try { - index = config.getAttribute(LaunchConfigDelegate.ATTR_DELAY, index); - } catch (CoreException e) { - // nothing to be done here, we'll use the default value - } - mNetworkDelay = EmulatorConfigTab.getDelay(index); - - try { - mEmulatorCommandLine = config.getAttribute( - LaunchConfigDelegate.ATTR_COMMANDLINE, ""); //$NON-NLS-1$ - } catch (CoreException e) { - // lets not do anything here, we'll use the default value - } - - try { - mWipeData = config.getAttribute(LaunchConfigDelegate.ATTR_WIPE_DATA, mWipeData); - } catch (CoreException e) { - // nothing to be done here, we'll use the default value - } - - try { - mNoBootAnim = config.getAttribute(LaunchConfigDelegate.ATTR_NO_BOOT_ANIM, - mNoBootAnim); - } catch (CoreException e) { - // nothing to be done here, we'll use the default value - } - } - } - - /** - * Output receiver for am process (activity Manager); - */ - private final class AMReceiver extends MultiLineReceiver { - private DelayedLaunchInfo mLaunchInfo; - private Device mDevice; - - /** - * Basic constructor. - * @param launchInfo The launch info associated with the am process. - * @param device The device on which the launch is done. - */ - public AMReceiver(DelayedLaunchInfo launchInfo, Device device) { - mLaunchInfo = launchInfo; - mDevice = device; - } - - @Override - public void processNewLines(String[] lines) { - // first we check if one starts with error - ArrayList array = new ArrayList(); - boolean error = false; - boolean warning = false; - for (String s : lines) { - // ignore empty lines. - if (s.length() == 0) { - continue; - } - - // check for errors that output an error type, if the attempt count is still - // valid. If not the whole text will be output in the console - if (mLaunchInfo.mAttemptCount < MAX_ATTEMPT_COUNT && - mLaunchInfo.mCancelled == false) { - Matcher m = sAmErrorType.matcher(s); - if (m.matches()) { - // get the error type - int type = Integer.parseInt(m.group(1)); - - final int waitTime = 3; - String msg; - - switch (type) { - case 1: - /* Intended fall through */ - case 2: - msg = String.format( - "Device not ready. Waiting %1$d seconds before next attempt.", - waitTime); - break; - case 3: - msg = String.format( - "New package not yet registered with the system. Waiting %1$d seconds before next attempt.", - waitTime); - break; - default: - msg = String.format( - "Device not ready (%2$d). Waiting %1$d seconds before next attempt.", - waitTime, type); - break; - - } - - AdtPlugin.printToConsole(mLaunchInfo.mProject, msg); - - // launch another thread, that waits a bit and attempts another launch - new Thread("Delayed Launch attempt") { - @Override - public void run() { - try { - sleep(waitTime * 1000); - } catch (InterruptedException e) { - } - - launchApp(mLaunchInfo, mDevice); - } - }.start(); - - // no need to parse the rest - return; - } - } - - // check for error if needed - if (error == false && s.startsWith("Error:")) { //$NON-NLS-1$ - error = true; - } - if (warning == false && s.startsWith("Warning:")) { //$NON-NLS-1$ - warning = true; - } - - // add the line to the list - array.add("ActivityManager: " + s); //$NON-NLS-1$ - } - - // then we display them in the console - if (warning || error) { - AdtPlugin.printErrorToConsole(mLaunchInfo.mProject, array.toArray()); - } else { - AdtPlugin.printToConsole(mLaunchInfo.mProject, array.toArray()); - } - - // if error then we cancel the launch, and remove the delayed info - if (error) { - mLaunchInfo.mLaunch.stopLaunch(); - synchronized (sListLock) { - mWaitingForReadyEmulatorList.remove(mLaunchInfo); - } - } - } - - public boolean isCancelled() { - return false; - } - } - - /** - * Output receiver for "pm install package.apk" command line. - */ - private final static class InstallReceiver extends MultiLineReceiver { - - private final static String SUCCESS_OUTPUT = "Success"; //$NON-NLS-1$ - private final static Pattern FAILURE_PATTERN = Pattern.compile("Failure\\s+\\[(.*)\\]"); //$NON-NLS-1$ - - private String mSuccess = null; - - public InstallReceiver() { - } - - @Override - public void processNewLines(String[] lines) { - for (String line : lines) { - if (line.length() > 0) { - if (line.startsWith(SUCCESS_OUTPUT)) { - mSuccess = null; - } else { - Matcher m = FAILURE_PATTERN.matcher(line); - if (m.matches()) { - mSuccess = m.group(1); - } - } - } - } - } - - public boolean isCancelled() { - return false; - } - - public String getSuccess() { - return mSuccess; - } - } - - - /** private constructor to enforce singleton */ - private AndroidLaunchController() { - AndroidDebugBridge.addDebugBridgeChangeListener(this); - AndroidDebugBridge.addDeviceChangeListener(this); - AndroidDebugBridge.addClientChangeListener(this); - } - - /** - * Returns the singleton reference. - */ - public static AndroidLaunchController getInstance() { - return sThis; - } - - - /** - * Launches a remote java debugging session on an already running application - * @param project The project of the application to debug. - * @param debugPort The port to connect the debugger to. - */ - public static void debugRunningApp(IProject project, int debugPort) { - // get an existing or new launch configuration - ILaunchConfiguration config = AndroidLaunchController.getLaunchConfig(project); - - if (config != null) { - setPortLaunchConfigAssociation(config, debugPort); - - // and launch - DebugUITools.launch(config, ILaunchManager.DEBUG_MODE); - } - } - - /** - * Returns an {@link ILaunchConfiguration} for the specified {@link IProject}. - * @param project the project - * @return a new or already existing ILaunchConfiguration or null if there was - * an error when creating a new one. - */ - public static ILaunchConfiguration getLaunchConfig(IProject project) { - // get the launch manager - ILaunchManager manager = DebugPlugin.getDefault().getLaunchManager(); - - // now get the config type for our particular android type. - ILaunchConfigurationType configType = manager.getLaunchConfigurationType( - LaunchConfigDelegate.ANDROID_LAUNCH_TYPE_ID); - - String name = project.getName(); - - // search for an existing launch configuration - ILaunchConfiguration config = findConfig(manager, configType, name); - - // test if we found one or not - if (config == null) { - // Didn't find a matching config, so we make one. - // It'll be made in the "working copy" object first. - ILaunchConfigurationWorkingCopy wc = null; - - try { - // make the working copy object - wc = configType.newInstance(null, - manager.generateUniqueLaunchConfigurationNameFrom(name)); - - // set the project name - wc.setAttribute(IJavaLaunchConfigurationConstants.ATTR_PROJECT_NAME, name); - - // set the launch mode to default. - wc.setAttribute(LaunchConfigDelegate.ATTR_LAUNCH_ACTION, - LaunchConfigDelegate.DEFAULT_LAUNCH_ACTION); - - // set default target mode - wc.setAttribute(LaunchConfigDelegate.ATTR_TARGET_MODE, - LaunchConfigDelegate.DEFAULT_TARGET_MODE); - - // default AVD: None - wc.setAttribute(LaunchConfigDelegate.ATTR_AVD_NAME, (String)null); - - // set the default network speed - wc.setAttribute(LaunchConfigDelegate.ATTR_SPEED, - LaunchConfigDelegate.DEFAULT_SPEED); - - // and delay - wc.setAttribute(LaunchConfigDelegate.ATTR_DELAY, - LaunchConfigDelegate.DEFAULT_DELAY); - - // default wipe data mode - wc.setAttribute(LaunchConfigDelegate.ATTR_WIPE_DATA, - LaunchConfigDelegate.DEFAULT_WIPE_DATA); - - // default disable boot animation option - wc.setAttribute(LaunchConfigDelegate.ATTR_NO_BOOT_ANIM, - LaunchConfigDelegate.DEFAULT_NO_BOOT_ANIM); - - // set default emulator options - IPreferenceStore store = AdtPlugin.getDefault().getPreferenceStore(); - String emuOptions = store.getString(AdtPlugin.PREFS_EMU_OPTIONS); - wc.setAttribute(LaunchConfigDelegate.ATTR_COMMANDLINE, emuOptions); - - // map the config and the project - wc.setMappedResources(getResourcesToMap(project)); - - // save the working copy to get the launch config object which we return. - return wc.doSave(); - - } catch (CoreException e) { - String msg = String.format( - "Failed to create a Launch config for project '%1$s': %2$s", - project.getName(), e.getMessage()); - AdtPlugin.printErrorToConsole(project, msg); - - // no launch! - return null; - } - } - - return config; - } - - /** - * Returns the list of resources to map to a Launch Configuration. - * @param project the project associated to the launch configuration. - */ - public static IResource[] getResourcesToMap(IProject project) { - ArrayList array = new ArrayList(2); - array.add(project); - - AndroidManifestHelper helper = new AndroidManifestHelper(project); - IFile manifest = helper.getManifestIFile(); - if (manifest != null) { - array.add(manifest); - } - - return array.toArray(new IResource[array.size()]); - } - - /** - * Launches an android app on the device or emulator - * - * @param project The project we're launching - * @param mode the mode in which to launch, one of the mode constants - * defined by ILaunchManager - RUN_MODE or - * DEBUG_MODE. - * @param apk the resource to the apk to launch. - * @param debuggable the debuggable value of the app, or null if not set. - * @param requiredApiVersionNumber the api version required by the app, or -1 if none. - * @param activity the class to provide to am to launch - * @param config the launch configuration - * @param launch the launch object - */ - public void launch(final IProject project, String mode, IFile apk, - String packageName, Boolean debuggable, int requiredApiVersionNumber, String activity, - final AndroidLaunchConfiguration config, final AndroidLaunch launch, - IProgressMonitor monitor) { - - String message; - if (config.mLaunchAction == LaunchConfigDelegate.ACTION_DO_NOTHING) { - message = String.format("Only Syncing Application Package"); - } else { - message = String.format("Launching: %1$s", activity); - } - AdtPlugin.printToConsole(project, message); - - // create the launch info - final DelayedLaunchInfo launchInfo = new DelayedLaunchInfo(project, packageName, - activity, apk, debuggable, requiredApiVersionNumber, config.mLaunchAction, - launch, monitor); - - // set the debug mode - launchInfo.mDebugMode = mode.equals(ILaunchManager.DEBUG_MODE); - - // get the SDK - Sdk currentSdk = Sdk.getCurrent(); - AvdManager avdManager = currentSdk.getAvdManager(); - - // get the project target - final IAndroidTarget projectTarget = currentSdk.getTarget(project); - - // FIXME: check errors on missing sdk, AVD manager, or project target. - - // device chooser response object. - final DeviceChooserResponse response = new DeviceChooserResponse(); - - /* - * Launch logic: - * - Manually Mode - * Always display a UI that lets a user see the current running emulators/devices. - * The UI must show which devices are compatibles, and allow launching new emulators - * with compatible (and not yet running) AVD. - * - Automatic Way - * * Preferred AVD set. - * If Preferred AVD is not running: launch it. - * Launch the application on the preferred AVD. - * * No preferred AVD. - * Count the number of compatible emulators/devices. - * If != 1, display a UI similar to manual mode. - * If == 1, launch the application on this AVD/device. - */ - - if (config.mTargetMode == AndroidLaunchConfiguration.AUTO_TARGET_MODE) { - // if we are in automatic target mode, we need to find the current devices - Device[] devices = AndroidDebugBridge.getBridge().getDevices(); - - // first check if we have a preferred AVD name, and if it actually exists, and is valid - // (ie able to run the project). - // We need to check this in case the AVD was recreated with a different target that is - // not compatible. - AvdInfo preferredAvd = null; - if (config.mAvdName != null) { - preferredAvd = avdManager.getAvd(config.mAvdName); - if (projectTarget.isCompatibleBaseFor(preferredAvd.getTarget()) == false) { - preferredAvd = null; - - AdtPlugin.printErrorToConsole(project, String.format( - "Preferred AVD '%1$s' is not compatible with the project target '%2$s'. Looking for a compatible AVD...", - config.mAvdName, projectTarget.getName())); - } - } - - if (preferredAvd != null) { - // look for a matching device - for (Device d : devices) { - String deviceAvd = d.getAvdName(); - if (deviceAvd != null && deviceAvd.equals(config.mAvdName)) { - response.setDeviceToUse(d); - - AdtPlugin.printToConsole(project, String.format( - "Automatic Target Mode: Preferred AVD '%1$s' is available on emulator '%2$s'", - config.mAvdName, d)); - - continueLaunch(response, project, launch, launchInfo, config); - return; - } - } - - // at this point we have a valid preferred AVD that is not running. - // We need to start it. - response.setAvdToLaunch(preferredAvd); - - AdtPlugin.printToConsole(project, String.format( - "Automatic Target Mode: Preferred AVD '%1$s' is not available. Launching new emulator.", - config.mAvdName)); - - continueLaunch(response, project, launch, launchInfo, config); - return; - } - - // no (valid) preferred AVD? look for one. - HashMap compatibleRunningAvds = new HashMap(); - boolean hasDevice = false; // if there's 1+ device running, we may force manual mode, - // as we cannot always detect proper compatibility with - // devices. This is the case if the project target is not - // a standard platform - for (Device d : devices) { - String deviceAvd = d.getAvdName(); - if (deviceAvd != null) { // physical devices return null. - AvdInfo info = avdManager.getAvd(deviceAvd); - if (info != null && projectTarget.isCompatibleBaseFor(info.getTarget())) { - compatibleRunningAvds.put(d, info); - } - } else { - if (projectTarget.isPlatform()) { // means this can run on any device as long - // as api level is high enough - String apiString = d.getProperty(SdkManager.PROP_VERSION_SDK); - try { - int apiNumber = Integer.parseInt(apiString); - if (apiNumber >= projectTarget.getApiVersionNumber()) { - // device is compatible with project - compatibleRunningAvds.put(d, null); - continue; - } - } catch (NumberFormatException e) { - // do nothing, we'll consider it a non compatible device below. - } - } - hasDevice = true; - } - } - - // depending on the number of devices, we'll simulate an automatic choice - // from the device chooser or simply show up the device chooser. - if (hasDevice == false && compatibleRunningAvds.size() == 0) { - // if zero emulators/devices, we launch an emulator. - // We need to figure out which AVD first. - - // we are going to take the closest AVD. ie a compatible AVD that has the API level - // closest to the project target. - AvdInfo[] avds = avdManager.getAvds(); - AvdInfo defaultAvd = null; - for (AvdInfo avd : avds) { - if (projectTarget.isCompatibleBaseFor(avd.getTarget())) { - if (defaultAvd == null || - avd.getTarget().getApiVersionNumber() < - defaultAvd.getTarget().getApiVersionNumber()) { - defaultAvd = avd; - } - } - } - - if (defaultAvd != null) { - response.setAvdToLaunch(defaultAvd); - - AdtPlugin.printToConsole(project, String.format( - "Automatic Target Mode: launching new emulator with compatible AVD '%1$s'", - defaultAvd.getName())); - - continueLaunch(response, project, launch, launchInfo, config); - return; - } else { - // FIXME: ask the user if he wants to create a AVD. - // we found no compatible AVD. - AdtPlugin.printErrorToConsole(project, String.format( - "Failed to find a AVD compatible with target '%1$s'. Launch aborted.", - projectTarget.getName())); - launch.stopLaunch(); - return; - } - } else if (hasDevice == false && compatibleRunningAvds.size() == 1) { - Entry e = compatibleRunningAvds.entrySet().iterator().next(); - response.setDeviceToUse(e.getKey()); - - // get the AvdInfo, if null, the device is a physical device. - AvdInfo avdInfo = e.getValue(); - if (avdInfo != null) { - message = String.format("Automatic Target Mode: using existing emulator '%1$s' running compatible AVD '%2$s'", - response.getDeviceToUse(), e.getValue().getName()); - } else { - message = String.format("Automatic Target Mode: using device '%1$s'", - response.getDeviceToUse()); - } - AdtPlugin.printToConsole(project, message); - - continueLaunch(response, project, launch, launchInfo, config); - return; - } - - // if more than one device, we'll bring up the DeviceChooser dialog below. - if (compatibleRunningAvds.size() >= 2) { - message = "Automatic Target Mode: Several compatible targets. Please select a target device."; - } else if (hasDevice) { - message = "Automatic Target Mode: Unable to detect device compatibility. Please select a target device."; - } - - AdtPlugin.printToConsole(project, message); - } - - // bring up the device chooser. - AdtPlugin.getDisplay().asyncExec(new Runnable() { - public void run() { - try { - // open the chooser dialog. It'll fill 'response' with the device to use - // or the AVD to launch. - DeviceChooserDialog dialog = new DeviceChooserDialog( - AdtPlugin.getDisplay().getActiveShell(), - response, launchInfo.mPackageName, projectTarget); - if (dialog.open() == Dialog.OK) { - AndroidLaunchController.this.continueLaunch(response, project, launch, - launchInfo, config); - } else { - AdtPlugin.printErrorToConsole(project, "Launch canceled!"); - launch.stopLaunch(); - return; - } - } catch (Exception e) { - // there seems to be some case where the shell will be null. (might be - // an OS X bug). Because of this the creation of the dialog will throw - // and IllegalArg exception interrupting the launch with no user feedback. - // So we trap all the exception and display something. - String msg = e.getMessage(); - if (msg == null) { - msg = e.getClass().getCanonicalName(); - } - AdtPlugin.printErrorToConsole(project, - String.format("Error during launch: %s", msg)); - launch.stopLaunch(); - } - } - }); - } - - /** - * Continues the launch based on the DeviceChooser response. - * @param response the device chooser response - * @param project The project being launched - * @param launch The eclipse launch info - * @param launchInfo The {@link DelayedLaunchInfo} - * @param config The config needed to start a new emulator. - */ - void continueLaunch(final DeviceChooserResponse response, final IProject project, - final AndroidLaunch launch, final DelayedLaunchInfo launchInfo, - final AndroidLaunchConfiguration config) { - - // Since this is called from the UI thread we spawn a new thread - // to finish the launch. - new Thread() { - @Override - public void run() { - if (response.getAvdToLaunch() != null) { - // there was no selected device, we start a new emulator. - synchronized (sListLock) { - AvdInfo info = response.getAvdToLaunch(); - mWaitingForEmulatorLaunches.add(launchInfo); - AdtPlugin.printToConsole(project, String.format( - "Launching a new emulator with Virtual Device '%1$s'", - info.getName())); - boolean status = launchEmulator(config, info); - - if (status == false) { - // launching the emulator failed! - AdtPlugin.displayError("Emulator Launch", - "Couldn't launch the emulator! Make sure the SDK directory is properly setup and the emulator is not missing."); - - // stop the launch and return - mWaitingForEmulatorLaunches.remove(launchInfo); - AdtPlugin.printErrorToConsole(project, "Launch canceled!"); - launch.stopLaunch(); - return; - } - - return; - } - } else if (response.getDeviceToUse() != null) { - launchInfo.mDevice = response.getDeviceToUse(); - simpleLaunch(launchInfo, launchInfo.mDevice); - } - } - }.start(); - } - - /** - * Queries for a debugger port for a specific {@link ILaunchConfiguration}. - *

    - * If the configuration and a debugger port where added through - * {@link #setPortLaunchConfigAssociation(ILaunchConfiguration, int)}, then this method - * will return the debugger port, and remove the configuration from the list. - * @param launchConfig the {@link ILaunchConfiguration} - * @return the debugger port or {@link LaunchConfigDelegate#INVALID_DEBUG_PORT} if the - * configuration was not setup. - */ - static int getPortForConfig(ILaunchConfiguration launchConfig) { - synchronized (sListLock) { - Integer port = sRunningAppMap.get(launchConfig); - if (port != null) { - sRunningAppMap.remove(launchConfig); - return port; - } - } - - return LaunchConfigDelegate.INVALID_DEBUG_PORT; - } - - /** - * Set a {@link ILaunchConfiguration} and its associated debug port, in the list of - * launch config to connect directly to a running app instead of doing full launch (sync, - * launch, and connect to). - * @param launchConfig the {@link ILaunchConfiguration} object. - * @param port The debugger port to connect to. - */ - private static void setPortLaunchConfigAssociation(ILaunchConfiguration launchConfig, - int port) { - synchronized (sListLock) { - sRunningAppMap.put(launchConfig, port); - } - } - - /** - * Checks the build information, and returns whether the launch should continue. - *

    The value tested are: - *

      - *
    • Minimum API version requested by the application. If the target device does not match, - * the launch is canceled.
    • - *
    • Debuggable attribute of the application and whether or not the device requires it. If - * the device requires it and it is not set in the manifest, the launch will be forced to - * "release" mode instead of "debug"
    • - *
        - */ - private boolean checkBuildInfo(DelayedLaunchInfo launchInfo, Device device) { - if (device != null) { - // check the app required API level versus the target device API level - - String deviceApiVersionName = device.getProperty(Device.PROP_BUILD_VERSION); - String value = device.getProperty(Device.PROP_BUILD_VERSION_NUMBER); - int deviceApiVersionNumber = 0; - try { - deviceApiVersionNumber = Integer.parseInt(value); - } catch (NumberFormatException e) { - // pass, we'll keep the deviceVersionNumber value at 0. - } - - if (launchInfo.mRequiredApiVersionNumber == 0) { - // warn the API level requirement is not set. - AdtPlugin.printErrorToConsole(launchInfo.mProject, - "WARNING: Application does not specify an API level requirement!"); - - // and display the target device API level (if known) - if (deviceApiVersionName == null || deviceApiVersionNumber == 0) { - AdtPlugin.printErrorToConsole(launchInfo.mProject, - "WARNING: Unknown device API version!"); - } else { - AdtPlugin.printErrorToConsole(launchInfo.mProject, String.format( - "Device API version is %1$d (Android %2$s)", deviceApiVersionNumber, - deviceApiVersionName)); - } - } else { // app requires a specific API level - if (deviceApiVersionName == null || deviceApiVersionNumber == 0) { - AdtPlugin.printToConsole(launchInfo.mProject, - "WARNING: Unknown device API version!"); - } else if (deviceApiVersionNumber < launchInfo.mRequiredApiVersionNumber) { - String msg = String.format( - "ERROR: Application requires API version %1$d. Device API version is %2$d (Android %3$s).", - launchInfo.mRequiredApiVersionNumber, deviceApiVersionNumber, - deviceApiVersionName); - AdtPlugin.printErrorToConsole(launchInfo.mProject, msg); - - // abort the launch - return false; - } - } - - // now checks that the device/app can be debugged (if needed) - if (device.isEmulator() == false && launchInfo.mDebugMode) { - String debuggableDevice = device.getProperty(Device.PROP_DEBUGGABLE); - if (debuggableDevice != null && debuggableDevice.equals("0")) { //$NON-NLS-1$ - // the device is "secure" and requires apps to declare themselves as debuggable! - if (launchInfo.mDebuggable == null) { - String message1 = String.format( - "Device '%1$s' requires that applications explicitely declare themselves as debuggable in their manifest.", - device.getSerialNumber()); - String message2 = String.format("Application '%1$s' does not have the attribute 'debuggable' set to TRUE in its manifest and cannot be debugged.", - launchInfo.mPackageName); - AdtPlugin.printErrorToConsole(launchInfo.mProject, message1, message2); - - // because am -D does not check for ro.debuggable and the - // 'debuggable' attribute, it is important we do not use the -D option - // in this case or the app will wait for a debugger forever and never - // really launch. - launchInfo.mDebugMode = false; - } else if (launchInfo.mDebuggable == Boolean.FALSE) { - String message = String.format("Application '%1$s' has its 'debuggable' attribute set to FALSE and cannot be debugged.", - launchInfo.mPackageName); - AdtPlugin.printErrorToConsole(launchInfo.mProject, message); - - // because am -D does not check for ro.debuggable and the - // 'debuggable' attribute, it is important we do not use the -D option - // in this case or the app will wait for a debugger forever and never - // really launch. - launchInfo.mDebugMode = false; - } - } - } - } - - return true; - } - - /** - * Do a simple launch on the specified device, attempting to sync the new - * package, and then launching the application. Failed sync/launch will - * stop the current AndroidLaunch and return false; - * @param launchInfo - * @param device - * @return true if succeed - */ - private boolean simpleLaunch(DelayedLaunchInfo launchInfo, Device device) { - // API level check - if (checkBuildInfo(launchInfo, device) == false) { - AdtPlugin.printErrorToConsole(launchInfo.mProject, "Launch canceled!"); - launchInfo.mLaunch.stopLaunch(); - return false; - } - - // sync the app - if (syncApp(launchInfo, device) == false) { - AdtPlugin.printErrorToConsole(launchInfo.mProject, "Launch canceled!"); - launchInfo.mLaunch.stopLaunch(); - return false; - } - - // launch the app - launchApp(launchInfo, device); - - return true; - } - - - /** - * Syncs the application on the device/emulator. - * - * @param launchInfo The Launch information object. - * @param device the device on which to sync the application - * @return true if the install succeeded. - */ - private boolean syncApp(DelayedLaunchInfo launchInfo, Device device) { - SyncService sync = device.getSyncService(); - if (sync != null) { - IPath path = launchInfo.mPackageFile.getLocation(); - String message = String.format("Uploading %1$s onto device '%2$s'", - path.lastSegment(), device.getSerialNumber()); - AdtPlugin.printToConsole(launchInfo.mProject, message); - - String osLocalPath = path.toOSString(); - String apkName = launchInfo.mPackageFile.getName(); - String remotePath = "/data/local/tmp/" + apkName; //$NON-NLS-1$ - - SyncResult result = sync.pushFile(osLocalPath, remotePath, - SyncService.getNullProgressMonitor()); - - if (result.getCode() != SyncService.RESULT_OK) { - String msg = String.format("Failed to upload %1$s on '%2$s': %3$s", - apkName, device.getSerialNumber(), result.getMessage()); - AdtPlugin.printErrorToConsole(launchInfo.mProject, msg); - return false; - } - - // Now that the package is uploaded, we can install it properly. - // This will check that there isn't another apk declaring the same package, or - // that another install used a different key. - boolean installResult = installPackage(launchInfo, remotePath, device); - - // now we delete the app we sync'ed - try { - device.executeShellCommand("rm " + remotePath, new MultiLineReceiver() { //$NON-NLS-1$ - @Override - public void processNewLines(String[] lines) { - // pass - } - public boolean isCancelled() { - return false; - } - }); - } catch (IOException e) { - AdtPlugin.printErrorToConsole(launchInfo.mProject, String.format( - "Failed to delete temporary package: %1$s", e.getMessage())); - return false; - } - - return installResult; - } - - String msg = String.format( - "Failed to upload %1$s on device '%2$s': Unable to open sync connection!", - launchInfo.mPackageFile.getName(), device.getSerialNumber()); - AdtPlugin.printErrorToConsole(launchInfo.mProject, msg); - - return false; - } - - /** - * Installs the application package that was pushed to a temporary location on the device. - * @param launchInfo The launch information - * @param remotePath The remote path of the package. - * @param device The device on which the launch is done. - */ - private boolean installPackage(DelayedLaunchInfo launchInfo, final String remotePath, - final Device device) { - - String message = String.format("Installing %1$s...", launchInfo.mPackageFile.getName()); - AdtPlugin.printToConsole(launchInfo.mProject, message); - - try { - String result = doInstall(launchInfo, remotePath, device, false /* reinstall */); - - /* For now we force to retry the install (after uninstalling) because there's no - * other way around it: adb install does not want to update a package w/o uninstalling - * the old one first! - */ - return checkInstallResult(result, device, launchInfo, remotePath, - InstallRetryMode.ALWAYS); - } catch (IOException e) { - // do nothing, we'll return false - } - - return false; - } - - /** - * Checks the result of an installation, and takes optional actions based on it. - * @param result the result string from the installation - * @param device the device on which the installation occured. - * @param launchInfo the {@link DelayedLaunchInfo} - * @param remotePath the temporary path of the package on the device - * @param retryMode indicates what to do in case, a package already exists. - * @return true if success, false otherwise. - * @throws IOException - */ - private boolean checkInstallResult(String result, Device device, DelayedLaunchInfo launchInfo, - String remotePath, InstallRetryMode retryMode) throws IOException { - if (result == null) { - AdtPlugin.printToConsole(launchInfo.mProject, "Success!"); - return true; - } else if (result.equals("INSTALL_FAILED_ALREADY_EXISTS")) { //$NON-NLS-1$ - if (retryMode == InstallRetryMode.PROMPT) { - boolean prompt = AdtPlugin.displayPrompt("Application Install", - "A previous installation needs to be uninstalled before the new package can be installed.\nDo you want to uninstall?"); - if (prompt) { - retryMode = InstallRetryMode.ALWAYS; - } else { - AdtPlugin.printErrorToConsole(launchInfo.mProject, - "Installation error! The package already exists."); - return false; - } - } - - if (retryMode == InstallRetryMode.ALWAYS) { - /* - * TODO: create a UI that gives the dev the choice to: - * - clean uninstall on launch - * - full uninstall if application exists. - * - soft uninstall if application exists (keeps the app data around). - * - always ask (choice of soft-reinstall, full reinstall) - AdtPlugin.printErrorToConsole(launchInfo.mProject, - "Application already exists, uninstalling..."); - String res = doUninstall(device, launchInfo); - if (res == null) { - AdtPlugin.printToConsole(launchInfo.mProject, "Success!"); - } else { - AdtPlugin.printErrorToConsole(launchInfo.mProject, - String.format("Failed to uninstall: %1$s", res)); - return false; - } - */ - - AdtPlugin.printToConsole(launchInfo.mProject, - "Application already exists. Attempting to re-install instead..."); - String res = doInstall(launchInfo, remotePath, device, true /* reinstall */); - return checkInstallResult(res, device, launchInfo, remotePath, - InstallRetryMode.NEVER); - } - - AdtPlugin.printErrorToConsole(launchInfo.mProject, - "Installation error! The package already exists."); - } else if (result.equals("INSTALL_FAILED_INVALID_APK")) { //$NON-NLS-1$ - AdtPlugin.printErrorToConsole(launchInfo.mProject, - "Installation failed due to invalid APK file!", - "Please check logcat output for more details."); - } else if (result.equals("INSTALL_FAILED_INVALID_URI")) { //$NON-NLS-1$ - AdtPlugin.printErrorToConsole(launchInfo.mProject, - "Installation failed due to invalid URI!", - "Please check logcat output for more details."); - } else if (result.equals("INSTALL_FAILED_COULDNT_COPY")) { //$NON-NLS-1$ - AdtPlugin.printErrorToConsole(launchInfo.mProject, - String.format("Installation failed: Could not copy %1$s to its final location!", - launchInfo.mPackageFile.getName()), - "Please check logcat output for more details."); - } else if (result.equals("INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES")) { - AdtPlugin.printErrorToConsole(launchInfo.mProject, - "Re-installation failed due to different application signatures.", - "You must perform a full uninstall of the application. WARNING: This will remove the application data!", - String.format("Please execute 'adb uninstall %1$s' in a shell.", launchInfo.mPackageName)); - } else { - AdtPlugin.printErrorToConsole(launchInfo.mProject, - String.format("Installation error: %1$s", result), - "Please check logcat output for more details."); - } - - return false; - } - - /** - * Performs the uninstallation of an application. - * @param device the device on which to install the application. - * @param launchInfo the {@link DelayedLaunchInfo}. - * @return a {@link String} with an error code, or null if success. - * @throws IOException - */ - @SuppressWarnings("unused") - private String doUninstall(Device device, DelayedLaunchInfo launchInfo) throws IOException { - InstallReceiver receiver = new InstallReceiver(); - try { - device.executeShellCommand("pm uninstall " + launchInfo.mPackageName, //$NON-NLS-1$ - receiver); - } catch (IOException e) { - String msg = String.format( - "Failed to uninstall %1$s: %2$s", launchInfo.mPackageName, e.getMessage()); - AdtPlugin.printErrorToConsole(launchInfo.mProject, msg); - throw e; - } - - return receiver.getSuccess(); - } - - /** - * Performs the installation of an application whose package has been uploaded on the device. - *

        Before doing it, if the application is already running on the device, it is killed. - * @param launchInfo the {@link DelayedLaunchInfo}. - * @param remotePath the path of the application package in the device tmp folder. - * @param device the device on which to install the application. - * @param reinstall - * @return a {@link String} with an error code, or null if success. - * @throws IOException - */ - private String doInstall(DelayedLaunchInfo launchInfo, final String remotePath, - final Device device, boolean reinstall) throws IOException { - // kill running application - Client application = device.getClient(launchInfo.mPackageName); - if (application != null) { - application.kill(); - } - - InstallReceiver receiver = new InstallReceiver(); - try { - String cmd = String.format( - reinstall ? "pm install -r \"%1$s\"" : "pm install \"%1$s\"", //$NON-NLS-1$ //$NON-NLS-2$ - remotePath); //$NON-NLS-1$ //$NON-NLS-2$ - device.executeShellCommand(cmd, receiver); - } catch (IOException e) { - String msg = String.format( - "Failed to install %1$s on device '%2$s': %3$s", - launchInfo.mPackageFile.getName(), device.getSerialNumber(), e.getMessage()); - AdtPlugin.printErrorToConsole(launchInfo.mProject, msg); - throw e; - } - - return receiver.getSuccess(); - } - - /** - * launches an application on a device or emulator - * - * @param info the {@link DelayedLaunchInfo} that indicates the activity to launch - * @param device the device or emulator to launch the application on - */ - private void launchApp(final DelayedLaunchInfo info, Device device) { - // if we're not supposed to do anything, just stop the Launch item and return; - if (info.mLaunchAction == LaunchConfigDelegate.ACTION_DO_NOTHING) { - String msg = String.format("%1$s installed on device", - info.mPackageFile.getFullPath().toOSString()); - AdtPlugin.printToConsole(info.mProject, msg, "Done!"); - info.mLaunch.stopLaunch(); - return; - } - try { - String msg = String.format("Starting activity %1$s on device ", info.mActivity, - info.mDevice); - AdtPlugin.printToConsole(info.mProject, msg); - - // In debug mode, we need to add the info to the list of application monitoring - // client changes. - if (info.mDebugMode) { - synchronized (sListLock) { - if (mWaitingForDebuggerApplications.contains(info) == false) { - mWaitingForDebuggerApplications.add(info); - } - } - } - - // increment launch attempt count, to handle retries and timeouts - info.mAttemptCount++; - - // now we actually launch the app. - device.executeShellCommand("am start" //$NON-NLS-1$ - + (info.mDebugMode ? " -D" //$NON-NLS-1$ - : "") //$NON-NLS-1$ - + " -n " //$NON-NLS-1$ - + info.mPackageName + "/" //$NON-NLS-1$ - + info.mActivity.replaceAll("\\$", "\\\\\\$"), //$NON-NLS-1$ //$NON-NLS-2$ - new AMReceiver(info, device)); - - // if the app is not a debug app, we need to do some clean up, as - // the process is done! - if (info.mDebugMode == false) { - // stop the launch object, since there's no debug, and it can't - // provide any control over the app - info.mLaunch.stopLaunch(); - } - } catch (IOException e) { - // something went wrong trying to launch the app. - // lets stop the Launch - AdtPlugin.printErrorToConsole(info.mProject, - String.format("Launch error: %s", e.getMessage())); - info.mLaunch.stopLaunch(); - - // and remove it from the list of app waiting for debuggers - synchronized (sListLock) { - mWaitingForDebuggerApplications.remove(info); - } - } - } - - private boolean launchEmulator(AndroidLaunchConfiguration config, AvdInfo avdToLaunch) { - - // split the custom command line in segments - ArrayList customArgs = new ArrayList(); - boolean has_wipe_data = false; - if (config.mEmulatorCommandLine != null && config.mEmulatorCommandLine.length() > 0) { - String[] segments = config.mEmulatorCommandLine.split("\\s+"); //$NON-NLS-1$ - - // we need to remove the empty strings - for (String s : segments) { - if (s.length() > 0) { - customArgs.add(s); - if (!has_wipe_data && s.equals(FLAG_WIPE_DATA)) { - has_wipe_data = true; - } - } - } - } - - boolean needs_wipe_data = config.mWipeData && !has_wipe_data; - if (needs_wipe_data) { - if (!AdtPlugin.displayPrompt("Android Launch", "Are you sure you want to wipe all user data when starting this emulator?")) { - needs_wipe_data = false; - } - } - - // build the command line based on the available parameters. - ArrayList list = new ArrayList(); - - list.add(AdtPlugin.getOsAbsoluteEmulator()); - list.add(FLAG_AVD); - list.add(avdToLaunch.getName()); - - if (config.mNetworkSpeed != null) { - list.add(FLAG_NETSPEED); - list.add(config.mNetworkSpeed); - } - - if (config.mNetworkDelay != null) { - list.add(FLAG_NETDELAY); - list.add(config.mNetworkDelay); - } - - if (needs_wipe_data) { - list.add(FLAG_WIPE_DATA); - } - - if (config.mNoBootAnim) { - list.add(FLAG_NO_BOOT_ANIM); - } - - list.addAll(customArgs); - - // convert the list into an array for the call to exec. - String[] command = list.toArray(new String[list.size()]); - - // launch the emulator - try { - Process process = Runtime.getRuntime().exec(command); - grabEmulatorOutput(process); - } catch (IOException e) { - return false; - } - - return true; - } - - /** - * Looks for and returns an existing {@link ILaunchConfiguration} object for a - * specified project. - * @param manager The {@link ILaunchManager}. - * @param type The {@link ILaunchConfigurationType}. - * @param projectName The name of the project - * @return an existing ILaunchConfiguration object matching the project, or - * null. - */ - private static ILaunchConfiguration findConfig(ILaunchManager manager, - ILaunchConfigurationType type, String projectName) { - try { - ILaunchConfiguration[] configs = manager.getLaunchConfigurations(type); - - for (ILaunchConfiguration config : configs) { - if (config.getAttribute(IJavaLaunchConfigurationConstants.ATTR_PROJECT_NAME, - "").equals(projectName)) { //$NON-NLS-1$ - return config; - } - } - } catch (CoreException e) { - MessageDialog.openError(AdtPlugin.getDisplay().getActiveShell(), - "Launch Error", e.getStatus().getMessage()); - } - - // didn't find anything that matches. Return null - return null; - - } - - - /** - * Connects a remote debugger on the specified port. - * @param debugPort The port to connect the debugger to - * @param launch The associated AndroidLaunch object. - * @param monitor A Progress monitor - * @return false if cancelled by the monitor - * @throws CoreException - */ - public static boolean connectRemoteDebugger(int debugPort, - AndroidLaunch launch, IProgressMonitor monitor) - throws CoreException { - // get some default parameters. - int connectTimeout = JavaRuntime.getPreferences().getInt(JavaRuntime.PREF_CONNECT_TIMEOUT); - - HashMap newMap = new HashMap(); - - newMap.put("hostname", "localhost"); //$NON-NLS-1$ //$NON-NLS-2$ - - newMap.put("port", Integer.toString(debugPort)); //$NON-NLS-1$ - - newMap.put("timeout", Integer.toString(connectTimeout)); - - // get the default VM connector - IVMConnector connector = JavaRuntime.getDefaultVMConnector(); - - // connect to remote VM - connector.connect(newMap, monitor, launch); - - // check for cancellation - if (monitor.isCanceled()) { - IDebugTarget[] debugTargets = launch.getDebugTargets(); - for (IDebugTarget target : debugTargets) { - if (target.canDisconnect()) { - target.disconnect(); - } - } - return false; - } - - return true; - } - - /** - * Launch a new thread that connects a remote debugger on the specified port. - * @param debugPort The port to connect the debugger to - * @param androidLaunch The associated AndroidLaunch object. - * @param monitor A Progress monitor - * @see #connectRemoteDebugger(int, AndroidLaunch, IProgressMonitor) - */ - public static void launchRemoteDebugger( final int debugPort, final AndroidLaunch androidLaunch, - final IProgressMonitor monitor) { - new Thread("Debugger connection") { //$NON-NLS-1$ - @Override - public void run() { - try { - connectRemoteDebugger(debugPort, androidLaunch, monitor); - } catch (CoreException e) { - androidLaunch.stopLaunch(); - } - monitor.done(); - } - }.start(); - } - - /** - * Sent when a new {@link AndroidDebugBridge} is started. - *

        - * This is sent from a non UI thread. - * @param bridge the new {@link AndroidDebugBridge} object. - * - * @see IDebugBridgeChangeListener#bridgeChanged(AndroidDebugBridge) - */ - public void bridgeChanged(AndroidDebugBridge bridge) { - // The adb server has changed. We cancel any pending launches. - String message1 = "adb server change: cancelling '%1$s' launch!"; - String message2 = "adb server change: cancelling sync!"; - synchronized (sListLock) { - for (DelayedLaunchInfo launchInfo : mWaitingForReadyEmulatorList) { - if (launchInfo.mLaunchAction == LaunchConfigDelegate.ACTION_DO_NOTHING) { - AdtPlugin.printErrorToConsole(launchInfo.mProject, message2); - } else { - AdtPlugin.printErrorToConsole(launchInfo.mProject, - String.format(message1, launchInfo.mActivity)); - } - launchInfo.mLaunch.stopLaunch(); - } - for (DelayedLaunchInfo launchInfo : mWaitingForDebuggerApplications) { - if (launchInfo.mLaunchAction == LaunchConfigDelegate.ACTION_DO_NOTHING) { - AdtPlugin.printErrorToConsole(launchInfo.mProject, message2); - } else { - AdtPlugin.printErrorToConsole(launchInfo.mProject, - String.format(message1, launchInfo.mActivity)); - } - launchInfo.mLaunch.stopLaunch(); - } - - mWaitingForReadyEmulatorList.clear(); - mWaitingForDebuggerApplications.clear(); - } - } - - /** - * Sent when the a device is connected to the {@link AndroidDebugBridge}. - *

        - * This is sent from a non UI thread. - * @param device the new device. - * - * @see IDeviceChangeListener#deviceConnected(Device) - */ - public void deviceConnected(Device device) { - synchronized (sListLock) { - // look if there's an app waiting for a device - if (mWaitingForEmulatorLaunches.size() > 0) { - // get/remove first launch item from the list - // FIXME: what if we have multiple launches waiting? - DelayedLaunchInfo launchInfo = mWaitingForEmulatorLaunches.get(0); - mWaitingForEmulatorLaunches.remove(0); - - // give the launch item its device for later use. - launchInfo.mDevice = device; - - // and move it to the other list - mWaitingForReadyEmulatorList.add(launchInfo); - - // and tell the user about it - AdtPlugin.printToConsole(launchInfo.mProject, - String.format("New emulator found: %1$s", device.getSerialNumber())); - AdtPlugin.printToConsole(launchInfo.mProject, - String.format("Waiting for HOME ('%1$s') to be launched...", - AdtPlugin.getDefault().getPreferenceStore().getString( - AdtPlugin.PREFS_HOME_PACKAGE))); - } - } - } - - /** - * Sent when the a device is connected to the {@link AndroidDebugBridge}. - *

        - * This is sent from a non UI thread. - * @param device the new device. - * - * @see IDeviceChangeListener#deviceDisconnected(Device) - */ - @SuppressWarnings("unchecked") - public void deviceDisconnected(Device device) { - // any pending launch on this device must be canceled. - String message = "%1$s disconnected! Cancelling '%2$s' launch!"; - synchronized (sListLock) { - ArrayList copyList = - (ArrayList)mWaitingForReadyEmulatorList.clone(); - for (DelayedLaunchInfo launchInfo : copyList) { - if (launchInfo.mDevice == device) { - AdtPlugin.printErrorToConsole(launchInfo.mProject, - String.format(message, device.getSerialNumber(), launchInfo.mActivity)); - launchInfo.mLaunch.stopLaunch(); - mWaitingForReadyEmulatorList.remove(launchInfo); - } - } - copyList = (ArrayList)mWaitingForDebuggerApplications.clone(); - for (DelayedLaunchInfo launchInfo : copyList) { - if (launchInfo.mDevice == device) { - AdtPlugin.printErrorToConsole(launchInfo.mProject, - String.format(message, device.getSerialNumber(), launchInfo.mActivity)); - launchInfo.mLaunch.stopLaunch(); - mWaitingForDebuggerApplications.remove(launchInfo); - } - } - } - } - - /** - * Sent when a device data changed, or when clients are started/terminated on the device. - *

        - * This is sent from a non UI thread. - * @param device the device that was updated. - * @param changeMask the mask indicating what changed. - * - * @see IDeviceChangeListener#deviceChanged(Device, int) - */ - public void deviceChanged(Device device, int changeMask) { - // We could check if any starting device we care about is now ready, but we can wait for - // its home app to show up, so... - } - - /** - * Sent when an existing client information changed. - *

        - * This is sent from a non UI thread. - * @param client the updated client. - * @param changeMask the bit mask describing the changed properties. It can contain - * any of the following values: {@link Client#CHANGE_INFO}, {@link Client#CHANGE_NAME} - * {@link Client#CHANGE_DEBUGGER_INTEREST}, {@link Client#CHANGE_THREAD_MODE}, - * {@link Client#CHANGE_THREAD_DATA}, {@link Client#CHANGE_HEAP_MODE}, - * {@link Client#CHANGE_HEAP_DATA}, {@link Client#CHANGE_NATIVE_HEAP_DATA} - * - * @see IClientChangeListener#clientChanged(Client, int) - */ - public void clientChanged(final Client client, int changeMask) { - boolean connectDebugger = false; - if ((changeMask & Client.CHANGE_NAME) == Client.CHANGE_NAME) { - String applicationName = client.getClientData().getClientDescription(); - if (applicationName != null) { - IPreferenceStore store = AdtPlugin.getDefault().getPreferenceStore(); - String home = store.getString(AdtPlugin.PREFS_HOME_PACKAGE); - - if (home.equals(applicationName)) { - - // looks like home is up, get its device - Device device = client.getDevice(); - - // look for application waiting for home - synchronized (sListLock) { - for (int i = 0 ; i < mWaitingForReadyEmulatorList.size() ;) { - DelayedLaunchInfo launchInfo = mWaitingForReadyEmulatorList.get(i); - if (launchInfo.mDevice == device) { - // it's match, remove from the list - mWaitingForReadyEmulatorList.remove(i); - - // We couldn't check earlier the API level of the device - // (it's asynchronous when the device boot, and usually - // deviceConnected is called before it's queried for its build info) - // so we check now - if (checkBuildInfo(launchInfo, device) == false) { - // device is not the proper API! - AdtPlugin.printErrorToConsole(launchInfo.mProject, - "Launch canceled!"); - launchInfo.mLaunch.stopLaunch(); - return; - } - - AdtPlugin.printToConsole(launchInfo.mProject, - String.format("HOME is up on device '%1$s'", - device.getSerialNumber())); - - // attempt to sync the new package onto the device. - if (syncApp(launchInfo, device)) { - // application package is sync'ed, lets attempt to launch it. - launchApp(launchInfo, device); - } else { - // failure! Cancel and return - AdtPlugin.printErrorToConsole(launchInfo.mProject, - "Launch canceled!"); - launchInfo.mLaunch.stopLaunch(); - } - - break; - } else { - i++; - } - } - } - } - - // check if it's already waiting for a debugger, and if so we connect to it. - if (client.getClientData().getDebuggerConnectionStatus() == ClientData.DEBUGGER_WAITING) { - // search for this client in the list; - synchronized (sListLock) { - int index = mUnknownClientsWaitingForDebugger.indexOf(client); - if (index != -1) { - connectDebugger = true; - mUnknownClientsWaitingForDebugger.remove(client); - } - } - } - } - } - - // if it's not home, it could be an app that is now in debugger mode that we're waiting for - // lets check it - - if ((changeMask & Client.CHANGE_DEBUGGER_INTEREST) == Client.CHANGE_DEBUGGER_INTEREST) { - ClientData clientData = client.getClientData(); - String applicationName = client.getClientData().getClientDescription(); - if (clientData.getDebuggerConnectionStatus() == ClientData.DEBUGGER_WAITING) { - // Get the application name, and make sure its valid. - if (applicationName == null) { - // looks like we don't have the client yet, so we keep it around for when its - // name becomes available. - synchronized (sListLock) { - mUnknownClientsWaitingForDebugger.add(client); - } - return; - } else { - connectDebugger = true; - } - } - } - - if (connectDebugger) { - Log.d("adt", "Debugging " + client); - // now check it against the apps waiting for a debugger - String applicationName = client.getClientData().getClientDescription(); - Log.d("adt", "App Name: " + applicationName); - synchronized (sListLock) { - for (int i = 0 ; i < mWaitingForDebuggerApplications.size() ;) { - final DelayedLaunchInfo launchInfo = mWaitingForDebuggerApplications.get(i); - if (client.getDevice() == launchInfo.mDevice && - applicationName.equals(launchInfo.mPackageName)) { - // this is a match. We remove the launch info from the list - mWaitingForDebuggerApplications.remove(i); - - // and connect the debugger. - String msg = String.format( - "Attempting to connect debugger to '%1$s' on port %2$d", - launchInfo.mPackageName, client.getDebuggerListenPort()); - AdtPlugin.printToConsole(launchInfo.mProject, msg); - - new Thread("Debugger Connection") { //$NON-NLS-1$ - @Override - public void run() { - try { - if (connectRemoteDebugger( - client.getDebuggerListenPort(), - launchInfo.mLaunch, launchInfo.mMonitor) == false) { - return; - } - } catch (CoreException e) { - // well something went wrong. - AdtPlugin.printErrorToConsole(launchInfo.mProject, - String.format("Launch error: %s", e.getMessage())); - // stop the launch - launchInfo.mLaunch.stopLaunch(); - } - - launchInfo.mMonitor.done(); - } - }.start(); - - // we're done processing this client. - return; - - } else { - i++; - } - } - } - - // if we get here, we haven't found an app that we were launching, so we look - // for opened android projects that contains the app asking for a debugger. - // If we find one, we automatically connect to it. - IProject project = ProjectHelper.findAndroidProjectByAppName(applicationName); - - if (project != null) { - debugRunningApp(project, client.getDebuggerListenPort()); - } - } - } - - /** - * Get the stderr/stdout outputs of a process and return when the process is done. - * Both must be read or the process will block on windows. - * @param process The process to get the ouput from - */ - private void grabEmulatorOutput(final Process process) { - // read the lines as they come. if null is returned, it's - // because the process finished - new Thread("") { //$NON-NLS-1$ - @Override - public void run() { - // create a buffer to read the stderr output - InputStreamReader is = new InputStreamReader(process.getErrorStream()); - BufferedReader errReader = new BufferedReader(is); - - try { - while (true) { - String line = errReader.readLine(); - if (line != null) { - AdtPlugin.printErrorToConsole("Emulator", line); - } else { - break; - } - } - } catch (IOException e) { - // do nothing. - } - } - }.start(); - - new Thread("") { //$NON-NLS-1$ - @Override - public void run() { - InputStreamReader is = new InputStreamReader(process.getInputStream()); - BufferedReader outReader = new BufferedReader(is); - - try { - while (true) { - String line = outReader.readLine(); - if (line != null) { - AdtPlugin.printToConsole("Emulator", line); - } else { - break; - } - } - } catch (IOException e) { - // do nothing. - } - } - }.start(); - } - -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/debug/launching/DeviceChooserDialog.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/debug/launching/DeviceChooserDialog.java deleted file mode 100644 index a260350..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/debug/launching/DeviceChooserDialog.java +++ /dev/null @@ -1,705 +0,0 @@ -/* - * Copyright (C) 2007 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.debug.launching; - -import com.android.ddmlib.AndroidDebugBridge; -import com.android.ddmlib.Client; -import com.android.ddmlib.Device; -import com.android.ddmlib.IDevice; -import com.android.ddmlib.AndroidDebugBridge.IDeviceChangeListener; -import com.android.ddmlib.Device.DeviceState; -import com.android.ddmuilib.IImageLoader; -import com.android.ddmuilib.ImageHelper; -import com.android.ddmuilib.TableHelper; -import com.android.ide.eclipse.adt.AdtPlugin; -import com.android.ide.eclipse.adt.sdk.Sdk; -import com.android.ide.eclipse.ddms.DdmsPlugin; -import com.android.sdklib.IAndroidTarget; -import com.android.sdklib.avd.AvdManager; -import com.android.sdklib.avd.AvdManager.AvdInfo; -import com.android.sdkuilib.AvdSelector; - -import org.eclipse.jface.dialogs.Dialog; -import org.eclipse.jface.dialogs.IDialogConstants; -import org.eclipse.jface.preference.IPreferenceStore; -import org.eclipse.jface.viewers.DoubleClickEvent; -import org.eclipse.jface.viewers.IDoubleClickListener; -import org.eclipse.jface.viewers.ILabelProviderListener; -import org.eclipse.jface.viewers.ISelection; -import org.eclipse.jface.viewers.IStructuredContentProvider; -import org.eclipse.jface.viewers.IStructuredSelection; -import org.eclipse.jface.viewers.ITableLabelProvider; -import org.eclipse.jface.viewers.StructuredSelection; -import org.eclipse.jface.viewers.TableViewer; -import org.eclipse.jface.viewers.Viewer; -import org.eclipse.swt.SWT; -import org.eclipse.swt.SWTException; -import org.eclipse.swt.events.SelectionAdapter; -import org.eclipse.swt.events.SelectionEvent; -import org.eclipse.swt.graphics.Image; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.layout.GridLayout; -import org.eclipse.swt.widgets.Button; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; -import org.eclipse.swt.widgets.Display; -import org.eclipse.swt.widgets.Shell; -import org.eclipse.swt.widgets.Table; - -import java.util.ArrayList; - -public class DeviceChooserDialog extends Dialog implements IDeviceChangeListener { - - private final static int ICON_WIDTH = 16; - - private final static String PREFS_COL_SERIAL = "deviceChooser.serial"; //$NON-NLS-1$ - private final static String PREFS_COL_STATE = "deviceChooser.state"; //$NON-NLS-1$ - private final static String PREFS_COL_AVD = "deviceChooser.avd"; //$NON-NLS-1$ - private final static String PREFS_COL_TARGET = "deviceChooser.target"; //$NON-NLS-1$ - private final static String PREFS_COL_DEBUG = "deviceChooser.debug"; //$NON-NLS-1$ - - private Table mDeviceTable; - private TableViewer mViewer; - private AvdSelector mPreferredAvdSelector; - - private Image mDeviceImage; - private Image mEmulatorImage; - private Image mMatchImage; - private Image mNoMatchImage; - private Image mWarningImage; - - private final DeviceChooserResponse mResponse; - private final String mPackageName; - private final IAndroidTarget mProjectTarget; - private final Sdk mSdk; - - private final AvdInfo[] mFullAvdList; - - private Button mDeviceRadioButton; - - private boolean mDisableAvdSelectionChange = false; - - /** - * Basic Content Provider for a table full of {@link Device} objects. The input is - * a {@link AndroidDebugBridge}. - */ - private class ContentProvider implements IStructuredContentProvider { - public Object[] getElements(Object inputElement) { - if (inputElement instanceof AndroidDebugBridge) { - return ((AndroidDebugBridge)inputElement).getDevices(); - } - - return new Object[0]; - } - - public void dispose() { - // pass - } - - public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { - // pass - } - } - - - /** - * A Label Provider for the {@link TableViewer} in {@link DeviceChooserDialog}. - * It provides labels and images for {@link Device} objects. - */ - private class LabelProvider implements ITableLabelProvider { - - public Image getColumnImage(Object element, int columnIndex) { - if (element instanceof Device) { - Device device = (Device)element; - switch (columnIndex) { - case 0: - return device.isEmulator() ? mEmulatorImage : mDeviceImage; - - case 2: - // check for compatibility. - if (device.isEmulator() == false) { // physical device - // get the api level of the device - try { - String apiValue = device.getProperty( - IDevice.PROP_BUILD_VERSION_NUMBER); - if (apiValue != null) { - int api = Integer.parseInt(apiValue); - if (api >= mProjectTarget.getApiVersionNumber()) { - // if the project is compiling against an add-on, the optional - // API may be missing from the device. - return mProjectTarget.isPlatform() ? - mMatchImage : mWarningImage; - } else { - return mNoMatchImage; - } - } else { - return mWarningImage; - } - } catch (NumberFormatException e) { - // lets consider the device non compatible - return mNoMatchImage; - } - } else { - // get the AvdInfo - AvdInfo info = mSdk.getAvdManager().getAvd(device.getAvdName()); - if (info == null) { - return mWarningImage; - } - return mProjectTarget.isCompatibleBaseFor(info.getTarget()) ? - mMatchImage : mNoMatchImage; - } - } - } - - return null; - } - - public String getColumnText(Object element, int columnIndex) { - if (element instanceof Device) { - Device device = (Device)element; - switch (columnIndex) { - case 0: - return device.getSerialNumber(); - case 1: - if (device.isEmulator()) { - return device.getAvdName(); - } else { - return "N/A"; // devices don't have AVD names. - } - case 2: - if (device.isEmulator()) { - AvdInfo info = mSdk.getAvdManager().getAvd(device.getAvdName()); - if (info == null) { - return "?"; - } - return info.getTarget().getFullName(); - } else { - String deviceBuild = device.getProperty(IDevice.PROP_BUILD_VERSION); - if (deviceBuild == null) { - return "unknown"; - } - return deviceBuild; - } - case 3: - String debuggable = device.getProperty(IDevice.PROP_DEBUGGABLE); - if (debuggable != null && debuggable.equals("1")) { //$NON-NLS-1$ - return "Yes"; - } else { - return ""; - } - case 4: - return getStateString(device); - } - } - - return null; - } - - public void addListener(ILabelProviderListener listener) { - // pass - } - - public void dispose() { - // pass - } - - public boolean isLabelProperty(Object element, String property) { - // pass - return false; - } - - public void removeListener(ILabelProviderListener listener) { - // pass - } - } - - public static class DeviceChooserResponse { - private AvdInfo mAvdToLaunch; - private Device mDeviceToUse; - - public void setDeviceToUse(Device d) { - mDeviceToUse = d; - mAvdToLaunch = null; - } - - public void setAvdToLaunch(AvdInfo avd) { - mAvdToLaunch = avd; - mDeviceToUse = null; - } - - public Device getDeviceToUse() { - return mDeviceToUse; - } - - public AvdInfo getAvdToLaunch() { - return mAvdToLaunch; - } - } - - public DeviceChooserDialog(Shell parent, DeviceChooserResponse response, String packageName, - IAndroidTarget projectTarget) { - super(parent); - mResponse = response; - mPackageName = packageName; - mProjectTarget = projectTarget; - mSdk = Sdk.getCurrent(); - - // get the full list of Android Virtual Devices - AvdManager avdManager = mSdk.getAvdManager(); - if (avdManager != null) { - mFullAvdList = avdManager.getAvds(); - } else { - mFullAvdList = null; - } - - loadImages(); - } - - private void cleanup() { - // done listening. - AndroidDebugBridge.removeDeviceChangeListener(this); - - mEmulatorImage.dispose(); - mDeviceImage.dispose(); - mMatchImage.dispose(); - mNoMatchImage.dispose(); - mWarningImage.dispose(); - } - - @Override - protected void okPressed() { - cleanup(); - super.okPressed(); - } - - @Override - protected void cancelPressed() { - cleanup(); - super.cancelPressed(); - } - - @Override - protected Control createContents(Composite parent) { - Control content = super.createContents(parent); - - // this must be called after createContents() has happened so that the - // ok button has been created (it's created after the call to createDialogArea) - updateDefaultSelection(); - - return content; - } - - - @Override - protected Control createDialogArea(Composite parent) { - Composite top = new Composite(parent, SWT.NONE); - top.setLayout(new GridLayout(1, true)); - - mDeviceRadioButton = new Button(top, SWT.RADIO); - mDeviceRadioButton.setText("Choose a running Android device"); - mDeviceRadioButton.addSelectionListener(new SelectionAdapter() { - @Override - public void widgetSelected(SelectionEvent e) { - boolean deviceMode = mDeviceRadioButton.getSelection(); - - mDeviceTable.setEnabled(deviceMode); - mPreferredAvdSelector.setEnabled(!deviceMode); - - if (deviceMode) { - handleDeviceSelection(); - } else { - mResponse.setAvdToLaunch(mPreferredAvdSelector.getFirstSelected()); - } - - enableOkButton(); - } - }); - mDeviceRadioButton.setSelection(true); - - - // offset the selector from the radio button - Composite offsetComp = new Composite(top, SWT.NONE); - offsetComp.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); - GridLayout layout = new GridLayout(1, false); - layout.marginRight = layout.marginHeight = 0; - layout.marginLeft = 30; - offsetComp.setLayout(layout); - - IPreferenceStore store = AdtPlugin.getDefault().getPreferenceStore(); - mDeviceTable = new Table(offsetComp, SWT.SINGLE | SWT.FULL_SELECTION); - GridData gd; - mDeviceTable.setLayoutData(gd = new GridData(GridData.FILL_BOTH)); - gd.heightHint = 100; - - mDeviceTable.setHeaderVisible(true); - mDeviceTable.setLinesVisible(true); - - TableHelper.createTableColumn(mDeviceTable, "Serial Number", - SWT.LEFT, "AAA+AAAAAAAAAAAAAAAAAAA", //$NON-NLS-1$ - PREFS_COL_SERIAL, store); - - TableHelper.createTableColumn(mDeviceTable, "AVD Name", - SWT.LEFT, "engineering", //$NON-NLS-1$ - PREFS_COL_AVD, store); - - TableHelper.createTableColumn(mDeviceTable, "Target", - SWT.LEFT, "AAA+Android 9.9.9", //$NON-NLS-1$ - PREFS_COL_TARGET, store); - - TableHelper.createTableColumn(mDeviceTable, "Debug", - SWT.LEFT, "Debug", //$NON-NLS-1$ - PREFS_COL_DEBUG, store); - - TableHelper.createTableColumn(mDeviceTable, "State", - SWT.LEFT, "bootloader", //$NON-NLS-1$ - PREFS_COL_STATE, store); - - // create the viewer for it - mViewer = new TableViewer(mDeviceTable); - mViewer.setContentProvider(new ContentProvider()); - mViewer.setLabelProvider(new LabelProvider()); - mViewer.setInput(AndroidDebugBridge.getBridge()); - mViewer.addDoubleClickListener(new IDoubleClickListener() { - public void doubleClick(DoubleClickEvent event) { - ISelection selection = event.getSelection(); - if (selection instanceof IStructuredSelection) { - IStructuredSelection structuredSelection = (IStructuredSelection)selection; - Object object = structuredSelection.getFirstElement(); - if (object instanceof Device) { - mResponse.setDeviceToUse((Device)object); - } - } - } - }); - - Button radio2 = new Button(top, SWT.RADIO); - radio2.setText("Launch a new Android Virtual Device"); - - // offset the selector from the radio button - offsetComp = new Composite(top, SWT.NONE); - offsetComp.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); - layout = new GridLayout(1, false); - layout.marginRight = layout.marginHeight = 0; - layout.marginLeft = 30; - offsetComp.setLayout(layout); - - mPreferredAvdSelector = new AvdSelector(offsetComp, getNonRunningAvds(), mProjectTarget, - false /*allowMultipleSelection*/); - mPreferredAvdSelector.setTableHeightHint(100); - mPreferredAvdSelector.setEnabled(false); - mDeviceTable.addSelectionListener(new SelectionAdapter() { - @Override - public void widgetSelected(SelectionEvent e) { - handleDeviceSelection(); - } - }); - - mPreferredAvdSelector.setSelectionListener(new SelectionAdapter() { - @Override - public void widgetSelected(SelectionEvent e) { - if (mDisableAvdSelectionChange == false) { - mResponse.setAvdToLaunch(mPreferredAvdSelector.getFirstSelected()); - enableOkButton(); - } - } - }); - - AndroidDebugBridge.addDeviceChangeListener(this); - - return top; - } - - private void loadImages() { - IImageLoader ddmsLoader = DdmsPlugin.getImageLoader(); - Display display = DdmsPlugin.getDisplay(); - IImageLoader adtLoader = AdtPlugin.getImageLoader(); - - if (mDeviceImage == null) { - mDeviceImage = ImageHelper.loadImage(ddmsLoader, display, - "device.png", //$NON-NLS-1$ - ICON_WIDTH, ICON_WIDTH, - display.getSystemColor(SWT.COLOR_RED)); - } - if (mEmulatorImage == null) { - mEmulatorImage = ImageHelper.loadImage(ddmsLoader, display, - "emulator.png", ICON_WIDTH, ICON_WIDTH, //$NON-NLS-1$ - display.getSystemColor(SWT.COLOR_BLUE)); - } - - if (mMatchImage == null) { - mMatchImage = ImageHelper.loadImage(adtLoader, display, - "match.png", //$NON-NLS-1$ - ICON_WIDTH, ICON_WIDTH, - display.getSystemColor(SWT.COLOR_GREEN)); - } - - if (mNoMatchImage == null) { - mNoMatchImage = ImageHelper.loadImage(adtLoader, display, - "error.png", //$NON-NLS-1$ - ICON_WIDTH, ICON_WIDTH, - display.getSystemColor(SWT.COLOR_RED)); - } - - if (mWarningImage == null) { - mWarningImage = ImageHelper.loadImage(adtLoader, display, - "warning.png", //$NON-NLS-1$ - ICON_WIDTH, ICON_WIDTH, - display.getSystemColor(SWT.COLOR_YELLOW)); - } - - } - - /** - * Returns a display string representing the state of the device. - * @param d the device - */ - private static String getStateString(Device d) { - DeviceState deviceState = d.getState(); - if (deviceState == DeviceState.ONLINE) { - return "Online"; - } else if (deviceState == DeviceState.OFFLINE) { - return "Offline"; - } else if (deviceState == DeviceState.BOOTLOADER) { - return "Bootloader"; - } - - return "??"; - } - - /** - * Sent when the a device is connected to the {@link AndroidDebugBridge}. - *

        - * This is sent from a non UI thread. - * @param device the new device. - * - * @see IDeviceChangeListener#deviceConnected(Device) - */ - public void deviceConnected(Device device) { - final DeviceChooserDialog dialog = this; - exec(new Runnable() { - public void run() { - if (mDeviceTable.isDisposed() == false) { - // refresh all - mViewer.refresh(); - - // update the selection - updateDefaultSelection(); - - // update the display of AvdInfo (since it's filtered to only display - // non running AVD.) - refillAvdList(); - } else { - // table is disposed, we need to do something. - // lets remove ourselves from the listener. - AndroidDebugBridge.removeDeviceChangeListener(dialog); - } - - } - }); - } - - /** - * Sent when the a device is connected to the {@link AndroidDebugBridge}. - *

        - * This is sent from a non UI thread. - * @param device the new device. - * - * @see IDeviceChangeListener#deviceDisconnected(Device) - */ - public void deviceDisconnected(Device device) { - deviceConnected(device); - } - - /** - * Sent when a device data changed, or when clients are started/terminated on the device. - *

        - * This is sent from a non UI thread. - * @param device the device that was updated. - * @param changeMask the mask indicating what changed. - * - * @see IDeviceChangeListener#deviceChanged(Device, int) - */ - public void deviceChanged(final Device device, int changeMask) { - if ((changeMask & (Device.CHANGE_STATE | Device.CHANGE_BUILD_INFO)) != 0) { - final DeviceChooserDialog dialog = this; - exec(new Runnable() { - public void run() { - if (mDeviceTable.isDisposed() == false) { - // refresh the device - mViewer.refresh(device); - - // update the defaultSelection. - updateDefaultSelection(); - - // update the display of AvdInfo (since it's filtered to only display - // non running AVD). This is done on deviceChanged because the avd name - // of a (emulator) device may be updated as the emulator boots. - refillAvdList(); - - // if the changed device is the current selection, - // we update the OK button based on its state. - if (device == mResponse.getDeviceToUse()) { - enableOkButton(); - } - - } else { - // table is disposed, we need to do something. - // lets remove ourselves from the listener. - AndroidDebugBridge.removeDeviceChangeListener(dialog); - } - } - }); - } - } - - /** - * Returns whether the dialog is in "device" mode (true), or in "avd" mode (false). - */ - private boolean isDeviceMode() { - return mDeviceRadioButton.getSelection(); - } - - /** - * Enables or disables the OK button of the dialog based on various selections in the dialog. - */ - private void enableOkButton() { - Button okButton = getButton(IDialogConstants.OK_ID); - - if (isDeviceMode()) { - okButton.setEnabled(mResponse.getDeviceToUse() != null && - mResponse.getDeviceToUse().isOnline()); - } else { - okButton.setEnabled(mResponse.getAvdToLaunch() != null); - } - } - - /** - * Executes the {@link Runnable} in the UI thread. - * @param runnable the runnable to execute. - */ - private void exec(Runnable runnable) { - try { - Display display = mDeviceTable.getDisplay(); - display.asyncExec(runnable); - } catch (SWTException e) { - // tree is disposed, we need to do something. lets remove ourselves from the listener. - AndroidDebugBridge.removeDeviceChangeListener(this); - } - } - - private void handleDeviceSelection() { - int count = mDeviceTable.getSelectionCount(); - if (count != 1) { - handleSelection(null); - } else { - int index = mDeviceTable.getSelectionIndex(); - Object data = mViewer.getElementAt(index); - if (data instanceof Device) { - handleSelection((Device)data); - } else { - handleSelection(null); - } - } - } - - private void handleSelection(Device device) { - mResponse.setDeviceToUse(device); - enableOkButton(); - } - - /** - * Look for a default device to select. This is done by looking for the running - * clients on each device and finding one similar to the one being launched. - *

        - * This is done every time the device list changed unless there is a already selection. - */ - private void updateDefaultSelection() { - if (mDeviceTable.getSelectionCount() == 0) { - AndroidDebugBridge bridge = AndroidDebugBridge.getBridge(); - - Device[] devices = bridge.getDevices(); - - for (Device device : devices) { - Client[] clients = device.getClients(); - - for (Client client : clients) { - - if (mPackageName.equals(client.getClientData().getClientDescription())) { - // found a match! Select it. - mViewer.setSelection(new StructuredSelection(device)); - handleSelection(device); - - // and we're done. - return; - } - } - } - } - - handleDeviceSelection(); - } - - /** - * Returns the list of {@link AvdInfo} that are not already running in an emulator. - */ - private AvdInfo[] getNonRunningAvds() { - ArrayList list = new ArrayList(); - - Device[] devices = AndroidDebugBridge.getBridge().getDevices(); - - // loop through all the Avd and put the one that are not running in the list. - avdLoop: for (AvdInfo info : mFullAvdList) { - for (Device d : devices) { - if (info.getName().equals(d.getAvdName())) { - continue avdLoop; - } - } - list.add(info); - } - - return list.toArray(new AvdInfo[list.size()]); - } - - /** - * Refills the AVD list keeping the current selection. - */ - private void refillAvdList() { - AvdInfo[] array = getNonRunningAvds(); - - // save the current selection - AvdInfo selected = mPreferredAvdSelector.getFirstSelected(); - - // disable selection change. - mDisableAvdSelectionChange = true; - - // set the new list in the selector - mPreferredAvdSelector.setAvds(array, mProjectTarget); - - // attempt to reselect the proper avd if needed - if (selected != null) { - if (mPreferredAvdSelector.setSelection(selected) == false) { - // looks like the selection is lost. this can happen if an emulator - // running the AVD that was selected was launched from outside of Eclipse). - mResponse.setAvdToLaunch(null); - enableOkButton(); - } - } - - // enable the selection change - mDisableAvdSelectionChange = false; - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/debug/launching/LaunchConfigDelegate.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/debug/launching/LaunchConfigDelegate.java deleted file mode 100644 index bbd320b..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/debug/launching/LaunchConfigDelegate.java +++ /dev/null @@ -1,431 +0,0 @@ -/* - * Copyright (C) 2007 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.debug.launching; - -import com.android.ddmlib.AndroidDebugBridge; -import com.android.ide.eclipse.adt.AdtPlugin; -import com.android.ide.eclipse.adt.debug.launching.AndroidLaunchController.AndroidLaunchConfiguration; -import com.android.ide.eclipse.adt.project.ProjectHelper; -import com.android.ide.eclipse.common.AndroidConstants; -import com.android.ide.eclipse.common.project.AndroidManifestParser; -import com.android.ide.eclipse.common.project.BaseProjectHelper; - -import org.eclipse.core.resources.IFile; -import org.eclipse.core.resources.IFolder; -import org.eclipse.core.resources.IProject; -import org.eclipse.core.resources.IResource; -import org.eclipse.core.resources.IWorkspace; -import org.eclipse.core.resources.ResourcesPlugin; -import org.eclipse.core.runtime.CoreException; -import org.eclipse.core.runtime.IProgressMonitor; -import org.eclipse.core.runtime.IStatus; -import org.eclipse.core.runtime.Status; -import org.eclipse.debug.core.ILaunch; -import org.eclipse.debug.core.ILaunchConfiguration; -import org.eclipse.debug.core.model.LaunchConfigurationDelegate; -import org.eclipse.jdt.core.JavaCore; -import org.eclipse.jdt.launching.IJavaLaunchConfigurationConstants; - -/** - * Implementation of an eclipse LauncConfigurationDelegate to launch android - * application in debug. - */ -public class LaunchConfigDelegate extends LaunchConfigurationDelegate { - final static int INVALID_DEBUG_PORT = -1; - - public final static String ANDROID_LAUNCH_TYPE_ID = - "com.android.ide.eclipse.adt.debug.LaunchConfigType"; //$NON-NLS-1$ - - /** Target mode parameters: true is automatic, false is manual */ - public static final String ATTR_TARGET_MODE = AdtPlugin.PLUGIN_ID + ".target"; //$NON-NLS-1$ - public static final boolean DEFAULT_TARGET_MODE = true; //automatic mode - - /** - * Launch action: - *

          - *
        • 0: launch default activity
        • - *
        • 1: launch specified activity. See {@link #ATTR_ACTIVITY}
        • - *
        • 2: Do Nothing
        • - *
        - */ - public final static String ATTR_LAUNCH_ACTION = AdtPlugin.PLUGIN_ID + ".action"; //$NON-NLS-1$ - - /** Default launch action. This launches the activity that is setup to be found in the HOME - * screen. - */ - public final static int ACTION_DEFAULT = 0; - /** Launch action starting a specific activity. */ - public final static int ACTION_ACTIVITY = 1; - /** Launch action that does nothing. */ - public final static int ACTION_DO_NOTHING = 2; - /** Default launch action value. */ - public final static int DEFAULT_LAUNCH_ACTION = ACTION_DEFAULT; - - /** - * Activity to be launched if {@link #ATTR_LAUNCH_ACTION} is 1 - */ - public static final String ATTR_ACTIVITY = AdtPlugin.PLUGIN_ID + ".activity"; //$NON-NLS-1$ - - public static final String ATTR_AVD_NAME = AdtPlugin.PLUGIN_ID + ".avd"; //$NON-NLS-1$ - - public static final String ATTR_SPEED = AdtPlugin.PLUGIN_ID + ".speed"; //$NON-NLS-1$ - - /** - * Index of the default network speed setting for the emulator.
        - * Get the emulator option with EmulatorConfigTab.getSpeed(index) - */ - public static final int DEFAULT_SPEED = 0; - - public static final String ATTR_DELAY = AdtPlugin.PLUGIN_ID + ".delay"; //$NON-NLS-1$ - - /** - * Index of the default network latency setting for the emulator.
        - * Get the emulator option with EmulatorConfigTab.getDelay(index) - */ - public static final int DEFAULT_DELAY = 0; - - public static final String ATTR_COMMANDLINE = AdtPlugin.PLUGIN_ID + ".commandline"; //$NON-NLS-1$ - - public static final String ATTR_WIPE_DATA = AdtPlugin.PLUGIN_ID + ".wipedata"; //$NON-NLS-1$ - public static final boolean DEFAULT_WIPE_DATA = false; - - public static final String ATTR_NO_BOOT_ANIM = AdtPlugin.PLUGIN_ID + ".nobootanim"; //$NON-NLS-1$ - public static final boolean DEFAULT_NO_BOOT_ANIM = false; - - public static final String ATTR_DEBUG_PORT = - AdtPlugin.PLUGIN_ID + ".debugPort"; //$NON-NLS-1$ - - public void launch(ILaunchConfiguration configuration, String mode, - ILaunch launch, IProgressMonitor monitor) throws CoreException { - // We need to check if it's a standard launch or if it's a launch - // to debug an application already running. - int debugPort = AndroidLaunchController.getPortForConfig(configuration); - - // get the project - IProject project = getProject(configuration); - - // first we make sure the launch is of the proper type - AndroidLaunch androidLaunch = null; - if (launch instanceof AndroidLaunch) { - androidLaunch = (AndroidLaunch)launch; - } else { - // wrong type, not sure how we got there, but we don't do - // anything else - AdtPlugin.printErrorToConsole(project, "Wrong Launch Type!"); - return; - } - - // if we have a valid debug port, this means we're debugging an app - // that's already launched. - if (debugPort != INVALID_DEBUG_PORT) { - AndroidLaunchController.launchRemoteDebugger(debugPort, androidLaunch, monitor); - return; - } - - if (project == null) { - AdtPlugin.printErrorToConsole("Couldn't get project object!"); - androidLaunch.stopLaunch(); - return; - } - - // check if the project has errors, and abort in this case. - if (ProjectHelper.hasError(project, true)) { - AdtPlugin.displayError("Android Launch", - "Your project contains error(s), please fix them before running your application."); - return; - } - - AdtPlugin.printToConsole(project, "------------------------------"); //$NON-NLS-1$ - AdtPlugin.printToConsole(project, "Android Launch!"); - - // check if the project is using the proper sdk. - // if that throws an exception, we simply let it propage to the caller. - if (checkAndroidProject(project) == false) { - AdtPlugin.printErrorToConsole(project, "Project is not an Android Project. Aborting!"); - androidLaunch.stopLaunch(); - return; - } - - // Check adb status and abort if needed. - AndroidDebugBridge bridge = AndroidDebugBridge.getBridge(); - if (bridge == null || bridge.isConnected() == false) { - try { - int connections = -1; - int restarts = -1; - if (bridge != null) { - connections = bridge.getConnectionAttemptCount(); - restarts = bridge.getRestartAttemptCount(); - } - - // if we get -1, the device monitor is not even setup (anymore?). - // We need to ask the user to restart eclipse. - // This shouldn't happen, but it's better to let the user know in case it does. - if (connections == -1 || restarts == -1) { - AdtPlugin.printErrorToConsole(project, - "The connection to adb is down, and a severe error has occured.", - "You must restart adb and Eclipse.", - String.format( - "Please ensure that adb is correctly located at '%1$s' and can be executed.", - AdtPlugin.getOsAbsoluteAdb())); - return; - } - - if (restarts == 0) { - AdtPlugin.printErrorToConsole(project, - "Connection with adb was interrupted.", - String.format("%1$s attempts have been made to reconnect.", connections), - "You may want to manually restart adb from the Devices view."); - } else { - AdtPlugin.printErrorToConsole(project, - "Connection with adb was interrupted, and attempts to reconnect have failed.", - String.format("%1$s attempts have been made to restart adb.", restarts), - "You may want to manually restart adb from the Devices view."); - - } - return; - } finally { - androidLaunch.stopLaunch(); - } - } - - // since adb is working, we let the user know - // TODO have a verbose mode for launch with more info (or some of the less useful info we now have). - AdtPlugin.printToConsole(project, "adb is running normally."); - - // make a config class - AndroidLaunchConfiguration config = new AndroidLaunchConfiguration(); - - // fill it with the config coming from the ILaunchConfiguration object - config.set(configuration); - - // get the launch controller singleton - AndroidLaunchController controller = AndroidLaunchController.getInstance(); - - // get the application package - IFile applicationPackage = getApplicationPackage(project); - if (applicationPackage == null) { - androidLaunch.stopLaunch(); - return; - } - - // we need some information from the manifest - AndroidManifestParser manifestParser = AndroidManifestParser.parse( - BaseProjectHelper.getJavaProject(project), null /* errorListener */, - true /* gatherData */, false /* markErrors */); - - if (manifestParser == null) { - AdtPlugin.printErrorToConsole(project, "Failed to parse AndroidManifest: aborting!"); - androidLaunch.stopLaunch(); - return; - } - - String activityName = null; - - if (config.mLaunchAction == ACTION_ACTIVITY) { - // Get the activity name defined in the config - activityName = getActivityName(configuration); - - // Get the full activity list and make sure the one we got matches. - String[] activities = manifestParser.getActivities(); - - // first we check that there are, in fact, activities. - if (activities.length == 0) { - // if the activities list is null, then the manifest is empty - // and we can't launch the app. We'll revert to a sync-only launch - AdtPlugin.printErrorToConsole(project, - "The Manifest defines no activity!", - "The launch will only sync the application package on the device!"); - config.mLaunchAction = ACTION_DO_NOTHING; - } else if (activityName == null) { - // if the activity we got is null, we look for the default one. - AdtPlugin.printErrorToConsole(project, - "No activity specified! Getting the launcher activity."); - activityName = manifestParser.getLauncherActivity(); - - // if there's no default activity. We revert to a sync-only launch. - if (activityName == null) { - revertToNoActionLaunch(project, config); - } - } else { - - // check the one we got from the config matches any from the list - boolean match = false; - for (String a : activities) { - if (a != null && a.equals(activityName)) { - match = true; - break; - } - } - - // if we didn't find a match, we revert to the default activity if any. - if (match == false) { - AdtPlugin.printErrorToConsole(project, - "The specified activity does not exist! Getting the launcher activity."); - activityName = manifestParser.getLauncherActivity(); - - // if there's no default activity. We revert to a sync-only launch. - if (activityName == null) { - revertToNoActionLaunch(project, config); - } - } - } - } else if (config.mLaunchAction == ACTION_DEFAULT) { - activityName = manifestParser.getLauncherActivity(); - - // if there's no default activity. We revert to a sync-only launch. - if (activityName == null) { - revertToNoActionLaunch(project, config); - } - } - - // everything seems fine, we ask the launch controller to handle - // the rest - controller.launch(project, mode, applicationPackage, manifestParser.getPackage(), - manifestParser.getDebuggable(), manifestParser.getApiLevelRequirement(), - activityName, config, androidLaunch, monitor); - } - - @Override - public boolean buildForLaunch(ILaunchConfiguration configuration, - String mode, IProgressMonitor monitor) throws CoreException { - - // need to check we have everything - IProject project = getProject(configuration); - - if (project != null) { - // force an incremental build to be sure the resources will - // be updated if they were not saved before the launch was launched. - return true; - } - - throw new CoreException(new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID, - 1 /* code, unused */, "Can't find the project!", null /* exception */)); - } - - /** - * {@inheritDoc} - * @throws CoreException - */ - @Override - public ILaunch getLaunch(ILaunchConfiguration configuration, String mode) - throws CoreException { - return new AndroidLaunch(configuration, mode, null); - } - - /** - * Returns the IProject object matching the name found in the configuration - * object under the name - * IJavaLaunchConfigurationConstants.ATTR_PROJECT_NAME - * @param configuration - * @return The IProject object or null - */ - private IProject getProject(ILaunchConfiguration configuration){ - // get the project name from the config - String projectName; - try { - projectName = configuration.getAttribute( - IJavaLaunchConfigurationConstants.ATTR_PROJECT_NAME, ""); - } catch (CoreException e) { - return null; - } - - // get the current workspace - IWorkspace workspace = ResourcesPlugin.getWorkspace(); - - // and return the project with the name from the config - return workspace.getRoot().getProject(projectName); - } - - /** - * Checks the project is an android project. - * @param project The project to check - * @return true if the project is an android SDK. - * @throws CoreException - */ - private boolean checkAndroidProject(IProject project) throws CoreException { - // check if the project is a java and an android project. - if (project.hasNature(JavaCore.NATURE_ID) == false) { - String msg = String.format("%1$s is not a Java project!", project.getName()); - AdtPlugin.displayError("Android Launch", msg); - return false; - } - - if (project.hasNature(AndroidConstants.NATURE) == false) { - String msg = String.format("%1$s is not an Android project!", project.getName()); - AdtPlugin.displayError("Android Launch", msg); - return false; - } - - return true; - } - - - /** - * Returns the android package file as an IFile object for the specified - * project. - * @param project The project - * @return The android package as an IFile object or null if not found. - */ - private IFile getApplicationPackage(IProject project) { - // get the output folder - IFolder outputLocation = BaseProjectHelper.getOutputFolder(project); - - if (outputLocation == null) { - AdtPlugin.printErrorToConsole(project, - "Failed to get the output location of the project. Check build path properties" - ); - return null; - } - - - // get the package path - String packageName = project.getName() + AndroidConstants.DOT_ANDROID_PACKAGE; - IResource r = outputLocation.findMember(packageName); - - // check the package is present - if (r instanceof IFile && r.exists()) { - return (IFile)r; - } - - String msg = String.format("Could not find %1$s!", packageName); - AdtPlugin.printErrorToConsole(project, msg); - - return null; - } - - /** - * Returns the name of the activity. - */ - private String getActivityName(ILaunchConfiguration configuration) { - String empty = ""; - String activityName; - try { - activityName = configuration.getAttribute(ATTR_ACTIVITY, empty); - } catch (CoreException e) { - return null; - } - - return (activityName != empty) ? activityName : null; - } - - private final void revertToNoActionLaunch(IProject project, AndroidLaunchConfiguration config) { - AdtPlugin.printErrorToConsole(project, - "No Launcher activity found!", - "The launch will only sync the application package on the device!"); - config.mLaunchAction = ACTION_DO_NOTHING; - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/debug/launching/LaunchShortcut.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/debug/launching/LaunchShortcut.java deleted file mode 100644 index 92677f1..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/debug/launching/LaunchShortcut.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright (C) 2007 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.debug.launching; - -import org.eclipse.core.resources.IProject; -import org.eclipse.core.resources.IResource; -import org.eclipse.core.runtime.IAdaptable; -import org.eclipse.debug.core.ILaunchConfiguration; -import org.eclipse.debug.ui.DebugUITools; -import org.eclipse.debug.ui.ILaunchShortcut; -import org.eclipse.jface.viewers.ISelection; -import org.eclipse.jface.viewers.IStructuredSelection; -import org.eclipse.ui.IEditorPart; - -/** - * Launch shortcut to launch debug/run configuration directly. - */ -public class LaunchShortcut implements ILaunchShortcut { - - - /* (non-Javadoc) - * @see org.eclipse.debug.ui.ILaunchShortcut#launch( - * org.eclipse.jface.viewers.ISelection, java.lang.String) - */ - public void launch(ISelection selection, String mode) { - if (selection instanceof IStructuredSelection) { - - // get the object and the project from it - IStructuredSelection structSelect = (IStructuredSelection)selection; - Object o = structSelect.getFirstElement(); - - // get the first (and normally only) element - if (o instanceof IAdaptable) { - IResource r = (IResource)((IAdaptable)o).getAdapter(IResource.class); - - // get the project from the resource - if (r != null) { - IProject project = r.getProject(); - - if (project != null) { - // and launch - launch(project, mode); - } - } - } - } - } - - /* (non-Javadoc) - * @see org.eclipse.debug.ui.ILaunchShortcut#launch( - * org.eclipse.ui.IEditorPart, java.lang.String) - */ - public void launch(IEditorPart editor, String mode) { - // since we force the shortcut to only work on selection in the - // package explorer, this will never be called. - } - - - /** - * Launch a config for the specified project. - * @param project The project to launch - * @param mode The launch mode ("debug", "run" or "profile") - */ - private void launch(IProject project, String mode) { - // get an existing or new launch configuration - ILaunchConfiguration config = AndroidLaunchController.getLaunchConfig(project); - - if (config != null) { - // and launch! - DebugUITools.launch(config, mode); - } - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/debug/ui/EmulatorConfigTab.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/debug/ui/EmulatorConfigTab.java deleted file mode 100644 index d919c1f..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/debug/ui/EmulatorConfigTab.java +++ /dev/null @@ -1,459 +0,0 @@ -/* - * Copyright (C) 2007 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.debug.ui; - -import com.android.ide.eclipse.adt.AdtPlugin; -import com.android.ide.eclipse.adt.debug.launching.LaunchConfigDelegate; -import com.android.ide.eclipse.adt.sdk.Sdk; -import com.android.ide.eclipse.common.project.BaseProjectHelper; -import com.android.ide.eclipse.ddms.DdmsPlugin; -import com.android.sdklib.IAndroidTarget; -import com.android.sdklib.avd.AvdManager; -import com.android.sdklib.avd.AvdManager.AvdInfo; -import com.android.sdkuilib.AvdSelector; - -import org.eclipse.core.resources.IProject; -import org.eclipse.core.runtime.CoreException; -import org.eclipse.debug.core.ILaunchConfiguration; -import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy; -import org.eclipse.debug.ui.AbstractLaunchConfigurationTab; -import org.eclipse.jdt.core.IJavaProject; -import org.eclipse.jdt.launching.IJavaLaunchConfigurationConstants; -import org.eclipse.jface.preference.IPreferenceStore; -import org.eclipse.swt.SWT; -import org.eclipse.swt.events.ModifyEvent; -import org.eclipse.swt.events.ModifyListener; -import org.eclipse.swt.events.SelectionAdapter; -import org.eclipse.swt.events.SelectionEvent; -import org.eclipse.swt.graphics.Font; -import org.eclipse.swt.graphics.Image; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.layout.GridLayout; -import org.eclipse.swt.widgets.Button; -import org.eclipse.swt.widgets.Combo; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Group; -import org.eclipse.swt.widgets.Label; -import org.eclipse.swt.widgets.Text; - -/** - * Launch configuration tab to control the parameters of the Emulator - */ -public class EmulatorConfigTab extends AbstractLaunchConfigurationTab { - - private final static String[][] NETWORK_SPEEDS = new String[][] { - { "Full", "full" }, //$NON-NLS-2$ - { "GSM", "gsm" }, //$NON-NLS-2$ - { "HSCSD", "hscsd" }, //$NON-NLS-2$ - { "GPRS", "gprs" }, //$NON-NLS-2$ - { "EDGE", "edge" }, //$NON-NLS-2$ - { "UMTS", "umts" }, //$NON-NLS-2$ - { "HSPDA", "hsdpa" }, //$NON-NLS-2$ - }; - - private final static String[][] NETWORK_LATENCIES = new String[][] { - { "None", "none" }, //$NON-NLS-2$ - { "GPRS", "gprs" }, //$NON-NLS-2$ - { "EDGE", "edge" }, //$NON-NLS-2$ - { "UMTS", "umts" }, //$NON-NLS-2$ - }; - - private Button mAutoTargetButton; - private Button mManualTargetButton; - - private AvdSelector mPreferredAvdSelector; - - private Combo mSpeedCombo; - - private Combo mDelayCombo; - - private Group mEmulatorOptionsGroup; - - private Text mEmulatorCLOptions; - - private Button mWipeDataButton; - - private Button mNoBootAnimButton; - - private Label mPreferredAvdLabel; - - /** - * Returns the emulator ready speed option value. - * @param value The index of the combo selection. - */ - public static String getSpeed(int value) { - try { - return NETWORK_SPEEDS[value][1]; - } catch (ArrayIndexOutOfBoundsException e) { - return NETWORK_SPEEDS[LaunchConfigDelegate.DEFAULT_SPEED][1]; - } - } - - /** - * Returns the emulator ready network latency value. - * @param value The index of the combo selection. - */ - public static String getDelay(int value) { - try { - return NETWORK_LATENCIES[value][1]; - } catch (ArrayIndexOutOfBoundsException e) { - return NETWORK_LATENCIES[LaunchConfigDelegate.DEFAULT_DELAY][1]; - } - } - - /** - * - */ - public EmulatorConfigTab() { - } - - /* (non-Javadoc) - * @see org.eclipse.debug.ui.ILaunchConfigurationTab#createControl(org.eclipse.swt.widgets.Composite) - */ - public void createControl(Composite parent) { - Font font = parent.getFont(); - - Composite topComp = new Composite(parent, SWT.NONE); - setControl(topComp); - GridLayout topLayout = new GridLayout(); - topLayout.numColumns = 1; - topLayout.verticalSpacing = 0; - topComp.setLayout(topLayout); - topComp.setFont(font); - - GridData gd; - GridLayout layout; - - // radio button for the target mode - Group targetModeGroup = new Group(topComp, SWT.NONE); - targetModeGroup.setText("Device Target Selection Mode"); - gd = new GridData(GridData.FILL_HORIZONTAL); - targetModeGroup.setLayoutData(gd); - layout = new GridLayout(); - layout.numColumns = 1; - targetModeGroup.setLayout(layout); - targetModeGroup.setFont(font); - - mManualTargetButton = new Button(targetModeGroup, SWT.RADIO); - mManualTargetButton.setText("Manual"); - // Since there are only 2 radio buttons, we can put a listener on only one (they - // are both called on select and unselect event. - - // add the radio button - mAutoTargetButton = new Button(targetModeGroup, SWT.RADIO); - mAutoTargetButton.setText("Automatic"); - mAutoTargetButton.setSelection(true); - mAutoTargetButton.addSelectionListener(new SelectionAdapter() { - // called when selection changes - @Override - public void widgetSelected(SelectionEvent e) { - updateLaunchConfigurationDialog(); - - boolean auto = mAutoTargetButton.getSelection(); - mPreferredAvdSelector.setEnabled(auto); - mPreferredAvdLabel.setEnabled(auto); - } - }); - - Composite offsetComp = new Composite(targetModeGroup, SWT.NONE); - offsetComp.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); - layout = new GridLayout(1, false); - layout.marginRight = layout.marginHeight = 0; - layout.marginLeft = 30; - offsetComp.setLayout(layout); - - mPreferredAvdLabel = new Label(offsetComp, SWT.NONE); - mPreferredAvdLabel.setText("Select a preferred Android Virtual Device:"); - AvdInfo[] avds = new AvdInfo[0]; - mPreferredAvdSelector = new AvdSelector(offsetComp, avds, - false /*allowMultipleSelection*/); - mPreferredAvdSelector.setTableHeightHint(100); - mPreferredAvdSelector.setSelectionListener(new SelectionAdapter() { - @Override - public void widgetSelected(SelectionEvent e) { - updateLaunchConfigurationDialog(); - } - }); - - // emulator size - mEmulatorOptionsGroup = new Group(topComp, SWT.NONE); - mEmulatorOptionsGroup.setText("Emulator launch parameters:"); - mEmulatorOptionsGroup.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); - layout = new GridLayout(); - layout.numColumns = 2; - mEmulatorOptionsGroup.setLayout(layout); - mEmulatorOptionsGroup.setFont(font); - - // network options - new Label(mEmulatorOptionsGroup, SWT.NONE).setText("Network Speed:"); - - mSpeedCombo = new Combo(mEmulatorOptionsGroup, SWT.READ_ONLY); - for (String[] speed : NETWORK_SPEEDS) { - mSpeedCombo.add(speed[0]); - } - mSpeedCombo.addSelectionListener(new SelectionAdapter() { - // called when selection changes - @Override - public void widgetSelected(SelectionEvent e) { - updateLaunchConfigurationDialog(); - } - }); - mSpeedCombo.pack(); - - new Label(mEmulatorOptionsGroup, SWT.NONE).setText("Network Latency:"); - - mDelayCombo = new Combo(mEmulatorOptionsGroup, SWT.READ_ONLY); - - for (String[] delay : NETWORK_LATENCIES) { - mDelayCombo.add(delay[0]); - } - mDelayCombo.addSelectionListener(new SelectionAdapter() { - // called when selection changes - @Override - public void widgetSelected(SelectionEvent e) { - updateLaunchConfigurationDialog(); - } - }); - mDelayCombo.pack(); - - // wipe data option - mWipeDataButton = new Button(mEmulatorOptionsGroup, SWT.CHECK); - mWipeDataButton.setText("Wipe User Data"); - mWipeDataButton.setToolTipText("Check this if you want to wipe your user data each time you start the emulator. You will be prompted for confirmation when the emulator starts."); - gd = new GridData(GridData.FILL_HORIZONTAL); - gd.horizontalSpan = 2; - mWipeDataButton.setLayoutData(gd); - mWipeDataButton.addSelectionListener(new SelectionAdapter() { - @Override - public void widgetSelected(SelectionEvent e) { - updateLaunchConfigurationDialog(); - } - }); - - // no boot anim option - mNoBootAnimButton = new Button(mEmulatorOptionsGroup, SWT.CHECK); - mNoBootAnimButton.setText("Disable Boot Animation"); - mNoBootAnimButton.setToolTipText("Check this if you want to disable the boot animation. This can help the emulator start faster on slow machines."); - gd = new GridData(GridData.FILL_HORIZONTAL); - gd.horizontalSpan = 2; - mNoBootAnimButton.setLayoutData(gd); - mNoBootAnimButton.addSelectionListener(new SelectionAdapter() { - @Override - public void widgetSelected(SelectionEvent e) { - updateLaunchConfigurationDialog(); - } - }); - - // custom command line option for emulator - Label l = new Label(mEmulatorOptionsGroup, SWT.NONE); - l.setText("Additional Emulator Command Line Options"); - gd = new GridData(GridData.FILL_HORIZONTAL); - gd.horizontalSpan = 2; - l.setLayoutData(gd); - - mEmulatorCLOptions = new Text(mEmulatorOptionsGroup, SWT.BORDER); - gd = new GridData(GridData.FILL_HORIZONTAL); - gd.horizontalSpan = 2; - mEmulatorCLOptions.setLayoutData(gd); - mEmulatorCLOptions.addModifyListener(new ModifyListener() { - public void modifyText(ModifyEvent e) { - updateLaunchConfigurationDialog(); - } - }); - } - - /* (non-Javadoc) - * @see org.eclipse.debug.ui.ILaunchConfigurationTab#getName() - */ - public String getName() { - return "Target"; - } - - @Override - public Image getImage() { - return DdmsPlugin.getImageLoader().loadImage("emulator.png", null); //$NON-NLS-1$ - } - - /* (non-Javadoc) - * @see org.eclipse.debug.ui.ILaunchConfigurationTab#initializeFrom(org.eclipse.debug.core.ILaunchConfiguration) - */ - public void initializeFrom(ILaunchConfiguration configuration) { - AvdManager avdManager = Sdk.getCurrent().getAvdManager(); - - boolean value = LaunchConfigDelegate.DEFAULT_TARGET_MODE; // true == automatic - try { - value = configuration.getAttribute(LaunchConfigDelegate.ATTR_TARGET_MODE, value); - } catch (CoreException e) { - // let's not do anything here, we'll use the default value - } - mAutoTargetButton.setSelection(value); - mManualTargetButton.setSelection(!value); - - // look for the project name to get its target. - String stringValue = ""; - try { - stringValue = configuration.getAttribute( - IJavaLaunchConfigurationConstants.ATTR_PROJECT_NAME, stringValue); - } catch (CoreException ce) { - // let's not do anything here, we'll use the default value - } - - IProject project = null; - - // get the list of existing Android projects from the workspace. - IJavaProject[] projects = BaseProjectHelper.getAndroidProjects(); - if (projects != null) { - // look for the project whose name we read from the configuration. - for (IJavaProject p : projects) { - if (p.getElementName().equals(stringValue)) { - project = p.getProject(); - break; - } - } - } - - // update the AVD list - AvdInfo[] avds = null; - if (avdManager != null) { - avds = avdManager.getAvds(); - } - - IAndroidTarget projectTarget = null; - if (project != null) { - projectTarget = Sdk.getCurrent().getTarget(project); - } else { - avds = null; // no project? we don't want to display any "compatible" AVDs. - } - - mPreferredAvdSelector.setAvds(avds, projectTarget); - - stringValue = ""; - try { - stringValue = configuration.getAttribute(LaunchConfigDelegate.ATTR_AVD_NAME, - stringValue); - } catch (CoreException e) { - // let's not do anything here, we'll use the default value - } - - if (stringValue != null && stringValue.length() > 0 && avdManager != null) { - AvdInfo targetAvd = avdManager.getAvd(stringValue); - mPreferredAvdSelector.setSelection(targetAvd); - } else { - mPreferredAvdSelector.setSelection(null); - } - - value = LaunchConfigDelegate.DEFAULT_WIPE_DATA; - try { - value = configuration.getAttribute(LaunchConfigDelegate.ATTR_WIPE_DATA, value); - } catch (CoreException e) { - // let's not do anything here, we'll use the default value - } - mWipeDataButton.setSelection(value); - - value = LaunchConfigDelegate.DEFAULT_NO_BOOT_ANIM; - try { - value = configuration.getAttribute(LaunchConfigDelegate.ATTR_NO_BOOT_ANIM, value); - } catch (CoreException e) { - // let's not do anything here, we'll use the default value - } - mNoBootAnimButton.setSelection(value); - - int index = -1; - - index = LaunchConfigDelegate.DEFAULT_SPEED; - try { - index = configuration.getAttribute(LaunchConfigDelegate.ATTR_SPEED, - index); - } catch (CoreException e) { - // let's not do anything here, we'll use the default value - } - if (index == -1) { - mSpeedCombo.clearSelection(); - } else { - mSpeedCombo.select(index); - } - - index = LaunchConfigDelegate.DEFAULT_DELAY; - try { - index = configuration.getAttribute(LaunchConfigDelegate.ATTR_DELAY, - index); - } catch (CoreException e) { - // let's not do anything here, we'll put a proper value in - // performApply anyway - } - if (index == -1) { - mDelayCombo.clearSelection(); - } else { - mDelayCombo.select(index); - } - - String commandLine = null; - try { - commandLine = configuration.getAttribute( - LaunchConfigDelegate.ATTR_COMMANDLINE, ""); //$NON-NLS-1$ - } catch (CoreException e) { - // let's not do anything here, we'll use the default value - } - if (commandLine != null) { - mEmulatorCLOptions.setText(commandLine); - } - } - - /* (non-Javadoc) - * @see org.eclipse.debug.ui.ILaunchConfigurationTab#performApply(org.eclipse.debug.core.ILaunchConfigurationWorkingCopy) - */ - public void performApply(ILaunchConfigurationWorkingCopy configuration) { - configuration.setAttribute(LaunchConfigDelegate.ATTR_TARGET_MODE, - mAutoTargetButton.getSelection()); - AvdInfo avd = mPreferredAvdSelector.getFirstSelected(); - if (avd != null) { - configuration.setAttribute(LaunchConfigDelegate.ATTR_AVD_NAME, avd.getName()); - } else { - configuration.setAttribute(LaunchConfigDelegate.ATTR_AVD_NAME, (String)null); - } - configuration.setAttribute(LaunchConfigDelegate.ATTR_SPEED, - mSpeedCombo.getSelectionIndex()); - configuration.setAttribute(LaunchConfigDelegate.ATTR_DELAY, - mDelayCombo.getSelectionIndex()); - configuration.setAttribute(LaunchConfigDelegate.ATTR_COMMANDLINE, - mEmulatorCLOptions.getText()); - configuration.setAttribute(LaunchConfigDelegate.ATTR_WIPE_DATA, - mWipeDataButton.getSelection()); - configuration.setAttribute(LaunchConfigDelegate.ATTR_NO_BOOT_ANIM, - mNoBootAnimButton.getSelection()); - } - - /* (non-Javadoc) - * @see org.eclipse.debug.ui.ILaunchConfigurationTab#setDefaults(org.eclipse.debug.core.ILaunchConfigurationWorkingCopy) - */ - public void setDefaults(ILaunchConfigurationWorkingCopy configuration) { - configuration.setAttribute(LaunchConfigDelegate.ATTR_TARGET_MODE, - LaunchConfigDelegate.DEFAULT_TARGET_MODE); - configuration.setAttribute(LaunchConfigDelegate.ATTR_SPEED, - LaunchConfigDelegate.DEFAULT_SPEED); - configuration.setAttribute(LaunchConfigDelegate.ATTR_DELAY, - LaunchConfigDelegate.DEFAULT_DELAY); - configuration.setAttribute(LaunchConfigDelegate.ATTR_WIPE_DATA, - LaunchConfigDelegate.DEFAULT_WIPE_DATA); - configuration.setAttribute(LaunchConfigDelegate.ATTR_NO_BOOT_ANIM, - LaunchConfigDelegate.DEFAULT_NO_BOOT_ANIM); - - IPreferenceStore store = AdtPlugin.getDefault().getPreferenceStore(); - String emuOptions = store.getString(AdtPlugin.PREFS_EMU_OPTIONS); - configuration.setAttribute(LaunchConfigDelegate.ATTR_COMMANDLINE, emuOptions); - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/debug/ui/LaunchConfigTabGroup.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/debug/ui/LaunchConfigTabGroup.java deleted file mode 100644 index c0dbd54..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/debug/ui/LaunchConfigTabGroup.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (C) 2007 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.debug.ui; - -import org.eclipse.debug.ui.AbstractLaunchConfigurationTabGroup; -import org.eclipse.debug.ui.CommonTab; -import org.eclipse.debug.ui.ILaunchConfigurationDialog; -import org.eclipse.debug.ui.ILaunchConfigurationTab; - -/** - * Tab group object for Android Launch Config type. - */ -public class LaunchConfigTabGroup extends AbstractLaunchConfigurationTabGroup { - - public LaunchConfigTabGroup() { - } - - public void createTabs(ILaunchConfigurationDialog dialog, String mode) { - ILaunchConfigurationTab[] tabs = new ILaunchConfigurationTab[] { - new MainLaunchConfigTab(), - new EmulatorConfigTab(), - new CommonTab() - }; - setTabs(tabs); - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/debug/ui/MainLaunchConfigTab.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/debug/ui/MainLaunchConfigTab.java deleted file mode 100644 index 6a40ed0..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/debug/ui/MainLaunchConfigTab.java +++ /dev/null @@ -1,489 +0,0 @@ -/* - * Copyright (C) 2007 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.debug.ui; - -import com.android.ide.eclipse.adt.AdtPlugin; -import com.android.ide.eclipse.adt.debug.launching.AndroidLaunchController; -import com.android.ide.eclipse.adt.debug.launching.LaunchConfigDelegate; -import com.android.ide.eclipse.common.project.AndroidManifestParser; -import com.android.ide.eclipse.common.project.BaseProjectHelper; -import com.android.ide.eclipse.common.project.ProjectChooserHelper; - -import org.eclipse.core.resources.IProject; -import org.eclipse.core.resources.IResource; -import org.eclipse.core.resources.IWorkspaceRoot; -import org.eclipse.core.resources.ResourcesPlugin; -import org.eclipse.core.runtime.CoreException; -import org.eclipse.debug.core.ILaunchConfiguration; -import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy; -import org.eclipse.debug.ui.AbstractLaunchConfigurationTab; -import org.eclipse.debug.ui.ILaunchConfigurationTab; -import org.eclipse.jdt.core.IJavaModel; -import org.eclipse.jdt.core.IJavaProject; -import org.eclipse.jdt.core.JavaCore; -import org.eclipse.jdt.launching.IJavaLaunchConfigurationConstants; -import org.eclipse.swt.SWT; -import org.eclipse.swt.events.ModifyEvent; -import org.eclipse.swt.events.ModifyListener; -import org.eclipse.swt.events.SelectionAdapter; -import org.eclipse.swt.events.SelectionEvent; -import org.eclipse.swt.events.SelectionListener; -import org.eclipse.swt.graphics.Font; -import org.eclipse.swt.graphics.Image; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.layout.GridLayout; -import org.eclipse.swt.widgets.Button; -import org.eclipse.swt.widgets.Combo; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Group; -import org.eclipse.swt.widgets.Text; - -/** - * Class for the main launch configuration tab. - */ -public class MainLaunchConfigTab extends AbstractLaunchConfigurationTab { - - protected static final String EMPTY_STRING = ""; //$NON-NLS-1$ - - protected Text mProjText; - private Button mProjButton; - - private Combo mActivityCombo; - private String[] mActivities; - - private WidgetListener mListener = new WidgetListener(); - - private Button mDefaultActionButton; - private Button mActivityActionButton; - private Button mDoNothingActionButton; - private int mLaunchAction = LaunchConfigDelegate.DEFAULT_LAUNCH_ACTION; - - private ProjectChooserHelper mProjectChooserHelper; - - /** - * A listener which handles widget change events for the controls in this - * tab. - */ - private class WidgetListener implements ModifyListener, SelectionListener { - - public void modifyText(ModifyEvent e) { - IProject project = checkParameters(); - loadActivities(project); - setDirty(true); - } - - public void widgetDefaultSelected(SelectionEvent e) {/* do nothing */ - } - - public void widgetSelected(SelectionEvent e) { - Object source = e.getSource(); - if (source == mProjButton) { - handleProjectButtonSelected(); - } else { - checkParameters(); - } - } - } - - public MainLaunchConfigTab() { - } - - public void createControl(Composite parent) { - mProjectChooserHelper = new ProjectChooserHelper(parent.getShell()); - - Font font = parent.getFont(); - Composite comp = new Composite(parent, SWT.NONE); - setControl(comp); - GridLayout topLayout = new GridLayout(); - topLayout.verticalSpacing = 0; - comp.setLayout(topLayout); - comp.setFont(font); - createProjectEditor(comp); - createVerticalSpacer(comp, 1); - - // create the combo for the activity chooser - Group group = new Group(comp, SWT.NONE); - group.setText("Launch Action:"); - GridData gd = new GridData(GridData.FILL_HORIZONTAL); - group.setLayoutData(gd); - GridLayout layout = new GridLayout(); - layout.numColumns = 2; - group.setLayout(layout); - group.setFont(font); - - mDefaultActionButton = new Button(group, SWT.RADIO); - gd = new GridData(GridData.FILL_HORIZONTAL); - gd.horizontalSpan = 2; - mDefaultActionButton.setLayoutData(gd); - mDefaultActionButton.setText("Launch Default Activity"); - mDefaultActionButton.addSelectionListener(new SelectionAdapter() { - @Override - public void widgetSelected(SelectionEvent e) { - // event are received for both selection and deselection, so we only process - // the selection event to avoid doing it twice. - if (mDefaultActionButton.getSelection() == true) { - mLaunchAction = LaunchConfigDelegate.ACTION_DEFAULT; - mActivityCombo.setEnabled(false); - checkParameters(); - } - } - }); - - mActivityActionButton = new Button(group, SWT.RADIO); - mActivityActionButton.setText("Launch:"); - mActivityActionButton.addSelectionListener(new SelectionAdapter() { - @Override - public void widgetSelected(SelectionEvent e) { - // event are received for both selection and deselection, so we only process - // the selection event to avoid doing it twice. - if (mActivityActionButton.getSelection() == true) { - mLaunchAction = LaunchConfigDelegate.ACTION_ACTIVITY; - mActivityCombo.setEnabled(true); - checkParameters(); - } - } - }); - - mActivityCombo = new Combo(group, SWT.DROP_DOWN | SWT.READ_ONLY); - gd = new GridData(GridData.FILL_HORIZONTAL); - mActivityCombo.setLayoutData(gd); - mActivityCombo.clearSelection(); - mActivityCombo.setEnabled(false); - mActivityCombo.addSelectionListener(new SelectionAdapter() { - @Override - public void widgetSelected(SelectionEvent e) { - checkParameters(); - } - }); - - mDoNothingActionButton = new Button(group, SWT.RADIO); - gd = new GridData(GridData.FILL_HORIZONTAL); - gd.horizontalSpan = 2; - mDoNothingActionButton.setLayoutData(gd); - mDoNothingActionButton.setText("Do Nothing"); - mDoNothingActionButton.addSelectionListener(new SelectionAdapter() { - @Override - public void widgetSelected(SelectionEvent e) { - // event are received for both selection and deselection, so we only process - // the selection event to avoid doing it twice. - if (mDoNothingActionButton.getSelection() == true) { - mLaunchAction = LaunchConfigDelegate.ACTION_DO_NOTHING; - mActivityCombo.setEnabled(false); - checkParameters(); - } - } - }); - - } - - public String getName() { - return "Android"; - } - - @Override - public Image getImage() { - return AdtPlugin.getImageLoader().loadImage("mainLaunchTab.png", null); - } - - - public void performApply(ILaunchConfigurationWorkingCopy configuration) { - configuration.setAttribute( - IJavaLaunchConfigurationConstants.ATTR_PROJECT_NAME, mProjText.getText()); - configuration.setAttribute( - IJavaLaunchConfigurationConstants.ATTR_ALLOW_TERMINATE, true); - - // add the launch mode - configuration.setAttribute(LaunchConfigDelegate.ATTR_LAUNCH_ACTION, mLaunchAction); - - // add the activity - int selection = mActivityCombo.getSelectionIndex(); - if (mActivities != null && selection >=0 && selection < mActivities.length) { - configuration.setAttribute(LaunchConfigDelegate.ATTR_ACTIVITY, mActivities[selection]); - } - - // link the project and the launch config. - mapResources(configuration); - } - - public void setDefaults(ILaunchConfigurationWorkingCopy configuration) { - configuration.setAttribute(LaunchConfigDelegate.ATTR_LAUNCH_ACTION, - LaunchConfigDelegate.DEFAULT_LAUNCH_ACTION); - } - - /** - * Creates the widgets for specifying a main type. - * - * @param parent the parent composite - */ - protected void createProjectEditor(Composite parent) { - Font font = parent.getFont(); - Group group = new Group(parent, SWT.NONE); - group.setText("Project:"); - GridData gd = new GridData(GridData.FILL_HORIZONTAL); - group.setLayoutData(gd); - GridLayout layout = new GridLayout(); - layout.numColumns = 2; - group.setLayout(layout); - group.setFont(font); - mProjText = new Text(group, SWT.SINGLE | SWT.BORDER); - gd = new GridData(GridData.FILL_HORIZONTAL); - mProjText.setLayoutData(gd); - mProjText.setFont(font); - mProjText.addModifyListener(mListener); - mProjButton = createPushButton(group, "Browse...", null); - mProjButton.addSelectionListener(mListener); - } - - /** - * returns the default listener from this class. For all subclasses this - * listener will only provide the functi Jaonality of updating the current - * tab - * - * @return a widget listener - */ - protected WidgetListener getDefaultListener() { - return mListener; - } - - /** - * Return the {@link IJavaProject} corresponding to the project name in the project - * name text field, or null if the text does not match a project name. - * @param javaModel the Java Model object corresponding for the current workspace root. - * @return a IJavaProject object or null. - */ - protected IJavaProject getJavaProject(IJavaModel javaModel) { - String projectName = mProjText.getText().trim(); - if (projectName.length() < 1) { - return null; - } - return javaModel.getJavaProject(projectName); - } - - /** - * Show a dialog that lets the user select a project. This in turn provides - * context for the main type, allowing the user to key a main type name, or - * constraining the search for main types to the specified project. - */ - protected void handleProjectButtonSelected() { - IJavaProject javaProject = mProjectChooserHelper.chooseJavaProject( - mProjText.getText().trim()); - if (javaProject == null) { - return; - }// end if - String projectName = javaProject.getElementName(); - mProjText.setText(projectName); - - // get the list of activities and fill the combo - IProject project = javaProject.getProject(); - loadActivities(project); - }// end handle selected - - /** - * Initializes this tab's controls with values from the given - * launch configuration. This method is called when - * a configuration is selected to view or edit, after this - * tab's control has been created. - * - * @param config launch configuration - * - * @see ILaunchConfigurationTab - */ - public void initializeFrom(ILaunchConfiguration config) { - String projectName = EMPTY_STRING; - try { - projectName = config.getAttribute(IJavaLaunchConfigurationConstants.ATTR_PROJECT_NAME, - EMPTY_STRING); - }// end try - catch (CoreException ce) { - } - mProjText.setText(projectName); - - // get the list of projects - IJavaProject[] projects = mProjectChooserHelper.getAndroidProjects(null); - - if (projects != null) { - // look for the currently selected project - IProject proj = null; - for (IJavaProject p : projects) { - if (p.getElementName().equals(projectName)) { - proj = p.getProject(); - break; - } - } - - loadActivities(proj); - } - - // load the launch action. - mLaunchAction = LaunchConfigDelegate.DEFAULT_LAUNCH_ACTION; - try { - mLaunchAction = config.getAttribute(LaunchConfigDelegate.ATTR_LAUNCH_ACTION, - mLaunchAction); - } catch (CoreException e) { - // nothing to be done really. launchAction will keep its default value. - } - - mDefaultActionButton.setSelection(mLaunchAction == LaunchConfigDelegate.ACTION_DEFAULT); - mActivityActionButton.setSelection(mLaunchAction == LaunchConfigDelegate.ACTION_ACTIVITY); - mDoNothingActionButton.setSelection( - mLaunchAction == LaunchConfigDelegate.ACTION_DO_NOTHING); - - // now look for the activity and load it if present, otherwise, revert - // to the current one. - String activityName = EMPTY_STRING; - try { - activityName = config.getAttribute(LaunchConfigDelegate.ATTR_ACTIVITY, EMPTY_STRING); - }// end try - catch (CoreException ce) { - // nothing to be done really. activityName will stay empty - } - - if (mLaunchAction != LaunchConfigDelegate.ACTION_ACTIVITY) { - mActivityCombo.setEnabled(false); - mActivityCombo.clearSelection(); - } else { - mActivityCombo.setEnabled(true); - if (activityName == null || activityName.equals(EMPTY_STRING)) { - mActivityCombo.clearSelection(); - } else if (mActivities != null && mActivities.length > 0) { - // look for the name of the activity in the combo. - boolean found = false; - for (int i = 0 ; i < mActivities.length ; i++) { - if (activityName.equals(mActivities[i])) { - found = true; - mActivityCombo.select(i); - break; - } - } - - // if we haven't found a matching activity we clear the combo selection - if (found == false) { - mActivityCombo.clearSelection(); - } - } - } - } - - /** - * Associates the launch config and the project. This allows Eclipse to delete the launch - * config when the project is deleted. - * - * @param config the launch config working copy. - */ - protected void mapResources(ILaunchConfigurationWorkingCopy config) { - // get the java model - IWorkspaceRoot workspaceRoot = ResourcesPlugin.getWorkspace().getRoot(); - IJavaModel javaModel = JavaCore.create(workspaceRoot); - - // get the IJavaProject described by the text field. - IJavaProject javaProject = getJavaProject(javaModel); - IResource[] resources = null; - if (javaProject != null) { - resources = AndroidLaunchController.getResourcesToMap(javaProject.getProject()); - } - config.setMappedResources(resources); - } - - /** - * Loads the ui with the activities of the specified project, and stores the - * activities in mActivities. - *

        - * First activity is selected by default if present. - * - * @param project The project to load the activities from. - */ - private void loadActivities(IProject project) { - if (project != null) { - try { - // parse the manifest for the list of activities. - AndroidManifestParser manifestParser = AndroidManifestParser.parse( - BaseProjectHelper.getJavaProject(project), null /* errorListener */, - true /* gatherData */, false /* markErrors */); - if (manifestParser != null) { - mActivities = manifestParser.getActivities(); - - mActivityCombo.removeAll(); - - if (mActivities.length > 0) { - if (mLaunchAction == LaunchConfigDelegate.ACTION_ACTIVITY) { - mActivityCombo.setEnabled(true); - } - for (String s : mActivities) { - mActivityCombo.add(s); - } - } else { - mActivityCombo.setEnabled(false); - } - - // the selection will be set when we update the ui from the current - // config object. - mActivityCombo.clearSelection(); - - return; - } - - } catch (CoreException e) { - // The AndroidManifest parsing failed. The builders must have reported the errors - // already so there's nothing to do. - } - } - - // if we reach this point, either project is null, or we got an exception during - // the parsing. In either case, we empty the activity list. - mActivityCombo.removeAll(); - mActivities = null; - } - - /** - * Checks the parameters for correctness, and update the error message and buttons. - * @return the current IProject of this launch config. - */ - private IProject checkParameters() { - try { - //test the project name first! - String text = mProjText.getText(); - if (text.length() == 0) { - setErrorMessage("Project Name is required!"); - } else if (text.matches("[a-zA-Z0-9_ \\.-]+") == false) { - setErrorMessage("Project name contains unsupported characters!"); - } else { - IJavaProject[] projects = mProjectChooserHelper.getAndroidProjects(null); - IProject found = null; - for (IJavaProject javaProject : projects) { - if (javaProject.getProject().getName().equals(text)) { - found = javaProject.getProject(); - break; - } - - } - - if (found != null) { - setErrorMessage(null); - } else { - setErrorMessage(String.format("There is no android project named '%1$s'", - text)); - } - - return found; - } - } finally { - updateLaunchConfigurationDialog(); - } - - return null; - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/AndroidLaunch.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/AndroidLaunch.java new file mode 100644 index 0000000..7029206 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/AndroidLaunch.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2007 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.launch; + +import org.eclipse.debug.core.DebugException; +import org.eclipse.debug.core.ILaunchConfiguration; +import org.eclipse.debug.core.ILaunchManager; +import org.eclipse.debug.core.Launch; +import org.eclipse.debug.core.model.ISourceLocator; + +/** + * Custom implementation of Launch to allow access to the LaunchManager + * + */ +class AndroidLaunch extends Launch { + + /** + * Basic constructor does nothing special + * @param launchConfiguration + * @param mode + * @param locator + */ + public AndroidLaunch(ILaunchConfiguration launchConfiguration, String mode, + ISourceLocator locator) { + super(launchConfiguration, mode, locator); + } + + /** Stops the launch, and removes it from the launch manager */ + public void stopLaunch() { + ILaunchManager mgr = getLaunchManager(); + + if (canTerminate()) { + try { + terminate(); + } catch (DebugException e) { + // well looks like we couldn't stop it. nothing else to be + // done really + } + } + // remove the launch + mgr.removeLaunch(this); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/AndroidLaunchController.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/AndroidLaunchController.java new file mode 100644 index 0000000..9c5f09b --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/AndroidLaunchController.java @@ -0,0 +1,1837 @@ +/* + * Copyright (C) 2007 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.launch; + +import com.android.ddmlib.AndroidDebugBridge; +import com.android.ddmlib.Client; +import com.android.ddmlib.ClientData; +import com.android.ddmlib.Device; +import com.android.ddmlib.Log; +import com.android.ddmlib.MultiLineReceiver; +import com.android.ddmlib.SyncService; +import com.android.ddmlib.AndroidDebugBridge.IClientChangeListener; +import com.android.ddmlib.AndroidDebugBridge.IDebugBridgeChangeListener; +import com.android.ddmlib.AndroidDebugBridge.IDeviceChangeListener; +import com.android.ddmlib.SyncService.SyncResult; +import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.ide.eclipse.adt.launch.DeviceChooserDialog.DeviceChooserResponse; +import com.android.ide.eclipse.adt.project.ProjectHelper; +import com.android.ide.eclipse.adt.sdk.Sdk; +import com.android.ide.eclipse.common.project.AndroidManifestHelper; +import com.android.sdklib.IAndroidTarget; +import com.android.sdklib.SdkManager; +import com.android.sdklib.avd.AvdManager; +import com.android.sdklib.avd.AvdManager.AvdInfo; + +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.debug.core.DebugPlugin; +import org.eclipse.debug.core.ILaunchConfiguration; +import org.eclipse.debug.core.ILaunchConfigurationType; +import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy; +import org.eclipse.debug.core.ILaunchManager; +import org.eclipse.debug.core.model.IDebugTarget; +import org.eclipse.debug.ui.DebugUITools; +import org.eclipse.jdt.launching.IJavaLaunchConfigurationConstants; +import org.eclipse.jdt.launching.IVMConnector; +import org.eclipse.jdt.launching.JavaRuntime; +import org.eclipse.jface.dialogs.Dialog; +import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.jface.preference.IPreferenceStore; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map.Entry; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Controls the launch of Android application either on a device or on the + * emulator. If an emulator is already running, this class will attempt to reuse + * it. + */ +public final class AndroidLaunchController implements IDebugBridgeChangeListener, + IDeviceChangeListener, IClientChangeListener { + + private static final String FLAG_AVD = "-avd"; //$NON-NLS-1$ + private static final String FLAG_NETDELAY = "-netdelay"; //$NON-NLS-1$ + private static final String FLAG_NETSPEED = "-netspeed"; //$NON-NLS-1$ + private static final String FLAG_WIPE_DATA = "-wipe-data"; //$NON-NLS-1$ + private static final String FLAG_NO_BOOT_ANIM = "-no-boot-anim"; //$NON-NLS-1$ + + private static final int MAX_ATTEMPT_COUNT = 5; + + private final static Pattern sAmErrorType = Pattern.compile("Error type (\\d+)"); //$NON-NLS-1$ + + /** + * A delayed launch waiting for a device to be present or ready before the + * application is launched. + */ + static final class DelayedLaunchInfo { + /** The device on which to launch the app */ + Device mDevice = null; + + /** The eclipse project */ + IProject mProject; + + /** Package name */ + String mPackageName; + + /** fully qualified name of the activity */ + String mActivity; + + /** IFile to the package (.apk) file */ + IFile mPackageFile; + + /** Debuggable attribute of the manifest file. */ + Boolean mDebuggable = null; + + /** Required ApiVersionNumber by the app. 0 means no requirements */ + int mRequiredApiVersionNumber = 0; + + InstallRetryMode mRetryMode = InstallRetryMode.NEVER; + + /** + * Launch action. See {@link LaunchConfigDelegate#ACTION_DEFAULT}, + * {@link LaunchConfigDelegate#ACTION_ACTIVITY}, + * {@link LaunchConfigDelegate#ACTION_DO_NOTHING} + */ + int mLaunchAction; + + /** the launch object */ + AndroidLaunch mLaunch; + + /** the monitor object */ + IProgressMonitor mMonitor; + + /** debug mode flag */ + boolean mDebugMode; + + int mAttemptCount = 0; + + boolean mCancelled = false; + + /** Basic constructor with activity and package info. */ + private DelayedLaunchInfo(IProject project, String packageName, String activity, + IFile pack, Boolean debuggable, int requiredApiVersionNumber, int launchAction, + AndroidLaunch launch, IProgressMonitor monitor) { + mProject = project; + mPackageName = packageName; + mActivity = activity; + mPackageFile = pack; + mLaunchAction = launchAction; + mLaunch = launch; + mMonitor = monitor; + mDebuggable = debuggable; + mRequiredApiVersionNumber = requiredApiVersionNumber; + } + } + + /** + * Map to store {@link ILaunchConfiguration} objects that must be launched as simple connection + * to running application. The integer is the port on which to connect. + * ALL ACCESS MUST BE INSIDE A synchronized (sListLock) block! + */ + private final static HashMap sRunningAppMap = + new HashMap(); + + private final static Object sListLock = sRunningAppMap; + + /** + * List of {@link DelayedLaunchInfo} waiting for an emulator to connect. + *

        Once an emulator has connected, {@link DelayedLaunchInfo#mDevice} is set and the + * DelayedLaunchInfo object is moved to {@link AndroidLaunchController#mWaitingForReadyEmulatorList}. + * ALL ACCESS MUST BE INSIDE A synchronized (sListLock) block! + */ + private final ArrayList mWaitingForEmulatorLaunches = + new ArrayList(); + + /** + * List of application waiting to be launched on a device/emulator.
        + * ALL ACCESS MUST BE INSIDE A synchronized (sListLock) block! + * */ + private final ArrayList mWaitingForReadyEmulatorList = + new ArrayList(); + + /** + * Application waiting to show up as waiting for debugger. + * ALL ACCESS MUST BE INSIDE A synchronized (sListLock) block! + */ + private final ArrayList mWaitingForDebuggerApplications = + new ArrayList(); + + /** + * List of clients that have appeared as waiting for debugger before their name was available. + * ALL ACCESS MUST BE INSIDE A synchronized (sListLock) block! + */ + private final ArrayList mUnknownClientsWaitingForDebugger = new ArrayList(); + + /** static instance for singleton */ + private static AndroidLaunchController sThis = new AndroidLaunchController(); + + enum InstallRetryMode { + NEVER, ALWAYS, PROMPT; + } + + /** + * Launch configuration data. This stores the result of querying the + * {@link ILaunchConfiguration} so that it's only done once. + */ + static final class AndroidLaunchConfiguration { + + /** + * Launch action. See {@link LaunchConfigDelegate#ACTION_DEFAULT}, + * {@link LaunchConfigDelegate#ACTION_ACTIVITY}, + * {@link LaunchConfigDelegate#ACTION_DO_NOTHING} + */ + public int mLaunchAction = LaunchConfigDelegate.DEFAULT_LAUNCH_ACTION; + + public static final boolean AUTO_TARGET_MODE = true; + + /** + * Target selection mode. + *

          + *
        • true: automatic mode, see {@link #AUTO_TARGET_MODE}
        • + *
        • false: manual mode
        • + *
        + */ + public boolean mTargetMode = LaunchConfigDelegate.DEFAULT_TARGET_MODE; + + /** + * Indicates whether the emulator should be called with -wipe-data + */ + public boolean mWipeData = LaunchConfigDelegate.DEFAULT_WIPE_DATA; + + /** + * Indicates whether the emulator should be called with -no-boot-anim + */ + public boolean mNoBootAnim = LaunchConfigDelegate.DEFAULT_NO_BOOT_ANIM; + + /** + * AVD Name. + */ + public String mAvdName = null; + + public String mNetworkSpeed = EmulatorConfigTab.getSpeed( + LaunchConfigDelegate.DEFAULT_SPEED); + public String mNetworkDelay = EmulatorConfigTab.getDelay( + LaunchConfigDelegate.DEFAULT_DELAY); + + /** + * Optional custom command line parameter to launch the emulator + */ + public String mEmulatorCommandLine; + + /** + * Initialized the structure from an ILaunchConfiguration object. + * @param config + */ + public void set(ILaunchConfiguration config) { + try { + mLaunchAction = config.getAttribute(LaunchConfigDelegate.ATTR_LAUNCH_ACTION, + mLaunchAction); + } catch (CoreException e1) { + // nothing to be done here, we'll use the default value + } + + try { + mTargetMode = config.getAttribute(LaunchConfigDelegate.ATTR_TARGET_MODE, + mTargetMode); + } catch (CoreException e) { + // nothing to be done here, we'll use the default value + } + + try { + mAvdName = config.getAttribute(LaunchConfigDelegate.ATTR_AVD_NAME, mAvdName); + } catch (CoreException e) { + } + + int index = LaunchConfigDelegate.DEFAULT_SPEED; + try { + index = config.getAttribute(LaunchConfigDelegate.ATTR_SPEED, index); + } catch (CoreException e) { + // nothing to be done here, we'll use the default value + } + mNetworkSpeed = EmulatorConfigTab.getSpeed(index); + + index = LaunchConfigDelegate.DEFAULT_DELAY; + try { + index = config.getAttribute(LaunchConfigDelegate.ATTR_DELAY, index); + } catch (CoreException e) { + // nothing to be done here, we'll use the default value + } + mNetworkDelay = EmulatorConfigTab.getDelay(index); + + try { + mEmulatorCommandLine = config.getAttribute( + LaunchConfigDelegate.ATTR_COMMANDLINE, ""); //$NON-NLS-1$ + } catch (CoreException e) { + // lets not do anything here, we'll use the default value + } + + try { + mWipeData = config.getAttribute(LaunchConfigDelegate.ATTR_WIPE_DATA, mWipeData); + } catch (CoreException e) { + // nothing to be done here, we'll use the default value + } + + try { + mNoBootAnim = config.getAttribute(LaunchConfigDelegate.ATTR_NO_BOOT_ANIM, + mNoBootAnim); + } catch (CoreException e) { + // nothing to be done here, we'll use the default value + } + } + } + + /** + * Output receiver for am process (activity Manager); + */ + private final class AMReceiver extends MultiLineReceiver { + private DelayedLaunchInfo mLaunchInfo; + private Device mDevice; + + /** + * Basic constructor. + * @param launchInfo The launch info associated with the am process. + * @param device The device on which the launch is done. + */ + public AMReceiver(DelayedLaunchInfo launchInfo, Device device) { + mLaunchInfo = launchInfo; + mDevice = device; + } + + @Override + public void processNewLines(String[] lines) { + // first we check if one starts with error + ArrayList array = new ArrayList(); + boolean error = false; + boolean warning = false; + for (String s : lines) { + // ignore empty lines. + if (s.length() == 0) { + continue; + } + + // check for errors that output an error type, if the attempt count is still + // valid. If not the whole text will be output in the console + if (mLaunchInfo.mAttemptCount < MAX_ATTEMPT_COUNT && + mLaunchInfo.mCancelled == false) { + Matcher m = sAmErrorType.matcher(s); + if (m.matches()) { + // get the error type + int type = Integer.parseInt(m.group(1)); + + final int waitTime = 3; + String msg; + + switch (type) { + case 1: + /* Intended fall through */ + case 2: + msg = String.format( + "Device not ready. Waiting %1$d seconds before next attempt.", + waitTime); + break; + case 3: + msg = String.format( + "New package not yet registered with the system. Waiting %1$d seconds before next attempt.", + waitTime); + break; + default: + msg = String.format( + "Device not ready (%2$d). Waiting %1$d seconds before next attempt.", + waitTime, type); + break; + + } + + AdtPlugin.printToConsole(mLaunchInfo.mProject, msg); + + // launch another thread, that waits a bit and attempts another launch + new Thread("Delayed Launch attempt") { + @Override + public void run() { + try { + sleep(waitTime * 1000); + } catch (InterruptedException e) { + } + + launchApp(mLaunchInfo, mDevice); + } + }.start(); + + // no need to parse the rest + return; + } + } + + // check for error if needed + if (error == false && s.startsWith("Error:")) { //$NON-NLS-1$ + error = true; + } + if (warning == false && s.startsWith("Warning:")) { //$NON-NLS-1$ + warning = true; + } + + // add the line to the list + array.add("ActivityManager: " + s); //$NON-NLS-1$ + } + + // then we display them in the console + if (warning || error) { + AdtPlugin.printErrorToConsole(mLaunchInfo.mProject, array.toArray()); + } else { + AdtPlugin.printToConsole(mLaunchInfo.mProject, array.toArray()); + } + + // if error then we cancel the launch, and remove the delayed info + if (error) { + mLaunchInfo.mLaunch.stopLaunch(); + synchronized (sListLock) { + mWaitingForReadyEmulatorList.remove(mLaunchInfo); + } + } + } + + public boolean isCancelled() { + return false; + } + } + + /** + * Output receiver for "pm install package.apk" command line. + */ + private final static class InstallReceiver extends MultiLineReceiver { + + private final static String SUCCESS_OUTPUT = "Success"; //$NON-NLS-1$ + private final static Pattern FAILURE_PATTERN = Pattern.compile("Failure\\s+\\[(.*)\\]"); //$NON-NLS-1$ + + private String mSuccess = null; + + public InstallReceiver() { + } + + @Override + public void processNewLines(String[] lines) { + for (String line : lines) { + if (line.length() > 0) { + if (line.startsWith(SUCCESS_OUTPUT)) { + mSuccess = null; + } else { + Matcher m = FAILURE_PATTERN.matcher(line); + if (m.matches()) { + mSuccess = m.group(1); + } + } + } + } + } + + public boolean isCancelled() { + return false; + } + + public String getSuccess() { + return mSuccess; + } + } + + + /** private constructor to enforce singleton */ + private AndroidLaunchController() { + AndroidDebugBridge.addDebugBridgeChangeListener(this); + AndroidDebugBridge.addDeviceChangeListener(this); + AndroidDebugBridge.addClientChangeListener(this); + } + + /** + * Returns the singleton reference. + */ + public static AndroidLaunchController getInstance() { + return sThis; + } + + + /** + * Launches a remote java debugging session on an already running application + * @param project The project of the application to debug. + * @param debugPort The port to connect the debugger to. + */ + public static void debugRunningApp(IProject project, int debugPort) { + // get an existing or new launch configuration + ILaunchConfiguration config = AndroidLaunchController.getLaunchConfig(project); + + if (config != null) { + setPortLaunchConfigAssociation(config, debugPort); + + // and launch + DebugUITools.launch(config, ILaunchManager.DEBUG_MODE); + } + } + + /** + * Returns an {@link ILaunchConfiguration} for the specified {@link IProject}. + * @param project the project + * @return a new or already existing ILaunchConfiguration or null if there was + * an error when creating a new one. + */ + public static ILaunchConfiguration getLaunchConfig(IProject project) { + // get the launch manager + ILaunchManager manager = DebugPlugin.getDefault().getLaunchManager(); + + // now get the config type for our particular android type. + ILaunchConfigurationType configType = manager.getLaunchConfigurationType( + LaunchConfigDelegate.ANDROID_LAUNCH_TYPE_ID); + + String name = project.getName(); + + // search for an existing launch configuration + ILaunchConfiguration config = findConfig(manager, configType, name); + + // test if we found one or not + if (config == null) { + // Didn't find a matching config, so we make one. + // It'll be made in the "working copy" object first. + ILaunchConfigurationWorkingCopy wc = null; + + try { + // make the working copy object + wc = configType.newInstance(null, + manager.generateUniqueLaunchConfigurationNameFrom(name)); + + // set the project name + wc.setAttribute(IJavaLaunchConfigurationConstants.ATTR_PROJECT_NAME, name); + + // set the launch mode to default. + wc.setAttribute(LaunchConfigDelegate.ATTR_LAUNCH_ACTION, + LaunchConfigDelegate.DEFAULT_LAUNCH_ACTION); + + // set default target mode + wc.setAttribute(LaunchConfigDelegate.ATTR_TARGET_MODE, + LaunchConfigDelegate.DEFAULT_TARGET_MODE); + + // default AVD: None + wc.setAttribute(LaunchConfigDelegate.ATTR_AVD_NAME, (String)null); + + // set the default network speed + wc.setAttribute(LaunchConfigDelegate.ATTR_SPEED, + LaunchConfigDelegate.DEFAULT_SPEED); + + // and delay + wc.setAttribute(LaunchConfigDelegate.ATTR_DELAY, + LaunchConfigDelegate.DEFAULT_DELAY); + + // default wipe data mode + wc.setAttribute(LaunchConfigDelegate.ATTR_WIPE_DATA, + LaunchConfigDelegate.DEFAULT_WIPE_DATA); + + // default disable boot animation option + wc.setAttribute(LaunchConfigDelegate.ATTR_NO_BOOT_ANIM, + LaunchConfigDelegate.DEFAULT_NO_BOOT_ANIM); + + // set default emulator options + IPreferenceStore store = AdtPlugin.getDefault().getPreferenceStore(); + String emuOptions = store.getString(AdtPlugin.PREFS_EMU_OPTIONS); + wc.setAttribute(LaunchConfigDelegate.ATTR_COMMANDLINE, emuOptions); + + // map the config and the project + wc.setMappedResources(getResourcesToMap(project)); + + // save the working copy to get the launch config object which we return. + return wc.doSave(); + + } catch (CoreException e) { + String msg = String.format( + "Failed to create a Launch config for project '%1$s': %2$s", + project.getName(), e.getMessage()); + AdtPlugin.printErrorToConsole(project, msg); + + // no launch! + return null; + } + } + + return config; + } + + /** + * Returns the list of resources to map to a Launch Configuration. + * @param project the project associated to the launch configuration. + */ + public static IResource[] getResourcesToMap(IProject project) { + ArrayList array = new ArrayList(2); + array.add(project); + + AndroidManifestHelper helper = new AndroidManifestHelper(project); + IFile manifest = helper.getManifestIFile(); + if (manifest != null) { + array.add(manifest); + } + + return array.toArray(new IResource[array.size()]); + } + + /** + * Launches an android app on the device or emulator + * + * @param project The project we're launching + * @param mode the mode in which to launch, one of the mode constants + * defined by ILaunchManager - RUN_MODE or + * DEBUG_MODE. + * @param apk the resource to the apk to launch. + * @param debuggable the debuggable value of the app, or null if not set. + * @param requiredApiVersionNumber the api version required by the app, or -1 if none. + * @param activity the class to provide to am to launch + * @param config the launch configuration + * @param launch the launch object + */ + public void launch(final IProject project, String mode, IFile apk, + String packageName, Boolean debuggable, int requiredApiVersionNumber, String activity, + final AndroidLaunchConfiguration config, final AndroidLaunch launch, + IProgressMonitor monitor) { + + String message; + if (config.mLaunchAction == LaunchConfigDelegate.ACTION_DO_NOTHING) { + message = String.format("Only Syncing Application Package"); + } else { + message = String.format("Launching: %1$s", activity); + } + AdtPlugin.printToConsole(project, message); + + // create the launch info + final DelayedLaunchInfo launchInfo = new DelayedLaunchInfo(project, packageName, + activity, apk, debuggable, requiredApiVersionNumber, config.mLaunchAction, + launch, monitor); + + // set the debug mode + launchInfo.mDebugMode = mode.equals(ILaunchManager.DEBUG_MODE); + + // get the SDK + Sdk currentSdk = Sdk.getCurrent(); + AvdManager avdManager = currentSdk.getAvdManager(); + + // get the project target + final IAndroidTarget projectTarget = currentSdk.getTarget(project); + + // FIXME: check errors on missing sdk, AVD manager, or project target. + + // device chooser response object. + final DeviceChooserResponse response = new DeviceChooserResponse(); + + /* + * Launch logic: + * - Manually Mode + * Always display a UI that lets a user see the current running emulators/devices. + * The UI must show which devices are compatibles, and allow launching new emulators + * with compatible (and not yet running) AVD. + * - Automatic Way + * * Preferred AVD set. + * If Preferred AVD is not running: launch it. + * Launch the application on the preferred AVD. + * * No preferred AVD. + * Count the number of compatible emulators/devices. + * If != 1, display a UI similar to manual mode. + * If == 1, launch the application on this AVD/device. + */ + + if (config.mTargetMode == AndroidLaunchConfiguration.AUTO_TARGET_MODE) { + // if we are in automatic target mode, we need to find the current devices + Device[] devices = AndroidDebugBridge.getBridge().getDevices(); + + // first check if we have a preferred AVD name, and if it actually exists, and is valid + // (ie able to run the project). + // We need to check this in case the AVD was recreated with a different target that is + // not compatible. + AvdInfo preferredAvd = null; + if (config.mAvdName != null) { + preferredAvd = avdManager.getAvd(config.mAvdName); + if (projectTarget.isCompatibleBaseFor(preferredAvd.getTarget()) == false) { + preferredAvd = null; + + AdtPlugin.printErrorToConsole(project, String.format( + "Preferred AVD '%1$s' is not compatible with the project target '%2$s'. Looking for a compatible AVD...", + config.mAvdName, projectTarget.getName())); + } + } + + if (preferredAvd != null) { + // look for a matching device + for (Device d : devices) { + String deviceAvd = d.getAvdName(); + if (deviceAvd != null && deviceAvd.equals(config.mAvdName)) { + response.setDeviceToUse(d); + + AdtPlugin.printToConsole(project, String.format( + "Automatic Target Mode: Preferred AVD '%1$s' is available on emulator '%2$s'", + config.mAvdName, d)); + + continueLaunch(response, project, launch, launchInfo, config); + return; + } + } + + // at this point we have a valid preferred AVD that is not running. + // We need to start it. + response.setAvdToLaunch(preferredAvd); + + AdtPlugin.printToConsole(project, String.format( + "Automatic Target Mode: Preferred AVD '%1$s' is not available. Launching new emulator.", + config.mAvdName)); + + continueLaunch(response, project, launch, launchInfo, config); + return; + } + + // no (valid) preferred AVD? look for one. + HashMap compatibleRunningAvds = new HashMap(); + boolean hasDevice = false; // if there's 1+ device running, we may force manual mode, + // as we cannot always detect proper compatibility with + // devices. This is the case if the project target is not + // a standard platform + for (Device d : devices) { + String deviceAvd = d.getAvdName(); + if (deviceAvd != null) { // physical devices return null. + AvdInfo info = avdManager.getAvd(deviceAvd); + if (info != null && projectTarget.isCompatibleBaseFor(info.getTarget())) { + compatibleRunningAvds.put(d, info); + } + } else { + if (projectTarget.isPlatform()) { // means this can run on any device as long + // as api level is high enough + String apiString = d.getProperty(SdkManager.PROP_VERSION_SDK); + try { + int apiNumber = Integer.parseInt(apiString); + if (apiNumber >= projectTarget.getApiVersionNumber()) { + // device is compatible with project + compatibleRunningAvds.put(d, null); + continue; + } + } catch (NumberFormatException e) { + // do nothing, we'll consider it a non compatible device below. + } + } + hasDevice = true; + } + } + + // depending on the number of devices, we'll simulate an automatic choice + // from the device chooser or simply show up the device chooser. + if (hasDevice == false && compatibleRunningAvds.size() == 0) { + // if zero emulators/devices, we launch an emulator. + // We need to figure out which AVD first. + + // we are going to take the closest AVD. ie a compatible AVD that has the API level + // closest to the project target. + AvdInfo[] avds = avdManager.getAvds(); + AvdInfo defaultAvd = null; + for (AvdInfo avd : avds) { + if (projectTarget.isCompatibleBaseFor(avd.getTarget())) { + if (defaultAvd == null || + avd.getTarget().getApiVersionNumber() < + defaultAvd.getTarget().getApiVersionNumber()) { + defaultAvd = avd; + } + } + } + + if (defaultAvd != null) { + response.setAvdToLaunch(defaultAvd); + + AdtPlugin.printToConsole(project, String.format( + "Automatic Target Mode: launching new emulator with compatible AVD '%1$s'", + defaultAvd.getName())); + + continueLaunch(response, project, launch, launchInfo, config); + return; + } else { + // FIXME: ask the user if he wants to create a AVD. + // we found no compatible AVD. + AdtPlugin.printErrorToConsole(project, String.format( + "Failed to find a AVD compatible with target '%1$s'. Launch aborted.", + projectTarget.getName())); + launch.stopLaunch(); + return; + } + } else if (hasDevice == false && compatibleRunningAvds.size() == 1) { + Entry e = compatibleRunningAvds.entrySet().iterator().next(); + response.setDeviceToUse(e.getKey()); + + // get the AvdInfo, if null, the device is a physical device. + AvdInfo avdInfo = e.getValue(); + if (avdInfo != null) { + message = String.format("Automatic Target Mode: using existing emulator '%1$s' running compatible AVD '%2$s'", + response.getDeviceToUse(), e.getValue().getName()); + } else { + message = String.format("Automatic Target Mode: using device '%1$s'", + response.getDeviceToUse()); + } + AdtPlugin.printToConsole(project, message); + + continueLaunch(response, project, launch, launchInfo, config); + return; + } + + // if more than one device, we'll bring up the DeviceChooser dialog below. + if (compatibleRunningAvds.size() >= 2) { + message = "Automatic Target Mode: Several compatible targets. Please select a target device."; + } else if (hasDevice) { + message = "Automatic Target Mode: Unable to detect device compatibility. Please select a target device."; + } + + AdtPlugin.printToConsole(project, message); + } + + // bring up the device chooser. + AdtPlugin.getDisplay().asyncExec(new Runnable() { + public void run() { + try { + // open the chooser dialog. It'll fill 'response' with the device to use + // or the AVD to launch. + DeviceChooserDialog dialog = new DeviceChooserDialog( + AdtPlugin.getDisplay().getActiveShell(), + response, launchInfo.mPackageName, projectTarget); + if (dialog.open() == Dialog.OK) { + AndroidLaunchController.this.continueLaunch(response, project, launch, + launchInfo, config); + } else { + AdtPlugin.printErrorToConsole(project, "Launch canceled!"); + launch.stopLaunch(); + return; + } + } catch (Exception e) { + // there seems to be some case where the shell will be null. (might be + // an OS X bug). Because of this the creation of the dialog will throw + // and IllegalArg exception interrupting the launch with no user feedback. + // So we trap all the exception and display something. + String msg = e.getMessage(); + if (msg == null) { + msg = e.getClass().getCanonicalName(); + } + AdtPlugin.printErrorToConsole(project, + String.format("Error during launch: %s", msg)); + launch.stopLaunch(); + } + } + }); + } + + /** + * Continues the launch based on the DeviceChooser response. + * @param response the device chooser response + * @param project The project being launched + * @param launch The eclipse launch info + * @param launchInfo The {@link DelayedLaunchInfo} + * @param config The config needed to start a new emulator. + */ + void continueLaunch(final DeviceChooserResponse response, final IProject project, + final AndroidLaunch launch, final DelayedLaunchInfo launchInfo, + final AndroidLaunchConfiguration config) { + + // Since this is called from the UI thread we spawn a new thread + // to finish the launch. + new Thread() { + @Override + public void run() { + if (response.getAvdToLaunch() != null) { + // there was no selected device, we start a new emulator. + synchronized (sListLock) { + AvdInfo info = response.getAvdToLaunch(); + mWaitingForEmulatorLaunches.add(launchInfo); + AdtPlugin.printToConsole(project, String.format( + "Launching a new emulator with Virtual Device '%1$s'", + info.getName())); + boolean status = launchEmulator(config, info); + + if (status == false) { + // launching the emulator failed! + AdtPlugin.displayError("Emulator Launch", + "Couldn't launch the emulator! Make sure the SDK directory is properly setup and the emulator is not missing."); + + // stop the launch and return + mWaitingForEmulatorLaunches.remove(launchInfo); + AdtPlugin.printErrorToConsole(project, "Launch canceled!"); + launch.stopLaunch(); + return; + } + + return; + } + } else if (response.getDeviceToUse() != null) { + launchInfo.mDevice = response.getDeviceToUse(); + simpleLaunch(launchInfo, launchInfo.mDevice); + } + } + }.start(); + } + + /** + * Queries for a debugger port for a specific {@link ILaunchConfiguration}. + *

        + * If the configuration and a debugger port where added through + * {@link #setPortLaunchConfigAssociation(ILaunchConfiguration, int)}, then this method + * will return the debugger port, and remove the configuration from the list. + * @param launchConfig the {@link ILaunchConfiguration} + * @return the debugger port or {@link LaunchConfigDelegate#INVALID_DEBUG_PORT} if the + * configuration was not setup. + */ + static int getPortForConfig(ILaunchConfiguration launchConfig) { + synchronized (sListLock) { + Integer port = sRunningAppMap.get(launchConfig); + if (port != null) { + sRunningAppMap.remove(launchConfig); + return port; + } + } + + return LaunchConfigDelegate.INVALID_DEBUG_PORT; + } + + /** + * Set a {@link ILaunchConfiguration} and its associated debug port, in the list of + * launch config to connect directly to a running app instead of doing full launch (sync, + * launch, and connect to). + * @param launchConfig the {@link ILaunchConfiguration} object. + * @param port The debugger port to connect to. + */ + private static void setPortLaunchConfigAssociation(ILaunchConfiguration launchConfig, + int port) { + synchronized (sListLock) { + sRunningAppMap.put(launchConfig, port); + } + } + + /** + * Checks the build information, and returns whether the launch should continue. + *

        The value tested are: + *

          + *
        • Minimum API version requested by the application. If the target device does not match, + * the launch is canceled.
        • + *
        • Debuggable attribute of the application and whether or not the device requires it. If + * the device requires it and it is not set in the manifest, the launch will be forced to + * "release" mode instead of "debug"
        • + *
            + */ + private boolean checkBuildInfo(DelayedLaunchInfo launchInfo, Device device) { + if (device != null) { + // check the app required API level versus the target device API level + + String deviceApiVersionName = device.getProperty(Device.PROP_BUILD_VERSION); + String value = device.getProperty(Device.PROP_BUILD_VERSION_NUMBER); + int deviceApiVersionNumber = 0; + try { + deviceApiVersionNumber = Integer.parseInt(value); + } catch (NumberFormatException e) { + // pass, we'll keep the deviceVersionNumber value at 0. + } + + if (launchInfo.mRequiredApiVersionNumber == 0) { + // warn the API level requirement is not set. + AdtPlugin.printErrorToConsole(launchInfo.mProject, + "WARNING: Application does not specify an API level requirement!"); + + // and display the target device API level (if known) + if (deviceApiVersionName == null || deviceApiVersionNumber == 0) { + AdtPlugin.printErrorToConsole(launchInfo.mProject, + "WARNING: Unknown device API version!"); + } else { + AdtPlugin.printErrorToConsole(launchInfo.mProject, String.format( + "Device API version is %1$d (Android %2$s)", deviceApiVersionNumber, + deviceApiVersionName)); + } + } else { // app requires a specific API level + if (deviceApiVersionName == null || deviceApiVersionNumber == 0) { + AdtPlugin.printToConsole(launchInfo.mProject, + "WARNING: Unknown device API version!"); + } else if (deviceApiVersionNumber < launchInfo.mRequiredApiVersionNumber) { + String msg = String.format( + "ERROR: Application requires API version %1$d. Device API version is %2$d (Android %3$s).", + launchInfo.mRequiredApiVersionNumber, deviceApiVersionNumber, + deviceApiVersionName); + AdtPlugin.printErrorToConsole(launchInfo.mProject, msg); + + // abort the launch + return false; + } + } + + // now checks that the device/app can be debugged (if needed) + if (device.isEmulator() == false && launchInfo.mDebugMode) { + String debuggableDevice = device.getProperty(Device.PROP_DEBUGGABLE); + if (debuggableDevice != null && debuggableDevice.equals("0")) { //$NON-NLS-1$ + // the device is "secure" and requires apps to declare themselves as debuggable! + if (launchInfo.mDebuggable == null) { + String message1 = String.format( + "Device '%1$s' requires that applications explicitely declare themselves as debuggable in their manifest.", + device.getSerialNumber()); + String message2 = String.format("Application '%1$s' does not have the attribute 'debuggable' set to TRUE in its manifest and cannot be debugged.", + launchInfo.mPackageName); + AdtPlugin.printErrorToConsole(launchInfo.mProject, message1, message2); + + // because am -D does not check for ro.debuggable and the + // 'debuggable' attribute, it is important we do not use the -D option + // in this case or the app will wait for a debugger forever and never + // really launch. + launchInfo.mDebugMode = false; + } else if (launchInfo.mDebuggable == Boolean.FALSE) { + String message = String.format("Application '%1$s' has its 'debuggable' attribute set to FALSE and cannot be debugged.", + launchInfo.mPackageName); + AdtPlugin.printErrorToConsole(launchInfo.mProject, message); + + // because am -D does not check for ro.debuggable and the + // 'debuggable' attribute, it is important we do not use the -D option + // in this case or the app will wait for a debugger forever and never + // really launch. + launchInfo.mDebugMode = false; + } + } + } + } + + return true; + } + + /** + * Do a simple launch on the specified device, attempting to sync the new + * package, and then launching the application. Failed sync/launch will + * stop the current AndroidLaunch and return false; + * @param launchInfo + * @param device + * @return true if succeed + */ + private boolean simpleLaunch(DelayedLaunchInfo launchInfo, Device device) { + // API level check + if (checkBuildInfo(launchInfo, device) == false) { + AdtPlugin.printErrorToConsole(launchInfo.mProject, "Launch canceled!"); + launchInfo.mLaunch.stopLaunch(); + return false; + } + + // sync the app + if (syncApp(launchInfo, device) == false) { + AdtPlugin.printErrorToConsole(launchInfo.mProject, "Launch canceled!"); + launchInfo.mLaunch.stopLaunch(); + return false; + } + + // launch the app + launchApp(launchInfo, device); + + return true; + } + + + /** + * Syncs the application on the device/emulator. + * + * @param launchInfo The Launch information object. + * @param device the device on which to sync the application + * @return true if the install succeeded. + */ + private boolean syncApp(DelayedLaunchInfo launchInfo, Device device) { + SyncService sync = device.getSyncService(); + if (sync != null) { + IPath path = launchInfo.mPackageFile.getLocation(); + String message = String.format("Uploading %1$s onto device '%2$s'", + path.lastSegment(), device.getSerialNumber()); + AdtPlugin.printToConsole(launchInfo.mProject, message); + + String osLocalPath = path.toOSString(); + String apkName = launchInfo.mPackageFile.getName(); + String remotePath = "/data/local/tmp/" + apkName; //$NON-NLS-1$ + + SyncResult result = sync.pushFile(osLocalPath, remotePath, + SyncService.getNullProgressMonitor()); + + if (result.getCode() != SyncService.RESULT_OK) { + String msg = String.format("Failed to upload %1$s on '%2$s': %3$s", + apkName, device.getSerialNumber(), result.getMessage()); + AdtPlugin.printErrorToConsole(launchInfo.mProject, msg); + return false; + } + + // Now that the package is uploaded, we can install it properly. + // This will check that there isn't another apk declaring the same package, or + // that another install used a different key. + boolean installResult = installPackage(launchInfo, remotePath, device); + + // now we delete the app we sync'ed + try { + device.executeShellCommand("rm " + remotePath, new MultiLineReceiver() { //$NON-NLS-1$ + @Override + public void processNewLines(String[] lines) { + // pass + } + public boolean isCancelled() { + return false; + } + }); + } catch (IOException e) { + AdtPlugin.printErrorToConsole(launchInfo.mProject, String.format( + "Failed to delete temporary package: %1$s", e.getMessage())); + return false; + } + + return installResult; + } + + String msg = String.format( + "Failed to upload %1$s on device '%2$s': Unable to open sync connection!", + launchInfo.mPackageFile.getName(), device.getSerialNumber()); + AdtPlugin.printErrorToConsole(launchInfo.mProject, msg); + + return false; + } + + /** + * Installs the application package that was pushed to a temporary location on the device. + * @param launchInfo The launch information + * @param remotePath The remote path of the package. + * @param device The device on which the launch is done. + */ + private boolean installPackage(DelayedLaunchInfo launchInfo, final String remotePath, + final Device device) { + + String message = String.format("Installing %1$s...", launchInfo.mPackageFile.getName()); + AdtPlugin.printToConsole(launchInfo.mProject, message); + + try { + String result = doInstall(launchInfo, remotePath, device, false /* reinstall */); + + /* For now we force to retry the install (after uninstalling) because there's no + * other way around it: adb install does not want to update a package w/o uninstalling + * the old one first! + */ + return checkInstallResult(result, device, launchInfo, remotePath, + InstallRetryMode.ALWAYS); + } catch (IOException e) { + // do nothing, we'll return false + } + + return false; + } + + /** + * Checks the result of an installation, and takes optional actions based on it. + * @param result the result string from the installation + * @param device the device on which the installation occured. + * @param launchInfo the {@link DelayedLaunchInfo} + * @param remotePath the temporary path of the package on the device + * @param retryMode indicates what to do in case, a package already exists. + * @return true if success, false otherwise. + * @throws IOException + */ + private boolean checkInstallResult(String result, Device device, DelayedLaunchInfo launchInfo, + String remotePath, InstallRetryMode retryMode) throws IOException { + if (result == null) { + AdtPlugin.printToConsole(launchInfo.mProject, "Success!"); + return true; + } else if (result.equals("INSTALL_FAILED_ALREADY_EXISTS")) { //$NON-NLS-1$ + if (retryMode == InstallRetryMode.PROMPT) { + boolean prompt = AdtPlugin.displayPrompt("Application Install", + "A previous installation needs to be uninstalled before the new package can be installed.\nDo you want to uninstall?"); + if (prompt) { + retryMode = InstallRetryMode.ALWAYS; + } else { + AdtPlugin.printErrorToConsole(launchInfo.mProject, + "Installation error! The package already exists."); + return false; + } + } + + if (retryMode == InstallRetryMode.ALWAYS) { + /* + * TODO: create a UI that gives the dev the choice to: + * - clean uninstall on launch + * - full uninstall if application exists. + * - soft uninstall if application exists (keeps the app data around). + * - always ask (choice of soft-reinstall, full reinstall) + AdtPlugin.printErrorToConsole(launchInfo.mProject, + "Application already exists, uninstalling..."); + String res = doUninstall(device, launchInfo); + if (res == null) { + AdtPlugin.printToConsole(launchInfo.mProject, "Success!"); + } else { + AdtPlugin.printErrorToConsole(launchInfo.mProject, + String.format("Failed to uninstall: %1$s", res)); + return false; + } + */ + + AdtPlugin.printToConsole(launchInfo.mProject, + "Application already exists. Attempting to re-install instead..."); + String res = doInstall(launchInfo, remotePath, device, true /* reinstall */); + return checkInstallResult(res, device, launchInfo, remotePath, + InstallRetryMode.NEVER); + } + + AdtPlugin.printErrorToConsole(launchInfo.mProject, + "Installation error! The package already exists."); + } else if (result.equals("INSTALL_FAILED_INVALID_APK")) { //$NON-NLS-1$ + AdtPlugin.printErrorToConsole(launchInfo.mProject, + "Installation failed due to invalid APK file!", + "Please check logcat output for more details."); + } else if (result.equals("INSTALL_FAILED_INVALID_URI")) { //$NON-NLS-1$ + AdtPlugin.printErrorToConsole(launchInfo.mProject, + "Installation failed due to invalid URI!", + "Please check logcat output for more details."); + } else if (result.equals("INSTALL_FAILED_COULDNT_COPY")) { //$NON-NLS-1$ + AdtPlugin.printErrorToConsole(launchInfo.mProject, + String.format("Installation failed: Could not copy %1$s to its final location!", + launchInfo.mPackageFile.getName()), + "Please check logcat output for more details."); + } else if (result.equals("INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES")) { + AdtPlugin.printErrorToConsole(launchInfo.mProject, + "Re-installation failed due to different application signatures.", + "You must perform a full uninstall of the application. WARNING: This will remove the application data!", + String.format("Please execute 'adb uninstall %1$s' in a shell.", launchInfo.mPackageName)); + } else { + AdtPlugin.printErrorToConsole(launchInfo.mProject, + String.format("Installation error: %1$s", result), + "Please check logcat output for more details."); + } + + return false; + } + + /** + * Performs the uninstallation of an application. + * @param device the device on which to install the application. + * @param launchInfo the {@link DelayedLaunchInfo}. + * @return a {@link String} with an error code, or null if success. + * @throws IOException + */ + @SuppressWarnings("unused") + private String doUninstall(Device device, DelayedLaunchInfo launchInfo) throws IOException { + InstallReceiver receiver = new InstallReceiver(); + try { + device.executeShellCommand("pm uninstall " + launchInfo.mPackageName, //$NON-NLS-1$ + receiver); + } catch (IOException e) { + String msg = String.format( + "Failed to uninstall %1$s: %2$s", launchInfo.mPackageName, e.getMessage()); + AdtPlugin.printErrorToConsole(launchInfo.mProject, msg); + throw e; + } + + return receiver.getSuccess(); + } + + /** + * Performs the installation of an application whose package has been uploaded on the device. + *

            Before doing it, if the application is already running on the device, it is killed. + * @param launchInfo the {@link DelayedLaunchInfo}. + * @param remotePath the path of the application package in the device tmp folder. + * @param device the device on which to install the application. + * @param reinstall + * @return a {@link String} with an error code, or null if success. + * @throws IOException + */ + private String doInstall(DelayedLaunchInfo launchInfo, final String remotePath, + final Device device, boolean reinstall) throws IOException { + // kill running application + Client application = device.getClient(launchInfo.mPackageName); + if (application != null) { + application.kill(); + } + + InstallReceiver receiver = new InstallReceiver(); + try { + String cmd = String.format( + reinstall ? "pm install -r \"%1$s\"" : "pm install \"%1$s\"", //$NON-NLS-1$ //$NON-NLS-2$ + remotePath); //$NON-NLS-1$ //$NON-NLS-2$ + device.executeShellCommand(cmd, receiver); + } catch (IOException e) { + String msg = String.format( + "Failed to install %1$s on device '%2$s': %3$s", + launchInfo.mPackageFile.getName(), device.getSerialNumber(), e.getMessage()); + AdtPlugin.printErrorToConsole(launchInfo.mProject, msg); + throw e; + } + + return receiver.getSuccess(); + } + + /** + * launches an application on a device or emulator + * + * @param info the {@link DelayedLaunchInfo} that indicates the activity to launch + * @param device the device or emulator to launch the application on + */ + private void launchApp(final DelayedLaunchInfo info, Device device) { + // if we're not supposed to do anything, just stop the Launch item and return; + if (info.mLaunchAction == LaunchConfigDelegate.ACTION_DO_NOTHING) { + String msg = String.format("%1$s installed on device", + info.mPackageFile.getFullPath().toOSString()); + AdtPlugin.printToConsole(info.mProject, msg, "Done!"); + info.mLaunch.stopLaunch(); + return; + } + try { + String msg = String.format("Starting activity %1$s on device ", info.mActivity, + info.mDevice); + AdtPlugin.printToConsole(info.mProject, msg); + + // In debug mode, we need to add the info to the list of application monitoring + // client changes. + if (info.mDebugMode) { + synchronized (sListLock) { + if (mWaitingForDebuggerApplications.contains(info) == false) { + mWaitingForDebuggerApplications.add(info); + } + } + } + + // increment launch attempt count, to handle retries and timeouts + info.mAttemptCount++; + + // now we actually launch the app. + device.executeShellCommand("am start" //$NON-NLS-1$ + + (info.mDebugMode ? " -D" //$NON-NLS-1$ + : "") //$NON-NLS-1$ + + " -n " //$NON-NLS-1$ + + info.mPackageName + "/" //$NON-NLS-1$ + + info.mActivity.replaceAll("\\$", "\\\\\\$"), //$NON-NLS-1$ //$NON-NLS-2$ + new AMReceiver(info, device)); + + // if the app is not a debug app, we need to do some clean up, as + // the process is done! + if (info.mDebugMode == false) { + // stop the launch object, since there's no debug, and it can't + // provide any control over the app + info.mLaunch.stopLaunch(); + } + } catch (IOException e) { + // something went wrong trying to launch the app. + // lets stop the Launch + AdtPlugin.printErrorToConsole(info.mProject, + String.format("Launch error: %s", e.getMessage())); + info.mLaunch.stopLaunch(); + + // and remove it from the list of app waiting for debuggers + synchronized (sListLock) { + mWaitingForDebuggerApplications.remove(info); + } + } + } + + private boolean launchEmulator(AndroidLaunchConfiguration config, AvdInfo avdToLaunch) { + + // split the custom command line in segments + ArrayList customArgs = new ArrayList(); + boolean has_wipe_data = false; + if (config.mEmulatorCommandLine != null && config.mEmulatorCommandLine.length() > 0) { + String[] segments = config.mEmulatorCommandLine.split("\\s+"); //$NON-NLS-1$ + + // we need to remove the empty strings + for (String s : segments) { + if (s.length() > 0) { + customArgs.add(s); + if (!has_wipe_data && s.equals(FLAG_WIPE_DATA)) { + has_wipe_data = true; + } + } + } + } + + boolean needs_wipe_data = config.mWipeData && !has_wipe_data; + if (needs_wipe_data) { + if (!AdtPlugin.displayPrompt("Android Launch", "Are you sure you want to wipe all user data when starting this emulator?")) { + needs_wipe_data = false; + } + } + + // build the command line based on the available parameters. + ArrayList list = new ArrayList(); + + list.add(AdtPlugin.getOsAbsoluteEmulator()); + list.add(FLAG_AVD); + list.add(avdToLaunch.getName()); + + if (config.mNetworkSpeed != null) { + list.add(FLAG_NETSPEED); + list.add(config.mNetworkSpeed); + } + + if (config.mNetworkDelay != null) { + list.add(FLAG_NETDELAY); + list.add(config.mNetworkDelay); + } + + if (needs_wipe_data) { + list.add(FLAG_WIPE_DATA); + } + + if (config.mNoBootAnim) { + list.add(FLAG_NO_BOOT_ANIM); + } + + list.addAll(customArgs); + + // convert the list into an array for the call to exec. + String[] command = list.toArray(new String[list.size()]); + + // launch the emulator + try { + Process process = Runtime.getRuntime().exec(command); + grabEmulatorOutput(process); + } catch (IOException e) { + return false; + } + + return true; + } + + /** + * Looks for and returns an existing {@link ILaunchConfiguration} object for a + * specified project. + * @param manager The {@link ILaunchManager}. + * @param type The {@link ILaunchConfigurationType}. + * @param projectName The name of the project + * @return an existing ILaunchConfiguration object matching the project, or + * null. + */ + private static ILaunchConfiguration findConfig(ILaunchManager manager, + ILaunchConfigurationType type, String projectName) { + try { + ILaunchConfiguration[] configs = manager.getLaunchConfigurations(type); + + for (ILaunchConfiguration config : configs) { + if (config.getAttribute(IJavaLaunchConfigurationConstants.ATTR_PROJECT_NAME, + "").equals(projectName)) { //$NON-NLS-1$ + return config; + } + } + } catch (CoreException e) { + MessageDialog.openError(AdtPlugin.getDisplay().getActiveShell(), + "Launch Error", e.getStatus().getMessage()); + } + + // didn't find anything that matches. Return null + return null; + + } + + + /** + * Connects a remote debugger on the specified port. + * @param debugPort The port to connect the debugger to + * @param launch The associated AndroidLaunch object. + * @param monitor A Progress monitor + * @return false if cancelled by the monitor + * @throws CoreException + */ + public static boolean connectRemoteDebugger(int debugPort, + AndroidLaunch launch, IProgressMonitor monitor) + throws CoreException { + // get some default parameters. + int connectTimeout = JavaRuntime.getPreferences().getInt(JavaRuntime.PREF_CONNECT_TIMEOUT); + + HashMap newMap = new HashMap(); + + newMap.put("hostname", "localhost"); //$NON-NLS-1$ //$NON-NLS-2$ + + newMap.put("port", Integer.toString(debugPort)); //$NON-NLS-1$ + + newMap.put("timeout", Integer.toString(connectTimeout)); + + // get the default VM connector + IVMConnector connector = JavaRuntime.getDefaultVMConnector(); + + // connect to remote VM + connector.connect(newMap, monitor, launch); + + // check for cancellation + if (monitor.isCanceled()) { + IDebugTarget[] debugTargets = launch.getDebugTargets(); + for (IDebugTarget target : debugTargets) { + if (target.canDisconnect()) { + target.disconnect(); + } + } + return false; + } + + return true; + } + + /** + * Launch a new thread that connects a remote debugger on the specified port. + * @param debugPort The port to connect the debugger to + * @param androidLaunch The associated AndroidLaunch object. + * @param monitor A Progress monitor + * @see #connectRemoteDebugger(int, AndroidLaunch, IProgressMonitor) + */ + public static void launchRemoteDebugger( final int debugPort, final AndroidLaunch androidLaunch, + final IProgressMonitor monitor) { + new Thread("Debugger connection") { //$NON-NLS-1$ + @Override + public void run() { + try { + connectRemoteDebugger(debugPort, androidLaunch, monitor); + } catch (CoreException e) { + androidLaunch.stopLaunch(); + } + monitor.done(); + } + }.start(); + } + + /** + * Sent when a new {@link AndroidDebugBridge} is started. + *

            + * This is sent from a non UI thread. + * @param bridge the new {@link AndroidDebugBridge} object. + * + * @see IDebugBridgeChangeListener#bridgeChanged(AndroidDebugBridge) + */ + public void bridgeChanged(AndroidDebugBridge bridge) { + // The adb server has changed. We cancel any pending launches. + String message1 = "adb server change: cancelling '%1$s' launch!"; + String message2 = "adb server change: cancelling sync!"; + synchronized (sListLock) { + for (DelayedLaunchInfo launchInfo : mWaitingForReadyEmulatorList) { + if (launchInfo.mLaunchAction == LaunchConfigDelegate.ACTION_DO_NOTHING) { + AdtPlugin.printErrorToConsole(launchInfo.mProject, message2); + } else { + AdtPlugin.printErrorToConsole(launchInfo.mProject, + String.format(message1, launchInfo.mActivity)); + } + launchInfo.mLaunch.stopLaunch(); + } + for (DelayedLaunchInfo launchInfo : mWaitingForDebuggerApplications) { + if (launchInfo.mLaunchAction == LaunchConfigDelegate.ACTION_DO_NOTHING) { + AdtPlugin.printErrorToConsole(launchInfo.mProject, message2); + } else { + AdtPlugin.printErrorToConsole(launchInfo.mProject, + String.format(message1, launchInfo.mActivity)); + } + launchInfo.mLaunch.stopLaunch(); + } + + mWaitingForReadyEmulatorList.clear(); + mWaitingForDebuggerApplications.clear(); + } + } + + /** + * Sent when the a device is connected to the {@link AndroidDebugBridge}. + *

            + * This is sent from a non UI thread. + * @param device the new device. + * + * @see IDeviceChangeListener#deviceConnected(Device) + */ + public void deviceConnected(Device device) { + synchronized (sListLock) { + // look if there's an app waiting for a device + if (mWaitingForEmulatorLaunches.size() > 0) { + // get/remove first launch item from the list + // FIXME: what if we have multiple launches waiting? + DelayedLaunchInfo launchInfo = mWaitingForEmulatorLaunches.get(0); + mWaitingForEmulatorLaunches.remove(0); + + // give the launch item its device for later use. + launchInfo.mDevice = device; + + // and move it to the other list + mWaitingForReadyEmulatorList.add(launchInfo); + + // and tell the user about it + AdtPlugin.printToConsole(launchInfo.mProject, + String.format("New emulator found: %1$s", device.getSerialNumber())); + AdtPlugin.printToConsole(launchInfo.mProject, + String.format("Waiting for HOME ('%1$s') to be launched...", + AdtPlugin.getDefault().getPreferenceStore().getString( + AdtPlugin.PREFS_HOME_PACKAGE))); + } + } + } + + /** + * Sent when the a device is connected to the {@link AndroidDebugBridge}. + *

            + * This is sent from a non UI thread. + * @param device the new device. + * + * @see IDeviceChangeListener#deviceDisconnected(Device) + */ + @SuppressWarnings("unchecked") + public void deviceDisconnected(Device device) { + // any pending launch on this device must be canceled. + String message = "%1$s disconnected! Cancelling '%2$s' launch!"; + synchronized (sListLock) { + ArrayList copyList = + (ArrayList)mWaitingForReadyEmulatorList.clone(); + for (DelayedLaunchInfo launchInfo : copyList) { + if (launchInfo.mDevice == device) { + AdtPlugin.printErrorToConsole(launchInfo.mProject, + String.format(message, device.getSerialNumber(), launchInfo.mActivity)); + launchInfo.mLaunch.stopLaunch(); + mWaitingForReadyEmulatorList.remove(launchInfo); + } + } + copyList = (ArrayList)mWaitingForDebuggerApplications.clone(); + for (DelayedLaunchInfo launchInfo : copyList) { + if (launchInfo.mDevice == device) { + AdtPlugin.printErrorToConsole(launchInfo.mProject, + String.format(message, device.getSerialNumber(), launchInfo.mActivity)); + launchInfo.mLaunch.stopLaunch(); + mWaitingForDebuggerApplications.remove(launchInfo); + } + } + } + } + + /** + * Sent when a device data changed, or when clients are started/terminated on the device. + *

            + * This is sent from a non UI thread. + * @param device the device that was updated. + * @param changeMask the mask indicating what changed. + * + * @see IDeviceChangeListener#deviceChanged(Device, int) + */ + public void deviceChanged(Device device, int changeMask) { + // We could check if any starting device we care about is now ready, but we can wait for + // its home app to show up, so... + } + + /** + * Sent when an existing client information changed. + *

            + * This is sent from a non UI thread. + * @param client the updated client. + * @param changeMask the bit mask describing the changed properties. It can contain + * any of the following values: {@link Client#CHANGE_INFO}, {@link Client#CHANGE_NAME} + * {@link Client#CHANGE_DEBUGGER_INTEREST}, {@link Client#CHANGE_THREAD_MODE}, + * {@link Client#CHANGE_THREAD_DATA}, {@link Client#CHANGE_HEAP_MODE}, + * {@link Client#CHANGE_HEAP_DATA}, {@link Client#CHANGE_NATIVE_HEAP_DATA} + * + * @see IClientChangeListener#clientChanged(Client, int) + */ + public void clientChanged(final Client client, int changeMask) { + boolean connectDebugger = false; + if ((changeMask & Client.CHANGE_NAME) == Client.CHANGE_NAME) { + String applicationName = client.getClientData().getClientDescription(); + if (applicationName != null) { + IPreferenceStore store = AdtPlugin.getDefault().getPreferenceStore(); + String home = store.getString(AdtPlugin.PREFS_HOME_PACKAGE); + + if (home.equals(applicationName)) { + + // looks like home is up, get its device + Device device = client.getDevice(); + + // look for application waiting for home + synchronized (sListLock) { + for (int i = 0 ; i < mWaitingForReadyEmulatorList.size() ;) { + DelayedLaunchInfo launchInfo = mWaitingForReadyEmulatorList.get(i); + if (launchInfo.mDevice == device) { + // it's match, remove from the list + mWaitingForReadyEmulatorList.remove(i); + + // We couldn't check earlier the API level of the device + // (it's asynchronous when the device boot, and usually + // deviceConnected is called before it's queried for its build info) + // so we check now + if (checkBuildInfo(launchInfo, device) == false) { + // device is not the proper API! + AdtPlugin.printErrorToConsole(launchInfo.mProject, + "Launch canceled!"); + launchInfo.mLaunch.stopLaunch(); + return; + } + + AdtPlugin.printToConsole(launchInfo.mProject, + String.format("HOME is up on device '%1$s'", + device.getSerialNumber())); + + // attempt to sync the new package onto the device. + if (syncApp(launchInfo, device)) { + // application package is sync'ed, lets attempt to launch it. + launchApp(launchInfo, device); + } else { + // failure! Cancel and return + AdtPlugin.printErrorToConsole(launchInfo.mProject, + "Launch canceled!"); + launchInfo.mLaunch.stopLaunch(); + } + + break; + } else { + i++; + } + } + } + } + + // check if it's already waiting for a debugger, and if so we connect to it. + if (client.getClientData().getDebuggerConnectionStatus() == ClientData.DEBUGGER_WAITING) { + // search for this client in the list; + synchronized (sListLock) { + int index = mUnknownClientsWaitingForDebugger.indexOf(client); + if (index != -1) { + connectDebugger = true; + mUnknownClientsWaitingForDebugger.remove(client); + } + } + } + } + } + + // if it's not home, it could be an app that is now in debugger mode that we're waiting for + // lets check it + + if ((changeMask & Client.CHANGE_DEBUGGER_INTEREST) == Client.CHANGE_DEBUGGER_INTEREST) { + ClientData clientData = client.getClientData(); + String applicationName = client.getClientData().getClientDescription(); + if (clientData.getDebuggerConnectionStatus() == ClientData.DEBUGGER_WAITING) { + // Get the application name, and make sure its valid. + if (applicationName == null) { + // looks like we don't have the client yet, so we keep it around for when its + // name becomes available. + synchronized (sListLock) { + mUnknownClientsWaitingForDebugger.add(client); + } + return; + } else { + connectDebugger = true; + } + } + } + + if (connectDebugger) { + Log.d("adt", "Debugging " + client); + // now check it against the apps waiting for a debugger + String applicationName = client.getClientData().getClientDescription(); + Log.d("adt", "App Name: " + applicationName); + synchronized (sListLock) { + for (int i = 0 ; i < mWaitingForDebuggerApplications.size() ;) { + final DelayedLaunchInfo launchInfo = mWaitingForDebuggerApplications.get(i); + if (client.getDevice() == launchInfo.mDevice && + applicationName.equals(launchInfo.mPackageName)) { + // this is a match. We remove the launch info from the list + mWaitingForDebuggerApplications.remove(i); + + // and connect the debugger. + String msg = String.format( + "Attempting to connect debugger to '%1$s' on port %2$d", + launchInfo.mPackageName, client.getDebuggerListenPort()); + AdtPlugin.printToConsole(launchInfo.mProject, msg); + + new Thread("Debugger Connection") { //$NON-NLS-1$ + @Override + public void run() { + try { + if (connectRemoteDebugger( + client.getDebuggerListenPort(), + launchInfo.mLaunch, launchInfo.mMonitor) == false) { + return; + } + } catch (CoreException e) { + // well something went wrong. + AdtPlugin.printErrorToConsole(launchInfo.mProject, + String.format("Launch error: %s", e.getMessage())); + // stop the launch + launchInfo.mLaunch.stopLaunch(); + } + + launchInfo.mMonitor.done(); + } + }.start(); + + // we're done processing this client. + return; + + } else { + i++; + } + } + } + + // if we get here, we haven't found an app that we were launching, so we look + // for opened android projects that contains the app asking for a debugger. + // If we find one, we automatically connect to it. + IProject project = ProjectHelper.findAndroidProjectByAppName(applicationName); + + if (project != null) { + debugRunningApp(project, client.getDebuggerListenPort()); + } + } + } + + /** + * Get the stderr/stdout outputs of a process and return when the process is done. + * Both must be read or the process will block on windows. + * @param process The process to get the ouput from + */ + private void grabEmulatorOutput(final Process process) { + // read the lines as they come. if null is returned, it's + // because the process finished + new Thread("") { //$NON-NLS-1$ + @Override + public void run() { + // create a buffer to read the stderr output + InputStreamReader is = new InputStreamReader(process.getErrorStream()); + BufferedReader errReader = new BufferedReader(is); + + try { + while (true) { + String line = errReader.readLine(); + if (line != null) { + AdtPlugin.printErrorToConsole("Emulator", line); + } else { + break; + } + } + } catch (IOException e) { + // do nothing. + } + } + }.start(); + + new Thread("") { //$NON-NLS-1$ + @Override + public void run() { + InputStreamReader is = new InputStreamReader(process.getInputStream()); + BufferedReader outReader = new BufferedReader(is); + + try { + while (true) { + String line = outReader.readLine(); + if (line != null) { + AdtPlugin.printToConsole("Emulator", line); + } else { + break; + } + } + } catch (IOException e) { + // do nothing. + } + } + }.start(); + } + +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/DeviceChooserDialog.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/DeviceChooserDialog.java new file mode 100644 index 0000000..275addf --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/DeviceChooserDialog.java @@ -0,0 +1,705 @@ +/* + * Copyright (C) 2007 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.launch; + +import com.android.ddmlib.AndroidDebugBridge; +import com.android.ddmlib.Client; +import com.android.ddmlib.Device; +import com.android.ddmlib.IDevice; +import com.android.ddmlib.AndroidDebugBridge.IDeviceChangeListener; +import com.android.ddmlib.Device.DeviceState; +import com.android.ddmuilib.IImageLoader; +import com.android.ddmuilib.ImageHelper; +import com.android.ddmuilib.TableHelper; +import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.ide.eclipse.adt.sdk.Sdk; +import com.android.ide.eclipse.ddms.DdmsPlugin; +import com.android.sdklib.IAndroidTarget; +import com.android.sdklib.avd.AvdManager; +import com.android.sdklib.avd.AvdManager.AvdInfo; +import com.android.sdkuilib.AvdSelector; + +import org.eclipse.jface.dialogs.Dialog; +import org.eclipse.jface.dialogs.IDialogConstants; +import org.eclipse.jface.preference.IPreferenceStore; +import org.eclipse.jface.viewers.DoubleClickEvent; +import org.eclipse.jface.viewers.IDoubleClickListener; +import org.eclipse.jface.viewers.ILabelProviderListener; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.IStructuredContentProvider; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.jface.viewers.ITableLabelProvider; +import org.eclipse.jface.viewers.StructuredSelection; +import org.eclipse.jface.viewers.TableViewer; +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.swt.SWT; +import org.eclipse.swt.SWTException; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Table; + +import java.util.ArrayList; + +public class DeviceChooserDialog extends Dialog implements IDeviceChangeListener { + + private final static int ICON_WIDTH = 16; + + private final static String PREFS_COL_SERIAL = "deviceChooser.serial"; //$NON-NLS-1$ + private final static String PREFS_COL_STATE = "deviceChooser.state"; //$NON-NLS-1$ + private final static String PREFS_COL_AVD = "deviceChooser.avd"; //$NON-NLS-1$ + private final static String PREFS_COL_TARGET = "deviceChooser.target"; //$NON-NLS-1$ + private final static String PREFS_COL_DEBUG = "deviceChooser.debug"; //$NON-NLS-1$ + + private Table mDeviceTable; + private TableViewer mViewer; + private AvdSelector mPreferredAvdSelector; + + private Image mDeviceImage; + private Image mEmulatorImage; + private Image mMatchImage; + private Image mNoMatchImage; + private Image mWarningImage; + + private final DeviceChooserResponse mResponse; + private final String mPackageName; + private final IAndroidTarget mProjectTarget; + private final Sdk mSdk; + + private final AvdInfo[] mFullAvdList; + + private Button mDeviceRadioButton; + + private boolean mDisableAvdSelectionChange = false; + + /** + * Basic Content Provider for a table full of {@link Device} objects. The input is + * a {@link AndroidDebugBridge}. + */ + private class ContentProvider implements IStructuredContentProvider { + public Object[] getElements(Object inputElement) { + if (inputElement instanceof AndroidDebugBridge) { + return ((AndroidDebugBridge)inputElement).getDevices(); + } + + return new Object[0]; + } + + public void dispose() { + // pass + } + + public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { + // pass + } + } + + + /** + * A Label Provider for the {@link TableViewer} in {@link DeviceChooserDialog}. + * It provides labels and images for {@link Device} objects. + */ + private class LabelProvider implements ITableLabelProvider { + + public Image getColumnImage(Object element, int columnIndex) { + if (element instanceof Device) { + Device device = (Device)element; + switch (columnIndex) { + case 0: + return device.isEmulator() ? mEmulatorImage : mDeviceImage; + + case 2: + // check for compatibility. + if (device.isEmulator() == false) { // physical device + // get the api level of the device + try { + String apiValue = device.getProperty( + IDevice.PROP_BUILD_VERSION_NUMBER); + if (apiValue != null) { + int api = Integer.parseInt(apiValue); + if (api >= mProjectTarget.getApiVersionNumber()) { + // if the project is compiling against an add-on, the optional + // API may be missing from the device. + return mProjectTarget.isPlatform() ? + mMatchImage : mWarningImage; + } else { + return mNoMatchImage; + } + } else { + return mWarningImage; + } + } catch (NumberFormatException e) { + // lets consider the device non compatible + return mNoMatchImage; + } + } else { + // get the AvdInfo + AvdInfo info = mSdk.getAvdManager().getAvd(device.getAvdName()); + if (info == null) { + return mWarningImage; + } + return mProjectTarget.isCompatibleBaseFor(info.getTarget()) ? + mMatchImage : mNoMatchImage; + } + } + } + + return null; + } + + public String getColumnText(Object element, int columnIndex) { + if (element instanceof Device) { + Device device = (Device)element; + switch (columnIndex) { + case 0: + return device.getSerialNumber(); + case 1: + if (device.isEmulator()) { + return device.getAvdName(); + } else { + return "N/A"; // devices don't have AVD names. + } + case 2: + if (device.isEmulator()) { + AvdInfo info = mSdk.getAvdManager().getAvd(device.getAvdName()); + if (info == null) { + return "?"; + } + return info.getTarget().getFullName(); + } else { + String deviceBuild = device.getProperty(IDevice.PROP_BUILD_VERSION); + if (deviceBuild == null) { + return "unknown"; + } + return deviceBuild; + } + case 3: + String debuggable = device.getProperty(IDevice.PROP_DEBUGGABLE); + if (debuggable != null && debuggable.equals("1")) { //$NON-NLS-1$ + return "Yes"; + } else { + return ""; + } + case 4: + return getStateString(device); + } + } + + return null; + } + + public void addListener(ILabelProviderListener listener) { + // pass + } + + public void dispose() { + // pass + } + + public boolean isLabelProperty(Object element, String property) { + // pass + return false; + } + + public void removeListener(ILabelProviderListener listener) { + // pass + } + } + + public static class DeviceChooserResponse { + private AvdInfo mAvdToLaunch; + private Device mDeviceToUse; + + public void setDeviceToUse(Device d) { + mDeviceToUse = d; + mAvdToLaunch = null; + } + + public void setAvdToLaunch(AvdInfo avd) { + mAvdToLaunch = avd; + mDeviceToUse = null; + } + + public Device getDeviceToUse() { + return mDeviceToUse; + } + + public AvdInfo getAvdToLaunch() { + return mAvdToLaunch; + } + } + + public DeviceChooserDialog(Shell parent, DeviceChooserResponse response, String packageName, + IAndroidTarget projectTarget) { + super(parent); + mResponse = response; + mPackageName = packageName; + mProjectTarget = projectTarget; + mSdk = Sdk.getCurrent(); + + // get the full list of Android Virtual Devices + AvdManager avdManager = mSdk.getAvdManager(); + if (avdManager != null) { + mFullAvdList = avdManager.getAvds(); + } else { + mFullAvdList = null; + } + + loadImages(); + } + + private void cleanup() { + // done listening. + AndroidDebugBridge.removeDeviceChangeListener(this); + + mEmulatorImage.dispose(); + mDeviceImage.dispose(); + mMatchImage.dispose(); + mNoMatchImage.dispose(); + mWarningImage.dispose(); + } + + @Override + protected void okPressed() { + cleanup(); + super.okPressed(); + } + + @Override + protected void cancelPressed() { + cleanup(); + super.cancelPressed(); + } + + @Override + protected Control createContents(Composite parent) { + Control content = super.createContents(parent); + + // this must be called after createContents() has happened so that the + // ok button has been created (it's created after the call to createDialogArea) + updateDefaultSelection(); + + return content; + } + + + @Override + protected Control createDialogArea(Composite parent) { + Composite top = new Composite(parent, SWT.NONE); + top.setLayout(new GridLayout(1, true)); + + mDeviceRadioButton = new Button(top, SWT.RADIO); + mDeviceRadioButton.setText("Choose a running Android device"); + mDeviceRadioButton.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + boolean deviceMode = mDeviceRadioButton.getSelection(); + + mDeviceTable.setEnabled(deviceMode); + mPreferredAvdSelector.setEnabled(!deviceMode); + + if (deviceMode) { + handleDeviceSelection(); + } else { + mResponse.setAvdToLaunch(mPreferredAvdSelector.getFirstSelected()); + } + + enableOkButton(); + } + }); + mDeviceRadioButton.setSelection(true); + + + // offset the selector from the radio button + Composite offsetComp = new Composite(top, SWT.NONE); + offsetComp.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + GridLayout layout = new GridLayout(1, false); + layout.marginRight = layout.marginHeight = 0; + layout.marginLeft = 30; + offsetComp.setLayout(layout); + + IPreferenceStore store = AdtPlugin.getDefault().getPreferenceStore(); + mDeviceTable = new Table(offsetComp, SWT.SINGLE | SWT.FULL_SELECTION); + GridData gd; + mDeviceTable.setLayoutData(gd = new GridData(GridData.FILL_BOTH)); + gd.heightHint = 100; + + mDeviceTable.setHeaderVisible(true); + mDeviceTable.setLinesVisible(true); + + TableHelper.createTableColumn(mDeviceTable, "Serial Number", + SWT.LEFT, "AAA+AAAAAAAAAAAAAAAAAAA", //$NON-NLS-1$ + PREFS_COL_SERIAL, store); + + TableHelper.createTableColumn(mDeviceTable, "AVD Name", + SWT.LEFT, "engineering", //$NON-NLS-1$ + PREFS_COL_AVD, store); + + TableHelper.createTableColumn(mDeviceTable, "Target", + SWT.LEFT, "AAA+Android 9.9.9", //$NON-NLS-1$ + PREFS_COL_TARGET, store); + + TableHelper.createTableColumn(mDeviceTable, "Debug", + SWT.LEFT, "Debug", //$NON-NLS-1$ + PREFS_COL_DEBUG, store); + + TableHelper.createTableColumn(mDeviceTable, "State", + SWT.LEFT, "bootloader", //$NON-NLS-1$ + PREFS_COL_STATE, store); + + // create the viewer for it + mViewer = new TableViewer(mDeviceTable); + mViewer.setContentProvider(new ContentProvider()); + mViewer.setLabelProvider(new LabelProvider()); + mViewer.setInput(AndroidDebugBridge.getBridge()); + mViewer.addDoubleClickListener(new IDoubleClickListener() { + public void doubleClick(DoubleClickEvent event) { + ISelection selection = event.getSelection(); + if (selection instanceof IStructuredSelection) { + IStructuredSelection structuredSelection = (IStructuredSelection)selection; + Object object = structuredSelection.getFirstElement(); + if (object instanceof Device) { + mResponse.setDeviceToUse((Device)object); + } + } + } + }); + + Button radio2 = new Button(top, SWT.RADIO); + radio2.setText("Launch a new Android Virtual Device"); + + // offset the selector from the radio button + offsetComp = new Composite(top, SWT.NONE); + offsetComp.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + layout = new GridLayout(1, false); + layout.marginRight = layout.marginHeight = 0; + layout.marginLeft = 30; + offsetComp.setLayout(layout); + + mPreferredAvdSelector = new AvdSelector(offsetComp, getNonRunningAvds(), mProjectTarget, + false /*allowMultipleSelection*/); + mPreferredAvdSelector.setTableHeightHint(100); + mPreferredAvdSelector.setEnabled(false); + mDeviceTable.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + handleDeviceSelection(); + } + }); + + mPreferredAvdSelector.setSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + if (mDisableAvdSelectionChange == false) { + mResponse.setAvdToLaunch(mPreferredAvdSelector.getFirstSelected()); + enableOkButton(); + } + } + }); + + AndroidDebugBridge.addDeviceChangeListener(this); + + return top; + } + + private void loadImages() { + IImageLoader ddmsLoader = DdmsPlugin.getImageLoader(); + Display display = DdmsPlugin.getDisplay(); + IImageLoader adtLoader = AdtPlugin.getImageLoader(); + + if (mDeviceImage == null) { + mDeviceImage = ImageHelper.loadImage(ddmsLoader, display, + "device.png", //$NON-NLS-1$ + ICON_WIDTH, ICON_WIDTH, + display.getSystemColor(SWT.COLOR_RED)); + } + if (mEmulatorImage == null) { + mEmulatorImage = ImageHelper.loadImage(ddmsLoader, display, + "emulator.png", ICON_WIDTH, ICON_WIDTH, //$NON-NLS-1$ + display.getSystemColor(SWT.COLOR_BLUE)); + } + + if (mMatchImage == null) { + mMatchImage = ImageHelper.loadImage(adtLoader, display, + "match.png", //$NON-NLS-1$ + ICON_WIDTH, ICON_WIDTH, + display.getSystemColor(SWT.COLOR_GREEN)); + } + + if (mNoMatchImage == null) { + mNoMatchImage = ImageHelper.loadImage(adtLoader, display, + "error.png", //$NON-NLS-1$ + ICON_WIDTH, ICON_WIDTH, + display.getSystemColor(SWT.COLOR_RED)); + } + + if (mWarningImage == null) { + mWarningImage = ImageHelper.loadImage(adtLoader, display, + "warning.png", //$NON-NLS-1$ + ICON_WIDTH, ICON_WIDTH, + display.getSystemColor(SWT.COLOR_YELLOW)); + } + + } + + /** + * Returns a display string representing the state of the device. + * @param d the device + */ + private static String getStateString(Device d) { + DeviceState deviceState = d.getState(); + if (deviceState == DeviceState.ONLINE) { + return "Online"; + } else if (deviceState == DeviceState.OFFLINE) { + return "Offline"; + } else if (deviceState == DeviceState.BOOTLOADER) { + return "Bootloader"; + } + + return "??"; + } + + /** + * Sent when the a device is connected to the {@link AndroidDebugBridge}. + *

            + * This is sent from a non UI thread. + * @param device the new device. + * + * @see IDeviceChangeListener#deviceConnected(Device) + */ + public void deviceConnected(Device device) { + final DeviceChooserDialog dialog = this; + exec(new Runnable() { + public void run() { + if (mDeviceTable.isDisposed() == false) { + // refresh all + mViewer.refresh(); + + // update the selection + updateDefaultSelection(); + + // update the display of AvdInfo (since it's filtered to only display + // non running AVD.) + refillAvdList(); + } else { + // table is disposed, we need to do something. + // lets remove ourselves from the listener. + AndroidDebugBridge.removeDeviceChangeListener(dialog); + } + + } + }); + } + + /** + * Sent when the a device is connected to the {@link AndroidDebugBridge}. + *

            + * This is sent from a non UI thread. + * @param device the new device. + * + * @see IDeviceChangeListener#deviceDisconnected(Device) + */ + public void deviceDisconnected(Device device) { + deviceConnected(device); + } + + /** + * Sent when a device data changed, or when clients are started/terminated on the device. + *

            + * This is sent from a non UI thread. + * @param device the device that was updated. + * @param changeMask the mask indicating what changed. + * + * @see IDeviceChangeListener#deviceChanged(Device, int) + */ + public void deviceChanged(final Device device, int changeMask) { + if ((changeMask & (Device.CHANGE_STATE | Device.CHANGE_BUILD_INFO)) != 0) { + final DeviceChooserDialog dialog = this; + exec(new Runnable() { + public void run() { + if (mDeviceTable.isDisposed() == false) { + // refresh the device + mViewer.refresh(device); + + // update the defaultSelection. + updateDefaultSelection(); + + // update the display of AvdInfo (since it's filtered to only display + // non running AVD). This is done on deviceChanged because the avd name + // of a (emulator) device may be updated as the emulator boots. + refillAvdList(); + + // if the changed device is the current selection, + // we update the OK button based on its state. + if (device == mResponse.getDeviceToUse()) { + enableOkButton(); + } + + } else { + // table is disposed, we need to do something. + // lets remove ourselves from the listener. + AndroidDebugBridge.removeDeviceChangeListener(dialog); + } + } + }); + } + } + + /** + * Returns whether the dialog is in "device" mode (true), or in "avd" mode (false). + */ + private boolean isDeviceMode() { + return mDeviceRadioButton.getSelection(); + } + + /** + * Enables or disables the OK button of the dialog based on various selections in the dialog. + */ + private void enableOkButton() { + Button okButton = getButton(IDialogConstants.OK_ID); + + if (isDeviceMode()) { + okButton.setEnabled(mResponse.getDeviceToUse() != null && + mResponse.getDeviceToUse().isOnline()); + } else { + okButton.setEnabled(mResponse.getAvdToLaunch() != null); + } + } + + /** + * Executes the {@link Runnable} in the UI thread. + * @param runnable the runnable to execute. + */ + private void exec(Runnable runnable) { + try { + Display display = mDeviceTable.getDisplay(); + display.asyncExec(runnable); + } catch (SWTException e) { + // tree is disposed, we need to do something. lets remove ourselves from the listener. + AndroidDebugBridge.removeDeviceChangeListener(this); + } + } + + private void handleDeviceSelection() { + int count = mDeviceTable.getSelectionCount(); + if (count != 1) { + handleSelection(null); + } else { + int index = mDeviceTable.getSelectionIndex(); + Object data = mViewer.getElementAt(index); + if (data instanceof Device) { + handleSelection((Device)data); + } else { + handleSelection(null); + } + } + } + + private void handleSelection(Device device) { + mResponse.setDeviceToUse(device); + enableOkButton(); + } + + /** + * Look for a default device to select. This is done by looking for the running + * clients on each device and finding one similar to the one being launched. + *

            + * This is done every time the device list changed unless there is a already selection. + */ + private void updateDefaultSelection() { + if (mDeviceTable.getSelectionCount() == 0) { + AndroidDebugBridge bridge = AndroidDebugBridge.getBridge(); + + Device[] devices = bridge.getDevices(); + + for (Device device : devices) { + Client[] clients = device.getClients(); + + for (Client client : clients) { + + if (mPackageName.equals(client.getClientData().getClientDescription())) { + // found a match! Select it. + mViewer.setSelection(new StructuredSelection(device)); + handleSelection(device); + + // and we're done. + return; + } + } + } + } + + handleDeviceSelection(); + } + + /** + * Returns the list of {@link AvdInfo} that are not already running in an emulator. + */ + private AvdInfo[] getNonRunningAvds() { + ArrayList list = new ArrayList(); + + Device[] devices = AndroidDebugBridge.getBridge().getDevices(); + + // loop through all the Avd and put the one that are not running in the list. + avdLoop: for (AvdInfo info : mFullAvdList) { + for (Device d : devices) { + if (info.getName().equals(d.getAvdName())) { + continue avdLoop; + } + } + list.add(info); + } + + return list.toArray(new AvdInfo[list.size()]); + } + + /** + * Refills the AVD list keeping the current selection. + */ + private void refillAvdList() { + AvdInfo[] array = getNonRunningAvds(); + + // save the current selection + AvdInfo selected = mPreferredAvdSelector.getFirstSelected(); + + // disable selection change. + mDisableAvdSelectionChange = true; + + // set the new list in the selector + mPreferredAvdSelector.setAvds(array, mProjectTarget); + + // attempt to reselect the proper avd if needed + if (selected != null) { + if (mPreferredAvdSelector.setSelection(selected) == false) { + // looks like the selection is lost. this can happen if an emulator + // running the AVD that was selected was launched from outside of Eclipse). + mResponse.setAvdToLaunch(null); + enableOkButton(); + } + } + + // enable the selection change + mDisableAvdSelectionChange = false; + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/EmulatorConfigTab.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/EmulatorConfigTab.java new file mode 100644 index 0000000..5b4cdbb --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/EmulatorConfigTab.java @@ -0,0 +1,458 @@ +/* + * Copyright (C) 2007 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.launch; + +import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.ide.eclipse.adt.sdk.Sdk; +import com.android.ide.eclipse.common.project.BaseProjectHelper; +import com.android.ide.eclipse.ddms.DdmsPlugin; +import com.android.sdklib.IAndroidTarget; +import com.android.sdklib.avd.AvdManager; +import com.android.sdklib.avd.AvdManager.AvdInfo; +import com.android.sdkuilib.AvdSelector; + +import org.eclipse.core.resources.IProject; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.debug.core.ILaunchConfiguration; +import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy; +import org.eclipse.debug.ui.AbstractLaunchConfigurationTab; +import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jdt.launching.IJavaLaunchConfigurationConstants; +import org.eclipse.jface.preference.IPreferenceStore; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.ModifyEvent; +import org.eclipse.swt.events.ModifyListener; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.graphics.Font; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Combo; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Group; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Text; + +/** + * Launch configuration tab to control the parameters of the Emulator + */ +public class EmulatorConfigTab extends AbstractLaunchConfigurationTab { + + private final static String[][] NETWORK_SPEEDS = new String[][] { + { "Full", "full" }, //$NON-NLS-2$ + { "GSM", "gsm" }, //$NON-NLS-2$ + { "HSCSD", "hscsd" }, //$NON-NLS-2$ + { "GPRS", "gprs" }, //$NON-NLS-2$ + { "EDGE", "edge" }, //$NON-NLS-2$ + { "UMTS", "umts" }, //$NON-NLS-2$ + { "HSPDA", "hsdpa" }, //$NON-NLS-2$ + }; + + private final static String[][] NETWORK_LATENCIES = new String[][] { + { "None", "none" }, //$NON-NLS-2$ + { "GPRS", "gprs" }, //$NON-NLS-2$ + { "EDGE", "edge" }, //$NON-NLS-2$ + { "UMTS", "umts" }, //$NON-NLS-2$ + }; + + private Button mAutoTargetButton; + private Button mManualTargetButton; + + private AvdSelector mPreferredAvdSelector; + + private Combo mSpeedCombo; + + private Combo mDelayCombo; + + private Group mEmulatorOptionsGroup; + + private Text mEmulatorCLOptions; + + private Button mWipeDataButton; + + private Button mNoBootAnimButton; + + private Label mPreferredAvdLabel; + + /** + * Returns the emulator ready speed option value. + * @param value The index of the combo selection. + */ + public static String getSpeed(int value) { + try { + return NETWORK_SPEEDS[value][1]; + } catch (ArrayIndexOutOfBoundsException e) { + return NETWORK_SPEEDS[LaunchConfigDelegate.DEFAULT_SPEED][1]; + } + } + + /** + * Returns the emulator ready network latency value. + * @param value The index of the combo selection. + */ + public static String getDelay(int value) { + try { + return NETWORK_LATENCIES[value][1]; + } catch (ArrayIndexOutOfBoundsException e) { + return NETWORK_LATENCIES[LaunchConfigDelegate.DEFAULT_DELAY][1]; + } + } + + /** + * + */ + public EmulatorConfigTab() { + } + + /* (non-Javadoc) + * @see org.eclipse.debug.ui.ILaunchConfigurationTab#createControl(org.eclipse.swt.widgets.Composite) + */ + public void createControl(Composite parent) { + Font font = parent.getFont(); + + Composite topComp = new Composite(parent, SWT.NONE); + setControl(topComp); + GridLayout topLayout = new GridLayout(); + topLayout.numColumns = 1; + topLayout.verticalSpacing = 0; + topComp.setLayout(topLayout); + topComp.setFont(font); + + GridData gd; + GridLayout layout; + + // radio button for the target mode + Group targetModeGroup = new Group(topComp, SWT.NONE); + targetModeGroup.setText("Device Target Selection Mode"); + gd = new GridData(GridData.FILL_HORIZONTAL); + targetModeGroup.setLayoutData(gd); + layout = new GridLayout(); + layout.numColumns = 1; + targetModeGroup.setLayout(layout); + targetModeGroup.setFont(font); + + mManualTargetButton = new Button(targetModeGroup, SWT.RADIO); + mManualTargetButton.setText("Manual"); + // Since there are only 2 radio buttons, we can put a listener on only one (they + // are both called on select and unselect event. + + // add the radio button + mAutoTargetButton = new Button(targetModeGroup, SWT.RADIO); + mAutoTargetButton.setText("Automatic"); + mAutoTargetButton.setSelection(true); + mAutoTargetButton.addSelectionListener(new SelectionAdapter() { + // called when selection changes + @Override + public void widgetSelected(SelectionEvent e) { + updateLaunchConfigurationDialog(); + + boolean auto = mAutoTargetButton.getSelection(); + mPreferredAvdSelector.setEnabled(auto); + mPreferredAvdLabel.setEnabled(auto); + } + }); + + Composite offsetComp = new Composite(targetModeGroup, SWT.NONE); + offsetComp.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + layout = new GridLayout(1, false); + layout.marginRight = layout.marginHeight = 0; + layout.marginLeft = 30; + offsetComp.setLayout(layout); + + mPreferredAvdLabel = new Label(offsetComp, SWT.NONE); + mPreferredAvdLabel.setText("Select a preferred Android Virtual Device:"); + AvdInfo[] avds = new AvdInfo[0]; + mPreferredAvdSelector = new AvdSelector(offsetComp, avds, + false /*allowMultipleSelection*/); + mPreferredAvdSelector.setTableHeightHint(100); + mPreferredAvdSelector.setSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + updateLaunchConfigurationDialog(); + } + }); + + // emulator size + mEmulatorOptionsGroup = new Group(topComp, SWT.NONE); + mEmulatorOptionsGroup.setText("Emulator launch parameters:"); + mEmulatorOptionsGroup.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + layout = new GridLayout(); + layout.numColumns = 2; + mEmulatorOptionsGroup.setLayout(layout); + mEmulatorOptionsGroup.setFont(font); + + // network options + new Label(mEmulatorOptionsGroup, SWT.NONE).setText("Network Speed:"); + + mSpeedCombo = new Combo(mEmulatorOptionsGroup, SWT.READ_ONLY); + for (String[] speed : NETWORK_SPEEDS) { + mSpeedCombo.add(speed[0]); + } + mSpeedCombo.addSelectionListener(new SelectionAdapter() { + // called when selection changes + @Override + public void widgetSelected(SelectionEvent e) { + updateLaunchConfigurationDialog(); + } + }); + mSpeedCombo.pack(); + + new Label(mEmulatorOptionsGroup, SWT.NONE).setText("Network Latency:"); + + mDelayCombo = new Combo(mEmulatorOptionsGroup, SWT.READ_ONLY); + + for (String[] delay : NETWORK_LATENCIES) { + mDelayCombo.add(delay[0]); + } + mDelayCombo.addSelectionListener(new SelectionAdapter() { + // called when selection changes + @Override + public void widgetSelected(SelectionEvent e) { + updateLaunchConfigurationDialog(); + } + }); + mDelayCombo.pack(); + + // wipe data option + mWipeDataButton = new Button(mEmulatorOptionsGroup, SWT.CHECK); + mWipeDataButton.setText("Wipe User Data"); + mWipeDataButton.setToolTipText("Check this if you want to wipe your user data each time you start the emulator. You will be prompted for confirmation when the emulator starts."); + gd = new GridData(GridData.FILL_HORIZONTAL); + gd.horizontalSpan = 2; + mWipeDataButton.setLayoutData(gd); + mWipeDataButton.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + updateLaunchConfigurationDialog(); + } + }); + + // no boot anim option + mNoBootAnimButton = new Button(mEmulatorOptionsGroup, SWT.CHECK); + mNoBootAnimButton.setText("Disable Boot Animation"); + mNoBootAnimButton.setToolTipText("Check this if you want to disable the boot animation. This can help the emulator start faster on slow machines."); + gd = new GridData(GridData.FILL_HORIZONTAL); + gd.horizontalSpan = 2; + mNoBootAnimButton.setLayoutData(gd); + mNoBootAnimButton.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + updateLaunchConfigurationDialog(); + } + }); + + // custom command line option for emulator + Label l = new Label(mEmulatorOptionsGroup, SWT.NONE); + l.setText("Additional Emulator Command Line Options"); + gd = new GridData(GridData.FILL_HORIZONTAL); + gd.horizontalSpan = 2; + l.setLayoutData(gd); + + mEmulatorCLOptions = new Text(mEmulatorOptionsGroup, SWT.BORDER); + gd = new GridData(GridData.FILL_HORIZONTAL); + gd.horizontalSpan = 2; + mEmulatorCLOptions.setLayoutData(gd); + mEmulatorCLOptions.addModifyListener(new ModifyListener() { + public void modifyText(ModifyEvent e) { + updateLaunchConfigurationDialog(); + } + }); + } + + /* (non-Javadoc) + * @see org.eclipse.debug.ui.ILaunchConfigurationTab#getName() + */ + public String getName() { + return "Target"; + } + + @Override + public Image getImage() { + return DdmsPlugin.getImageLoader().loadImage("emulator.png", null); //$NON-NLS-1$ + } + + /* (non-Javadoc) + * @see org.eclipse.debug.ui.ILaunchConfigurationTab#initializeFrom(org.eclipse.debug.core.ILaunchConfiguration) + */ + public void initializeFrom(ILaunchConfiguration configuration) { + AvdManager avdManager = Sdk.getCurrent().getAvdManager(); + + boolean value = LaunchConfigDelegate.DEFAULT_TARGET_MODE; // true == automatic + try { + value = configuration.getAttribute(LaunchConfigDelegate.ATTR_TARGET_MODE, value); + } catch (CoreException e) { + // let's not do anything here, we'll use the default value + } + mAutoTargetButton.setSelection(value); + mManualTargetButton.setSelection(!value); + + // look for the project name to get its target. + String stringValue = ""; + try { + stringValue = configuration.getAttribute( + IJavaLaunchConfigurationConstants.ATTR_PROJECT_NAME, stringValue); + } catch (CoreException ce) { + // let's not do anything here, we'll use the default value + } + + IProject project = null; + + // get the list of existing Android projects from the workspace. + IJavaProject[] projects = BaseProjectHelper.getAndroidProjects(); + if (projects != null) { + // look for the project whose name we read from the configuration. + for (IJavaProject p : projects) { + if (p.getElementName().equals(stringValue)) { + project = p.getProject(); + break; + } + } + } + + // update the AVD list + AvdInfo[] avds = null; + if (avdManager != null) { + avds = avdManager.getAvds(); + } + + IAndroidTarget projectTarget = null; + if (project != null) { + projectTarget = Sdk.getCurrent().getTarget(project); + } else { + avds = null; // no project? we don't want to display any "compatible" AVDs. + } + + mPreferredAvdSelector.setAvds(avds, projectTarget); + + stringValue = ""; + try { + stringValue = configuration.getAttribute(LaunchConfigDelegate.ATTR_AVD_NAME, + stringValue); + } catch (CoreException e) { + // let's not do anything here, we'll use the default value + } + + if (stringValue != null && stringValue.length() > 0 && avdManager != null) { + AvdInfo targetAvd = avdManager.getAvd(stringValue); + mPreferredAvdSelector.setSelection(targetAvd); + } else { + mPreferredAvdSelector.setSelection(null); + } + + value = LaunchConfigDelegate.DEFAULT_WIPE_DATA; + try { + value = configuration.getAttribute(LaunchConfigDelegate.ATTR_WIPE_DATA, value); + } catch (CoreException e) { + // let's not do anything here, we'll use the default value + } + mWipeDataButton.setSelection(value); + + value = LaunchConfigDelegate.DEFAULT_NO_BOOT_ANIM; + try { + value = configuration.getAttribute(LaunchConfigDelegate.ATTR_NO_BOOT_ANIM, value); + } catch (CoreException e) { + // let's not do anything here, we'll use the default value + } + mNoBootAnimButton.setSelection(value); + + int index = -1; + + index = LaunchConfigDelegate.DEFAULT_SPEED; + try { + index = configuration.getAttribute(LaunchConfigDelegate.ATTR_SPEED, + index); + } catch (CoreException e) { + // let's not do anything here, we'll use the default value + } + if (index == -1) { + mSpeedCombo.clearSelection(); + } else { + mSpeedCombo.select(index); + } + + index = LaunchConfigDelegate.DEFAULT_DELAY; + try { + index = configuration.getAttribute(LaunchConfigDelegate.ATTR_DELAY, + index); + } catch (CoreException e) { + // let's not do anything here, we'll put a proper value in + // performApply anyway + } + if (index == -1) { + mDelayCombo.clearSelection(); + } else { + mDelayCombo.select(index); + } + + String commandLine = null; + try { + commandLine = configuration.getAttribute( + LaunchConfigDelegate.ATTR_COMMANDLINE, ""); //$NON-NLS-1$ + } catch (CoreException e) { + // let's not do anything here, we'll use the default value + } + if (commandLine != null) { + mEmulatorCLOptions.setText(commandLine); + } + } + + /* (non-Javadoc) + * @see org.eclipse.debug.ui.ILaunchConfigurationTab#performApply(org.eclipse.debug.core.ILaunchConfigurationWorkingCopy) + */ + public void performApply(ILaunchConfigurationWorkingCopy configuration) { + configuration.setAttribute(LaunchConfigDelegate.ATTR_TARGET_MODE, + mAutoTargetButton.getSelection()); + AvdInfo avd = mPreferredAvdSelector.getFirstSelected(); + if (avd != null) { + configuration.setAttribute(LaunchConfigDelegate.ATTR_AVD_NAME, avd.getName()); + } else { + configuration.setAttribute(LaunchConfigDelegate.ATTR_AVD_NAME, (String)null); + } + configuration.setAttribute(LaunchConfigDelegate.ATTR_SPEED, + mSpeedCombo.getSelectionIndex()); + configuration.setAttribute(LaunchConfigDelegate.ATTR_DELAY, + mDelayCombo.getSelectionIndex()); + configuration.setAttribute(LaunchConfigDelegate.ATTR_COMMANDLINE, + mEmulatorCLOptions.getText()); + configuration.setAttribute(LaunchConfigDelegate.ATTR_WIPE_DATA, + mWipeDataButton.getSelection()); + configuration.setAttribute(LaunchConfigDelegate.ATTR_NO_BOOT_ANIM, + mNoBootAnimButton.getSelection()); + } + + /* (non-Javadoc) + * @see org.eclipse.debug.ui.ILaunchConfigurationTab#setDefaults(org.eclipse.debug.core.ILaunchConfigurationWorkingCopy) + */ + public void setDefaults(ILaunchConfigurationWorkingCopy configuration) { + configuration.setAttribute(LaunchConfigDelegate.ATTR_TARGET_MODE, + LaunchConfigDelegate.DEFAULT_TARGET_MODE); + configuration.setAttribute(LaunchConfigDelegate.ATTR_SPEED, + LaunchConfigDelegate.DEFAULT_SPEED); + configuration.setAttribute(LaunchConfigDelegate.ATTR_DELAY, + LaunchConfigDelegate.DEFAULT_DELAY); + configuration.setAttribute(LaunchConfigDelegate.ATTR_WIPE_DATA, + LaunchConfigDelegate.DEFAULT_WIPE_DATA); + configuration.setAttribute(LaunchConfigDelegate.ATTR_NO_BOOT_ANIM, + LaunchConfigDelegate.DEFAULT_NO_BOOT_ANIM); + + IPreferenceStore store = AdtPlugin.getDefault().getPreferenceStore(); + String emuOptions = store.getString(AdtPlugin.PREFS_EMU_OPTIONS); + configuration.setAttribute(LaunchConfigDelegate.ATTR_COMMANDLINE, emuOptions); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/JUnitLaunchConfigDelegate.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/JUnitLaunchConfigDelegate.java new file mode 100644 index 0000000..7a74309 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/JUnitLaunchConfigDelegate.java @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2009 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.launch; + +import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.ide.eclipse.common.AndroidConstants; +import com.android.sdklib.SdkConstants; + +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.FileLocator; +import org.eclipse.core.runtime.Platform; +import org.eclipse.debug.core.ILaunchConfiguration; +import org.eclipse.jdt.junit.launcher.JUnitLaunchConfigurationDelegate; +import org.osgi.framework.Bundle; +import java.io.IOException; +import java.net.URL; + +/** + *

            + * For Android projects, android.jar gets added to the launch configuration of + * JUnit tests as a bootstrap entry. This breaks JUnit tests as android.jar + * contains a skeleton version of JUnit classes and the JVM will stop with an error similar + * to:

            Error occurred during initialization of VM + * java/lang/NoClassDefFoundError: java/lang/ref/FinalReference
            + *

            + * At compile time, Eclipse does not know that there is no valid junit.jar in + * the classpath since it can find a correct reference to all the necessary + * org.junit.* classes in the android.jar so it does not prompt the user to add + * the JUnit3 or JUnit4 jar. + *

            + * This delegates removes the android.jar from the bootstrap path and if + * necessary also puts back the junit.jar in the user classpath. + *

            + * This delegate will be present for both Java and Android projects (delegates + * setting instead of only the current project) but the behavior for Java + * projects should be neutral since: + *

              + *
            1. Java tests can only compile (and then run) when a valid junit.jar is + * present + *
            2. There is no android.jar in Java projects + *
            + */ +public class JUnitLaunchConfigDelegate extends JUnitLaunchConfigurationDelegate { + + private static final String JUNIT_JAR = "junit.jar"; //$NON-NLS-1$ + + @Override + public String[][] getBootpathExt(ILaunchConfiguration configuration) throws CoreException { + String[][] bootpath = super.getBootpathExt(configuration); + return fixBootpathExt(bootpath); + } + + @Override + public String[] getClasspath(ILaunchConfiguration configuration) throws CoreException { + String[] classpath = super.getClasspath(configuration); + return fixClasspath(classpath, getJavaProjectName(configuration)); + } + + /** + * Removes the android.jar from the bootstrap path if present. + * + * @param bootpath Array of Arrays of bootstrap class paths + * @return a new modified (if applicable) bootpath + */ + public static String[][] fixBootpathExt(String[][] bootpath) { + for (int i = 0; i < bootpath.length; i++) { + if (bootpath[i] != null) { + // we assume that the android.jar can only be present in the + // bootstrap path of android tests + if (bootpath[i][0].endsWith(SdkConstants.FN_FRAMEWORK_LIBRARY)) { + bootpath[i] = null; + } + } + } + return bootpath; + } + + /** + * Add the junit.jar to the user classpath; since Eclipse was relying on + * android.jar to provide the appropriate org.junit classes, it does not + * know it actually needs the junit.jar. + * + * @param classpath Array containing classpath + * @param projectName The name of the project (for logging purposes) + * + * @return a new modified (if applicable) classpath + */ + public static String[] fixClasspath(String[] classpath, String projectName) { + // search for junit.jar; if any are found return immediately + for (int i = 0; i < classpath.length; i++) { + if (classpath[i].endsWith(JUNIT_JAR)) { + return classpath; + } + } + + // This delegate being called without a junit.jar present is only + // possible for Android projects. In a non-Android project, the test + // would not compile and would be unable to run. + try { + // junit4 is backward compatible with junit3 and they uses the + // same junit.jar from bundle org.junit: + // When a project has mixed JUnit3 and JUnit4 tests, if JUnit3 jar + // is added first it is then replaced by the JUnit4 jar when user is + // prompted to fix the JUnit4 test failure + String jarLocation = getJunitJarLocation(); + // we extend the classpath by one element and append junit.jar + String[] newClasspath = new String[classpath.length + 1]; + System.arraycopy(classpath, 0, newClasspath, 0, classpath.length); + newClasspath[newClasspath.length - 1] = jarLocation; + classpath = newClasspath; + } catch (IOException e) { + // This should not happen as we depend on the org.junit + // plugin explicitly; the error is logged here so that the user can + // trace back the cause when the test fails to run + AdtPlugin.log(e, "Could not find a valid junit.jar"); + AdtPlugin.printErrorToConsole(projectName, + "Could not find a valid junit.jar"); + // Return the classpath as-is (with no junit.jar) anyway because we + // will let the actual launch config fails. + } + + return classpath; + } + + /** + * Returns the path of the junit jar in the highest version bundle. + * + * (This is public only so that the test can call it) + * + * @return the path as a string + * @throws IOException + */ + public static String getJunitJarLocation() throws IOException { + Bundle bundle = Platform.getBundle("org.junit"); //$NON-NLS-1$ + if (bundle == null) { + throw new IOException("Cannot find org.junit bundle"); + } + URL jarUrl = bundle.getEntry(AndroidConstants.WS_SEP + JUNIT_JAR); + return FileLocator.resolve(jarUrl).getFile(); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/LaunchConfigDelegate.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/LaunchConfigDelegate.java new file mode 100644 index 0000000..a46f56c --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/LaunchConfigDelegate.java @@ -0,0 +1,431 @@ +/* + * Copyright (C) 2007 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.launch; + +import com.android.ddmlib.AndroidDebugBridge; +import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.ide.eclipse.adt.launch.AndroidLaunchController.AndroidLaunchConfiguration; +import com.android.ide.eclipse.adt.project.ProjectHelper; +import com.android.ide.eclipse.common.AndroidConstants; +import com.android.ide.eclipse.common.project.AndroidManifestParser; +import com.android.ide.eclipse.common.project.BaseProjectHelper; + +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IFolder; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.IWorkspace; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.debug.core.ILaunch; +import org.eclipse.debug.core.ILaunchConfiguration; +import org.eclipse.debug.core.model.LaunchConfigurationDelegate; +import org.eclipse.jdt.core.JavaCore; +import org.eclipse.jdt.launching.IJavaLaunchConfigurationConstants; + +/** + * Implementation of an eclipse LauncConfigurationDelegate to launch android + * application in debug. + */ +public class LaunchConfigDelegate extends LaunchConfigurationDelegate { + final static int INVALID_DEBUG_PORT = -1; + + public final static String ANDROID_LAUNCH_TYPE_ID = + "com.android.ide.eclipse.adt.debug.LaunchConfigType"; //$NON-NLS-1$ + + /** Target mode parameters: true is automatic, false is manual */ + public static final String ATTR_TARGET_MODE = AdtPlugin.PLUGIN_ID + ".target"; //$NON-NLS-1$ + public static final boolean DEFAULT_TARGET_MODE = true; //automatic mode + + /** + * Launch action: + *
              + *
            • 0: launch default activity
            • + *
            • 1: launch specified activity. See {@link #ATTR_ACTIVITY}
            • + *
            • 2: Do Nothing
            • + *
            + */ + public final static String ATTR_LAUNCH_ACTION = AdtPlugin.PLUGIN_ID + ".action"; //$NON-NLS-1$ + + /** Default launch action. This launches the activity that is setup to be found in the HOME + * screen. + */ + public final static int ACTION_DEFAULT = 0; + /** Launch action starting a specific activity. */ + public final static int ACTION_ACTIVITY = 1; + /** Launch action that does nothing. */ + public final static int ACTION_DO_NOTHING = 2; + /** Default launch action value. */ + public final static int DEFAULT_LAUNCH_ACTION = ACTION_DEFAULT; + + /** + * Activity to be launched if {@link #ATTR_LAUNCH_ACTION} is 1 + */ + public static final String ATTR_ACTIVITY = AdtPlugin.PLUGIN_ID + ".activity"; //$NON-NLS-1$ + + public static final String ATTR_AVD_NAME = AdtPlugin.PLUGIN_ID + ".avd"; //$NON-NLS-1$ + + public static final String ATTR_SPEED = AdtPlugin.PLUGIN_ID + ".speed"; //$NON-NLS-1$ + + /** + * Index of the default network speed setting for the emulator.
            + * Get the emulator option with EmulatorConfigTab.getSpeed(index) + */ + public static final int DEFAULT_SPEED = 0; + + public static final String ATTR_DELAY = AdtPlugin.PLUGIN_ID + ".delay"; //$NON-NLS-1$ + + /** + * Index of the default network latency setting for the emulator.
            + * Get the emulator option with EmulatorConfigTab.getDelay(index) + */ + public static final int DEFAULT_DELAY = 0; + + public static final String ATTR_COMMANDLINE = AdtPlugin.PLUGIN_ID + ".commandline"; //$NON-NLS-1$ + + public static final String ATTR_WIPE_DATA = AdtPlugin.PLUGIN_ID + ".wipedata"; //$NON-NLS-1$ + public static final boolean DEFAULT_WIPE_DATA = false; + + public static final String ATTR_NO_BOOT_ANIM = AdtPlugin.PLUGIN_ID + ".nobootanim"; //$NON-NLS-1$ + public static final boolean DEFAULT_NO_BOOT_ANIM = false; + + public static final String ATTR_DEBUG_PORT = + AdtPlugin.PLUGIN_ID + ".debugPort"; //$NON-NLS-1$ + + public void launch(ILaunchConfiguration configuration, String mode, + ILaunch launch, IProgressMonitor monitor) throws CoreException { + // We need to check if it's a standard launch or if it's a launch + // to debug an application already running. + int debugPort = AndroidLaunchController.getPortForConfig(configuration); + + // get the project + IProject project = getProject(configuration); + + // first we make sure the launch is of the proper type + AndroidLaunch androidLaunch = null; + if (launch instanceof AndroidLaunch) { + androidLaunch = (AndroidLaunch)launch; + } else { + // wrong type, not sure how we got there, but we don't do + // anything else + AdtPlugin.printErrorToConsole(project, "Wrong Launch Type!"); + return; + } + + // if we have a valid debug port, this means we're debugging an app + // that's already launched. + if (debugPort != INVALID_DEBUG_PORT) { + AndroidLaunchController.launchRemoteDebugger(debugPort, androidLaunch, monitor); + return; + } + + if (project == null) { + AdtPlugin.printErrorToConsole("Couldn't get project object!"); + androidLaunch.stopLaunch(); + return; + } + + // check if the project has errors, and abort in this case. + if (ProjectHelper.hasError(project, true)) { + AdtPlugin.displayError("Android Launch", + "Your project contains error(s), please fix them before running your application."); + return; + } + + AdtPlugin.printToConsole(project, "------------------------------"); //$NON-NLS-1$ + AdtPlugin.printToConsole(project, "Android Launch!"); + + // check if the project is using the proper sdk. + // if that throws an exception, we simply let it propage to the caller. + if (checkAndroidProject(project) == false) { + AdtPlugin.printErrorToConsole(project, "Project is not an Android Project. Aborting!"); + androidLaunch.stopLaunch(); + return; + } + + // Check adb status and abort if needed. + AndroidDebugBridge bridge = AndroidDebugBridge.getBridge(); + if (bridge == null || bridge.isConnected() == false) { + try { + int connections = -1; + int restarts = -1; + if (bridge != null) { + connections = bridge.getConnectionAttemptCount(); + restarts = bridge.getRestartAttemptCount(); + } + + // if we get -1, the device monitor is not even setup (anymore?). + // We need to ask the user to restart eclipse. + // This shouldn't happen, but it's better to let the user know in case it does. + if (connections == -1 || restarts == -1) { + AdtPlugin.printErrorToConsole(project, + "The connection to adb is down, and a severe error has occured.", + "You must restart adb and Eclipse.", + String.format( + "Please ensure that adb is correctly located at '%1$s' and can be executed.", + AdtPlugin.getOsAbsoluteAdb())); + return; + } + + if (restarts == 0) { + AdtPlugin.printErrorToConsole(project, + "Connection with adb was interrupted.", + String.format("%1$s attempts have been made to reconnect.", connections), + "You may want to manually restart adb from the Devices view."); + } else { + AdtPlugin.printErrorToConsole(project, + "Connection with adb was interrupted, and attempts to reconnect have failed.", + String.format("%1$s attempts have been made to restart adb.", restarts), + "You may want to manually restart adb from the Devices view."); + + } + return; + } finally { + androidLaunch.stopLaunch(); + } + } + + // since adb is working, we let the user know + // TODO have a verbose mode for launch with more info (or some of the less useful info we now have). + AdtPlugin.printToConsole(project, "adb is running normally."); + + // make a config class + AndroidLaunchConfiguration config = new AndroidLaunchConfiguration(); + + // fill it with the config coming from the ILaunchConfiguration object + config.set(configuration); + + // get the launch controller singleton + AndroidLaunchController controller = AndroidLaunchController.getInstance(); + + // get the application package + IFile applicationPackage = getApplicationPackage(project); + if (applicationPackage == null) { + androidLaunch.stopLaunch(); + return; + } + + // we need some information from the manifest + AndroidManifestParser manifestParser = AndroidManifestParser.parse( + BaseProjectHelper.getJavaProject(project), null /* errorListener */, + true /* gatherData */, false /* markErrors */); + + if (manifestParser == null) { + AdtPlugin.printErrorToConsole(project, "Failed to parse AndroidManifest: aborting!"); + androidLaunch.stopLaunch(); + return; + } + + String activityName = null; + + if (config.mLaunchAction == ACTION_ACTIVITY) { + // Get the activity name defined in the config + activityName = getActivityName(configuration); + + // Get the full activity list and make sure the one we got matches. + String[] activities = manifestParser.getActivities(); + + // first we check that there are, in fact, activities. + if (activities.length == 0) { + // if the activities list is null, then the manifest is empty + // and we can't launch the app. We'll revert to a sync-only launch + AdtPlugin.printErrorToConsole(project, + "The Manifest defines no activity!", + "The launch will only sync the application package on the device!"); + config.mLaunchAction = ACTION_DO_NOTHING; + } else if (activityName == null) { + // if the activity we got is null, we look for the default one. + AdtPlugin.printErrorToConsole(project, + "No activity specified! Getting the launcher activity."); + activityName = manifestParser.getLauncherActivity(); + + // if there's no default activity. We revert to a sync-only launch. + if (activityName == null) { + revertToNoActionLaunch(project, config); + } + } else { + + // check the one we got from the config matches any from the list + boolean match = false; + for (String a : activities) { + if (a != null && a.equals(activityName)) { + match = true; + break; + } + } + + // if we didn't find a match, we revert to the default activity if any. + if (match == false) { + AdtPlugin.printErrorToConsole(project, + "The specified activity does not exist! Getting the launcher activity."); + activityName = manifestParser.getLauncherActivity(); + + // if there's no default activity. We revert to a sync-only launch. + if (activityName == null) { + revertToNoActionLaunch(project, config); + } + } + } + } else if (config.mLaunchAction == ACTION_DEFAULT) { + activityName = manifestParser.getLauncherActivity(); + + // if there's no default activity. We revert to a sync-only launch. + if (activityName == null) { + revertToNoActionLaunch(project, config); + } + } + + // everything seems fine, we ask the launch controller to handle + // the rest + controller.launch(project, mode, applicationPackage, manifestParser.getPackage(), + manifestParser.getDebuggable(), manifestParser.getApiLevelRequirement(), + activityName, config, androidLaunch, monitor); + } + + @Override + public boolean buildForLaunch(ILaunchConfiguration configuration, + String mode, IProgressMonitor monitor) throws CoreException { + + // need to check we have everything + IProject project = getProject(configuration); + + if (project != null) { + // force an incremental build to be sure the resources will + // be updated if they were not saved before the launch was launched. + return true; + } + + throw new CoreException(new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID, + 1 /* code, unused */, "Can't find the project!", null /* exception */)); + } + + /** + * {@inheritDoc} + * @throws CoreException + */ + @Override + public ILaunch getLaunch(ILaunchConfiguration configuration, String mode) + throws CoreException { + return new AndroidLaunch(configuration, mode, null); + } + + /** + * Returns the IProject object matching the name found in the configuration + * object under the name + * IJavaLaunchConfigurationConstants.ATTR_PROJECT_NAME + * @param configuration + * @return The IProject object or null + */ + private IProject getProject(ILaunchConfiguration configuration){ + // get the project name from the config + String projectName; + try { + projectName = configuration.getAttribute( + IJavaLaunchConfigurationConstants.ATTR_PROJECT_NAME, ""); + } catch (CoreException e) { + return null; + } + + // get the current workspace + IWorkspace workspace = ResourcesPlugin.getWorkspace(); + + // and return the project with the name from the config + return workspace.getRoot().getProject(projectName); + } + + /** + * Checks the project is an android project. + * @param project The project to check + * @return true if the project is an android SDK. + * @throws CoreException + */ + private boolean checkAndroidProject(IProject project) throws CoreException { + // check if the project is a java and an android project. + if (project.hasNature(JavaCore.NATURE_ID) == false) { + String msg = String.format("%1$s is not a Java project!", project.getName()); + AdtPlugin.displayError("Android Launch", msg); + return false; + } + + if (project.hasNature(AndroidConstants.NATURE) == false) { + String msg = String.format("%1$s is not an Android project!", project.getName()); + AdtPlugin.displayError("Android Launch", msg); + return false; + } + + return true; + } + + + /** + * Returns the android package file as an IFile object for the specified + * project. + * @param project The project + * @return The android package as an IFile object or null if not found. + */ + private IFile getApplicationPackage(IProject project) { + // get the output folder + IFolder outputLocation = BaseProjectHelper.getOutputFolder(project); + + if (outputLocation == null) { + AdtPlugin.printErrorToConsole(project, + "Failed to get the output location of the project. Check build path properties" + ); + return null; + } + + + // get the package path + String packageName = project.getName() + AndroidConstants.DOT_ANDROID_PACKAGE; + IResource r = outputLocation.findMember(packageName); + + // check the package is present + if (r instanceof IFile && r.exists()) { + return (IFile)r; + } + + String msg = String.format("Could not find %1$s!", packageName); + AdtPlugin.printErrorToConsole(project, msg); + + return null; + } + + /** + * Returns the name of the activity. + */ + private String getActivityName(ILaunchConfiguration configuration) { + String empty = ""; + String activityName; + try { + activityName = configuration.getAttribute(ATTR_ACTIVITY, empty); + } catch (CoreException e) { + return null; + } + + return (activityName != empty) ? activityName : null; + } + + private final void revertToNoActionLaunch(IProject project, AndroidLaunchConfiguration config) { + AdtPlugin.printErrorToConsole(project, + "No Launcher activity found!", + "The launch will only sync the application package on the device!"); + config.mLaunchAction = ACTION_DO_NOTHING; + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/LaunchConfigTabGroup.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/LaunchConfigTabGroup.java new file mode 100644 index 0000000..f1dbd26 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/LaunchConfigTabGroup.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2007 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.launch; + +import org.eclipse.debug.ui.AbstractLaunchConfigurationTabGroup; +import org.eclipse.debug.ui.CommonTab; +import org.eclipse.debug.ui.ILaunchConfigurationDialog; +import org.eclipse.debug.ui.ILaunchConfigurationTab; + +/** + * Tab group object for Android Launch Config type. + */ +public class LaunchConfigTabGroup extends AbstractLaunchConfigurationTabGroup { + + public LaunchConfigTabGroup() { + } + + public void createTabs(ILaunchConfigurationDialog dialog, String mode) { + ILaunchConfigurationTab[] tabs = new ILaunchConfigurationTab[] { + new MainLaunchConfigTab(), + new EmulatorConfigTab(), + new CommonTab() + }; + setTabs(tabs); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/LaunchShortcut.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/LaunchShortcut.java new file mode 100644 index 0000000..6b2744c --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/LaunchShortcut.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2007 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.launch; + +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.runtime.IAdaptable; +import org.eclipse.debug.core.ILaunchConfiguration; +import org.eclipse.debug.ui.DebugUITools; +import org.eclipse.debug.ui.ILaunchShortcut; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.ui.IEditorPart; + +/** + * Launch shortcut to launch debug/run configuration directly. + */ +public class LaunchShortcut implements ILaunchShortcut { + + + /* (non-Javadoc) + * @see org.eclipse.debug.ui.ILaunchShortcut#launch( + * org.eclipse.jface.viewers.ISelection, java.lang.String) + */ + public void launch(ISelection selection, String mode) { + if (selection instanceof IStructuredSelection) { + + // get the object and the project from it + IStructuredSelection structSelect = (IStructuredSelection)selection; + Object o = structSelect.getFirstElement(); + + // get the first (and normally only) element + if (o instanceof IAdaptable) { + IResource r = (IResource)((IAdaptable)o).getAdapter(IResource.class); + + // get the project from the resource + if (r != null) { + IProject project = r.getProject(); + + if (project != null) { + // and launch + launch(project, mode); + } + } + } + } + } + + /* (non-Javadoc) + * @see org.eclipse.debug.ui.ILaunchShortcut#launch( + * org.eclipse.ui.IEditorPart, java.lang.String) + */ + public void launch(IEditorPart editor, String mode) { + // since we force the shortcut to only work on selection in the + // package explorer, this will never be called. + } + + + /** + * Launch a config for the specified project. + * @param project The project to launch + * @param mode The launch mode ("debug", "run" or "profile") + */ + private void launch(IProject project, String mode) { + // get an existing or new launch configuration + ILaunchConfiguration config = AndroidLaunchController.getLaunchConfig(project); + + if (config != null) { + // and launch! + DebugUITools.launch(config, mode); + } + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/MainLaunchConfigTab.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/MainLaunchConfigTab.java new file mode 100644 index 0000000..599da5f --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/MainLaunchConfigTab.java @@ -0,0 +1,487 @@ +/* + * Copyright (C) 2007 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.launch; + +import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.ide.eclipse.common.project.AndroidManifestParser; +import com.android.ide.eclipse.common.project.BaseProjectHelper; +import com.android.ide.eclipse.common.project.ProjectChooserHelper; + +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.IWorkspaceRoot; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.debug.core.ILaunchConfiguration; +import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy; +import org.eclipse.debug.ui.AbstractLaunchConfigurationTab; +import org.eclipse.debug.ui.ILaunchConfigurationTab; +import org.eclipse.jdt.core.IJavaModel; +import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jdt.core.JavaCore; +import org.eclipse.jdt.launching.IJavaLaunchConfigurationConstants; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.ModifyEvent; +import org.eclipse.swt.events.ModifyListener; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.events.SelectionListener; +import org.eclipse.swt.graphics.Font; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Combo; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Group; +import org.eclipse.swt.widgets.Text; + +/** + * Class for the main launch configuration tab. + */ +public class MainLaunchConfigTab extends AbstractLaunchConfigurationTab { + + protected static final String EMPTY_STRING = ""; //$NON-NLS-1$ + + protected Text mProjText; + private Button mProjButton; + + private Combo mActivityCombo; + private String[] mActivities; + + private WidgetListener mListener = new WidgetListener(); + + private Button mDefaultActionButton; + private Button mActivityActionButton; + private Button mDoNothingActionButton; + private int mLaunchAction = LaunchConfigDelegate.DEFAULT_LAUNCH_ACTION; + + private ProjectChooserHelper mProjectChooserHelper; + + /** + * A listener which handles widget change events for the controls in this + * tab. + */ + private class WidgetListener implements ModifyListener, SelectionListener { + + public void modifyText(ModifyEvent e) { + IProject project = checkParameters(); + loadActivities(project); + setDirty(true); + } + + public void widgetDefaultSelected(SelectionEvent e) {/* do nothing */ + } + + public void widgetSelected(SelectionEvent e) { + Object source = e.getSource(); + if (source == mProjButton) { + handleProjectButtonSelected(); + } else { + checkParameters(); + } + } + } + + public MainLaunchConfigTab() { + } + + public void createControl(Composite parent) { + mProjectChooserHelper = new ProjectChooserHelper(parent.getShell()); + + Font font = parent.getFont(); + Composite comp = new Composite(parent, SWT.NONE); + setControl(comp); + GridLayout topLayout = new GridLayout(); + topLayout.verticalSpacing = 0; + comp.setLayout(topLayout); + comp.setFont(font); + createProjectEditor(comp); + createVerticalSpacer(comp, 1); + + // create the combo for the activity chooser + Group group = new Group(comp, SWT.NONE); + group.setText("Launch Action:"); + GridData gd = new GridData(GridData.FILL_HORIZONTAL); + group.setLayoutData(gd); + GridLayout layout = new GridLayout(); + layout.numColumns = 2; + group.setLayout(layout); + group.setFont(font); + + mDefaultActionButton = new Button(group, SWT.RADIO); + gd = new GridData(GridData.FILL_HORIZONTAL); + gd.horizontalSpan = 2; + mDefaultActionButton.setLayoutData(gd); + mDefaultActionButton.setText("Launch Default Activity"); + mDefaultActionButton.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + // event are received for both selection and deselection, so we only process + // the selection event to avoid doing it twice. + if (mDefaultActionButton.getSelection() == true) { + mLaunchAction = LaunchConfigDelegate.ACTION_DEFAULT; + mActivityCombo.setEnabled(false); + checkParameters(); + } + } + }); + + mActivityActionButton = new Button(group, SWT.RADIO); + mActivityActionButton.setText("Launch:"); + mActivityActionButton.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + // event are received for both selection and deselection, so we only process + // the selection event to avoid doing it twice. + if (mActivityActionButton.getSelection() == true) { + mLaunchAction = LaunchConfigDelegate.ACTION_ACTIVITY; + mActivityCombo.setEnabled(true); + checkParameters(); + } + } + }); + + mActivityCombo = new Combo(group, SWT.DROP_DOWN | SWT.READ_ONLY); + gd = new GridData(GridData.FILL_HORIZONTAL); + mActivityCombo.setLayoutData(gd); + mActivityCombo.clearSelection(); + mActivityCombo.setEnabled(false); + mActivityCombo.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + checkParameters(); + } + }); + + mDoNothingActionButton = new Button(group, SWT.RADIO); + gd = new GridData(GridData.FILL_HORIZONTAL); + gd.horizontalSpan = 2; + mDoNothingActionButton.setLayoutData(gd); + mDoNothingActionButton.setText("Do Nothing"); + mDoNothingActionButton.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + // event are received for both selection and deselection, so we only process + // the selection event to avoid doing it twice. + if (mDoNothingActionButton.getSelection() == true) { + mLaunchAction = LaunchConfigDelegate.ACTION_DO_NOTHING; + mActivityCombo.setEnabled(false); + checkParameters(); + } + } + }); + + } + + public String getName() { + return "Android"; + } + + @Override + public Image getImage() { + return AdtPlugin.getImageLoader().loadImage("mainLaunchTab.png", null); + } + + + public void performApply(ILaunchConfigurationWorkingCopy configuration) { + configuration.setAttribute( + IJavaLaunchConfigurationConstants.ATTR_PROJECT_NAME, mProjText.getText()); + configuration.setAttribute( + IJavaLaunchConfigurationConstants.ATTR_ALLOW_TERMINATE, true); + + // add the launch mode + configuration.setAttribute(LaunchConfigDelegate.ATTR_LAUNCH_ACTION, mLaunchAction); + + // add the activity + int selection = mActivityCombo.getSelectionIndex(); + if (mActivities != null && selection >=0 && selection < mActivities.length) { + configuration.setAttribute(LaunchConfigDelegate.ATTR_ACTIVITY, mActivities[selection]); + } + + // link the project and the launch config. + mapResources(configuration); + } + + public void setDefaults(ILaunchConfigurationWorkingCopy configuration) { + configuration.setAttribute(LaunchConfigDelegate.ATTR_LAUNCH_ACTION, + LaunchConfigDelegate.DEFAULT_LAUNCH_ACTION); + } + + /** + * Creates the widgets for specifying a main type. + * + * @param parent the parent composite + */ + protected void createProjectEditor(Composite parent) { + Font font = parent.getFont(); + Group group = new Group(parent, SWT.NONE); + group.setText("Project:"); + GridData gd = new GridData(GridData.FILL_HORIZONTAL); + group.setLayoutData(gd); + GridLayout layout = new GridLayout(); + layout.numColumns = 2; + group.setLayout(layout); + group.setFont(font); + mProjText = new Text(group, SWT.SINGLE | SWT.BORDER); + gd = new GridData(GridData.FILL_HORIZONTAL); + mProjText.setLayoutData(gd); + mProjText.setFont(font); + mProjText.addModifyListener(mListener); + mProjButton = createPushButton(group, "Browse...", null); + mProjButton.addSelectionListener(mListener); + } + + /** + * returns the default listener from this class. For all subclasses this + * listener will only provide the functi Jaonality of updating the current + * tab + * + * @return a widget listener + */ + protected WidgetListener getDefaultListener() { + return mListener; + } + + /** + * Return the {@link IJavaProject} corresponding to the project name in the project + * name text field, or null if the text does not match a project name. + * @param javaModel the Java Model object corresponding for the current workspace root. + * @return a IJavaProject object or null. + */ + protected IJavaProject getJavaProject(IJavaModel javaModel) { + String projectName = mProjText.getText().trim(); + if (projectName.length() < 1) { + return null; + } + return javaModel.getJavaProject(projectName); + } + + /** + * Show a dialog that lets the user select a project. This in turn provides + * context for the main type, allowing the user to key a main type name, or + * constraining the search for main types to the specified project. + */ + protected void handleProjectButtonSelected() { + IJavaProject javaProject = mProjectChooserHelper.chooseJavaProject( + mProjText.getText().trim()); + if (javaProject == null) { + return; + }// end if + String projectName = javaProject.getElementName(); + mProjText.setText(projectName); + + // get the list of activities and fill the combo + IProject project = javaProject.getProject(); + loadActivities(project); + }// end handle selected + + /** + * Initializes this tab's controls with values from the given + * launch configuration. This method is called when + * a configuration is selected to view or edit, after this + * tab's control has been created. + * + * @param config launch configuration + * + * @see ILaunchConfigurationTab + */ + public void initializeFrom(ILaunchConfiguration config) { + String projectName = EMPTY_STRING; + try { + projectName = config.getAttribute(IJavaLaunchConfigurationConstants.ATTR_PROJECT_NAME, + EMPTY_STRING); + }// end try + catch (CoreException ce) { + } + mProjText.setText(projectName); + + // get the list of projects + IJavaProject[] projects = mProjectChooserHelper.getAndroidProjects(null); + + if (projects != null) { + // look for the currently selected project + IProject proj = null; + for (IJavaProject p : projects) { + if (p.getElementName().equals(projectName)) { + proj = p.getProject(); + break; + } + } + + loadActivities(proj); + } + + // load the launch action. + mLaunchAction = LaunchConfigDelegate.DEFAULT_LAUNCH_ACTION; + try { + mLaunchAction = config.getAttribute(LaunchConfigDelegate.ATTR_LAUNCH_ACTION, + mLaunchAction); + } catch (CoreException e) { + // nothing to be done really. launchAction will keep its default value. + } + + mDefaultActionButton.setSelection(mLaunchAction == LaunchConfigDelegate.ACTION_DEFAULT); + mActivityActionButton.setSelection(mLaunchAction == LaunchConfigDelegate.ACTION_ACTIVITY); + mDoNothingActionButton.setSelection( + mLaunchAction == LaunchConfigDelegate.ACTION_DO_NOTHING); + + // now look for the activity and load it if present, otherwise, revert + // to the current one. + String activityName = EMPTY_STRING; + try { + activityName = config.getAttribute(LaunchConfigDelegate.ATTR_ACTIVITY, EMPTY_STRING); + }// end try + catch (CoreException ce) { + // nothing to be done really. activityName will stay empty + } + + if (mLaunchAction != LaunchConfigDelegate.ACTION_ACTIVITY) { + mActivityCombo.setEnabled(false); + mActivityCombo.clearSelection(); + } else { + mActivityCombo.setEnabled(true); + if (activityName == null || activityName.equals(EMPTY_STRING)) { + mActivityCombo.clearSelection(); + } else if (mActivities != null && mActivities.length > 0) { + // look for the name of the activity in the combo. + boolean found = false; + for (int i = 0 ; i < mActivities.length ; i++) { + if (activityName.equals(mActivities[i])) { + found = true; + mActivityCombo.select(i); + break; + } + } + + // if we haven't found a matching activity we clear the combo selection + if (found == false) { + mActivityCombo.clearSelection(); + } + } + } + } + + /** + * Associates the launch config and the project. This allows Eclipse to delete the launch + * config when the project is deleted. + * + * @param config the launch config working copy. + */ + protected void mapResources(ILaunchConfigurationWorkingCopy config) { + // get the java model + IWorkspaceRoot workspaceRoot = ResourcesPlugin.getWorkspace().getRoot(); + IJavaModel javaModel = JavaCore.create(workspaceRoot); + + // get the IJavaProject described by the text field. + IJavaProject javaProject = getJavaProject(javaModel); + IResource[] resources = null; + if (javaProject != null) { + resources = AndroidLaunchController.getResourcesToMap(javaProject.getProject()); + } + config.setMappedResources(resources); + } + + /** + * Loads the ui with the activities of the specified project, and stores the + * activities in mActivities. + *

            + * First activity is selected by default if present. + * + * @param project The project to load the activities from. + */ + private void loadActivities(IProject project) { + if (project != null) { + try { + // parse the manifest for the list of activities. + AndroidManifestParser manifestParser = AndroidManifestParser.parse( + BaseProjectHelper.getJavaProject(project), null /* errorListener */, + true /* gatherData */, false /* markErrors */); + if (manifestParser != null) { + mActivities = manifestParser.getActivities(); + + mActivityCombo.removeAll(); + + if (mActivities.length > 0) { + if (mLaunchAction == LaunchConfigDelegate.ACTION_ACTIVITY) { + mActivityCombo.setEnabled(true); + } + for (String s : mActivities) { + mActivityCombo.add(s); + } + } else { + mActivityCombo.setEnabled(false); + } + + // the selection will be set when we update the ui from the current + // config object. + mActivityCombo.clearSelection(); + + return; + } + + } catch (CoreException e) { + // The AndroidManifest parsing failed. The builders must have reported the errors + // already so there's nothing to do. + } + } + + // if we reach this point, either project is null, or we got an exception during + // the parsing. In either case, we empty the activity list. + mActivityCombo.removeAll(); + mActivities = null; + } + + /** + * Checks the parameters for correctness, and update the error message and buttons. + * @return the current IProject of this launch config. + */ + private IProject checkParameters() { + try { + //test the project name first! + String text = mProjText.getText(); + if (text.length() == 0) { + setErrorMessage("Project Name is required!"); + } else if (text.matches("[a-zA-Z0-9_ \\.-]+") == false) { + setErrorMessage("Project name contains unsupported characters!"); + } else { + IJavaProject[] projects = mProjectChooserHelper.getAndroidProjects(null); + IProject found = null; + for (IJavaProject javaProject : projects) { + if (javaProject.getProject().getName().equals(text)) { + found = javaProject.getProject(); + break; + } + + } + + if (found != null) { + setErrorMessage(null); + } else { + setErrorMessage(String.format("There is no android project named '%1$s'", + text)); + } + + return found; + } + } finally { + updateLaunchConfigurationDialog(); + } + + return null; + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/CreateAidlImportAction.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/CreateAidlImportAction.java deleted file mode 100644 index a1b3c38..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/CreateAidlImportAction.java +++ /dev/null @@ -1,210 +0,0 @@ -/* - * Copyright (C) 2008 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.project; - -import com.android.ide.eclipse.adt.AdtPlugin; -import com.android.ide.eclipse.common.AndroidConstants; - -import org.eclipse.core.resources.IProject; -import org.eclipse.core.resources.IResource; -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.core.runtime.jobs.Job; -import org.eclipse.jdt.core.ICompilationUnit; -import org.eclipse.jdt.core.IJavaElement; -import org.eclipse.jdt.core.IJavaProject; -import org.eclipse.jdt.core.IPackageFragment; -import org.eclipse.jdt.core.IPackageFragmentRoot; -import org.eclipse.jdt.core.IType; -import org.eclipse.jdt.core.ITypeHierarchy; -import org.eclipse.jdt.core.JavaCore; -import org.eclipse.jdt.core.JavaModelException; -import org.eclipse.jface.action.IAction; -import org.eclipse.jface.viewers.ISelection; -import org.eclipse.jface.viewers.IStructuredSelection; -import org.eclipse.ui.IObjectActionDelegate; -import org.eclipse.ui.IWorkbenchPart; - -import java.io.File; -import java.io.FileWriter; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Iterator; - -/** - * Action going through all the source of a project and creating a pre-processed aidl file - * with all the custom parcelable classes. - */ -public class CreateAidlImportAction implements IObjectActionDelegate { - - private ISelection mSelection; - - public CreateAidlImportAction() { - // pass - } - - public void setActivePart(IAction action, IWorkbenchPart targetPart) { - // pass - } - - /* - * (non-Javadoc) - * @see org.eclipse.ui.IActionDelegate#run(org.eclipse.jface.action.IAction) - */ - public void run(IAction action) { - if (mSelection instanceof IStructuredSelection) { - for (Iterator it = ((IStructuredSelection)mSelection).iterator(); it.hasNext();) { - Object element = it.next(); - IProject project = null; - if (element instanceof IProject) { - project = (IProject)element; - } else if (element instanceof IAdaptable) { - project = (IProject)((IAdaptable)element).getAdapter(IProject.class); - } - if (project != null) { - final IProject fproject = project; - new Job("Aidl preprocess") { - @Override - protected IStatus run(IProgressMonitor monitor) { - return createImportFile(fproject, monitor); - } - }.schedule(); - } - } - } - } - - public void selectionChanged(IAction action, ISelection selection) { - mSelection = selection; - } - - private IStatus createImportFile(IProject project, IProgressMonitor monitor) { - try { - if (monitor != null) { - monitor.beginTask(String.format( - "Creating aid preprocess file for %1$s", project.getName()), 1); - } - - ArrayList parcelables = new ArrayList(); - - IJavaProject javaProject = JavaCore.create(project); - - IPackageFragmentRoot[] roots = javaProject.getPackageFragmentRoots(); - - for (IPackageFragmentRoot root : roots) { - if (root.isArchive() == false && root.isExternal() == false) { - parsePackageFragmentRoot(root, parcelables, monitor); - } - } - - // create the file with the parcelables - if (parcelables.size() > 0) { - IPath path = project.getLocation(); - path = path.append(AndroidConstants.FN_PROJECT_AIDL); - - File f = new File(path.toOSString()); - if (f.exists() == false) { - if (f.createNewFile() == false) { - return new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID, - "Failed to create /project.aidl"); - } - } - - FileWriter fw = new FileWriter(f); - - fw.write("// This file is auto-generated by the\n"); - fw.write("// 'Create Aidl preprocess file for Parcelable classes'\n"); - fw.write("// action. Do not modify!\n\n"); - - for (String parcelable : parcelables) { - fw.write("parcelable "); //$NON-NLS-1$ - fw.write(parcelable); - fw.append(";\n"); //$NON-NLS-1$ - } - - fw.close(); - - // need to refresh the level just below the project to make sure it's being picked - // up by eclipse. - project.refreshLocal(IResource.DEPTH_ONE, monitor); - } - - if (monitor != null) { - monitor.worked(1); - monitor.done(); - } - - return Status.OK_STATUS; - } catch (JavaModelException e) { - return e.getJavaModelStatus(); - } catch (IOException e) { - return new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID, - "Failed to create /project.aidl", e); - } catch (CoreException e) { - return e.getStatus(); - } finally { - if (monitor != null) { - monitor.done(); - } - } - } - - private void parsePackageFragmentRoot(IPackageFragmentRoot root, - ArrayList parcelables, IProgressMonitor monitor) throws JavaModelException { - - IJavaElement[] elements = root.getChildren(); - - for (IJavaElement element : elements) { - if (element instanceof IPackageFragment) { - ICompilationUnit[] compilationUnits = - ((IPackageFragment)element).getCompilationUnits(); - - for (ICompilationUnit unit : compilationUnits) { - IType[] types = unit.getTypes(); - - for (IType type : types) { - parseType(type, parcelables, monitor); - } - } - } - } - } - - private void parseType(IType type, ArrayList parcelables, IProgressMonitor monitor) - throws JavaModelException { - // first look in this type if it somehow extends parcelable. - ITypeHierarchy typeHierarchy = type.newSupertypeHierarchy(monitor); - - IType[] superInterfaces = typeHierarchy.getAllSuperInterfaces(type); - for (IType superInterface : superInterfaces) { - if (AndroidConstants.CLASS_PARCELABLE.equals(superInterface.getFullyQualifiedName())) { - parcelables.add(type.getFullyQualifiedName()); - } - } - - // then look in inner types. - IType[] innerTypes = type.getTypes(); - - for (IType innerType : innerTypes) { - parseType(innerType, parcelables, monitor); - } - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/FixLaunchConfig.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/FixLaunchConfig.java index b8a0b0c..49bbd81 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/FixLaunchConfig.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/FixLaunchConfig.java @@ -17,7 +17,7 @@ package com.android.ide.eclipse.adt.project; import com.android.ide.eclipse.adt.AdtPlugin; -import com.android.ide.eclipse.adt.debug.launching.LaunchConfigDelegate; +import com.android.ide.eclipse.adt.launch.LaunchConfigDelegate; import org.eclipse.core.resources.IProject; import org.eclipse.core.runtime.CoreException; diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/FolderDecorator.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/FolderDecorator.java index 7fc3318..59b2b06 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/FolderDecorator.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/FolderDecorator.java @@ -39,7 +39,7 @@ public class FolderDecorator implements ILightweightLabelDecorator { private ImageDescriptor mDescriptor; public FolderDecorator() { - mDescriptor = AdtPlugin.getImageDescriptor("/icons/android_project.png"); + mDescriptor = AdtPlugin.getImageDescriptor("/icons/android_project.png"); //$NON-NLS-1$ } public void decorate(Object element, IDecoration decoration) { @@ -55,13 +55,13 @@ public class FolderDecorator implements ILightweightLabelDecorator { if (folder.getParent().getType() == IResource.PROJECT) { String name = folder.getName(); if (name.equals(SdkConstants.FD_ASSETS)) { - decorate(decoration, " [Android assets]"); - decoration.addOverlay(mDescriptor, IDecoration.TOP_RIGHT); + doDecoration(decoration, null); } else if (name.equals(SdkConstants.FD_RESOURCES)) { - decorate(decoration, " [Android resources]"); - decoration.addOverlay(mDescriptor, IDecoration.TOP_RIGHT); - } else if (name.equals(SdkConstants.FD_NATIVE_LIBS)) { - decorate(decoration, " [Native Libraries]"); + doDecoration(decoration, null); + } else if (name.equals(SdkConstants.FD_GEN_SOURCES)) { + doDecoration(decoration, " [Generated Java Files]"); + } else if (name.equals(SdkConstants.FD_NATIVE_LIBS)) { + doDecoration(decoration, null); } } } @@ -72,20 +72,24 @@ public class FolderDecorator implements ILightweightLabelDecorator { } } - public void decorate(IDecoration decoration, String suffix) { - decoration.addOverlay(mDescriptor, IDecoration.TOP_RIGHT); + public void doDecoration(IDecoration decoration, String suffix) { + decoration.addOverlay(mDescriptor, IDecoration.TOP_LEFT); - // this is broken as it changes the color of the whole text, not only of the decoration. - // TODO: figure out how to change the color of the decoration only. -// decoration.addSuffix(suffix); -// ITheme theme = PlatformUI.getWorkbench().getThemeManager().getCurrentTheme(); -// ColorRegistry registry = theme.getColorRegistry(); -// decoration.setForegroundColor(registry.get("org.eclipse.jdt.ui.ColoredLabels.decorations")); + if (suffix != null) { + decoration.addSuffix(suffix); + + // this is broken as it changes the color of the whole text, not only of the decoration. + // TODO: figure out how to change the color of the decoration only. +// ITheme theme = PlatformUI.getWorkbench().getThemeManager().getCurrentTheme(); +// ColorRegistry registry = theme.getColorRegistry(); +// decoration.setForegroundColor( +// registry.get("org.eclipse.jdt.ui.ColoredLabels.decorations")); //$NON-NLS-1$ + } } public boolean isLabelProperty(Object element, String property) { - // at this time return false. + // Property change do not affect the label return false; } @@ -93,13 +97,11 @@ public class FolderDecorator implements ILightweightLabelDecorator { // No state change will affect the rendering. } - - public void removeListener(ILabelProviderListener listener) { // No state change will affect the rendering. } public void dispose() { - // nothind to dispose + // nothing to dispose } } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/AndroidConstants.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/AndroidConstants.java index e201132..5abfd81 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/AndroidConstants.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/AndroidConstants.java @@ -87,7 +87,6 @@ public class AndroidConstants { /** Name of the manifest file, i.e. "AndroidManifest.xml". */ public static final String FN_ANDROID_MANIFEST = "AndroidManifest.xml"; //$NON-NLS-1$ - public static final String FN_PROJECT_AIDL = "project.aidl"; //$NON-NLS-1$ /** Name of the android sources directory */ public static final String FD_ANDROID_SOURCES = "sources"; //$NON-NLS-1$ diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/META-INF/MANIFEST.MF b/eclipse/plugins/com.android.ide.eclipse.tests/META-INF/MANIFEST.MF index 266008c..c7f5ba8 100644 --- a/eclipse/plugins/com.android.ide.eclipse.tests/META-INF/MANIFEST.MF +++ b/eclipse/plugins/com.android.ide.eclipse.tests/META-INF/MANIFEST.MF @@ -13,7 +13,7 @@ Require-Bundle: org.eclipse.ui, org.eclipse.jdt.launching, org.eclipse.ui.views, com.android.ide.eclipse.ddms -Eclipse-LazyStart: true +Bundle-ActivationPolicy: lazy Bundle-Vendor: The Android Open Source Project Bundle-ClassPath: kxml2-2.3.0.jar, . diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/launch/JUnitLaunchConfigDelegateTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/launch/JUnitLaunchConfigDelegateTest.java new file mode 100644 index 0000000..df3745e --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/launch/JUnitLaunchConfigDelegateTest.java @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2009 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.launch; + +import java.io.IOException; +import java.util.Arrays; +import junit.framework.TestCase; + +public class JUnitLaunchConfigDelegateTest extends TestCase { + + public void testAbleToFetchJunitJar() throws IOException { + assertTrue(JUnitLaunchConfigDelegate.getJunitJarLocation().endsWith("junit.jar")); + } + + public void testFixBootpathExtWithAndroidJar() { + String[][] testArray = { + null, + { "android.jar"}, + null, + { "some_other_jar.jar" }, + }; + + String[][] expectedArray = { + null, + null, + null, + { "some_other_jar.jar" }, + }; + + assertEqualsArrays(expectedArray, JUnitLaunchConfigDelegate.fixBootpathExt(testArray)); + } + + public void testFixBootpathExtWithNoAndroidJar() { + String[][] testArray = { + null, + { "somejar.jar"}, + null, + }; + + String[][] expectedArray = { + null, + { "somejar.jar"}, + null, + }; + + assertEqualsArrays(expectedArray, JUnitLaunchConfigDelegate.fixBootpathExt(testArray)); + } + + public void testFixClasspathWithJunitJar() throws IOException { + String[] testArray = { + JUnitLaunchConfigDelegate.getJunitJarLocation(), + }; + + String[] expectedArray = { + JUnitLaunchConfigDelegate.getJunitJarLocation(), + }; + + assertEqualsArrays(expectedArray, + JUnitLaunchConfigDelegate.fixClasspath(testArray, "test")); + } + + public void testFixClasspathWithoutJunitJar() throws IOException { + String[] testArray = { + "random.jar", + }; + + String[] expectedArray = { + "random.jar", + JUnitLaunchConfigDelegate.getJunitJarLocation(), + }; + + assertEqualsArrays(expectedArray, + JUnitLaunchConfigDelegate.fixClasspath(testArray, "test")); + } + + + public void testFixClasspathWithNoJars() throws IOException { + String[] testArray = { + }; + + String[] expectedArray = { + JUnitLaunchConfigDelegate.getJunitJarLocation(), + }; + + assertEqualsArrays(expectedArray, + JUnitLaunchConfigDelegate.fixClasspath(testArray, "test")); + } + + private void assertEqualsArrays(String[][] a1, String[][] a2) { + assertTrue(Arrays.deepEquals(a1, a2)); + } + + private void assertEqualsArrays(String[] a1, String[] a2) { + assertTrue(Arrays.deepEquals(a1, a2)); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/tests/EclipseTestCollector.java b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/tests/EclipseTestCollector.java index 29538bb..6aaa209 100644 --- a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/tests/EclipseTestCollector.java +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/tests/EclipseTestCollector.java @@ -15,15 +15,14 @@ */ package com.android.ide.eclipse.tests; -import junit.framework.TestCase; -import junit.framework.TestSuite; - import org.eclipse.core.runtime.Plugin; import java.lang.reflect.Modifier; import java.net.URL; import java.util.Enumeration; -import java.util.logging.Logger; + +import junit.framework.TestCase; +import junit.framework.TestSuite; /** * Class for collecting all test cases in an eclipse plugin @@ -31,8 +30,6 @@ import java.util.logging.Logger; */ public class EclipseTestCollector { - private static final Logger sLogger = Logger.getLogger(EclipseTestCollector.class.getName()); - /** * Constructor */ @@ -49,13 +46,13 @@ public class EclipseTestCollector { */ public void addTestCases(TestSuite suite, Plugin plugin, String expectedPackage) { if (plugin != null) { - Enumeration entries = plugin.getBundle().findEntries("/", "*.class", true); + Enumeration entries = plugin.getBundle().findEntries("/", "*.class", true); while (entries.hasMoreElements()) { URL entry = (URL)entries.nextElement(); String filePath = entry.getPath().replace(".class", ""); try { - Class testClass = getClass(filePath, expectedPackage); + Class testClass = getClass(filePath, expectedPackage); if (isTestClass(testClass)) { suite.addTestSuite(testClass); } @@ -69,11 +66,11 @@ public class EclipseTestCollector { } /** - * Returns true if given class shouk\ld be added to suite + * Returns true if given class should be added to suite * @param testClass * @return */ - protected boolean isTestClass(Class testClass) { + protected boolean isTestClass(Class testClass) { return TestCase.class.isAssignableFrom(testClass) && Modifier.isPublic(testClass.getModifiers()) && hasPublicConstructor(testClass); @@ -84,7 +81,7 @@ public class EclipseTestCollector { * @param testClass * @return */ - protected boolean hasPublicConstructor(Class testClass) { + protected boolean hasPublicConstructor(Class testClass) { try { TestSuite.getTestConstructor(testClass); } catch(NoSuchMethodException e) { @@ -100,7 +97,7 @@ public class EclipseTestCollector { * @return * @throws ClassNotFoundException */ - protected Class getClass(String filePath, String expectedPackage) throws ClassNotFoundException { + protected Class getClass(String filePath, String expectedPackage) throws ClassNotFoundException { String dotPath = filePath.replace('/', '.'); // remove the output folders, by finding where package name starts int index = dotPath.indexOf(expectedPackage); diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/tests/UnitTests.java b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/tests/UnitTests.java index ac928db..67e7cb2 100644 --- a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/tests/UnitTests.java +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/tests/UnitTests.java @@ -49,7 +49,7 @@ public class UnitTests { * Override parent class to exclude functional tests */ @Override - protected boolean isTestClass(Class testClass) { + protected boolean isTestClass(Class testClass) { return super.isTestClass(testClass) && !testClass.getPackage().getName().startsWith(FuncTests.FUNC_TEST_PACKAGE); } diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/editors/resources/manager/ConfigMatchTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/editors/resources/manager/ConfigMatchTest.java index 25a86c3..46e60ba 100644 --- a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/editors/resources/manager/ConfigMatchTest.java +++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/editors/resources/manager/ConfigMatchTest.java @@ -31,7 +31,6 @@ import com.android.ide.eclipse.mock.FolderMock; import java.lang.reflect.Field; import java.lang.reflect.Method; -import java.util.ArrayList; import junit.framework.TestCase; @@ -47,7 +46,6 @@ public class ConfigMatchTest extends TestCase { private FolderConfiguration config2; private FolderConfiguration config1; - @SuppressWarnings("unchecked") @Override protected void setUp() throws Exception { super.setUp(); diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/editors/resources/manager/QualifierListTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/editors/resources/manager/QualifierListTest.java index 6a555a4..0920f00 100644 --- a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/editors/resources/manager/QualifierListTest.java +++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/editors/resources/manager/QualifierListTest.java @@ -20,7 +20,6 @@ import com.android.ide.eclipse.editors.resources.configurations.FolderConfigurat import com.android.ide.eclipse.editors.resources.configurations.ResourceQualifier; import java.lang.reflect.Field; -import java.util.ArrayList; import junit.framework.TestCase; @@ -41,7 +40,6 @@ public class QualifierListTest extends TestCase { mManager = null; } - @SuppressWarnings("unchecked") public void testQualifierList() { try { // get the list of qualifier in the resource manager diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/mock/FileMock.java b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/mock/FileMock.java index a95286c..2220ed1 100644 --- a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/mock/FileMock.java +++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/mock/FileMock.java @@ -424,11 +424,13 @@ public class FileMock implements IFile { throw new NotImplementedException(); } - public Map getPersistentProperties() throws CoreException { + @SuppressWarnings("unchecked") + public Map getPersistentProperties() throws CoreException { throw new NotImplementedException(); } - public Map getSessionProperties() throws CoreException { + @SuppressWarnings("unchecked") + public Map getSessionProperties() throws CoreException { throw new NotImplementedException(); } diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/mock/FolderMock.java b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/mock/FolderMock.java index 223deb0..73a69aa 100644 --- a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/mock/FolderMock.java +++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/mock/FolderMock.java @@ -428,11 +428,11 @@ public final class FolderMock implements IFolder { throw new NotImplementedException(); } - public Map getPersistentProperties() throws CoreException { + public Map getPersistentProperties() throws CoreException { throw new NotImplementedException(); } - public Map getSessionProperties() throws CoreException { + public Map getSessionProperties() throws CoreException { throw new NotImplementedException(); } diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/mock/ProjectMock.java b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/mock/ProjectMock.java index 4c409dc..0e6fde0 100644 --- a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/mock/ProjectMock.java +++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/mock/ProjectMock.java @@ -42,6 +42,7 @@ import sun.reflect.generics.reflectiveObjects.NotImplementedException; import java.net.URI; import java.util.Map; +@SuppressWarnings("deprecation") public class ProjectMock implements IProject { public void build(int kind, IProgressMonitor monitor) throws CoreException { @@ -95,7 +96,6 @@ public class ProjectMock implements IProject { throw new NotImplementedException(); } - @SuppressWarnings("deprecation") public IPath getPluginWorkingLocation(IPluginDescriptor plugin) { throw new NotImplementedException(); } @@ -459,6 +459,8 @@ public class ProjectMock implements IProject { throw new NotImplementedException(); } + + @SuppressWarnings("unchecked") public Object getAdapter(Class adapter) { throw new NotImplementedException(); } @@ -476,11 +478,11 @@ public class ProjectMock implements IProject { throw new NotImplementedException(); } - public Map getPersistentProperties() throws CoreException { + public Map getPersistentProperties() throws CoreException { throw new NotImplementedException(); } - public Map getSessionProperties() throws CoreException { + public Map getSessionProperties() throws CoreException { throw new NotImplementedException(); } diff --git a/ninepatch/src/com/android/ninepatch/NinePatch.java b/ninepatch/src/com/android/ninepatch/NinePatch.java index 39e05c6..35a1824 100644 --- a/ninepatch/src/com/android/ninepatch/NinePatch.java +++ b/ninepatch/src/com/android/ninepatch/NinePatch.java @@ -127,8 +127,7 @@ public class NinePatch { try { - if (mPatches.size() == 0 || mHorizontalPatches.size() == 0 || - mVerticalPatches.size() == 0) { + if (mPatches.size() == 0) { g.drawImage(mImage, x, y, scaledWidth, scaledHeight, null); return; } @@ -254,6 +253,14 @@ public class NinePatch { start = rect.x; } } + } else { + int start = -1; + for (Rectangle rect : mPatches) { + if (rect.x > start) { + mHorizontalPatchesSum += rect.width; + start = rect.x; + } + } } mVerticalPatchesSum = 0; @@ -265,6 +272,14 @@ public class NinePatch { start = rect.y; } } + } else { + int start = -1; + for (Rectangle rect : mPatches) { + if (rect.y > start) { + mVerticalPatchesSum += rect.height; + start = rect.y; + } + } } } @@ -286,28 +301,11 @@ public class NinePatch { boolean[] result = new boolean[1]; Pair>> left = getPatches(column, result); mVerticalStartWithPatch = result[0]; - - // compute the min size, based on the list of fixed sections, which is stored in - // Pair.mFirst - mMinHeight = 0; - List> fixedSections = left.mFirst; - for (Pair section : fixedSections) { - mMinHeight += section.mSecond - section.mFirst; - } result = new boolean[1]; Pair>> top = getPatches(row, result); mHorizontalStartWithPatch = result[0]; - // compute the min size, based on the list of fixed sections, which is stored in - // Pair.mFirst - - mMinWidth = 0; - fixedSections = top.mFirst; - for (Pair section : fixedSections) { - mMinWidth += section.mSecond - section.mFirst; - } - mFixed = getRectangles(left.mFirst, top.mFirst); mPatches = getRectangles(left.mSecond, top.mSecond); @@ -315,7 +313,15 @@ public class NinePatch { mHorizontalPatches = getRectangles(left.mFirst, top.mSecond); mVerticalPatches = getRectangles(left.mSecond, top.mFirst); } else { - mHorizontalPatches = mVerticalPatches = new ArrayList(0); + if (top.mFirst.size() > 0) { + mHorizontalPatches = new ArrayList(0); + mVerticalPatches = getVerticalRectangles(top.mFirst); + } else if (left.mFirst.size() > 0) { + mHorizontalPatches = getHorizontalRectangles(left.mFirst); + mVerticalPatches = new ArrayList(0); + } else { + mHorizontalPatches = mVerticalPatches = new ArrayList(0); + } } row = GraphicsUtilities.getPixels(mImage, 0, height - 1, width, 1, row); @@ -326,31 +332,30 @@ public class NinePatch { left = getPatches(column, result); mVerticalPadding = getPadding(left.mFirst); - - mHorizontalPatchesSum = 0; - if (mHorizontalPatches.size() > 0) { - int start = -1; - for (Rectangle rect : mHorizontalPatches) { - if (rect.x > start) { - mHorizontalPatchesSum += rect.width; - start = rect.x; - } - } - } + } - mVerticalPatchesSum = 0; - if (mVerticalPatches.size() > 0) { - int start = -1; - for (Rectangle rect : mVerticalPatches) { - if (rect.y > start) { - mVerticalPatchesSum += rect.height; - start = rect.y; - } - } + private List getVerticalRectangles(List> topPairs) { + List rectangles = new ArrayList(); + for (Pair top : topPairs) { + int x = top.mFirst; + int width = top.mSecond - top.mFirst; + + rectangles.add(new Rectangle(x, 1, width, mImage.getHeight() - 2)); } + return rectangles; + } + private List getHorizontalRectangles(List> leftPairs) { + List rectangles = new ArrayList(); + for (Pair left : leftPairs) { + int y = left.mFirst; + int height = left.mSecond - left.mFirst; + + rectangles.add(new Rectangle(1, y, mImage.getWidth() - 2, height)); + } + return rectangles; } - + private Pair getPadding(List> pairs) { if (pairs.size() == 0) { return new Pair(0, 0); @@ -366,14 +371,14 @@ public class NinePatch { pairs.get(index).mSecond - pairs.get(index).mFirst); } } - + private List getRectangles(List> leftPairs, List> topPairs) { List rectangles = new ArrayList(); for (Pair left : leftPairs) { int y = left.mFirst; int height = left.mSecond - left.mFirst; - for (Pair top: topPairs) { + for (Pair top : topPairs) { int x = top.mFirst; int width = top.mSecond - top.mFirst; @@ -382,7 +387,7 @@ public class NinePatch { } return rectangles; } - + private Pair>> getPatches(int[] pixels, boolean[] startWithPatch) { int lastIndex = 1; int lastPixel = pixels[1]; @@ -390,7 +395,7 @@ public class NinePatch { List> fixed = new ArrayList>(); List> patches = new ArrayList>(); - + for (int i = 1; i < pixels.length - 1; i++) { int pixel = pixels[i]; if (pixel != lastPixel) { @@ -418,6 +423,7 @@ public class NinePatch { startWithPatch[0] = true; fixed.clear(); } + return new Pair>>(fixed, patches); } -- cgit v1.1