Code drop from //branches/cupcake/...@124589

This commit is contained in:
The Android Open Source Project
2008-12-17 18:04:04 -08:00
parent 5c11852110
commit e943f2fd8e
659 changed files with 47382 additions and 9976 deletions

View File

@@ -19,11 +19,6 @@
<property name="id" value="com.android.ide.eclipse.adt" />
</ant>
<ant antfile="${genericTargets}" target="${target}">
<property name="type" value="feature" />
<property name="id" value="com.android.ide.eclipse.editors" />
</ant>
<antcall target="buildInternalFeatures"/>
</target>
@@ -33,17 +28,12 @@
<!-- Builds if property internalSite is set -->
<!-- ===================================================================== -->
<target name="buildInternalFeatures" if="internalSite">
<ant antfile="${genericTargets}" target="${target}">
<property name="type" value="feature" />
<property name="id" value="com.android.ide.eclipse.platform" />
</ant>
<ant antfile="${genericTargets}" target="${target}">
<property name="type" value="feature" />
<property name="id" value="com.android.ide.eclipse.tests" />
</ant>
</target>
<!-- ===================================================================== -->
<!-- Targets to assemble the built elements for particular configurations -->
<!-- These generally call the generated assemble scripts (named in -->
@@ -55,14 +45,6 @@
<ant antfile="${assembleScriptName}" dir="${buildDirectory}"/>
</target>
<target name="assemble.com.android.ide.eclipse.editors">
<ant antfile="${assembleScriptName}" dir="${buildDirectory}"/>
</target>
<target name="assemble.com.android.ide.eclipse.platform">
<ant antfile="${assembleScriptName}" dir="${buildDirectory}"/>
</target>
<target name="assemble.com.android.ide.eclipse.tests">
<ant antfile="${assembleScriptName}" dir="${buildDirectory}"/>
</target>

View File

@@ -1,3 +1,6 @@
0.9.0 (work in progress)
- Support for SDK with multiple versions of the Android platform and vendor supplied add-ons.
0.8.1:
- Alternate Layout wizard. In the layout editor, the "create" button is now enabled, and allows to easily create alternate versions.
@@ -5,6 +8,8 @@
- Export Wizard: To export an application for release, sign with a non debug key. Accessible from the export menu, from the Android Tools contextual menu, or from the overview page of the manifest editor.
- New XML File Wizard: To easily create new XML resources file in the /res directory.
- New checks on launch when attempting to debug on a device.
- Basic support for drag'n'drop in Graphical layout editor. You can add new items by drag'n'drop from the palette. There's is no support for moving/resizing yet.
- Undo/redo support in all XML form editors and Graphical layout editor.
0.8.0:

View File

@@ -2,7 +2,7 @@
<feature
id="com.android.ide.eclipse.adt"
label="Android Development Tools"
version="0.8.1.qualifier"
version="0.9.0.qualifier"
provider-name="The Android Open Source Project"
plugin="com.android.ide.eclipse.adt">
@@ -14,36 +14,34 @@
Copyright (C) 2007 The Android Open Source Project
</copyright>
<license>
License TBD.
</license>
<url>
<update label="Android Update Site" url="https://dl-ssl.google.com/android/eclipse/"/>
</url>
<requires>
<import plugin="org.eclipse.core.resources"/>
<import plugin="org.eclipse.core.runtime"/>
<import plugin="org.eclipse.jdt.core"/>
<import plugin="org.eclipse.ui.console"/>
<import plugin="org.eclipse.ui"/>
<import plugin="org.eclipse.jdt.ui"/>
<import plugin="org.eclipse.jface.text"/>
<import plugin="org.eclipse.ui.editors"/>
<import plugin="org.eclipse.core.resources"/>
<import plugin="org.eclipse.debug.core"/>
<import plugin="org.eclipse.debug.ui"/>
<import plugin="org.eclipse.jdt"/>
<import plugin="org.eclipse.ant.core"/>
<import plugin="org.eclipse.jdt.core"/>
<import plugin="org.eclipse.jdt.ui"/>
<import plugin="org.eclipse.jdt.launching"/>
<import plugin="org.eclipse.jface.text"/>
<import plugin="org.eclipse.ui.editors"/>
<import plugin="org.eclipse.ui.workbench.texteditor"/>
<import plugin="org.eclipse.ui.console"/>
<import plugin="org.eclipse.core.filesystem"/>
<import plugin="org.eclipse.ui"/>
<import plugin="org.eclipse.ui.ide"/>
<import plugin="org.eclipse.ui.forms"/>
</requires>
<plugin
id="com.android.ide.eclipse.common"
download-size="0"
install-size="0"
version="0.0.0"
unpack="false"/>
<plugin
id="com.android.ide.eclipse.adt"
download-size="0"

View File

@@ -1,17 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>editors-feature</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.pde.FeatureBuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.pde.FeatureNature</nature>
</natures>
</projectDescription>

View File

@@ -1 +0,0 @@
bin.includes = feature.xml

View File

@@ -1,49 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<feature
id="com.android.ide.eclipse.editors"
label="Android Editors"
version="0.8.1.qualifier"
provider-name="The Android Open Source Project"
plugin="com.android.ide.eclipse.editors">
<description>
This feature provides Editors for Android files.
</description>
<copyright>
Copyright (C) 2007 The Android Open Source Project
</copyright>
<url>
<update label="Android Update Site" url="https://dl-ssl.google.com/android/eclipse/"/>
</url>
<requires>
<import plugin="com.android.ide.eclipse.common"/>
<import plugin="org.eclipse.ui"/>
<import plugin="org.eclipse.core.runtime"/>
<import plugin="org.eclipse.core.resources"/>
<import plugin="org.eclipse.ui.editors"/>
<import plugin="org.eclipse.jface.text"/>
<import plugin="org.eclipse.ui.ide"/>
<import plugin="org.eclipse.wst.sse.ui"/>
<import plugin="org.eclipse.wst.xml.ui"/>
<import plugin="org.eclipse.wst.xml.core"/>
<import plugin="org.eclipse.wst.sse.core"/>
<import plugin="org.eclipse.ui.forms"/>
<import plugin="org.eclipse.jdt.core"/>
<import plugin="org.eclipse.ui.browser"/>
<import plugin="org.eclipse.jdt.ui"/>
<import plugin="org.eclipse.gef"/>
<import plugin="org.eclipse.ui.views"/>
<import plugin="org.eclipse.ui.console"/>
</requires>
<plugin
id="com.android.ide.eclipse.editors"
download-size="0"
install-size="0"
version="0.0.0"
unpack="false"/>
</feature>

View File

@@ -1,17 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>platform-feature</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.pde.FeatureBuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.pde.FeatureNature</nature>
</natures>
</projectDescription>

View File

@@ -1 +0,0 @@
bin.includes = feature.xml

View File

@@ -1,52 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<feature
id="com.android.ide.eclipse.platform"
label="Android Platform Tools"
version="0.8.1.qualifier"
provider-name="The Android Open Source Project">
<description>
This feature provides a ddms perspective and editor functionnality for Android.
</description>
<copyright>
Copyright (C) 2007 The Android Open Source Project
</copyright>
<url>
<update label="Android Update Site" url="https://android.corp.google.com/adt/"/>
</url>
<requires>
<import plugin="org.eclipse.core.resources"/>
<import plugin="org.eclipse.core.runtime"/>
<import plugin="org.eclipse.jdt.core"/>
<import plugin="org.eclipse.ui.console"/>
<import plugin="org.eclipse.ui"/>
<import plugin="org.eclipse.jdt.ui"/>
<import plugin="org.eclipse.jface.text"/>
<import plugin="org.eclipse.ui.editors"/>
</requires>
<plugin
id="com.android.ide.eclipse.common"
download-size="0"
install-size="0"
version="0.0.0"
unpack="false"/>
<plugin
id="com.android.ide.eclipse.platform"
download-size="0"
install-size="0"
version="0.0.0"
unpack="false"/>
<plugin
id="com.android.ide.eclipse.ddms"
download-size="0"
install-size="0"
version="0.0.0"
unpack="false"/>
</feature>

View File

@@ -2,11 +2,11 @@
<feature
id="com.android.ide.eclipse.tests"
label="ADT Tests"
version="0.8.1.qualifier"
version="0.9.0.qualifier"
provider-name="The Android Open Source Project">
<copyright>
Copyright (C) 2007 The Android Open Source Project
<copyright>
Copyright (C) 2007 The Android Open Source Project
</copyright>
<requires>
@@ -15,8 +15,6 @@
<import plugin="org.eclipse.core.resources"/>
<import plugin="com.android.ide.eclipse.adt"/>
<import plugin="org.junit"/>
<import plugin="com.android.ide.eclipse.common"/>
<import plugin="com.android.ide.eclipse.editors"/>
<import plugin="org.eclipse.jdt.core"/>
<import plugin="org.eclipse.jdt.launching"/>
<import plugin="org.eclipse.ui.views"/>

View File

@@ -5,5 +5,12 @@
<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
<classpathentry kind="lib" path="jarutils.jar"/>
<classpathentry kind="lib" path="androidprefs.jar"/>
<classpathentry kind="lib" path="sdkstats.jar"/>
<classpathentry kind="lib" path="kxml2-2.3.0.jar"/>
<classpathentry kind="lib" path="layoutlib_api.jar"/>
<classpathentry kind="lib" path="layoutlib_utils.jar"/>
<classpathentry kind="lib" path="ninepatch.jar"/>
<classpathentry kind="lib" path="sdklib.jar"/>
<classpathentry kind="lib" path="sdkuilib.jar"/>
<classpathentry kind="output" path="bin"/>
</classpath>

View File

@@ -2,14 +2,20 @@ Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Android Development Toolkit
Bundle-SymbolicName: com.android.ide.eclipse.adt;singleton:=true
Bundle-Version: 0.8.1.qualifier
Bundle-Version: 0.9.0.qualifier
Bundle-ClassPath: .,
jarutils.jar,
androidprefs.jar
androidprefs.jar,
sdkstats.jar,
kxml2-2.3.0.jar,
layoutlib_api.jar,
ninepatch.jar,
layoutlib_utils.jar,
sdklib.jar,
sdkuilib.jar
Bundle-Activator: com.android.ide.eclipse.adt.AdtPlugin
Bundle-Vendor: The Android Open Source Project
Require-Bundle: com.android.ide.eclipse.common,
com.android.ide.eclipse.ddms,
Require-Bundle: com.android.ide.eclipse.ddms,
org.eclipse.core.runtime,
org.eclipse.core.resources,
org.eclipse.debug.core,
@@ -26,11 +32,48 @@ Require-Bundle: com.android.ide.eclipse.common,
org.eclipse.core.filesystem,
org.eclipse.ui,
org.eclipse.ui.ide,
org.eclipse.ui.forms
org.eclipse.ui.forms,
org.eclipse.gef,
org.eclipse.ui.browser,
org.eclipse.ui.views,
org.eclipse.wst.sse.core,
org.eclipse.wst.sse.ui,
org.eclipse.wst.xml.core,
org.eclipse.wst.xml.ui
Eclipse-LazyStart: true
Export-Package: com.android.ide.eclipse.adt.build;x-friends:="com.android.ide.eclipse.tests",
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.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.resources;x-friends:="com.android.ide.eclipse.tests"
com.android.ide.eclipse.adt.sdk;x-friends:="com.android.ide.eclipse.tests",
com.android.ide.eclipse.adt.wizards.newproject;x-friends:="com.android.ide.eclipse.tests",
com.android.ide.eclipse.common,
com.android.ide.eclipse.common.project,
com.android.ide.eclipse.common.resources,
com.android.ide.eclipse.editors;x-friends:="com.android.ide.eclipse.tests",
com.android.ide.eclipse.editors.descriptors;x-friends:="com.android.ide.eclipse.tests",
com.android.ide.eclipse.editors.layout;x-friends:="com.android.ide.eclipse.tests",
com.android.ide.eclipse.editors.layout.descriptors;x-friends:="com.android.ide.eclipse.tests",
com.android.ide.eclipse.editors.layout.parts;x-friends:="com.android.ide.eclipse.tests",
com.android.ide.eclipse.editors.layout.uimodel;x-friends:="com.android.ide.eclipse.tests",
com.android.ide.eclipse.editors.manifest;x-friends:="com.android.ide.eclipse.tests",
com.android.ide.eclipse.editors.manifest.descriptors;x-friends:="com.android.ide.eclipse.tests",
com.android.ide.eclipse.editors.manifest.model;x-friends:="com.android.ide.eclipse.tests",
com.android.ide.eclipse.editors.manifest.pages;x-friends:="com.android.ide.eclipse.tests",
com.android.ide.eclipse.editors.menu;x-friends:="com.android.ide.eclipse.tests",
com.android.ide.eclipse.editors.menu.descriptors;x-friends:="com.android.ide.eclipse.tests",
com.android.ide.eclipse.editors.resources;x-friends:="com.android.ide.eclipse.tests",
com.android.ide.eclipse.editors.resources.configurations;x-friends:="com.android.ide.eclipse.tests",
com.android.ide.eclipse.editors.resources.descriptors;x-friends:="com.android.ide.eclipse.tests",
com.android.ide.eclipse.editors.resources.explorer;x-friends:="com.android.ide.eclipse.tests",
com.android.ide.eclipse.editors.resources.manager;x-friends:="com.android.ide.eclipse.tests",
com.android.ide.eclipse.editors.resources.manager.files;x-friends:="com.android.ide.eclipse.tests",
com.android.ide.eclipse.editors.resources.uimodel;x-friends:="com.android.ide.eclipse.tests",
com.android.ide.eclipse.editors.ui;x-friends:="com.android.ide.eclipse.tests",
com.android.ide.eclipse.editors.ui.tree;x-friends:="com.android.ide.eclipse.tests",
com.android.ide.eclipse.editors.uimodel;x-friends:="com.android.ide.eclipse.tests",
com.android.ide.eclipse.editors.wizards;x-friends:="com.android.ide.eclipse.tests",
com.android.ide.eclipse.editors.xml;x-friends:="com.android.ide.eclipse.tests",
com.android.ide.eclipse.editors.xml.descriptors;x-friends:="com.android.ide.eclipse.tests"

View File

@@ -5,6 +5,13 @@ bin.includes = plugin.xml,\
templates/,\
about.ini,\
jarutils.jar,\
androidprefs.jar
androidprefs.jar,\
sdkstats.jar,\
kxml2-2.3.0.jar,\
layoutlib_api.jar,\
layoutlib_utils.jar,\
ninepatch.jar,\
sdklib.jar,\
sdkuilib.jar
source.. = src/
output.. = bin/

View File

Before

Width:  |  Height:  |  Size: 146 B

After

Width:  |  Height:  |  Size: 146 B

View File

Before

Width:  |  Height:  |  Size: 157 B

After

Width:  |  Height:  |  Size: 157 B

View File

Before

Width:  |  Height:  |  Size: 302 B

After

Width:  |  Height:  |  Size: 302 B

View File

Before

Width:  |  Height:  |  Size: 194 B

After

Width:  |  Height:  |  Size: 194 B

View File

Before

Width:  |  Height:  |  Size: 138 B

After

Width:  |  Height:  |  Size: 138 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 463 B

View File

Before

Width:  |  Height:  |  Size: 265 B

After

Width:  |  Height:  |  Size: 265 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 445 B

View File

Before

Width:  |  Height:  |  Size: 344 B

After

Width:  |  Height:  |  Size: 344 B

View File

Before

Width:  |  Height:  |  Size: 137 B

After

Width:  |  Height:  |  Size: 137 B

View File

@@ -1,6 +1,38 @@
<?xml version="1.0" encoding="UTF-8"?>
<?eclipse version="3.2"?>
<plugin>
<extension
id="com.android.ide.eclipse.common.xmlProblem"
name="Android XML Format Problem"
point="org.eclipse.core.resources.markers">
<super type="org.eclipse.core.resources.problemmarker"/>
<super type="org.eclipse.core.resources.textmarker"/>
<persistent value="true"/>
</extension>
<extension
id="com.android.ide.eclipse.common.aaptProblem"
name="Android AAPT Problem"
point="org.eclipse.core.resources.markers">
<super type="org.eclipse.core.resources.problemmarker"/>
<super type="org.eclipse.core.resources.textmarker"/>
<persistent value="true"/>
</extension>
<extension
id="com.android.ide.eclipse.common.aidlProblem"
name="Android AIDL Problem"
point="org.eclipse.core.resources.markers">
<super type="org.eclipse.core.resources.problemmarker"/>
<super type="org.eclipse.core.resources.textmarker"/>
<persistent value="true"/>
</extension>
<extension
id="com.android.ide.eclipse.common.androidProblem"
name="Android XML Content Problem"
point="org.eclipse.core.resources.markers">
<super type="org.eclipse.core.resources.problemmarker"/>
<super type="org.eclipse.core.resources.textmarker"/>
<persistent value="true"/>
</extension>
<extension
id="ResourceManagerBuilder"
name="Android Resource Manager"
@@ -47,7 +79,7 @@
<wizard
canFinishEarly="false"
category="com.android.ide.eclipse.wizards.category"
class="com.android.ide.eclipse.adt.project.internal.NewProjectWizard"
class="com.android.ide.eclipse.adt.wizards.newproject.NewProjectWizard"
finalPerspective="org.eclipse.jdt.ui.JavaPerspective"
hasPages="true"
icon="icons/android.png"
@@ -55,6 +87,18 @@
name="Android Project"
preferredPerspectives="org.eclipse.jdt.ui.JavaPerspective"
project="true"/>
<wizard
canFinishEarly="false"
category="com.android.ide.eclipse.wizards.category"
class="com.android.ide.eclipse.editors.wizards.NewXmlFileWizard"
finalPerspective="org.eclipse.jdt.ui.JavaPerspective"
hasPages="true"
icon="icons/android.png"
id="com.android.ide.eclipse.editors.wizards.NewXmlFileWizard"
name="Android XML File"
preferredPerspectives="org.eclipse.jdt.ui.JavaPerspective"
project="false">
</wizard>
</extension>
<extension
point="org.eclipse.debug.core.launchConfigurationTypes">
@@ -173,6 +217,13 @@
id="com.android.ide.eclipse.adt.project.CreateAidlImportAction"
label="Create Aidl preprocess file for Parcelable classes"
menubarPath="com.android.ide.eclipse.adt.AndroidTools/group1"/>
<action
class="com.android.ide.eclipse.adt.project.NewXmlFileWizardAction"
enablesFor="1"
id="com.android.ide.eclipse.adt.project.NewXmlFileWizardAction"
label="New Resource File..."
menubarPath="com.android.ide.eclipse.adt.AndroidTools/group1">
</action>
<action
class="com.android.ide.eclipse.adt.project.ExportAction"
enablesFor="1"
@@ -183,7 +234,7 @@
class="com.android.ide.eclipse.adt.project.ExportWizardAction"
enablesFor="1"
id="com.android.ide.eclipse.adt.project.ExportWizardAction"
label="Export Application..."
label="Export Signed Application Package..."
menubarPath="com.android.ide.eclipse.adt.AndroidTools/group2"/>
<action
class="com.android.ide.eclipse.adt.project.FixProjectAction"
@@ -209,29 +260,31 @@
class="com.android.ide.eclipse.adt.preferences.LaunchPreferencePage"
id="com.android.ide.eclipse.adt.preferences.LaunchPreferencePage"
name="Launch"/>
<page
category="com.android.ide.eclipse.preferences.main"
class="com.android.ide.eclipse.common.preferences.UsagePreferencePage"
id="com.android.ide.eclipse.common.preferences.UsagePreferencePage"
name="Usage Stats">
</page>
</extension>
<extension
point="org.eclipse.core.runtime.preferences">
<initializer class="com.android.ide.eclipse.adt.preferences.PreferenceInitializer"/>
</extension>
<extension
point="org.eclipse.ui.editors">
<editor
class="com.android.ide.eclipse.adt.editors.java.ReadOnlyJavaEditor"
contributorClass="org.eclipse.ui.texteditor.BasicTextEditorActionContributor"
default="true"
filenames="R.java, Manifest.java"
icon="icons/android.png"
id="com.android.ide.eclipse.adt.editors.java.ReadOnlyJavaEditor"
name="Android Java Editor"/>
</extension>
<extension
id="com.android.ide.eclipse.adt.adtProblem"
name="Generic ADT Problem"
name="Android ADT Problem"
point="org.eclipse.core.resources.markers">
<super type="org.eclipse.core.resources.problemmarker"/>
<persistent value="true"/>
</extension>
<extension
id="com.android.ide.eclipse.adt.targetProblem"
name="Android Target Problem"
point="org.eclipse.core.resources.markers">
<super type="org.eclipse.core.resources.problemmarker"/>
<persistent value="false"/>
</extension>
<extension
point="org.eclipse.ui.perspectiveExtensions">
<perspectiveExtension targetID="org.eclipse.jdt.ui.JavaPerspective">
@@ -305,4 +358,110 @@
keyConfigurationId="org.eclipse.ui.defaultAcceleratorConfiguration">
</keyBinding>
</extension>
<extension
point="org.eclipse.ui.decorators">
<decorator
adaptable="true"
class="com.android.ide.eclipse.adt.project.FolderDecorator"
id="com.android.ide.eclipse.adt.project.FolderDecorator"
label="Android Decorator"
lightweight="true"
location="TOP_RIGHT"
objectClass="org.eclipse.core.resources.IFolder"
state="true">
</decorator>
</extension>
<extension
point="org.eclipse.ui.editors">
<editor
class="com.android.ide.eclipse.editors.manifest.ManifestEditor"
default="true"
filenames="AndroidManifest.xml"
icon="icons/android.png"
id="com.android.ide.eclipse.editors.manifest.ManifestEditor"
name="Android Manifest Editor">
</editor>
<editor
class="com.android.ide.eclipse.editors.resources.ResourcesEditor"
default="false"
extensions="xml"
icon="icons/android.png"
id="com.android.ide.eclipse.editors.resources.ResourcesEditor"
name="Android Resource Editor">
</editor>
<editor
class="com.android.ide.eclipse.editors.layout.LayoutEditor"
default="false"
extensions="xml"
icon="icons/android.png"
id="com.android.ide.eclipse.editors.layout.LayoutEditor"
matchingStrategy="com.android.ide.eclipse.editors.layout.MatchingStrategy"
name="Android Layout Editor">
</editor>
<editor
class="com.android.ide.eclipse.editors.menu.MenuEditor"
default="false"
extensions="xml"
icon="icons/android.png"
id="com.android.ide.eclipse.editors.menu.MenuEditor"
name="Android Menu Editor">
</editor>
<editor
class="com.android.ide.eclipse.editors.xml.XmlEditor"
default="false"
extensions="xml"
icon="icons/android.png"
id="com.android.ide.eclipse.editors.xml.XmlEditor"
name="Android Xml Resources Editor">
</editor>
</extension>
<extension
point="org.eclipse.ui.views">
<view
allowMultiple="false"
category="com.android.ide.eclipse.ddms.views.category"
class="com.android.ide.eclipse.editors.resources.explorer.ResourceExplorerView"
icon="icons/android.png"
id="com.android.ide.eclipse.editors.resources.explorer.ResourceExplorerView"
name="Resource Explorer">
</view>
</extension>
<extension
point="org.eclipse.wst.sse.ui.editorConfiguration">
<sourceViewerConfiguration
class="com.android.ide.eclipse.editors.manifest.ManifestSourceViewerConfig"
target="com.android.ide.eclipse.editors.manifest.ManifestEditor">
</sourceViewerConfiguration>
<sourceViewerConfiguration
class="com.android.ide.eclipse.editors.resources.ResourcesSourceViewerConfig"
target="com.android.ide.eclipse.editors.resources.ResourcesEditor">
</sourceViewerConfiguration>
<sourceViewerConfiguration
class="com.android.ide.eclipse.editors.layout.LayoutSourceViewerConfig"
target="com.android.ide.eclipse.editors.layout.LayoutEditor">
</sourceViewerConfiguration>
<sourceViewerConfiguration
class="com.android.ide.eclipse.editors.menu.MenuSourceViewerConfig"
target="com.android.ide.eclipse.editors.menu.MenuEditor">
</sourceViewerConfiguration>
<sourceViewerConfiguration
class="com.android.ide.eclipse.editors.xml.XmlSourceViewerConfig"
target="com.android.ide.eclipse.editors.xml.XmlEditor">
</sourceViewerConfiguration>
</extension>
<extension
point="org.eclipse.ui.propertyPages">
<page
adaptable="true"
class="com.android.ide.eclipse.adt.project.properties.AndroidPropertyPage"
id="com.android.ide.eclipse.adt.project.properties.AndroidPropertyPage"
name="Android"
nameFilter="*"
objectClass="org.eclipse.core.resources.IProject">
<enabledWhen>
<test property="org.eclipse.jdt.launching.hasProjectNature"
args="com.android.ide.eclipse.adt.AndroidNature"/>
</enabledWhen>
</page>
</extension>
</plugin>

View File

@@ -17,6 +17,7 @@
package com.android.ide.eclipse.adt;
/**
* Constant definition class.<br>
* <br>
@@ -39,6 +40,12 @@ public class AdtConstants {
/** Generic marker for ADT errors. */
public final static String MARKER_ADT = AdtPlugin.PLUGIN_ID + ".adtProblem"; //$NON-NLS-1$
/** Marker for Android Target errors.
* This is not cleared on each like other markers. Instead, it's cleared
* when a ContainerClasspathInitialized has succeeded in creating an
* {@link AndroidClasspathContainer}*/
public final static String MARKER_TARGET = AdtPlugin.PLUGIN_ID + ".targetProblem"; //$NON-NLS-1$
/** Build verbosity "Always". Those messages are always displayed. */
public final static int BUILD_ALWAYS = 0;

View File

@@ -22,34 +22,55 @@ import com.android.ddmuilib.console.DdmConsole;
import com.android.ddmuilib.console.IDdmConsole;
import com.android.ide.eclipse.adt.build.DexWrapper;
import com.android.ide.eclipse.adt.debug.launching.AndroidLaunchController;
import com.android.ide.eclipse.adt.debug.ui.SkinRepository;
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;
import com.android.ide.eclipse.adt.project.internal.AndroidClasspathContainerInitializer;
import com.android.ide.eclipse.adt.resources.FrameworkResourceParser;
import com.android.ide.eclipse.adt.sdk.AndroidTargetParser;
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.CommonPlugin;
import com.android.ide.eclipse.common.EclipseUiHelper;
import com.android.ide.eclipse.common.SdkStatsHelper;
import com.android.ide.eclipse.common.StreamHelper;
import com.android.ide.eclipse.common.project.BaseProjectHelper;
import com.android.ide.eclipse.common.project.ExportHelper;
import com.android.ide.eclipse.common.project.ExportHelper.IExportCallback;
import com.android.ide.eclipse.common.resources.FrameworkResourceManager;
import com.android.ide.eclipse.ddms.DdmsPlugin;
import com.android.ide.eclipse.ddms.ImageLoader;
import com.android.ide.eclipse.editors.IconFactory;
import com.android.ide.eclipse.editors.layout.LayoutEditor;
import com.android.ide.eclipse.editors.menu.MenuEditor;
import com.android.ide.eclipse.editors.resources.ResourcesEditor;
import com.android.ide.eclipse.editors.resources.manager.ProjectResources;
import com.android.ide.eclipse.editors.resources.manager.ResourceFolder;
import com.android.ide.eclipse.editors.resources.manager.ResourceFolderType;
import com.android.ide.eclipse.editors.resources.manager.ResourceManager;
import com.android.ide.eclipse.editors.resources.manager.ResourceMonitor;
import com.android.ide.eclipse.editors.resources.manager.ResourceMonitor.IFileListener;
import com.android.ide.eclipse.editors.xml.XmlEditor;
import com.android.sdklib.IAndroidTarget;
import com.android.sdklib.SdkConstants;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IMarkerDelta;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResourceDelta;
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.Platform;
import org.eclipse.core.runtime.Preferences;
import org.eclipse.core.runtime.QualifiedName;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.SubMonitor;
import org.eclipse.core.runtime.Preferences.IPropertyChangeListener;
import org.eclipse.core.runtime.Preferences.PropertyChangeEvent;
import org.eclipse.core.runtime.jobs.IJobChangeEvent;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.core.runtime.jobs.JobChangeAdapter;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.preference.IPreferenceStore;
@@ -57,14 +78,21 @@ import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.wizard.WizardDialog;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.IEditorDescriptor;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.console.ConsolePlugin;
import org.eclipse.ui.console.IConsole;
import org.eclipse.ui.console.IConsoleConstants;
import org.eclipse.ui.console.MessageConsole;
import org.eclipse.ui.console.MessageConsoleStream;
import org.eclipse.ui.ide.IDE;
import org.eclipse.ui.part.FileEditorInput;
import org.eclipse.ui.plugin.AbstractUIPlugin;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
@@ -81,6 +109,8 @@ import java.io.PrintStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* The activator class controls the plug-in life cycle
@@ -106,14 +136,17 @@ public class AdtPlugin extends AbstractUIPlugin {
/** singleton instance */
private static AdtPlugin sPlugin;
private static Image sAndroidLogo;
private static ImageDescriptor sAndroidLogoDesc;
/** default store, provided by eclipse */
private IPreferenceStore mStore;
/** cached location for the sdk folder */
private String mOsSdkLocation;
/** SDK Api Version */
String mSdkApiVersion;
/** The global android console */
private MessageConsole mAndroidConsole;
/** Stream to write in the android console */
private MessageConsoleStream mAndroidConsoleStream;
@@ -130,14 +163,14 @@ public class AdtPlugin extends AbstractUIPlugin {
/** Color used in the error console */
private Color mRed;
private final ArrayList<IJavaProject> mPostDexProjects = new ArrayList<IJavaProject>();
/** Load status of the SDK. Any access MUST be in a synchronized(mPostLoadProjects) block */
private LoadStatus mSdkIsLoaded = LoadStatus.LOADING;
/** Project to update once the SDK is loaded.
* Any access MUST be in a synchronized(mPostLoadProjects) block */
private final ArrayList<IJavaProject> mPostLoadProjects = new ArrayList<IJavaProject>();
/** Boolean wrapper to run dialog in the UI thread, and still get the
* return code.
*/
private static final class BooleanWrapper {
public boolean b;
}
private ResourceMonitor mResourceMonitor;
private ArrayList<Runnable> mResourceRefreshListener = new ArrayList<Runnable>();
/**
* Custom PrintStream for Dx output. This class overrides the method
@@ -210,10 +243,14 @@ public class AdtPlugin extends AbstractUIPlugin {
Display display = getDisplay();
// set the default android console.
mAndroidConsole = new MessageConsole("Android", null); //$NON-NLS-1$
ConsolePlugin.getDefault().getConsoleManager().addConsoles(
new IConsole[] { mAndroidConsole });
// get the stream to write in the android console.
MessageConsole androidConsole = CommonPlugin.getDefault().getAndroidConsole();
mAndroidConsoleStream = androidConsole.newMessageStream();
mAndroidConsoleErrorStream = androidConsole.newMessageStream();
mAndroidConsoleStream = mAndroidConsole.newMessageStream();
mAndroidConsoleErrorStream = mAndroidConsole.newMessageStream();
mRed = new Color(display, 0xFF, 0x00, 0x00);
// because this can be run, in some cases, by a non ui thread, and beccause
@@ -267,22 +304,19 @@ public class AdtPlugin extends AbstractUIPlugin {
// get the SDK location and build id.
if (checkSdkLocationAndId()) {
// if sdk if valid, reparse the skin folder
SkinRepository.getInstance().parseFolder(getOsSkinFolder());
// if sdk if valid, reparse it
// add the current Android project to the list of projects to be updated
// after the SDK is reloaded
synchronized (mPostLoadProjects) {
// get the project to refresh.
IJavaProject[] androidProjects = BaseProjectHelper.getAndroidProjects();
mPostLoadProjects.addAll(Arrays.asList(androidProjects));
}
// parse the SDK resources at the new location
parseSdkContent();
}
// parse the SDK resources at the new location
parseSdkContent();
// get the project to refresh.
IJavaProject[] androidProjects = BaseProjectHelper.getAndroidProjects();
// Setup the new container for each project. By providing new instances of
// AndroidClasspathContainer, this will force JDT to call
// IClasspathContainer#getClasspathEntries() again and receive the new
// path to the framework jar.
AndroidClasspathContainerInitializer.updateProjects(androidProjects);
} else if (PREFS_BUILD_VERBOSITY.equals(property)) {
mBuildVerbosity = BuildPreferencePage.getBuildLevel(
mStore.getString(PREFS_BUILD_VERBOSITY));
@@ -299,20 +333,11 @@ public class AdtPlugin extends AbstractUIPlugin {
}
// check the location of SDK
if (checkSdkLocationAndId()) {
// if sdk if valid, parse the skin folder
SkinRepository.getInstance().parseFolder(getOsSkinFolder());
// parse the SDK resources.
parseSdkContent();
}
final boolean isSdkLocationValid = checkSdkLocationAndId();
mBuildVerbosity = BuildPreferencePage.getBuildLevel(
mStore.getString(PREFS_BUILD_VERBOSITY));
// Ping the usage start server.
pingUsageServer();
// create the loader that's able to load the images
mLoader = new ImageLoader(this);
@@ -356,18 +381,31 @@ public class AdtPlugin extends AbstractUIPlugin {
dialog.open();
}
});
// initialize editors
startEditors();
/* The Editors plugin must be started as soon as Android projects are opened or created,
* in order to properly set default editors on the layout/values XML files.
*
* This ensures that the default editors is really only set when a new XML file
* is added to the workspace (IResourceDelta.ADDED event), through project creation or
* manual add.
* Other methods would force to go through existing projects when the Editors plugin is
* started, and set the default editors for their XML files, possibly erasing user set
* default editors.
*/
startEditorsPlugin();
// Ping the usage server and parse the SDK content.
// This is deferred in separate jobs to avoid blocking the bundle start.
// We also serialize them to avoid too many parallel jobs when Eclipse starts.
Job pingJob = createPingUsageServerJob();
pingJob.addJobChangeListener(new JobChangeAdapter() {
@Override
public void done(IJobChangeEvent event) {
super.done(event);
// Once the ping job is finished, start the SDK parser
if (isSdkLocationValid) {
// parse the SDK resources.
parseSdkContent();
}
}
});
// build jobs are run after other interactive jobs
pingJob.setPriority(Job.BUILD);
// Wait 2 seconds before starting the ping job. This leaves some time to the
// other bundles to initialize.
pingJob.schedule(2000 /*milliseconds*/);
}
/*
@@ -379,6 +417,8 @@ public class AdtPlugin extends AbstractUIPlugin {
public void stop(BundleContext context) throws Exception {
super.stop(context);
stopEditors();
DexWrapper.unloadDex();
mRed.dispose();
@@ -418,37 +458,22 @@ public class AdtPlugin extends AbstractUIPlugin {
/** Returns the adb path relative to the sdk folder */
public static String getOsRelativeAdb() {
return AndroidConstants.OS_SDK_TOOLS_FOLDER + AndroidConstants.FN_ADB;
return SdkConstants.OS_SDK_TOOLS_FOLDER + AndroidConstants.FN_ADB;
}
/** Returns the aapt path relative to the sdk folder */
public static String getOsRelativeAapt() {
return AndroidConstants.OS_SDK_TOOLS_FOLDER + AndroidConstants.FN_AAPT;
return SdkConstants.OS_SDK_TOOLS_FOLDER + AndroidConstants.FN_AAPT;
}
/** Returns the emulator path relative to the sdk folder */
public static String getOsRelativeEmulator() {
return AndroidConstants.OS_SDK_TOOLS_FOLDER + AndroidConstants.FN_EMULATOR;
return SdkConstants.OS_SDK_TOOLS_FOLDER + AndroidConstants.FN_EMULATOR;
}
/** Returns the aidl path relative to the sdk folder */
public static String getOsRelativeAidl() {
return AndroidConstants.OS_SDK_TOOLS_FOLDER + AndroidConstants.FN_AIDL;
}
/** Returns the framework jar path relative to the sdk folder */
public static String getOsRelativeFramework() {
return AndroidConstants.FN_FRAMEWORK_LIBRARY;
}
/** Returns the android sources path relative to the sdk folder */
public static String getOsRelativeAndroidSources() {
return AndroidConstants.FD_ANDROID_SOURCES;
}
/** Returns the framework jar path relative to the sdk folder */
public static String getOsRelativeAttrsXml() {
return AndroidConstants.OS_SDK_LIBS_FOLDER + AndroidConstants.FN_ATTRS_XML;
return SdkConstants.OS_SDK_TOOLS_FOLDER + AndroidConstants.FN_AIDL;
}
/** Returns the absolute adb path */
@@ -458,7 +483,7 @@ public class AdtPlugin extends AbstractUIPlugin {
/** Returns the absolute traceview path */
public static String getOsAbsoluteTraceview() {
return getOsSdkFolder() + AndroidConstants.OS_SDK_TOOLS_FOLDER +
return getOsSdkFolder() + SdkConstants.OS_SDK_TOOLS_FOLDER +
AndroidConstants.FN_TRACEVIEW;
}
@@ -467,21 +492,6 @@ public class AdtPlugin extends AbstractUIPlugin {
return getOsSdkFolder() + getOsRelativeAapt();
}
/** Returns the absolute sdk framework path */
public static String getOsAbsoluteFramework() {
return getOsSdkFolder() + getOsRelativeFramework();
}
/** Returns the absolute android sources path in the sdk */
public static String getOsAbsoluteAndroidSources() {
return getOsSdkFolder() + getOsRelativeAndroidSources();
}
/** Returns the absolute attrs.xml path */
public static String getOsAbsoluteAttrsXml() {
return getOsSdkFolder() + getOsRelativeAttrsXml();
}
/** Returns the absolute emulator path */
public static String getOsAbsoluteEmulator() {
return getOsSdkFolder() + getOsRelativeEmulator();
@@ -492,16 +502,6 @@ public class AdtPlugin extends AbstractUIPlugin {
return getOsSdkFolder() + getOsRelativeAidl();
}
/** Returns the absolute path to the aidl framework import file. */
public static String getOsAbsoluteFrameworkAidl() {
return getOsSdkFolder() + AndroidConstants.OS_SDK_LIBS_FOLDER +
AndroidConstants.FN_FRAMEWORK_AIDL;
}
public static String getOsSdkSamplesFolder() {
return getOsSdkFolder() + AndroidConstants.OS_SDK_SAMPLES_FOLDER;
}
/**
* Returns a Url file path to the javaDoc folder.
*/
@@ -526,11 +526,7 @@ public class AdtPlugin extends AbstractUIPlugin {
}
public static String getOsSdkToolsFolder() {
return getOsSdkFolder() + AndroidConstants.OS_SDK_TOOLS_FOLDER;
}
public static String getOsSkinFolder() {
return getOsSdkFolder() + AndroidConstants.OS_SDK_SKINS_FOLDER;
return getOsSdkFolder() + SdkConstants.OS_SDK_TOOLS_FOLDER;
}
public static synchronized boolean getAutoResRefresh() {
@@ -540,18 +536,6 @@ public class AdtPlugin extends AbstractUIPlugin {
return sPlugin.mStore.getBoolean(PREFS_RES_AUTO_REFRESH);
}
/**
* Returns the SDK build id.
* @return a string containing the SDK build id, or null it it is unknownn.
*/
public static synchronized String getSdkApiVersion() {
if (sPlugin != null) {
return sPlugin.mSdkApiVersion;
}
return null;
}
public static synchronized int getBuildVerbosity() {
if (sPlugin != null) {
return sPlugin.mBuildVerbosity;
@@ -705,23 +689,25 @@ public class AdtPlugin extends AbstractUIPlugin {
final Display display = getDisplay();
// we need to ask the user what he wants to do.
final BooleanWrapper wrapper = new BooleanWrapper();
final boolean[] result = new boolean[1];
display.syncExec(new Runnable() {
public void run() {
Shell shell = display.getActiveShell();
wrapper.b = MessageDialog.openQuestion(shell, title, message);
result[0] = MessageDialog.openQuestion(shell, title, message);
}
});
return wrapper.b;
return result[0];
}
/**
* Logs a message to the default Eclipse log.
*
* @param severity The severity code. Valid values are: {@link IStatus#OK}, {@link IStatus#ERROR},
* {@link IStatus#INFO}, {@link IStatus#WARNING} or {@link IStatus#CANCEL}.
* @param severity The severity code. Valid values are: {@link IStatus#OK},
* {@link IStatus#ERROR}, {@link IStatus#INFO}, {@link IStatus#WARNING} or
* {@link IStatus#CANCEL}.
* @param format The format string, like for {@link String#format(String, Object...)}.
* @param args The arguments for the format string, like for {@link String#format(String, Object...)}.
* @param args The arguments for the format string, like for
* {@link String#format(String, Object...)}.
*/
public static void log(int severity, String format, Object ... args) {
String message = String.format(format, args);
@@ -845,7 +831,7 @@ public class AdtPlugin extends AbstractUIPlugin {
// now make sure it's not docked.
ConsolePlugin.getDefault().getConsoleManager().showConsoleView(
CommonPlugin.getDefault().getAndroidConsole());
AdtPlugin.getDefault().getAndroidConsole());
}
/**
@@ -883,21 +869,17 @@ public class AdtPlugin extends AbstractUIPlugin {
}
/**
* Adds a {@link IJavaProject} to a list of projects to be recompiled once dx.jar is loaded.
* @param javaProject
* Returns whether the Sdk has been loaded. If the SDK has not been loaded, the given
* <var>project</var> is added to a list of projects to recompile after the SDK is loaded.
*/
public void addPostDexProject(IJavaProject javaProject) {
synchronized (mPostDexProjects) {
if (DexWrapper.getStatus() == DexWrapper.LoadStatus.LOADED) {
// Setup the new container for each project. By providing new instances of
// AndroidClasspathContainer, this will force JDT to call
// IClasspathContainer#getClasspathEntries() again and receive the new
// path to the framework jar, and the project will be recompiled.
AndroidClasspathContainerInitializer.updateProjects(new IJavaProject [] {
javaProject });
} else {
mPostDexProjects.add(javaProject);
public LoadStatus getSdkLoadStatus(IJavaProject project) {
synchronized (mPostLoadProjects) {
// only add the project to the list, if we are still loading.
if (mSdkIsLoaded == LoadStatus.LOADING && project != null) {
mPostLoadProjects.add(project);
}
return mSdkIsLoaded;
}
}
@@ -907,9 +889,6 @@ public class AdtPlugin extends AbstractUIPlugin {
* @return false if the location is not correct.
*/
private boolean checkSdkLocationAndId() {
// Reset the sdk build first in case the SDK is invalid and we abort.
mSdkApiVersion = null;
if (mOsSdkLocation == null || mOsSdkLocation.length() == 0) {
displayError(Messages.Dialog_Title_SDK_Location, Messages.SDK_Not_Setup);
return false;
@@ -949,17 +928,16 @@ public class AdtPlugin extends AbstractUIPlugin {
String.format(Messages.Could_Not_Find_Folder, osSdkLocation));
}
String osTools = osSdkLocation + AndroidConstants.OS_SDK_TOOLS_FOLDER;
String osTools = osSdkLocation + SdkConstants.OS_SDK_TOOLS_FOLDER;
File toolsFolder = new File(osTools);
if (toolsFolder.isDirectory() == false) {
return errorHandler.handleError(
String.format(Messages.Could_Not_Find_Folder_In_SDK,
AndroidConstants.FD_TOOLS, osSdkLocation));
SdkConstants.FD_TOOLS, osSdkLocation));
}
// check the path to various tools we use
String[] filesToCheck = new String[] {
osSdkLocation + getOsRelativeFramework(),
osSdkLocation + getOsRelativeAdb(),
osSdkLocation + getOsRelativeAapt(),
osSdkLocation + getOsRelativeAidl(),
@@ -990,118 +968,106 @@ public class AdtPlugin extends AbstractUIPlugin {
}
/**
* Pings the usage start server.
* Creates a job than can ping the usage server.
*/
private void pingUsageServer() {
private Job createPingUsageServerJob() {
// In order to not block the plugin loading, so we spawn another thread.
new Thread("Ping!") { //$NON-NLS-1$
Job job = new Job("Android SDK Ping") { // Job name, visible in progress view
@Override
public void run() {
// get the version of the plugin
String versionString = (String) getBundle().getHeaders().get(
Constants.BUNDLE_VERSION);
Version version = new Version(versionString);
SdkStatsHelper.pingUsageServer("adt", version); //$NON-NLS-1$
}
}.start();
}
/**
* Starts the Editors plugin.
* <p/>
* Since we do not want any dependencies between the plugins (Editors is an optional
* plugin not needed for Android development), we attempt to start the plugin through
* OSGi directly.
* <p/>
* This is done in another thread to not delay the start of this plugin.
*/
private void startEditorsPlugin() {
new Thread() {
@Override
public void run() {
protected IStatus run(IProgressMonitor monitor) {
try {
// look for the bundle of the Editors plugin
Bundle editorsBundle = Platform.getBundle(AndroidConstants.EDITORS_PLUGIN_ID);
if (editorsBundle != null) {
// we only start if the bundle is installed and not started.
// STARTING means that its start is pending a triggering.
int bundleState = editorsBundle.getState();
if ((bundleState & (Bundle.RESOLVED | Bundle.INSTALLED
| Bundle.STARTING)) != 0) {
// Attempt to start it.
// START_TRANSIENT is used because we don't want
// to change the auto start value.
editorsBundle.start(Bundle.START_TRANSIENT);
}
}
} catch (Exception e) {
log(e, Messages.AdtPlugin_Failed_To_Start_s, AndroidConstants.EDITORS_PLUGIN_ID);
// get the version of the plugin
String versionString = (String) getBundle().getHeaders().get(
Constants.BUNDLE_VERSION);
Version version = new Version(versionString);
SdkStatsHelper.pingUsageServer("editors", version); //$NON-NLS-1$
return Status.OK_STATUS;
} catch (Throwable t) {
log(t, "pingUsageServer failed"); //$NON-NLS-1$
return new Status(IStatus.ERROR, PLUGIN_ID,
"pingUsageServer failed", t);
}
}
}.start();
};
return job;
}
/**
* Parses the SDK resources and set them in the {@link FrameworkResourceManager}.
* Parses the SDK resources.
*/
private void parseSdkContent() {
// Perform the update in a thread (here an Eclipse runtime job)
// since this should never block the caller (especially the start method)
new Job(Messages.AdtPlugin_Android_SDK_Content_Loader) {
Job job = new Job(Messages.AdtPlugin_Android_SDK_Content_Loader) {
@SuppressWarnings("unchecked")
@Override
protected IStatus run(IProgressMonitor monitor) {
try {
SubMonitor progress = null;
try {
progress = SubMonitor.convert(monitor, Messages.AdtPlugin_Parsing_Resources, 100);
// load the values.
FrameworkResourceParser parser = new FrameworkResourceParser();
parser.parse(mOsSdkLocation, FrameworkResourceManager.getInstance(),
progress);
// set the location of the layout lib jar file.
FrameworkResourceManager.getInstance().setLayoutLibLocation(
mOsSdkLocation + AndroidConstants.OS_SDK_LIBS_LAYOUTLIB_JAR);
FrameworkResourceManager.getInstance().setFrameworkResourcesLocation(
mOsSdkLocation + AndroidConstants.OS_SDK_RESOURCES_FOLDER);
FrameworkResourceManager.getInstance().setFrameworkFontLocation(
mOsSdkLocation + AndroidConstants.OS_SDK_FONTS_FOLDER);
} catch (Throwable e) {
AdtPlugin.log(e, "Android SDK Resource Parser failed"); //$NON-NLS-1$
AdtPlugin.printErrorToConsole(Messages.AdtPlugin_Android_SDK_Resource_Parser,
Messages.AdtPlugin_Failed_To_Parse_s + e.getMessage());
return new Status(IStatus.ERROR, PLUGIN_ID, e.getMessage(), e);
} finally {
if (progress != null) {
progress.worked(100);
}
}
try {
SubMonitor progress = SubMonitor.convert(monitor,
"Initialize SDK Manager", 100);
try {
progress = SubMonitor.convert(monitor, Messages.AdtPlugin_Parsing_Resources, 20);
Sdk sdk = Sdk.loadSdk(mOsSdkLocation);
if (sdk != null) {
progress.setTaskName(Messages.AdtPlugin_Parsing_Resources);
for (IAndroidTarget target : sdk.getTargets()) {
IStatus status = new AndroidTargetParser(target).run(progress);
if (status.getCode() != IStatus.OK) {
synchronized (mPostLoadProjects) {
mSdkIsLoaded = LoadStatus.FAILED;
mPostLoadProjects.clear();
}
return status;
}
}
// FIXME: move this per platform, or somewhere else.
progress = SubMonitor.convert(monitor,
Messages.AdtPlugin_Parsing_Resources, 20);
DexWrapper.unloadDex();
IStatus res = DexWrapper.loadDex(
mOsSdkLocation + AndroidConstants.OS_SDK_LIBS_DX_JAR);
if (res != Status.OK_STATUS) {
synchronized (mPostLoadProjects) {
mSdkIsLoaded = LoadStatus.FAILED;
mPostLoadProjects.clear();
}
return res;
} else {
}
synchronized (mPostLoadProjects) {
mSdkIsLoaded = LoadStatus.LOADED;
// update the project that needs recompiling.
synchronized (mPostDexProjects) {
if (mPostDexProjects.size() > 0) {
IJavaProject[] array = mPostDexProjects.toArray(
new IJavaProject[mPostDexProjects.size()]);
AndroidClasspathContainerInitializer.updateProjects(array);
mPostDexProjects.clear();
}
if (mPostLoadProjects.size() > 0) {
IJavaProject[] array = mPostLoadProjects.toArray(
new IJavaProject[mPostLoadProjects.size()]);
AndroidClasspathContainerInitializer.updateProjects(array);
mPostLoadProjects.clear();
}
}
} finally {
if (progress != null) {
progress.worked(20);
}
// Notify resource changed listeners
progress.subTask("Refresh UI");
progress.setWorkRemaining(mResourceRefreshListener.size());
// Clone the list before iterating, to avoid Concurrent Modification
// exceptions
List<Runnable> listeners = (List<Runnable>)mResourceRefreshListener.clone();
for (Runnable listener : listeners) {
try {
AdtPlugin.getDisplay().syncExec(listener);
} catch (Exception e) {
AdtPlugin.log(e, "ResourceRefreshListener Failed"); //$NON-NLS-1$
} finally {
progress.worked(1);
}
}
} finally {
@@ -1112,6 +1078,252 @@ public class AdtPlugin extends AbstractUIPlugin {
return Status.OK_STATUS;
}
}.schedule();
};
job.setPriority(Job.BUILD); // build jobs are run after other interactive jobs
job.schedule();
}
/** Returns the global android console */
public MessageConsole getAndroidConsole() {
return mAndroidConsole;
}
// ----- Methods for Editors -------
public void startEditors() {
sAndroidLogoDesc = imageDescriptorFromPlugin(AdtPlugin.PLUGIN_ID,
"/icons/android.png"); //$NON-NLS-1$
sAndroidLogo = sAndroidLogoDesc.createImage();
// get the stream to write in the android console.
MessageConsole androidConsole = AdtPlugin.getDefault().getAndroidConsole();
mAndroidConsoleStream = androidConsole.newMessageStream();
mAndroidConsoleErrorStream = androidConsole.newMessageStream();
mRed = new Color(getDisplay(), 0xFF, 0x00, 0x00);
// because this can be run, in some cases, by a non ui thread, and beccause
// changing the console properties update the ui, we need to make this change
// in the ui thread.
getDisplay().asyncExec(new Runnable() {
public void run() {
mAndroidConsoleErrorStream.setColor(mRed);
}
});
// Add a resource listener to handle compiled resources.
IWorkspace ws = ResourcesPlugin.getWorkspace();
mResourceMonitor = ResourceMonitor.startMonitoring(ws);
if (mResourceMonitor != null) {
try {
setupDefaultEditor(mResourceMonitor);
ResourceManager.setup(mResourceMonitor);
} catch (Throwable t) {
log(t, "ResourceManager.setup failed"); //$NON-NLS-1$
}
}
}
/**
* The <code>AbstractUIPlugin</code> implementation of this <code>Plugin</code>
* method saves this plug-in's preference and dialog stores and shuts down
* its image registry (if they are in use). Subclasses may extend this
* method, but must send super <b>last</b>. A try-finally statement should
* be used where necessary to ensure that <code>super.shutdown()</code> is
* always done.
*
* @see org.eclipse.ui.plugin.AbstractUIPlugin#stop(org.osgi.framework.BundleContext)
*/
public void stopEditors() {
sAndroidLogo.dispose();
IconFactory.getInstance().Dispose();
// Remove the resource listener that handles compiled resources.
IWorkspace ws = ResourcesPlugin.getWorkspace();
ResourceMonitor.stopMonitoring(ws);
mRed.dispose();
}
/**
* Returns an Image for the small Android logo.
*
* Callers should not dispose it.
*/
public static Image getAndroidLogo() {
return sAndroidLogo;
}
/**
* Returns an {@link ImageDescriptor} for the small Android logo.
*
* Callers should not dispose it.
*/
public static ImageDescriptor getAndroidLogoDesc() {
return sAndroidLogoDesc;
}
/**
* Returns the ResourceMonitor object.
*/
public ResourceMonitor getResourceMonitor() {
return mResourceMonitor;
}
/**
* Sets up the editor to register default editors for resource files when needed.
*
* This is called by the {@link AdtPlugin} during initialization.
*
* @param monitor The main Resource Monitor object.
*/
public void setupDefaultEditor(ResourceMonitor monitor) {
monitor.addFileListener(new IFileListener() {
private static final String UNKNOWN_EDITOR = "unknown-editor"; //$NON-NLS-1$
/* (non-Javadoc)
* Sent when a file changed.
* @param file The file that changed.
* @param markerDeltas The marker deltas for the file.
* @param kind The change kind. This is equivalent to
* {@link IResourceDelta#accept(IResourceDeltaVisitor)}
*
* @see IFileListener#fileChanged
*/
public void fileChanged(IFile file, IMarkerDelta[] markerDeltas, int kind) {
if (AndroidConstants.EXT_XML.equals(file.getFileExtension())) {
// The resources files must have a file path similar to
// project/res/.../*.xml
// There is no support for sub folders, so the segment count must be 4
if (file.getFullPath().segmentCount() == 4) {
// check if we are inside the res folder.
String segment = file.getFullPath().segment(1);
if (segment.equalsIgnoreCase(AndroidConstants.FD_RESOURCES)) {
// we are inside a res/ folder, get the actual ResourceFolder
ProjectResources resources = ResourceManager.getInstance().
getProjectResources(file.getProject());
// This happens when importing old Android projects in Eclipse
// that lack the container (probably because resources fail to build
// properly.)
if (resources == null) {
log(IStatus.INFO,
"getProjectResources failed for path %1$s in project %2$s", //$NON-NLS-1$
file.getFullPath().toOSString(),
file.getProject().getName());
return;
}
ResourceFolder resFolder = resources.getResourceFolder(
(IFolder)file.getParent());
if (resFolder != null) {
if (kind == IResourceDelta.ADDED) {
resourceAdded(file, resFolder.getType());
} else if (kind == IResourceDelta.CHANGED) {
resourceChanged(file, resFolder.getType());
}
} else {
// if the res folder is null, this means the name is invalid,
// in this case we remove whatever android editors that was set
// as the default editor.
IEditorDescriptor desc = IDE.getDefaultEditor(file);
String editorId = desc.getId();
if (editorId.startsWith(AndroidConstants.EDITORS_NAMESPACE)) {
// reset the default editor.
IDE.setDefaultEditor(file, null);
}
}
}
}
}
}
private void resourceAdded(IFile file, ResourceFolderType type) {
// set the default editor based on the type.
if (type == ResourceFolderType.LAYOUT) {
IDE.setDefaultEditor(file, LayoutEditor.ID);
} else if (type == ResourceFolderType.DRAWABLE
|| type == ResourceFolderType.VALUES) {
IDE.setDefaultEditor(file, ResourcesEditor.ID);
} else if (type == ResourceFolderType.MENU) {
IDE.setDefaultEditor(file, MenuEditor.ID);
} else if (type == ResourceFolderType.XML) {
if (XmlEditor.canHandleFile(file)) {
IDE.setDefaultEditor(file, XmlEditor.ID);
} else {
// set a property to determine later if the XML can be handled
QualifiedName qname = new QualifiedName(
AdtPlugin.PLUGIN_ID,
UNKNOWN_EDITOR);
try {
file.setPersistentProperty(qname, "1");
} catch (CoreException e) {
// pass
}
}
}
}
private void resourceChanged(IFile file, ResourceFolderType type) {
if (type == ResourceFolderType.XML) {
IEditorDescriptor ed = IDE.getDefaultEditor(file);
if (ed == null || ed.getId() != XmlEditor.ID) {
QualifiedName qname = new QualifiedName(
AdtPlugin.PLUGIN_ID,
UNKNOWN_EDITOR);
String prop = null;
try {
prop = file.getPersistentProperty(qname);
} catch (CoreException e) {
// pass
}
if (prop != null && XmlEditor.canHandleFile(file)) {
try {
// remove the property & set editor
file.setPersistentProperty(qname, null);
IWorkbenchPage page = PlatformUI.getWorkbench().
getActiveWorkbenchWindow().getActivePage();
IEditorPart oldEditor = page.findEditor(new FileEditorInput(file));
if (oldEditor != null &&
AdtPlugin.displayPrompt("Android XML Editor",
String.format("The file you just saved as been recognized as a file that could be better handled using the Android XML Editor. Do you want to edit '%1$s' using the Android XML editor instead?",
file.getFullPath()))) {
IDE.setDefaultEditor(file, XmlEditor.ID);
IEditorPart newEditor = page.openEditor(
new FileEditorInput(file),
XmlEditor.ID,
true, /* activate */
IWorkbenchPage.MATCH_NONE);
if (newEditor != null) {
page.closeEditor(oldEditor, true /* save */);
}
}
} catch (CoreException e) {
// setPersistentProperty or page.openEditor may have failed
}
}
}
}
}
}, IResourceDelta.ADDED | IResourceDelta.CHANGED);
}
public void addResourceChangedListener(Runnable resourceRefreshListener) {
mResourceRefreshListener.add(resourceRefreshListener);
}
public void removeResourceChangedListener(Runnable resourceRefreshListener) {
mResourceRefreshListener.remove(resourceRefreshListener);
}
public static synchronized OutputStream getErrorStream() {
return sPlugin.mAndroidConsoleErrorStream;
}
}

View File

@@ -16,9 +16,8 @@
package com.android.ide.eclipse.adt;
import com.android.ddmlib.Device;
import com.android.ide.eclipse.adt.AdtPlugin.CheckSdkErrorHandler;
import com.android.ide.eclipse.common.AndroidConstants;
import com.android.sdklib.SdkConstants;
import org.osgi.framework.Constants;
import org.osgi.framework.Version;
@@ -42,19 +41,6 @@ import java.util.regex.Pattern;
*
*/
final class VersionCheck {
/** Pattern to get the SDK build incremental version from the
* <code>$SDK/tools/lib/build.prop file</code>. */
private final static Pattern sBuildVersionPattern = Pattern.compile(
"^" + Device.PROP_BUILD_VERSION + "=(.+)$"); //$NON-NLS-1$
/**
* Pattern to parse release type SDK version number. This parses the content read with
* <code>sBuildIdPattern</code>.
*/
private final static Pattern sSdkVersionPattern = Pattern.compile(
"^(\\d+)\\.(\\d+)_r(\\d+)$", Pattern.CASE_INSENSITIVE); //$NON-NLS-1$
/**
* Pattern to get the minimum plugin version supported by the SDK. This is read from
* the file <code>$SDK/tools/lib/plugin.prop</code>.
@@ -69,58 +55,7 @@ final class VersionCheck {
*/
public static boolean checkVersion(String osSdkPath, CheckSdkErrorHandler errorHandler) {
AdtPlugin plugin = AdtPlugin.getDefault();
String osLibs = osSdkPath + AndroidConstants.OS_SDK_LIBS_FOLDER;
/*
* All plugins should work with all SDKs. Newer SDKs may require a newer plugin
* but this is handled below.
* Still, we need to grab the SDK version from this file. This is used
* to compare to running emulator/device when launching run/debug sessions.
*/
try {
FileReader reader = new FileReader(osLibs + AndroidConstants.FN_BUILD_PROP);
BufferedReader bReader = new BufferedReader(reader);
String line;
while ((line = bReader.readLine()) != null) {
Matcher m = sBuildVersionPattern.matcher(line);
if (m.matches()) {
plugin.mSdkApiVersion = m.group(1).trim();
/*
* No checks on the version at the moment.
*/
/*
if (plugin.mSdkBuildVersion != null) {
// attempt to get version number from the build id
m = sSdkVersionPattern.matcher(plugin.mSdkBuildVersion);
if (m.matches()) {
// get the platform version number
int platformMajor = Integer.parseInt(m.group(1));
int platformMinor = Integer.parseInt(m.group(2));
@SuppressWarnings("unused") //$NON-NLS-1$
int sdkRelease = Integer.parseInt(m.group(3));
if (platformMajor != 0 || platformMinor != 9) {
return errorHandler.handleError(String.format(
"This version of ADT requires the Android SDK version 0.9\n\nCurrent version is %1$s.\n\nPlease update your SDK to the latest version.",
plugin.mSdkBuildVersion));
}
} else {
// unknown version format.
AdtPlugin.printErrorToConsole(
(Object)String.format(Messages.VersionCheck_Unable_To_Parse_Version_s,
plugin.mSdkBuildVersion));
}
}
*/
break;
}
}
} catch (FileNotFoundException e) {
// the build id will be null, and this is handled by the builders.
} catch (IOException e) {
// the build id will be null, and this is handled by the builders.
}
String osLibs = osSdkPath + SdkConstants.OS_SDK_TOOLS_LIB_FOLDER;
// get the plugin property file, and grab the minimum plugin version required
// to work with the sdk
@@ -128,7 +63,7 @@ final class VersionCheck {
int minMinorVersion = -1;
int minMicroVersion = -1;
try {
FileReader reader = new FileReader(osLibs + AndroidConstants.FN_PLUGIN_PROP);
FileReader reader = new FileReader(osLibs + SdkConstants.FN_PLUGIN_PROP);
BufferedReader bReader = new BufferedReader(reader);
String line;
while ((line = bReader.readLine()) != null) {

View File

@@ -19,6 +19,8 @@ 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.jarutils.DebugKeyProvider;
@@ -28,6 +30,7 @@ import com.android.jarutils.DebugKeyProvider.IKeyGenOutput;
import com.android.jarutils.DebugKeyProvider.KeytoolException;
import com.android.jarutils.SignedJarBuilder.IZipEntryFilter;
import com.android.prefs.AndroidLocation.AndroidLocationException;
import com.android.sdklib.IAndroidTarget;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
@@ -44,6 +47,7 @@ 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.Path;
import org.eclipse.core.runtime.Status;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.JavaCore;
@@ -296,6 +300,15 @@ public class ApkBuilder extends BaseBuilder {
saveProjectBooleanProperty(PROPERTY_PACKAGE_RESOURCES, mPackageResources);
saveProjectBooleanProperty(PROPERTY_BUILD_APK, mBuildFinalPackage);
// At this point, we can abort the build if we have to, as we have computed
// our resource delta and stored the result.
// check if we have finished loading the SDK.
if (AdtPlugin.getDefault().getSdkLoadStatus(javaProject) != LoadStatus.LOADED) {
// we exit silently
return referencedProjects;
}
// Now check the compiler compliance level, not displaying the error
// message since this is not the first builder.
if (ProjectHelper.checkCompilerCompliance(getProject())
@@ -502,7 +515,8 @@ public class ApkBuilder extends BaseBuilder {
commandArray.add(osAssetsPath);
}
commandArray.add("-I"); //$NON-NLS-1$
commandArray.add(AdtPlugin.getOsAbsoluteFramework());
commandArray.add(
Sdk.getCurrent().getTarget(project).getPath(IAndroidTarget.ANDROID_JAR));
commandArray.add("-F"); //$NON-NLS-1$
commandArray.add(osOutFilePath);
@@ -584,16 +598,9 @@ public class ApkBuilder extends BaseBuilder {
DexWrapper wrapper = DexWrapper.getWrapper();
if (wrapper == null) {
if (DexWrapper.getStatus() == DexWrapper.LoadStatus.FAILED) {
if (DexWrapper.getStatus() == LoadStatus.FAILED) {
throw new CoreException(new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
Messages.ApkBuilder_UnableBuild_Dex_Not_loaded));
} else {
// means we haven't loaded the dex jar yet.
// We set the project to be recompiled after dex is loaded.
AdtPlugin.getDefault().addPostDexProject(javaProject);
// and we exit silently
return false;
}
}
@@ -677,7 +684,7 @@ public class ApkBuilder extends BaseBuilder {
}
// TODO: get the store type from somewhere else.
DebugKeyProvider provider = new DebugKeyProvider(null /* storeType */,
DebugKeyProvider provider = new DebugKeyProvider(osKeyPath, null /* storeType */,
new IKeyGenOutput() {
public void err(String message) {
AdtPlugin.printErrorToConsole(javaProject.getProject(),
@@ -747,9 +754,18 @@ public class ApkBuilder extends BaseBuilder {
}
}
// now write the native libraries.
// First look if the lib folder is there.
IResource libFolder = javaProject.getProject().findMember(
AndroidConstants.FD_NATIVE_LIBS);
if (libFolder != null && libFolder.exists() &&
libFolder.getType() == IResource.FOLDER) {
// look inside and put .so in lib/* by keeping the relative folder path.
writeNativeLibraries(libFolder.getFullPath().segmentCount(), builder, libFolder);
}
// close the jar file and write the manifest and sign it.
builder.close();
} catch (GeneralSecurityException e1) {
// mark project and return
String msg = String.format(Messages.Final_Archive_Error_s, e1.getMessage());
@@ -784,6 +800,12 @@ public class ApkBuilder extends BaseBuilder {
// and also output it in the console
AdtPlugin.printErrorToConsole(javaProject.getProject(), msg);
} catch (CoreException e) {
// mark project and return
String msg = String.format(Messages.Final_Archive_Error_s, e.getMessage());
AdtPlugin.printErrorToConsole(javaProject.getProject(), msg);
markProject(AdtConstants.MARKER_ADT, msg, IMarker.SEVERITY_ERROR);
return false;
} finally {
if (fos != null) {
try {
@@ -797,6 +819,48 @@ public class ApkBuilder extends BaseBuilder {
return true;
}
/**
* Writes native libraries into a {@link SignedJarBuilder}.
* <p/>This recursively go through folder and writes .so files.
* The path in the archive is based on the root folder containing the libraries in the project.
* Its segment count is passed to the method to compute the resources path relative to the root
* folder.
* Native libraries in the archive must be in a "lib" folder. Everything in the project native
* lib folder directly goes in this "lib" folder in the archive.
*
*
* @param rooSegmentCount The number of segment of the path of the folder containing the
* libraries. This is used to compute the path in the archive.
* @param jarBuilder the {@link SignedJarBuilder} used to create the archive.
* @param resource the IResource to write.
* @throws CoreException
* @throws IOException
*/
private void writeNativeLibraries(int rootSegmentCount, SignedJarBuilder jarBuilder,
IResource resource) throws CoreException, IOException {
if (resource.getType() == IResource.FILE) {
IPath path = resource.getFullPath();
// check the extension.
if (path.getFileExtension().equalsIgnoreCase(AndroidConstants.EXT_NATIVE_LIB)) {
// remove the first segment to build the path inside the archive.
path = path.removeFirstSegments(rootSegmentCount);
// add it to the archive.
IPath apkPath = new Path(AndroidConstants.FD_APK_NATIVE_LIBS);
apkPath = apkPath.append(path);
// writes the file in the apk.
jarBuilder.writeFile(resource.getLocation().toFile(), apkPath.toString());
}
} else if (resource.getType() == IResource.FOLDER) {
IResource[] members = ((IFolder)resource).members();
for (IResource member : members) {
writeNativeLibraries(rootSegmentCount, jarBuilder, member);
}
}
}
/**
* Writes the standard resources of a project and its referenced projects
* into a {@link SignedJarBuilder}.

View File

@@ -42,6 +42,7 @@ import java.util.ArrayList;
* <li>Any change to the classes.dex inside the output folder</li>
* <li>Any change to the packaged resources file inside the output folder</li>
* <li>Any change to a non java/aidl file inside the source folders</li>
* <li>Any change to .so file inside the lib (native library) folder</li>
* </ul>
*/
public class ApkDeltaVisitor extends BaseDeltaVisitor
@@ -79,6 +80,8 @@ public class ApkDeltaVisitor extends BaseDeltaVisitor
private IPath mResPath;
private IPath mLibFolder;
/**
* Builds the object with a specified output folder.
* @param builder the xml builder using this object to visit the
@@ -104,6 +107,11 @@ public class ApkDeltaVisitor extends BaseDeltaVisitor
if (resFolder != null) {
mResPath = resFolder.getFullPath();
}
IResource libFolder = builder.getProject().findMember(AndroidConstants.FD_NATIVE_LIBS);
if (libFolder != null) {
mLibFolder = libFolder.getFullPath();
}
}
public boolean getConvertToDex() {
@@ -161,6 +169,7 @@ public class ApkDeltaVisitor extends BaseDeltaVisitor
// check the other folders.
if (mOutputPath != null && mOutputPath.isPrefixOf(path)) {
// a resource changed inside the output folder.
if (type == IResource.FILE) {
// just check this is a .class file. Any modification will
// trigger a change in the classes.dex file
@@ -217,6 +226,17 @@ public class ApkDeltaVisitor extends BaseDeltaVisitor
mPackageResources = true;
mMakeFinalPackage = true;
return false;
} else if (mLibFolder != null && mLibFolder.isPrefixOf(path)) {
// inside the native library folder. Test if the changed resource is a .so file.
if (type == IResource.FILE &&
path.getFileExtension().equalsIgnoreCase(AndroidConstants.EXT_NATIVE_LIB)) {
mMakeFinalPackage = true;
return false; // return false for file.
}
// for folders, return true only if we don't already know we have to make the
// final package.
return mMakeFinalPackage == false;
} else {
// we are in a folder that is neither the resource folders, nor the output.
// check against all the source folders, unless we already know we need to do

View File

@@ -806,12 +806,6 @@ abstract class BaseBuilder extends IncrementalProjectBuilder {
// get the IPath
IPath path = e.getPath();
// get the file name. if it's the framework jar, we ignore that file.
// since we now use classpath container, this is here for legacy purpose only.
if (AndroidConstants.FN_FRAMEWORK_LIBRARY.equals(path.lastSegment())) {
continue;
}
// check the name ends with .jar
if (AndroidConstants.EXT_JAR.equalsIgnoreCase(path.getFileExtension())) {
boolean local = false;

View File

@@ -17,6 +17,7 @@
package com.android.ide.eclipse.adt.build;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.sdk.LoadStatus;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IStatus;
@@ -47,9 +48,6 @@ public final class DexWrapper {
private static DexWrapper sWrapper;
/** Status for the Loading of the dex jar file */
public enum LoadStatus { LOADING, LOADED, FAILED }
private static LoadStatus sLoadStatus = LoadStatus.LOADING;
private Method mRunMethod;

View File

@@ -18,14 +18,16 @@ 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.editors.java.ReadOnlyJavaEditor;
import com.android.ide.eclipse.adt.project.FixLaunchConfig;
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.AndroidManifestHelper;
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 org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
@@ -44,7 +46,6 @@ import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Path;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.ui.ide.IDE;
import java.io.IOException;
import java.util.ArrayList;
@@ -130,7 +131,6 @@ public class PreCompilerBuilder extends BaseBuilder {
mSource);
try {
mNewFile.setDerived(true);
IDE.setDefaultEditor(mNewFile, ReadOnlyJavaEditor.ID);
} 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.
@@ -266,8 +266,14 @@ public class PreCompilerBuilder extends BaseBuilder {
// record the state
mCompileResources |= dv.getCompileResources();
mergeAidlFileModifications(dv.getAidlToCompile(),
dv.getAidlToRemove());
// handle aidl modification
if (dv.getFullAidlRecompilation()) {
buildAidlCompilationList(project, sourceList);
} else {
mergeAidlFileModifications(dv.getAidlToCompile(),
dv.getAidlToRemove());
}
// if there was some XML errors, we just return w/o doing
// anything since we've put some markers in the files anyway.
@@ -290,6 +296,12 @@ public class PreCompilerBuilder extends BaseBuilder {
// At this point we have stored what needs to be build, so we can
// do some high level test and abort if needed.
// check if we have finished loading the SDK.
if (AdtPlugin.getDefault().getSdkLoadStatus(javaProject) != LoadStatus.LOADED) {
// we exit silently
return null;
}
// check the compiler compliance level, not displaying the error message
// since this is not the first builder.
if (ProjectHelper.checkCompilerCompliance(getProject())
@@ -302,13 +314,19 @@ public class PreCompilerBuilder extends BaseBuilder {
// Check that the SDK directory has been setup.
String osSdkFolder = AdtPlugin.getOsSdkFolder();
if (osSdkFolder.length() == 0) {
if (osSdkFolder == null || osSdkFolder.length() == 0) {
AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, project,
Messages.No_SDK_Setup_Error);
markProject(AdtConstants.MARKER_ADT, Messages.No_SDK_Setup_Error,
IMarker.SEVERITY_ERROR);
return null;
}
IAndroidTarget projectTarget = Sdk.getCurrent().getTarget(project);
if (projectTarget == null) {
// no target. error has been output by the container initializer: exit silently.
return null;
}
// get the manifest file
IFile manifest = AndroidManifestHelper.getManifest(project);
@@ -448,7 +466,7 @@ public class PreCompilerBuilder extends BaseBuilder {
array.add("-S"); //$NON-NLS-1$
array.add(osResPath);
array.add("-I"); //$NON-NLS-1$
array.add(AdtPlugin.getOsAbsoluteFramework());
array.add(projectTarget.getPath(IAndroidTarget.ANDROID_JAR));
if (AdtPlugin.getBuildVerbosity() == AdtConstants.BUILD_VERBOSE) {
StringBuilder sb = new StringBuilder();
@@ -563,6 +581,8 @@ public class PreCompilerBuilder extends BaseBuilder {
deleteObsoleteGeneratedClass(AndroidConstants.FN_MANIFEST_CLASS,
mManifestPackageSourceFolder, mManifestPackage);
}
// FIXME: delete all java generated from aidl.
}
@Override
@@ -736,12 +756,14 @@ public class PreCompilerBuilder extends BaseBuilder {
if (mAidlToCompile.size() == 0 && mAidlToRemove.size() == 0) {
return false;
}
// create the command line
String[] command = new String[4 + sourceFolders.size() + (folderAidlPath != null ? 1 : 0)];
int index = 0;
int aidlIndex;
command[index++] = AdtPlugin.getOsAbsoluteAidl();
command[index++] = "-p" + AdtPlugin.getOsAbsoluteFrameworkAidl(); //$NON-NLS-1$
command[aidlIndex = index++] = "-p"; //$NON-NLS-1$
if (folderAidlPath != null) {
command[index++] = "-p" + folderAidlPath; //$NON-NLS-1$
}
@@ -813,6 +835,8 @@ public class PreCompilerBuilder extends BaseBuilder {
prepareFileForExternalModification(javaFile);
// 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;
@@ -922,7 +946,8 @@ public class PreCompilerBuilder extends BaseBuilder {
}
/**
* Goes through the buildpath and fills the list of aidl files to compile.
* 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.
*/
@@ -977,7 +1002,7 @@ public class PreCompilerBuilder extends BaseBuilder {
scanContainerForAidl((IFolder)r);
break;
default:
// this would mean it's a project or the workspaceroot
// this would mean it's a project or the workspace root
// which is unlikely to happen. we do nothing
break;
}

View File

@@ -42,6 +42,7 @@ import java.util.ArrayList;
class PreCompilerDeltaVisitor extends BaseDeltaVisitor implements
IResourceDeltaVisitor {
// Result fields.
/**
* 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
@@ -50,6 +51,22 @@ class PreCompilerDeltaVisitor extends BaseDeltaVisitor implements
*/
private boolean mCompileResources = false;
/** List of .aidl files found that are modified or new. */
private final ArrayList<IFile> mAidlToCompile = new ArrayList<IFile>();
/** List of .aidl files that have been removed. */
private final ArrayList<IFile> mAidlToRemove = new ArrayList<IFile>();
/** 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;
/** Application Pacakge, gathered from the parsing of the manifest */
private String mJavaPackage = null;
// Internal usage fields.
/**
* In Resource folder flag. This allows us to know if we're in the
* resource folder.
@@ -65,14 +82,6 @@ class PreCompilerDeltaVisitor extends BaseDeltaVisitor implements
/** List of source folders. */
private ArrayList<IPath> mSourceFolders;
/** List of .aidl files found that are modified or new. */
private final ArrayList<IFile> mAidlToCompile = new ArrayList<IFile>();
/** List of .aidl files that have been removed. */
private final ArrayList<IFile> mAidlToRemove = new ArrayList<IFile>();
private boolean mCheckedManifestXml = false;
private String mJavaPackage = null;
public PreCompilerDeltaVisitor(BaseBuilder builder, ArrayList<IPath> sourceFolders) {
super(builder);
@@ -90,6 +99,10 @@ class PreCompilerDeltaVisitor extends BaseDeltaVisitor implements
public ArrayList<IFile> getAidlToRemove() {
return mAidlToRemove;
}
public boolean getFullAidlRecompilation() {
return mFullAidlCompilation;
}
/**
* Returns whether the manifest file was parsed/checked for error during the resource delta
@@ -100,7 +113,7 @@ class PreCompilerDeltaVisitor extends BaseDeltaVisitor implements
}
/**
* Returns the manifest package if the manifest was checked.
* Returns the manifest package if the manifest was checked/parsed.
* <p/>
* This can return null in two cases:
* <ul>
@@ -169,11 +182,14 @@ 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;
}
}
// at this point we can either be in the source folder or in the
// resource foler or in a different folder that contains a source
// resource folder or in a different folder that contains a source
// folder.
// This is due to not all source folder being src/. Some could be
// something/somethingelse/src/
@@ -227,7 +243,8 @@ class PreCompilerDeltaVisitor extends BaseDeltaVisitor implements
PreCompilerBuilder.PROPERTY_ANDROID_CONFLICT, true);
}
// we add it anyway so that we can try to compile it every time.
// we add it anyway so that we can try to compile it at every compilation
// until the conflict is fixed.
mAidlToCompile.add(file);
} else {
@@ -245,6 +262,8 @@ class PreCompilerDeltaVisitor extends BaseDeltaVisitor implements
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

View File

@@ -62,13 +62,10 @@ public class ResourceManagerBuilder extends IncrementalProjectBuilder {
switch (res) {
case ProjectHelper.COMPILER_COMPLIANCE_LEVEL:
errorMessage = Messages.Requires_Compiler_Compliance_5;
return null;
case ProjectHelper.COMPILER_COMPLIANCE_SOURCE:
errorMessage = Messages.Requires_Source_Compatibility_5;
return null;
case ProjectHelper.COMPILER_COMPLIANCE_CODEGEN_TARGET:
errorMessage = Messages.Requires_Class_Compatibility_5;
return null;
}
if (errorMessage != null) {

View File

@@ -30,9 +30,9 @@ 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.debug.ui.SkinRepository;
import com.android.ide.eclipse.adt.project.ProjectHelper;
import com.android.ide.eclipse.common.project.AndroidManifestHelper;
import com.android.sdklib.SdkConstants;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
@@ -102,6 +102,9 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener
/** 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;
/**
@@ -126,8 +129,8 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener
/** Basic constructor with activity and package info. */
public DelayedLaunchInfo(IProject project, String packageName, String activity,
IFile pack, Boolean debuggable, int launchAction, AndroidLaunch launch,
IProgressMonitor monitor) {
IFile pack, Boolean debuggable, int requiredApiVersionNumber, int launchAction,
AndroidLaunch launch, IProgressMonitor monitor) {
mProject = project;
mPackageName = packageName;
mActivity = activity;
@@ -136,6 +139,7 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener
mLaunch = launch;
mMonitor = monitor;
mDebuggable = debuggable;
mRequiredApiVersionNumber = requiredApiVersionNumber;
}
}
@@ -256,13 +260,10 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener
try {
mSkin = config.getAttribute(LaunchConfigDelegate.ATTR_SKIN, mSkin);
if (mSkin == null) {
mSkin = SkinRepository.getInstance().checkSkin(
LaunchConfigDelegate.DEFAULT_SKIN);
} else {
mSkin = SkinRepository.getInstance().checkSkin(mSkin);
mSkin = SdkConstants.SKIN_DEFAULT;
}
} catch (CoreException e) {
mSkin = SkinRepository.getInstance().checkSkin(LaunchConfigDelegate.DEFAULT_SKIN);
mSkin = SdkConstants.SKIN_DEFAULT;
}
int index = LaunchConfigDelegate.DEFAULT_SPEED;
@@ -539,7 +540,7 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener
LaunchConfigDelegate.DEFAULT_DELAY);
// default skin
wc.setAttribute(LaunchConfigDelegate.ATTR_SKIN, LaunchConfigDelegate.DEFAULT_SKIN);
wc.setAttribute(LaunchConfigDelegate.ATTR_SKIN, SdkConstants.SKIN_DEFAULT);
// default wipe data mode
wc.setAttribute(LaunchConfigDelegate.ATTR_WIPE_DATA,
@@ -599,13 +600,14 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener
* defined by <code>ILaunchManager</code> - <code>RUN_MODE</code> or
* <code>DEBUG_MODE</code>.
* @param apk the resource to the apk to launch.
* @param debuggable
* @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, String activity,
String packageName, Boolean debuggable, int requiredApiVersionNumber, String activity,
final AndroidLaunchConfiguration config, final AndroidLaunch launch,
IProgressMonitor monitor) {
@@ -619,7 +621,8 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener
// create the launch info
final DelayedLaunchInfo launchInfo = new DelayedLaunchInfo(project, packageName,
activity, apk, debuggable, config.mLaunchAction, launch, monitor);
activity, apk, debuggable, requiredApiVersionNumber, config.mLaunchAction,
launch, monitor);
// set the debug mode
launchInfo.mDebugMode = mode.equals(ILaunchManager.DEBUG_MODE);
@@ -711,6 +714,7 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener
// stop the launch and return
mWaitingForEmulatorLaunches.remove(launchInfo);
AdtPlugin.printErrorToConsole(project, "Launch canceled!");
launch.stopLaunch();
return;
}
@@ -761,31 +765,63 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener
}
}
private void checkBuildInfo(DelayedLaunchInfo launchInfo, Device device) {
/**
* Checks the build information, and returns whether the launch should continue.
* <p/>The value tested are:
* <ul>
* <li>Minimum API version requested by the application. If the target device does not match,
* the launch is canceled.</li>
* <li>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"</li>
* <ul>
* @param launchInfo
* @param device
* @return
*/
private boolean checkBuildInfo(DelayedLaunchInfo launchInfo, Device device) {
if (device != null) {
// get the SDK build
String sdkBuild = AdtPlugin.getSdkApiVersion();
// can only complain if the sdkBuild is known
if (sdkBuild != null) {
String deviceVersion = device.getProperty(Device.PROP_BUILD_VERSION);
if (deviceVersion == null) {
AdtPlugin.printToConsole(launchInfo.mProject, "WARNING: Unknown device API version!");
} else {
if (sdkBuild.equals(deviceVersion) == false) {
// TODO do a proper check, including testing the content of the uses-sdk string in the manifest to detect real incompatibility.
String msg = String.format(
"WARNING: Device API version (%1$s) does not match SDK API version (%2$s)",
deviceVersion, sdkBuild);
AdtPlugin.printErrorToConsole(launchInfo.mProject, msg);
}
}
} else {
AdtPlugin.printToConsole(launchInfo.mProject, "WARNING: Unknown SDK API version!");
// 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);
@@ -818,6 +854,8 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener
}
}
}
return true;
}
/**
@@ -829,10 +867,16 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener
* @return true if succeed
*/
private boolean simpleLaunch(DelayedLaunchInfo launchInfo, Device device) {
checkBuildInfo(launchInfo, 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;
}
@@ -1127,6 +1171,8 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener
} 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
@@ -1139,25 +1185,20 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener
private boolean launchEmulator(AndroidLaunchConfiguration config) {
// split the custom command line in segments
String[] segs;
ArrayList<String> customArgs = new ArrayList<String>();
boolean has_wipe_data = false;
if (config.mEmulatorCommandLine != null && config.mEmulatorCommandLine.length() > 0) {
segs = config.mEmulatorCommandLine.split("\\s+"); //$NON-NLS-1$
String[] segments = config.mEmulatorCommandLine.split("\\s+"); //$NON-NLS-1$
// we need to remove the empty strings
ArrayList<String> array = new ArrayList<String>();
for (String s : segs) {
for (String s : segments) {
if (s.length() > 0) {
array.add(s);
customArgs.add(s);
if (!has_wipe_data && s.equals(FLAG_WIPE_DATA)) {
has_wipe_data = true;
}
}
}
segs = array.toArray(new String[array.size()]);
} else {
segs = new String[0];
}
boolean needs_wipe_data = config.mWipeData && !has_wipe_data;
@@ -1167,30 +1208,38 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener
}
}
boolean needs_no_boot_anim = config.mNoBootAnim;
// build the command line based on the available parameters.
ArrayList<String> list = new ArrayList<String>();
list.add(AdtPlugin.getOsAbsoluteEmulator());
if (config.mSkin != null) {
list.add(FLAG_SKIN);
list.add(config.mSkin);
}
if (config.mNetworkSpeed != null) {
list.add(FLAG_NETSPEED);
list.add(config.mNetworkSpeed);
}
if (config.mNetworkDelay != null) {
list.add(FLAG_NETDELAY);
list.add(config.mNetworkDelay);
}
// get the command line
String[] command = new String[7 + segs.length +
(needs_wipe_data ? 1 : 0) +
(needs_no_boot_anim ? 1 : 0)];
int index = 0;
command[index++] = AdtPlugin.getOsAbsoluteEmulator();
command[index++] = FLAG_SKIN; //$NON-NLS-1$
command[index++] = config.mSkin;
command[index++] = FLAG_NETSPEED; //$NON-NLS-1$
command[index++] = config.mNetworkSpeed;
command[index++] = FLAG_NETDELAY; //$NON-NLS-1$
command[index++] = config.mNetworkDelay;
if (needs_wipe_data) {
command[index++] = FLAG_WIPE_DATA;
list.add(FLAG_WIPE_DATA);
}
if (needs_no_boot_anim) {
command[index++] = FLAG_NO_BOOT_ANIM;
}
for (String s : segs) {
command[index++] = s;
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);
@@ -1278,12 +1327,11 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener
/**
* Launch a new thread that connects a remote debugger on the specified port.
* @param debugPort The port to connect the debugger to
* @param launch The associated AndroidLaunch object.
* @param androidLaunch The associated AndroidLaunch object.
* @param monitor A Progress monitor
* @see connectRemoveDebugger()
*/
public static void launchRemoteDebugger(final ILaunchConfiguration config,
final int debugPort, final AndroidLaunch androidLaunch,
public static void launchRemoteDebugger( final int debugPort, final AndroidLaunch androidLaunch,
final IProgressMonitor monitor) {
new Thread("Debugger connection") { //$NON-NLS-1$
@Override
@@ -1342,13 +1390,14 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener
synchronized (sListLock) {
// look if there's an app waiting for a device
if (mWaitingForEmulatorLaunches.size() > 0) {
// remove first item from the list
// 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 it its device
// give the launch item its device for later use.
launchInfo.mDevice = device;
// and move it to the other list
mWaitingForReadyEmulatorList.add(launchInfo);
@@ -1433,12 +1482,23 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener
// look for application waiting for home
synchronized (sListLock) {
boolean foundMatch = false;
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'",
@@ -1448,16 +1508,14 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener
if (syncApp(launchInfo, device)) {
// application package is sync'ed, lets attempt to launch it.
launchApp(launchInfo, device);
// if we haven't checked the device build info, lets do it here
if (foundMatch == false) {
foundMatch = true;
checkBuildInfo(launchInfo, device);
}
} else {
// failure! Cancel and return
AdtPlugin.printErrorToConsole(launchInfo.mProject,
"Launch canceled!");
launchInfo.mLaunch.stopLaunch();
}
break;
} else {
i++;
}
@@ -1530,6 +1588,8 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener
}
} catch (CoreException e) {
// well something went wrong.
AdtPlugin.printErrorToConsole(launchInfo.mProject,
String.format("Launch error: %s", e.getMessage()));
// stop the launch
launchInfo.mLaunch.stopLaunch();
}

View File

@@ -83,11 +83,6 @@ public class LaunchConfigDelegate extends LaunchConfigurationDelegate {
/** Skin to be used to launch the emulator */
public static final String ATTR_SKIN = AdtPlugin.PLUGIN_ID + ".skin"; //$NON-NLS-1$
/**
* Name of the default Skin.
*/
public static final String DEFAULT_SKIN = "HVGA"; //$NON-NLS-1$
public static final String ATTR_SPEED = AdtPlugin.PLUGIN_ID + ".speed"; //$NON-NLS-1$
/**
@@ -138,8 +133,7 @@ public class LaunchConfigDelegate extends LaunchConfigurationDelegate {
// 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(configuration,
debugPort, androidLaunch, monitor);
AndroidLaunchController.launchRemoteDebugger(debugPort, androidLaunch, monitor);
return;
}
@@ -302,7 +296,8 @@ public class LaunchConfigDelegate extends LaunchConfigurationDelegate {
// everything seems fine, we ask the launch controller to handle
// the rest
controller.launch(project, mode, applicationPackage, manifestParser.getPackage(),
manifestParser.getDebuggable(), activityName, config, androidLaunch, monitor);
manifestParser.getDebuggable(), manifestParser.getApiLevelRequirement(),
activityName, config, androidLaunch, monitor);
}
@Override

View File

@@ -18,13 +18,19 @@ 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.debug.ui.SkinRepository.Skin;
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.SdkConstants;
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;
@@ -82,6 +88,8 @@ public class EmulatorConfigTab extends AbstractLaunchConfigurationTab {
private Button mNoBootAnimButton;
private IAndroidTarget mTarget;
/**
* Returns the emulator ready speed option value.
* @param value The index of the combo selection.
@@ -169,12 +177,6 @@ public class EmulatorConfigTab extends AbstractLaunchConfigurationTab {
new Label(mEmulatorOptionsGroup, SWT.NONE).setText("Screen Size:");
mSkinCombo = new Combo(mEmulatorOptionsGroup, SWT.READ_ONLY);
Skin[] skins = SkinRepository.getInstance().getSkins();
if (skins != null) {
for (Skin skin : skins) {
mSkinCombo.add(skin.getDescription());
}
}
mSkinCombo.addSelectionListener(new SelectionAdapter() {
// called when selection changes
@Override
@@ -182,7 +184,6 @@ public class EmulatorConfigTab extends AbstractLaunchConfigurationTab {
updateLaunchConfigurationDialog();
}
});
mSkinCombo.pack();
// network options
new Label(mEmulatorOptionsGroup, SWT.NONE).setText("Network Speed:");
@@ -287,6 +288,42 @@ public class EmulatorConfigTab extends AbstractLaunchConfigurationTab {
}
mAutoTargetButton.setSelection(value);
mManualTargetButton.setSelection(!value);
// look for the project name to get its target.
String projectName = "";
try {
projectName = configuration.getAttribute(
IJavaLaunchConfigurationConstants.ATTR_PROJECT_NAME, projectName);
} catch (CoreException ce) {
}
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(projectName)) {
project = p.getProject();
break;
}
}
}
mSkinCombo.removeAll();
if (project != null) {
mTarget = Sdk.getCurrent().getTarget(project);
if (mTarget != null) {
String[] skins = mTarget.getSkins();
if (skins != null) {
for (String skin : skins) {
mSkinCombo.add(skin);
}
mSkinCombo.pack();
}
}
}
value = LaunchConfigDelegate.DEFAULT_WIPE_DATA;
try {
@@ -307,16 +344,18 @@ public class EmulatorConfigTab extends AbstractLaunchConfigurationTab {
int index = -1;
try {
String skin = configuration.getAttribute(LaunchConfigDelegate.ATTR_SKIN, (String)null);
if (skin != null) {
index = getSkinIndex(skin);
if (skin == null) {
skin = SdkConstants.SKIN_DEFAULT;
}
index = getSkinIndex(skin);
} catch (CoreException e) {
index = getSkinIndex(SkinRepository.getInstance().checkSkin(
LaunchConfigDelegate.DEFAULT_SKIN));
index = getSkinIndex(SdkConstants.SKIN_DEFAULT);
}
if (index == -1) {
mSkinCombo.clearSelection();
mSkinCombo.select(0);
updateLaunchConfigurationDialog();
} else {
mSkinCombo.select(index);
}
@@ -387,7 +426,7 @@ public class EmulatorConfigTab extends AbstractLaunchConfigurationTab {
configuration.setAttribute(LaunchConfigDelegate.ATTR_TARGET_MODE,
LaunchConfigDelegate.DEFAULT_TARGET_MODE);
configuration.setAttribute(LaunchConfigDelegate.ATTR_SKIN,
LaunchConfigDelegate.DEFAULT_SKIN);
SdkConstants.SKIN_DEFAULT);
configuration.setAttribute(LaunchConfigDelegate.ATTR_SPEED,
LaunchConfigDelegate.DEFAULT_SPEED);
configuration.setAttribute(LaunchConfigDelegate.ATTR_DELAY,
@@ -403,11 +442,31 @@ public class EmulatorConfigTab extends AbstractLaunchConfigurationTab {
}
private String getSkinNameByIndex(int index) {
return SkinRepository.getInstance().getSkinNameByIndex(index);
if (mTarget != null && index > 0) {
String[] skins = mTarget.getSkins();
if (skins != null && index < skins.length) {
return skins[index];
}
}
return null;
}
private int getSkinIndex(String name) {
return SkinRepository.getInstance().getSkinIndex(name);
if (mTarget != null) {
String[] skins = mTarget.getSkins();
if (skins != null) {
int index = 0;
for (String skin : skins) {
if (skin.equalsIgnoreCase(name)) {
return index;
}
index++;
}
}
}
return -1;
}
}

View File

@@ -1,152 +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.common.AndroidConstants;
import java.io.File;
import java.util.ArrayList;
/**
* Repository for the emulator skins. This class is responsible for parsing the skin folder.
*/
public class SkinRepository {
private final static SkinRepository sInstance = new SkinRepository();
private Skin[] mSkins;
public static class Skin {
String mName;
public Skin(String name) {
mName = name;
}
public String getName() {
return mName;
}
/**
* Returns the human readable description of the skin.
*/
public String getDescription() {
// TODO: parse the skin and output the description.
return mName;
}
}
/**
* Returns the singleton instance.
*/
public static SkinRepository getInstance() {
return sInstance;
}
/**
* Parse the skin folder and build the skin list.
* @param osPath The path of the skin folder.
*/
public void parseFolder(String osPath) {
File skinFolder = new File(osPath);
if (skinFolder.isDirectory()) {
ArrayList<Skin> skinList = new ArrayList<Skin>();
File[] files = skinFolder.listFiles();
for (File skin : files) {
if (skin.isDirectory()) {
// check for layout file
File layout = new File(skin.getPath() + File.separator
+ AndroidConstants.FN_LAYOUT);
if (layout.isFile()) {
// for now we don't parse the content of the layout and
// simply add the directory to the list.
skinList.add(new Skin(skin.getName()));
}
}
}
mSkins = skinList.toArray(new Skin[skinList.size()]);
} else {
mSkins = new Skin[0];
}
}
public Skin[] getSkins() {
return mSkins;
}
/**
* Returns a valid skin folder name. If <code>skin</code> is valid, then it is returned,
* otherwise the first valid skin name is returned.
* @param skin the Skin name to check
* @return a valid skin name or null if there aren't any.
*/
public String checkSkin(String skin) {
if (mSkins != null) {
for (Skin s : mSkins) {
if (s.getName().equals(skin)) {
return skin;
}
}
if (mSkins.length > 0) {
return mSkins[0].getName();
}
}
return null;
}
/**
* Returns the name of a skin by index.
* @param index The index of the skin to return
* @return the skin name of null if the index is invalid.
*/
public String getSkinNameByIndex(int index) {
if (mSkins != null) {
if (index >= 0 && index < mSkins.length) {
return mSkins[index].getName();
}
}
return null;
}
/**
* Returns the index (0 based) of the skin matching the name.
* @param name The name of the skin to look for.
* @return the index of the skin or -1 if the skin was not found.
*/
public int getSkinIndex(String name) {
if (mSkins != null) {
int count = mSkins.length;
for (int i = 0 ; i < count ; i++) {
Skin s = mSkins[i];
if (s.mName.equals(name)) {
return i;
}
}
}
return -1;
}
}

View File

@@ -1,49 +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.editors.java;
import org.eclipse.jdt.ui.PreferenceConstants;
import org.eclipse.jdt.ui.text.IJavaPartitions;
import org.eclipse.jdt.ui.text.JavaSourceViewerConfiguration;
import org.eclipse.jdt.ui.text.JavaTextTools;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.ui.texteditor.AbstractDecoratedTextEditor;
/**
* Read only java editors. This looks like the regular Java editor, except that it
* prevents editing the files. This is used for automatically generated java classes.
*/
public class ReadOnlyJavaEditor extends AbstractDecoratedTextEditor {
public final static String ID =
"com.android.ide.eclipse.adt.editors.java.ReadOnlyJavaEditor"; //$NON-NLS-1$
public ReadOnlyJavaEditor() {
IPreferenceStore javaUiStore = PreferenceConstants.getPreferenceStore();
JavaTextTools jtt = new JavaTextTools(javaUiStore);
JavaSourceViewerConfiguration jsvc = new JavaSourceViewerConfiguration(
jtt.getColorManager(), javaUiStore, this, IJavaPartitions.JAVA_PARTITIONING);
setSourceViewerConfiguration(jsvc);
}
@Override
public boolean isEditable() {
return false;
}
}

View File

@@ -18,6 +18,9 @@ package com.android.ide.eclipse.adt.preferences;
import com.android.ide.eclipse.adt.AdtConstants;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.jarutils.DebugKeyProvider;
import com.android.jarutils.DebugKeyProvider.KeytoolException;
import com.android.prefs.AndroidLocation.AndroidLocationException;
import org.eclipse.jface.preference.BooleanFieldEditor;
import org.eclipse.jface.preference.FieldEditorPreferencePage;
@@ -29,6 +32,13 @@ import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.IWorkbenchPreferencePage;
import java.io.File;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.util.Date;
/**
* Preference page for build options.
*
@@ -75,7 +85,7 @@ public class BuildPreferencePage extends FieldEditorPreferencePage implements
addField(new ReadOnlyFieldEditor(AdtPlugin.PREFS_DEFAULT_DEBUG_KEYSTORE,
Messages.BuildPreferencePage_Default_KeyStore, getFieldEditorParent()));
addField(new FileFieldEditor(AdtPlugin.PREFS_CUSTOM_DEBUG_KEYSTORE,
addField(new KeystoreFieldEditor(AdtPlugin.PREFS_CUSTOM_DEBUG_KEYSTORE,
Messages.BuildPreferencePage_Custom_Keystore, getFieldEditorParent()));
}
@@ -105,4 +115,103 @@ public class BuildPreferencePage extends FieldEditorPreferencePage implements
control.setEditable(false);
}
}
/**
* Custom {@link FileFieldEditor} that checks that the keystore is valid.
*/
private static class KeystoreFieldEditor extends FileFieldEditor {
public KeystoreFieldEditor(String name, String label, Composite parent) {
super(name, label, parent);
setValidateStrategy(VALIDATE_ON_KEY_STROKE);
}
@Override
protected boolean checkState() {
String fileName = getTextControl().getText();
fileName = fileName.trim();
// empty values are considered ok.
if (fileName.length() > 0) {
File file = new File(fileName);
if (file.isFile()) {
// attempt to load the debug key.
try {
DebugKeyProvider provider = new DebugKeyProvider(fileName,
null /* storeType */, null /* key gen output */);
PrivateKey key = provider.getDebugKey();
X509Certificate certificate = (X509Certificate)provider.getCertificate();
if (key == null || certificate == null) {
showErrorMessage("Unable to find debug key in keystore!");
return false;
}
Date today = new Date();
if (certificate.getNotAfter().compareTo(today) < 0) {
showErrorMessage("Certificate is expired!");
return false;
}
if (certificate.getNotBefore().compareTo(today) > 0) {
showErrorMessage("Certificate validity is in the future!");
return false;
}
// we're good!
clearErrorMessage();
return true;
} catch (GeneralSecurityException e) {
handleException(e);
return false;
} catch (IOException e) {
handleException(e);
return false;
} catch (KeytoolException e) {
handleException(e);
return false;
} catch (AndroidLocationException e) {
handleException(e);
return false;
}
} else {
// file does not exist.
showErrorMessage("Not a valid keystore path.");
return false; // Apply/OK must be disabled
}
}
clearErrorMessage();
return true;
}
@Override
public Text getTextControl(Composite parent) {
setValidateStrategy(VALIDATE_ON_KEY_STROKE);
return super.getTextControl(parent);
}
/**
* Set the error message from a {@link Throwable}. If the exception has no message, try
* to get the message from the cause.
* @param t the Throwable.
*/
private void handleException(Throwable t) {
String msg = t.getMessage();
if (t == null) {
Throwable cause = t.getCause();
if (cause != null) {
handleException(cause);
} else {
setErrorMessage("Uknown error when getting the debug key!");
}
return;
}
// valid text, display it.
showErrorMessage(msg);
}
}
}

View File

@@ -17,6 +17,7 @@
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;
@@ -117,7 +118,7 @@ public class CreateAidlImportAction implements IObjectActionDelegate {
// create the file with the parcelables
if (parcelables.size() > 0) {
IPath path = project.getLocation();
path = path.append("/project.aidl"); //$NON-NLS-1$
path = path.append(AndroidConstants.FN_PROJECT_AIDL);
File f = new File(path.toOSString());
if (f.exists() == false) {
@@ -194,7 +195,7 @@ public class CreateAidlImportAction implements IObjectActionDelegate {
IType[] superInterfaces = typeHierarchy.getAllSuperInterfaces(type);
for (IType superInterface : superInterfaces) {
if ("android.os.Parcelable".equals(superInterface.getFullyQualifiedName())) { //$NON-NLS-1$
if (AndroidConstants.CLASS_PARCELABLE.equals(superInterface.getFullyQualifiedName())) {
parcelables.add(type.getFullyQualifiedName());
}
}

View File

@@ -0,0 +1,104 @@
/*
* 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.IFolder;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.viewers.IDecoration;
import org.eclipse.jface.viewers.ILabelDecorator;
import org.eclipse.jface.viewers.ILabelProviderListener;
import org.eclipse.jface.viewers.ILightweightLabelDecorator;
/**
* A {@link ILabelDecorator} associated with an org.eclipse.ui.decorators extension.
* This is used to add android icons in some special folders in the package explorer.
*/
public class FolderDecorator implements ILightweightLabelDecorator {
private ImageDescriptor mDescriptor;
public FolderDecorator() {
mDescriptor = AdtPlugin.getImageDescriptor("/icons/android_project.png");
}
public void decorate(Object element, IDecoration decoration) {
if (element instanceof IFolder) {
IFolder folder = (IFolder)element;
// get the project and make sure this is an android project
IProject project = folder.getProject();
try {
if (project.hasNature(AndroidConstants.NATURE)) {
// check the folder is directly under the project.
if (folder.getParent().getType() == IResource.PROJECT) {
String name = folder.getName();
if (name.equals(AndroidConstants.FD_ASSETS)) {
decorate(decoration, " [Android assets]");
decoration.addOverlay(mDescriptor, IDecoration.TOP_RIGHT);
} else if (name.equals(AndroidConstants.FD_RESOURCES)) {
decorate(decoration, " [Android resources]");
decoration.addOverlay(mDescriptor, IDecoration.TOP_RIGHT);
} else if (name.equals(AndroidConstants.FD_NATIVE_LIBS)) {
decorate(decoration, " [Native Libraries]");
}
}
}
} catch (CoreException e) {
// log the error
AdtPlugin.log(e, "Unable to get nature of project '%s'.", project.getName());
}
}
}
public void decorate(IDecoration decoration, String suffix) {
decoration.addOverlay(mDescriptor, IDecoration.TOP_RIGHT);
// 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"));
}
public boolean isLabelProperty(Object element, String property) {
// at this time return false.
return false;
}
public void addListener(ILabelProviderListener listener) {
// 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
}
}

View File

@@ -0,0 +1,57 @@
/*
* 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.editors.wizards.NewXmlFileWizard;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.wizard.WizardDialog;
import org.eclipse.ui.IObjectActionDelegate;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.IWorkbenchPart;
public class NewXmlFileWizardAction implements IObjectActionDelegate {
private ISelection mSelection;
private IWorkbench mWorkbench;
/**
* @see IObjectActionDelegate#setActivePart(IAction, IWorkbenchPart)
*/
public void setActivePart(IAction action, IWorkbenchPart targetPart) {
mWorkbench = targetPart.getSite().getWorkbenchWindow().getWorkbench();
}
public void run(IAction action) {
if (mSelection instanceof IStructuredSelection) {
IStructuredSelection selection = (IStructuredSelection)mSelection;
// call the new xml file wizard on the current selection.
NewXmlFileWizard wizard = new NewXmlFileWizard();
wizard.init(mWorkbench, selection);
WizardDialog dialog = new WizardDialog(mWorkbench.getDisplay().getActiveShell(),
wizard);
dialog.open();
}
}
public void selectionChanged(IAction action, ISelection selection) {
this.mSelection = selection;
}
}

View File

@@ -33,7 +33,6 @@ import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.QualifiedName;
import org.eclipse.jdt.core.IClasspathAttribute;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.JavaCore;
@@ -177,31 +176,6 @@ public final class ProjectHelper {
return -1;
}
/**
* Check the validity of the javadoc attributes in a classpath entry.
* @param frameworkEntry the classpath entry to check.
* @return true if the javadoc attributes is valid, false otherwise.
*/
public static boolean checkJavaDocPath(IClasspathEntry frameworkEntry) {
// get the list of extra attributes
IClasspathAttribute[] attributes = frameworkEntry.getExtraAttributes();
// and search for the one about the javadoc
for (IClasspathAttribute att : attributes) {
if (IClasspathAttribute.JAVADOC_LOCATION_ATTRIBUTE_NAME.
equals(att.getName())) {
// we found a javadoc attribute. Now we test its value.
// get the expect value
String validJavaDoc = getJavaDocPath(AdtPlugin.getOsAbsoluteFramework());
// now compare and return
return validJavaDoc.equals(att.getValue());
}
}
return false;
}
/**
* Fix the project. This checks the SDK location.
* @param project The project to fix.
@@ -264,14 +238,7 @@ public final class ProjectHelper {
continue;
}
} else if (kind == IClasspathEntry.CPE_CONTAINER) {
IPath containerPath = entry.getPath();
if (AndroidClasspathContainerInitializer.checkOldPath(containerPath)) {
entries = ProjectHelper.removeEntryFromClasspath(entries, i);
// continue, to skip the i++;
continue;
} else if (AndroidClasspathContainerInitializer.checkPath(containerPath)) {
if (AndroidClasspathContainerInitializer.checkPath(entry.getPath())) {
foundContainer = true;
}
}

View File

@@ -18,7 +18,10 @@ package com.android.ide.eclipse.adt.project.export;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.project.ProjectHelper;
import com.android.jarutils.KeystoreHelper;
import com.android.jarutils.SignedJarBuilder;
import com.android.jarutils.DebugKeyProvider.IKeyGenOutput;
import com.android.jarutils.DebugKeyProvider.KeytoolException;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
@@ -27,28 +30,38 @@ import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.wizard.Wizard;
import org.eclipse.jface.wizard.WizardPage;
import org.eclipse.swt.events.VerifyEvent;
import org.eclipse.swt.events.VerifyListener;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.IExportWizard;
import org.eclipse.ui.IWorkbench;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.KeyStore.PrivateKeyEntry;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;
/**
* Export wizard to export an apk signed with a release key/certificate.
*/
public class ExportWizard extends Wizard implements IExportWizard {
public final class ExportWizard extends Wizard implements IExportWizard {
private static final String PROJECT_LOGO_LARGE = "icons/android_large.png"; //$NON-NLS-1$
private static final String PAGE_PRE = "preExportPage"; //$NON-NLS-1$
private static final String PAGE_SIGNING = "signingExportPage"; //$NON-NLS-1$
private static final String PAGE_FINAL = "finalExportPage"; //$NON-NLS-1$
private static final String PAGE_PROJECT_CHECK = "Page_ProjectCheck"; //$NON-NLS-1$
private static final String PAGE_KEYSTORE_SELECTION = "Page_KeystoreSelection"; //$NON-NLS-1$
private static final String PAGE_KEY_CREATION = "Page_KeyCreation"; //$NON-NLS-1$
private static final String PAGE_KEY_SELECTION = "Page_KeySelection"; //$NON-NLS-1$
private static final String PAGE_KEY_CHECK = "Page_KeyCheck"; //$NON-NLS-1$
static final String PROPERTY_KEYSTORE = "keystore"; //$NON-NLS-1$
static final String PROPERTY_ALIAS = "alias"; //$NON-NLS-1$
@@ -59,7 +72,41 @@ public class ExportWizard extends Wizard implements IExportWizard {
*/
static abstract class ExportWizardPage extends WizardPage {
protected boolean mNewProjectReference = true;
/** bit mask constant for project data change event */
protected static final int DATA_PROJECT = 0x001;
/** bit mask constant for keystore data change event */
protected static final int DATA_KEYSTORE = 0x002;
/** bit mask constant for key data change event */
protected static final int DATA_KEY = 0x004;
protected static final VerifyListener sPasswordVerifier = new VerifyListener() {
public void verifyText(VerifyEvent e) {
// verify the characters are valid for password.
int len = e.text.length();
// first limit to 127 characters max
if (len + ((Text)e.getSource()).getText().length() > 127) {
e.doit = false;
return;
}
// now only take non control characters
for (int i = 0 ; i < len ; i++) {
if (e.text.charAt(i) < 32) {
e.doit = false;
return;
}
}
}
};
/**
* Bit mask indicating what changed while the page was hidden.
* @see #DATA_PROJECT
* @see #DATA_KEYSTORE
* @see #DATA_KEY
*/
protected int mProjectDataChanged = 0;
ExportWizardPage(String name) {
super(name);
@@ -72,23 +119,38 @@ public class ExportWizard extends Wizard implements IExportWizard {
super.setVisible(visible);
if (visible) {
onShow();
mNewProjectReference = false;
mProjectDataChanged = 0;
}
}
void newProjectReference() {
mNewProjectReference = true;
final void projectDataChanged(int changeMask) {
mProjectDataChanged |= changeMask;
}
/**
* Calls {@link #setErrorMessage(String)} and {@link #setPageComplete(boolean)} based on a
* {@link Throwable} object.
*/
protected final void onException(Throwable t) {
String message = getExceptionMessage(t);
setErrorMessage(message);
setPageComplete(false);
}
}
private ExportWizardPage mPages[] = new ExportWizardPage[3];
private ExportWizardPage mPages[] = new ExportWizardPage[5];
private IProject mProject;
private String mKeystore;
private String mKeystorePassword;
private boolean mKeystoreCreationMode;
private String mKeyAlias;
private char[] mKeystorePassword;
private char[] mKeyPassword;
private String mKeyPassword;
private int mValidity;
private String mDName;
private PrivateKey mPrivateKey;
private X509Certificate mCertificate;
@@ -97,6 +159,15 @@ public class ExportWizard extends Wizard implements IExportWizard {
private String mApkFilePath;
private String mApkFileName;
private ExportWizardPage mKeystoreSelectionPage;
private ExportWizardPage mKeyCreationPage;
private ExportWizardPage mKeySelectionPage;
private ExportWizardPage mKeyCheckPage;
private boolean mKeyCreationMode;
private List<String> mExistingAliases;
public ExportWizard() {
setHelpAvailable(false); // TODO have help
setWindowTitle("Export Android Application");
@@ -105,46 +176,101 @@ public class ExportWizard extends Wizard implements IExportWizard {
@Override
public void addPages() {
addPage(mPages[0] = new PreExportPage(this, PAGE_PRE));
addPage(mPages[1] = new SigningExportPage(this, PAGE_SIGNING));
addPage(mPages[2] = new FinalExportPage(this, PAGE_FINAL));
addPage(mPages[0] = new ProjectCheckPage(this, PAGE_PROJECT_CHECK));
addPage(mKeystoreSelectionPage = mPages[1] = new KeystoreSelectionPage(this,
PAGE_KEYSTORE_SELECTION));
addPage(mKeyCreationPage = mPages[2] = new KeyCreationPage(this, PAGE_KEY_CREATION));
addPage(mKeySelectionPage = mPages[3] = new KeySelectionPage(this, PAGE_KEY_SELECTION));
addPage(mKeyCheckPage = mPages[4] = new KeyCheckPage(this, PAGE_KEY_CHECK));
}
@Override
public boolean performFinish() {
// first we make sure export is fine if the destination file already exists
File f = new File(mDestinationPath);
if (f.isFile()) {
if (AdtPlugin.displayPrompt("Export Wizard",
"File already exists. Do you want to overwrite it?") == false) {
return false;
}
}
// save the properties
ProjectHelper.saveStringProperty(mProject, PROPERTY_KEYSTORE, mKeystore);
ProjectHelper.saveStringProperty(mProject, PROPERTY_ALIAS, mKeyAlias);
ProjectHelper.saveStringProperty(mProject, PROPERTY_DESTINATION, mDestinationPath);
try {
FileOutputStream fos = new FileOutputStream(mDestinationPath);
SignedJarBuilder builder = new SignedJarBuilder(fos, mPrivateKey, mCertificate);
// get the input file.
FileInputStream fis = new FileInputStream(mApkFilePath);
try {
builder.writeZip(fis, null /* filter */);
} finally {
if (mKeystoreCreationMode || mKeyCreationMode) {
final ArrayList<String> output = new ArrayList<String>();
if (KeystoreHelper.createNewStore(
mKeystore,
null /*storeType*/,
mKeystorePassword,
mKeyAlias,
mKeyPassword,
mDName,
mValidity,
new IKeyGenOutput() {
public void err(String message) {
output.add(message);
}
public void out(String message) {
output.add(message);
}
}) == false) {
// keystore creation error!
displayError(output.toArray(new String[output.size()]));
return false;
}
// keystore is created, now load the private key and certificate.
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
FileInputStream fis = new FileInputStream(mKeystore);
keyStore.load(fis, mKeystorePassword.toCharArray());
fis.close();
PrivateKeyEntry entry = (KeyStore.PrivateKeyEntry)keyStore.getEntry(
mKeyAlias, new KeyStore.PasswordProtection(mKeyPassword.toCharArray()));
if (entry != null) {
mPrivateKey = entry.getPrivateKey();
mCertificate = (X509Certificate)entry.getCertificate();
} else {
// this really shouldn't happen since we now let the user choose the key
// from a list read from the store.
displayError("Could not find key");
return false;
}
}
builder.close();
fos.close();
return true;
// check the private key/certificate again since it may have been created just above.
if (mPrivateKey != null && mCertificate != null) {
FileOutputStream fos = new FileOutputStream(mDestinationPath);
SignedJarBuilder builder = new SignedJarBuilder(fos, mPrivateKey, mCertificate);
// get the input file.
FileInputStream fis = new FileInputStream(mApkFilePath);
try {
builder.writeZip(fis, null /* filter */);
} finally {
fis.close();
}
builder.close();
fos.close();
return true;
}
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
displayError(e);
} catch (NoSuchAlgorithmException e) {
// TODO Auto-generated catch block
e.printStackTrace();
displayError(e);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
displayError(e);
} catch (GeneralSecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
displayError(e);
} catch (KeytoolException e) {
displayError(e);
}
return false;
@@ -152,8 +278,13 @@ public class ExportWizard extends Wizard implements IExportWizard {
@Override
public boolean canFinish() {
// check if we have the apk to resign, the destination location, and either
// a private key/certificate or the creation mode. In creation mode, unless
// all the key/keystore info is valid, the user cannot reach the last page, so there's
// no need to check them again here.
return mApkFilePath != null &&
mPrivateKey != null && mCertificate != null &&
((mPrivateKey != null && mCertificate != null)
|| mKeystoreCreationMode || mKeyCreationMode) &&
mDestinationPath != null;
}
@@ -175,6 +306,22 @@ public class ExportWizard extends Wizard implements IExportWizard {
}
}
ExportWizardPage getKeystoreSelectionPage() {
return mKeystoreSelectionPage;
}
ExportWizardPage getKeyCreationPage() {
return mKeyCreationPage;
}
ExportWizardPage getKeySelectionPage() {
return mKeySelectionPage;
}
ExportWizardPage getKeyCheckPage() {
return mKeyCheckPage;
}
/**
* Returns an image descriptor for the wizard logo.
*/
@@ -192,10 +339,7 @@ public class ExportWizard extends Wizard implements IExportWizard {
mApkFilePath = apkFilePath;
mApkFileName = filename;
// indicate to the page that the project was changed.
for (ExportWizardPage page : mPages) {
page.newProjectReference();
}
updatePageOnChange(ExportWizardPage.DATA_PROJECT);
}
String getApkFilename() {
@@ -206,42 +350,95 @@ public class ExportWizard extends Wizard implements IExportWizard {
mKeystore = path;
mPrivateKey = null;
mCertificate = null;
updatePageOnChange(ExportWizardPage.DATA_KEYSTORE);
}
String getKeystore() {
return mKeystore;
}
void setKeystoreCreationMode(boolean createStore) {
mKeystoreCreationMode = createStore;
updatePageOnChange(ExportWizardPage.DATA_KEYSTORE);
}
boolean getKeystoreCreationMode() {
return mKeystoreCreationMode;
}
void setKeystorePassword(String password) {
mKeystorePassword = password;
mPrivateKey = null;
mCertificate = null;
updatePageOnChange(ExportWizardPage.DATA_KEYSTORE);
}
String getKeystorePassword() {
return mKeystorePassword;
}
void setKeyCreationMode(boolean createKey) {
mKeyCreationMode = createKey;
updatePageOnChange(ExportWizardPage.DATA_KEY);
}
boolean getKeyCreationMode() {
return mKeyCreationMode;
}
void setExistingAliases(List<String> aliases) {
mExistingAliases = aliases;
}
List<String> getExistingAliases() {
return mExistingAliases;
}
void setKeyAlias(String name) {
mKeyAlias = name;
mPrivateKey = null;
mCertificate = null;
updatePageOnChange(ExportWizardPage.DATA_KEY);
}
String getKeyAlias() {
return mKeyAlias;
}
void setKeystorePassword(char[] password) {
mKeystorePassword = password;
mPrivateKey = null;
mCertificate = null;
}
char[] getKeystorePassword() {
return mKeystorePassword;
}
void setKeyPassword(char[] password) {
void setKeyPassword(String password) {
mKeyPassword = password;
mPrivateKey = null;
mCertificate = null;
updatePageOnChange(ExportWizardPage.DATA_KEY);
}
char[] getKeyPassword() {
String getKeyPassword() {
return mKeyPassword;
}
void setValidity(int validity) {
mValidity = validity;
updatePageOnChange(ExportWizardPage.DATA_KEY);
}
int getValidity() {
return mValidity;
}
void setDName(String dName) {
mDName = dName;
updatePageOnChange(ExportWizardPage.DATA_KEY);
}
String getDName() {
return mDName;
}
void setSigningInfo(PrivateKey privateKey, X509Certificate certificate) {
mPrivateKey = privateKey;
mCertificate = certificate;
@@ -251,4 +448,54 @@ public class ExportWizard extends Wizard implements IExportWizard {
mDestinationPath = path;
}
void updatePageOnChange(int changeMask) {
for (ExportWizardPage page : mPages) {
page.projectDataChanged(changeMask);
}
}
private void displayError(String... messages) {
String message = null;
if (messages.length == 1) {
message = messages[0];
} else {
StringBuilder sb = new StringBuilder(messages[0]);
for (int i = 1; i < messages.length; i++) {
sb.append('\n');
sb.append(messages[i]);
}
message = sb.toString();
}
AdtPlugin.displayError("Export Wizard", message);
}
private void displayError(Exception e) {
String message = getExceptionMessage(e);
displayError(message);
AdtPlugin.log(e, "Export Wizard Error");
}
/**
* Returns the {@link Throwable#getMessage()}. If the {@link Throwable#getMessage()} returns
* <code>null</code>, the method is called again on the cause of the Throwable object.
* <p/>If no Throwable in the chain has a valid message, the canonical name of the first
* exception is returned.
*/
private static String getExceptionMessage(Throwable t) {
String message = t.getMessage();
if (message == null) {
Throwable cause = t.getCause();
if (cause != null) {
return getExceptionMessage(cause);
}
// no more cause and still no message. display the first exception.
return cause.getClass().getCanonicalName();
}
return message;
}
}

View File

@@ -1,275 +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.export;
import com.android.ide.eclipse.adt.project.ProjectHelper;
import com.android.ide.eclipse.adt.project.export.ExportWizard.ExportWizardPage;
import org.eclipse.core.resources.IProject;
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.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.FileDialog;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.forms.widgets.FormText;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.UnrecoverableEntryException;
import java.security.KeyStore.PrivateKeyEntry;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Calendar;
public class FinalExportPage extends ExportWizardPage {
private final ExportWizard mWizard;
private PrivateKey mPrivateKey;
private X509Certificate mCertificate;
private Text mDestination;
private Button mBrowseButton;
private boolean mFatalSigningError;
private FormText mDetailText;
protected FinalExportPage(ExportWizard wizard, String pageName) {
super(pageName);
mWizard = wizard;
setTitle("Application Export");
setDescription("Export the signed Application package.");
}
public void createControl(Composite parent) {
setErrorMessage(null);
setMessage(null);
// build the ui.
Composite composite = new Composite(parent, SWT.NULL);
composite.setLayoutData(new GridData(GridData.FILL_BOTH));
GridLayout gl = new GridLayout(3, false);
gl.verticalSpacing *= 3;
composite.setLayout(gl);
GridData gd;
new Label(composite, SWT.NONE).setText("Destination APK file:");
mDestination = new Text(composite, SWT.BORDER);
mDestination.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL));
mDestination.addModifyListener(new ModifyListener() {
public void modifyText(ModifyEvent e) {
onDestinationChange();
}
});
mBrowseButton = new Button(composite, SWT.PUSH);
mBrowseButton.setText("Browse...");
mBrowseButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
FileDialog fileDialog = new FileDialog(mBrowseButton.getShell(), SWT.SAVE);
fileDialog.setText("Destination file name");
fileDialog.setFileName(mWizard.getApkFilename());
String saveLocation = fileDialog.open();
if (saveLocation != null) {
mDestination.setText(saveLocation);
}
}
});
mDetailText = new FormText(composite, SWT.NONE);
mDetailText.setLayoutData(gd = new GridData(GridData.FILL_BOTH));
gd.horizontalSpan = 3;
setControl(composite);
}
@Override
void onShow() {
// fill the texts with information loaded from the project.
if (mNewProjectReference) {
// reset the destination from the content of the project
IProject project = mWizard.getProject();
String destination = ProjectHelper.loadStringProperty(project,
ExportWizard.PROPERTY_DESTINATION);
if (destination != null) {
mDestination.setText(destination);
}
}
// reset the wizard with no key/cert to make it not finishable, unless a valid
// key/cert is found.
mWizard.setSigningInfo(null, null);
try {
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
FileInputStream fis = new FileInputStream(mWizard.getKeystore());
keyStore.load(fis, mWizard.getKeystorePassword());
fis.close();
PrivateKeyEntry entry = (KeyStore.PrivateKeyEntry)keyStore.getEntry(
mWizard.getKeyAlias(),
new KeyStore.PasswordProtection(mWizard.getKeyPassword()));
if (entry != null) {
mPrivateKey = entry.getPrivateKey();
mCertificate = (X509Certificate)entry.getCertificate();
} else {
setErrorMessage("Unable to find key");
setPageComplete(false);
}
} catch (FileNotFoundException e) {
// this was checked at the first previous step and will not happen here, unless
// the file was removed during the export wizard execution.
onException(e);
} catch (KeyStoreException e) {
onException(e);
} catch (NoSuchAlgorithmException e) {
onException(e);
} catch (UnrecoverableEntryException e) {
onException(e);
} catch (CertificateException e) {
onException(e);
} catch (IOException e) {
onException(e);
}
if (mPrivateKey != null && mCertificate != null) {
mFatalSigningError = false;
Calendar expirationCalendar = Calendar.getInstance();
expirationCalendar.setTime(mCertificate.getNotAfter());
Calendar today = Calendar.getInstance();
if (expirationCalendar.before(today)) {
mDetailText.setText(String.format("<form><p>Certificate expired on %s</p></form>",
mCertificate.getNotAfter().toString()),
true /* parseTags */, true /* expandURLs */);
// fatal error = nothing can make the page complete.
mFatalSigningError = true;
setErrorMessage("Certificate is expired!");
setPageComplete(false);
} else {
// valid, key/cert: put it in the wizard so that it can be finished
mWizard.setSigningInfo(mPrivateKey, mCertificate);
StringBuilder sb = new StringBuilder(String.format("<form><p>Certificate expires on %s.</p>",
mCertificate.getNotAfter().toString()));
int expirationYear = expirationCalendar.get(Calendar.YEAR);
int thisYear = today.get(Calendar.YEAR);
if (thisYear + 25 < expirationYear) {
// do nothing
} else {
if (expirationYear == thisYear) {
sb.append("<p>The certificate expires this year!</p>");
} else {
int count = expirationYear-thisYear;
sb.append(String.format("<p>The Certificate expires in %1$s %2$s!</p>",
count, count == 1 ? "year" : "years"));
}
sb.append("<p>Make sure the certificate is valid for the planned lifetime of the product.</p>");
sb.append("<p>If the certificate expires, you will be forced to sign your application with a different one.</p>");
sb.append("<p>Applications cannot be upgraded if their certificate changes from one version to another, ");
sb.append("forcing a full uninstall/install, which will make the user lose his/her data.</p>");
sb.append("<p>Android Market currently requires certificates to be valid until 2033.</p>");
sb.append("</form>");
}
mDetailText.setText(sb.toString(), true /* parseTags */, true /* expandURLs */);
}
mDetailText.getParent().layout();
} else {
// fatal error = nothing can make the page complete.
mFatalSigningError = true;
}
onDestinationChange();
}
private void onDestinationChange() {
if (mFatalSigningError == false) {
String path = mDestination.getText().trim();
if (path.length() == 0) {
setErrorMessage("Enter destination for the APK file.");
mWizard.setDestination(null); // this is to reset canFinish in the wizard
setPageComplete(false);
} else {
File file = new File(path);
if (file.isDirectory()) {
setErrorMessage("Destination is a directory!");
mWizard.setDestination(null); // this is to reset canFinish in the wizard
setPageComplete(false);
} else {
File parentFile = file.getParentFile();
if (parentFile == null || parentFile.isDirectory() == false) {
setErrorMessage("Not a valid directory.");
mWizard.setDestination(null); // this is to reset canFinish in the wizard
setPageComplete(false);
} else {
mWizard.setDestination(path);
setErrorMessage(null);
setPageComplete(true);
}
}
}
}
}
/**
* Calls {@link #setErrorMessage(String)} and {@link #setPageComplete(boolean)} based on a
* {@link Throwable} object. If the {@link Throwable#getMessage()} returns <code>null</code>,
* the method is called again on the cause of the Throwable.
*/
private void onException(Throwable t) {
String message = t.getMessage();
if (message == null) {
Throwable cause = t.getCause();
if (cause != null) {
onException(cause);
} else {
// no more cause and still no message. display the first exception.
setErrorMessage(cause.getClass().getCanonicalName());
setPageComplete(false);
}
return;
}
setErrorMessage(message);
setPageComplete(false);
}
}

View File

@@ -0,0 +1,291 @@
/*
* 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.export;
import com.android.ide.eclipse.adt.project.ProjectHelper;
import com.android.ide.eclipse.adt.project.export.ExportWizard.ExportWizardPage;
import org.eclipse.core.resources.IProject;
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.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.FileDialog;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.forms.widgets.FormText;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.UnrecoverableEntryException;
import java.security.KeyStore.PrivateKeyEntry;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Calendar;
/**
* Final page of the wizard that checks the key and ask for the ouput location.
*/
final class KeyCheckPage extends ExportWizardPage {
private final ExportWizard mWizard;
private PrivateKey mPrivateKey;
private X509Certificate mCertificate;
private Text mDestination;
private boolean mFatalSigningError;
private FormText mDetailText;
protected KeyCheckPage(ExportWizard wizard, String pageName) {
super(pageName);
mWizard = wizard;
setTitle("Destination and key/certificate checks");
setDescription(""); // TODO
}
public void createControl(Composite parent) {
setErrorMessage(null);
setMessage(null);
// build the ui.
Composite composite = new Composite(parent, SWT.NULL);
composite.setLayoutData(new GridData(GridData.FILL_BOTH));
GridLayout gl = new GridLayout(3, false);
gl.verticalSpacing *= 3;
composite.setLayout(gl);
GridData gd;
new Label(composite, SWT.NONE).setText("Destination APK file:");
mDestination = new Text(composite, SWT.BORDER);
mDestination.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL));
mDestination.addModifyListener(new ModifyListener() {
public void modifyText(ModifyEvent e) {
onDestinationChange();
}
});
final Button browseButton = new Button(composite, SWT.PUSH);
browseButton.setText("Browse...");
browseButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
FileDialog fileDialog = new FileDialog(browseButton.getShell(), SWT.SAVE);
fileDialog.setText("Destination file name");
fileDialog.setFileName(mWizard.getApkFilename());
String saveLocation = fileDialog.open();
if (saveLocation != null) {
mDestination.setText(saveLocation);
}
}
});
mDetailText = new FormText(composite, SWT.NONE);
mDetailText.setLayoutData(gd = new GridData(GridData.FILL_BOTH));
gd.horizontalSpan = 3;
setControl(composite);
}
@Override
void onShow() {
// fill the texts with information loaded from the project.
if ((mProjectDataChanged & DATA_PROJECT) != 0) {
// reset the destination from the content of the project
IProject project = mWizard.getProject();
String destination = ProjectHelper.loadStringProperty(project,
ExportWizard.PROPERTY_DESTINATION);
if (destination != null) {
mDestination.setText(destination);
}
}
// if anything change we basically reload the data.
if (mProjectDataChanged != 0) {
mFatalSigningError = false;
// reset the wizard with no key/cert to make it not finishable, unless a valid
// key/cert is found.
mWizard.setSigningInfo(null, null);
if (mWizard.getKeystoreCreationMode() || mWizard.getKeyCreationMode()) {
int validity = mWizard.getValidity();
StringBuilder sb = new StringBuilder(
String.format("<form><p>Certificate expires in %d years.</p>",
validity));
if (validity < 25) {
sb.append("<p>Make sure the certificate is valid for the planned lifetime of the product.</p>");
sb.append("<p>If the certificate expires, you will be forced to sign your application with a different one.</p>");
sb.append("<p>Applications cannot be upgraded if their certificate changes from one version to another, ");
sb.append("forcing a full uninstall/install, which will make the user lose his/her data.</p>");
sb.append("<p>Android Market currently requires certificates to be valid until 2033.</p>");
}
sb.append("</form>");
mDetailText.setText(sb.toString(), true /* parseTags */, true /* expandURLs */);
} else {
try {
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
FileInputStream fis = new FileInputStream(mWizard.getKeystore());
keyStore.load(fis, mWizard.getKeystorePassword().toCharArray());
fis.close();
PrivateKeyEntry entry = (KeyStore.PrivateKeyEntry)keyStore.getEntry(
mWizard.getKeyAlias(),
new KeyStore.PasswordProtection(
mWizard.getKeyPassword().toCharArray()));
if (entry != null) {
mPrivateKey = entry.getPrivateKey();
mCertificate = (X509Certificate)entry.getCertificate();
} else {
setErrorMessage("Unable to find key.");
setPageComplete(false);
}
} catch (FileNotFoundException e) {
// this was checked at the first previous step and will not happen here, unless
// the file was removed during the export wizard execution.
onException(e);
} catch (KeyStoreException e) {
onException(e);
} catch (NoSuchAlgorithmException e) {
onException(e);
} catch (UnrecoverableEntryException e) {
onException(e);
} catch (CertificateException e) {
onException(e);
} catch (IOException e) {
onException(e);
}
if (mPrivateKey != null && mCertificate != null) {
Calendar expirationCalendar = Calendar.getInstance();
expirationCalendar.setTime(mCertificate.getNotAfter());
Calendar today = Calendar.getInstance();
if (expirationCalendar.before(today)) {
mDetailText.setText(String.format(
"<form><p>Certificate expired on %s</p></form>",
mCertificate.getNotAfter().toString()),
true /* parseTags */, true /* expandURLs */);
// fatal error = nothing can make the page complete.
mFatalSigningError = true;
setErrorMessage("Certificate is expired.");
setPageComplete(false);
} else {
// valid, key/cert: put it in the wizard so that it can be finished
mWizard.setSigningInfo(mPrivateKey, mCertificate);
StringBuilder sb = new StringBuilder(String.format(
"<form><p>Certificate expires on %s.</p>",
mCertificate.getNotAfter().toString()));
int expirationYear = expirationCalendar.get(Calendar.YEAR);
int thisYear = today.get(Calendar.YEAR);
if (thisYear + 25 < expirationYear) {
// do nothing
} else {
if (expirationYear == thisYear) {
sb.append("<p>The certificate expires this year.</p>");
} else {
int count = expirationYear-thisYear;
sb.append(String.format(
"<p>The Certificate expires in %1$s %2$s.</p>",
count, count == 1 ? "year" : "years"));
}
sb.append("<p>Make sure the certificate is valid for the planned lifetime of the product.</p>");
sb.append("<p>If the certificate expires, you will be forced to sign your application with a different one.</p>");
sb.append("<p>Applications cannot be upgraded if their certificate changes from one version to another, ");
sb.append("forcing a full uninstall/install, which will make the user lose his/her data.</p>");
sb.append("<p>Android Market currently requires certificates to be valid until 2033.</p>");
}
sb.append("</form>");
mDetailText.setText(sb.toString(), true /* parseTags */, true /* expandURLs */);
}
mDetailText.getParent().layout();
} else {
// fatal error = nothing can make the page complete.
mFatalSigningError = true;
}
}
}
onDestinationChange();
}
private void onDestinationChange() {
if (mFatalSigningError == false) {
// reset messages for now.
setErrorMessage(null);
setMessage(null);
String path = mDestination.getText().trim();
if (path.length() == 0) {
setErrorMessage("Enter destination for the APK file.");
mWizard.setDestination(null); // this is to reset canFinish in the wizard
setPageComplete(false);
return;
}
File file = new File(path);
if (file.isDirectory()) {
setErrorMessage("Destination is a directory.");
mWizard.setDestination(null); // this is to reset canFinish in the wizard
setPageComplete(false);
return;
}
File parentFile = file.getParentFile();
if (parentFile == null || parentFile.isDirectory() == false) {
setErrorMessage("Not a valid directory.");
mWizard.setDestination(null); // this is to reset canFinish in the wizard
setPageComplete(false);
return;
}
// no error, set the destination in the wizard.
mWizard.setDestination(path);
setPageComplete(true);
// However, we should also test if the file already exists.
if (file.isFile()) {
setMessage("Destination file already exists.", WARNING);
}
}
}
}

View File

@@ -0,0 +1,332 @@
/*
* 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.export;
import com.android.ide.eclipse.adt.project.ProjectHelper;
import com.android.ide.eclipse.adt.project.export.ExportWizard.ExportWizardPage;
import org.eclipse.core.resources.IProject;
import org.eclipse.jface.wizard.IWizardPage;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.VerifyEvent;
import org.eclipse.swt.events.VerifyListener;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Text;
import java.util.List;
/**
* Key creation page.
*/
final class KeyCreationPage extends ExportWizardPage {
private final ExportWizard mWizard;
private Text mAlias;
private Text mKeyPassword;
private Text mKeyPassword2;
private Text mCnField;
private boolean mDisableOnChange = false;
private Text mOuField;
private Text mOField;
private Text mLField;
private Text mStField;
private Text mCField;
private String mDName;
private int mValidity = 0;
private List<String> mExistingAliases;
protected KeyCreationPage(ExportWizard wizard, String pageName) {
super(pageName);
mWizard = wizard;
setTitle("Key Creation");
setDescription(""); // TODO?
}
public void createControl(Composite parent) {
Composite composite = new Composite(parent, SWT.NULL);
composite.setLayoutData(new GridData(GridData.FILL_BOTH));
GridLayout gl = new GridLayout(2, false);
composite.setLayout(gl);
GridData gd;
new Label(composite, SWT.NONE).setText("Alias:");
mAlias = new Text(composite, SWT.BORDER);
mAlias.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL));
new Label(composite, SWT.NONE).setText("Password:");
mKeyPassword = new Text(composite, SWT.BORDER | SWT.PASSWORD);
mKeyPassword.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL));
mKeyPassword.addVerifyListener(sPasswordVerifier);
new Label(composite, SWT.NONE).setText("Confirm:");
mKeyPassword2 = new Text(composite, SWT.BORDER | SWT.PASSWORD);
mKeyPassword2.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL));
mKeyPassword2.addVerifyListener(sPasswordVerifier);
new Label(composite, SWT.NONE).setText("Validity (years):");
final Text validityText = new Text(composite, SWT.BORDER);
validityText.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL));
validityText.addVerifyListener(new VerifyListener() {
public void verifyText(VerifyEvent e) {
// check for digit only.
for (int i = 0 ; i < e.text.length(); i++) {
char letter = e.text.charAt(i);
if (letter < '0' || letter > '9') {
e.doit = false;
return;
}
}
}
});
new Label(composite, SWT.SEPARATOR | SWT.HORIZONTAL).setLayoutData(
gd = new GridData(GridData.FILL_HORIZONTAL));
gd.horizontalSpan = 2;
new Label(composite, SWT.NONE).setText("First and Last Name:");
mCnField = new Text(composite, SWT.BORDER);
mCnField.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL));
new Label(composite, SWT.NONE).setText("Organizational Unit:");
mOuField = new Text(composite, SWT.BORDER);
mOuField.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL));
new Label(composite, SWT.NONE).setText("Organization:");
mOField = new Text(composite, SWT.BORDER);
mOField.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL));
new Label(composite, SWT.NONE).setText("City or Locality:");
mLField = new Text(composite, SWT.BORDER);
mLField.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL));
new Label(composite, SWT.NONE).setText("State or Province:");
mStField = new Text(composite, SWT.BORDER);
mStField.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL));
new Label(composite, SWT.NONE).setText("Country Code (XX):");
mCField = new Text(composite, SWT.BORDER);
mCField.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL));
// Show description the first time
setErrorMessage(null);
setMessage(null);
setControl(composite);
mAlias.addModifyListener(new ModifyListener() {
public void modifyText(ModifyEvent e) {
mWizard.setKeyAlias(mAlias.getText().trim());
onChange();
}
});
mKeyPassword.addModifyListener(new ModifyListener() {
public void modifyText(ModifyEvent e) {
mWizard.setKeyPassword(mKeyPassword.getText());
onChange();
}
});
mKeyPassword2.addModifyListener(new ModifyListener() {
public void modifyText(ModifyEvent e) {
onChange();
}
});
validityText.addModifyListener(new ModifyListener() {
public void modifyText(ModifyEvent e) {
try {
mValidity = Integer.parseInt(validityText.getText());
} catch (NumberFormatException e2) {
// this should only happen if the text field is empty due to the verifyListener.
mValidity = 0;
}
mWizard.setValidity(mValidity);
onChange();
}
});
ModifyListener dNameListener = new ModifyListener() {
public void modifyText(ModifyEvent e) {
onDNameChange();
}
};
mCnField.addModifyListener(dNameListener);
mOuField.addModifyListener(dNameListener);
mOField.addModifyListener(dNameListener);
mLField.addModifyListener(dNameListener);
mStField.addModifyListener(dNameListener);
mCField.addModifyListener(dNameListener);
}
@Override
void onShow() {
// fill the texts with information loaded from the project.
if ((mProjectDataChanged & (DATA_PROJECT | DATA_KEYSTORE)) != 0) {
// reset the keystore/alias from the content of the project
IProject project = mWizard.getProject();
// disable onChange for now. we'll call it once at the end.
mDisableOnChange = true;
String alias = ProjectHelper.loadStringProperty(project, ExportWizard.PROPERTY_ALIAS);
if (alias != null) {
mAlias.setText(alias);
}
// get the existing list of keys if applicable
if (mWizard.getKeyCreationMode()) {
mExistingAliases = mWizard.getExistingAliases();
} else {
mExistingAliases = null;
}
// reset the passwords
mKeyPassword.setText(""); //$NON-NLS-1$
mKeyPassword2.setText(""); //$NON-NLS-1$
// enable onChange, and call it to display errors and enable/disable pageCompleted.
mDisableOnChange = false;
onChange();
}
}
@Override
public IWizardPage getPreviousPage() {
if (mWizard.getKeyCreationMode()) { // this means we create a key from an existing store
return mWizard.getKeySelectionPage();
}
return mWizard.getKeystoreSelectionPage();
}
@Override
public IWizardPage getNextPage() {
return mWizard.getKeyCheckPage();
}
/**
* Handles changes and update the error message and calls {@link #setPageComplete(boolean)}.
*/
private void onChange() {
if (mDisableOnChange) {
return;
}
setErrorMessage(null);
setMessage(null);
if (mAlias.getText().trim().length() == 0) {
setErrorMessage("Enter key alias.");
setPageComplete(false);
return;
} else if (mExistingAliases != null) {
// we cannot use indexOf, because we need to do a case-insensitive check
String keyAlias = mAlias.getText().trim();
for (String alias : mExistingAliases) {
if (alias.equalsIgnoreCase(keyAlias)) {
setErrorMessage("Key alias already exists in keystore.");
setPageComplete(false);
return;
}
}
}
String value = mKeyPassword.getText();
if (value.length() == 0) {
setErrorMessage("Enter key password.");
setPageComplete(false);
return;
} else if (value.length() < 6) {
setErrorMessage("Key password is too short - must be at least 6 characters.");
setPageComplete(false);
return;
}
if (value.equals(mKeyPassword2.getText()) == false) {
setErrorMessage("Key passwords don't match.");
setPageComplete(false);
return;
}
if (mValidity == 0) {
setErrorMessage("Key certificate validity is required.");
setPageComplete(false);
return;
} else if (mValidity < 25) {
setMessage("A 25 year certificate validity is recommended.", WARNING);
} else if (mValidity > 1000) {
setErrorMessage("Key certificate validity must be between 1 and 1000 years.");
setPageComplete(false);
return;
}
if (mDName == null || mDName.length() == 0) {
setErrorMessage("At least one Certificate issuer field is required to be non-empty.");
setPageComplete(false);
return;
}
setPageComplete(true);
}
/**
* Handles changes in the DName fields.
*/
private void onDNameChange() {
StringBuilder sb = new StringBuilder();
buildDName("CN", mCnField, sb);
buildDName("OU", mOuField, sb);
buildDName("O", mOField, sb);
buildDName("L", mLField, sb);
buildDName("ST", mStField, sb);
buildDName("C", mCField, sb);
mDName = sb.toString();
mWizard.setDName(mDName);
onChange();
}
/**
* Builds the distinguished name string with the provided {@link StringBuilder}.
* @param prefix the prefix of the entry.
* @param textField The {@link Text} field containing the entry value.
* @param sb the string builder containing the dname.
*/
private void buildDName(String prefix, Text textField, StringBuilder sb) {
if (textField != null) {
String value = textField.getText().trim();
if (value.length() > 0) {
if (sb.length() > 0) {
sb.append(",");
}
sb.append(prefix);
sb.append('=');
sb.append(value);
}
}
}
}

View File

@@ -0,0 +1,266 @@
/*
* 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.export;
import com.android.ide.eclipse.adt.project.ProjectHelper;
import com.android.ide.eclipse.adt.project.export.ExportWizard.ExportWizardPage;
import org.eclipse.core.resources.IProject;
import org.eclipse.jface.wizard.IWizardPage;
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.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.Label;
import org.eclipse.swt.widgets.Text;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.util.ArrayList;
import java.util.Enumeration;
/**
* Key Selection Page. This is used when an existing keystore is used.
*/
final class KeySelectionPage extends ExportWizardPage {
private final ExportWizard mWizard;
private Label mKeyAliasesLabel;
private Combo mKeyAliases;
private Label mKeyPasswordLabel;
private Text mKeyPassword;
private boolean mDisableOnChange = false;
private Button mUseExistingKey;
private Button mCreateKey;
protected KeySelectionPage(ExportWizard wizard, String pageName) {
super(pageName);
mWizard = wizard;
setTitle("Key alias selection");
setDescription(""); // TODO
}
public void createControl(Composite parent) {
Composite composite = new Composite(parent, SWT.NULL);
composite.setLayoutData(new GridData(GridData.FILL_BOTH));
GridLayout gl = new GridLayout(3, false);
composite.setLayout(gl);
GridData gd;
mUseExistingKey = new Button(composite, SWT.RADIO);
mUseExistingKey.setText("Use existing key");
mUseExistingKey.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL));
gd.horizontalSpan = 3;
mUseExistingKey.setSelection(true);
new Composite(composite, SWT.NONE).setLayoutData(gd = new GridData());
gd.heightHint = 0;
gd.widthHint = 50;
mKeyAliasesLabel = new Label(composite, SWT.NONE);
mKeyAliasesLabel.setText("Alias:");
mKeyAliases = new Combo(composite, SWT.READ_ONLY);
mKeyAliases.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
new Composite(composite, SWT.NONE).setLayoutData(gd = new GridData());
gd.heightHint = 0;
gd.widthHint = 50;
mKeyPasswordLabel = new Label(composite, SWT.NONE);
mKeyPasswordLabel.setText("Password:");
mKeyPassword = new Text(composite, SWT.BORDER | SWT.PASSWORD);
mKeyPassword.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
mCreateKey = new Button(composite, SWT.RADIO);
mCreateKey.setText("Create new key");
mCreateKey.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL));
gd.horizontalSpan = 3;
// Show description the first time
setErrorMessage(null);
setMessage(null);
setControl(composite);
mUseExistingKey.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
mWizard.setKeyCreationMode(!mUseExistingKey.getSelection());
enableWidgets();
onChange();
}
});
mKeyAliases.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
mWizard.setKeyAlias(mKeyAliases.getItem(mKeyAliases.getSelectionIndex()));
onChange();
}
});
mKeyPassword.addModifyListener(new ModifyListener() {
public void modifyText(ModifyEvent e) {
mWizard.setKeyPassword(mKeyPassword.getText());
onChange();
}
});
}
@Override
void onShow() {
// fill the texts with information loaded from the project.
if ((mProjectDataChanged & (DATA_PROJECT | DATA_KEYSTORE)) != 0) {
// disable onChange for now. we'll call it once at the end.
mDisableOnChange = true;
// reset the alias from the content of the project
try {
// reset to using a key
mWizard.setKeyCreationMode(false);
mUseExistingKey.setSelection(true);
mCreateKey.setSelection(false);
enableWidgets();
// remove the content of the alias combo always and first, in case the
// keystore password is wrong
mKeyAliases.removeAll();
// get the alias list (also used as a keystore password test)
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
FileInputStream fis = new FileInputStream(mWizard.getKeystore());
keyStore.load(fis, mWizard.getKeystorePassword().toCharArray());
fis.close();
Enumeration<String> aliases = keyStore.aliases();
// get the alias from the project previous export, and look for a match as
// we add the aliases to the combo.
IProject project = mWizard.getProject();
String keyAlias = ProjectHelper.loadStringProperty(project,
ExportWizard.PROPERTY_ALIAS);
ArrayList<String> aliasList = new ArrayList<String>();
int selection = -1;
int count = 0;
while (aliases.hasMoreElements()) {
String alias = aliases.nextElement();
mKeyAliases.add(alias);
aliasList.add(alias);
if (selection == -1 && alias.equalsIgnoreCase(keyAlias)) {
selection = count;
}
count++;
}
mWizard.setExistingAliases(aliasList);
if (selection != -1) {
mKeyAliases.select(selection);
// since a match was found and is selected, we need to give it to
// the wizard as well
mWizard.setKeyAlias(keyAlias);
} else {
mKeyAliases.clearSelection();
}
// reset the password
mKeyPassword.setText(""); //$NON-NLS-1$
// enable onChange, and call it to display errors and enable/disable pageCompleted.
mDisableOnChange = false;
onChange();
} catch (KeyStoreException e) {
onException(e);
} catch (FileNotFoundException e) {
onException(e);
} catch (NoSuchAlgorithmException e) {
onException(e);
} catch (CertificateException e) {
onException(e);
} catch (IOException e) {
onException(e);
} finally {
// in case we exit with an exception, we need to reset this
mDisableOnChange = false;
}
}
}
@Override
public IWizardPage getPreviousPage() {
return mWizard.getKeystoreSelectionPage();
}
@Override
public IWizardPage getNextPage() {
if (mWizard.getKeyCreationMode()) {
return mWizard.getKeyCreationPage();
}
return mWizard.getKeyCheckPage();
}
/**
* Handles changes and update the error message and calls {@link #setPageComplete(boolean)}.
*/
private void onChange() {
if (mDisableOnChange) {
return;
}
setErrorMessage(null);
setMessage(null);
if (mWizard.getKeyCreationMode() == false) {
if (mKeyAliases.getSelectionIndex() == -1) {
setErrorMessage("Select a key alias.");
setPageComplete(false);
return;
}
if (mKeyPassword.getText().trim().length() == 0) {
setErrorMessage("Enter key password.");
setPageComplete(false);
return;
}
}
setPageComplete(true);
}
private void enableWidgets() {
boolean useKey = !mWizard.getKeyCreationMode();
mKeyAliasesLabel.setEnabled(useKey);
mKeyAliases.setEnabled(useKey);
mKeyPassword.setEnabled(useKey);
mKeyPasswordLabel.setEnabled(useKey);
}
}

View File

@@ -20,6 +20,7 @@ import com.android.ide.eclipse.adt.project.ProjectHelper;
import com.android.ide.eclipse.adt.project.export.ExportWizard.ExportWizardPage;
import org.eclipse.core.resources.IProject;
import org.eclipse.jface.wizard.IWizardPage;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
@@ -36,23 +37,26 @@ import org.eclipse.swt.widgets.Text;
import java.io.File;
/**
* Second export wizard page.
* Keystore selection page. This page allows to choose to create a new keystore or use an
* existing one.
*/
public class SigningExportPage extends ExportWizardPage {
final class KeystoreSelectionPage extends ExportWizardPage {
private final ExportWizard mWizard;
private Button mUseExistingKeystore;
private Button mCreateKeystore;
private Text mKeystore;
private Text mAlias;
private Text mKeystorePassword;
private Text mKeyPassword;
private Label mConfirmLabel;
private Text mKeystorePassword2;
private boolean mDisableOnChange = false;
protected SigningExportPage(ExportWizard wizard, String pageName) {
protected KeystoreSelectionPage(ExportWizard wizard, String pageName) {
super(pageName);
mWizard = wizard;
setTitle("Application Signing");
setDescription("Defines which store, key and certificate to use to sign the Android Application.");
setTitle("Keystore selection");
setDescription(""); //TODO
}
public void createControl(Composite parent) {
@@ -62,8 +66,19 @@ public class SigningExportPage extends ExportWizardPage {
composite.setLayout(gl);
GridData gd;
mUseExistingKeystore = new Button(composite, SWT.RADIO);
mUseExistingKeystore.setText("Use existing keystore");
mUseExistingKeystore.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL));
gd.horizontalSpan = 3;
mUseExistingKeystore.setSelection(true);
new Label(composite, SWT.NONE).setText("Keystore:");
mCreateKeystore = new Button(composite, SWT.RADIO);
mCreateKeystore.setText("Create new keystore");
mCreateKeystore.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL));
gd.horizontalSpan = 3;
new Label(composite, SWT.NONE).setText("Location:");
mKeystore = new Text(composite, SWT.BORDER);
mKeystore.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL));
final Button browseButton = new Button(composite, SWT.PUSH);
@@ -71,8 +86,14 @@ public class SigningExportPage extends ExportWizardPage {
browseButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
FileDialog fileDialog = new FileDialog(browseButton.getShell(), SWT.OPEN);
fileDialog.setText("Load Keystore");
FileDialog fileDialog;
if (mUseExistingKeystore.getSelection()) {
fileDialog = new FileDialog(browseButton.getShell(),SWT.OPEN);
fileDialog.setText("Load Keystore");
} else {
fileDialog = new FileDialog(browseButton.getShell(),SWT.SAVE);
fileDialog.setText("Select Keystore Name");
}
String fileName = fileDialog.open();
if (fileName != null) {
@@ -80,64 +101,73 @@ public class SigningExportPage extends ExportWizardPage {
}
}
});
new Composite(composite, SWT.NONE).setLayoutData(gd = new GridData());
gd.horizontalSpan = 2;
gd.heightHint = 0;
new Button(composite, SWT.PUSH).setText("New...");
new Label(composite, SWT.NONE).setText("Key Alias:");
mAlias = new Text(composite, SWT.BORDER);
mAlias.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL));
new Label(composite, SWT.SEPARATOR | SWT.HORIZONTAL).setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL));
gd.horizontalSpan = 3;
new Label(composite, SWT.NONE).setText("Store password:");
new Label(composite, SWT.NONE).setText("Password:");
mKeystorePassword = new Text(composite, SWT.BORDER | SWT.PASSWORD);
mKeystorePassword.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL));
mKeystorePassword.addVerifyListener(sPasswordVerifier);
new Composite(composite, SWT.NONE).setLayoutData(gd = new GridData());
gd.heightHint = gd.widthHint = 0;
new Label(composite, SWT.NONE).setText("Key password:");
mKeyPassword = new Text(composite, SWT.BORDER | SWT.PASSWORD);
mKeyPassword.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL));
mConfirmLabel = new Label(composite, SWT.NONE);
mConfirmLabel.setText("Confirm:");
mKeystorePassword2 = new Text(composite, SWT.BORDER | SWT.PASSWORD);
mKeystorePassword2.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL));
mKeystorePassword2.addVerifyListener(sPasswordVerifier);
new Composite(composite, SWT.NONE).setLayoutData(gd = new GridData());
gd.heightHint = gd.widthHint = 0;
mKeystorePassword2.setEnabled(false);
// Show description the first time
setErrorMessage(null);
setMessage(null);
setControl(composite);
mUseExistingKeystore.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
boolean createStore = !mUseExistingKeystore.getSelection();
mKeystorePassword2.setEnabled(createStore);
mConfirmLabel.setEnabled(createStore);
mWizard.setKeystoreCreationMode(createStore);
onChange();
}
});
mKeystore.addModifyListener(new ModifyListener() {
public void modifyText(ModifyEvent e) {
mWizard.setKeystore(mKeystore.getText().trim());
onChange();
}
});
mAlias.addModifyListener(new ModifyListener() {
public void modifyText(ModifyEvent e) {
mWizard.setKeyAlias(mAlias.getText().trim());
onChange();
}
});
mKeystorePassword.addModifyListener(new ModifyListener() {
public void modifyText(ModifyEvent e) {
mWizard.setKeystorePassword(mKeystorePassword.getText().trim().toCharArray());
mWizard.setKeystorePassword(mKeystorePassword.getText());
onChange();
}
});
mKeyPassword.addModifyListener(new ModifyListener() {
mKeystorePassword2.addModifyListener(new ModifyListener() {
public void modifyText(ModifyEvent e) {
mWizard.setKeyPassword(mKeyPassword.getText().trim().toCharArray());
onChange();
}
});
}
@Override
public IWizardPage getNextPage() {
if (mUseExistingKeystore.getSelection()) {
return mWizard.getKeySelectionPage();
}
return mWizard.getKeyCreationPage();
}
@Override
void onShow() {
// fill the texts with information loaded from the project.
if (mNewProjectReference) {
if ((mProjectDataChanged & DATA_PROJECT) != 0) {
// reset the keystore/alias from the content of the project
IProject project = mWizard.getProject();
@@ -150,14 +180,9 @@ public class SigningExportPage extends ExportWizardPage {
mKeystore.setText(keystore);
}
String alias = ProjectHelper.loadStringProperty(project, ExportWizard.PROPERTY_ALIAS);
if (alias != null) {
mAlias.setText(alias);
}
// reset the passwords
mKeystorePassword.setText(""); //$NON-NLS-1$
mKeyPassword.setText(""); //$NON-NLS-1$
mKeystorePassword2.setText(""); //$NON-NLS-1$
// enable onChange, and call it to display errors and enable/disable pageCompleted.
mDisableOnChange = false;
@@ -176,6 +201,8 @@ public class SigningExportPage extends ExportWizardPage {
setErrorMessage(null);
setMessage(null);
boolean createStore = !mUseExistingKeystore.getSelection();
// checks the keystore path is non null.
String keystore = mKeystore.getText().trim();
if (keystore.length() == 0) {
@@ -185,34 +212,49 @@ public class SigningExportPage extends ExportWizardPage {
} else {
File f = new File(keystore);
if (f.exists() == false) {
setErrorMessage("Keystore does not exists!");
setPageComplete(false);
return;
if (createStore == false) {
setErrorMessage("Keystore does not exist.");
setPageComplete(false);
return;
}
} else if (f.isDirectory()) {
setErrorMessage("Keystore is a directory!");
setErrorMessage("Keystore path is a directory.");
setPageComplete(false);
return;
} else if (f.isFile()) {
if (createStore) {
setErrorMessage("File already exists.");
setPageComplete(false);
return;
}
}
}
if (mAlias.getText().trim().length() == 0) {
setErrorMessage("Enter key alias.");
setPageComplete(false);
return;
}
if (mKeystorePassword.getText().trim().length() == 0) {
String value = mKeystorePassword.getText();
if (value.length() == 0) {
setErrorMessage("Enter keystore password.");
setPageComplete(false);
return;
}
if (mKeyPassword.getText().trim().length() == 0) {
setErrorMessage("Enter key password.");
} else if (createStore && value.length() < 6) {
setErrorMessage("Keystore password is too short - must be at least 6 characters.");
setPageComplete(false);
return;
}
if (createStore) {
if (mKeystorePassword2.getText().length() == 0) {
setErrorMessage("Confirm keystore password.");
setPageComplete(false);
return;
}
if (mKeystorePassword.getText().equals(mKeystorePassword2.getText()) == false) {
setErrorMessage("Keystore passwords do not match.");
setPageComplete(false);
return;
}
}
setPageComplete(true);
}
}

View File

@@ -47,7 +47,7 @@ import java.io.File;
/**
* First Export Wizard Page. Display warning/errors.
*/
public class PreExportPage extends ExportWizardPage {
final class ProjectCheckPage extends ExportWizardPage {
private final static String IMG_ERROR = "error.png"; //$NON-NLS-1$
private final static String IMG_WARNING = "warning.png"; //$NON-NLS-1$
@@ -60,12 +60,13 @@ public class PreExportPage extends ExportWizardPage {
private Composite mErrorComposite;
private Text mProjectText;
private ProjectChooserHelper mProjectChooserHelper;
private boolean mFirstOnShow = true;
protected PreExportPage(ExportWizard wizard, String pageName) {
protected ProjectCheckPage(ExportWizard wizard, String pageName) {
super(pageName);
mWizard = wizard;
setTitle("Pre Export Checks");
setTitle("Project Checks");
setDescription("Performs a set of checks to make sure the application can be exported.");
}
@@ -123,10 +124,14 @@ public class PreExportPage extends ExportWizardPage {
@Override
void onShow() {
// get the project and init the ui
IProject project = mWizard.getProject();
if (project != null) {
mProjectText.setText(project.getName());
if (mFirstOnShow) {
// get the project and init the ui
IProject project = mWizard.getProject();
if (project != null) {
mProjectText.setText(project.getName());
}
mFirstOnShow = false;
}
}

View File

@@ -27,16 +27,19 @@ class AndroidClasspathContainer implements IClasspathContainer {
private IClasspathEntry[] mClasspathEntry;
private IPath mContainerPath;
private String mName;
/**
* Constructs the container with the {@link IClasspathEntry} representing the android
* framework jar file and the container id
* @param entry the entry representing the android framework.
* @param path the path containing the classpath container id.
* @param name the name of the container to display.
*/
AndroidClasspathContainer(IClasspathEntry entry, IPath path) {
AndroidClasspathContainer(IClasspathEntry entry, IPath path, String name) {
mClasspathEntry = new IClasspathEntry[] { entry };
mContainerPath = path;
mName = name;
}
public IClasspathEntry[] getClasspathEntries() {
@@ -44,7 +47,7 @@ class AndroidClasspathContainer implements IClasspathContainer {
}
public String getDescription() {
return "Android Library";
return mName;
}
public int getKind() {

View File

@@ -16,10 +16,16 @@
package com.android.ide.eclipse.adt.project.internal;
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.project.BaseProjectHelper;
import com.android.sdklib.IAndroidTarget;
import org.eclipse.core.resources.IMarker;
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.NullProgressMonitor;
@@ -38,10 +44,6 @@ import org.eclipse.jdt.core.JavaModelException;
* {@link IProject}s. This removes the hard-coded path to the android.jar.
*/
public class AndroidClasspathContainerInitializer extends ClasspathContainerInitializer {
/** The old container id */
private final static String OLD_CONTAINER_ID =
"com.android.ide.eclipse.adt.project.AndroidClasspathContainerInitializer"; //$NON-NLS-1$
/** The container id for the android framework jar file */
private final static String CONTAINER_ID =
"com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"; //$NON-NLS-1$
@@ -58,17 +60,10 @@ public class AndroidClasspathContainerInitializer extends ClasspathContainerInit
*/
@Override
public void initialize(IPath containerPath, IJavaProject project) throws CoreException {
String id = null;
if (OLD_CONTAINER_ID.equals(containerPath.toString())) {
id = OLD_CONTAINER_ID;
} else if (CONTAINER_ID.equals(containerPath.toString())) {
id = CONTAINER_ID;
}
if (id != null) {
JavaCore.setClasspathContainer(new Path(id),
if (CONTAINER_ID.equals(containerPath.toString())) {
JavaCore.setClasspathContainer(new Path(CONTAINER_ID),
new IJavaProject[] { project },
new IClasspathContainer[] { allocateAndroidContainer(id) },
new IClasspathContainer[] { allocateAndroidContainer(CONTAINER_ID, project) },
new NullProgressMonitor());
}
}
@@ -81,15 +76,6 @@ public class AndroidClasspathContainerInitializer extends ClasspathContainerInit
return JavaCore.newContainerEntry(new Path(CONTAINER_ID));
}
/**
* Checks the {@link IPath} objects against the old android framework container id and
* returns <code>true</code> if they are identical.
* @param path the <code>IPath</code> to check.
*/
public static boolean checkOldPath(IPath path) {
return OLD_CONTAINER_ID.equals(path.toString());
}
/**
* Checks the {@link IPath} objects against the android framework container id and
* returns <code>true</code> if they are identical.
@@ -106,41 +92,18 @@ public class AndroidClasspathContainerInitializer extends ClasspathContainerInit
* @return <code>true</code> if success, <code>false</code> otherwise.
*/
public static boolean updateProjects(IJavaProject[] androidProjects) {
try {
// because those projects could have the old id, we are going to fix
// them dynamically here.
for (IJavaProject javaProject: androidProjects) {
IClasspathEntry[] entries = javaProject.getRawClasspath();
int containerIndex = ProjectHelper.findClasspathEntryByPath(entries,
OLD_CONTAINER_ID,
IClasspathEntry.CPE_CONTAINER);
if (containerIndex != -1) {
// the project has the old container, we remove it
entries = ProjectHelper.removeEntryFromClasspath(entries, containerIndex);
// we add the new one instead
entries = ProjectHelper.addEntryToClasspath(entries, getContainerEntry());
// and give the new entries to the project
javaProject.setRawClasspath(entries, new NullProgressMonitor());
}
}
// Allocate a new AndroidClasspathContainer, and associate it to the android framework
// container id for each projects.
// By providing a new association between a container id and a IClasspathContainer,
// this forces the JDT to query the IClasspathContainer for new IClasspathEntry (with
// IClasspathContainer#getClasspathEntries()), and therefore force recompilation of
// the projects.
// TODO: We could only do that for the projects haven't fixed above
// (this isn't something that will happen a lot though)
int projectCount = androidProjects.length;
IClasspathContainer[] containers = new IClasspathContainer[projectCount];
for (int i = 0 ; i < projectCount; i++) {
containers[i] = allocateAndroidContainer(CONTAINER_ID);
containers[i] = allocateAndroidContainer(CONTAINER_ID, androidProjects[i]);
}
// give each project their new container in one call.
@@ -158,23 +121,123 @@ public class AndroidClasspathContainerInitializer extends ClasspathContainerInit
* Allocates and returns an {@link AndroidClasspathContainer} object with the proper
* path to the framework jar file.
* @param containerId the container id to be used.
* @param javaProject The java project that will receive the container.
*/
private static IClasspathContainer allocateAndroidContainer(String containerId) {
return new AndroidClasspathContainer(createFrameworkClasspath(), new Path(containerId));
private static IClasspathContainer allocateAndroidContainer(String containerId,
IJavaProject javaProject) {
IProject iProject = javaProject.getProject();
// remove potential MARKER_TARGETs.
try {
if (iProject.exists()) {
iProject.deleteMarkers(AdtConstants.MARKER_TARGET, true,
IResource.DEPTH_INFINITE);
}
} catch (CoreException ce) {
// just log the error
AdtPlugin.log(ce, "Error removing target marker.");
}
// first we check if the SDK has been loaded
boolean sdkIsLoaded = AdtPlugin.getDefault().getSdkLoadStatus(javaProject) ==
LoadStatus.LOADED;
// then we check if the project has a valid target.
IAndroidTarget target = null;
if (sdkIsLoaded) {
target = Sdk.getCurrent().getTarget(iProject);
}
// if we are loaded and the target is non null, we create a valid ClassPathContainer
if (sdkIsLoaded && target != null) {
String targetName = null;
if (target.isPlatform()) {
targetName = target.getName();
} else {
targetName = String.format("%1$s (%2$s)", target.getName(),
target.getApiVersionName());
}
return new AndroidClasspathContainer(createFrameworkClasspath(target),
new Path(containerId), targetName);
}
// else we put a marker on the project, and return a dummy container (to replace the
// previous one if there was one.)
// Get the project's target's hash string (if it exists)
String hashString = Sdk.getProjectTargetHashString(iProject);
String message = null;
boolean outputToConsole = true;
if (hashString == null || hashString.length() == 0) {
message = String.format(
"Project has no target set. Edit the project properties to set one.");
} else if (sdkIsLoaded) {
message = String.format(
"Unable to resolve target '%s'", hashString);
} else {
// this is the case where there is a hashString but the SDK is not yet
// loaded and therefore we can't get the target yet.
message = String.format(
"Unable to resolve target '%s' until the SDK is loaded.", hashString);
// let's not log this one to the console as it will happen at every boot,
// and it's expected. (we do keep the error marker though).
outputToConsole = false;
}
// log the error and put the marker on the project
if (outputToConsole) {
AdtPlugin.printBuildToConsole(AdtConstants.BUILD_ALWAYS, iProject, message);
}
IMarker marker = BaseProjectHelper.addMarker(iProject, AdtConstants.MARKER_TARGET, message,
IMarker.SEVERITY_ERROR);
// add a marker priority as this is an more important error than the error that will
// spring from the lack of library
try {
marker.setAttribute(IMarker.PRIORITY, IMarker.PRIORITY_HIGH);
} catch (CoreException e) {
// just log the error
AdtPlugin.log(e, "Error changing target marker priority.");
}
// return a dummy container to replace the one we may have had before.
return new IClasspathContainer() {
public IClasspathEntry[] getClasspathEntries() {
return new IClasspathEntry[0];
}
public String getDescription() {
return "Unable to get system library for the project";
}
public int getKind() {
return IClasspathContainer.K_DEFAULT_SYSTEM;
}
public IPath getPath() {
return null;
}
};
}
/**
* Creates and returns a new {@link IClasspathEntry} object for the android framework.
* <p/>This references the OS path to the android.jar and the java doc directory. This is
* dynamically created when a project is opened, and never saved in the project itself, so
* there's no risk of storing an obsolete path.
* Creates and returns a new {@link IClasspathEntry} object for the android
* framework. <p/>This references the OS path to the android.jar and the
* java doc directory. This is dynamically created when a project is opened,
* and never saved in the project itself, so there's no risk of storing an
* obsolete path.
*
* @param target The target that contains the libraries.
*/
private static IClasspathEntry createFrameworkClasspath() {
private static IClasspathEntry createFrameworkClasspath(IAndroidTarget target) {
// now add the android framework to the class path.
// create the path object.
IPath android_lib = new Path(AdtPlugin.getOsAbsoluteFramework());
IPath android_src = new Path(AdtPlugin.getOsAbsoluteAndroidSources());
IPath android_lib = new Path(target.getPath(IAndroidTarget.ANDROID_JAR));
IPath android_src = new Path(target.getPath(IAndroidTarget.SOURCES));
// create the java doc link.
IClasspathAttribute cpAttribute = JavaCore.newClasspathAttribute(

View File

@@ -0,0 +1,98 @@
/*
* 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.properties;
import com.android.ide.eclipse.adt.sdk.Sdk;
import com.android.sdklib.IAndroidTarget;
import com.android.sdkuilib.SdkTargetSelector;
import org.eclipse.core.resources.IProject;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Label;
import org.eclipse.ui.IWorkbenchPropertyPage;
import org.eclipse.ui.dialogs.PropertyPage;
/**
* Property page for "Android" project.
* This is accessible from the Package Explorer when right clicking a project and choosing
* "Properties".
*
*/
public class AndroidPropertyPage extends PropertyPage implements IWorkbenchPropertyPage {
private IProject mProject;
private SdkTargetSelector mSelector;
public AndroidPropertyPage() {
// pass
}
@Override
protected Control createContents(Composite parent) {
// get the element (this is not yet valid in the constructor).
mProject = (IProject)getElement();
Composite top = new Composite(parent, SWT.NONE);
top.setLayoutData(new GridData(GridData.FILL_BOTH));
top.setLayout(new GridLayout(1, false));
Label l = new Label(top, SWT.NONE);
l.setText("Project Target");
// get the targets from the sdk
IAndroidTarget[] targets = null;
if (Sdk.getCurrent() != null) {
targets = Sdk.getCurrent().getTargets();
}
// build the UI.
mSelector = new SdkTargetSelector(top, targets, false /*allowMultipleSelection*/);
if (Sdk.getCurrent() != null) {
IAndroidTarget target = Sdk.getCurrent().getTarget(mProject);
if (target != null) {
mSelector.setSelection(target);
}
}
mSelector.setSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
// look for the selection and validate the page if there is a selection
IAndroidTarget target = mSelector.getFirstSelected();
setValid(target != null);
}
});
return top;
}
@Override
public boolean performOk() {
if (Sdk.getCurrent() != null) {
Sdk.getCurrent().setProject(mProject, mSelector.getFirstSelected());
}
return true;
}
}

View File

@@ -14,9 +14,8 @@
* limitations under the License.
*/
package com.android.ide.eclipse.adt.resources;
package com.android.ide.eclipse.adt.sdk;
import com.android.ide.eclipse.adt.resources.LayoutParamsParser.IClass;
import com.android.ide.eclipse.common.AndroidConstants;
import org.eclipse.core.runtime.IProgressMonitor;
@@ -35,9 +34,12 @@ import javax.management.InvalidAttributeValueException;
/**
* Custom class loader able to load a class from the SDK jar file.
*/
public class AndroidJarLoader extends ClassLoader implements IAndroidLoader {
public class AndroidJarLoader extends ClassLoader implements IAndroidClassLoader {
public final static class ClassWrapper implements IClass {
/**
* Wrapper around a {@link Class} to provide the methods of {@link IClassDescriptor}.
*/
public final static class ClassWrapper implements IClassDescriptor {
private Class<?> mClass;
public ClassWrapper(Class<?> clazz) {
@@ -48,9 +50,9 @@ public class AndroidJarLoader extends ClassLoader implements IAndroidLoader {
return mClass.getCanonicalName();
}
public IClass[] getDeclaredClasses() {
public IClassDescriptor[] getDeclaredClasses() {
Class<?>[] classes = mClass.getDeclaredClasses();
IClass[] iclasses = new IClass[classes.length];
IClassDescriptor[] iclasses = new IClassDescriptor[classes.length];
for (int i = 0 ; i < classes.length ; i++) {
iclasses[i] = new ClassWrapper(classes[i]);
}
@@ -58,7 +60,7 @@ public class AndroidJarLoader extends ClassLoader implements IAndroidLoader {
return iclasses;
}
public IClass getEnclosingClass() {
public IClassDescriptor getEnclosingClass() {
return new ClassWrapper(mClass.getEnclosingClass());
}
@@ -66,7 +68,7 @@ public class AndroidJarLoader extends ClassLoader implements IAndroidLoader {
return mClass.getSimpleName();
}
public IClass getSuperclass() {
public IClassDescriptor getSuperclass() {
return new ClassWrapper(mClass.getSuperclass());
}
@@ -131,17 +133,18 @@ public class AndroidJarLoader extends ClassLoader implements IAndroidLoader {
* @param packageFilter The package that contains all the class data to preload, using a fully
* qualified binary name (.e.g "com.my.package."). The matching algorithm
* is simple "startsWith". Use an empty string to include everything.
* @param taskLabel An optional task name for the sub monitor. Can be null.
* @param monitor A progress monitor. Can be null. Caller is responsible for calling done.
* @throws IOException
* @throws InvalidAttributeValueException
* @throws ClassFormatError
*/
public void preLoadClasses(String packageFilter, IProgressMonitor monitor)
public void preLoadClasses(String packageFilter, String taskLabel, IProgressMonitor monitor)
throws IOException, InvalidAttributeValueException, ClassFormatError {
// Transform the package name into a zip entry path
String pathFilter = packageFilter.replaceAll("\\.", "/"); //$NON-NLS-1$ //$NON-NLS-2$
SubMonitor progress = SubMonitor.convert(monitor, 100);
SubMonitor progress = SubMonitor.convert(monitor, taskLabel == null ? "" : taskLabel, 100);
// create streams to read the intermediary archive
FileInputStream fis = new FileInputStream(mOsFrameworkLocation);
@@ -174,6 +177,7 @@ public class AndroidJarLoader extends ClassLoader implements IAndroidLoader {
// advance 5% of whatever is allocated on the progress bar
progress.setWorkRemaining(100);
progress.worked(5);
progress.subTask(String.format("Preload %1$s", className));
}
}
@@ -193,17 +197,18 @@ public class AndroidJarLoader extends ClassLoader implements IAndroidLoader {
* @throws InvalidAttributeValueException
* @throws ClassFormatError
*/
public HashMap<String, ArrayList<IClass>> findClassesDerivingFrom(
public HashMap<String, ArrayList<IClassDescriptor>> findClassesDerivingFrom(
String packageFilter,
String[] superClasses)
throws IOException, InvalidAttributeValueException, ClassFormatError {
packageFilter = packageFilter.replaceAll("\\.", "/"); //$NON-NLS-1$ //$NON-NLS-2$
HashMap<String, ArrayList<IClass>> mClassesFound = new HashMap<String, ArrayList<IClass>>();
HashMap<String, ArrayList<IClassDescriptor>> mClassesFound =
new HashMap<String, ArrayList<IClassDescriptor>>();
for (String className : superClasses) {
mClassesFound.put(className, new ArrayList<IClass>());
mClassesFound.put(className, new ArrayList<IClassDescriptor>());
}
// create streams to read the intermediary archive
@@ -415,7 +420,7 @@ public class AndroidJarLoader extends ClassLoader implements IAndroidLoader {
* @param className the fully-qualified name of the class to return.
* @throws ClassNotFoundException
*/
public IClass getClass(String className) throws ClassNotFoundException {
public IClassDescriptor getClass(String className) throws ClassNotFoundException {
try {
return new ClassWrapper(loadClass(className));
} catch (ClassNotFoundException e) {

View File

@@ -0,0 +1,285 @@
/*
* 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.sdk;
import com.android.ide.eclipse.common.resources.IResourceRepository;
import com.android.ide.eclipse.editors.descriptors.IDescriptorProvider;
import com.android.ide.eclipse.editors.layout.descriptors.LayoutDescriptors;
import com.android.ide.eclipse.editors.manifest.descriptors.AndroidManifestDescriptors;
import com.android.ide.eclipse.editors.menu.descriptors.MenuDescriptors;
import com.android.ide.eclipse.editors.resources.descriptors.ResourcesDescriptors;
import com.android.ide.eclipse.editors.resources.manager.ProjectResources;
import com.android.ide.eclipse.editors.xml.descriptors.XmlDescriptors;
import com.android.layoutlib.api.ILayoutBridge;
import com.android.sdklib.IAndroidTarget;
import java.util.Hashtable;
import java.util.Map;
/**
* This class contains the data of an Android Target as loaded from the SDK.
*/
public class AndroidTargetData {
public final static int DESCRIPTOR_MANIFEST = 1;
public final static int DESCRIPTOR_LAYOUT = 2;
public final static int DESCRIPTOR_MENU = 3;
public final static int DESCRIPTOR_XML = 4;
public final static int DESCRIPTOR_RESOURCES = 5;
public final static int DESCRIPTOR_SEARCHABLE = 6;
public final static int DESCRIPTOR_PREFERENCES = 7;
public final static class LayoutBridge {
/** Link to the layout bridge */
public ILayoutBridge bridge;
public LoadStatus status = LoadStatus.LOADING;
public ClassLoader classLoader;
}
private final IAndroidTarget mTarget;
/**
* mAttributeValues is a map { key => list [ values ] }.
* The key for the map is "(element-xml-name,attribute-namespace:attribute-xml-local-name)".
* The attribute namespace prefix must be:
* - "android" for AndroidConstants.NS_RESOURCES
* - "xmlns" for the XMLNS URI.
*
* This is used for attributes that do not have a unique name, but still need to be populated
* with values in the UI. Uniquely named attributes have their values in {@link #mEnumValueMap}.
*/
private final Hashtable<String, String[]> mAttributeValues = new Hashtable<String, String[]>();
private IResourceRepository mSystemResourceRepository;
private final AndroidManifestDescriptors mManifestDescriptors;
private final LayoutDescriptors mLayoutDescriptors;
private final MenuDescriptors mMenuDescriptors;
private final XmlDescriptors mXmlDescriptors;
private final Map<String, Map<String, Integer>> mEnumValueMap;
private final ProjectResources mFrameworkResources;
private final LayoutBridge mLayoutBridge;
private boolean mLayoutBridgeInit = false;
/**
* Creates an AndroidTargetData object.
*/
AndroidTargetData(IAndroidTarget androidTarget,
IResourceRepository systemResourceRepository,
AndroidManifestDescriptors manifestDescriptors,
LayoutDescriptors layoutDescriptors,
MenuDescriptors menuDescriptors,
XmlDescriptors xmlDescriptors,
Map<String, Map<String, Integer>> enumValueMap,
String[] permissionValues,
String[] activityIntentActionValues,
String[] broadcastIntentActionValues,
String[] serviceIntentActionValues,
String[] intentCategoryValues,
ProjectResources resources,
LayoutBridge layoutBridge) {
mTarget = androidTarget;
mSystemResourceRepository = systemResourceRepository;
mManifestDescriptors = manifestDescriptors;
mLayoutDescriptors = layoutDescriptors;
mMenuDescriptors = menuDescriptors;
mXmlDescriptors = xmlDescriptors;
mEnumValueMap = enumValueMap;
mFrameworkResources = resources;
mLayoutBridge = layoutBridge;
setPermissions(permissionValues);
setIntentFilterActionsAndCategories(activityIntentActionValues, broadcastIntentActionValues,
serviceIntentActionValues, intentCategoryValues);
}
public IResourceRepository getSystemResources() {
return mSystemResourceRepository;
}
/**
* Returns an {@link IDescriptorProvider} from a given Id.
* The Id can be one of {@link #DESCRIPTOR_MANIFEST}, {@link #DESCRIPTOR_LAYOUT},
* {@link #DESCRIPTOR_MENU}, or {@link #DESCRIPTOR_XML}.
* All other values will throw an {@link IllegalArgumentException}.
*/
public IDescriptorProvider getDescriptorProvider(int descriptorId) {
switch (descriptorId) {
case DESCRIPTOR_MANIFEST:
return mManifestDescriptors;
case DESCRIPTOR_LAYOUT:
return mLayoutDescriptors;
case DESCRIPTOR_MENU:
return mMenuDescriptors;
case DESCRIPTOR_XML:
return mXmlDescriptors;
case DESCRIPTOR_RESOURCES:
// FIXME: since it's hard-coded the Resources Descriptors are not platform dependent.
return ResourcesDescriptors.getInstance();
case DESCRIPTOR_PREFERENCES:
return mXmlDescriptors.getPreferencesProvider();
case DESCRIPTOR_SEARCHABLE:
return mXmlDescriptors.getSearchableProvider();
default :
throw new IllegalArgumentException();
}
}
/**
* Returns the manifest descriptors.
*/
public AndroidManifestDescriptors getManifestDescriptors() {
return mManifestDescriptors;
}
/**
* Returns the layout Descriptors.
*/
public LayoutDescriptors getLayoutDescriptors() {
return mLayoutDescriptors;
}
/**
* Returns the menu descriptors.
*/
public MenuDescriptors getMenuDescriptors() {
return mMenuDescriptors;
}
/**
* Returns the XML descriptors
*/
public XmlDescriptors getXmlDescriptors() {
return mXmlDescriptors;
}
/**
* Returns this list of possible values for an XML attribute.
* <p/>This should only be called for attributes for which possible values depend on the
* parent element node.
* <p/>For attributes that have the same values no matter the parent node, use
* {@link #getEnumValueMap()}.
* @param elementName the name of the element containing the attribute.
* @param attributeName the name of the attribute
* @return an array of String with the possible values, or <code>null</code> if no values were
* found.
*/
public String[] getAttributeValues(String elementName, String attributeName) {
String key = String.format("(%1$s,%2$s)", elementName, attributeName); //$NON-NLS-1$
return mAttributeValues.get(key);
}
/**
* Returns this list of possible values for an XML attribute.
* <p/>This should only be called for attributes for which possible values depend on the
* parent and great-grand-parent element node.
* <p/>The typical example of this is for the 'name' attribute under
* activity/intent-filter/action
* <p/>For attributes that have the same values no matter the parent node, use
* {@link #getEnumValueMap()}.
* @param elementName the name of the element containing the attribute.
* @param attributeName the name of the attribute
* @param greatGrandParentElementName the great-grand-parent node.
* @return an array of String with the possible values, or <code>null</code> if no values were
* found.
*/
public String[] getAttributeValues(String elementName, String attributeName,
String greatGrandParentElementName) {
if (greatGrandParentElementName != null) {
String key = String.format("(%1$s,%2$s,%3$s)", //$NON-NLS-1$
greatGrandParentElementName, elementName, attributeName);
String[] values = mAttributeValues.get(key);
if (values != null) {
return values;
}
}
return getAttributeValues(elementName, attributeName);
}
/**
* Returns the enum values map.
* <p/>The map defines the possible values for XML attributes. The key is the attribute name
* and the value is a map of (string, integer) in which the key (string) is the name of
* the value, and the Integer is the numerical value in the compiled binary XML files.
*/
public Map<String, Map<String, Integer>> getEnumValueMap() {
return mEnumValueMap;
}
/**
* Returns the {@link ProjectResources} containing the Framework Resources.
*/
public ProjectResources getFrameworkResources() {
return mFrameworkResources;
}
/**
* Returns a {@link LayoutBridge} object possibly containing a {@link ILayoutBridge} object.
* <p/>If {@link LayoutBridge#bridge} is <code>null</code>, {@link LayoutBridge#status} will
* contain the reason (either {@link LoadStatus#LOADING} or {@link LoadStatus#FAILED}).
* <p/>Valid {@link ILayoutBridge} objects are always initialized before being returned.
*/
public synchronized LayoutBridge getLayoutBridge() {
if (mLayoutBridgeInit == false && mLayoutBridge.bridge != null) {
mLayoutBridge.bridge.init(mTarget.getPath(IAndroidTarget.FONTS),
getEnumValueMap());
mLayoutBridgeInit = true;
}
return mLayoutBridge;
}
/**
* Sets the permission values
* @param permissionValues the list of permissions
*/
private void setPermissions(String[] permissionValues) {
setValues("(uses-permission,android:name)", permissionValues); //$NON-NLS-1$
setValues("(application,android:permission)", permissionValues); //$NON-NLS-1$
setValues("(activity,android:permission)", permissionValues); //$NON-NLS-1$
setValues("(receiver,android:permission)", permissionValues); //$NON-NLS-1$
setValues("(service,android:permission)", permissionValues); //$NON-NLS-1$
setValues("(provider,android:permission)", permissionValues); //$NON-NLS-1$
}
private void setIntentFilterActionsAndCategories(String[] activityIntentActions,
String[] broadcastIntentActions, String[] serviceIntentActions,
String[] intentCategoryValues) {
setValues("(activity,action,android:name)", activityIntentActions); //$NON-NLS-1$
setValues("(receiver,action,android:name)", broadcastIntentActions); //$NON-NLS-1$
setValues("(service,action,android:name)", serviceIntentActions); //$NON-NLS-1$
setValues("(category,android:name)", intentCategoryValues); //$NON-NLS-1$
}
/**
* Sets a (name, values) pair in the hash map.
* <p/>
* If the name is already present in the map, it is first removed.
* @param name the name associated with the values.
* @param values The values to add.
*/
private void setValues(String name, String[] values) {
mAttributeValues.remove(name);
mAttributeValues.put(name, values);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2007 The Android Open Source Project
* 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.
@@ -14,19 +14,29 @@
* limitations under the License.
*/
package com.android.ide.eclipse.adt.resources;
package com.android.ide.eclipse.adt.sdk;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.sdk.AndroidTargetData.LayoutBridge;
import com.android.ide.eclipse.common.AndroidConstants;
import com.android.ide.eclipse.common.resources.AttrsXmlParser;
import com.android.ide.eclipse.common.resources.DeclareStyleableInfo;
import com.android.ide.eclipse.common.resources.FrameworkResourceManager;
import com.android.ide.eclipse.common.resources.IResourceRepository;
import com.android.ide.eclipse.common.resources.ResourceItem;
import com.android.ide.eclipse.common.resources.ResourceType;
import com.android.ide.eclipse.common.resources.ViewClassInfo;
import com.android.ide.eclipse.editors.layout.descriptors.LayoutDescriptors;
import com.android.ide.eclipse.editors.manifest.descriptors.AndroidManifestDescriptors;
import com.android.ide.eclipse.editors.menu.descriptors.MenuDescriptors;
import com.android.ide.eclipse.editors.resources.manager.ProjectResources;
import com.android.ide.eclipse.editors.resources.manager.ResourceManager;
import com.android.ide.eclipse.editors.xml.descriptors.XmlDescriptors;
import com.android.layoutlib.api.ILayoutBridge;
import com.android.sdklib.IAndroidTarget;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.SubMonitor;
import java.io.BufferedReader;
@@ -34,10 +44,11 @@ import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -48,7 +59,7 @@ import java.util.Map;
import javax.management.InvalidAttributeValueException;
/**
* Parser for the framework library.
* Parser for the platform data in an SDK.
* <p/>
* This gather the following information:
* <ul>
@@ -57,14 +68,16 @@ import javax.management.InvalidAttributeValueException;
* <li></li>
* </ul>
*/
public final class FrameworkResourceParser {
public final class AndroidTargetParser {
private static final String TAG = "Framework Resource Parser";
private final IAndroidTarget mAndroidTarget;
/**
* Creates a framework resource parser.
* Creates a platform data parser.
*/
public FrameworkResourceParser() {
public AndroidTargetParser(IAndroidTarget platformTarget) {
mAndroidTarget = platformTarget;
}
/**
@@ -77,34 +90,29 @@ public final class FrameworkResourceParser {
* @param monitor A progress monitor. Can be null. Caller is responsible for calling done.
* @return True if the SDK path was valid and parsing has been attempted.
*/
public boolean parse(String osSdkPath, FrameworkResourceManager resourceManager,
IProgressMonitor monitor) {
if (osSdkPath == null || osSdkPath.length() == 0) {
return false;
}
public IStatus run(IProgressMonitor monitor) {
try {
SubMonitor progress = SubMonitor.convert(monitor, 100);
SubMonitor progress = SubMonitor.convert(monitor,
String.format("Parsing SDK %1$s", mAndroidTarget.getName()),
120);
AndroidJarLoader classLoader =
new AndroidJarLoader(osSdkPath + AndroidConstants.FN_FRAMEWORK_LIBRARY);
new AndroidJarLoader(mAndroidTarget.getPath(IAndroidTarget.ANDROID_JAR));
progress.subTask("Preloading");
preload(classLoader, progress.newChild(40));
progress.setWorkRemaining(60);
preload(classLoader, progress.newChild(40, SubMonitor.SUPPRESS_NONE));
progress.setWorkRemaining(80);
if (progress.isCanceled()) {
return false;
return Status.CANCEL_STATUS;
}
// get the resource Ids.
progress.subTask("Resource IDs");
FrameworkResourceRepository systemResourceRepository = new FrameworkResourceRepository(
collectResourceIds(classLoader));
IResourceRepository frameworkRepository = collectResourceIds(classLoader);
progress.worked(5);
if (progress.isCanceled()) {
return false;
return Status.CANCEL_STATUS;
}
// get the permissions
@@ -113,56 +121,59 @@ public final class FrameworkResourceParser {
progress.worked(5);
if (progress.isCanceled()) {
return false;
return Status.CANCEL_STATUS;
}
String osLibPath = osSdkPath + AndroidConstants.OS_SDK_LIBS_FOLDER;
// get the action and category values for the Intents.
progress.subTask("Intents");
ArrayList<String> activity_actions = new ArrayList<String>();
ArrayList<String> broadcast_actions = new ArrayList<String>();
ArrayList<String> service_actions = new ArrayList<String>();
ArrayList<String> categories = new ArrayList<String>();
collectIntentFilterActionsAndCategories(osLibPath,
activity_actions, broadcast_actions, service_actions, categories);
collectIntentFilterActionsAndCategories(activity_actions, broadcast_actions,
service_actions, categories);
progress.worked(5);
if (progress.isCanceled()) {
return false;
return Status.CANCEL_STATUS;
}
progress.subTask("Layouts");
// gather the attribute definition
progress.subTask("Attributes definitions");
AttrsXmlParser attrsXmlParser = new AttrsXmlParser(
osSdkPath + AndroidConstants.OS_SDK_ATTRS_XML);
mAndroidTarget.getPath(IAndroidTarget.ATTRIBUTES));
attrsXmlParser.preload();
progress.subTask("Manifest definitions");
AttrsXmlParser attrsManifestXmlParser = new AttrsXmlParser(
osSdkPath + AndroidConstants.OS_SDK_ATTRS_MANIFEST_XML,
mAndroidTarget.getPath(IAndroidTarget.MANIFEST_ATTRIBUTES),
attrsXmlParser);
attrsManifestXmlParser.preload();
Collection<ViewClassInfo> mainList = new ArrayList<ViewClassInfo>();
Collection<ViewClassInfo> groupList = new ArrayList<ViewClassInfo>();
collectLayoutClasses(osLibPath, classLoader, attrsXmlParser, mainList, groupList,
// collect the layout/widgets classes
progress.subTask("Widgets and layouts");
collectLayoutClasses(classLoader, attrsXmlParser, mainList, groupList,
progress.newChild(40));
if (progress.isCanceled()) {
return false;
return Status.CANCEL_STATUS;
}
ViewClassInfo[] layoutViewsInfo = mainList.toArray(new ViewClassInfo[mainList.size()]);
ViewClassInfo[] layoutGroupsInfo = groupList.toArray(
new ViewClassInfo[groupList.size()]);
// collect the preferences classes.
mainList.clear();
groupList.clear();
collectPreferenceClasses(classLoader, attrsXmlParser, mainList, groupList,
progress.newChild(5));
if (progress.isCanceled()) {
return false;
return Status.CANCEL_STATUS;
}
ViewClassInfo[] preferencesInfo = mainList.toArray(new ViewClassInfo[mainList.size()]);
@@ -177,34 +188,77 @@ public final class FrameworkResourceParser {
Map<String, Map<String, Integer>> enumValueMap = attrsXmlParser.getEnumFlagValues();
if (progress.isCanceled()) {
return false;
return Status.CANCEL_STATUS;
}
String docBaseUrl = getDocumentationBaseUrl(
osSdkPath + AndroidConstants.OS_SDK_DOCS_FOLDER);
// From the information that was collected, create the pieces that will be put in
// the PlatformData object.
AndroidManifestDescriptors manifestDescriptors = new AndroidManifestDescriptors();
manifestDescriptors.updateDescriptors(manifestMap);
progress.worked(10);
FrameworkResourceManager.getInstance().setResources(systemResourceRepository,
layoutViewsInfo,
layoutGroupsInfo,
preferencesInfo,
preferenceGroupsInfo,
xmlMenuMap,
xmlSearchableMap,
manifestMap,
if (progress.isCanceled()) {
return Status.CANCEL_STATUS;
}
LayoutDescriptors layoutDescriptors = new LayoutDescriptors();
layoutDescriptors.updateDescriptors(layoutViewsInfo, layoutGroupsInfo);
progress.worked(10);
if (progress.isCanceled()) {
return Status.CANCEL_STATUS;
}
MenuDescriptors menuDescriptors = new MenuDescriptors();
menuDescriptors.updateDescriptors(xmlMenuMap);
progress.worked(10);
if (progress.isCanceled()) {
return Status.CANCEL_STATUS;
}
XmlDescriptors xmlDescriptors = new XmlDescriptors();
xmlDescriptors.updateDescriptors(xmlSearchableMap, preferencesInfo,
preferenceGroupsInfo);
progress.worked(10);
// load the framework resources.
ProjectResources resources = ResourceManager.getInstance().loadFrameworkResources(
mAndroidTarget);
progress.worked(10);
// now load the layout lib bridge
LayoutBridge layoutBridge = loadLayoutBridge();
progress.worked(10);
// and finally create the PlatformData with all that we loaded.
AndroidTargetData targetData = new AndroidTargetData(mAndroidTarget,
frameworkRepository,
manifestDescriptors,
layoutDescriptors,
menuDescriptors,
xmlDescriptors,
enumValueMap,
permissionValues,
activity_actions.toArray(new String[activity_actions.size()]),
broadcast_actions.toArray(new String[broadcast_actions.size()]),
service_actions.toArray(new String[service_actions.size()]),
categories.toArray(new String[categories.size()]),
docBaseUrl);
resources,
layoutBridge);
Sdk.getCurrent().setTargetData(mAndroidTarget, targetData);
return true;
return Status.OK_STATUS;
} catch (Exception e) {
AdtPlugin.logAndPrintError(e, TAG, "SDK parser failed"); //$NON-NLS-1$
AdtPlugin.printToConsole("SDK parser failed", e.getMessage());
return new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID, "SDK parser failed", e);
} finally {
if (monitor != null) {
monitor.done();
}
}
return false;
}
/**
@@ -217,7 +271,9 @@ public final class FrameworkResourceParser {
*/
private void preload(AndroidJarLoader classLoader, IProgressMonitor monitor) {
try {
classLoader.preLoadClasses("" /* all classes */, monitor); //$NON-NLS-1$
classLoader.preLoadClasses("" /* all classes */, //$NON-NLS-1$
mAndroidTarget.getName(), // monitor task label
monitor);
} catch (InvalidAttributeValueException e) {
AdtPlugin.log(e, "Problem preloading classes"); //$NON-NLS-1$
} catch (IOException e) {
@@ -226,24 +282,27 @@ public final class FrameworkResourceParser {
}
/**
* Collects the resources IDs found in the SDK.
* Creates an IResourceRepository for the framework resources.
*
* @param classLoader The framework SDK jar classloader
* @return a map of the resources, or null if it failed.
*/
private Map<ResourceType, List<ResourceItem>> collectResourceIds(
private IResourceRepository collectResourceIds(
AndroidJarLoader classLoader) {
try {
Class<?> r = classLoader.loadClass(AndroidConstants.CLASS_R);
if (r != null) {
return parseRClass(r);
Map<ResourceType, List<ResourceItem>> map = parseRClass(r);
if (map != null) {
return new FrameworkResourceRepository(map);
}
}
} catch (ClassNotFoundException e) {
AdtPlugin.logAndPrintError(e, TAG,
"Collect resource IDs failed, class %1$s not found in %2$s", //$NON-NLS-1$
AndroidConstants.CLASS_R,
classLoader.getSource());
mAndroidTarget.getPath(IAndroidTarget.ANDROID_JAR));
}
return null;
@@ -331,7 +390,7 @@ public final class FrameworkResourceParser {
AdtPlugin.logAndPrintError(e, TAG,
"Collect permissions failed, class %1$s not found in %2$s", //$NON-NLS-1$
AndroidConstants.CLASS_MANIFEST_PERMISSION,
classLoader.getSource());
mAndroidTarget.getPath(IAndroidTarget.ANDROID_JAR));
}
return new String[0];
@@ -347,13 +406,17 @@ public final class FrameworkResourceParser {
* @param serviceActions the list which will receive the service action values.
* @param categories the list which will receive the category values.
*/
private void collectIntentFilterActionsAndCategories(String osLibPath,
ArrayList<String> activityActions, ArrayList<String> broadcastActions,
private void collectIntentFilterActionsAndCategories(ArrayList<String> activityActions,
ArrayList<String> broadcastActions,
ArrayList<String> serviceActions, ArrayList<String> categories) {
collectValues(osLibPath + "activity_actions.txt" , activityActions);
collectValues(osLibPath + "broadcast_actions.txt" , broadcastActions);
collectValues(osLibPath + "service_actions.txt" , serviceActions);
collectValues(osLibPath + "categories.txt" , categories);
collectValues(mAndroidTarget.getPath(IAndroidTarget.ACTIONS_ACTIVITY),
activityActions);
collectValues(mAndroidTarget.getPath(IAndroidTarget.ACTIONS_BROADCAST),
broadcastActions);
collectValues(mAndroidTarget.getPath(IAndroidTarget.ACTIONS_SERVICE),
serviceActions);
collectValues(mAndroidTarget.getPath(IAndroidTarget.CATEGORIES),
categories);
}
/**
@@ -400,21 +463,21 @@ public final class FrameworkResourceParser {
* Collects all layout classes information from the class loader and the
* attrs.xml and sets the corresponding structures in the resource manager.
*
* @param osLibPath The OS path to the SDK tools/lib folder, ending with a separator.
* @param classLoader The framework SDK jar classloader
* @param classLoader The framework SDK jar classloader in case we cannot get the widget from
* the platform directly
* @param attrsXmlParser The parser of the attrs.xml file
* @param mainList the Collection to receive the main list of {@link ViewClassInfo}.
* @param groupList the Collection to receive the group list of {@link ViewClassInfo}.
* @param monitor A progress monitor. Can be null. Caller is responsible for calling done.
*/
private void collectLayoutClasses(String osLibPath,
AndroidJarLoader classLoader,
private void collectLayoutClasses(AndroidJarLoader classLoader,
AttrsXmlParser attrsXmlParser,
Collection<ViewClassInfo> mainList, Collection<ViewClassInfo> groupList,
IProgressMonitor monitor) {
LayoutParamsParser ldp = null;
try {
WidgetListLoader loader = new WidgetListLoader(osLibPath + "widgets.txt");
WidgetClassLoader loader = new WidgetClassLoader(
mAndroidTarget.getPath(IAndroidTarget.WIDGETS));
if (loader.parseWidgetList(monitor)) {
ldp = new LayoutParamsParser(loader, attrsXmlParser);
}
@@ -465,12 +528,13 @@ public final class FrameworkResourceParser {
}
} catch (NoClassDefFoundError e) {
AdtPlugin.logAndPrintError(e, TAG,
"Collect preferences failed, class %1$s not found in %2$s", //$NON-NLS-1$
"Collect preferences failed, class %1$s not found in %2$s",
e.getMessage(),
classLoader.getSource());
} catch (Throwable e) {
AdtPlugin.log(e, "Android Framework Parser: failed to collect preference classes"); //$NON-NLS-1$
AdtPlugin.printErrorToConsole("Android Framework Parser", "failed to collect preference classes");
AdtPlugin.printErrorToConsole("Android Framework Parser",
"failed to collect preference classes");
}
}
@@ -537,40 +601,51 @@ public final class FrameworkResourceParser {
}
/**
* Returns the URL to the local documentation.
* Can return null if no documentation is found in the current SDK.
*
* @param osDocsPath Path to the documentation folder in the current SDK.
* The folder may not actually exist.
* @return A file:// URL on the local documentation folder if it exists or null.
* Loads the layout bridge from the dynamically loaded layoutlib.jar
*/
private String getDocumentationBaseUrl(String osDocsPath) {
File f = new File(osDocsPath);
private LayoutBridge loadLayoutBridge() {
LayoutBridge layoutBridge = new LayoutBridge();
if (f.isDirectory()) {
try {
// Note: to create a file:// URL, one would typically use something like
// f.toURI().toURL().toString(). However this generates a broken path on
// Windows, namely "C:\\foo" is converted to "file:/C:/foo" instead of
// "file:///C:/foo" (i.e. there should be 3 / after "file:"). So we'll
// do the correct thing manually.
try {
// get the URL for the file.
File f = new File(mAndroidTarget.getPath(IAndroidTarget.LAYOUT_LIB));
if (f.isFile() == false) {
AdtPlugin.log(IStatus.ERROR, "layoutlib.jar is missing!"); //$NON-NLS-1$
} else {
URL url = f.toURL();
String path = f.getAbsolutePath();
if (File.separatorChar != '/') {
path = path.replace(File.separatorChar, '/');
// create a class loader. Because this jar reference interfaces
// that are in the editors plugin, it's important to provide
// a parent class loader.
layoutBridge.classLoader = new URLClassLoader(new URL[] { url },
this.getClass().getClassLoader());
// load the class
Class<?> clazz = layoutBridge.classLoader.loadClass(AndroidConstants.CLASS_BRIDGE);
if (clazz != null) {
// instantiate an object of the class.
Constructor<?> constructor = clazz.getConstructor();
if (constructor != null) {
Object bridge = constructor.newInstance();
if (bridge instanceof ILayoutBridge) {
layoutBridge.bridge = (ILayoutBridge)bridge;
}
}
}
// For some reason the URL class doesn't add the mandatory "//" after
// the "file:" protocol name, so it has to be hacked into the path.
URL url = new URL("file", null, "//" + path); //$NON-NLS-1$ //$NON-NLS-2$
String result = url.toString();
return result;
} catch (MalformedURLException e) {
// ignore malformed URLs
if (layoutBridge.bridge == null) {
layoutBridge.status = LoadStatus.FAILED;
AdtPlugin.log(IStatus.ERROR, "Failed to load " + AndroidConstants.CLASS_BRIDGE); //$NON-NLS-1$
} else {
layoutBridge.status = LoadStatus.LOADED;
}
}
} catch (Throwable t) {
layoutBridge.status = LoadStatus.FAILED;
// log the error.
AdtPlugin.log(t, "Failed to load the LayoutLib");
}
return null;
return layoutBridge;
}
}

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package com.android.ide.eclipse.adt.resources;
package com.android.ide.eclipse.adt.sdk;
import com.android.ide.eclipse.common.resources.IResourceRepository;
import com.android.ide.eclipse.common.resources.ResourceItem;
@@ -26,9 +26,9 @@ import java.util.Set;
/**
* Implementation of the {@link IResourceRepository} interface to hold the system resource Ids
* parsed by {@link FrameworkResourceParser}.
* parsed by {@link AndroidTargetParser}.
*/
public final class FrameworkResourceRepository implements IResourceRepository {
final class FrameworkResourceRepository implements IResourceRepository {
private Map<ResourceType, List<ResourceItem>> mResourcesMap;

View File

@@ -14,9 +14,7 @@
* limitations under the License.
*/
package com.android.ide.eclipse.adt.resources;
import com.android.ide.eclipse.adt.resources.LayoutParamsParser.IClass;
package com.android.ide.eclipse.adt.sdk;
import java.io.IOException;
import java.util.ArrayList;
@@ -28,7 +26,25 @@ import javax.management.InvalidAttributeValueException;
* Classes which implements this interface provide methods to access framework resource
* data loaded from the SDK.
*/
public interface IAndroidLoader {
public interface IAndroidClassLoader {
/**
* Classes which implement this interface provide methods to describe a class.
*/
public interface IClassDescriptor {
String getCanonicalName();
IClassDescriptor getSuperclass();
String getSimpleName();
IClassDescriptor getEnclosingClass();
IClassDescriptor[] getDeclaredClasses();
boolean isInstantiable();
}
/**
* Finds and loads all classes that derive from a given set of super classes.
@@ -43,7 +59,7 @@ public interface IAndroidLoader {
* @throws InvalidAttributeValueException
* @throws ClassFormatError
*/
public HashMap<String, ArrayList<IClass>> findClassesDerivingFrom(
public HashMap<String, ArrayList<IClassDescriptor>> findClassesDerivingFrom(
String rootPackage, String[] superClasses)
throws IOException, InvalidAttributeValueException, ClassFormatError;
@@ -52,7 +68,7 @@ public interface IAndroidLoader {
* @param className the fully-qualified name of the class to return.
* @throws ClassNotFoundException
*/
public IClass getClass(String className) throws ClassNotFoundException;
public IClassDescriptor getClass(String className) throws ClassNotFoundException;
/**
* Returns a string indicating the source of the classes, typically for debugging

View File

@@ -14,10 +14,11 @@
* limitations under the License.
*/
package com.android.ide.eclipse.adt.resources;
package com.android.ide.eclipse.adt.sdk;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.sdk.IAndroidClassLoader.IClassDescriptor;
import com.android.ide.eclipse.common.AndroidConstants;
import com.android.ide.eclipse.common.CommonPlugin;
import com.android.ide.eclipse.common.resources.AttrsXmlParser;
import com.android.ide.eclipse.common.resources.ViewClassInfo;
import com.android.ide.eclipse.common.resources.ViewClassInfo.LayoutParamsInfo;
@@ -52,23 +53,10 @@ import javax.management.InvalidAttributeValueException;
public class LayoutParamsParser {
/**
* Classes which implement this interface provide methods to describe a class.
* Class extending {@link ViewClassInfo} by adding the notion of instantiability.
* {@link LayoutParamsParser#getViews()} and {@link LayoutParamsParser#getGroups()} should
* only return classes that can be instantiated.
*/
public interface IClass {
public String getCanonicalName();
public IClass getSuperclass();
public String getSimpleName();
public IClass getEnclosingClass();
public IClass[] getDeclaredClasses();
public boolean isInstantiable();
}
final static class ExtViewClassInfo extends ViewClassInfo {
private boolean mIsInstantiable;
@@ -87,16 +75,16 @@ public class LayoutParamsParser {
/* Note: protected members/methods are overridden in unit tests */
/** Reference to android.view.View */
protected IClass mTopViewClass;
protected IClassDescriptor mTopViewClass;
/** Reference to android.view.ViewGroup */
protected IClass mTopGroupClass;
protected IClassDescriptor mTopGroupClass;
/** Reference to android.view.ViewGroup$LayoutParams */
protected IClass mTopLayoutParamsClass;
protected IClassDescriptor mTopLayoutParamsClass;
/** Input list of all classes deriving from android.view.View */
protected ArrayList<IClass> mViewList;
protected ArrayList<IClassDescriptor> mViewList;
/** Input list of all classes deriving from android.view.ViewGroup */
protected ArrayList<IClass> mGroupList;
protected ArrayList<IClassDescriptor> mGroupList;
/** Output map of FQCN => info on View classes */
protected TreeMap<String, ExtViewClassInfo> mViewMap;
@@ -109,14 +97,14 @@ public class LayoutParamsParser {
protected AttrsXmlParser mAttrsXmlParser;
/** The android.jar class loader */
protected IAndroidLoader mClassLoader;
protected IAndroidClassLoader mClassLoader;
/**
* Instantiate a new LayoutParamsParser.
* @param classLoader The android.jar class loader
* @param attrsXmlParser The parser of the attrs.xml file
*/
public LayoutParamsParser(IAndroidLoader classLoader,
public LayoutParamsParser(IAndroidClassLoader classLoader,
AttrsXmlParser attrsXmlParser) {
mClassLoader = classLoader;
mAttrsXmlParser = attrsXmlParser;
@@ -135,8 +123,8 @@ public class LayoutParamsParser {
/**
* TODO: doc here.
* <p/>
* Note: on output we should have NO dependency on IClass, otherwise we wouldn't be able
* to unload the class loader later.
* Note: on output we should have NO dependency on {@link IClassDescriptor},
* otherwise we wouldn't be able to unload the class loader later.
* <p/>
* Note on Vocabulary: FQCN=Fully Qualified Class Name (e.g. "my.package.class$innerClass")
* @param monitor A progress monitor. Can be null. Caller is responsible for calling done.
@@ -168,8 +156,8 @@ public class LayoutParamsParser {
if (paramsClassName != null) {
superClasses[2] = paramsClassName;
}
HashMap<String, ArrayList<IClass>> found = mClassLoader.findClassesDerivingFrom(
"android.", superClasses);
HashMap<String, ArrayList<IClassDescriptor>> found =
mClassLoader.findClassesDerivingFrom("android.", superClasses);
mTopViewClass = mClassLoader.getClass(rootClassName);
mTopGroupClass = mClassLoader.getClass(groupClassName);
if (paramsClassName != null) {
@@ -196,26 +184,26 @@ public class LayoutParamsParser {
progress.setWorkRemaining(mGroupList.size() + mViewList.size());
for (IClass groupChild : mGroupList) {
for (IClassDescriptor groupChild : mGroupList) {
addGroup(groupChild);
progress.worked(1);
}
for (IClass viewChild : mViewList) {
for (IClassDescriptor viewChild : mViewList) {
if (viewChild != mTopGroupClass) {
addView(viewChild);
}
progress.worked(1);
}
} catch (ClassNotFoundException e) {
CommonPlugin.log(e, "Problem loading class %1$s or %2$s", //$NON-NLS-1$
AdtPlugin.log(e, "Problem loading class %1$s or %2$s", //$NON-NLS-1$
rootClassName, groupClassName);
} catch (InvalidAttributeValueException e) {
CommonPlugin.log(e, "Problem loading classes"); //$NON-NLS-1$
AdtPlugin.log(e, "Problem loading classes"); //$NON-NLS-1$
} catch (ClassFormatError e) {
CommonPlugin.log(e, "Problem loading classes"); //$NON-NLS-1$
AdtPlugin.log(e, "Problem loading classes"); //$NON-NLS-1$
} catch (IOException e) {
CommonPlugin.log(e, "Problem loading classes"); //$NON-NLS-1$
AdtPlugin.log(e, "Problem loading classes"); //$NON-NLS-1$
}
}
@@ -223,7 +211,7 @@ public class LayoutParamsParser {
* Parses a View class and adds a ExtViewClassInfo for it in mViewMap.
* It calls itself recursively to handle super classes which are also Views.
*/
private ExtViewClassInfo addView(IClass viewClass) {
private ExtViewClassInfo addView(IClassDescriptor viewClass) {
String fqcn = viewClass.getCanonicalName();
if (mViewMap.containsKey(fqcn)) {
return mViewMap.get(fqcn);
@@ -238,7 +226,7 @@ public class LayoutParamsParser {
// All view classes derive from mTopViewClass by design.
// Do not lookup the super class for mTopViewClass itself.
if (viewClass.equals(mTopViewClass) == false) {
IClass superClass = viewClass.getSuperclass();
IClassDescriptor superClass = viewClass.getSuperclass();
ExtViewClassInfo superClassInfo = addView(superClass);
info.setSuperClass(superClassInfo);
}
@@ -251,7 +239,7 @@ public class LayoutParamsParser {
* Parses a ViewGroup class and adds a ExtViewClassInfo for it in mGroupMap.
* It calls itself recursively to handle super classes which are also ViewGroups.
*/
private ExtViewClassInfo addGroup(IClass groupClass) {
private ExtViewClassInfo addGroup(IClassDescriptor groupClass) {
String fqcn = groupClass.getCanonicalName();
if (mGroupMap.containsKey(fqcn)) {
return mGroupMap.get(fqcn);
@@ -265,7 +253,7 @@ public class LayoutParamsParser {
// android.view.View (i.e. mTopViewClass here). So the only group that can have View as
// its super class is the ViewGroup base class and we don't try to resolve it since groups
// are loaded before views.
IClass superClass = groupClass.getSuperclass();
IClassDescriptor superClass = groupClass.getSuperclass();
// Assertion: at this point, we should have
// superClass != mTopViewClass || fqcn.equals(AndroidConstants.CLASS_VIEWGROUP);
@@ -291,15 +279,15 @@ public class LayoutParamsParser {
*
* @return The {@link LayoutParamsInfo} for the ViewGroup class or null.
*/
private LayoutParamsInfo addLayoutParams(IClass groupClass) {
private LayoutParamsInfo addLayoutParams(IClassDescriptor groupClass) {
// Is there a LayoutParams in this group class?
IClass layoutParamsClass = findLayoutParams(groupClass);
IClassDescriptor layoutParamsClass = findLayoutParams(groupClass);
// if there's no layout data in the group class, link to the one from the
// super class.
if (layoutParamsClass == null) {
for (IClass superClass = groupClass.getSuperclass();
for (IClassDescriptor superClass = groupClass.getSuperclass();
layoutParamsClass == null &&
superClass != null &&
superClass.equals(mTopViewClass) == false;
@@ -319,7 +307,7 @@ public class LayoutParamsParser {
* Parses a LayoutParams class and returns a LayoutParamsInfo object for it.
* It calls itself recursively to handle the super class of the LayoutParams.
*/
private LayoutParamsInfo getLayoutParamsInfo(IClass layoutParamsClass) {
private LayoutParamsInfo getLayoutParamsInfo(IClassDescriptor layoutParamsClass) {
String fqcn = layoutParamsClass.getCanonicalName();
LayoutParamsInfo layoutParamsInfo = mLayoutParamsMap.get(fqcn);
@@ -330,7 +318,7 @@ public class LayoutParamsParser {
// Find the link on the LayoutParams super class
LayoutParamsInfo superClassInfo = null;
if (layoutParamsClass.equals(mTopLayoutParamsClass) == false) {
IClass superClass = layoutParamsClass.getSuperclass();
IClassDescriptor superClass = layoutParamsClass.getSuperclass();
superClassInfo = getLayoutParamsInfo(superClass);
}
@@ -355,9 +343,9 @@ public class LayoutParamsParser {
* @param groupClass The ViewGroup derived class
* @return The Class of the inner LayoutParams or null if none is declared.
*/
private IClass findLayoutParams(IClass groupClass) {
IClass[] innerClasses = groupClass.getDeclaredClasses();
for (IClass innerClass : innerClasses) {
private IClassDescriptor findLayoutParams(IClassDescriptor groupClass) {
IClassDescriptor[] innerClasses = groupClass.getDeclaredClasses();
for (IClassDescriptor innerClass : innerClasses) {
if (innerClass.getSimpleName().equals(AndroidConstants.CLASS_LAYOUTPARAMS)) {
return innerClass;
}
@@ -365,12 +353,16 @@ public class LayoutParamsParser {
return null;
}
/**
* Computes and return a list of ViewClassInfo from a map by filtering out the class that
* cannot be instantiated.
*/
private List<ViewClassInfo> getInstantiables(SortedMap<String, ExtViewClassInfo> map) {
Collection<ExtViewClassInfo> values = map.values();
ArrayList<ViewClassInfo> list = new ArrayList<ViewClassInfo>();
for (ExtViewClassInfo info : values) {
if (info.mIsInstantiable) {
if (info.isInstantiable()) {
list.add(info);
}
}

View File

@@ -0,0 +1,24 @@
/*
* 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.sdk;
/**
* Enum for loading status of various SDK parts.
*/
public enum LoadStatus {
LOADING, LOADED, FAILED;
}

View File

@@ -0,0 +1,282 @@
/*
* 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.sdk;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.project.internal.AndroidClasspathContainerInitializer;
import com.android.sdklib.IAndroidTarget;
import com.android.sdklib.ISdkLog;
import com.android.sdklib.SdkConstants;
import com.android.sdklib.SdkManager;
import com.android.sdklib.project.ProjectProperties;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IProjectDescription;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.JavaCore;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
/**
* Central point to load, manipulate and deal with the Android SDK. Only one SDK can be used
* at the same time.
*
* To start using an SDK, call {@link #loadSdk(String)} which returns the instance of
* the Sdk object.
*
* To get the list of platforms present in the SDK, call {@link #getPlatforms()}.
* To get the list of add-ons present in the SDK, call {@link #getAddons()}.
*
*/
public class Sdk {
private final static String PROPERTY_PROJECT_TARGET = "androidTarget"; //$NON-NLS-1$
private static Sdk sCurrentSdk = null;
private final SdkManager mManager;
private final HashMap<IProject, IAndroidTarget> mProjectMap =
new HashMap<IProject, IAndroidTarget>();
private final HashMap<IAndroidTarget, AndroidTargetData> mTargetMap =
new HashMap<IAndroidTarget, AndroidTargetData>();
private final String mDocBaseUrl;
/**
* Loads an SDK and returns an {@link Sdk} object if success.
* @param sdkLocation the OS path to the SDK.
*/
public static Sdk loadSdk(String sdkLocation) {
if (sCurrentSdk != null) {
// manual unload?
sCurrentSdk = null;
}
final ArrayList<String> logMessages = new ArrayList<String>();
ISdkLog log = new ISdkLog() {
public void error(String errorFormat, Object... arg) {
logMessages.add(String.format(errorFormat, arg));
}
public void warning(String warningFormat, Object... arg) {
logMessages.add(String.format(warningFormat, arg));
}
};
// get an SdkManager object for the location
SdkManager manager = SdkManager.createManager(sdkLocation, log);
if (manager != null) {
sCurrentSdk = new Sdk(manager);
return sCurrentSdk;
} else {
StringBuilder sb = new StringBuilder("Error Loading the SDK:\n");
for (String msg : logMessages) {
sb.append('\n');
sb.append(msg);
}
AdtPlugin.displayError("Android SDK", sb.toString());
}
return null;
}
/**
* Returns the current {@link Sdk} object.
*/
public static Sdk getCurrent() {
return sCurrentSdk;
}
/**
* Returns the URL to the local documentation.
* Can return null if no documentation is found in the current SDK.
*
* @return A file:// URL on the local documentation folder if it exists or null.
*/
public String getDocumentationBaseUrl() {
return mDocBaseUrl;
}
/**
* Returns the list of targets that are available in the SDK.
*/
public IAndroidTarget[] getTargets() {
return mManager.getTargets();
}
/**
* Returns a target from a hash that was generated by {@link IAndroidTarget#hashString()}.
* @param hash the hash
*/
public IAndroidTarget getTargetFromHashString(String hash) {
return mManager.getTargetFromHashString(hash);
}
/**
* Associates an {@link IProject} and an {@link IAndroidTarget}.
*/
public void setProject(IProject project, IAndroidTarget target) {
synchronized (mProjectMap) {
// look for the current target of the project
IAndroidTarget previousTarget = mProjectMap.get(project);
if (target != previousTarget) {
// save the target hash string in the project persistent property
setProjectTargetHashString(project, target.hashString());
// put it in a local map for easy access.
mProjectMap.put(project, target);
// recompile the project if needed.
IJavaProject javaProject = JavaCore.create(project);
AndroidClasspathContainerInitializer.updateProjects(
new IJavaProject[] { javaProject });
}
}
}
/**
* Returns the {@link IAndroidTarget} object associated with the given {@link IProject}.
*/
public IAndroidTarget getTarget(IProject project) {
synchronized (mProjectMap) {
IAndroidTarget target = mProjectMap.get(project);
if (target == null) {
// get the value from the project persistent property.
String targetHashString = getProjectTargetHashString(project);
if (targetHashString != null) {
target = mManager.getTargetFromHashString(targetHashString);
}
}
return target;
}
}
/**
* Returns the hash string uniquely identifying the target of a project. This methods reads
* the string from the project persistent preferences/properties.
* <p/>The string is equivalent to the return of {@link IAndroidTarget#hashString()}.
* @param project The project for which to return the target hash string.
* @return the hash string or null if the project does not have a target set.
*/
public static String getProjectTargetHashString(IProject project) {
// load the default.properties from the project folder.
ProjectProperties properties = ProjectProperties.load(project.getLocation().toOSString());
if (properties == null) {
AdtPlugin.log(IStatus.ERROR, "Failed to load properties file for project '%s'",
project.getName());
return null;
}
return properties.getProperty(ProjectProperties.PROPERTY_TARGET);
}
/**
* Sets a target hash string in a project's persistent preferences/property storage.
* @param project The project in which to save the hash string.
* @param targetHashString The target hash string to save. This must be the result from
* {@link IAndroidTarget#hashString()}.
*/
public static void setProjectTargetHashString(IProject project, String targetHashString) {
// because we don't want to erase other properties from default.properties, we first load
// them
ProjectProperties properties = ProjectProperties.load(project.getLocation().toOSString());
if (properties == null) {
// doesn't exist yet? we create it.
properties = ProjectProperties.create(project.getLocation().toOSString());
}
// add/change the target hash string.
properties.setProperty(ProjectProperties.PROPERTY_TARGET, targetHashString);
// and rewrite the file.
try {
properties.save();
} catch (IOException e) {
AdtPlugin.log(e, "Failed to save default.properties for project '%s'",
project.getName());
}
}
/**
* Return the {@link PlatformData} for a given {@link IAndroidTarget}.
*/
public AndroidTargetData getTargetData(IAndroidTarget target) {
synchronized (mTargetMap) {
return mTargetMap.get(target);
}
}
private Sdk(SdkManager manager) {
mManager = manager;
// pre-compute some paths
mDocBaseUrl = getDocumentationBaseUrl(mManager.getLocation() +
SdkConstants.OS_SDK_DOCS_FOLDER);
}
void setTargetData(IAndroidTarget target, AndroidTargetData data) {
synchronized (mTargetMap) {
mTargetMap.put(target, data);
}
}
/**
* Returns the URL to the local documentation.
* Can return null if no documentation is found in the current SDK.
*
* @param osDocsPath Path to the documentation folder in the current SDK.
* The folder may not actually exist.
* @return A file:// URL on the local documentation folder if it exists or null.
*/
private String getDocumentationBaseUrl(String osDocsPath) {
File f = new File(osDocsPath);
if (f.isDirectory()) {
try {
// Note: to create a file:// URL, one would typically use something like
// f.toURI().toURL().toString(). However this generates a broken path on
// Windows, namely "C:\\foo" is converted to "file:/C:/foo" instead of
// "file:///C:/foo" (i.e. there should be 3 / after "file:"). So we'll
// do the correct thing manually.
String path = f.getAbsolutePath();
if (File.separatorChar != '/') {
path = path.replace(File.separatorChar, '/');
}
// For some reason the URL class doesn't add the mandatory "//" after
// the "file:" protocol name, so it has to be hacked into the path.
URL url = new URL("file", null, "//" + path); //$NON-NLS-1$ //$NON-NLS-2$
String result = url.toString();
return result;
} catch (MalformedURLException e) {
// ignore malformed URLs
}
}
return null;
}
}

View File

@@ -14,9 +14,8 @@
* limitations under the License.
*/
package com.android.ide.eclipse.adt.resources;
package com.android.ide.eclipse.adt.sdk;
import com.android.ide.eclipse.adt.resources.LayoutParamsParser.IClass;
import com.android.ide.eclipse.common.AndroidConstants;
import org.eclipse.core.runtime.IProgressMonitor;
@@ -42,18 +41,19 @@ import javax.management.InvalidAttributeValueException;
* where code is a single letter (W for widget, L for layout, P for layout params), and class names
* are the fully qualified name of the classes.
*/
public final class WidgetListLoader implements IAndroidLoader {
public final class WidgetClassLoader implements IAndroidClassLoader {
/**
* Basic class containing the class descriptions found in the text file.
*/
private final static class ClassDescriptor implements IClass {
private final static class ClassDescriptor implements IClassDescriptor {
private String mName;
private String mSimpleName;
private ClassDescriptor mSuperClass;
private ClassDescriptor mEnclosingClass;
private final ArrayList<IClass> mDeclaredClasses = new ArrayList<IClass>();
private final ArrayList<IClassDescriptor> mDeclaredClasses =
new ArrayList<IClassDescriptor>();
private boolean mIsInstantiable = false;
ClassDescriptor(String fqcn) {
@@ -69,15 +69,15 @@ public final class WidgetListLoader implements IAndroidLoader {
return mSimpleName;
}
public IClass[] getDeclaredClasses() {
return mDeclaredClasses.toArray(new IClass[mDeclaredClasses.size()]);
public IClassDescriptor[] getDeclaredClasses() {
return mDeclaredClasses.toArray(new IClassDescriptor[mDeclaredClasses.size()]);
}
private void addDeclaredClass(ClassDescriptor declaredClass) {
mDeclaredClasses.add(declaredClass);
}
public IClass getEnclosingClass() {
public IClassDescriptor getEnclosingClass() {
return mEnclosingClass;
}
@@ -93,7 +93,7 @@ public final class WidgetListLoader implements IAndroidLoader {
mName = enclosingClass.mName + "$" + mName.substring(enclosingClass.mName.length() + 1);
}
public IClass getSuperclass() {
public IClassDescriptor getSuperclass() {
return mSuperClass;
}
@@ -147,7 +147,7 @@ public final class WidgetListLoader implements IAndroidLoader {
* @param osFilePath the OS path of the file to load.
* @throws FileNotFoundException if the file is not found.
*/
WidgetListLoader(String osFilePath) throws FileNotFoundException {
WidgetClassLoader(String osFilePath) throws FileNotFoundException {
mOsFilePath = osFilePath;
mReader = new BufferedReader(new FileReader(osFilePath));
}
@@ -301,20 +301,21 @@ public final class WidgetListLoader implements IAndroidLoader {
* @throws InvalidAttributeValueException
* @throws ClassFormatError
*/
public HashMap<String, ArrayList<IClass>> findClassesDerivingFrom(String rootPackage,
public HashMap<String, ArrayList<IClassDescriptor>> findClassesDerivingFrom(String rootPackage,
String[] superClasses) throws IOException, InvalidAttributeValueException,
ClassFormatError {
HashMap<String, ArrayList<IClass>> map = new HashMap<String, ArrayList<IClass>>();
HashMap<String, ArrayList<IClassDescriptor>> map =
new HashMap<String, ArrayList<IClassDescriptor>>();
ArrayList<IClass> list = new ArrayList<IClass>();
ArrayList<IClassDescriptor> list = new ArrayList<IClassDescriptor>();
list.addAll(mWidgetMap.values());
map.put(AndroidConstants.CLASS_VIEW, list);
list = new ArrayList<IClass>();
list = new ArrayList<IClassDescriptor>();
list.addAll(mLayoutMap.values());
map.put(AndroidConstants.CLASS_VIEWGROUP, list);
list = new ArrayList<IClass>();
list = new ArrayList<IClassDescriptor>();
list.addAll(mLayoutParamsMap.values());
map.put(AndroidConstants.CLASS_VIEWGROUP_LAYOUTPARAMS, list);
@@ -326,7 +327,7 @@ public final class WidgetListLoader implements IAndroidLoader {
* @param className the fully-qualified name of the class to return.
* @throws ClassNotFoundException
*/
public IClass getClass(String className) throws ClassNotFoundException {
public IClassDescriptor getClass(String className) throws ClassNotFoundException {
return mMap.get(className);
}

View File

@@ -20,11 +20,13 @@
* org.eclipse.jdt.internal.ui.wizards.JavaProjectWizardFirstPage
*/
package com.android.ide.eclipse.adt.project.internal;
package com.android.ide.eclipse.adt.wizards.newproject;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.sdk.Sdk;
import com.android.ide.eclipse.common.AndroidConstants;
import com.android.ide.eclipse.common.project.AndroidManifestHelper;
import com.android.sdklib.IAndroidTarget;
import com.android.sdkuilib.SdkTargetSelector;
import org.eclipse.core.filesystem.URIUtil;
import org.eclipse.core.resources.IProject;
@@ -120,6 +122,7 @@ public class NewProjectCreationPage extends WizardPage {
private boolean mInternalActivityNameUpdate;
protected boolean mProjectNameModifiedByUser;
protected boolean mApplicationNameModifiedByUser;
private SdkTargetSelector mSdkTargetSelector;
/**
@@ -133,7 +136,9 @@ public class NewProjectCreationPage extends WizardPage {
if (sCustomLocationOsPath == null ||
sCustomLocationOsPath.length() == 0 ||
!new File(sCustomLocationOsPath).isDirectory()) {
sCustomLocationOsPath = AdtPlugin.getOsSdkSamplesFolder();
// FIXME location of samples is pretty much impossible here.
//sCustomLocationOsPath = AdtPlugin.getOsSdkSamplesFolder();
sCustomLocationOsPath = File.listRoots()[0].getAbsolutePath();
}
}
@@ -200,6 +205,11 @@ public class NewProjectCreationPage extends WizardPage {
return mSourceFolder;
}
}
/** Returns the current sdk target or null if none has been selected yet. */
public IAndroidTarget getSdkTarget() {
return mSdkTargetSelector == null ? null : mSdkTargetSelector.getFirstSelected();
}
/**
* Overrides @DialogPage.setVisible(boolean) to put the focus in the project name when
@@ -232,15 +242,19 @@ public class NewProjectCreationPage extends WizardPage {
createProjectNameGroup(composite);
createLocationGroup(composite);
createTargetGroup(composite);
createPropertiesGroup(composite);
// Update state the first time
enableLocationWidgets();
setPageComplete(validatePage());
// Show description the first time
setErrorMessage(null);
setMessage(null);
setControl(composite);
// Validate. This will complain about the first empty field.
setPageComplete(validatePage());
}
/**
@@ -356,6 +370,33 @@ public class NewProjectCreationPage extends WizardPage {
});
}
/**
* Creates the target group.
* It only contains an SdkTargetSelector.
*/
private void createTargetGroup(Composite parent) {
Group group = new Group(parent, SWT.SHADOW_ETCHED_IN);
// Layout has 1 column
group.setLayout(new GridLayout());
group.setLayoutData(new GridData(GridData.FILL_BOTH));
group.setFont(parent.getFont());
group.setText("Target");
// get the targets from the sdk
IAndroidTarget[] targets = null;
if (Sdk.getCurrent() != null) {
targets = Sdk.getCurrent().getTargets();
}
mSdkTargetSelector = new SdkTargetSelector(group, targets, false /*multi-selection*/);
mSdkTargetSelector.setSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
setPageComplete(validatePage());
}
});
}
/**
* Display a directory browser and update the location path field with the selected path
*/
@@ -754,6 +795,9 @@ public class NewProjectCreationPage extends WizardPage {
if ((status & MSG_ERROR) == 0) {
status |= validateLocationPath(workspace);
}
if ((status & MSG_ERROR) == 0) {
status |= validateSdkTarget();
}
if ((status & MSG_ERROR) == 0) {
status |= validatePackageField();
}
@@ -893,6 +937,18 @@ public class NewProjectCreationPage extends WizardPage {
return MSG_NONE;
}
/**
* Validates the sdk target choice.
*
* @return The wizard message type, one of MSG_ERROR, MSG_WARNING or MSG_NONE.
*/
private int validateSdkTarget() {
if (getSdkTarget() == null) {
return setStatus("An SDK Target must be specified.", MSG_ERROR);
}
return MSG_NONE;
}
/**
* Validates the activity name field.
*

View File

@@ -14,12 +14,14 @@
* limitations under the License.
*/
package com.android.ide.eclipse.adt.project.internal;
package com.android.ide.eclipse.adt.wizards.newproject;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.project.AndroidNature;
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.sdklib.IAndroidTarget;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
@@ -60,6 +62,7 @@ import java.net.MalformedURLException;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;
/**
* A "New Android Project" Wizard.
@@ -81,6 +84,7 @@ public class NewProjectWizard extends Wizard implements INewWizard {
private static final String PARAM_STRING_CONTENT = "STRING_CONTENT"; //$NON-NLS-1$
private static final String PARAM_IS_NEW_PROJECT = "IS_NEW_PROJECT"; //$NON-NLS-1$
private static final String PARAM_SRC_FOLDER = "SRC_FOLDER"; //$NON-NLS-1$
private static final String PARAM_SDK_TARGET = "SDK_TARGET"; //$NON-NLS-1$
private static final String PH_ACTIVITIES = "ACTIVITIES"; //$NON-NLS-1$
private static final String PH_INTENT_FILTERS = "INTENT_FILTERS"; //$NON-NLS-1$
@@ -223,13 +227,14 @@ public class NewProjectWizard extends Wizard implements INewWizard {
final IProject project = workspace.getRoot().getProject(mMainPage.getProjectName());
final IProjectDescription description = workspace.newProjectDescription(project.getName());
final Map<String, String> parameters = new HashMap<String, String>();
final Map<String, Object> parameters = new HashMap<String, Object>();
parameters.put(PARAM_PROJECT, mMainPage.getProjectName());
parameters.put(PARAM_PACKAGE, mMainPage.getPackageName());
parameters.put(PARAM_APPLICATION, STRING_RSRC_PREFIX + STRING_APP_NAME);
parameters.put(PARAM_SDK_TOOLS_DIR, AdtPlugin.getOsSdkToolsFolder());
parameters.put(PARAM_IS_NEW_PROJECT, Boolean.toString(mMainPage.isNewProject()));
parameters.put(PARAM_IS_NEW_PROJECT, mMainPage.isNewProject());
parameters.put(PARAM_SRC_FOLDER, mMainPage.getSourceFolder());
parameters.put(PARAM_SDK_TARGET, mMainPage.getSdkTarget());
if (mMainPage.isCreateActivity()) {
// An activity name can be of the form ".package.Class" or ".Class".
@@ -315,7 +320,7 @@ public class NewProjectWizard extends Wizard implements INewWizard {
* to create or modify the project or if it is canceled by the user.
*/
private void createProjectAsync(IProject project, IProjectDescription description,
IProgressMonitor monitor, Map<String, String> parameters,
IProgressMonitor monitor, Map<String, Object> parameters,
Map<String, String> stringDictionary)
throws InvocationTargetException {
monitor.beginTask("Create Android Project", 100);
@@ -330,7 +335,7 @@ public class NewProjectWizard extends Wizard implements INewWizard {
// Create folders in the project if they don't already exist
addDefaultDirectories(project, AndroidConstants.WS_ROOT, DEFAULT_DIRECTORIES, monitor);
String[] sourceFolder = new String[] { parameters.get(PARAM_SRC_FOLDER) };
String[] sourceFolder = new String[] { (String) parameters.get(PARAM_SRC_FOLDER) };
addDefaultDirectories(project, AndroidConstants.WS_ROOT, sourceFolder, monitor);
// Create the resource folders in the project if they don't already exist.
@@ -340,7 +345,7 @@ public class NewProjectWizard extends Wizard implements INewWizard {
IJavaProject javaProject = JavaCore.create(project);
setupSourceFolder(javaProject, sourceFolder[0], monitor);
if (Boolean.parseBoolean(parameters.get(PARAM_IS_NEW_PROJECT))) {
if (((Boolean) parameters.get(PARAM_IS_NEW_PROJECT)).booleanValue()) {
// Create files in the project if they don't already exist
addManifest(project, parameters, stringDictionary, monitor);
@@ -360,6 +365,8 @@ public class NewProjectWizard extends Wizard implements INewWizard {
monitor);
}
Sdk.getCurrent().setProject(project, (IAndroidTarget) parameters.get(PARAM_SDK_TARGET));
// Fix the project to make sure all properties are as expected.
// Necessary for existing projects and good for new ones to.
ProjectHelper.fixProject(project);
@@ -409,7 +416,7 @@ public class NewProjectWizard extends Wizard implements INewWizard {
* @throws IOException if the method fails to create the files in the
* project.
*/
private void addManifest(IProject project, Map<String, String> parameters,
private void addManifest(IProject project, Map<String, Object> parameters,
Map<String, String> stringDictionary, IProgressMonitor monitor)
throws CoreException, IOException {
@@ -543,16 +550,16 @@ public class NewProjectWizard extends Wizard implements INewWizard {
* project.
*/
private void addSampleCode(IProject project, String sourceFolder,
Map<String, String> parameters, Map<String, String> stringDictionary,
Map<String, Object> parameters, Map<String, String> stringDictionary,
IProgressMonitor monitor) throws CoreException, IOException {
// create the java package directories.
IFolder pkgFolder = project.getFolder(sourceFolder);
String packageName = parameters.get(PARAM_PACKAGE);
String packageName = (String) parameters.get(PARAM_PACKAGE);
// The PARAM_ACTIVITY key will be absent if no activity should be created,
// in which case activityName will be null.
String activityName = parameters.get(PARAM_ACTIVITY);
Map<String, String> java_activity_parameters = parameters;
String activityName = (String) parameters.get(PARAM_ACTIVITY);
Map<String, Object> java_activity_parameters = parameters;
if (activityName != null) {
if (activityName.indexOf('.') >= 0) {
// There are package names in the activity name. Transform packageName to add
@@ -564,7 +571,7 @@ public class NewProjectWizard extends Wizard implements INewWizard {
// Also update the values used in the JAVA_FILE_TEMPLATE below
// (but not the ones from the manifest so don't change the caller's dictionary)
java_activity_parameters = new HashMap<String, String>(parameters);
java_activity_parameters = new HashMap<String, Object>(parameters);
java_activity_parameters.put(PARAM_PACKAGE, packageName);
java_activity_parameters.put(PARAM_ACTIVITY, activityName);
}
@@ -665,7 +672,7 @@ public class NewProjectWizard extends Wizard implements INewWizard {
* length.
*/
private void copyFile(String resourceFilename, IFile destFile,
Map<String, String> parameters, IProgressMonitor monitor)
Map<String, Object> parameters, IProgressMonitor monitor)
throws CoreException, IOException {
// Read existing file.
@@ -692,13 +699,14 @@ public class NewProjectWizard extends Wizard implements INewWizard {
* Replaces placeholders found in a string with values.
*
* @param str the string to search for placeholders.
* @param parameters a map of <placeholder, Value> to search for in the
* string
* @param parameters a map of <placeholder, Value> to search for in the string
* @return A new String object with the placeholder replaced by the values.
*/
private String replaceParameters(String str, Map<String, String> parameters) {
for (String key : parameters.keySet()) {
str = str.replaceAll(key, parameters.get(key));
private String replaceParameters(String str, Map<String, Object> parameters) {
for (Entry<String, Object> entry : parameters.entrySet()) {
if (entry.getValue() instanceof String) {
str = str.replaceAll(entry.getKey(), (String) entry.getValue());
}
}
return str;

View File

@@ -16,6 +16,8 @@
package com.android.ide.eclipse.common;
import com.android.sdklib.SdkConstants;
import java.io.File;
/**
@@ -38,8 +40,11 @@ import java.io.File;
*
*/
public class AndroidConstants {
/** The Editors Plugin ID */
public static final String EDITORS_PLUGIN_ID = "com.android.ide.eclipse.editors"; // $NON-NLS-1$
/**
* The old Editors Plugin ID. It is still used in some places for compatibility.
* Please do not use for new features.
*/
public static final String EDITORS_NAMESPACE = "com.android.ide.eclipse.editors"; // $NON-NLS-1$
/** Nature of android projects */
public final static String NATURE = "com.android.ide.eclipse.adt.AndroidNature"; //$NON-NLS-1$
@@ -72,6 +77,8 @@ public class AndroidConstants {
public final static String EXT_JAR = "jar"; //$NON-NLS-1$
/** Extension of aidl files, i.e. "aidl" */
public final static String EXT_AIDL = "aidl"; //$NON-NLS-1$
/** Extension of native libraries, i.e. "so" */
public final static String EXT_NATIVE_LIB = "so"; //$NON-NLS-1$
private final static String DOT = "."; //$NON-NLS-1$
@@ -90,17 +97,8 @@ 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 framework library, i.e. "android.jar" */
public static final String FN_FRAMEWORK_LIBRARY = "android.jar"; //$NON-NLS-1$
/** Name of the layout attributes, i.e. "attrs.xml" */
public static final String FN_ATTRS_XML = "attrs.xml"; //$NON-NLS-1$
/** Name of the layout attributes, i.e. "attrs_manifest.xml" */
public static final String FN_ATTRS_MANIFEST_XML = "attrs_manifest.xml"; //$NON-NLS-1$
/** framework aidl import file */
public static final String FN_FRAMEWORK_AIDL = "framework.aidl"; //$NON-NLS-1$
/** layoutlib.jar file */
public static final String FN_LAYOUTLIB_JAR = "layoutlib.jar"; //$NON-NLS-1$
/** dex.jar file */
public static final String FN_DX_JAR = "dx.jar"; //$NON-NLS-1$
/** Name of the android sources directory */
@@ -116,10 +114,6 @@ public class AndroidConstants {
public final static String FN_CLASSES_DEX = "classes.dex"; //$NON-NLS-1$
/** Temporary packaged resources file name, i.e. "resources.ap_" */
public final static String FN_RESOURCES_AP_ = "resources.ap_"; //$NON-NLS-1$
/** build properties file */
public final static String FN_BUILD_PROP = "build.prop"; //$NON-NLS-1$
/** plugin properties file */
public final static String FN_PLUGIN_PROP = "plugin.prop"; //$NON-NLS-1$
public final static String FN_ADB = (CURRENT_PLATFORM == PLATFORM_WINDOWS) ?
"adb.exe" : "adb"; //$NON-NLS-1$ //$NON-NLS-2$
@@ -136,15 +130,21 @@ public class AndroidConstants {
public final static String FN_TRACEVIEW = (CURRENT_PLATFORM == PLATFORM_WINDOWS) ?
"traceview.exe" : "traceview"; //$NON-NLS-1$ //$NON-NLS-2$
/** Skin layout file */
public final static String FN_LAYOUT = "layout";//$NON-NLS-1$
/** Folder Names for Android Projects . */
/** Resources folder name, i.e. "res". */
/* Resources folder name, i.e. "res". */
public final static String FD_RESOURCES = "res"; //$NON-NLS-1$
/** Assets folder name, i.e. "assets" */
public final static String FD_ASSETS = "assets"; //$NON-NLS-1$
/** Default source folder name, i.e. "src" */
public final static String FD_SOURCES = "src"; //$NON-NLS-1$
/** Default native library folder name inside the project, i.e. "libs"
* While the folder inside the .apk is "lib", we call that one libs because
* that's what we use in ant for both .jar and .so and we need to make the 2 development ways
* compatible. */
public final static String FD_NATIVE_LIBS = "libs"; //$NON-NLS-1$
/** Native lib folder inside the APK: "lib" */
public final static String FD_APK_NATIVE_LIBS = "lib"; //$NON-NLS-1$
/** Default bin folder name, i.e. "bin" */
public final static String FD_BINARIES = "bin"; //$NON-NLS-1$
/** Default anim resource folder name, i.e. "anim" */
@@ -163,22 +163,6 @@ public class AndroidConstants {
public final static String FD_XML = "xml"; //$NON-NLS-1$
/** Default raw resource folder name, i.e. "raw" */
public final static String FD_RAW = "raw"; //$NON-NLS-1$
/** Name of the tools folder. */
public final static String FD_TOOLS = "tools"; //$NON-NLS-1$
/** Name of the libs folder. */
public final static String FD_LIBS = "lib"; //$NON-NLS-1$
/** Name of the docs folder. */
public final static String FD_DOCS = "docs"; //$NON-NLS-1$
/** Name of the images folder. */
public final static String FD_IMAGES = "images"; //$NON-NLS-1$
/** Name of the skins folder. */
public final static String FD_SKINS = "skins"; //$NON-NLS-1$
/** Name of the samples folder. */
public final static String FD_SAMPLES = "samples"; //$NON-NLS-1$
/** Name of the folder containing the default framework resources. */
public final static String FD_DEFAULT_RES = "default"; //$NON-NLS-1$
/** SDK font folder name, i.e. "fonts" */
public final static String FD_FONTS = "fonts"; //$NON-NLS-1$
/** Absolute path of the workspace root, i.e. "/" */
public final static String WS_ROOT = WS_SEP;
@@ -190,57 +174,16 @@ public class AndroidConstants {
public final static String WS_ASSETS = WS_SEP + FD_ASSETS;
/** Leaf of the javaDoc folder. Does not start with a separator. */
public final static String WS_JAVADOC_FOLDER_LEAF = FD_DOCS + "/reference"; //$NON-NLS-1$
/** Path of the documentation directory relative to the sdk folder.
* This is an OS path, ending with a separator. */
public final static String OS_SDK_DOCS_FOLDER = FD_DOCS + File.separator;
/** Path of the tools directory relative to the sdk folder.
* This is an OS path, ending with a separator. */
public final static String OS_SDK_TOOLS_FOLDER = FD_TOOLS + File.separator;
public final static String WS_JAVADOC_FOLDER_LEAF = SdkConstants.FD_DOCS + "/reference"; //$NON-NLS-1$
/** Path of the samples directory relative to the sdk folder.
* This is an OS path, ending with a separator. */
public final static String OS_SDK_SAMPLES_FOLDER = FD_SAMPLES + File.separator;
/** Path of the lib directory relative to the sdk folder.
* This is an OS path, ending with a separator. */
public final static String OS_SDK_LIBS_FOLDER =
OS_SDK_TOOLS_FOLDER + FD_LIBS + File.separator;
/** Path of the resources directory relative to the sdk folder.
* This is an OS path, ending with a separator. */
public final static String OS_SDK_RESOURCES_FOLDER =
OS_SDK_LIBS_FOLDER + FD_RESOURCES + File.separator;
/** Path of the resources directory relative to the sdk folder.
* This is an OS path, ending with a separator. */
public final static String OS_SDK_FONTS_FOLDER =
OS_SDK_LIBS_FOLDER + FD_FONTS + File.separator;
/** Path of the skin directory relative to the sdk folder.
* This is an OS path, ending with a separator. */
public final static String OS_SDK_SKINS_FOLDER =
OS_SDK_LIBS_FOLDER + FD_IMAGES + File.separator + FD_SKINS + File.separator;
/** Path of the attrs.xml file relative to the sdk folder. */
public final static String OS_SDK_ATTRS_XML =
OS_SDK_RESOURCES_FOLDER + File.separator + FD_DEFAULT_RES + File.separator +
FD_VALUES + File.separator + FN_ATTRS_XML;
/** Path of the attrs_manifest.xml file relative to the sdk folder. */
public final static String OS_SDK_ATTRS_MANIFEST_XML =
OS_SDK_RESOURCES_FOLDER + File.separator + FD_DEFAULT_RES + File.separator +
FD_VALUES + File.separator + FN_ATTRS_MANIFEST_XML;
/** Path of the layoutlib.jar file relative to the sdk folder. */
public final static String OS_SDK_LIBS_LAYOUTLIB_JAR =
OS_SDK_LIBS_FOLDER + FN_LAYOUTLIB_JAR;
* This is an OS path, ending with a separator.
* FIXME: remove once the NPW is fixed. */
public final static String OS_SDK_SAMPLES_FOLDER = SdkConstants.FD_SAMPLES + File.separator;
/** Path of the dx.jar file relative to the sdk folder. */
public final static String OS_SDK_LIBS_DX_JAR =
OS_SDK_LIBS_FOLDER + FN_DX_JAR;
SdkConstants.OS_SDK_TOOLS_LIB_FOLDER + FN_DX_JAR;
/** Regexp for single dot */
public final static String RE_DOT = "\\."; //$NON-NLS-1$
@@ -255,17 +198,20 @@ public class AndroidConstants {
/** Namespace pattern for the custom resource XML, i.e. "http://schemas.android.com/apk/res/%s" */
public final static String NS_CUSTOM_RESOURCES = "http://schemas.android.com/apk/res/%1$s"; //$NON-NLS-1$
/** The old common plug-in ID. Please do not use for new features. */
public static final String COMMON_PLUGIN_ID = "com.android.ide.eclipse.common"; //$NON-NLS-1$
/** aapt marker error. */
public final static String MARKER_AAPT = CommonPlugin.PLUGIN_ID + ".aaptProblem"; //$NON-NLS-1$
public final static String MARKER_AAPT = COMMON_PLUGIN_ID + ".aaptProblem"; //$NON-NLS-1$
/** XML marker error. */
public final static String MARKER_XML = CommonPlugin.PLUGIN_ID + ".xmlProblem"; //$NON-NLS-1$
public final static String MARKER_XML = COMMON_PLUGIN_ID + ".xmlProblem"; //$NON-NLS-1$
/** aidl marker error. */
public final static String MARKER_AIDL = CommonPlugin.PLUGIN_ID + ".aidlProblem"; //$NON-NLS-1$
public final static String MARKER_AIDL = COMMON_PLUGIN_ID + ".aidlProblem"; //$NON-NLS-1$
/** android marker error */
public final static String MARKER_ANDROID = CommonPlugin.PLUGIN_ID + ".androidProblem"; //$NON-NLS-1$
public final static String MARKER_ANDROID = COMMON_PLUGIN_ID + ".androidProblem"; //$NON-NLS-1$
/** Name for the "type" marker attribute */
public final static String MARKER_ATTR_TYPE = "android.type"; //$NON-NLS-1$
@@ -301,6 +247,7 @@ public class AndroidConstants {
public final static String CLASS_PREFERENCES =
"android.preference." + CLASS_PREFERENCE_SCREEN; //$NON-NLS-1$
public final static String CLASS_PREFERENCEGROUP = "android.preference.PreferenceGroup"; //$NON-NLS-1$
public final static String CLASS_PARCELABLE = "android.os.Parcelable"; //$NON-NLS-1$
public final static String CLASS_BRIDGE = "com.android.layoutlib.bridge.Bridge"; //$NON-NLS-1$

View File

@@ -53,7 +53,7 @@ public final class EclipseUiHelper {
try {
IViewPart part = page.showView(viewId,
null /* secondaryId */,
activate ? page.VIEW_ACTIVATE : page.VIEW_VISIBLE);
activate ? IWorkbenchPage.VIEW_ACTIVATE : IWorkbenchPage.VIEW_VISIBLE);
} catch (PartInitException e) {
// ignore
}

View File

@@ -44,6 +44,7 @@ public class AndroidManifestParser {
private final static String ATTRIBUTE_NAME = "name"; //$NON-NLS-1$
private final static String ATTRIBUTE_PROCESS = "process"; //$NON-NLS-$
private final static String ATTRIBUTE_DEBUGGABLE = "debuggable"; //$NON-NLS-$
private final static String ATTRIBUTE_MIN_SDK_VERSION = "minSdkVersion"; //$NON-NLS-$
private final static String NODE_MANIFEST = "manifest"; //$NON-NLS-1$
private final static String NODE_APPLICATION = "application"; //$NON-NLS-1$
private final static String NODE_ACTIVITY = "activity"; //$NON-NLS-1$
@@ -53,6 +54,7 @@ public class AndroidManifestParser {
private final static String NODE_INTENT = "intent-filter"; //$NON-NLS-1$
private final static String NODE_ACTION = "action"; //$NON-NLS-1$
private final static String NODE_CATEGORY = "category"; //$NON-NLS-1$
private final static String NODE_USES_SDK = "uses-sdk"; //$NON-NLS-1$
private final static int LEVEL_MANIFEST = 0;
private final static int LEVEL_APPLICATION = 1;
@@ -77,6 +79,8 @@ public class AndroidManifestParser {
private Set<String> mProcesses = null;
/** debuggable attribute value. If null, the attribute is not present. */
private Boolean mDebuggable = null;
/** API level requirement. if 0 the attribute was not present. */
private int mApiLevelRequirement = 0;
//--- temporary data/flags used during parsing
private IJavaProject mJavaProject;
@@ -142,12 +146,19 @@ public class AndroidManifestParser {
}
/**
* Returns the debuggable attribute value or null if it is not set.
* Returns the <code>debuggable</code> attribute value or null if it is not set.
*/
Boolean getDebuggable() {
return mDebuggable;
}
/**
* Returns the <code>minSdkVersion</code> attribute, or 0 if it's not set.
*/
int getApiLevelRequirement() {
return mApiLevelRequirement;
}
/* (non-Javadoc)
* @see org.xml.sax.helpers.DefaultHandler#setDocumentLocator(org.xml.sax.Locator)
*/
@@ -171,7 +182,7 @@ public class AndroidManifestParser {
// if we're at a valid level
if (mValidLevel == mCurrentLevel) {
String processName;
String value;
switch (mValidLevel) {
case LEVEL_MANIFEST:
if (NODE_MANIFEST.equals(localName)) {
@@ -183,19 +194,28 @@ public class AndroidManifestParser {
break;
case LEVEL_APPLICATION:
if (NODE_APPLICATION.equals(localName)) {
processName = getAttributeValue(attributes, ATTRIBUTE_PROCESS,
value = getAttributeValue(attributes, ATTRIBUTE_PROCESS,
true /* hasNamespace */);
if (processName != null) {
addProcessName(processName);
if (value != null) {
addProcessName(value);
}
String debuggable = getAttributeValue(attributes,
ATTRIBUTE_DEBUGGABLE, true /* hasNamespace*/);
if (debuggable != null) {
mDebuggable = Boolean.parseBoolean(debuggable);
value = getAttributeValue(attributes, ATTRIBUTE_DEBUGGABLE,
true /* hasNamespace*/);
if (value != null) {
mDebuggable = Boolean.parseBoolean(value);
}
mValidLevel++;
} else if (NODE_USES_SDK.equals(localName)) {
value = getAttributeValue(attributes, ATTRIBUTE_MIN_SDK_VERSION,
true /* hasNamespace */);
try {
mApiLevelRequirement = Integer.parseInt(value);
} catch (NumberFormatException e) {
handleError(e, -1 /* lineNumber */);
}
}
break;
case LEVEL_ACTIVITY:
@@ -301,7 +321,7 @@ public class AndroidManifestParser {
@Override
public void error(SAXParseException e) throws SAXException {
if (mMarkErrors) {
super.error(e);
handleError(e, e.getLineNumber());
}
}
@@ -311,7 +331,7 @@ public class AndroidManifestParser {
@Override
public void fatalError(SAXParseException e) throws SAXException {
if (mMarkErrors) {
super.fatalError(e);
handleError(e, e.getLineNumber());
}
}
@@ -457,6 +477,7 @@ public class AndroidManifestParser {
private final String mLauncherActivity;
private final String[] mProcesses;
private final Boolean mDebuggable;
private final int mApiLevelRequirement;
static {
sParserFactory = SAXParserFactory.newInstance();
@@ -491,7 +512,8 @@ public class AndroidManifestParser {
return new AndroidManifestParser(manifestHandler.getPackage(),
manifestHandler.getActivities(), manifestHandler.getLauncherActivity(),
manifestHandler.getProcesses(), manifestHandler.getDebuggable());
manifestHandler.getProcesses(), manifestHandler.getDebuggable(),
manifestHandler.getApiLevelRequirement());
} catch (ParserConfigurationException e) {
} catch (SAXException e) {
} catch (IOException e) {
@@ -530,7 +552,8 @@ public class AndroidManifestParser {
// get the result from the handler
return new AndroidManifestParser(manifestHandler.getPackage(),
manifestHandler.getActivities(), manifestHandler.getLauncherActivity(),
manifestHandler.getProcesses(), manifestHandler.getDebuggable());
manifestHandler.getProcesses(), manifestHandler.getDebuggable(),
manifestHandler.getApiLevelRequirement());
}
} catch (ParserConfigurationException e) {
} catch (SAXException e) {
@@ -609,6 +632,14 @@ public class AndroidManifestParser {
return mDebuggable;
}
/**
* Returns the <code>minSdkVersion</code> attribute, or 0 if it's not set.
*/
public int getApiLevelRequirement() {
return mApiLevelRequirement;
}
/**
* Private constructor to enforce using
* {@link #parse(IJavaProject, XmlErrorListener, boolean, boolean)},
@@ -619,14 +650,17 @@ public class AndroidManifestParser {
* @param activities the list of activities parsed from the manifest.
* @param launcherActivity the launcher activity parser from the manifest.
* @param processes the list of custom processes declared in the manifest.
* @param debuggable the debuggable attribute.
* @param debuggable the debuggable attribute, or null if not set.
* @param apiLevelRequirement the minSdkVersion attribute value or 0 if not set.
*/
private AndroidManifestParser(String javaPackage, String[] activities,
String launcherActivity, String[] processes, Boolean debuggable) {
String launcherActivity, String[] processes, Boolean debuggable,
int apiLevelRequirement) {
mJavaPackage = javaPackage;
mActivities = activities;
mLauncherActivity = launcherActivity;
mProcesses = processes;
mDebuggable = debuggable;
mApiLevelRequirement = apiLevelRequirement;
}
}

View File

@@ -16,8 +16,8 @@
package com.android.ide.eclipse.common.project;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.common.AndroidConstants;
import com.android.ide.eclipse.common.CommonPlugin;
import com.android.ide.eclipse.common.project.XmlErrorHandler.XmlErrorListener;
import org.eclipse.core.resources.IFile;
@@ -108,7 +108,7 @@ public final class BaseProjectHelper {
return marker;
} catch (CoreException e) {
CommonPlugin.log(e, "Failed to add marker '%1$s' to '%2$s'",
AdtPlugin.log(e, "Failed to add marker '%1$s' to '%2$s'",
markerId, file.getFullPath());
}
@@ -117,10 +117,9 @@ public final class BaseProjectHelper {
/**
* Adds a marker to a resource.
* @param file the file to be marked
* @param resource the file to be marked
* @param markerId The id of the marker to add.
* @param message the message associated with the mark
* @param lineNumber the line number where to put the mark
* @param severity the severity of the marker.
* @return the IMarker that was added or null if it failed to add one.
*/
@@ -138,7 +137,7 @@ public final class BaseProjectHelper {
return marker;
} catch (CoreException e) {
CommonPlugin.log(e, "Failed to add marker '%1$s' to '%2$s'",
AdtPlugin.log(e, "Failed to add marker '%1$s' to '%2$s'",
markerId, resource.getFullPath());
}

View File

@@ -65,11 +65,7 @@ public class XmlErrorHandler extends DefaultHandler {
*/
@Override
public void error(SAXParseException exception) throws SAXException {
if (mErrorListener != null) {
mErrorListener.errorFound();
}
BaseProjectHelper.addMarker(mFile, AndroidConstants.MARKER_XML, exception.getMessage(),
exception.getLineNumber(), IMarker.SEVERITY_ERROR);
handleError(exception, exception.getLineNumber());
}
/**
@@ -77,13 +73,8 @@ public class XmlErrorHandler extends DefaultHandler {
* @param exception the parsing exception
*/
@Override
public void fatalError(SAXParseException exception)
throws SAXException {
if (mErrorListener != null) {
mErrorListener.errorFound();
}
BaseProjectHelper.addMarker(mFile, AndroidConstants.MARKER_XML, exception.getMessage(),
exception.getLineNumber(), IMarker.SEVERITY_ERROR);
public void fatalError(SAXParseException exception) throws SAXException {
handleError(exception, exception.getLineNumber());
}
/**
@@ -99,4 +90,23 @@ public class XmlErrorHandler extends DefaultHandler {
protected final IFile getFile() {
return mFile;
}
/**
* Handles a parsing error and an optional line number.
* @param exception
* @param lineNumber
*/
protected void handleError(Exception exception, int lineNumber) {
if (mErrorListener != null) {
mErrorListener.errorFound();
}
if (lineNumber != -1) {
BaseProjectHelper.addMarker(mFile, AndroidConstants.MARKER_XML, exception.getMessage(),
lineNumber, IMarker.SEVERITY_ERROR);
} else {
BaseProjectHelper.addMarker(mFile, AndroidConstants.MARKER_XML, exception.getMessage(),
IMarker.SEVERITY_ERROR);
}
}
}

View File

@@ -16,7 +16,7 @@
package com.android.ide.eclipse.common.resources;
import com.android.ide.eclipse.common.CommonPlugin;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.common.resources.DeclareStyleableInfo.AttributeInfo;
import com.android.ide.eclipse.common.resources.DeclareStyleableInfo.AttributeInfo.Format;
import com.android.ide.eclipse.common.resources.ViewClassInfo.LayoutParamsInfo;
@@ -106,7 +106,7 @@ public final class AttrsXmlParser {
Document doc = getDocument();
if (doc == null) {
CommonPlugin.log(IStatus.WARNING, "Failed to find %1$s", //$NON-NLS-1$
AdtPlugin.log(IStatus.WARNING, "Failed to find %1$s", //$NON-NLS-1$
mOsAttrsXmlPath);
return this;
}
@@ -119,7 +119,7 @@ public final class AttrsXmlParser {
}
if (res == null) {
CommonPlugin.log(IStatus.WARNING, "Failed to find a <resources> node in %1$s", //$NON-NLS-1$
AdtPlugin.log(IStatus.WARNING, "Failed to find a <resources> node in %1$s", //$NON-NLS-1$
mOsAttrsXmlPath);
return this;
}
@@ -189,13 +189,13 @@ public final class AttrsXmlParser {
DocumentBuilder builder = factory.newDocumentBuilder();
mDocument = builder.parse(new File(mOsAttrsXmlPath));
} catch (ParserConfigurationException e) {
CommonPlugin.log(e, "Failed to create XML document builder for %1$s", //$NON-NLS-1$
AdtPlugin.log(e, "Failed to create XML document builder for %1$s", //$NON-NLS-1$
mOsAttrsXmlPath);
} catch (SAXException e) {
CommonPlugin.log(e, "Failed to parse XML document %1$s", //$NON-NLS-1$
AdtPlugin.log(e, "Failed to parse XML document %1$s", //$NON-NLS-1$
mOsAttrsXmlPath);
} catch (IOException e) {
CommonPlugin.log(e, "Failed to read XML document %1$s", //$NON-NLS-1$
AdtPlugin.log(e, "Failed to read XML document %1$s", //$NON-NLS-1$
mOsAttrsXmlPath);
}
}
@@ -340,7 +340,7 @@ public final class AttrsXmlParser {
formats.add(format);
}
} catch (IllegalArgumentException e) {
CommonPlugin.log(e, "Unknown format name '%s' in <attr name=\"%s\">, file '%s'.", //$NON-NLS-1$
AdtPlugin.log(e, "Unknown format name '%s' in <attr name=\"%s\">, file '%s'.", //$NON-NLS-1$
f, name, getOsAttrsXmlPath());
}
}
@@ -389,7 +389,7 @@ public final class AttrsXmlParser {
if (child.getNodeType() == Node.ELEMENT_NODE && child.getNodeName().equals(filter)) {
Node nameNode = child.getAttributes().getNamedItem("name"); //$NON-NLS-1$
if (nameNode == null) {
CommonPlugin.log(IStatus.WARNING,
AdtPlugin.log(IStatus.WARNING,
"Missing name attribute in <attr name=\"%s\"><%s></attr>", //$NON-NLS-1$
attrName, filter);
} else {
@@ -401,7 +401,7 @@ public final class AttrsXmlParser {
Node valueNode = child.getAttributes().getNamedItem("value"); //$NON-NLS-1$
if (valueNode == null) {
CommonPlugin.log(IStatus.WARNING,
AdtPlugin.log(IStatus.WARNING,
"Missing value attribute in <attr name=\"%s\"><%s name=\"%s\"></attr>", //$NON-NLS-1$
attrName, filter, name);
} else {
@@ -419,7 +419,7 @@ public final class AttrsXmlParser {
map.put(name, Integer.valueOf(i));
} catch(NumberFormatException e) {
CommonPlugin.log(e,
AdtPlugin.log(e,
"Value in <attr name=\"%s\"><%s name=\"%s\" value=\"%s\"></attr> is not a valid decimal or hexadecimal", //$NON-NLS-1$
attrName, filter, name, value);
}

View File

@@ -21,7 +21,7 @@ package com.android.ide.eclipse.common.resources;
*/
public enum ResourceType {
ANIM("anim", "Animation"), //$NON-NLS-1$
ARRAY("array", "Array"), //$NON-NLS-1$
ARRAY("array", "Array", "string-array", "integer-array"), //$NON-NLS-1$ //$NON-NLS-3$ //$NON-NLS-4$
ATTR("attr", "Attr"), //$NON-NLS-1$
COLOR("color", "Color"), //$NON-NLS-1$
DIMEN("dimen", "Dimension"), //$NON-NLS-1$
@@ -35,12 +35,14 @@ public enum ResourceType {
STYLEABLE("styleable", "Styleable"), //$NON-NLS-1$
XML("xml", "XML"); //$NON-NLS-1$
private String mName;
private String mDisplayName;
private final String mName;
private final String mDisplayName;
private final String[] mAlternateXmlNames;
ResourceType(String name, String displayName) {
ResourceType(String name, String displayName, String... alternateXmlNames) {
mName = name;
mDisplayName = displayName;
mAlternateXmlNames = alternateXmlNames;
}
/**
@@ -66,6 +68,13 @@ public enum ResourceType {
for (ResourceType rType : values()) {
if (rType.mName.equals(name)) {
return rType;
} else if (rType.mAlternateXmlNames != null) {
// if there are alternate Xml Names, we test those too
for (String alternate : rType.mAlternateXmlNames) {
if (alternate.equals(name)) {
return rType;
}
}
}
}
return null;

View File

@@ -16,11 +16,12 @@
package com.android.ide.eclipse.editors;
import com.android.ide.eclipse.adt.sdk.AndroidTargetData;
import com.android.ide.eclipse.common.AndroidConstants;
import com.android.ide.eclipse.common.resources.FrameworkResourceManager;
import com.android.ide.eclipse.editors.descriptors.AttributeDescriptor;
import com.android.ide.eclipse.editors.descriptors.DescriptorsUtils;
import com.android.ide.eclipse.editors.descriptors.ElementDescriptor;
import com.android.ide.eclipse.editors.descriptors.IDescriptorProvider;
import com.android.ide.eclipse.editors.descriptors.SeparatorAttributeDescriptor;
import com.android.ide.eclipse.editors.descriptors.TextAttributeDescriptor;
import com.android.ide.eclipse.editors.descriptors.TextValueDescriptor;
@@ -80,15 +81,20 @@ public abstract class AndroidContentAssist implements IContentAssistProcessor {
protected final static String ROOT_ELEMENT = "";
/** Descriptor of the root of the XML hierarchy. This a "fake" ElementDescriptor which
* is used to list all the possible roots given by actual implementations. */
* is used to list all the possible roots given by actual implementations.
* DO NOT USE DIRECTLY. Call {@link #getRootDescriptor()} instead. */
private ElementDescriptor mRootDescriptor;
private final int mDescriptorId;
private AndroidEditor mEditor;
/**
* Constructor for AndroidContentAssist
* @param rootElementDescriptors The valid root elements of the XML hierarchy
*/
public AndroidContentAssist(ElementDescriptor[] rootElementDescriptors) {
mRootDescriptor = new ElementDescriptor("", rootElementDescriptors);
public AndroidContentAssist(int descriptorId) {
mDescriptorId = descriptorId;
}
/**
@@ -104,8 +110,11 @@ public abstract class AndroidContentAssist implements IContentAssistProcessor {
*/
public ICompletionProposal[] computeCompletionProposals(ITextViewer viewer, int offset) {
AndroidEditor editor = getAndroidEditor(viewer);
UiElementNode rootUiNode = editor.getUiRootNode();
if (mEditor == null) {
mEditor = getAndroidEditor(viewer);
}
UiElementNode rootUiNode = mEditor.getUiRootNode();
Object[] choices = null; /* An array of ElementDescriptor, or AttributeDescriptor
or String or null */
@@ -245,7 +254,7 @@ public abstract class AndroidContentAssist implements IContentAssistProcessor {
if (current_node.getParentNode().getNodeType() == Node.ELEMENT_NODE) {
grandparent = getDescriptor(current_node.getParentNode().getNodeName());
} else if (current_node.getParentNode().getNodeType() == Node.DOCUMENT_NODE) {
grandparent = mRootDescriptor;
grandparent = getRootDescriptor();
}
if (grandparent != null) {
for (ElementDescriptor e : grandparent.getChildren()) {
@@ -339,8 +348,11 @@ public abstract class AndroidContentAssist implements IContentAssistProcessor {
greatGrandParentName = greatGrandParent.getLocalName();
}
}
choices = FrameworkResourceManager.getInstance().getValues(
parent, attrInfo.name, greatGrandParentName);
AndroidTargetData data = mEditor.getTargetData();
if (data != null) {
choices = data.getAttributeValues(parent, attrInfo.name, greatGrandParentName);
}
}
} else {
// Editing an attribute's name... Get attributes valid for the parent node.
@@ -378,7 +390,7 @@ public abstract class AndroidContentAssist implements IContentAssistProcessor {
} else if (parent_node.getNodeType() == Node.DOCUMENT_NODE) {
// We're editing a text node at the first level (i.e. root node).
// Limit content assist to the only valid root elements.
choices = mRootDescriptor.getChildren();
choices = getRootDescriptor().getChildren();
}
return choices;
}
@@ -509,7 +521,7 @@ public abstract class AndroidContentAssist implements IContentAssistProcessor {
* is returned.
*/
private ElementDescriptor getDescriptor(String nodeName) {
return mRootDescriptor.findChildrenDescriptor(nodeName, true /* recursive */);
return getRootDescriptor().findChildrenDescriptor(nodeName, true /* recursive */);
}
public IContextInformation[] computeContextInformation(ITextViewer viewer, int offset) {
@@ -539,8 +551,8 @@ public abstract class AndroidContentAssist implements IContentAssistProcessor {
public String getErrorMessage() {
return null;
}
}
/**
* Heuristically extracts the prefix used for determining template relevance
* from the viewer's document. The default implementation returns the String from
@@ -709,6 +721,26 @@ public abstract class AndroidContentAssist implements IContentAssistProcessor {
return node;
}
/**
* Computes (if needed) and returns the root descriptor.
* @return
*/
private ElementDescriptor getRootDescriptor() {
if (mRootDescriptor == null) {
AndroidTargetData data = mEditor.getTargetData();
if (data != null) {
IDescriptorProvider descriptorProvider = data.getDescriptorProvider(mDescriptorId);
if (descriptorProvider != null) {
mRootDescriptor = new ElementDescriptor("",
descriptorProvider.getRootElementDescriptors());
}
}
}
return mRootDescriptor;
}
/**
* Returns the active {@link AndroidEditor} matching this source viewer.
*/
@@ -729,5 +761,7 @@ public abstract class AndroidContentAssist implements IContentAssistProcessor {
return null;
}
}

View File

@@ -16,10 +16,14 @@
package com.android.ide.eclipse.editors;
import com.android.ide.eclipse.common.AndroidConstants;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.sdk.AndroidTargetData;
import com.android.ide.eclipse.adt.sdk.Sdk;
import com.android.ide.eclipse.editors.uimodel.UiElementNode;
import com.android.sdklib.IAndroidTarget;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceChangeEvent;
import org.eclipse.core.resources.IResourceChangeListener;
@@ -29,15 +33,18 @@ import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.QualifiedName;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.dialogs.ErrorDialog;
import org.eclipse.jface.text.source.ISourceViewer;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.IActionBars;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IEditorSite;
import org.eclipse.ui.IFileEditorInput;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.actions.ActionFactory;
import org.eclipse.ui.browser.IWorkbenchBrowserSupport;
import org.eclipse.ui.forms.IManagedForm;
import org.eclipse.ui.forms.editor.FormEditor;
@@ -71,7 +78,7 @@ import java.net.URL;
* source editor. This can be a no-op if desired.
*/
public abstract class AndroidEditor extends FormEditor implements IResourceChangeListener {
/** Preference name for the current page of this file */
private static final String PREF_CURRENT_PAGE = "_current_page";
@@ -87,9 +94,11 @@ public abstract class AndroidEditor extends FormEditor implements IResourceChang
/** Page index of the text editor (always the last page) */
private int mTextPageIndex;
/** The text editor */
private StructuredTextEditor mEditor;
private StructuredTextEditor mTextEditor;
/** Listener for the XML model from the StructuredEditor */
private XmlModelStateListener mXmlModelStateListener;
/** Listener to update the root node if the resource framework changes */
private Runnable mResourceRefreshListener;
/**
* Creates a form editor.
@@ -97,6 +106,16 @@ public abstract class AndroidEditor extends FormEditor implements IResourceChang
public AndroidEditor() {
super();
ResourcesPlugin.getWorkspace().addResourceChangeListener(this);
mResourceRefreshListener = new Runnable() {
public void run() {
commitPages(false /* onSave */);
// recreate the ui root node always
initUiRootNode(true /*force*/);
}
};
AdtPlugin.getDefault().addResourceChangedListener(mResourceRefreshListener);
}
// ---- Abstract Methods ----
@@ -113,6 +132,12 @@ public abstract class AndroidEditor extends FormEditor implements IResourceChang
* Derived classes must implement this to add their own specific tabs.
*/
abstract protected void createFormPages();
/**
* Creates the initial UI Root Node, including the known mandatory elements.
* @param force if true, a new UiManifestNode is recreated even if it already exists.
*/
abstract protected void initUiRootNode(boolean force);
/**
* Subclasses should override this method to process the new XML Model, which XML
@@ -143,6 +168,26 @@ public abstract class AndroidEditor extends FormEditor implements IResourceChang
protected void createAndroidPages() {
createFormPages();
createTextEditor();
createUndoRedoActions();
}
/**
* Creates undo redo actions for the editor site (so that it works for any page of this
* multi-page editor) by re-using the actions defined by the {@link StructuredTextEditor}
* (aka the XML text editor.)
*/
private void createUndoRedoActions() {
IActionBars bars = getEditorSite().getActionBars();
if (bars != null) {
IAction action = mTextEditor.getAction(ActionFactory.UNDO.getId());
bars.setGlobalActionHandler(ActionFactory.UNDO.getId(), action);
action = mTextEditor.getAction(ActionFactory.REDO.getId());
bars.setGlobalActionHandler(ActionFactory.REDO.getId(), action);
bars.updateActionBars();
}
}
/**
@@ -155,7 +200,7 @@ public abstract class AndroidEditor extends FormEditor implements IResourceChang
if (getEditorInput() instanceof IFileEditorInput) {
IFile file = ((IFileEditorInput) getEditorInput()).getFile();
QualifiedName qname = new QualifiedName(AndroidConstants.EDITORS_PLUGIN_ID,
QualifiedName qname = new QualifiedName(AdtPlugin.PLUGIN_ID,
getClass().getSimpleName() + PREF_CURRENT_PAGE);
String pageId;
try {
@@ -177,7 +222,7 @@ public abstract class AndroidEditor extends FormEditor implements IResourceChang
// AssertionError from setActivePage when the index is out of bounds.
// Generally speaking we just want to ignore any exception and fall back on the
// first page rather than crash the editor load. Logging the error is enough.
EditorsPlugin.log(e, "Selecting page '%s' in AndroidEditor failed", defaultPageId);
AdtPlugin.log(e, "Selecting page '%s' in AndroidEditor failed", defaultPageId);
}
}
}
@@ -224,7 +269,7 @@ public abstract class AndroidEditor extends FormEditor implements IResourceChang
if (getEditorInput() instanceof IFileEditorInput) {
IFile file = ((IFileEditorInput) getEditorInput()).getFile();
QualifiedName qname = new QualifiedName(AndroidConstants.EDITORS_PLUGIN_ID,
QualifiedName qname = new QualifiedName(AdtPlugin.PLUGIN_ID,
getClass().getSimpleName() + PREF_CURRENT_PAGE);
try {
file.setPersistentProperty(qname, Integer.toString(newPageIndex));
@@ -248,10 +293,10 @@ public abstract class AndroidEditor extends FormEditor implements IResourceChang
IWorkbenchPage[] pages = getSite().getWorkbenchWindow()
.getPages();
for (int i = 0; i < pages.length; i++) {
if (((FileEditorInput)mEditor.getEditorInput())
if (((FileEditorInput)mTextEditor.getEditorInput())
.getFile().getProject().equals(
event.getResource())) {
IEditorPart editorPart = pages[i].findEditor(mEditor
IEditorPart editorPart = pages[i].findEditor(mTextEditor
.getEditorInput());
pages[i].closeEditor(editorPart, true);
}
@@ -294,6 +339,12 @@ public abstract class AndroidEditor extends FormEditor implements IResourceChang
}
}
ResourcesPlugin.getWorkspace().removeResourceChangeListener(this);
if (mResourceRefreshListener != null) {
AdtPlugin.getDefault().removeResourceChangedListener(mResourceRefreshListener);
mResourceRefreshListener = null;
}
super.dispose();
}
@@ -447,13 +498,13 @@ public abstract class AndroidEditor extends FormEditor implements IResourceChang
*/
private void createTextEditor() {
try {
mEditor = new StructuredTextEditor();
int index = addPage(mEditor, getEditorInput());
mTextEditor = new StructuredTextEditor();
int index = addPage(mTextEditor, getEditorInput());
mTextPageIndex = index;
setPageText(index, mEditor.getTitle());
setPageText(index, mTextEditor.getTitle());
if (!(mEditor.getTextViewer().getDocument() instanceof IStructuredDocument)) {
Status status = new Status(IStatus.ERROR, AndroidConstants.EDITORS_PLUGIN_ID,
if (!(mTextEditor.getTextViewer().getDocument() instanceof IStructuredDocument)) {
Status status = new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
"Error opening the Android XML editor. Is the document an XML file?");
throw new RuntimeException("Android XML Editor Error", new CoreException(status));
}
@@ -464,6 +515,8 @@ public abstract class AndroidEditor extends FormEditor implements IResourceChang
mXmlModelStateListener = new XmlModelStateListener();
xml_model.addModelStateListener(mXmlModelStateListener);
mXmlModelStateListener.modelChanged(xml_model);
} catch (Exception e) {
AdtPlugin.log(e, "Error while loading editor"); //$NON-NLS-1$
} finally {
xml_model.releaseFromRead();
}
@@ -478,11 +531,11 @@ public abstract class AndroidEditor extends FormEditor implements IResourceChang
* Returns the ISourceViewer associated with the Structured Text editor.
*/
public final ISourceViewer getStructuredSourceViewer() {
if (mEditor != null) {
if (mTextEditor != null) {
// We can't access mEditor.getSourceViewer() because it is protected,
// however getTextViewer simply returns the SourceViewer casted, so we
// can use it instead.
return mEditor.getTextViewer();
return mTextEditor.getTextViewer();
}
return null;
}
@@ -492,8 +545,8 @@ public abstract class AndroidEditor extends FormEditor implements IResourceChang
* Editor) or null if not available.
*/
public final IStructuredDocument getStructuredDocument() {
if (mEditor != null && mEditor.getTextViewer() != null) {
return (IStructuredDocument) mEditor.getTextViewer().getDocument();
if (mTextEditor != null && mTextEditor.getTextViewer() != null) {
return (IStructuredDocument) mTextEditor.getTextViewer().getDocument();
}
return null;
}
@@ -539,14 +592,17 @@ public abstract class AndroidEditor extends FormEditor implements IResourceChang
/**
* Helper class to perform edits on the XML model whilst making sure the
* model has been prepared to be changed.
* <p/>
* It first gets a model for edition using {@link #getModelForEdit()},
* then calls {@link IStructuredModel#aboutToChangeModel()},
* then performs the requested action
* and finally calls {@link IStructuredModel#changedModel()}
* and {@link IStructuredModel#releaseFromEdit()}.
* <p/>
* The method is synchronous. As soon as the {@link IStructuredModel#changedModel()} method
* is called, XML model listeners will be triggered.
*
* It first gets a model for edition, then calls aboutToChangeModel, then performs the
* requested action and finally calls changedModel and releaseFromEdit.
*
* The method is synchronous. As soon as the changedModel method is called, XML model
* listeners will be triggered.
*
* @param edit_action Something that will change
* @param edit_action Something that will change the XML.
*/
public final void editXmlModel(Runnable edit_action) {
IStructuredModel model = getModelForEdit();
@@ -560,12 +616,82 @@ public abstract class AndroidEditor extends FormEditor implements IResourceChang
}
}
/**
* Starts an "undo recording" session. This is managed by the underlying undo manager
* associated to the structured XML model.
* <p/>
* There <em>must</em> be a corresponding call to {@link #endUndoRecording()}.
* <p/>
* beginUndoRecording/endUndoRecording calls can be nested (inner calls are ignored, only one
* undo operation is recorded.)
*
* @param label The label for the undo operation. Can be null but we should really try to put
* something meaningful if possible.
* @return True if the undo recording actually started, false if any kind of error occured.
* {@link #endUndoRecording()} should only be called if True is returned.
*/
private final boolean beginUndoRecording(String label) {
IStructuredDocument document = getStructuredDocument();
if (document != null) {
IModelManager mm = StructuredModelManager.getModelManager();
if (mm != null) {
IStructuredModel model = mm.getModelForEdit(document);
if (model != null) {
model.beginRecording(this, label);
return true;
}
}
}
return false;
}
/**
* Ends an "undo recording" session.
* <p/>
* This is the counterpart call to {@link #beginUndoRecording(String)} and should only be
* used if the initial call returned true.
*/
private final void endUndoRecording() {
IStructuredDocument document = getStructuredDocument();
if (document != null) {
IModelManager mm = StructuredModelManager.getModelManager();
if (mm != null) {
IStructuredModel model = mm.getModelForEdit(document);
if (model != null) {
model.endRecording(this);
}
}
}
}
/**
* Creates an "undo recording" session by calling the undoableAction runnable
* using {@link #beginUndoRecording(String)} and {@link #endUndoRecording()}.
* <p>
* You can nest several calls to {@link #wrapUndoRecording(String, Runnable)}, only one
* recording session will be created.
*
* @param label The label for the undo operation. Can be null. Ideally we should really try
* to put something meaningful if possible.
*/
public void wrapUndoRecording(String label, Runnable undoableAction) {
boolean recording = false;
try {
recording = beginUndoRecording(label);
undoableAction.run();
} finally {
if (recording) {
endUndoRecording();
}
}
}
/**
* Returns the XML {@link Document} or null if we can't get it
*/
protected final Document getXmlDocument(IStructuredModel model) {
if (model == null) {
EditorsPlugin.log(IStatus.WARNING, "Android Editor: No XML model for root node."); //$NON-NLS-1$
AdtPlugin.log(IStatus.WARNING, "Android Editor: No XML model for root node."); //$NON-NLS-1$
return null;
}
@@ -576,6 +702,45 @@ public abstract class AndroidEditor extends FormEditor implements IResourceChang
return null;
}
/**
* Returns the {@link IProject} for the edited file.
*/
public IProject getProject() {
if (mTextEditor != null) {
IEditorInput input = mTextEditor.getEditorInput();
if (input instanceof FileEditorInput) {
FileEditorInput fileInput = (FileEditorInput)input;
IFile inputFile = fileInput.getFile();
if (inputFile != null) {
return inputFile.getProject();
}
}
}
return null;
}
/**
* Returns the {@link PlatformData} for the edited file.
*/
public AndroidTargetData getTargetData() {
IProject project = getProject();
if (project != null) {
Sdk currentSdk = Sdk.getCurrent();
if (currentSdk != null) {
IAndroidTarget target = currentSdk.getTarget(project);
if (target != null) {
return currentSdk.getTargetData(target);
}
}
}
return null;
}
/**
* Listen to changes in the underlying XML model in the structured editor.
*/

Some files were not shown because too many files have changed in this diff Show More