auto import from //depot/cupcake/@135843

This commit is contained in:
The Android Open Source Project
2009-03-03 19:29:09 -08:00
parent d4aee0c0ca
commit 52d4c30ca5
2386 changed files with 299112 additions and 0 deletions

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry excluding="Makefile|resources/" kind="src" path="src"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
<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 combineaccessrules="false" kind="src" path="/SdkLib"/>
<classpathentry combineaccessrules="false" kind="src" path="/SdkUiLib"/>
<classpathentry kind="output" path="bin"/>
</classpath>

View File

@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>adt</name>
<comment></comment>
<projects>
<project>SdkLib</project>
<project>SdkUiLib</project>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.pde.ManifestBuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.pde.SchemaBuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.pde.PluginNature</nature>
<nature>org.eclipse.jdt.core.javanature</nature>
</natures>
</projectDescription>

View File

@@ -0,0 +1,79 @@
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Android Development Toolkit
Bundle-SymbolicName: com.android.ide.eclipse.adt;singleton:=true
Bundle-Version: 0.9.0.qualifier
Bundle-ClassPath: .,
jarutils.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.ddms,
org.eclipse.core.runtime,
org.eclipse.core.resources,
org.eclipse.debug.core,
org.eclipse.debug.ui,
org.eclipse.jdt,
org.eclipse.ant.core,
org.eclipse.jdt.core,
org.eclipse.jdt.ui,
org.eclipse.jdt.launching,
org.eclipse.jface.text,
org.eclipse.ui.editors,
org.eclipse.ui.workbench.texteditor,
org.eclipse.ui.console,
org.eclipse.core.filesystem,
org.eclipse.ui,
org.eclipse.ui.ide,
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,
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.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

@@ -0,0 +1,224 @@
*Eclipse Public License - v 1.0*
THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE
PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF
THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT.
*1. DEFINITIONS*
"Contribution" means:
a) in the case of the initial Contributor, the initial code and
documentation distributed under this Agreement, and
b) in the case of each subsequent Contributor:
i) changes to the Program, and
ii) additions to the Program;
where such changes and/or additions to the Program originate from and
are distributed by that particular Contributor. A Contribution
'originates' from a Contributor if it was added to the Program by such
Contributor itself or anyone acting on such Contributor's behalf.
Contributions do not include additions to the Program which: (i) are
separate modules of software distributed in conjunction with the Program
under their own license agreement, and (ii) are not derivative works of
the Program.
"Contributor" means any person or entity that distributes the Program.
"Licensed Patents " mean patent claims licensable by a Contributor which
are necessarily infringed by the use or sale of its Contribution alone
or when combined with the Program.
"Program" means the Contributions distributed in accordance with this
Agreement.
"Recipient" means anyone who receives the Program under this Agreement,
including all Contributors.
*2. GRANT OF RIGHTS*
a) Subject to the terms of this Agreement, each Contributor hereby
grants Recipient a non-exclusive, worldwide, royalty-free copyright
license to reproduce, prepare derivative works of, publicly display,
publicly perform, distribute and sublicense the Contribution of such
Contributor, if any, and such derivative works, in source code and
object code form.
b) Subject to the terms of this Agreement, each Contributor hereby
grants Recipient a non-exclusive, worldwide, royalty-free patent license
under Licensed Patents to make, use, sell, offer to sell, import and
otherwise transfer the Contribution of such Contributor, if any, in
source code and object code form. This patent license shall apply to the
combination of the Contribution and the Program if, at the time the
Contribution is added by the Contributor, such addition of the
Contribution causes such combination to be covered by the Licensed
Patents. The patent license shall not apply to any other combinations
which include the Contribution. No hardware per se is licensed hereunder.
c) Recipient understands that although each Contributor grants the
licenses to its Contributions set forth herein, no assurances are
provided by any Contributor that the Program does not infringe the
patent or other intellectual property rights of any other entity. Each
Contributor disclaims any liability to Recipient for claims brought by
any other entity based on infringement of intellectual property rights
or otherwise. As a condition to exercising the rights and licenses
granted hereunder, each Recipient hereby assumes sole responsibility to
secure any other intellectual property rights needed, if any. For
example, if a third party patent license is required to allow Recipient
to distribute the Program, it is Recipient's responsibility to acquire
that license before distributing the Program.
d) Each Contributor represents that to its knowledge it has sufficient
copyright rights in its Contribution, if any, to grant the copyright
license set forth in this Agreement.
*3. REQUIREMENTS*
A Contributor may choose to distribute the Program in object code form
under its own license agreement, provided that:
a) it complies with the terms and conditions of this Agreement; and
b) its license agreement:
i) effectively disclaims on behalf of all Contributors all warranties
and conditions, express and implied, including warranties or conditions
of title and non-infringement, and implied warranties or conditions of
merchantability and fitness for a particular purpose;
ii) effectively excludes on behalf of all Contributors all liability for
damages, including direct, indirect, special, incidental and
consequential damages, such as lost profits;
iii) states that any provisions which differ from this Agreement are
offered by that Contributor alone and not by any other party; and
iv) states that source code for the Program is available from such
Contributor, and informs licensees how to obtain it in a reasonable
manner on or through a medium customarily used for software exchange.
When the Program is made available in source code form:
a) it must be made available under this Agreement; and
b) a copy of this Agreement must be included with each copy of the Program.
Contributors may not remove or alter any copyright notices contained
within the Program.
Each Contributor must identify itself as the originator of its
Contribution, if any, in a manner that reasonably allows subsequent
Recipients to identify the originator of the Contribution.
*4. COMMERCIAL DISTRIBUTION*
Commercial distributors of software may accept certain responsibilities
with respect to end users, business partners and the like. While this
license is intended to facilitate the commercial use of the Program, the
Contributor who includes the Program in a commercial product offering
should do so in a manner which does not create potential liability for
other Contributors. Therefore, if a Contributor includes the Program in
a commercial product offering, such Contributor ("Commercial
Contributor") hereby agrees to defend and indemnify every other
Contributor ("Indemnified Contributor") against any losses, damages and
costs (collectively "Losses") arising from claims, lawsuits and other
legal actions brought by a third party against the Indemnified
Contributor to the extent caused by the acts or omissions of such
Commercial Contributor in connection with its distribution of the
Program in a commercial product offering. The obligations in this
section do not apply to any claims or Losses relating to any actual or
alleged intellectual property infringement. In order to qualify, an
Indemnified Contributor must: a) promptly notify the Commercial
Contributor in writing of such claim, and b) allow the Commercial
Contributor to control, and cooperate with the Commercial Contributor
in, the defense and any related settlement negotiations. The Indemnified
Contributor may participate in any such claim at its own expense.
For example, a Contributor might include the Program in a commercial
product offering, Product X. That Contributor is then a Commercial
Contributor. If that Commercial Contributor then makes performance
claims, or offers warranties related to Product X, those performance
claims and warranties are such Commercial Contributor's responsibility
alone. Under this section, the Commercial Contributor would have to
defend claims against the other Contributors related to those
performance claims and warranties, and if a court requires any other
Contributor to pay any damages as a result, the Commercial Contributor
must pay those damages.
*5. NO WARRANTY*
EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED
ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES
OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR
A PARTICULAR PURPOSE. Each Recipient is solely responsible for
determining the appropriateness of using and distributing the Program
and assumes all risks associated with its exercise of rights under this
Agreement , including but not limited to the risks and costs of program
errors, compliance with applicable laws, damage to or loss of data,
programs or equipment, and unavailability or interruption of operations.
*6. DISCLAIMER OF LIABILITY*
EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR
ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING
WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR
DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED
HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
*7. GENERAL*
If any provision of this Agreement is invalid or unenforceable under
applicable law, it shall not affect the validity or enforceability of
the remainder of the terms of this Agreement, and without further action
by the parties hereto, such provision shall be reformed to the minimum
extent necessary to make such provision valid and enforceable.
If Recipient institutes patent litigation against any entity (including
a cross-claim or counterclaim in a lawsuit) alleging that the Program
itself (excluding combinations of the Program with other software or
hardware) infringes such Recipient's patent(s), then such Recipient's
rights granted under Section 2(b) shall terminate as of the date such
litigation is filed.
All Recipient's rights under this Agreement shall terminate if it fails
to comply with any of the material terms or conditions of this Agreement
and does not cure such failure in a reasonable period of time after
becoming aware of such noncompliance. If all Recipient's rights under
this Agreement terminate, Recipient agrees to cease use and distribution
of the Program as soon as reasonably practicable. However, Recipient's
obligations under this Agreement and any licenses granted by Recipient
relating to the Program shall continue and survive.
Everyone is permitted to copy and distribute copies of this Agreement,
but in order to avoid inconsistency the Agreement is copyrighted and may
only be modified in the following manner. The Agreement Steward reserves
the right to publish new versions (including revisions) of this
Agreement from time to time. No one other than the Agreement Steward has
the right to modify this Agreement. The Eclipse Foundation is the
initial Agreement Steward. The Eclipse Foundation may assign the
responsibility to serve as the Agreement Steward to a suitable separate
entity. Each new version of the Agreement will be given a distinguishing
version number. The Program (including Contributions) may always be
distributed subject to the version of the Agreement under which it was
received. In addition, after a new version of the Agreement is
published, Contributor may elect to distribute the Program (including
its Contributions) under the new version. Except as expressly stated in
Sections 2(a) and 2(b) above, Recipient receives no rights or licenses
to the intellectual property of any Contributor under this Agreement,
whether expressly, by implication, estoppel or otherwise. All rights in
the Program not expressly granted under this Agreement are reserved.
This Agreement is governed by the laws of the State of New York and the
intellectual property laws of the United States of America. No party to
this Agreement will bring a legal action under this Agreement more than
one year after the cause of action arose. Each party waives its rights
to a jury trial in any resulting litigation.

View File

@@ -0,0 +1 @@
featureImage=icons/android_32X32.jpg

View File

@@ -0,0 +1,17 @@
bin.includes = plugin.xml,\
META-INF/,\
icons/,\
.,\
templates/,\
about.ini,\
jarutils.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/

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 363 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 320 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 157 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 302 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 194 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 307 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 287 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 308 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 463 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 265 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 308 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 664 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 325 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 445 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 321 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 344 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 137 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 B

View File

@@ -0,0 +1,502 @@
<?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.aapt2Problem"
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"
point="org.eclipse.core.resources.builders">
<builder
hasNature="true">
<run class="com.android.ide.eclipse.adt.build.ResourceManagerBuilder"/>
</builder>
</extension>
<extension
id="PreCompilerBuilder"
name="Android Pre Compiler"
point="org.eclipse.core.resources.builders">
<builder
hasNature="true">
<run class="com.android.ide.eclipse.adt.build.PreCompilerBuilder"/>
</builder>
</extension>
<extension
id="ApkBuilder"
name="Android Package Builder"
point="org.eclipse.core.resources.builders">
<builder
hasNature="true">
<run class="com.android.ide.eclipse.adt.build.ApkBuilder"/>
</builder>
</extension>
<extension
id="AndroidNature"
name="AndroidNature"
point="org.eclipse.core.resources.natures">
<runtime>
<run class="com.android.ide.eclipse.adt.project.AndroidNature"/>
</runtime>
<builder id="com.android.ide.eclipse.adt.ResourceManagerBuilder"/>
<builder id="com.android.ide.eclipse.adt.PreCompilerBuilder"/>
<builder id="com.android.ide.eclipse.adt.ApkBuilder"/>
</extension>
<extension
point="org.eclipse.ui.newWizards">
<category
id="com.android.ide.eclipse.wizards.category"
name="Android"/>
<wizard
canFinishEarly="false"
category="com.android.ide.eclipse.wizards.category"
class="com.android.ide.eclipse.adt.wizards.newproject.NewProjectWizard"
finalPerspective="org.eclipse.jdt.ui.JavaPerspective"
hasPages="true"
icon="icons/android.png"
id="com.android.ide.eclipse.adt.project.NewProjectWizard"
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">
<launchConfigurationType
delegate="com.android.ide.eclipse.adt.debug.launching.LaunchConfigDelegate"
delegateDescription="The Android Application Launcher supports running and debugging remote Android applications on devices or emulators."
delegateName="Android Launcher"
id="com.android.ide.eclipse.adt.debug.LaunchConfigType"
modes="debug, run"
name="Android Application"
public="true"
sourceLocatorId="org.eclipse.jdt.launching.sourceLocator.JavaSourceLookupDirector"
sourcePathComputerId="org.eclipse.jdt.launching.sourceLookup.javaSourcePathComputer">
</launchConfigurationType>
</extension>
<extension
point="org.eclipse.debug.ui.launchConfigurationTypeImages">
<launchConfigurationTypeImage
configTypeID="com.android.ide.eclipse.adt.debug.LaunchConfigType"
icon="icons/android.png"
id="com.android.ide.eclipse.adt.debug.LaunchConfigTypeImage"/>
</extension>
<extension
point="org.eclipse.debug.ui.launchConfigurationTabGroups">
<launchConfigurationTabGroup
class="com.android.ide.eclipse.adt.debug.ui.LaunchConfigTabGroup"
description="Android Application"
id="com.android.ide.eclipse.adt.debug.LaunchConfigTabGroup"
type="com.android.ide.eclipse.adt.debug.LaunchConfigType"/>
</extension>
<extension
point="org.eclipse.debug.ui.launchShortcuts">
<shortcut
category="com.android.ide.eclipse.adt.debug.LaunchConfigType"
class="com.android.ide.eclipse.adt.debug.launching.LaunchShortcut"
icon="icons/android.png"
id="com.android.ide.eclipse.adt.debug.launching.LaunchShortcut"
label="Android Application"
modes="debug, run">
<contextualLaunch>
<enablement>
<with variable="selection">
<count value="1"/>
<iterate>
<and>
<test property="org.eclipse.jdt.launching.isContainer"/>
<test property="org.eclipse.jdt.launching.hasProjectNature" args="com.android.ide.eclipse.adt.AndroidNature"/>
</and>
</iterate>
</with>
</enablement>
</contextualLaunch>
<perspective id="org.eclipse.jdt.ui.JavaPerspective"/>
<perspective id="org.eclipse.debug.ui.DebugPerspective"/>
<description
description="Runs an Android Application"
mode="run">
</description>
<description
description="Debugs an Android Application"
mode="debug">
</description>
</shortcut>
</extension>
<extension
point="org.eclipse.ui.popupMenus">
<objectContribution
id="com.android.ide.eclipse.adt.contribution1"
nameFilter="*"
objectClass="org.eclipse.core.resources.IProject"
adaptable="true">
<menu
id="com.android.ide.eclipse.adt.AndroidTools"
label="Android Tools"
path="additions">
<separator name="group1"/>
</menu>
<visibility>
<not>
<or>
<objectState
name="projectNature"
value="com.android.ide.eclipse.adt.AndroidNature"/>
<objectState
name="open"
value="false"/>
</or>
</not>
</visibility>
<action
class="com.android.ide.eclipse.adt.project.ConvertToAndroidAction"
enablesFor="1"
id="com.android.ide.eclipse.adt.ConvertToAndroidAction"
label="Convert To Android Project"
menubarPath="com.android.ide.eclipse.adt.AndroidTools/group1"/>
</objectContribution>
<objectContribution
id="com.android.ide.eclipse.adt.contribution2"
nameFilter="*"
objectClass="org.eclipse.core.resources.IProject"
adaptable="true">
<menu
id="com.android.ide.eclipse.adt.AndroidTools"
label="Android Tools"
path="additions">
<separator name="group1"/>
<separator name="group2"/>
</menu>
<filter
name="projectNature"
value="com.android.ide.eclipse.adt.AndroidNature">
</filter>
<action
class="com.android.ide.eclipse.adt.project.CreateAidlImportAction"
enablesFor="1"
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"
id="com.android.ide.eclipse.adt.project.ExportAction"
label="Export Unsigned Application Package..."
menubarPath="com.android.ide.eclipse.adt.AndroidTools/group2"/>
<action
class="com.android.ide.eclipse.adt.project.ExportWizardAction"
enablesFor="1"
id="com.android.ide.eclipse.adt.project.ExportWizardAction"
label="Export Signed Application Package..."
menubarPath="com.android.ide.eclipse.adt.AndroidTools/group2"/>
<action
class="com.android.ide.eclipse.adt.project.FixProjectAction"
enablesFor="1"
id="com.android.ide.eclipse.adt.project.FixProjectAction"
label="Fix Project Properties"
menubarPath="com.android.ide.eclipse.adt.AndroidTools/group3"/>
</objectContribution>
</extension>
<extension
point="org.eclipse.ui.preferencePages">
<page
class="com.android.ide.eclipse.adt.preferences.AndroidPreferencePage"
id="com.android.ide.eclipse.preferences.main"
name="Android"/>
<page
category="com.android.ide.eclipse.preferences.main"
class="com.android.ide.eclipse.adt.preferences.BuildPreferencePage"
id="com.android.ide.eclipse.adt.preferences.BuildPreferencePage"
name="Build"/>
<page
category="com.android.ide.eclipse.preferences.main"
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
id="com.android.ide.eclipse.adt.adtProblem"
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">
<newWizardShortcut id="com.android.ide.eclipse.adt.project.NewProjectWizard" />
<newWizardShortcut
id="com.android.ide.eclipse.editors.wizards.NewXmlFileWizard">
</newWizardShortcut>
</perspectiveExtension>
<perspectiveExtension targetID="org.eclipse.debug.ui.DebugPerspective">
<viewShortcut id="com.android.ide.eclipse.ddms.views.LogCatView"/>
<viewShortcut id="com.android.ide.eclipse.ddms.views.DeviceView"/>
</perspectiveExtension>
</extension>
<extension
point="org.eclipse.ui.ide.projectNatureImages">
<image
icon="icons/android_project.png"
id="com.android.ide.eclipse.adt.AndroidNature.image"
natureId="com.android.ide.eclipse.adt.AndroidNature">
</image>
</extension>
<extension
point="org.eclipse.jdt.core.classpathContainerInitializer">
<classpathContainerInitializer
class="com.android.ide.eclipse.adt.project.internal.AndroidClasspathContainerInitializer"
id="com.android.ide.eclipse.adt.project.AndroidClasspathContainerInitializer">
</classpathContainerInitializer>
<classpathContainerInitializer
class="com.android.ide.eclipse.adt.project.internal.AndroidClasspathContainerInitializer"
id="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK">
</classpathContainerInitializer>
</extension>
<extension
point="org.eclipse.ui.exportWizards">
<category
id="com.android.ide.eclipse.wizards.category"
name="Android">
</category>
<wizard
category="com.android.ide.eclipse.wizards.category"
class="com.android.ide.eclipse.adt.project.export.ExportWizard"
icon="icons/android.png"
id="com.android.ide.eclipse.adt.project.ExportWizard"
name="Export Android Application">
</wizard>
</extension>
<extension
point="org.eclipse.ui.commands">
<command
name="Debug Android Application"
description="Debug Android Application"
categoryId="org.eclipse.debug.ui.category.run"
id="com.android.ide.eclipse.adt.debug.launching.LaunchShortcut.debug">
</command>
<command
name="Run Android Application"
description="Run Android Application"
categoryId="org.eclipse.debug.ui.category.run"
id="com.android.ide.eclipse.adt.debug.launching.LaunchShortcut.run">
</command>
<keyBinding
keySequence="M3+M2+A D"
contextId="org.eclipse.ui.globalScope"
commandId="com.android.ide.eclipse.adt.debug.launching.LaunchShortcut.debug"
keyConfigurationId="org.eclipse.ui.defaultAcceleratorConfiguration">
</keyBinding>
<keyBinding
keySequence="M3+M2+A R"
contextId="org.eclipse.ui.globalScope"
commandId="com.android.ide.eclipse.adt.debug.launching.LaunchShortcut.run"
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>
<extension
point="org.eclipse.ui.actionSets">
<actionSet
description="Android Wizards"
id="adt.actionSet1"
label="Android Wizards"
visible="true">
<action
class="com.android.ide.eclipse.adt.wizards.actions.NewProjectAction"
icon="icons/new_adt_project.png"
id="com.android.ide.eclipse.adt.wizards.actions.NewProjectAction"
label="New Android Project"
style="push"
toolbarPath="android_project"
tooltip="Opens a wizard to help create a new Android project">
</action>
<action
class="com.android.ide.eclipse.adt.wizards.actions.NewXmlFileAction"
icon="icons/new_xml.png"
id="com.android.ide.eclipse.adt.wizards.actions.NewXmlFileAction"
label="New Android XML File"
style="push"
toolbarPath="android_project"
tooltip="Opens a wizard to help create a new Android XML file">
</action>
</actionSet>
</extension>
</plugin>

View File

@@ -0,0 +1,59 @@
/*
* 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;
import com.android.ide.eclipse.adt.project.internal.AndroidClasspathContainerInitializer;
/**
* Constant definition class.<br>
* <br>
* Most constants have a prefix defining the content.
* <ul>
* <li><code>WS_</code> Workspace path constant. Those are absolute paths,
* from the project root.</li>
* <li><code>OS_</code> OS path constant. These paths are different depending on the platform.</li>
* <li><code>FN_</code> File name constant.</li>
* <li><code>FD_</code> Folder name constant.</li>
* <li><code>MARKER_</code> Resource Marker Ids constant.</li>
* <li><code>EXT_</code> File extension constant. This does NOT include a dot.</li>
* <li><code>DOT_</code> File extension constant. This start with a dot.</li>
* <li><code>RE_</code> Regexp constant.</li>
* <li><code>BUILD_</code> Build verbosity level constant. To use with
* <code>AdtPlugin.printBuildToConsole()</code></li>
* </ul>
*/
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 an {@link AndroidClasspathContainerInitializer} has succeeded in creating an
* 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;
/** Build verbosity level "Normal" */
public final static int BUILD_NORMAL = 1;
/** Build verbosity level "Verbose". Those messages are only displayed in verbose mode */
public final static int BUILD_VERBOSE = 2;
}

View File

@@ -0,0 +1,48 @@
package com.android.ide.eclipse.adt;
import org.eclipse.osgi.util.NLS;
public class Messages extends NLS {
private static final String BUNDLE_NAME = "com.android.ide.eclipse.adt.messages"; //$NON-NLS-1$
public static String AdtPlugin_Android_SDK_Content_Loader;
public static String AdtPlugin_Android_SDK_Resource_Parser;
public static String AdtPlugin_Failed_To_Parse_s;
public static String AdtPlugin_Failed_To_Start_s;
public static String AdtPlugin_Parsing_Resources;
public static String Could_Not_Find;
public static String Could_Not_Find_Folder;
public static String Could_Not_Find_Folder_In_SDK;
public static String Dialog_Title_SDK_Location;
public static String Error_Check_Prefs;
public static String SDK_Not_Setup;
public static String VersionCheck_Plugin_Too_Old;
public static String VersionCheck_Plugin_Version_Failed;
public static String VersionCheck_SDK_Build_Too_Low;
public static String VersionCheck_SDK_Milestone_Too_Low;
public static String VersionCheck_Unable_To_Parse_Version_s;
static {
// initialize resource bundle
NLS.initializeMessages(BUNDLE_NAME, Messages.class);
}
private Messages() {
}
}

View File

@@ -0,0 +1,115 @@
/*
* 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;
import com.android.ide.eclipse.adt.AdtPlugin.CheckSdkErrorHandler;
import com.android.sdklib.SdkConstants;
import org.osgi.framework.Constants;
import org.osgi.framework.Version;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Class handling the version check for the plugin vs. the SDK.<br>
* The plugin must be able to support all version of the SDK.
*
* <p/>An SDK can require a new version of the plugin.
* <p/>The SDK contains a file with the minimum version for the plugin. This file is inside the
* <code>tools/lib</code> directory, and is called <code>plugin.prop</code>.<br>
* Inside that text file, there is a line in the format "plugin.version=#.#.#". This is checked
* against the current plugin version.<br>
*
*/
final class VersionCheck {
/**
* Pattern to get the minimum plugin version supported by the SDK. This is read from
* the file <code>$SDK/tools/lib/plugin.prop</code>.
*/
private final static Pattern sPluginVersionPattern = Pattern.compile(
"^plugin.version=(\\d+)\\.(\\d+)\\.(\\d+).*$"); //$NON-NLS-1$
/**
* Checks the plugin and the SDK have compatible versions.
* @param osSdkPath The path to the SDK
* @return true if compatible.
*/
public static boolean checkVersion(String osSdkPath, CheckSdkErrorHandler errorHandler) {
AdtPlugin plugin = AdtPlugin.getDefault();
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
int minMajorVersion = -1;
int minMinorVersion = -1;
int minMicroVersion = -1;
try {
FileReader reader = new FileReader(osLibs + SdkConstants.FN_PLUGIN_PROP);
BufferedReader bReader = new BufferedReader(reader);
String line;
while ((line = bReader.readLine()) != null) {
Matcher m = sPluginVersionPattern.matcher(line);
if (m.matches()) {
minMajorVersion = Integer.parseInt(m.group(1));
minMinorVersion = Integer.parseInt(m.group(2));
minMicroVersion = Integer.parseInt(m.group(3));
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.
}
// Failed to get the min plugin version number?
if (minMajorVersion == -1 || minMinorVersion == -1 || minMicroVersion ==-1) {
return errorHandler.handleWarning(Messages.VersionCheck_Plugin_Version_Failed);
}
// test the plugin number
String versionString = (String) plugin.getBundle().getHeaders().get(
Constants.BUNDLE_VERSION);
Version version = new Version(versionString);
boolean valid = true;
if (version.getMajor() < minMajorVersion) {
valid = false;
} else if (version.getMajor() == minMajorVersion) {
if (version.getMinor() < minMinorVersion) {
valid = false;
} else if (version.getMinor() == minMinorVersion) {
if (version.getMicro() < minMicroVersion) {
valid = false;
}
}
}
if (valid == false) {
return errorHandler.handleWarning(
String.format(Messages.VersionCheck_Plugin_Too_Old,
minMajorVersion, minMinorVersion, minMicroVersion, versionString));
}
return true; // no error!
}
}

View File

@@ -0,0 +1,280 @@
/*
* 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.build;
import com.android.ide.eclipse.adt.build.BaseBuilder.BaseDeltaVisitor;
import com.android.ide.eclipse.common.AndroidConstants;
import com.android.sdklib.SdkConstants;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceDelta;
import org.eclipse.core.resources.IResourceDeltaVisitor;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import java.util.ArrayList;
/**
* Delta resource visitor looking for changes that will trigger a new packaging of an Android
* application.
* <p/>
* This looks for the following changes:
* <ul>
* <li>Any change to the AndroidManifest.xml file</li>
* <li>Any change inside the assets/ folder</li>
* <li>Any file change inside the res/ folder</li>
* <li>Any .class file change inside the output folder</li>
* <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
implements IResourceDeltaVisitor {
/**
* compile flag. This is set to true if one of the changed/added/removed
* file is a .class file. Upon visiting all the delta resources, if this
* flag is true, then we know we'll have to make the "classes.dex" file.
*/
private boolean mConvertToDex = false;
/**
* compile flag. This is set to true if one of the changed/added/removed
* file is a resource file. Upon visiting all the delta resources, if
* this flag is true, then we know we'll have to make the intermediate
* apk file.
*/
private boolean mPackageResources = false;
/**
* Final package flag. This is set to true if one of the changed/added/removed
* file is a non java file (or aidl) in the resource folder. Upon visiting all the
* delta resources, if this flag is true, then we know we'll have to make the final
* package.
*/
private boolean mMakeFinalPackage = false;
/** List of source folders. */
private ArrayList<IPath> mSourceFolders;
private IPath mOutputPath;
private IPath mAssetPath;
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
* resource delta.
* @param sourceFolders the list of source folders for the project, relative to the workspace.
* @param outputfolder the output folder of the project.
*/
public ApkDeltaVisitor(BaseBuilder builder, ArrayList<IPath> sourceFolders,
IFolder outputfolder) {
super(builder);
mSourceFolders = sourceFolders;
if (outputfolder != null) {
mOutputPath = outputfolder.getFullPath();
}
IResource assetFolder = builder.getProject().findMember(SdkConstants.FD_ASSETS);
if (assetFolder != null) {
mAssetPath = assetFolder.getFullPath();
}
IResource resFolder = builder.getProject().findMember(SdkConstants.FD_RESOURCES);
if (resFolder != null) {
mResPath = resFolder.getFullPath();
}
IResource libFolder = builder.getProject().findMember(SdkConstants.FD_NATIVE_LIBS);
if (libFolder != null) {
mLibFolder = libFolder.getFullPath();
}
}
public boolean getConvertToDex() {
return mConvertToDex;
}
public boolean getPackageResources() {
return mPackageResources;
}
public boolean getMakeFinalPackage() {
return mMakeFinalPackage;
}
/**
* {@inheritDoc}
* @throws CoreException
*
* @see org.eclipse.core.resources.IResourceDeltaVisitor
* #visit(org.eclipse.core.resources.IResourceDelta)
*/
public boolean visit(IResourceDelta delta) throws CoreException {
// if all flags are true, we can stop going through the resource delta.
if (mConvertToDex && mPackageResources && mMakeFinalPackage) {
return false;
}
// we are only going to look for changes in res/, src/ and in
// AndroidManifest.xml since the delta visitor goes through the main
// folder before its childre we can check when the path segment
// count is 2 (format will be /$Project/folder) and make sure we are
// processing res/, src/ or AndroidManifest.xml
IResource resource = delta.getResource();
IPath path = resource.getFullPath();
String[] pathSegments = path.segments();
int type = resource.getType();
// since the delta visitor also visits the root we return true if
// segments.length = 1
if (pathSegments.length == 1) {
return true;
}
// check the manifest.
if (pathSegments.length == 2 &&
AndroidConstants.FN_ANDROID_MANIFEST.equalsIgnoreCase(pathSegments[1])) {
// if the manifest changed we have to repackage the
// resources.
mPackageResources = true;
mMakeFinalPackage = true;
// we don't want to go to the children, not like they are
// any for this resource anyway.
return false;
}
// 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
String ext = resource.getFileExtension();
if (AndroidConstants.EXT_CLASS.equalsIgnoreCase(ext)) {
mConvertToDex = true;
mMakeFinalPackage = true;
// no need to check the children, as we are in a package
// and there can only be subpackage children containing
// only .class files
return false;
}
// check for a few files directly in the output folder and force
// rebuild if they have been deleted.
if (delta.getKind() == IResourceDelta.REMOVED) {
IPath parentPath = path.removeLastSegments(1);
if (mOutputPath.equals(parentPath)) {
String resourceName = resource.getName();
// check if classes.dex was removed
if (resourceName.equalsIgnoreCase(AndroidConstants.FN_CLASSES_DEX)) {
mConvertToDex = true;
mMakeFinalPackage = true;
} else if (resourceName.equalsIgnoreCase(
AndroidConstants.FN_RESOURCES_AP_) ||
AndroidConstants.PATTERN_RESOURCES_S_AP_.matcher(
resourceName).matches()) {
// or if the default resources.ap_ or a configured version
// (resources-###.ap_) was removed.
mPackageResources = true;
mMakeFinalPackage = true;
}
}
}
}
// if this is a folder, we only go visit it if we don't already know
// that we need to convert to dex already.
return mConvertToDex == false;
} else if (mResPath != null && mResPath.isPrefixOf(path)) {
// in the res folder we are looking for any file modification
// (we don't care about folder being added/removed, only content
// is important)
if (type == IResource.FILE) {
mPackageResources = true;
mMakeFinalPackage = true;
return false;
}
// for folders, return true only if we don't already know we have to
// package the resources.
return mPackageResources == false;
} else if (mAssetPath != null && mAssetPath.isPrefixOf(path)) {
// this is the assets folder that was modified.
// we don't care what content was changed. All we care
// about is that something changed inside. No need to visit
// the children even.
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
// the final package.
// This could be a source folder or a folder leading to a source folder.
// However we only check this if we don't already know that we need to build the
// package anyway
if (mMakeFinalPackage == false) {
for (IPath sourcePath : mSourceFolders) {
if (sourcePath.isPrefixOf(path)) {
// In the source folders, we are looking for any kind of
// modification related to file that are not java files.
// Also excluded are aidl files, and package.html files
if (type == IResource.FOLDER) {
// always visit the subfolders, unless the folder is not to be included
return ApkBuilder.checkFolderForPackaging((IFolder)resource);
} else if (type == IResource.FILE) {
if (ApkBuilder.checkFileForPackaging((IFile)resource)) {
mMakeFinalPackage = true;
}
return false;
}
}
}
}
}
// if the folder is not inside one of the folders we are interested in (res, assets, output,
// source folders), it could be a folder leading to them, so we return true.
return true;
}
}

View File

@@ -0,0 +1,929 @@
/*
* 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.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.ide.eclipse.common.project.XmlErrorHandler;
import com.android.ide.eclipse.common.project.XmlErrorHandler.XmlErrorListener;
import com.android.sdklib.IAndroidTarget;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.IncrementalProjectBuilder;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.JavaCore;
import org.xml.sax.SAXException;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
/**
* Base builder for XML files. This class allows for basic XML parsing with
* error checking and marking the files for errors/warnings.
*/
abstract class BaseBuilder extends IncrementalProjectBuilder {
// TODO: rename the pattern to something that makes sense + javadoc comments.
/**
* Single line aapt warning for skipping files.<br>
* " (skipping hidden file '&lt;file path&gt;'"
*/
private final static Pattern sPattern0Line1 = Pattern.compile(
"^\\s+\\(skipping hidden file\\s'(.*)'\\)$"); //$NON-NLS-1$
/**
* First line of dual line aapt error.<br>
* "ERROR at line &lt;line&gt;: &lt;error&gt;"<br>
* " (Occurred while parsing &lt;path&gt;)"
*/
private final static Pattern sPattern1Line1 = Pattern.compile(
"^ERROR\\s+at\\s+line\\s+(\\d+):\\s+(.*)$"); //$NON-NLS-1$
/**
* Second line of dual line aapt error.<br>
* "ERROR at line &lt;line&gt;: &lt;error&gt;"<br>
* " (Occurred while parsing &lt;path&gt;)"<br>
* @see #sPattern1Line1
*/
private final static Pattern sPattern1Line2 = Pattern.compile(
"^\\s+\\(Occurred while parsing\\s+(.*)\\)$"); //$NON-NLS-1$
/**
* First line of dual line aapt error.<br>
* "ERROR: &lt;error&gt;"<br>
* "Defined at file &lt;path&gt; line &lt;line&gt;"
*/
private final static Pattern sPattern2Line1 = Pattern.compile(
"^ERROR:\\s+(.+)$"); //$NON-NLS-1$
/**
* Second line of dual line aapt error.<br>
* "ERROR: &lt;error&gt;"<br>
* "Defined at file &lt;path&gt; line &lt;line&gt;"<br>
* @see #sPattern2Line1
*/
private final static Pattern sPattern2Line2 = Pattern.compile(
"Defined\\s+at\\s+file\\s+(.+)\\s+line\\s+(\\d+)"); //$NON-NLS-1$
/**
* Single line aapt error<br>
* "&lt;path&gt; line &lt;line&gt;: &lt;error&gt;"
*/
private final static Pattern sPattern3Line1 = Pattern.compile(
"^(.+)\\sline\\s(\\d+):\\s(.+)$"); //$NON-NLS-1$
/**
* First line of dual line aapt error.<br>
* "ERROR parsing XML file &lt;path&gt;"<br>
* "&lt;error&gt; at line &lt;line&gt;"
*/
private final static Pattern sPattern4Line1 = Pattern.compile(
"^Error\\s+parsing\\s+XML\\s+file\\s(.+)$"); //$NON-NLS-1$
/**
* Second line of dual line aapt error.<br>
* "ERROR parsing XML file &lt;path&gt;"<br>
* "&lt;error&gt; at line &lt;line&gt;"<br>
* @see #sPattern4Line1
*/
private final static Pattern sPattern4Line2 = Pattern.compile(
"^(.+)\\s+at\\s+line\\s+(\\d+)$"); //$NON-NLS-1$
/**
* Single line aapt warning<br>
* "&lt;path&gt;:&lt;line&gt;: &lt;error&gt;"
*/
private final static Pattern sPattern5Line1 = Pattern.compile(
"^(.+?):(\\d+):\\s+WARNING:(.+)$"); //$NON-NLS-1$
/**
* Single line aapt error<br>
* "&lt;path&gt;:&lt;line&gt;: &lt;error&gt;"
*/
private final static Pattern sPattern6Line1 = Pattern.compile(
"^(.+?):(\\d+):\\s+(.+)$"); //$NON-NLS-1$
/**
* 4 line aapt error<br>
* "ERROR: 9-path image &lt;path&gt; malformed"<br>
* Line 2 and 3 are taken as-is while line 4 is ignored (it repeats with<br>
* 'ERROR: failure processing &lt;path&gt;)
*/
private final static Pattern sPattern7Line1 = Pattern.compile(
"^ERROR:\\s+9-patch\\s+image\\s+(.+)\\s+malformed\\.$"); //$NON-NLS-1$
private final static Pattern sPattern8Line1 = Pattern.compile(
"^(invalid resource directory name): (.*)$"); //$NON-NLS-1$
/**
* 2 line aapt error<br>
* "ERROR: Invalid configuration: foo"<br>
* " ^^^"<br>
* There's no need to parse the 2nd line.
*/
private final static Pattern sPattern9Line1 = Pattern.compile(
"^Invalid configuration: (.+)$"); //$NON-NLS-1$
/** SAX Parser factory. */
private SAXParserFactory mParserFactory;
/**
* Base Resource Delta Visitor to handle XML error
*/
protected static class BaseDeltaVisitor implements XmlErrorListener {
/** The Xml builder used to validate XML correctness. */
protected BaseBuilder mBuilder;
/**
* XML error flag. if true, we keep parsing the ResourceDelta but the
* compilation will not happen (we're putting markers)
*/
public boolean mXmlError = false;
public BaseDeltaVisitor(BaseBuilder builder) {
mBuilder = builder;
}
/**
* Finds a matching Source folder for the current path. This checkds if the current path
* leads to, or is a source folder.
* @param sourceFolders The list of source folders
* @param pathSegments The segments of the current path
* @return The segments of the source folder, or null if no match was found
*/
protected static String[] findMatchingSourceFolder(ArrayList<IPath> sourceFolders,
String[] pathSegments) {
for (IPath p : sourceFolders) {
// check if we are inside one of those source class path
// get the segments
String[] srcSegments = p.segments();
// compare segments. We want the path of the resource
// we're visiting to be
boolean valid = true;
int segmentCount = pathSegments.length;
for (int i = 0 ; i < segmentCount; i++) {
String s1 = pathSegments[i];
String s2 = srcSegments[i];
if (s1.equalsIgnoreCase(s2) == false) {
valid = false;
break;
}
}
if (valid) {
// this folder, or one of this children is a source
// folder!
// we return its segments
return srcSegments;
}
}
return null;
}
/**
* Sent when an XML error is detected.
* @see XmlErrorListener
*/
public void errorFound() {
mXmlError = true;
}
}
public BaseBuilder() {
super();
mParserFactory = SAXParserFactory.newInstance();
// FIXME when the compiled XML support for namespace is in, set this to true.
mParserFactory.setNamespaceAware(false);
}
/**
* Checks an Xml file for validity. Errors/warnings will be marked on the
* file
* @param resource the resource to check
* @param visitor a valid resource delta visitor
*/
protected final void checkXML(IResource resource, BaseDeltaVisitor visitor) {
// first make sure this is an xml file
if (resource instanceof IFile) {
IFile file = (IFile)resource;
// remove previous markers
removeMarkersFromFile(file, AndroidConstants.MARKER_XML);
// create the error handler
XmlErrorHandler reporter = new XmlErrorHandler(file, visitor);
try {
// parse
getParser().parse(file.getContents(), reporter);
} catch (Exception e1) {
}
}
}
/**
* Returns the SAXParserFactory, instantiating it first if it's not already
* created.
* @return the SAXParserFactory object
* @throws ParserConfigurationException
* @throws SAXException
*/
protected final SAXParser getParser() throws ParserConfigurationException,
SAXException {
return mParserFactory.newSAXParser();
}
/**
* Adds a marker to the current project.
*
* @param markerId The id of the marker to add.
* @param message the message associated with the mark
* @param severity the severity of the marker.
*/
protected final void markProject(String markerId, String message, int severity) {
BaseProjectHelper.addMarker(getProject(), markerId, message, severity);
}
/**
* Removes markers from a file.
* @param file The file from which to delete the markers.
* @param markerId The id of the markers to remove. If null, all marker of
* type <code>IMarker.PROBLEM</code> will be removed.
*/
protected final void removeMarkersFromFile(IFile file, String markerId) {
try {
if (file.exists()) {
file.deleteMarkers(markerId, true, IResource.DEPTH_ZERO);
}
} catch (CoreException ce) {
String msg = String.format(Messages.Marker_Delete_Error, markerId, file.toString());
AdtPlugin.printErrorToConsole(getProject(), msg);
}
}
/**
* Removes markers from a container and its children.
* @param folder The container from which to delete the markers.
* @param markerId The id of the markers to remove. If null, all marker of
* type <code>IMarker.PROBLEM</code> will be removed.
*/
protected final void removeMarkersFromContainer(IContainer folder, String markerId) {
try {
if (folder.exists()) {
folder.deleteMarkers(markerId, true, IResource.DEPTH_INFINITE);
}
} catch (CoreException ce) {
String msg = String.format(Messages.Marker_Delete_Error, markerId, folder.toString());
AdtPlugin.printErrorToConsole(getProject(), msg);
}
}
/**
* Removes markers from a project and its children.
* @param project The project from which to delete the markers
* @param markerId The id of the markers to remove. If null, all marker of
* type <code>IMarker.PROBLEM</code> will be removed.
*/
protected final static void removeMarkersFromProject(IProject project,
String markerId) {
try {
if (project.exists()) {
project.deleteMarkers(markerId, true, IResource.DEPTH_INFINITE);
}
} catch (CoreException ce) {
String msg = String.format(Messages.Marker_Delete_Error, markerId, project.getName());
AdtPlugin.printErrorToConsole(project, msg);
}
}
/**
* Get the stderr output of a process and return when the process is done.
* @param process The process to get the ouput from
* @param results The array to store the stderr output
* @return the process return code.
* @throws InterruptedException
*/
protected final int grabProcessOutput(final Process process,
final ArrayList<String> results)
throws InterruptedException {
// Due to the limited buffer size on windows for the standard io (stderr, stdout), we
// *need* to read both stdout and stderr all the time. If we don't and a process output
// a large amount, this could deadlock the process.
// read the lines as they come. if null is returned, it's
// because the process finished
new Thread("") { //$NON-NLS-1$
@Override
public void run() {
// create a buffer to read the stderr output
InputStreamReader is = new InputStreamReader(process.getErrorStream());
BufferedReader errReader = new BufferedReader(is);
try {
while (true) {
String line = errReader.readLine();
if (line != null) {
results.add(line);
} else {
break;
}
}
} catch (IOException e) {
// do nothing.
}
}
}.start();
new Thread("") { //$NON-NLS-1$
@Override
public void run() {
InputStreamReader is = new InputStreamReader(process.getInputStream());
BufferedReader outReader = new BufferedReader(is);
IProject project = getProject();
try {
while (true) {
String line = outReader.readLine();
if (line != null) {
AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE,
project, line);
} else {
break;
}
}
} catch (IOException e) {
// do nothing.
}
}
}.start();
// get the return code from the process
return process.waitFor();
}
/**
* Parse the output of aapt and mark the incorrect file with error markers
*
* @param results the output of aapt
* @param project the project containing the file to mark
* @return true if the parsing failed, false if success.
*/
protected final boolean parseAaptOutput(ArrayList<String> results,
IProject project) {
// nothing to parse? just return false;
if (results.size() == 0) {
return false;
}
// get the root of the project so that we can make IFile from full
// file path
String osRoot = project.getLocation().toOSString();
Matcher m;
for (int i = 0; i < results.size(); i++) {
String p = results.get(i);
m = sPattern0Line1.matcher(p);
if (m.matches()) {
// we ignore those (as this is an ignore message from aapt)
continue;
}
m = sPattern1Line1.matcher(p);
if (m.matches()) {
String lineStr = m.group(1);
String msg = m.group(2);
// get the matcher for the next line.
m = getNextLineMatcher(results, ++i, sPattern1Line2);
if (m == null) {
return true;
}
String location = m.group(1);
// check the values and attempt to mark the file.
if (checkAndMark(location, lineStr, msg, osRoot, project,
AndroidConstants.MARKER_AAPT_COMPILE, IMarker.SEVERITY_ERROR) == false) {
return true;
}
continue;
}
// this needs to be tested before Pattern2 since they both start with 'ERROR:'
m = sPattern7Line1.matcher(p);
if (m.matches()) {
String location = m.group(1);
String msg = p; // default msg is the line in case we don't find anything else
if (++i < results.size()) {
msg = results.get(i).trim();
if (++i < results.size()) {
msg = msg + " - " + results.get(i).trim(); //$NON-NLS-1$
// skip the next line
i++;
}
}
// display the error
if (checkAndMark(location, null, msg, osRoot, project,
AndroidConstants.MARKER_AAPT_COMPILE, IMarker.SEVERITY_ERROR) == false) {
return true;
}
// success, go to the next line
continue;
}
m = sPattern2Line1.matcher(p);
if (m.matches()) {
// get the msg
String msg = m.group(1);
// get the matcher for the next line.
m = getNextLineMatcher(results, ++i, sPattern2Line2);
if (m == null) {
return true;
}
String location = m.group(1);
String lineStr = m.group(2);
// check the values and attempt to mark the file.
if (checkAndMark(location, lineStr, msg, osRoot, project,
AndroidConstants.MARKER_AAPT_COMPILE, IMarker.SEVERITY_ERROR) == false) {
return true;
}
continue;
}
m = sPattern3Line1.matcher(p);
if (m.matches()) {
String location = m.group(1);
String lineStr = m.group(2);
String msg = m.group(3);
// check the values and attempt to mark the file.
if (checkAndMark(location, lineStr, msg, osRoot, project,
AndroidConstants.MARKER_AAPT_COMPILE, IMarker.SEVERITY_ERROR) == false) {
return true;
}
// success, go to the next line
continue;
}
m = sPattern4Line1.matcher(p);
if (m.matches()) {
// get the filename.
String location = m.group(1);
// get the matcher for the next line.
m = getNextLineMatcher(results, ++i, sPattern4Line2);
if (m == null) {
return true;
}
String msg = m.group(1);
String lineStr = m.group(2);
// check the values and attempt to mark the file.
if (checkAndMark(location, lineStr, msg, osRoot, project,
AndroidConstants.MARKER_AAPT_COMPILE, IMarker.SEVERITY_ERROR) == false) {
return true;
}
// success, go to the next line
continue;
}
m = sPattern5Line1.matcher(p);
if (m.matches()) {
String location = m.group(1);
String lineStr = m.group(2);
String msg = m.group(3);
// check the values and attempt to mark the file.
if (checkAndMark(location, lineStr, msg, osRoot, project,
AndroidConstants.MARKER_AAPT_COMPILE, IMarker.SEVERITY_WARNING) == false) {
return true;
}
// success, go to the next line
continue;
}
m = sPattern6Line1.matcher(p);
if (m.matches()) {
String location = m.group(1);
String lineStr = m.group(2);
String msg = m.group(3);
// check the values and attempt to mark the file.
if (checkAndMark(location, lineStr, msg, osRoot, project,
AndroidConstants.MARKER_AAPT_COMPILE, IMarker.SEVERITY_ERROR) == false) {
return true;
}
// success, go to the next line
continue;
}
m = sPattern8Line1.matcher(p);
if (m.matches()) {
String location = m.group(2);
String msg = m.group(1);
// check the values and attempt to mark the file.
if (checkAndMark(location, null, msg, osRoot, project,
AndroidConstants.MARKER_AAPT_COMPILE, IMarker.SEVERITY_ERROR) == false) {
return true;
}
// success, go to the next line
continue;
}
m = sPattern9Line1.matcher(p);
if (m.matches()) {
String badConfig = m.group(1);
String msg = String.format("APK Configuration filter '%1$s' is invalid", badConfig);
// skip the next line
i++;
// check the values and attempt to mark the file.
if (checkAndMark(null /*location*/, null, msg, osRoot, project,
AndroidConstants.MARKER_AAPT_PACKAGE, IMarker.SEVERITY_ERROR) == false) {
return true;
}
// success, go to the next line
continue;
}
// invalid line format, flag as error, and bail
return true;
}
return false;
}
/**
* Saves a String property into the persistent storage of the project.
* @param propertyName the name of the property. The id of the plugin is added to this string.
* @param value the value to save
* @return true if the save succeeded.
*/
protected boolean saveProjectStringProperty(String propertyName, String value) {
IProject project = getProject();
return ProjectHelper.saveStringProperty(project, propertyName, value);
}
/**
* Loads a String property from the persistent storage of the project.
* @param propertyName the name of the property. The id of the plugin is added to this string.
* @return the property value or null if it was not found.
*/
protected String loadProjectStringProperty(String propertyName) {
IProject project = getProject();
return ProjectHelper.loadStringProperty(project, propertyName);
}
/**
* Saves a property into the persistent storage of the project.
* @param propertyName the name of the property. The id of the plugin is added to this string.
* @param value the value to save
* @return true if the save succeeded.
*/
protected boolean saveProjectBooleanProperty(String propertyName, boolean value) {
IProject project = getProject();
return ProjectHelper.saveStringProperty(project, propertyName, Boolean.toString(value));
}
/**
* Loads a boolean property from the persistent storage of the project.
* @param propertyName the name of the property. The id of the plugin is added to this string.
* @param defaultValue The default value to return if the property was not found.
* @return the property value or the default value if the property was not found.
*/
protected boolean loadProjectBooleanProperty(String propertyName, boolean defaultValue) {
IProject project = getProject();
return ProjectHelper.loadBooleanProperty(project, propertyName, defaultValue);
}
/**
* Saves the path of a resource into the persistent storate of the project.
* @param propertyName the name of the property. The id of the plugin is added to this string.
* @param resource the resource which path is saved.
* @return true if the save succeeded
*/
protected boolean saveProjectResourceProperty(String propertyName, IResource resource) {
return ProjectHelper.saveResourceProperty(getProject(), propertyName, resource);
}
/**
* Loads the path of a resource from the persistent storage of the project, and returns the
* corresponding IResource object.
* @param propertyName the name of the property. The id of the plugin is added to this string.
* @return The corresponding IResource object (or children interface) or null
*/
protected IResource loadProjectResourceProperty(String propertyName) {
IProject project = getProject();
return ProjectHelper.loadResourceProperty(project, propertyName);
}
/**
* Check if the parameters gotten from the error output are valid, and mark
* the file with an AAPT marker.
* @param location the full OS path of the error file. If null, the project is marked
* @param lineStr
* @param message
* @param root The root directory of the project, in OS specific format.
* @param project
* @param markerId The marker id to put.
* @param severity The severity of the marker to put (IMarker.SEVERITY_*)
* @return true if the parameters were valid and the file was marked successfully.
*
* @see IMarker
*/
private final boolean checkAndMark(String location, String lineStr,
String message, String root, IProject project, String markerId, int severity) {
// check this is in fact a file
if (location != null) {
File f = new File(location);
if (f.exists() == false) {
return false;
}
}
// get the line number
int line = -1; // default value for error with no line.
if (lineStr != null) {
try {
line = Integer.parseInt(lineStr);
} catch (NumberFormatException e) {
// looks like the string we extracted wasn't a valid
// file number. Parsing failed and we return true
return false;
}
}
// add the marker
IResource f2 = project;
if (location != null) {
f2 = getResourceFromFullPath(location, root, project);
if (f2 == null) {
return false;
}
}
// check if there's a similar marker already, since aapt is launched twice
boolean markerAlreadyExists = false;
try {
IMarker[] markers = f2.findMarkers(markerId, true, IResource.DEPTH_ZERO);
for (IMarker marker : markers) {
int tmpLine = marker.getAttribute(IMarker.LINE_NUMBER, -1);
if (tmpLine != line) {
break;
}
int tmpSeverity = marker.getAttribute(IMarker.SEVERITY, -1);
if (tmpSeverity != severity) {
break;
}
String tmpMsg = marker.getAttribute(IMarker.MESSAGE, null);
if (tmpMsg == null || tmpMsg.equals(message) == false) {
break;
}
// if we're here, all the marker attributes are equals, we found it
// and exit
markerAlreadyExists = true;
break;
}
} catch (CoreException e) {
// if we couldn't get the markers, then we just mark the file again
// (since markerAlreadyExists is initialized to false, we do nothing)
}
if (markerAlreadyExists == false) {
if (line != -1) {
BaseProjectHelper.addMarker(f2, markerId, message, line,
severity);
} else {
BaseProjectHelper.addMarker(f2, markerId, message, severity);
}
}
return true;
}
/**
* Returns a matching matcher for the next line
* @param lines The array of lines
* @param nextIndex The index of the next line
* @param pattern The pattern to match
* @return null if error or no match, the matcher otherwise.
*/
private final Matcher getNextLineMatcher(ArrayList<String> lines,
int nextIndex, Pattern pattern) {
// unless we can't, because we reached the last line
if (nextIndex == lines.size()) {
// we expected a 2nd line, so we flag as error
// and we bail
return null;
}
Matcher m = pattern.matcher(lines.get(nextIndex));
if (m.matches()) {
return m;
}
return null;
}
private IResource getResourceFromFullPath(String filename, String root,
IProject project) {
if (filename.startsWith(root)) {
String file = filename.substring(root.length());
// get the resource
IResource r = project.findMember(file);
// if the resource is valid, we add the marker
if (r.exists()) {
return r;
}
}
return null;
}
/**
* Returns an array of external jar files used by the project.
* @return an array of OS-specific absolute file paths
*/
protected final String[] getExternalJars() {
// get the current project
IProject project = getProject();
// get a java project from it
IJavaProject javaProject = JavaCore.create(project);
IWorkspaceRoot wsRoot = ResourcesPlugin.getWorkspace().getRoot();
ArrayList<String> oslibraryList = new ArrayList<String>();
IClasspathEntry[] classpaths = javaProject.readRawClasspath();
if (classpaths != null) {
for (IClasspathEntry e : classpaths) {
if (e.getEntryKind() == IClasspathEntry.CPE_LIBRARY ||
e.getEntryKind() == IClasspathEntry.CPE_VARIABLE) {
// if this is a classpath variable reference, we resolve it.
if (e.getEntryKind() == IClasspathEntry.CPE_VARIABLE) {
e = JavaCore.getResolvedClasspathEntry(e);
}
// get the IPath
IPath path = e.getPath();
// check the name ends with .jar
if (AndroidConstants.EXT_JAR.equalsIgnoreCase(path.getFileExtension())) {
boolean local = false;
IResource resource = wsRoot.findMember(path);
if (resource != null && resource.exists() &&
resource.getType() == IResource.FILE) {
local = true;
oslibraryList.add(resource.getLocation().toOSString());
}
if (local == false) {
// if the jar path doesn't match a workspace resource,
// then we get an OSString and check if this links to a valid file.
String osFullPath = path.toOSString();
File f = new File(osFullPath);
if (f.exists()) {
oslibraryList.add(osFullPath);
} else {
String message = String.format( Messages.Couldnt_Locate_s_Error,
path);
AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE,
project, message);
// Also put a warning marker on the project
markProject(AdtConstants.MARKER_ADT, message,
IMarker.SEVERITY_WARNING);
}
}
}
}
}
}
return oslibraryList.toArray(new String[oslibraryList.size()]);
}
/**
* Aborts the build if the SDK/project setups are broken. This does not
* display any errors.
*
* @param project The {@link IJavaProject} being compiled.
* @throws CoreException
*/
protected final void abortOnBadSetup(IProject project) throws CoreException {
// check if we have finished loading the SDK.
if (AdtPlugin.getDefault().getSdkLoadStatus() != LoadStatus.LOADED) {
// we exit silently
stopBuild("SDK is not loaded yet");
}
// check the compiler compliance level.
if (ProjectHelper.checkCompilerCompliance(project) !=
ProjectHelper.COMPILER_COMPLIANCE_OK) {
// we exit silently
stopBuild(Messages.Compiler_Compliance_Error);
}
// Check that the SDK directory has been setup.
String osSdkFolder = AdtPlugin.getOsSdkFolder();
if (osSdkFolder == null || osSdkFolder.length() == 0) {
stopBuild(Messages.No_SDK_Setup_Error);
}
IAndroidTarget projectTarget = Sdk.getCurrent().getTarget(project);
if (projectTarget == null) {
// no target. error has been output by the container initializer:
// exit silently.
stopBuild("Project has no target");
}
}
/**
* Throws an exception to cancel the build.
*
* @param error the error message
* @param args the printf-style arguments to the error message.
* @throws CoreException
*/
protected final void stopBuild(String error, Object... args) throws CoreException {
throw new CoreException(new Status(IStatus.CANCEL, AdtPlugin.PLUGIN_ID,
String.format(error, args)));
}
}

View File

@@ -0,0 +1,168 @@
/*
* 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.build;
import com.android.ide.eclipse.adt.AdtPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import java.io.File;
import java.io.PrintStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
/**
* Wrapper to access dex.jar through reflection.
* <p/>Since there is no proper api to call the method in the dex library, this wrapper is going
* to access it through reflection.
*/
public final class DexWrapper {
private final static String DEX_MAIN = "com.android.dx.command.dexer.Main"; //$NON-NLS-1$
private final static String DEX_CONSOLE = "com.android.dx.command.DxConsole"; //$NON-NLS-1$
private final static String DEX_ARGS = "com.android.dx.command.dexer.Main$Arguments"; //$NON-NLS-1$
private final static String MAIN_RUN = "run"; //$NON-NLS-1$
private Method mRunMethod;
private Constructor<?> mArgConstructor;
private Field mArgOutName;
private Field mArgVerbose;
private Field mArgJarOutput;
private Field mArgFileNames;
private Field mConsoleOut;
private Field mConsoleErr;
/**
* Loads the dex library from a file path.
*
* The loaded library can be used via
* {@link DexWrapper#run(String, String[], boolean, PrintStream, PrintStream)}.
*
* @param osFilepath the location of the dex.jar file.
* @return an IStatus indicating the result of the load.
*/
public synchronized IStatus loadDex(String osFilepath) {
try {
File f = new File(osFilepath);
if (f.isFile() == false) {
return new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID, String.format(
Messages.DexWrapper_s_does_not_exists, osFilepath));
}
URL url = f.toURL();
URLClassLoader loader = new URLClassLoader(new URL[] { url },
DexWrapper.class.getClassLoader());
// get the classes.
Class<?> mainClass = loader.loadClass(DEX_MAIN);
Class<?> consoleClass = loader.loadClass(DEX_CONSOLE);
Class<?> argClass = loader.loadClass(DEX_ARGS);
try {
// now get the fields/methods we need
mRunMethod = mainClass.getMethod(MAIN_RUN, argClass);
mArgConstructor = argClass.getConstructor();
mArgOutName = argClass.getField("outName"); //$NON-NLS-1$
mArgJarOutput = argClass.getField("jarOutput"); //$NON-NLS-1$
mArgFileNames = argClass.getField("fileNames"); //$NON-NLS-1$
mArgVerbose = argClass.getField("verbose"); //$NON-NLS-1$
mConsoleOut = consoleClass.getField("out"); //$NON-NLS-1$
mConsoleErr = consoleClass.getField("err"); //$NON-NLS-1$
} catch (SecurityException e) {
return createErrorStatus(Messages.DexWrapper_SecuryEx_Unable_To_Find_API, e);
} catch (NoSuchMethodException e) {
return createErrorStatus(Messages.DexWrapper_SecuryEx_Unable_To_Find_Method, e);
} catch (NoSuchFieldException e) {
return createErrorStatus(Messages.DexWrapper_SecuryEx_Unable_To_Find_Field, e);
}
return Status.OK_STATUS;
} catch (MalformedURLException e) {
// really this should not happen.
return createErrorStatus(
String.format(Messages.DexWrapper_Failed_to_load_s, osFilepath), e);
} catch (ClassNotFoundException e) {
return createErrorStatus(
String.format(Messages.DexWrapper_Failed_to_load_s, osFilepath), e);
}
}
/**
* Runs the dex command.
* @param osOutFilePath the OS path to the outputfile (classes.dex
* @param osFilenames list of input source files (.class and .jar files)
* @param verbose verbose mode.
* @param outStream the stdout console
* @param errStream the stderr console
* @return the integer return code of com.android.dx.command.dexer.Main.run()
* @throws CoreException
*/
public synchronized int run(String osOutFilePath, String[] osFilenames,
boolean verbose, PrintStream outStream, PrintStream errStream) throws CoreException {
try {
// set the stream
mConsoleErr.set(null /* obj: static field */, errStream);
mConsoleOut.set(null /* obj: static field */, outStream);
// create the Arguments object.
Object args = mArgConstructor.newInstance();
mArgOutName.set(args, osOutFilePath);
mArgFileNames.set(args, osFilenames);
mArgJarOutput.set(args, false);
mArgVerbose.set(args, verbose);
// call the run method
Object res = mRunMethod.invoke(null /* obj: static method */, args);
if (res instanceof Integer) {
return ((Integer)res).intValue();
}
return -1;
} catch (IllegalAccessException e) {
throw new CoreException(createErrorStatus(
String.format(Messages.DexWrapper_Unable_To_Execute_Dex_s, e.getMessage()), e));
} catch (InstantiationException e) {
throw new CoreException(createErrorStatus(
String.format(Messages.DexWrapper_Unable_To_Execute_Dex_s, e.getMessage()), e));
} catch (InvocationTargetException e) {
throw new CoreException(createErrorStatus(
String.format(Messages.DexWrapper_Unable_To_Execute_Dex_s, e.getMessage()), e));
}
}
private static IStatus createErrorStatus(String message, Exception e) {
AdtPlugin.log(e, message);
AdtPlugin.printErrorToConsole(Messages.DexWrapper_Dex_Loader, message);
return new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID, message, e);
}
}

View File

@@ -0,0 +1,137 @@
package com.android.ide.eclipse.adt.build;
import org.eclipse.osgi.util.NLS;
public class Messages extends NLS {
private static final String BUNDLE_NAME = "com.android.ide.eclipse.adt.build.build_messages"; //$NON-NLS-1$
public static String AAPT_Error;
public static String AAPT_Exec_Error;
public static String Added_s_s_Needs_Updating;
public static String AIDL_Exec_Error;
public static String AIDL_Java_Conflict;
public static String ApkBuilder_Certificate_Expired_on_s;
public static String ApkBuilder_JAVA_HOME_is_s;
public static String ApkBuilder_Packaging_s;
public static String ApkBuilder_Packaging_s_into_s;
public static String ApkBuilder_s_Conflict_with_file_s;
public static String ApkBuilder_Signing_Key_Creation_s;
public static String ApkBuilder_Unable_To_Gey_Key;
public static String ApkBuilder_UnableBuild_Dex_Not_loaded;
public static String ApkBuilder_Update_or_Execute_manually_s;
public static String ApkBuilder_Using_Default_Key;
public static String ApkBuilder_Using_s_To_Sign;
public static String Checking_Package_Change;
public static String Compiler_Compliance_Error;
public static String Couldnt_Locate_s_Error;
public static String Dalvik_Error_d;
public static String Dalvik_Error_s;
public static String Delete_Obsolete_Error;
public static String DexWrapper_Dex_Loader;
public static String DexWrapper_Failed_to_load_s;
public static String DexWrapper_s_does_not_exists;
public static String DexWrapper_SecuryEx_Unable_To_Find_API;
public static String DexWrapper_SecuryEx_Unable_To_Find_Field;
public static String DexWrapper_SecuryEx_Unable_To_Find_Method;
public static String DexWrapper_Unable_To_Execute_Dex_s;
public static String DX_Jar_Error;
public static String Failed_To_Get_Output;
public static String Final_Archive_Error_s;
public static String Incompatible_VM_Warning;
public static String Marker_Delete_Error;
public static String No_SDK_Setup_Error;
public static String Nothing_To_Compile;
public static String Output_Missing;
public static String Package_s_Doesnt_Exist_Error;
public static String Preparing_Generated_Files;
public static String Project_Has_Errors;
public static String Refreshing_Res;
public static String Removing_Generated_Classes;
public static String Requires_1_5_Error;
public static String Requires_Class_Compatibility_5;
public static String Requires_Compiler_Compliance_5;
public static String Requires_Source_Compatibility_5;
public static String s_Contains_Xml_Error;
public static String s_Doesnt_Declare_Package_Error;
public static String s_File_Missing;
public static String s_Missing_Repackaging;
public static String s_Modified_Manually_Recreating_s;
public static String s_Modified_Recreating_s;
public static String s_Removed_Recreating_s;
public static String s_Removed_s_Needs_Updating;
public static String Start_Full_Apk_Build;
public static String Start_Full_Pre_Compiler;
public static String Start_Inc_Apk_Build;
public static String Start_Inc_Pre_Compiler;
public static String Unparsed_AAPT_Errors;
public static String Unparsed_AIDL_Errors;
public static String Xml_Error;
static {
// initialize resource bundle
NLS.initializeMessages(BUNDLE_NAME, Messages.class);
}
private Messages() {
}
}

View File

@@ -0,0 +1,432 @@
/*
* 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.build;
import com.android.ide.eclipse.adt.AdtConstants;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.build.BaseBuilder.BaseDeltaVisitor;
import com.android.ide.eclipse.adt.project.ProjectHelper;
import com.android.ide.eclipse.common.AndroidConstants;
import com.android.ide.eclipse.common.project.AndroidManifestParser;
import com.android.ide.eclipse.common.project.BaseProjectHelper;
import com.android.sdklib.SdkConstants;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceDelta;
import org.eclipse.core.resources.IResourceDeltaVisitor;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Path;
import java.util.ArrayList;
/**
* Resource Delta visitor for the pre-compiler.
*/
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
* this flag is true, then we know we'll have to compile the resources
* into R.java
*/
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.
*/
private boolean mInRes = false;
/**
* In Source folder flag. This allows us to know if we're in a source
* folder.
*/
private boolean mInSrc = false;
/** List of source folders. */
private ArrayList<IPath> mSourceFolders;
public PreCompilerDeltaVisitor(BaseBuilder builder, ArrayList<IPath> sourceFolders) {
super(builder);
mSourceFolders = sourceFolders;
}
public boolean getCompileResources() {
return mCompileResources;
}
public ArrayList<IFile> getAidlToCompile() {
return mAidlToCompile;
}
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
* visiting.
*/
public boolean getCheckedManifestXml() {
return mCheckedManifestXml;
}
/**
* Returns the manifest package if the manifest was checked/parsed.
* <p/>
* This can return null in two cases:
* <ul>
* <li>The manifest was not part of the resource change delta, and the manifest was
* not checked/parsed ({@link #getCheckedManifestXml()} returns <code>false</code>)</li>
* <li>The manifest was parsed ({@link #getCheckedManifestXml()} returns <code>true</code>),
* but the package declaration is missing</li>
* </ul>
* @return the manifest package or null.
*/
public String getManifestPackage() {
return mJavaPackage;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.core.resources.IResourceDeltaVisitor
* #visit(org.eclipse.core.resources.IResourceDelta)
*/
public boolean visit(IResourceDelta delta) throws CoreException {
// we are only going to look for changes in res/, source folders and in
// AndroidManifest.xml since the delta visitor goes through the main
// folder before its children we can check when the path segment
// count is 2 (format will be /$Project/folder) and make sure we are
// processing res/, source folders or AndroidManifest.xml
IResource resource = delta.getResource();
IPath path = resource.getFullPath();
String[] segments = path.segments();
// since the delta visitor also visits the root we return true if
// segments.length = 1
if (segments.length == 1) {
return true;
} else if (segments.length == 2) {
// if we are at an item directly under the root directory,
// then we are not yet in a source or resource folder
mInRes = mInSrc = false;
if (SdkConstants.FD_RESOURCES.equalsIgnoreCase(segments[1])) {
// this is the resource folder that was modified. we want to
// see its content.
// since we're going to visit its children next, we set the
// flag
mInRes = true;
mInSrc = false;
return true;
} else if (AndroidConstants.FN_ANDROID_MANIFEST.equalsIgnoreCase(segments[1])) {
// any change in the manifest could trigger a new R.java
// class, so we don't need to check the delta kind
if (delta.getKind() != IResourceDelta.REMOVED) {
// parse the manifest for errors
AndroidManifestParser parser = BaseProjectHelper.parseManifestForError(
(IFile)resource, this);
if (parser != null) {
mJavaPackage = parser.getPackage();
}
mCheckedManifestXml = true;
}
mCompileResources = true;
// 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 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/
// so first we test if we already know we are in a source or
// resource folder.
if (mInSrc) {
// if we are in the res folder, we are looking for the following changes:
// - added/removed/modified aidl files.
// - missing R.java file
// if the resource is a folder, we just go straight to the children
if (resource.getType() == IResource.FOLDER) {
return true;
}
if (resource.getType() != IResource.FILE) {
return false;
}
IFile file = (IFile)resource;
// get the modification kind
int kind = delta.getKind();
if (kind == IResourceDelta.ADDED) {
// we only care about added files (inside the source folders), if they
// are aidl files.
// get the extension of the resource
String ext = resource.getFileExtension();
if (AndroidConstants.EXT_AIDL.equalsIgnoreCase(ext)) {
// look for an already existing matching java file
String javaName = resource.getName().replaceAll(
AndroidConstants.RE_AIDL_EXT,
AndroidConstants.DOT_JAVA);
// get the parent container
IContainer ic = resource.getParent();
IFile javaFile = ic.getFile(new Path(javaName));
if (javaFile != null && javaFile.exists()) {
// check if that file was generated by the plugin. Normally those files are
// deleted automatically, but it's better to check.
String aidlPath = ProjectHelper.loadStringProperty(javaFile,
PreCompilerBuilder.PROPERTY_ANDROID_GENERATED);
if (aidlPath == null) {
// mark the aidl file that it cannot be compile just yet
ProjectHelper.saveBooleanProperty(file,
PreCompilerBuilder.PROPERTY_ANDROID_CONFLICT, true);
}
// we add it anyway so that we can try to compile it at every compilation
// until the conflict is fixed.
mAidlToCompile.add(file);
} else {
// the java file doesn't exist, we can safely add the file to the list
// of files to compile.
mAidlToCompile.add(file);
}
}
return false;
}
// get the filename
String fileName = segments[segments.length - 1];
boolean outputMessage = false;
// Special case of R.java/Manifest.java.
// FIXME: This does not check the package. Any modification of R.java/Manifest.java in another project will trigger a new recompilation of the resources.
if (AndroidConstants.FN_RESOURCE_CLASS.equals(fileName) ||
AndroidConstants.FN_MANIFEST_CLASS.equals(fileName)) {
// if it was removed, there's a possibility that it was removed due to a
// package change, or an aidl that was removed, but the only thing
// that will happen is that we'll have an extra build. Not much of a problem.
mCompileResources = true;
// we want a warning
outputMessage = true;
} else {
// get the extension of the resource
String ext = resource.getFileExtension();
if (AndroidConstants.EXT_AIDL.equalsIgnoreCase(ext)) {
if (kind == IResourceDelta.REMOVED) {
mAidlToRemove.add(file);
} else {
mAidlToCompile.add(file);
}
} else {
if (kind == IResourceDelta.REMOVED) {
// the file has been removed. we need to check it's a java file and that
// there's a matching aidl file. We can't check its persistent storage
// anymore.
if (AndroidConstants.EXT_JAVA.equalsIgnoreCase(ext)) {
String aidlFile = resource.getName().replaceAll(
AndroidConstants.RE_JAVA_EXT,
AndroidConstants.DOT_AIDL);
// get the parent container
IContainer ic = resource.getParent();
IFile f = ic.getFile(new Path(aidlFile));
if (f != null && f.exists() ) {
// make sure that the aidl file is not in conflict anymore, in
// case the java file was not generated by us.
if (ProjectHelper.loadBooleanProperty(f,
PreCompilerBuilder.PROPERTY_ANDROID_CONFLICT, false)) {
ProjectHelper.saveBooleanProperty(f,
PreCompilerBuilder.PROPERTY_ANDROID_CONFLICT, false);
} else {
outputMessage = true;
}
mAidlToCompile.add(f);
}
}
} else {
// check if it's an android generated java file.
IResource aidlSource = ProjectHelper.loadResourceProperty(
file, PreCompilerBuilder.PROPERTY_ANDROID_GENERATED);
if (aidlSource != null && aidlSource.exists() &&
aidlSource.getType() == IResource.FILE) {
// it looks like this was a java file created from an aidl file.
// we need to add the aidl file to the list of aidl file to compile
mAidlToCompile.add((IFile)aidlSource);
outputMessage = true;
}
}
}
}
if (outputMessage) {
if (kind == IResourceDelta.REMOVED) {
// We pring an error just so that it's red, but it's just a warning really.
String msg = String.format(Messages.s_Removed_Recreating_s, fileName);
AdtPlugin.printErrorToConsole(mBuilder.getProject(), msg);
} else if (kind == IResourceDelta.CHANGED) {
// the file was modified manually! we can't allow it.
String msg = String.format(Messages.s_Modified_Manually_Recreating_s, fileName);
AdtPlugin.printErrorToConsole(mBuilder.getProject(), msg);
}
}
// no children.
return false;
} else if (mInRes) {
// if we are in the res folder, we are looking for the following
// changes:
// - added/removed/modified xml files.
// - added/removed files of any other type
// if the resource is a folder, we just go straight to the
// children
if (resource.getType() == IResource.FOLDER) {
return true;
}
// get the extension of the resource
String ext = resource.getFileExtension();
int kind = delta.getKind();
String p = resource.getProjectRelativePath().toString();
String message = null;
switch (kind) {
case IResourceDelta.CHANGED:
// display verbose message
message = String.format(Messages.s_Modified_Recreating_s, p,
AndroidConstants.FN_RESOURCE_CLASS);
break;
case IResourceDelta.ADDED:
// display verbose message
message = String.format(Messages.Added_s_s_Needs_Updating, p,
AndroidConstants.FN_RESOURCE_CLASS);
break;
case IResourceDelta.REMOVED:
// display verbose message
message = String.format(Messages.s_Removed_s_Needs_Updating, p,
AndroidConstants.FN_RESOURCE_CLASS);
break;
}
if (message != null) {
AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE,
mBuilder.getProject(), message);
}
if (AndroidConstants.EXT_XML.equalsIgnoreCase(ext)) {
if (kind != IResourceDelta.REMOVED) {
// check xml Validity
mBuilder.checkXML(resource, this);
}
// if we are going through this resource, it was modified
// somehow.
// we don't care if it was an added/removed/changed event
mCompileResources = true;
return false;
} else {
// this is a non xml resource.
if (kind == IResourceDelta.ADDED
|| kind == IResourceDelta.REMOVED) {
mCompileResources = true;
return false;
}
}
} else if (resource instanceof IFolder) {
// in this case we may be inside a folder that contains a source
// folder.
String[] sourceFolderSegments = findMatchingSourceFolder(mSourceFolders, segments);
if (sourceFolderSegments != null) {
// we have a match!
mInRes = false;
// Check if the current folder is actually a source folder
if (sourceFolderSegments.length == segments.length) {
mInSrc = true;
}
// and return true to visit the content, no matter what
return true;
}
// if we're here, we are visiting another folder
// like /$Project/bin/ for instance (we get notified for changes
// in .class!)
// This could also be another source folder and we have found
// R.java in a previous source folder
// We don't want to visit its children
return false;
}
return false;
}
}

View File

@@ -0,0 +1,130 @@
/*
* 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.build;
import com.android.ide.eclipse.adt.AdtConstants;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.project.ProjectHelper;
import com.android.ide.eclipse.adt.sdk.LoadStatus;
import com.android.ide.eclipse.adt.sdk.Sdk;
import com.android.ide.eclipse.common.AndroidConstants;
import com.android.ide.eclipse.common.project.BaseProjectHelper;
import com.android.sdklib.IAndroidTarget;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import java.util.Map;
/**
* Resource manager builder whose only purpose is to refresh the resource folder
* so that the other builder use an up to date version.
*/
public class ResourceManagerBuilder extends BaseBuilder {
public static final String ID = "com.android.ide.eclipse.adt.ResourceManagerBuilder"; //$NON-NLS-1$
public ResourceManagerBuilder() {
super();
}
// build() returns a list of project from which this project depends for future compilation.
@SuppressWarnings("unchecked") //$NON-NLS-1$
@Override
protected IProject[] build(int kind, Map args, IProgressMonitor monitor)
throws CoreException {
// Get the project.
IProject project = getProject();
// Clear the project of the generic markers
BaseBuilder.removeMarkersFromProject(project, AdtConstants.MARKER_ADT);
// Check the compiler compliance level, displaying the error message
// since this is the first builder.
int res = ProjectHelper.checkCompilerCompliance(project);
String errorMessage = null;
switch (res) {
case ProjectHelper.COMPILER_COMPLIANCE_LEVEL:
errorMessage = Messages.Requires_Compiler_Compliance_5;
case ProjectHelper.COMPILER_COMPLIANCE_SOURCE:
errorMessage = Messages.Requires_Source_Compatibility_5;
case ProjectHelper.COMPILER_COMPLIANCE_CODEGEN_TARGET:
errorMessage = Messages.Requires_Class_Compatibility_5;
}
if (errorMessage != null) {
BaseProjectHelper.addMarker(project, AdtConstants.MARKER_ADT, errorMessage,
IMarker.SEVERITY_ERROR);
AdtPlugin.printErrorToConsole(project, errorMessage);
// interrupt the build. The next builders will not run.
stopBuild(errorMessage);
}
// Check that the SDK directory has been setup.
String osSdkFolder = AdtPlugin.getOsSdkFolder();
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);
// This interrupts the build. The next builders will not run.
stopBuild(Messages.No_SDK_Setup_Error);
}
// check if we have finished loading the SDK.
if (AdtPlugin.getDefault().getSdkLoadStatus() != LoadStatus.LOADED) {
// we exit silently
// This interrupts the build. The next builders will not run.
stopBuild("SDK is not loaded yet");
}
// check the project has a target
IAndroidTarget projectTarget = Sdk.getCurrent().getTarget(project);
if (projectTarget == null) {
// no target. marker has been set by the container initializer: exit silently.
// This interrupts the build. The next builders will not run.
stopBuild("Project has no target");
}
// Check the preference to be sure we are supposed to refresh
// the folders.
if (AdtPlugin.getAutoResRefresh()) {
AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, project,
Messages.Refreshing_Res);
// refresh the res folder.
IFolder resFolder = project.getFolder(
AndroidConstants.WS_RESOURCES);
resFolder.refreshLocal(IResource.DEPTH_INFINITE, monitor);
// Also refresh the assets folder to make sure the ApkBuilder
// will now it's changed and will force a new resource packaging.
IFolder assetsFolder = project.getFolder(
AndroidConstants.WS_ASSETS);
assetsFolder.refreshLocal(IResource.DEPTH_INFINITE, monitor);
}
return null;
}
}

View File

@@ -0,0 +1,61 @@
Start_Full_Apk_Build=Starting full Package build.
Start_Full_Pre_Compiler=Starting full Pre Compiler.
Start_Inc_Apk_Build=Starting incremental Package build: Checking resource changes.
Start_Inc_Pre_Compiler=Starting incremental Pre Compiler: Checking resource changes.
Xml_Error=Error in an XML file: aborting build.
s_Missing_Repackaging=%1$s missing. Repackaging.
Project_Has_Errors=Project contains error(s). Package Builder aborted.
Failed_To_Get_Output=Failed to get project output folder\!
Output_Missing=Output folder missing\! Make sure your project is configured properly.
s_File_Missing=%1$s file missing\!
Unparsed_AAPT_Errors=Unparsed aapt error(s)\! Check the console for output.
Unparsed_AIDL_Errors=Unparsed aidl error\! Check the console for output.
AAPT_Exec_Error=Error executing aapt. Please check aapt is present at %1$s
Dalvik_Error_d=Conversion to Dalvik format failed with error %1$d
DX_Jar_Error=Dx.jar is not found inside the plugin. Reinstall ADT\!
Dalvik_Error_s=Conversion to Dalvik format failed: %1$s
Incompatible_VM_Warning=Note: You may be using an incompatible virtual machine or class library.
Requires_1_5_Error=This program requires JDK 1.5 compatibility.
Final_Archive_Error_s=Error generating final archive: %1$s
Marker_Delete_Error=Failed to delete marker '%1$s' for %2$s
Couldnt_Locate_s_Error=Could not locate '%1$s'. This will not be added to the package.
Compiler_Compliance_Error=Compiler compliance level not compatible: Build aborted.
No_SDK_Setup_Error=SDK directory has not been setup. Please go to the Android preferences and enter the location of the SDK.
s_Contains_Xml_Error=%1$s contains XML error: Build aborted.
s_Doesnt_Declare_Package_Error=%1$s does not declare a Java package: Build aborted.
Checking_Package_Change=Checking Java package value did not change...
Package_s_Doesnt_Exist_Error=Package '%1$s' does not exist\!
Preparing_Generated_Files=Preparing generated java files for update/creation.
AAPT_Error='aapt' error. Pre Compiler Build aborted.
Nothing_To_Compile=Nothing to pre compile\!
Removing_Generated_Classes=Removing generated java classes.
Delete_Obsolete_Error=Failed to delete obsolete %1$s, please delete it manually
DexWrapper_Dex_Loader=Dex Loader
AIDL_Java_Conflict=%1$s is in the way of %2$s, remove it or rename of one the files.
AIDL_Exec_Error=Error executing aidl. Please check aidl is present at %1$s
s_Removed_Recreating_s=%1$s was removed\! Recreating %1$s\!
s_Modified_Manually_Recreating_s=%1$s was modified manually\! Reverting to generated version\!
s_Modified_Recreating_s='%1$s' was modified, %2$s needs to be updated.
Added_s_s_Needs_Updating=New resource file: '%1$s', %2$s needs to be updated.
s_Removed_s_Needs_Updating='%1$s' was removed, %2$s needs to be updated.
Requires_Compiler_Compliance_5=Android requires compiler compliance level 5.0. Please fix project properties.
Requires_Source_Compatibility_5=Android requires source compatibility set to 5.0. Please fix project properties.
Requires_Class_Compatibility_5=Android requires .class compatibility set to 5.0. Please fix project properties.
Refreshing_Res=Refreshing resource folders.
DexWrapper_s_does_not_exists=%1$s does not exist or is not a file
DexWrapper_Failed_to_load_s=Failed to load %1$s
DexWrapper_Unable_To_Execute_Dex_s=Unable to execute dex: %1$s
DexWrapper_SecuryEx_Unable_To_Find_API=SecurityException: Unable to find API for dex.jar
DexWrapper_SecuryEx_Unable_To_Find_Method=SecurityException: Unable to find method for dex.jar
DexWrapper_SecuryEx_Unable_To_Find_Field=SecurityException: Unable to find field for dex.jar
ApkBuilder_UnableBuild_Dex_Not_loaded=Unable to build: the file dex.jar was not loaded from the SDK folder\!
ApkBuilder_Using_Default_Key=Using default debug key to sign package
ApkBuilder_Using_s_To_Sign=Using '%1$s' to sign package
ApkBuilder_Signing_Key_Creation_s=Signing Key Creation:
ApkBuilder_Unable_To_Gey_Key=Unable to get debug signature key
ApkBuilder_Certificate_Expired_on_s=Debug certificate expired on %1$s\!
ApkBuilder_Packaging_s=Packaging %1$s
ApkBuilder_JAVA_HOME_is_s=The Java VM Home used is: %1$s
ApkBuilder_Update_or_Execute_manually_s=Update it if necessary, or manually execute the following command:
ApkBuilder_s_Conflict_with_file_s=%1$s conflicts with another file already put at %2$s
ApkBuilder_Packaging_s_into_s=Packaging %1$s into %2$s

View File

@@ -0,0 +1,57 @@
/*
* Copyright (C) 2007 The Android Open Source Project
*
* Licensed under the Eclipse Public License, Version 1.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.eclipse.org/org/documents/epl-v10.php
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.ide.eclipse.adt.debug.launching;
import org.eclipse.debug.core.DebugException;
import org.eclipse.debug.core.ILaunchConfiguration;
import org.eclipse.debug.core.ILaunchManager;
import org.eclipse.debug.core.Launch;
import org.eclipse.debug.core.model.ISourceLocator;
/**
* Custom implementation of Launch to allow access to the LaunchManager
*
*/
class AndroidLaunch extends Launch {
/**
* Basic constructor does nothing special
* @param launchConfiguration
* @param mode
* @param locator
*/
public AndroidLaunch(ILaunchConfiguration launchConfiguration, String mode,
ISourceLocator locator) {
super(launchConfiguration, mode, locator);
}
/** Stops the launch, and removes it from the launch manager */
public void stopLaunch() {
ILaunchManager mgr = getLaunchManager();
if (canTerminate()) {
try {
terminate();
} catch (DebugException e) {
// well looks like we couldn't stop it. nothing else to be
// done really
}
}
// remove the launch
mgr.removeLaunch(this);
}
}

View File

@@ -0,0 +1,705 @@
/*
* Copyright (C) 2007 The Android Open Source Project
*
* Licensed under the Eclipse Public License, Version 1.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.eclipse.org/org/documents/epl-v10.php
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.ide.eclipse.adt.debug.launching;
import com.android.ddmlib.AndroidDebugBridge;
import com.android.ddmlib.Client;
import com.android.ddmlib.Device;
import com.android.ddmlib.IDevice;
import com.android.ddmlib.AndroidDebugBridge.IDeviceChangeListener;
import com.android.ddmlib.Device.DeviceState;
import com.android.ddmuilib.IImageLoader;
import com.android.ddmuilib.ImageHelper;
import com.android.ddmuilib.TableHelper;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.sdk.Sdk;
import com.android.ide.eclipse.ddms.DdmsPlugin;
import com.android.sdklib.IAndroidTarget;
import com.android.sdklib.avd.AvdManager;
import com.android.sdklib.avd.AvdManager.AvdInfo;
import com.android.sdkuilib.AvdSelector;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.viewers.DoubleClickEvent;
import org.eclipse.jface.viewers.IDoubleClickListener;
import org.eclipse.jface.viewers.ILabelProviderListener;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredContentProvider;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.ITableLabelProvider;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.SWTException;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Table;
import java.util.ArrayList;
public class DeviceChooserDialog extends Dialog implements IDeviceChangeListener {
private final static int ICON_WIDTH = 16;
private final static String PREFS_COL_SERIAL = "deviceChooser.serial"; //$NON-NLS-1$
private final static String PREFS_COL_STATE = "deviceChooser.state"; //$NON-NLS-1$
private final static String PREFS_COL_AVD = "deviceChooser.avd"; //$NON-NLS-1$
private final static String PREFS_COL_TARGET = "deviceChooser.target"; //$NON-NLS-1$
private final static String PREFS_COL_DEBUG = "deviceChooser.debug"; //$NON-NLS-1$
private Table mDeviceTable;
private TableViewer mViewer;
private AvdSelector mPreferredAvdSelector;
private Image mDeviceImage;
private Image mEmulatorImage;
private Image mMatchImage;
private Image mNoMatchImage;
private Image mWarningImage;
private final DeviceChooserResponse mResponse;
private final String mPackageName;
private final IAndroidTarget mProjectTarget;
private final Sdk mSdk;
private final AvdInfo[] mFullAvdList;
private Button mDeviceRadioButton;
private boolean mDisableAvdSelectionChange = false;
/**
* Basic Content Provider for a table full of {@link Device} objects. The input is
* a {@link AndroidDebugBridge}.
*/
private class ContentProvider implements IStructuredContentProvider {
public Object[] getElements(Object inputElement) {
if (inputElement instanceof AndroidDebugBridge) {
return ((AndroidDebugBridge)inputElement).getDevices();
}
return new Object[0];
}
public void dispose() {
// pass
}
public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
// pass
}
}
/**
* A Label Provider for the {@link TableViewer} in {@link DeviceChooserDialog}.
* It provides labels and images for {@link Device} objects.
*/
private class LabelProvider implements ITableLabelProvider {
public Image getColumnImage(Object element, int columnIndex) {
if (element instanceof Device) {
Device device = (Device)element;
switch (columnIndex) {
case 0:
return device.isEmulator() ? mEmulatorImage : mDeviceImage;
case 2:
// check for compatibility.
if (device.isEmulator() == false) { // physical device
// get the api level of the device
try {
String apiValue = device.getProperty(
IDevice.PROP_BUILD_VERSION_NUMBER);
if (apiValue != null) {
int api = Integer.parseInt(apiValue);
if (api >= mProjectTarget.getApiVersionNumber()) {
// if the project is compiling against an add-on, the optional
// API may be missing from the device.
return mProjectTarget.isPlatform() ?
mMatchImage : mWarningImage;
} else {
return mNoMatchImage;
}
} else {
return mWarningImage;
}
} catch (NumberFormatException e) {
// lets consider the device non compatible
return mNoMatchImage;
}
} else {
// get the AvdInfo
AvdInfo info = mSdk.getAvdManager().getAvd(device.getAvdName());
if (info == null) {
return mWarningImage;
}
return mProjectTarget.isCompatibleBaseFor(info.getTarget()) ?
mMatchImage : mNoMatchImage;
}
}
}
return null;
}
public String getColumnText(Object element, int columnIndex) {
if (element instanceof Device) {
Device device = (Device)element;
switch (columnIndex) {
case 0:
return device.getSerialNumber();
case 1:
if (device.isEmulator()) {
return device.getAvdName();
} else {
return "N/A"; // devices don't have AVD names.
}
case 2:
if (device.isEmulator()) {
AvdInfo info = mSdk.getAvdManager().getAvd(device.getAvdName());
if (info == null) {
return "?";
}
return info.getTarget().getFullName();
} else {
String deviceBuild = device.getProperty(IDevice.PROP_BUILD_VERSION);
if (deviceBuild == null) {
return "unknown";
}
return deviceBuild;
}
case 3:
String debuggable = device.getProperty(IDevice.PROP_DEBUGGABLE);
if (debuggable != null && debuggable.equals("1")) { //$NON-NLS-1$
return "Yes";
} else {
return "";
}
case 4:
return getStateString(device);
}
}
return null;
}
public void addListener(ILabelProviderListener listener) {
// pass
}
public void dispose() {
// pass
}
public boolean isLabelProperty(Object element, String property) {
// pass
return false;
}
public void removeListener(ILabelProviderListener listener) {
// pass
}
}
public static class DeviceChooserResponse {
private AvdInfo mAvdToLaunch;
private Device mDeviceToUse;
public void setDeviceToUse(Device d) {
mDeviceToUse = d;
mAvdToLaunch = null;
}
public void setAvdToLaunch(AvdInfo avd) {
mAvdToLaunch = avd;
mDeviceToUse = null;
}
public Device getDeviceToUse() {
return mDeviceToUse;
}
public AvdInfo getAvdToLaunch() {
return mAvdToLaunch;
}
}
public DeviceChooserDialog(Shell parent, DeviceChooserResponse response, String packageName,
IAndroidTarget projectTarget) {
super(parent);
mResponse = response;
mPackageName = packageName;
mProjectTarget = projectTarget;
mSdk = Sdk.getCurrent();
// get the full list of Android Virtual Devices
AvdManager avdManager = mSdk.getAvdManager();
if (avdManager != null) {
mFullAvdList = avdManager.getAvds();
} else {
mFullAvdList = null;
}
loadImages();
}
private void cleanup() {
// done listening.
AndroidDebugBridge.removeDeviceChangeListener(this);
mEmulatorImage.dispose();
mDeviceImage.dispose();
mMatchImage.dispose();
mNoMatchImage.dispose();
mWarningImage.dispose();
}
@Override
protected void okPressed() {
cleanup();
super.okPressed();
}
@Override
protected void cancelPressed() {
cleanup();
super.cancelPressed();
}
@Override
protected Control createContents(Composite parent) {
Control content = super.createContents(parent);
// this must be called after createContents() has happened so that the
// ok button has been created (it's created after the call to createDialogArea)
updateDefaultSelection();
return content;
}
@Override
protected Control createDialogArea(Composite parent) {
Composite top = new Composite(parent, SWT.NONE);
top.setLayout(new GridLayout(1, true));
mDeviceRadioButton = new Button(top, SWT.RADIO);
mDeviceRadioButton.setText("Choose a running Android device");
mDeviceRadioButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
boolean deviceMode = mDeviceRadioButton.getSelection();
mDeviceTable.setEnabled(deviceMode);
mPreferredAvdSelector.setEnabled(!deviceMode);
if (deviceMode) {
handleDeviceSelection();
} else {
mResponse.setAvdToLaunch(mPreferredAvdSelector.getFirstSelected());
}
enableOkButton();
}
});
mDeviceRadioButton.setSelection(true);
// offset the selector from the radio button
Composite offsetComp = new Composite(top, SWT.NONE);
offsetComp.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
GridLayout layout = new GridLayout(1, false);
layout.marginRight = layout.marginHeight = 0;
layout.marginLeft = 30;
offsetComp.setLayout(layout);
IPreferenceStore store = AdtPlugin.getDefault().getPreferenceStore();
mDeviceTable = new Table(offsetComp, SWT.SINGLE | SWT.FULL_SELECTION);
GridData gd;
mDeviceTable.setLayoutData(gd = new GridData(GridData.FILL_BOTH));
gd.heightHint = 100;
mDeviceTable.setHeaderVisible(true);
mDeviceTable.setLinesVisible(true);
TableHelper.createTableColumn(mDeviceTable, "Serial Number",
SWT.LEFT, "AAA+AAAAAAAAAAAAAAAAAAA", //$NON-NLS-1$
PREFS_COL_SERIAL, store);
TableHelper.createTableColumn(mDeviceTable, "AVD Name",
SWT.LEFT, "engineering", //$NON-NLS-1$
PREFS_COL_AVD, store);
TableHelper.createTableColumn(mDeviceTable, "Target",
SWT.LEFT, "AAA+Android 9.9.9", //$NON-NLS-1$
PREFS_COL_TARGET, store);
TableHelper.createTableColumn(mDeviceTable, "Debug",
SWT.LEFT, "Debug", //$NON-NLS-1$
PREFS_COL_DEBUG, store);
TableHelper.createTableColumn(mDeviceTable, "State",
SWT.LEFT, "bootloader", //$NON-NLS-1$
PREFS_COL_STATE, store);
// create the viewer for it
mViewer = new TableViewer(mDeviceTable);
mViewer.setContentProvider(new ContentProvider());
mViewer.setLabelProvider(new LabelProvider());
mViewer.setInput(AndroidDebugBridge.getBridge());
mViewer.addDoubleClickListener(new IDoubleClickListener() {
public void doubleClick(DoubleClickEvent event) {
ISelection selection = event.getSelection();
if (selection instanceof IStructuredSelection) {
IStructuredSelection structuredSelection = (IStructuredSelection)selection;
Object object = structuredSelection.getFirstElement();
if (object instanceof Device) {
mResponse.setDeviceToUse((Device)object);
}
}
}
});
Button radio2 = new Button(top, SWT.RADIO);
radio2.setText("Launch a new Android Virtual Device");
// offset the selector from the radio button
offsetComp = new Composite(top, SWT.NONE);
offsetComp.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
layout = new GridLayout(1, false);
layout.marginRight = layout.marginHeight = 0;
layout.marginLeft = 30;
offsetComp.setLayout(layout);
mPreferredAvdSelector = new AvdSelector(offsetComp, getNonRunningAvds(), mProjectTarget,
false /*allowMultipleSelection*/);
mPreferredAvdSelector.setTableHeightHint(100);
mPreferredAvdSelector.setEnabled(false);
mDeviceTable.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
handleDeviceSelection();
}
});
mPreferredAvdSelector.setSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
if (mDisableAvdSelectionChange == false) {
mResponse.setAvdToLaunch(mPreferredAvdSelector.getFirstSelected());
enableOkButton();
}
}
});
AndroidDebugBridge.addDeviceChangeListener(this);
return top;
}
private void loadImages() {
IImageLoader ddmsLoader = DdmsPlugin.getImageLoader();
Display display = DdmsPlugin.getDisplay();
IImageLoader adtLoader = AdtPlugin.getImageLoader();
if (mDeviceImage == null) {
mDeviceImage = ImageHelper.loadImage(ddmsLoader, display,
"device.png", //$NON-NLS-1$
ICON_WIDTH, ICON_WIDTH,
display.getSystemColor(SWT.COLOR_RED));
}
if (mEmulatorImage == null) {
mEmulatorImage = ImageHelper.loadImage(ddmsLoader, display,
"emulator.png", ICON_WIDTH, ICON_WIDTH, //$NON-NLS-1$
display.getSystemColor(SWT.COLOR_BLUE));
}
if (mMatchImage == null) {
mMatchImage = ImageHelper.loadImage(adtLoader, display,
"match.png", //$NON-NLS-1$
ICON_WIDTH, ICON_WIDTH,
display.getSystemColor(SWT.COLOR_GREEN));
}
if (mNoMatchImage == null) {
mNoMatchImage = ImageHelper.loadImage(adtLoader, display,
"error.png", //$NON-NLS-1$
ICON_WIDTH, ICON_WIDTH,
display.getSystemColor(SWT.COLOR_RED));
}
if (mWarningImage == null) {
mWarningImage = ImageHelper.loadImage(adtLoader, display,
"warning.png", //$NON-NLS-1$
ICON_WIDTH, ICON_WIDTH,
display.getSystemColor(SWT.COLOR_YELLOW));
}
}
/**
* Returns a display string representing the state of the device.
* @param d the device
*/
private static String getStateString(Device d) {
DeviceState deviceState = d.getState();
if (deviceState == DeviceState.ONLINE) {
return "Online";
} else if (deviceState == DeviceState.OFFLINE) {
return "Offline";
} else if (deviceState == DeviceState.BOOTLOADER) {
return "Bootloader";
}
return "??";
}
/**
* Sent when the a device is connected to the {@link AndroidDebugBridge}.
* <p/>
* This is sent from a non UI thread.
* @param device the new device.
*
* @see IDeviceChangeListener#deviceConnected(Device)
*/
public void deviceConnected(Device device) {
final DeviceChooserDialog dialog = this;
exec(new Runnable() {
public void run() {
if (mDeviceTable.isDisposed() == false) {
// refresh all
mViewer.refresh();
// update the selection
updateDefaultSelection();
// update the display of AvdInfo (since it's filtered to only display
// non running AVD.)
refillAvdList();
} else {
// table is disposed, we need to do something.
// lets remove ourselves from the listener.
AndroidDebugBridge.removeDeviceChangeListener(dialog);
}
}
});
}
/**
* Sent when the a device is connected to the {@link AndroidDebugBridge}.
* <p/>
* This is sent from a non UI thread.
* @param device the new device.
*
* @see IDeviceChangeListener#deviceDisconnected(Device)
*/
public void deviceDisconnected(Device device) {
deviceConnected(device);
}
/**
* Sent when a device data changed, or when clients are started/terminated on the device.
* <p/>
* This is sent from a non UI thread.
* @param device the device that was updated.
* @param changeMask the mask indicating what changed.
*
* @see IDeviceChangeListener#deviceChanged(Device, int)
*/
public void deviceChanged(final Device device, int changeMask) {
if ((changeMask & (Device.CHANGE_STATE | Device.CHANGE_BUILD_INFO)) != 0) {
final DeviceChooserDialog dialog = this;
exec(new Runnable() {
public void run() {
if (mDeviceTable.isDisposed() == false) {
// refresh the device
mViewer.refresh(device);
// update the defaultSelection.
updateDefaultSelection();
// update the display of AvdInfo (since it's filtered to only display
// non running AVD). This is done on deviceChanged because the avd name
// of a (emulator) device may be updated as the emulator boots.
refillAvdList();
// if the changed device is the current selection,
// we update the OK button based on its state.
if (device == mResponse.getDeviceToUse()) {
enableOkButton();
}
} else {
// table is disposed, we need to do something.
// lets remove ourselves from the listener.
AndroidDebugBridge.removeDeviceChangeListener(dialog);
}
}
});
}
}
/**
* Returns whether the dialog is in "device" mode (true), or in "avd" mode (false).
*/
private boolean isDeviceMode() {
return mDeviceRadioButton.getSelection();
}
/**
* Enables or disables the OK button of the dialog based on various selections in the dialog.
*/
private void enableOkButton() {
Button okButton = getButton(IDialogConstants.OK_ID);
if (isDeviceMode()) {
okButton.setEnabled(mResponse.getDeviceToUse() != null &&
mResponse.getDeviceToUse().isOnline());
} else {
okButton.setEnabled(mResponse.getAvdToLaunch() != null);
}
}
/**
* Executes the {@link Runnable} in the UI thread.
* @param runnable the runnable to execute.
*/
private void exec(Runnable runnable) {
try {
Display display = mDeviceTable.getDisplay();
display.asyncExec(runnable);
} catch (SWTException e) {
// tree is disposed, we need to do something. lets remove ourselves from the listener.
AndroidDebugBridge.removeDeviceChangeListener(this);
}
}
private void handleDeviceSelection() {
int count = mDeviceTable.getSelectionCount();
if (count != 1) {
handleSelection(null);
} else {
int index = mDeviceTable.getSelectionIndex();
Object data = mViewer.getElementAt(index);
if (data instanceof Device) {
handleSelection((Device)data);
} else {
handleSelection(null);
}
}
}
private void handleSelection(Device device) {
mResponse.setDeviceToUse(device);
enableOkButton();
}
/**
* Look for a default device to select. This is done by looking for the running
* clients on each device and finding one similar to the one being launched.
* <p/>
* This is done every time the device list changed unless there is a already selection.
*/
private void updateDefaultSelection() {
if (mDeviceTable.getSelectionCount() == 0) {
AndroidDebugBridge bridge = AndroidDebugBridge.getBridge();
Device[] devices = bridge.getDevices();
for (Device device : devices) {
Client[] clients = device.getClients();
for (Client client : clients) {
if (mPackageName.equals(client.getClientData().getClientDescription())) {
// found a match! Select it.
mViewer.setSelection(new StructuredSelection(device));
handleSelection(device);
// and we're done.
return;
}
}
}
}
handleDeviceSelection();
}
/**
* Returns the list of {@link AvdInfo} that are not already running in an emulator.
*/
private AvdInfo[] getNonRunningAvds() {
ArrayList<AvdInfo> list = new ArrayList<AvdInfo>();
Device[] devices = AndroidDebugBridge.getBridge().getDevices();
// loop through all the Avd and put the one that are not running in the list.
avdLoop: for (AvdInfo info : mFullAvdList) {
for (Device d : devices) {
if (info.getName().equals(d.getAvdName())) {
continue avdLoop;
}
}
list.add(info);
}
return list.toArray(new AvdInfo[list.size()]);
}
/**
* Refills the AVD list keeping the current selection.
*/
private void refillAvdList() {
AvdInfo[] array = getNonRunningAvds();
// save the current selection
AvdInfo selected = mPreferredAvdSelector.getFirstSelected();
// disable selection change.
mDisableAvdSelectionChange = true;
// set the new list in the selector
mPreferredAvdSelector.setAvds(array, mProjectTarget);
// attempt to reselect the proper avd if needed
if (selected != null) {
if (mPreferredAvdSelector.setSelection(selected) == false) {
// looks like the selection is lost. this can happen if an emulator
// running the AVD that was selected was launched from outside of Eclipse).
mResponse.setAvdToLaunch(null);
enableOkButton();
}
}
// enable the selection change
mDisableAvdSelectionChange = false;
}
}

View File

@@ -0,0 +1,431 @@
/*
* Copyright (C) 2007 The Android Open Source Project
*
* Licensed under the Eclipse Public License, Version 1.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.eclipse.org/org/documents/epl-v10.php
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.ide.eclipse.adt.debug.launching;
import com.android.ddmlib.AndroidDebugBridge;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.debug.launching.AndroidLaunchController.AndroidLaunchConfiguration;
import com.android.ide.eclipse.adt.project.ProjectHelper;
import com.android.ide.eclipse.common.AndroidConstants;
import com.android.ide.eclipse.common.project.AndroidManifestParser;
import com.android.ide.eclipse.common.project.BaseProjectHelper;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.debug.core.ILaunch;
import org.eclipse.debug.core.ILaunchConfiguration;
import org.eclipse.debug.core.model.LaunchConfigurationDelegate;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.launching.IJavaLaunchConfigurationConstants;
/**
* Implementation of an eclipse LauncConfigurationDelegate to launch android
* application in debug.
*/
public class LaunchConfigDelegate extends LaunchConfigurationDelegate {
final static int INVALID_DEBUG_PORT = -1;
public final static String ANDROID_LAUNCH_TYPE_ID =
"com.android.ide.eclipse.adt.debug.LaunchConfigType"; //$NON-NLS-1$
/** Target mode parameters: true is automatic, false is manual */
public static final String ATTR_TARGET_MODE = AdtPlugin.PLUGIN_ID + ".target"; //$NON-NLS-1$
public static final boolean DEFAULT_TARGET_MODE = true; //automatic mode
/**
* Launch action:
* <ul>
* <li>0: launch default activity</li>
* <li>1: launch specified activity. See {@link #ATTR_ACTIVITY}</li>
* <li>2: Do Nothing</li>
* </ul>
*/
public final static String ATTR_LAUNCH_ACTION = AdtPlugin.PLUGIN_ID + ".action"; //$NON-NLS-1$
/** Default launch action. This launches the activity that is setup to be found in the HOME
* screen.
*/
public final static int ACTION_DEFAULT = 0;
/** Launch action starting a specific activity. */
public final static int ACTION_ACTIVITY = 1;
/** Launch action that does nothing. */
public final static int ACTION_DO_NOTHING = 2;
/** Default launch action value. */
public final static int DEFAULT_LAUNCH_ACTION = ACTION_DEFAULT;
/**
* Activity to be launched if {@link #ATTR_LAUNCH_ACTION} is 1
*/
public static final String ATTR_ACTIVITY = AdtPlugin.PLUGIN_ID + ".activity"; //$NON-NLS-1$
public static final String ATTR_AVD_NAME = AdtPlugin.PLUGIN_ID + ".avd"; //$NON-NLS-1$
public static final String ATTR_SPEED = AdtPlugin.PLUGIN_ID + ".speed"; //$NON-NLS-1$
/**
* Index of the default network speed setting for the emulator.<br>
* Get the emulator option with <code>EmulatorConfigTab.getSpeed(index)</code>
*/
public static final int DEFAULT_SPEED = 0;
public static final String ATTR_DELAY = AdtPlugin.PLUGIN_ID + ".delay"; //$NON-NLS-1$
/**
* Index of the default network latency setting for the emulator.<br>
* Get the emulator option with <code>EmulatorConfigTab.getDelay(index)</code>
*/
public static final int DEFAULT_DELAY = 0;
public static final String ATTR_COMMANDLINE = AdtPlugin.PLUGIN_ID + ".commandline"; //$NON-NLS-1$
public static final String ATTR_WIPE_DATA = AdtPlugin.PLUGIN_ID + ".wipedata"; //$NON-NLS-1$
public static final boolean DEFAULT_WIPE_DATA = false;
public static final String ATTR_NO_BOOT_ANIM = AdtPlugin.PLUGIN_ID + ".nobootanim"; //$NON-NLS-1$
public static final boolean DEFAULT_NO_BOOT_ANIM = false;
public static final String ATTR_DEBUG_PORT =
AdtPlugin.PLUGIN_ID + ".debugPort"; //$NON-NLS-1$
public void launch(ILaunchConfiguration configuration, String mode,
ILaunch launch, IProgressMonitor monitor) throws CoreException {
// We need to check if it's a standard launch or if it's a launch
// to debug an application already running.
int debugPort = AndroidLaunchController.getPortForConfig(configuration);
// get the project
IProject project = getProject(configuration);
// first we make sure the launch is of the proper type
AndroidLaunch androidLaunch = null;
if (launch instanceof AndroidLaunch) {
androidLaunch = (AndroidLaunch)launch;
} else {
// wrong type, not sure how we got there, but we don't do
// anything else
AdtPlugin.printErrorToConsole(project, "Wrong Launch Type!");
return;
}
// if we have a valid debug port, this means we're debugging an app
// that's already launched.
if (debugPort != INVALID_DEBUG_PORT) {
AndroidLaunchController.launchRemoteDebugger(debugPort, androidLaunch, monitor);
return;
}
if (project == null) {
AdtPlugin.printErrorToConsole("Couldn't get project object!");
androidLaunch.stopLaunch();
return;
}
// check if the project has errors, and abort in this case.
if (ProjectHelper.hasError(project, true)) {
AdtPlugin.displayError("Android Launch",
"Your project contains error(s), please fix them before running your application.");
return;
}
AdtPlugin.printToConsole(project, "------------------------------"); //$NON-NLS-1$
AdtPlugin.printToConsole(project, "Android Launch!");
// check if the project is using the proper sdk.
// if that throws an exception, we simply let it propage to the caller.
if (checkAndroidProject(project) == false) {
AdtPlugin.printErrorToConsole(project, "Project is not an Android Project. Aborting!");
androidLaunch.stopLaunch();
return;
}
// Check adb status and abort if needed.
AndroidDebugBridge bridge = AndroidDebugBridge.getBridge();
if (bridge == null || bridge.isConnected() == false) {
try {
int connections = -1;
int restarts = -1;
if (bridge != null) {
connections = bridge.getConnectionAttemptCount();
restarts = bridge.getRestartAttemptCount();
}
// if we get -1, the device monitor is not even setup (anymore?).
// We need to ask the user to restart eclipse.
// This shouldn't happen, but it's better to let the user know in case it does.
if (connections == -1 || restarts == -1) {
AdtPlugin.printErrorToConsole(project,
"The connection to adb is down, and a severe error has occured.",
"You must restart adb and Eclipse.",
String.format(
"Please ensure that adb is correctly located at '%1$s' and can be executed.",
AdtPlugin.getOsAbsoluteAdb()));
return;
}
if (restarts == 0) {
AdtPlugin.printErrorToConsole(project,
"Connection with adb was interrupted.",
String.format("%1$s attempts have been made to reconnect.", connections),
"You may want to manually restart adb from the Devices view.");
} else {
AdtPlugin.printErrorToConsole(project,
"Connection with adb was interrupted, and attempts to reconnect have failed.",
String.format("%1$s attempts have been made to restart adb.", restarts),
"You may want to manually restart adb from the Devices view.");
}
return;
} finally {
androidLaunch.stopLaunch();
}
}
// since adb is working, we let the user know
// TODO have a verbose mode for launch with more info (or some of the less useful info we now have).
AdtPlugin.printToConsole(project, "adb is running normally.");
// make a config class
AndroidLaunchConfiguration config = new AndroidLaunchConfiguration();
// fill it with the config coming from the ILaunchConfiguration object
config.set(configuration);
// get the launch controller singleton
AndroidLaunchController controller = AndroidLaunchController.getInstance();
// get the application package
IFile applicationPackage = getApplicationPackage(project);
if (applicationPackage == null) {
androidLaunch.stopLaunch();
return;
}
// we need some information from the manifest
AndroidManifestParser manifestParser = AndroidManifestParser.parse(
BaseProjectHelper.getJavaProject(project), null /* errorListener */,
true /* gatherData */, false /* markErrors */);
if (manifestParser == null) {
AdtPlugin.printErrorToConsole(project, "Failed to parse AndroidManifest: aborting!");
androidLaunch.stopLaunch();
return;
}
String activityName = null;
if (config.mLaunchAction == ACTION_ACTIVITY) {
// Get the activity name defined in the config
activityName = getActivityName(configuration);
// Get the full activity list and make sure the one we got matches.
String[] activities = manifestParser.getActivities();
// first we check that there are, in fact, activities.
if (activities.length == 0) {
// if the activities list is null, then the manifest is empty
// and we can't launch the app. We'll revert to a sync-only launch
AdtPlugin.printErrorToConsole(project,
"The Manifest defines no activity!",
"The launch will only sync the application package on the device!");
config.mLaunchAction = ACTION_DO_NOTHING;
} else if (activityName == null) {
// if the activity we got is null, we look for the default one.
AdtPlugin.printErrorToConsole(project,
"No activity specified! Getting the launcher activity.");
activityName = manifestParser.getLauncherActivity();
// if there's no default activity. We revert to a sync-only launch.
if (activityName == null) {
revertToNoActionLaunch(project, config);
}
} else {
// check the one we got from the config matches any from the list
boolean match = false;
for (String a : activities) {
if (a != null && a.equals(activityName)) {
match = true;
break;
}
}
// if we didn't find a match, we revert to the default activity if any.
if (match == false) {
AdtPlugin.printErrorToConsole(project,
"The specified activity does not exist! Getting the launcher activity.");
activityName = manifestParser.getLauncherActivity();
// if there's no default activity. We revert to a sync-only launch.
if (activityName == null) {
revertToNoActionLaunch(project, config);
}
}
}
} else if (config.mLaunchAction == ACTION_DEFAULT) {
activityName = manifestParser.getLauncherActivity();
// if there's no default activity. We revert to a sync-only launch.
if (activityName == null) {
revertToNoActionLaunch(project, config);
}
}
// everything seems fine, we ask the launch controller to handle
// the rest
controller.launch(project, mode, applicationPackage, manifestParser.getPackage(),
manifestParser.getDebuggable(), manifestParser.getApiLevelRequirement(),
activityName, config, androidLaunch, monitor);
}
@Override
public boolean buildForLaunch(ILaunchConfiguration configuration,
String mode, IProgressMonitor monitor) throws CoreException {
// need to check we have everything
IProject project = getProject(configuration);
if (project != null) {
// force an incremental build to be sure the resources will
// be updated if they were not saved before the launch was launched.
return true;
}
throw new CoreException(new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
1 /* code, unused */, "Can't find the project!", null /* exception */));
}
/**
* {@inheritDoc}
* @throws CoreException
*/
@Override
public ILaunch getLaunch(ILaunchConfiguration configuration, String mode)
throws CoreException {
return new AndroidLaunch(configuration, mode, null);
}
/**
* Returns the IProject object matching the name found in the configuration
* object under the name
* <code>IJavaLaunchConfigurationConstants.ATTR_PROJECT_NAME</code>
* @param configuration
* @return The IProject object or null
*/
private IProject getProject(ILaunchConfiguration configuration){
// get the project name from the config
String projectName;
try {
projectName = configuration.getAttribute(
IJavaLaunchConfigurationConstants.ATTR_PROJECT_NAME, "");
} catch (CoreException e) {
return null;
}
// get the current workspace
IWorkspace workspace = ResourcesPlugin.getWorkspace();
// and return the project with the name from the config
return workspace.getRoot().getProject(projectName);
}
/**
* Checks the project is an android project.
* @param project The project to check
* @return true if the project is an android SDK.
* @throws CoreException
*/
private boolean checkAndroidProject(IProject project) throws CoreException {
// check if the project is a java and an android project.
if (project.hasNature(JavaCore.NATURE_ID) == false) {
String msg = String.format("%1$s is not a Java project!", project.getName());
AdtPlugin.displayError("Android Launch", msg);
return false;
}
if (project.hasNature(AndroidConstants.NATURE) == false) {
String msg = String.format("%1$s is not an Android project!", project.getName());
AdtPlugin.displayError("Android Launch", msg);
return false;
}
return true;
}
/**
* Returns the android package file as an IFile object for the specified
* project.
* @param project The project
* @return The android package as an IFile object or null if not found.
*/
private IFile getApplicationPackage(IProject project) {
// get the output folder
IFolder outputLocation = BaseProjectHelper.getOutputFolder(project);
if (outputLocation == null) {
AdtPlugin.printErrorToConsole(project,
"Failed to get the output location of the project. Check build path properties"
);
return null;
}
// get the package path
String packageName = project.getName() + AndroidConstants.DOT_ANDROID_PACKAGE;
IResource r = outputLocation.findMember(packageName);
// check the package is present
if (r instanceof IFile && r.exists()) {
return (IFile)r;
}
String msg = String.format("Could not find %1$s!", packageName);
AdtPlugin.printErrorToConsole(project, msg);
return null;
}
/**
* Returns the name of the activity.
*/
private String getActivityName(ILaunchConfiguration configuration) {
String empty = "";
String activityName;
try {
activityName = configuration.getAttribute(ATTR_ACTIVITY, empty);
} catch (CoreException e) {
return null;
}
return (activityName != empty) ? activityName : null;
}
private final void revertToNoActionLaunch(IProject project, AndroidLaunchConfiguration config) {
AdtPlugin.printErrorToConsole(project,
"No Launcher activity found!",
"The launch will only sync the application package on the device!");
config.mLaunchAction = ACTION_DO_NOTHING;
}
}

View File

@@ -0,0 +1,87 @@
/*
* Copyright (C) 2007 The Android Open Source Project
*
* Licensed under the Eclipse Public License, Version 1.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.eclipse.org/org/documents/epl-v10.php
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.ide.eclipse.adt.debug.launching;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.debug.core.ILaunchConfiguration;
import org.eclipse.debug.ui.DebugUITools;
import org.eclipse.debug.ui.ILaunchShortcut;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.ui.IEditorPart;
/**
* Launch shortcut to launch debug/run configuration directly.
*/
public class LaunchShortcut implements ILaunchShortcut {
/* (non-Javadoc)
* @see org.eclipse.debug.ui.ILaunchShortcut#launch(
* org.eclipse.jface.viewers.ISelection, java.lang.String)
*/
public void launch(ISelection selection, String mode) {
if (selection instanceof IStructuredSelection) {
// get the object and the project from it
IStructuredSelection structSelect = (IStructuredSelection)selection;
Object o = structSelect.getFirstElement();
// get the first (and normally only) element
if (o instanceof IAdaptable) {
IResource r = (IResource)((IAdaptable)o).getAdapter(IResource.class);
// get the project from the resource
if (r != null) {
IProject project = r.getProject();
if (project != null) {
// and launch
launch(project, mode);
}
}
}
}
}
/* (non-Javadoc)
* @see org.eclipse.debug.ui.ILaunchShortcut#launch(
* org.eclipse.ui.IEditorPart, java.lang.String)
*/
public void launch(IEditorPart editor, String mode) {
// since we force the shortcut to only work on selection in the
// package explorer, this will never be called.
}
/**
* Launch a config for the specified project.
* @param project The project to launch
* @param mode The launch mode ("debug", "run" or "profile")
*/
private void launch(IProject project, String mode) {
// get an existing or new launch configuration
ILaunchConfiguration config = AndroidLaunchController.getLaunchConfig(project);
if (config != null) {
// and launch!
DebugUITools.launch(config, mode);
}
}
}

View File

@@ -0,0 +1,459 @@
/*
* Copyright (C) 2007 The Android Open Source Project
*
* Licensed under the Eclipse Public License, Version 1.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.eclipse.org/org/documents/epl-v10.php
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.ide.eclipse.adt.debug.ui;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.debug.launching.LaunchConfigDelegate;
import com.android.ide.eclipse.adt.sdk.Sdk;
import com.android.ide.eclipse.common.project.BaseProjectHelper;
import com.android.ide.eclipse.ddms.DdmsPlugin;
import com.android.sdklib.IAndroidTarget;
import com.android.sdklib.avd.AvdManager;
import com.android.sdklib.avd.AvdManager.AvdInfo;
import com.android.sdkuilib.AvdSelector;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.debug.core.ILaunchConfiguration;
import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy;
import org.eclipse.debug.ui.AbstractLaunchConfigurationTab;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.launching.IJavaLaunchConfigurationConstants;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Group;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Text;
/**
* Launch configuration tab to control the parameters of the Emulator
*/
public class EmulatorConfigTab extends AbstractLaunchConfigurationTab {
private final static String[][] NETWORK_SPEEDS = new String[][] {
{ "Full", "full" }, //$NON-NLS-2$
{ "GSM", "gsm" }, //$NON-NLS-2$
{ "HSCSD", "hscsd" }, //$NON-NLS-2$
{ "GPRS", "gprs" }, //$NON-NLS-2$
{ "EDGE", "edge" }, //$NON-NLS-2$
{ "UMTS", "umts" }, //$NON-NLS-2$
{ "HSPDA", "hsdpa" }, //$NON-NLS-2$
};
private final static String[][] NETWORK_LATENCIES = new String[][] {
{ "None", "none" }, //$NON-NLS-2$
{ "GPRS", "gprs" }, //$NON-NLS-2$
{ "EDGE", "edge" }, //$NON-NLS-2$
{ "UMTS", "umts" }, //$NON-NLS-2$
};
private Button mAutoTargetButton;
private Button mManualTargetButton;
private AvdSelector mPreferredAvdSelector;
private Combo mSpeedCombo;
private Combo mDelayCombo;
private Group mEmulatorOptionsGroup;
private Text mEmulatorCLOptions;
private Button mWipeDataButton;
private Button mNoBootAnimButton;
private Label mPreferredAvdLabel;
/**
* Returns the emulator ready speed option value.
* @param value The index of the combo selection.
*/
public static String getSpeed(int value) {
try {
return NETWORK_SPEEDS[value][1];
} catch (ArrayIndexOutOfBoundsException e) {
return NETWORK_SPEEDS[LaunchConfigDelegate.DEFAULT_SPEED][1];
}
}
/**
* Returns the emulator ready network latency value.
* @param value The index of the combo selection.
*/
public static String getDelay(int value) {
try {
return NETWORK_LATENCIES[value][1];
} catch (ArrayIndexOutOfBoundsException e) {
return NETWORK_LATENCIES[LaunchConfigDelegate.DEFAULT_DELAY][1];
}
}
/**
*
*/
public EmulatorConfigTab() {
}
/* (non-Javadoc)
* @see org.eclipse.debug.ui.ILaunchConfigurationTab#createControl(org.eclipse.swt.widgets.Composite)
*/
public void createControl(Composite parent) {
Font font = parent.getFont();
Composite topComp = new Composite(parent, SWT.NONE);
setControl(topComp);
GridLayout topLayout = new GridLayout();
topLayout.numColumns = 1;
topLayout.verticalSpacing = 0;
topComp.setLayout(topLayout);
topComp.setFont(font);
GridData gd;
GridLayout layout;
// radio button for the target mode
Group targetModeGroup = new Group(topComp, SWT.NONE);
targetModeGroup.setText("Device Target Selection Mode");
gd = new GridData(GridData.FILL_HORIZONTAL);
targetModeGroup.setLayoutData(gd);
layout = new GridLayout();
layout.numColumns = 1;
targetModeGroup.setLayout(layout);
targetModeGroup.setFont(font);
mManualTargetButton = new Button(targetModeGroup, SWT.RADIO);
mManualTargetButton.setText("Manual");
// Since there are only 2 radio buttons, we can put a listener on only one (they
// are both called on select and unselect event.
// add the radio button
mAutoTargetButton = new Button(targetModeGroup, SWT.RADIO);
mAutoTargetButton.setText("Automatic");
mAutoTargetButton.setSelection(true);
mAutoTargetButton.addSelectionListener(new SelectionAdapter() {
// called when selection changes
@Override
public void widgetSelected(SelectionEvent e) {
updateLaunchConfigurationDialog();
boolean auto = mAutoTargetButton.getSelection();
mPreferredAvdSelector.setEnabled(auto);
mPreferredAvdLabel.setEnabled(auto);
}
});
Composite offsetComp = new Composite(targetModeGroup, SWT.NONE);
offsetComp.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
layout = new GridLayout(1, false);
layout.marginRight = layout.marginHeight = 0;
layout.marginLeft = 30;
offsetComp.setLayout(layout);
mPreferredAvdLabel = new Label(offsetComp, SWT.NONE);
mPreferredAvdLabel.setText("Select a preferred Android Virtual Device:");
AvdInfo[] avds = new AvdInfo[0];
mPreferredAvdSelector = new AvdSelector(offsetComp, avds,
false /*allowMultipleSelection*/);
mPreferredAvdSelector.setTableHeightHint(100);
mPreferredAvdSelector.setSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
updateLaunchConfigurationDialog();
}
});
// emulator size
mEmulatorOptionsGroup = new Group(topComp, SWT.NONE);
mEmulatorOptionsGroup.setText("Emulator launch parameters:");
mEmulatorOptionsGroup.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
layout = new GridLayout();
layout.numColumns = 2;
mEmulatorOptionsGroup.setLayout(layout);
mEmulatorOptionsGroup.setFont(font);
// network options
new Label(mEmulatorOptionsGroup, SWT.NONE).setText("Network Speed:");
mSpeedCombo = new Combo(mEmulatorOptionsGroup, SWT.READ_ONLY);
for (String[] speed : NETWORK_SPEEDS) {
mSpeedCombo.add(speed[0]);
}
mSpeedCombo.addSelectionListener(new SelectionAdapter() {
// called when selection changes
@Override
public void widgetSelected(SelectionEvent e) {
updateLaunchConfigurationDialog();
}
});
mSpeedCombo.pack();
new Label(mEmulatorOptionsGroup, SWT.NONE).setText("Network Latency:");
mDelayCombo = new Combo(mEmulatorOptionsGroup, SWT.READ_ONLY);
for (String[] delay : NETWORK_LATENCIES) {
mDelayCombo.add(delay[0]);
}
mDelayCombo.addSelectionListener(new SelectionAdapter() {
// called when selection changes
@Override
public void widgetSelected(SelectionEvent e) {
updateLaunchConfigurationDialog();
}
});
mDelayCombo.pack();
// wipe data option
mWipeDataButton = new Button(mEmulatorOptionsGroup, SWT.CHECK);
mWipeDataButton.setText("Wipe User Data");
mWipeDataButton.setToolTipText("Check this if you want to wipe your user data each time you start the emulator. You will be prompted for confirmation when the emulator starts.");
gd = new GridData(GridData.FILL_HORIZONTAL);
gd.horizontalSpan = 2;
mWipeDataButton.setLayoutData(gd);
mWipeDataButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
updateLaunchConfigurationDialog();
}
});
// no boot anim option
mNoBootAnimButton = new Button(mEmulatorOptionsGroup, SWT.CHECK);
mNoBootAnimButton.setText("Disable Boot Animation");
mNoBootAnimButton.setToolTipText("Check this if you want to disable the boot animation. This can help the emulator start faster on slow machines.");
gd = new GridData(GridData.FILL_HORIZONTAL);
gd.horizontalSpan = 2;
mNoBootAnimButton.setLayoutData(gd);
mNoBootAnimButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
updateLaunchConfigurationDialog();
}
});
// custom command line option for emulator
Label l = new Label(mEmulatorOptionsGroup, SWT.NONE);
l.setText("Additional Emulator Command Line Options");
gd = new GridData(GridData.FILL_HORIZONTAL);
gd.horizontalSpan = 2;
l.setLayoutData(gd);
mEmulatorCLOptions = new Text(mEmulatorOptionsGroup, SWT.BORDER);
gd = new GridData(GridData.FILL_HORIZONTAL);
gd.horizontalSpan = 2;
mEmulatorCLOptions.setLayoutData(gd);
mEmulatorCLOptions.addModifyListener(new ModifyListener() {
public void modifyText(ModifyEvent e) {
updateLaunchConfigurationDialog();
}
});
}
/* (non-Javadoc)
* @see org.eclipse.debug.ui.ILaunchConfigurationTab#getName()
*/
public String getName() {
return "Target";
}
@Override
public Image getImage() {
return DdmsPlugin.getImageLoader().loadImage("emulator.png", null); //$NON-NLS-1$
}
/* (non-Javadoc)
* @see org.eclipse.debug.ui.ILaunchConfigurationTab#initializeFrom(org.eclipse.debug.core.ILaunchConfiguration)
*/
public void initializeFrom(ILaunchConfiguration configuration) {
AvdManager avdManager = Sdk.getCurrent().getAvdManager();
boolean value = LaunchConfigDelegate.DEFAULT_TARGET_MODE; // true == automatic
try {
value = configuration.getAttribute(LaunchConfigDelegate.ATTR_TARGET_MODE, value);
} catch (CoreException e) {
// let's not do anything here, we'll use the default value
}
mAutoTargetButton.setSelection(value);
mManualTargetButton.setSelection(!value);
// look for the project name to get its target.
String stringValue = "";
try {
stringValue = configuration.getAttribute(
IJavaLaunchConfigurationConstants.ATTR_PROJECT_NAME, stringValue);
} catch (CoreException ce) {
// let's not do anything here, we'll use the default value
}
IProject project = null;
// get the list of existing Android projects from the workspace.
IJavaProject[] projects = BaseProjectHelper.getAndroidProjects();
if (projects != null) {
// look for the project whose name we read from the configuration.
for (IJavaProject p : projects) {
if (p.getElementName().equals(stringValue)) {
project = p.getProject();
break;
}
}
}
// update the AVD list
AvdInfo[] avds = null;
if (avdManager != null) {
avds = avdManager.getAvds();
}
IAndroidTarget projectTarget = null;
if (project != null) {
projectTarget = Sdk.getCurrent().getTarget(project);
} else {
avds = null; // no project? we don't want to display any "compatible" AVDs.
}
mPreferredAvdSelector.setAvds(avds, projectTarget);
stringValue = "";
try {
stringValue = configuration.getAttribute(LaunchConfigDelegate.ATTR_AVD_NAME,
stringValue);
} catch (CoreException e) {
// let's not do anything here, we'll use the default value
}
if (stringValue != null && stringValue.length() > 0 && avdManager != null) {
AvdInfo targetAvd = avdManager.getAvd(stringValue);
mPreferredAvdSelector.setSelection(targetAvd);
} else {
mPreferredAvdSelector.setSelection(null);
}
value = LaunchConfigDelegate.DEFAULT_WIPE_DATA;
try {
value = configuration.getAttribute(LaunchConfigDelegate.ATTR_WIPE_DATA, value);
} catch (CoreException e) {
// let's not do anything here, we'll use the default value
}
mWipeDataButton.setSelection(value);
value = LaunchConfigDelegate.DEFAULT_NO_BOOT_ANIM;
try {
value = configuration.getAttribute(LaunchConfigDelegate.ATTR_NO_BOOT_ANIM, value);
} catch (CoreException e) {
// let's not do anything here, we'll use the default value
}
mNoBootAnimButton.setSelection(value);
int index = -1;
index = LaunchConfigDelegate.DEFAULT_SPEED;
try {
index = configuration.getAttribute(LaunchConfigDelegate.ATTR_SPEED,
index);
} catch (CoreException e) {
// let's not do anything here, we'll use the default value
}
if (index == -1) {
mSpeedCombo.clearSelection();
} else {
mSpeedCombo.select(index);
}
index = LaunchConfigDelegate.DEFAULT_DELAY;
try {
index = configuration.getAttribute(LaunchConfigDelegate.ATTR_DELAY,
index);
} catch (CoreException e) {
// let's not do anything here, we'll put a proper value in
// performApply anyway
}
if (index == -1) {
mDelayCombo.clearSelection();
} else {
mDelayCombo.select(index);
}
String commandLine = null;
try {
commandLine = configuration.getAttribute(
LaunchConfigDelegate.ATTR_COMMANDLINE, ""); //$NON-NLS-1$
} catch (CoreException e) {
// let's not do anything here, we'll use the default value
}
if (commandLine != null) {
mEmulatorCLOptions.setText(commandLine);
}
}
/* (non-Javadoc)
* @see org.eclipse.debug.ui.ILaunchConfigurationTab#performApply(org.eclipse.debug.core.ILaunchConfigurationWorkingCopy)
*/
public void performApply(ILaunchConfigurationWorkingCopy configuration) {
configuration.setAttribute(LaunchConfigDelegate.ATTR_TARGET_MODE,
mAutoTargetButton.getSelection());
AvdInfo avd = mPreferredAvdSelector.getFirstSelected();
if (avd != null) {
configuration.setAttribute(LaunchConfigDelegate.ATTR_AVD_NAME, avd.getName());
} else {
configuration.setAttribute(LaunchConfigDelegate.ATTR_AVD_NAME, (String)null);
}
configuration.setAttribute(LaunchConfigDelegate.ATTR_SPEED,
mSpeedCombo.getSelectionIndex());
configuration.setAttribute(LaunchConfigDelegate.ATTR_DELAY,
mDelayCombo.getSelectionIndex());
configuration.setAttribute(LaunchConfigDelegate.ATTR_COMMANDLINE,
mEmulatorCLOptions.getText());
configuration.setAttribute(LaunchConfigDelegate.ATTR_WIPE_DATA,
mWipeDataButton.getSelection());
configuration.setAttribute(LaunchConfigDelegate.ATTR_NO_BOOT_ANIM,
mNoBootAnimButton.getSelection());
}
/* (non-Javadoc)
* @see org.eclipse.debug.ui.ILaunchConfigurationTab#setDefaults(org.eclipse.debug.core.ILaunchConfigurationWorkingCopy)
*/
public void setDefaults(ILaunchConfigurationWorkingCopy configuration) {
configuration.setAttribute(LaunchConfigDelegate.ATTR_TARGET_MODE,
LaunchConfigDelegate.DEFAULT_TARGET_MODE);
configuration.setAttribute(LaunchConfigDelegate.ATTR_SPEED,
LaunchConfigDelegate.DEFAULT_SPEED);
configuration.setAttribute(LaunchConfigDelegate.ATTR_DELAY,
LaunchConfigDelegate.DEFAULT_DELAY);
configuration.setAttribute(LaunchConfigDelegate.ATTR_WIPE_DATA,
LaunchConfigDelegate.DEFAULT_WIPE_DATA);
configuration.setAttribute(LaunchConfigDelegate.ATTR_NO_BOOT_ANIM,
LaunchConfigDelegate.DEFAULT_NO_BOOT_ANIM);
IPreferenceStore store = AdtPlugin.getDefault().getPreferenceStore();
String emuOptions = store.getString(AdtPlugin.PREFS_EMU_OPTIONS);
configuration.setAttribute(LaunchConfigDelegate.ATTR_COMMANDLINE, emuOptions);
}
}

View File

@@ -0,0 +1,40 @@
/*
* Copyright (C) 2007 The Android Open Source Project
*
* Licensed under the Eclipse Public License, Version 1.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.eclipse.org/org/documents/epl-v10.php
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.ide.eclipse.adt.debug.ui;
import org.eclipse.debug.ui.AbstractLaunchConfigurationTabGroup;
import org.eclipse.debug.ui.CommonTab;
import org.eclipse.debug.ui.ILaunchConfigurationDialog;
import org.eclipse.debug.ui.ILaunchConfigurationTab;
/**
* Tab group object for Android Launch Config type.
*/
public class LaunchConfigTabGroup extends AbstractLaunchConfigurationTabGroup {
public LaunchConfigTabGroup() {
}
public void createTabs(ILaunchConfigurationDialog dialog, String mode) {
ILaunchConfigurationTab[] tabs = new ILaunchConfigurationTab[] {
new MainLaunchConfigTab(),
new EmulatorConfigTab(),
new CommonTab()
};
setTabs(tabs);
}
}

View File

@@ -0,0 +1,489 @@
/*
* Copyright (C) 2007 The Android Open Source Project
*
* Licensed under the Eclipse Public License, Version 1.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.eclipse.org/org/documents/epl-v10.php
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.ide.eclipse.adt.debug.ui;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.debug.launching.AndroidLaunchController;
import com.android.ide.eclipse.adt.debug.launching.LaunchConfigDelegate;
import com.android.ide.eclipse.common.project.AndroidManifestParser;
import com.android.ide.eclipse.common.project.BaseProjectHelper;
import com.android.ide.eclipse.common.project.ProjectChooserHelper;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.debug.core.ILaunchConfiguration;
import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy;
import org.eclipse.debug.ui.AbstractLaunchConfigurationTab;
import org.eclipse.debug.ui.ILaunchConfigurationTab;
import org.eclipse.jdt.core.IJavaModel;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.launching.IJavaLaunchConfigurationConstants;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Group;
import org.eclipse.swt.widgets.Text;
/**
* Class for the main launch configuration tab.
*/
public class MainLaunchConfigTab extends AbstractLaunchConfigurationTab {
protected static final String EMPTY_STRING = ""; //$NON-NLS-1$
protected Text mProjText;
private Button mProjButton;
private Combo mActivityCombo;
private String[] mActivities;
private WidgetListener mListener = new WidgetListener();
private Button mDefaultActionButton;
private Button mActivityActionButton;
private Button mDoNothingActionButton;
private int mLaunchAction = LaunchConfigDelegate.DEFAULT_LAUNCH_ACTION;
private ProjectChooserHelper mProjectChooserHelper;
/**
* A listener which handles widget change events for the controls in this
* tab.
*/
private class WidgetListener implements ModifyListener, SelectionListener {
public void modifyText(ModifyEvent e) {
IProject project = checkParameters();
loadActivities(project);
setDirty(true);
}
public void widgetDefaultSelected(SelectionEvent e) {/* do nothing */
}
public void widgetSelected(SelectionEvent e) {
Object source = e.getSource();
if (source == mProjButton) {
handleProjectButtonSelected();
} else {
checkParameters();
}
}
}
public MainLaunchConfigTab() {
}
public void createControl(Composite parent) {
mProjectChooserHelper = new ProjectChooserHelper(parent.getShell());
Font font = parent.getFont();
Composite comp = new Composite(parent, SWT.NONE);
setControl(comp);
GridLayout topLayout = new GridLayout();
topLayout.verticalSpacing = 0;
comp.setLayout(topLayout);
comp.setFont(font);
createProjectEditor(comp);
createVerticalSpacer(comp, 1);
// create the combo for the activity chooser
Group group = new Group(comp, SWT.NONE);
group.setText("Launch Action:");
GridData gd = new GridData(GridData.FILL_HORIZONTAL);
group.setLayoutData(gd);
GridLayout layout = new GridLayout();
layout.numColumns = 2;
group.setLayout(layout);
group.setFont(font);
mDefaultActionButton = new Button(group, SWT.RADIO);
gd = new GridData(GridData.FILL_HORIZONTAL);
gd.horizontalSpan = 2;
mDefaultActionButton.setLayoutData(gd);
mDefaultActionButton.setText("Launch Default Activity");
mDefaultActionButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
// event are received for both selection and deselection, so we only process
// the selection event to avoid doing it twice.
if (mDefaultActionButton.getSelection() == true) {
mLaunchAction = LaunchConfigDelegate.ACTION_DEFAULT;
mActivityCombo.setEnabled(false);
checkParameters();
}
}
});
mActivityActionButton = new Button(group, SWT.RADIO);
mActivityActionButton.setText("Launch:");
mActivityActionButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
// event are received for both selection and deselection, so we only process
// the selection event to avoid doing it twice.
if (mActivityActionButton.getSelection() == true) {
mLaunchAction = LaunchConfigDelegate.ACTION_ACTIVITY;
mActivityCombo.setEnabled(true);
checkParameters();
}
}
});
mActivityCombo = new Combo(group, SWT.DROP_DOWN | SWT.READ_ONLY);
gd = new GridData(GridData.FILL_HORIZONTAL);
mActivityCombo.setLayoutData(gd);
mActivityCombo.clearSelection();
mActivityCombo.setEnabled(false);
mActivityCombo.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
checkParameters();
}
});
mDoNothingActionButton = new Button(group, SWT.RADIO);
gd = new GridData(GridData.FILL_HORIZONTAL);
gd.horizontalSpan = 2;
mDoNothingActionButton.setLayoutData(gd);
mDoNothingActionButton.setText("Do Nothing");
mDoNothingActionButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
// event are received for both selection and deselection, so we only process
// the selection event to avoid doing it twice.
if (mDoNothingActionButton.getSelection() == true) {
mLaunchAction = LaunchConfigDelegate.ACTION_DO_NOTHING;
mActivityCombo.setEnabled(false);
checkParameters();
}
}
});
}
public String getName() {
return "Android";
}
@Override
public Image getImage() {
return AdtPlugin.getImageLoader().loadImage("mainLaunchTab.png", null);
}
public void performApply(ILaunchConfigurationWorkingCopy configuration) {
configuration.setAttribute(
IJavaLaunchConfigurationConstants.ATTR_PROJECT_NAME, mProjText.getText());
configuration.setAttribute(
IJavaLaunchConfigurationConstants.ATTR_ALLOW_TERMINATE, true);
// add the launch mode
configuration.setAttribute(LaunchConfigDelegate.ATTR_LAUNCH_ACTION, mLaunchAction);
// add the activity
int selection = mActivityCombo.getSelectionIndex();
if (mActivities != null && selection >=0 && selection < mActivities.length) {
configuration.setAttribute(LaunchConfigDelegate.ATTR_ACTIVITY, mActivities[selection]);
}
// link the project and the launch config.
mapResources(configuration);
}
public void setDefaults(ILaunchConfigurationWorkingCopy configuration) {
configuration.setAttribute(LaunchConfigDelegate.ATTR_LAUNCH_ACTION,
LaunchConfigDelegate.DEFAULT_LAUNCH_ACTION);
}
/**
* Creates the widgets for specifying a main type.
*
* @param parent the parent composite
*/
protected void createProjectEditor(Composite parent) {
Font font = parent.getFont();
Group group = new Group(parent, SWT.NONE);
group.setText("Project:");
GridData gd = new GridData(GridData.FILL_HORIZONTAL);
group.setLayoutData(gd);
GridLayout layout = new GridLayout();
layout.numColumns = 2;
group.setLayout(layout);
group.setFont(font);
mProjText = new Text(group, SWT.SINGLE | SWT.BORDER);
gd = new GridData(GridData.FILL_HORIZONTAL);
mProjText.setLayoutData(gd);
mProjText.setFont(font);
mProjText.addModifyListener(mListener);
mProjButton = createPushButton(group, "Browse...", null);
mProjButton.addSelectionListener(mListener);
}
/**
* returns the default listener from this class. For all subclasses this
* listener will only provide the functi Jaonality of updating the current
* tab
*
* @return a widget listener
*/
protected WidgetListener getDefaultListener() {
return mListener;
}
/**
* Return the {@link IJavaProject} corresponding to the project name in the project
* name text field, or null if the text does not match a project name.
* @param javaModel the Java Model object corresponding for the current workspace root.
* @return a IJavaProject object or null.
*/
protected IJavaProject getJavaProject(IJavaModel javaModel) {
String projectName = mProjText.getText().trim();
if (projectName.length() < 1) {
return null;
}
return javaModel.getJavaProject(projectName);
}
/**
* Show a dialog that lets the user select a project. This in turn provides
* context for the main type, allowing the user to key a main type name, or
* constraining the search for main types to the specified project.
*/
protected void handleProjectButtonSelected() {
IJavaProject javaProject = mProjectChooserHelper.chooseJavaProject(
mProjText.getText().trim());
if (javaProject == null) {
return;
}// end if
String projectName = javaProject.getElementName();
mProjText.setText(projectName);
// get the list of activities and fill the combo
IProject project = javaProject.getProject();
loadActivities(project);
}// end handle selected
/**
* Initializes this tab's controls with values from the given
* launch configuration. This method is called when
* a configuration is selected to view or edit, after this
* tab's control has been created.
*
* @param config launch configuration
*
* @see ILaunchConfigurationTab
*/
public void initializeFrom(ILaunchConfiguration config) {
String projectName = EMPTY_STRING;
try {
projectName = config.getAttribute(IJavaLaunchConfigurationConstants.ATTR_PROJECT_NAME,
EMPTY_STRING);
}// end try
catch (CoreException ce) {
}
mProjText.setText(projectName);
// get the list of projects
IJavaProject[] projects = mProjectChooserHelper.getAndroidProjects(null);
if (projects != null) {
// look for the currently selected project
IProject proj = null;
for (IJavaProject p : projects) {
if (p.getElementName().equals(projectName)) {
proj = p.getProject();
break;
}
}
loadActivities(proj);
}
// load the launch action.
mLaunchAction = LaunchConfigDelegate.DEFAULT_LAUNCH_ACTION;
try {
mLaunchAction = config.getAttribute(LaunchConfigDelegate.ATTR_LAUNCH_ACTION,
mLaunchAction);
} catch (CoreException e) {
// nothing to be done really. launchAction will keep its default value.
}
mDefaultActionButton.setSelection(mLaunchAction == LaunchConfigDelegate.ACTION_DEFAULT);
mActivityActionButton.setSelection(mLaunchAction == LaunchConfigDelegate.ACTION_ACTIVITY);
mDoNothingActionButton.setSelection(
mLaunchAction == LaunchConfigDelegate.ACTION_DO_NOTHING);
// now look for the activity and load it if present, otherwise, revert
// to the current one.
String activityName = EMPTY_STRING;
try {
activityName = config.getAttribute(LaunchConfigDelegate.ATTR_ACTIVITY, EMPTY_STRING);
}// end try
catch (CoreException ce) {
// nothing to be done really. activityName will stay empty
}
if (mLaunchAction != LaunchConfigDelegate.ACTION_ACTIVITY) {
mActivityCombo.setEnabled(false);
mActivityCombo.clearSelection();
} else {
mActivityCombo.setEnabled(true);
if (activityName == null || activityName.equals(EMPTY_STRING)) {
mActivityCombo.clearSelection();
} else if (mActivities != null && mActivities.length > 0) {
// look for the name of the activity in the combo.
boolean found = false;
for (int i = 0 ; i < mActivities.length ; i++) {
if (activityName.equals(mActivities[i])) {
found = true;
mActivityCombo.select(i);
break;
}
}
// if we haven't found a matching activity we clear the combo selection
if (found == false) {
mActivityCombo.clearSelection();
}
}
}
}
/**
* Associates the launch config and the project. This allows Eclipse to delete the launch
* config when the project is deleted.
*
* @param config the launch config working copy.
*/
protected void mapResources(ILaunchConfigurationWorkingCopy config) {
// get the java model
IWorkspaceRoot workspaceRoot = ResourcesPlugin.getWorkspace().getRoot();
IJavaModel javaModel = JavaCore.create(workspaceRoot);
// get the IJavaProject described by the text field.
IJavaProject javaProject = getJavaProject(javaModel);
IResource[] resources = null;
if (javaProject != null) {
resources = AndroidLaunchController.getResourcesToMap(javaProject.getProject());
}
config.setMappedResources(resources);
}
/**
* Loads the ui with the activities of the specified project, and stores the
* activities in <code>mActivities</code>.
* <p/>
* First activity is selected by default if present.
*
* @param project The project to load the activities from.
*/
private void loadActivities(IProject project) {
if (project != null) {
try {
// parse the manifest for the list of activities.
AndroidManifestParser manifestParser = AndroidManifestParser.parse(
BaseProjectHelper.getJavaProject(project), null /* errorListener */,
true /* gatherData */, false /* markErrors */);
if (manifestParser != null) {
mActivities = manifestParser.getActivities();
mActivityCombo.removeAll();
if (mActivities.length > 0) {
if (mLaunchAction == LaunchConfigDelegate.ACTION_ACTIVITY) {
mActivityCombo.setEnabled(true);
}
for (String s : mActivities) {
mActivityCombo.add(s);
}
} else {
mActivityCombo.setEnabled(false);
}
// the selection will be set when we update the ui from the current
// config object.
mActivityCombo.clearSelection();
return;
}
} catch (CoreException e) {
// The AndroidManifest parsing failed. The builders must have reported the errors
// already so there's nothing to do.
}
}
// if we reach this point, either project is null, or we got an exception during
// the parsing. In either case, we empty the activity list.
mActivityCombo.removeAll();
mActivities = null;
}
/**
* Checks the parameters for correctness, and update the error message and buttons.
* @return the current IProject of this launch config.
*/
private IProject checkParameters() {
try {
//test the project name first!
String text = mProjText.getText();
if (text.length() == 0) {
setErrorMessage("Project Name is required!");
} else if (text.matches("[a-zA-Z0-9_ \\.-]+") == false) {
setErrorMessage("Project name contains unsupported characters!");
} else {
IJavaProject[] projects = mProjectChooserHelper.getAndroidProjects(null);
IProject found = null;
for (IJavaProject javaProject : projects) {
if (javaProject.getProject().getName().equals(text)) {
found = javaProject.getProject();
break;
}
}
if (found != null) {
setErrorMessage(null);
} else {
setErrorMessage(String.format("There is no android project named '%1$s'",
text));
}
return found;
}
} finally {
updateLaunchConfigurationDialog();
}
return null;
}
}

View File

@@ -0,0 +1,16 @@
Dialog_Title_SDK_Location=Android SDK Location
SDK_Not_Setup=The location of the Android SDK has not been setup. Please go to Preferences > Android and set it up
Error_Check_Prefs=%1$s\nPlease check the Android plugin's preferences.
Could_Not_Find_Folder=Could not find SDK folder '%1$s'.
Could_Not_Find_Folder_In_SDK=Could not find folder '%1$s' inside SDK '%2$s'.
Could_Not_Find=Could not find %1$s\!
VersionCheck_SDK_Milestone_Too_Low=This version of ADT requires a SDK in version M%1$d or above.\n\nCurrent version is %2$s.\n\nPlease update your SDK to the latest version.
VersionCheck_SDK_Build_Too_Low=This version of ADT requires a SDK in version %1$d (M%2$d) or above.\n\nCurrent version is %3$s.\n\nPlease update your SDK to the latest version.
VersionCheck_Plugin_Version_Failed=Failed to get the required ADT version number from the SDK.\n\nThe Android Developer Toolkit may not work properly.
VersionCheck_Unable_To_Parse_Version_s=Unable to parse sdk build version: %1$s
VersionCheck_Plugin_Too_Old=This Android SDK requires Android Developer Toolkit version %1$d.%2$d.%3$d or above.\n\nCurrent version is %4$s.\n\nPlease update ADT to the latest version.
AdtPlugin_Failed_To_Start_s=Failed to start %1$s
AdtPlugin_Android_SDK_Content_Loader=Android SDK Content Loader
AdtPlugin_Parsing_Resources=Parsing Resources
AdtPlugin_Android_SDK_Resource_Parser=Android SDK Resource Parser
AdtPlugin_Failed_To_Parse_s=Failed to parse:

View File

@@ -0,0 +1,209 @@
/*
* 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.preferences;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.sdk.Sdk;
import com.android.ide.eclipse.adt.sdk.Sdk.ITargetChangeListener;
import com.android.sdklib.IAndroidTarget;
import com.android.sdkuilib.SdkTargetSelector;
import org.eclipse.core.resources.IProject;
import org.eclipse.jface.preference.DirectoryFieldEditor;
import org.eclipse.jface.preference.FieldEditorPreferencePage;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.IWorkbenchPreferencePage;
import java.io.File;
/**
* This class represents a preference page that is contributed to the
* Preferences dialog. By subclassing <samp>FieldEditorPreferencePage</samp>,
* we can use the field support built into JFace that allows us to create a page
* that is small and knows how to save, restore and apply itself.
* <p>
* This page is used to modify preferences only. They are stored in the
* preference store that belongs to the main plug-in class. That way,
* preferences can be accessed directly via the preference store.
*/
public class AndroidPreferencePage extends FieldEditorPreferencePage implements
IWorkbenchPreferencePage {
public AndroidPreferencePage() {
super(GRID);
setPreferenceStore(AdtPlugin.getDefault().getPreferenceStore());
setDescription(Messages.AndroidPreferencePage_Title);
}
/**
* Creates the field editors. Field editors are abstractions of the common
* GUI blocks needed to manipulate various types of preferences. Each field
* editor knows how to save and restore itself.
*/
@Override
public void createFieldEditors() {
addField(new SdkDirectoryFieldEditor(AdtPlugin.PREFS_SDK_DIR,
Messages.AndroidPreferencePage_SDK_Location_, getFieldEditorParent()));
}
/*
* (non-Javadoc)
*
* @see org.eclipse.ui.IWorkbenchPreferencePage#init(org.eclipse.ui.IWorkbench)
*/
public void init(IWorkbench workbench) {
}
/**
* Custom version of DirectoryFieldEditor which validates that the directory really
* contains an SDK.
*
* There's a known issue here, which is really a rare edge-case: if the pref dialog is open
* which a given sdk directory and the *content* of the directory changes such that the sdk
* state changed (i.e. from valid to invalid or vice versa), the pref panel will display or
* hide the error as appropriate but the pref panel will fail to validate the apply/ok buttons
* appropriately. The easy workaround is to cancel the pref panel and enter it again.
*/
private static class SdkDirectoryFieldEditor extends DirectoryFieldEditor {
private SdkTargetSelector mTargetSelector;
private TargetChangedListener mTargetChangeListener;
public SdkDirectoryFieldEditor(String name, String labelText, Composite parent) {
super(name, labelText, parent);
setEmptyStringAllowed(false);
}
/**
* Method declared on StringFieldEditor and overridden in DirectoryFieldEditor.
* Checks whether the text input field contains a valid directory.
*
* @return True if the apply/ok button should be enabled in the pref panel
*/
@Override
protected boolean doCheckState() {
String fileName = getTextControl().getText();
fileName = fileName.trim();
if (fileName.indexOf(',') >= 0 || fileName.indexOf(';') >= 0) {
setErrorMessage(Messages.AndroidPreferencePage_ERROR_Reserved_Char);
return false; // Apply/OK must be disabled
}
File file = new File(fileName);
if (!file.isDirectory()) {
setErrorMessage(JFaceResources.getString(
"DirectoryFieldEditor.errorMessage")); //$NON-NLS-1$
return false;
}
boolean ok = AdtPlugin.getDefault().checkSdkLocationAndId(fileName,
new AdtPlugin.CheckSdkErrorHandler() {
@Override
public boolean handleError(String message) {
setErrorMessage(message.replaceAll("\n", " ")); //$NON-NLS-1$ //$NON-NLS-2$
return false; // Apply/OK must be disabled
}
@Override
public boolean handleWarning(String message) {
showMessage(message.replaceAll("\n", " ")); //$NON-NLS-1$ //$NON-NLS-2$
return true; // Apply/OK must be enabled
}
});
if (ok) clearMessage();
return ok;
}
@Override
public Text getTextControl(Composite parent) {
setValidateStrategy(VALIDATE_ON_KEY_STROKE);
return super.getTextControl(parent);
}
/* (non-Javadoc)
* Method declared on StringFieldEditor (and FieldEditor).
*/
@Override
protected void doFillIntoGrid(Composite parent, int numColumns) {
super.doFillIntoGrid(parent, numColumns);
GridData gd;
Label l = new Label(parent, SWT.NONE);
l.setText("Note: The list of SDK Targets below is only reloaded once you hit 'Apply' or 'OK'.");
gd = new GridData(GridData.FILL_HORIZONTAL);
gd.horizontalSpan = numColumns;
l.setLayoutData(gd);
try {
// We may not have an sdk if the sdk path pref is empty or not valid.
Sdk sdk = Sdk.getCurrent();
IAndroidTarget[] targets = sdk != null ? sdk.getTargets() : null;
mTargetSelector = new SdkTargetSelector(parent,
targets,
false, /*allowSelection*/
false /*multipleSelection*/);
gd = (GridData) mTargetSelector.getLayoutData();
gd.horizontalSpan = numColumns;
if (mTargetChangeListener == null) {
mTargetChangeListener = new TargetChangedListener();
AdtPlugin.getDefault().addTargetListener(mTargetChangeListener);
}
} catch (Exception e) {
// We need to catch *any* exception that arises here, otherwise it disables
// the whole pref panel. We can live without the Sdk target selector but
// not being able to actually set an sdk path.
AdtPlugin.log(e, "SdkTargetSelector failed");
}
}
@Override
public void dispose() {
super.dispose();
if (mTargetChangeListener != null) {
AdtPlugin.getDefault().removeTargetListener(mTargetChangeListener);
mTargetChangeListener = null;
}
}
private class TargetChangedListener implements ITargetChangeListener {
public void onProjectTargetChange(IProject changedProject) {
// do nothing.
}
public void onTargetsLoaded() {
if (mTargetSelector != null) {
// We may not have an sdk if the sdk path pref is empty or not valid.
Sdk sdk = Sdk.getCurrent();
IAndroidTarget[] targets = sdk != null ? sdk.getTargets() : null;
mTargetSelector.setTargets(targets);
}
}
}
}
}

View File

@@ -0,0 +1,217 @@
/*
* 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.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;
import org.eclipse.jface.preference.FileFieldEditor;
import org.eclipse.jface.preference.RadioGroupFieldEditor;
import org.eclipse.jface.preference.StringFieldEditor;
import org.eclipse.swt.widgets.Composite;
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.
*
*/
public class BuildPreferencePage extends FieldEditorPreferencePage implements
IWorkbenchPreferencePage {
final static String BUILD_STR_SILENT = "silent"; //$NON-NLS-1$
final static String BUILD_STR_NORMAL = "normal"; //$NON-NLS-1$
final static String BUILD_STR_VERBOSE = "verbose"; //$NON-NLS-1$
public BuildPreferencePage() {
super(GRID);
setPreferenceStore(AdtPlugin.getDefault().getPreferenceStore());
setDescription(Messages.BuildPreferencePage_Title);
}
public static int getBuildLevel(String buildPrefValue) {
if (BUILD_STR_SILENT.equals(buildPrefValue)) {
return AdtConstants.BUILD_ALWAYS;
} else if (BUILD_STR_VERBOSE.equals(buildPrefValue)) {
return AdtConstants.BUILD_VERBOSE;
}
return AdtConstants.BUILD_NORMAL;
}
@Override
protected void createFieldEditors() {
addField(new BooleanFieldEditor(AdtPlugin.PREFS_RES_AUTO_REFRESH,
Messages.BuildPreferencePage_Auto_Refresh_Resources_on_Build,
getFieldEditorParent()));
RadioGroupFieldEditor rgfe = new RadioGroupFieldEditor(
AdtPlugin.PREFS_BUILD_VERBOSITY,
Messages.BuildPreferencePage_Build_Output, 1, new String[][] {
{ Messages.BuildPreferencePage_Silent, BUILD_STR_SILENT },
{ Messages.BuildPreferencePage_Normal, BUILD_STR_NORMAL },
{ Messages.BuildPreferencePage_Verbose, BUILD_STR_VERBOSE }
},
getFieldEditorParent(), true);
addField(rgfe);
addField(new ReadOnlyFieldEditor(AdtPlugin.PREFS_DEFAULT_DEBUG_KEYSTORE,
Messages.BuildPreferencePage_Default_KeyStore, getFieldEditorParent()));
addField(new KeystoreFieldEditor(AdtPlugin.PREFS_CUSTOM_DEBUG_KEYSTORE,
Messages.BuildPreferencePage_Custom_Keystore, getFieldEditorParent()));
}
/*
* (non-Javadoc)
*
* @see org.eclipse.ui.IWorkbenchPreferencePage#init(org.eclipse.ui.IWorkbench)
*/
public void init(IWorkbench workbench) {
}
/**
* A read-only string field editor.
*/
private static class ReadOnlyFieldEditor extends StringFieldEditor {
public ReadOnlyFieldEditor(String name, String labelText, Composite parent) {
super(name, labelText, parent);
}
@Override
protected void createControl(Composite parent) {
super.createControl(parent);
Text control = getTextControl();
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 (msg == 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

@@ -0,0 +1,51 @@
/*
* 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.preferences;
import com.android.ide.eclipse.adt.AdtPlugin;
import org.eclipse.jface.preference.FieldEditorPreferencePage;
import org.eclipse.jface.preference.StringFieldEditor;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.IWorkbenchPreferencePage;
/**
* Settings page for launch related preferences.
*/
public class LaunchPreferencePage extends FieldEditorPreferencePage implements
IWorkbenchPreferencePage {
public LaunchPreferencePage() {
super(GRID);
setPreferenceStore(AdtPlugin.getDefault().getPreferenceStore());
setDescription(Messages.LaunchPreferencePage_Title);
}
@Override
protected void createFieldEditors() {
addField(new StringFieldEditor(AdtPlugin.PREFS_EMU_OPTIONS,
Messages.LaunchPreferencePage_Default_Emu_Options, getFieldEditorParent()));
addField(new StringFieldEditor(AdtPlugin.PREFS_HOME_PACKAGE,
Messages.LaunchPreferencePage_Default_HOME_Package, getFieldEditorParent()));
}
public void init(IWorkbench workbench) {
// pass
}
}

View File

@@ -0,0 +1,43 @@
package com.android.ide.eclipse.adt.preferences;
import org.eclipse.osgi.util.NLS;
public class Messages extends NLS {
private static final String BUNDLE_NAME = "com.android.ide.eclipse.adt.preferences.messages"; //$NON-NLS-1$
public static String AndroidPreferencePage_ERROR_Reserved_Char;
public static String AndroidPreferencePage_SDK_Location_;
public static String AndroidPreferencePage_Title;
public static String BuildPreferencePage_Auto_Refresh_Resources_on_Build;
public static String BuildPreferencePage_Build_Output;
public static String BuildPreferencePage_Custom_Keystore;
public static String BuildPreferencePage_Default_KeyStore;
public static String BuildPreferencePage_Normal;
public static String BuildPreferencePage_Silent;
public static String BuildPreferencePage_Title;
public static String BuildPreferencePage_Verbose;
public static String LaunchPreferencePage_Default_Emu_Options;
public static String LaunchPreferencePage_Default_HOME_Package;
public static String LaunchPreferencePage_Title;
static {
// initialize resource bundle
NLS.initializeMessages(BUNDLE_NAME, Messages.class);
}
private Messages() {
}
}

View File

@@ -0,0 +1,57 @@
/*
* Copyright (C) 2007 The Android Open Source Project
*
* Licensed under the Eclipse Public License, Version 1.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.eclipse.org/org/documents/epl-v10.php
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.ide.eclipse.adt.preferences;
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.core.runtime.preferences.AbstractPreferenceInitializer;
import org.eclipse.jface.preference.IPreferenceStore;
/**
* Class used to initialize default preference values.
*/
public class PreferenceInitializer extends AbstractPreferenceInitializer {
/*
* (non-Javadoc)
*
* @see org.eclipse.core.runtime.preferences.AbstractPreferenceInitializer
* #initializeDefaultPreferences()
*/
@Override
public void initializeDefaultPreferences() {
IPreferenceStore store = AdtPlugin.getDefault().getPreferenceStore();
store.setDefault(AdtPlugin.PREFS_RES_AUTO_REFRESH, true);
store.setDefault(AdtPlugin.PREFS_BUILD_VERBOSITY, BuildPreferencePage.BUILD_STR_NORMAL);
store.setDefault(AdtPlugin.PREFS_HOME_PACKAGE, "android.process.acore"); //$NON-NLS-1$
try {
store.setDefault(AdtPlugin.PREFS_DEFAULT_DEBUG_KEYSTORE,
DebugKeyProvider.getDefaultKeyStoreOsPath());
} catch (KeytoolException e) {
AdtPlugin.log(e, "Get default debug keystore path failed"); //$NON-NLS-1$
} catch (AndroidLocationException e) {
AdtPlugin.log(e, "Get default debug keystore path failed"); //$NON-NLS-1$
}
}
}

View File

@@ -0,0 +1,14 @@
BuildPreferencePage_Title=Build Settings:
BuildPreferencePage_Auto_Refresh_Resources_on_Build=Automatically refresh Resources and Assets folder on build
BuildPreferencePage_Build_Output=Build output
BuildPreferencePage_Silent=Silent
BuildPreferencePage_Normal=Normal
BuildPreferencePage_Verbose=Verbose
BuildPreferencePage_Default_KeyStore=Default debug keystore:
BuildPreferencePage_Custom_Keystore=Custom debug keystore:
LaunchPreferencePage_Title=Launch Settings:
LaunchPreferencePage_Default_Emu_Options=Default emulator options:
LaunchPreferencePage_Default_HOME_Package=Default HOME package:
AndroidPreferencePage_Title=Android Preferences
AndroidPreferencePage_SDK_Location_=SDK Location:
AndroidPreferencePage_ERROR_Reserved_Char=Reserved characters ',' and ';' cannot be used in the SDK Location.

View File

@@ -0,0 +1,291 @@
/*
* 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.project;
import com.android.ide.eclipse.adt.build.ApkBuilder;
import com.android.ide.eclipse.adt.build.PreCompilerBuilder;
import com.android.ide.eclipse.adt.build.ResourceManagerBuilder;
import com.android.ide.eclipse.common.AndroidConstants;
import org.eclipse.core.resources.ICommand;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IProjectDescription;
import org.eclipse.core.resources.IProjectNature;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.jdt.core.JavaCore;
/**
* Project nature for the Android Projects.
*/
public class AndroidNature implements IProjectNature {
/** the project this nature object is associated with */
private IProject mProject;
/**
* Configures this nature for its project. This is called by the workspace
* when natures are added to the project using
* <code>IProject.setDescription</code> and should not be called directly
* by clients. The nature extension id is added to the list of natures
* before this method is called, and need not be added here.
*
* Exceptions thrown by this method will be propagated back to the caller of
* <code>IProject.setDescription</code>, but the nature will remain in
* the project description.
*
* The Android nature adds the pre-builder and the APK builder if necessary.
*
* @see org.eclipse.core.resources.IProjectNature#configure()
* @throws CoreException if configuration fails.
*/
public void configure() throws CoreException {
configureResourceManagerBuilder(mProject);
configurePreBuilder(mProject);
configureApkBuilder(mProject);
}
/**
* De-configures this nature for its project. This is called by the
* workspace when natures are removed from the project using
* <code>IProject.setDescription</code> and should not be called directly
* by clients. The nature extension id is removed from the list of natures
* before this method is called, and need not be removed here.
*
* Exceptions thrown by this method will be propagated back to the caller of
* <code>IProject.setDescription</code>, but the nature will still be
* removed from the project description.
*
* The Android nature removes the custom pre builder and APK builder.
*
* @see org.eclipse.core.resources.IProjectNature#deconfigure()
* @throws CoreException if configuration fails.
*/
public void deconfigure() throws CoreException {
// remove the android builders
removeBuilder(mProject, ResourceManagerBuilder.ID);
removeBuilder(mProject, PreCompilerBuilder.ID);
removeBuilder(mProject, ApkBuilder.ID);
}
/**
* Returns the project to which this project nature applies.
*
* @return the project handle
* @see org.eclipse.core.resources.IProjectNature#getProject()
*/
public IProject getProject() {
return mProject;
}
/**
* Sets the project to which this nature applies. Used when instantiating
* this project nature runtime. This is called by
* <code>IProject.create()</code> or
* <code>IProject.setDescription()</code> and should not be called
* directly by clients.
*
* @param project the project to which this nature applies
* @see org.eclipse.core.resources.IProjectNature#setProject(org.eclipse.core.resources.IProject)
*/
public void setProject(IProject project) {
mProject = project;
}
/**
* Adds the Android Nature and the Java Nature to the project if it doesn't
* already have them.
*
* @param project An existing or new project to update
* @param monitor An optional progress monitor. Can be null.
* @throws CoreException if fails to change the nature.
*/
public static synchronized void setupProjectNatures(IProject project,
IProgressMonitor monitor) throws CoreException {
if (project == null || !project.isOpen()) return;
if (monitor == null) monitor = new NullProgressMonitor();
// Add the natures. We need to add the Java nature first, so it adds its builder to the
// project first. This way, when the android nature is added, we can control where to put
// the android builders in relation to the java builder.
// Adding the java nature after the android one, would place the java builder before the
// android builders.
addNatureToProjectDescription(project, JavaCore.NATURE_ID, monitor);
addNatureToProjectDescription(project, AndroidConstants.NATURE, monitor);
}
/**
* Add the specified nature to the specified project. The nature is only
* added if not already present.
* <p/>
* Android Natures are always inserted at the beginning of the list of natures in order to
* have the jdt views/dialogs display the proper icon.
*
* @param project The project to modify.
* @param natureId The Id of the nature to add.
* @param monitor An existing progress monitor.
* @throws CoreException if fails to change the nature.
*/
private static void addNatureToProjectDescription(IProject project,
String natureId, IProgressMonitor monitor) throws CoreException {
if (!project.hasNature(natureId)) {
IProjectDescription description = project.getDescription();
String[] natures = description.getNatureIds();
String[] newNatures = new String[natures.length + 1];
// Android natures always come first.
if (natureId.equals(AndroidConstants.NATURE)) {
System.arraycopy(natures, 0, newNatures, 1, natures.length);
newNatures[0] = natureId;
} else {
System.arraycopy(natures, 0, newNatures, 0, natures.length);
newNatures[natures.length] = natureId;
}
description.setNatureIds(newNatures);
project.setDescription(description, new SubProgressMonitor(monitor, 10));
}
}
/**
* Adds the ResourceManagerBuilder, if its not already there. It'll insert
* itself as the first builder.
* @throws CoreException
*
*/
public static void configureResourceManagerBuilder(IProject project)
throws CoreException {
// get the builder list
IProjectDescription desc = project.getDescription();
ICommand[] commands = desc.getBuildSpec();
// look for the builder in case it's already there.
for (int i = 0; i < commands.length; ++i) {
if (ResourceManagerBuilder.ID.equals(commands[i].getBuilderName())) {
return;
}
}
// it's not there, lets add it at the beginning of the builders
ICommand[] newCommands = new ICommand[commands.length + 1];
System.arraycopy(commands, 0, newCommands, 1, commands.length);
ICommand command = desc.newCommand();
command.setBuilderName(ResourceManagerBuilder.ID);
newCommands[0] = command;
desc.setBuildSpec(newCommands);
project.setDescription(desc, null);
}
/**
* Adds the PreCompilerBuilder if its not already there. It'll check for
* presence of the ResourceManager and insert itself right after.
* @param project
* @throws CoreException
*/
public static void configurePreBuilder(IProject project)
throws CoreException {
// get the builder list
IProjectDescription desc = project.getDescription();
ICommand[] commands = desc.getBuildSpec();
// look for the builder in case it's already there.
for (int i = 0; i < commands.length; ++i) {
if (PreCompilerBuilder.ID.equals(commands[i].getBuilderName())) {
return;
}
}
// we need to add it after the resource manager builder.
// Let's look for it
int index = -1;
for (int i = 0; i < commands.length; ++i) {
if (ResourceManagerBuilder.ID.equals(commands[i].getBuilderName())) {
index = i;
break;
}
}
// we're inserting after
index++;
// do the insertion
// copy the builders before.
ICommand[] newCommands = new ICommand[commands.length + 1];
System.arraycopy(commands, 0, newCommands, 0, index);
// insert the new builder
ICommand command = desc.newCommand();
command.setBuilderName(PreCompilerBuilder.ID);
newCommands[index] = command;
// copy the builder after
System.arraycopy(commands, index, newCommands, index + 1, commands.length-index);
// set the new builders in the project
desc.setBuildSpec(newCommands);
project.setDescription(desc, null);
}
public static void configureApkBuilder(IProject project)
throws CoreException {
// Add the .apk builder at the end if it's not already there
IProjectDescription desc = project.getDescription();
ICommand[] commands = desc.getBuildSpec();
for (int i = 0; i < commands.length; ++i) {
if (ApkBuilder.ID.equals(commands[i].getBuilderName())) {
return;
}
}
ICommand[] newCommands = new ICommand[commands.length + 1];
System.arraycopy(commands, 0, newCommands, 0, commands.length);
ICommand command = desc.newCommand();
command.setBuilderName(ApkBuilder.ID);
newCommands[commands.length] = command;
desc.setBuildSpec(newCommands);
project.setDescription(desc, null);
}
/**
* Removes a builder from the project.
* @param project The project to remove the builder from.
* @param id The String ID of the builder to remove.
* @return true if the builder was found and removed.
* @throws CoreException
*/
public static boolean removeBuilder(IProject project, String id) throws CoreException {
IProjectDescription description = project.getDescription();
ICommand[] commands = description.getBuildSpec();
for (int i = 0; i < commands.length; ++i) {
if (id.equals(commands[i].getBuilderName())) {
ICommand[] newCommands = new ICommand[commands.length - 1];
System.arraycopy(commands, 0, newCommands, 0, i);
System.arraycopy(commands, i + 1, newCommands, i, commands.length - i - 1);
description.setBuildSpec(newCommands);
project.setDescription(description, null);
return true;
}
}
return false;
}
}

View File

@@ -0,0 +1,153 @@
/*
* 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.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.IProjectDescription;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.ui.IObjectActionDelegate;
import org.eclipse.ui.IWorkbenchPart;
import java.util.Iterator;
/**
* Converts a project created with the activity creator into an
* Android project.
*/
public class ConvertToAndroidAction implements IObjectActionDelegate {
private ISelection mSelection;
/*
* (non-Javadoc)
*
* @see IObjectActionDelegate#setActivePart(IAction, IWorkbenchPart)
*/
public void setActivePart(IAction action, IWorkbenchPart targetPart) {
// pass
}
/*
* (non-Javadoc)
*
* @see IActionDelegate#run(IAction)
*/
public void run(IAction action) {
if (mSelection instanceof IStructuredSelection) {
for (Iterator<?> it = ((IStructuredSelection)mSelection).iterator(); it.hasNext();) {
Object element = it.next();
IProject project = null;
if (element instanceof IProject) {
project = (IProject)element;
} else if (element instanceof IAdaptable) {
project = (IProject)((IAdaptable)element).getAdapter(IProject.class);
}
if (project != null) {
convertProject(project);
}
}
}
}
/*
* (non-Javadoc)
*
* @see IActionDelegate#selectionChanged(IAction, ISelection)
*/
public void selectionChanged(IAction action, ISelection selection) {
this.mSelection = selection;
}
/**
* Toggles sample nature on a project
*
* @param project to have sample nature added or removed
*/
private void convertProject(final IProject project) {
new Job("Convert Project") {
@Override
protected IStatus run(IProgressMonitor monitor) {
try {
if (monitor != null) {
monitor.beginTask(String.format(
"Convert %1$s to Android", project.getName()), 5);
}
IProjectDescription description = project.getDescription();
String[] natures = description.getNatureIds();
// check if the project already has the android nature.
for (int i = 0; i < natures.length; ++i) {
if (AndroidConstants.NATURE.equals(natures[i])) {
// we shouldn't be here as the visibility of the item
// is dependent on the project.
return new Status(Status.WARNING, AdtPlugin.PLUGIN_ID,
"Project is already an Android project");
}
}
if (monitor != null) {
monitor.worked(1);
}
String[] newNatures = new String[natures.length + 1];
System.arraycopy(natures, 0, newNatures, 1, natures.length);
newNatures[0] = AndroidConstants.NATURE;
// set the new nature list in the project
description.setNatureIds(newNatures);
project.setDescription(description, null);
if (monitor != null) {
monitor.worked(1);
}
// Fix the classpath entries.
// get a java project
IJavaProject javaProject = JavaCore.create(project);
ProjectHelper.fixProjectClasspathEntries(javaProject);
if (monitor != null) {
monitor.worked(1);
}
return Status.OK_STATUS;
} catch (JavaModelException e) {
return e.getJavaModelStatus();
} catch (CoreException e) {
return e.getStatus();
} finally {
if (monitor != null) {
monitor.done();
}
}
}
}.schedule();
}
}

View File

@@ -0,0 +1,210 @@
/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Eclipse Public License, Version 1.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.eclipse.org/org/documents/epl-v10.php
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.ide.eclipse.adt.project;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.common.AndroidConstants;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IPackageFragment;
import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.ITypeHierarchy;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.ui.IObjectActionDelegate;
import org.eclipse.ui.IWorkbenchPart;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
/**
* Action going through all the source of a project and creating a pre-processed aidl file
* with all the custom parcelable classes.
*/
public class CreateAidlImportAction implements IObjectActionDelegate {
private ISelection mSelection;
public CreateAidlImportAction() {
// pass
}
public void setActivePart(IAction action, IWorkbenchPart targetPart) {
// pass
}
/*
* (non-Javadoc)
* @see org.eclipse.ui.IActionDelegate#run(org.eclipse.jface.action.IAction)
*/
public void run(IAction action) {
if (mSelection instanceof IStructuredSelection) {
for (Iterator<?> it = ((IStructuredSelection)mSelection).iterator(); it.hasNext();) {
Object element = it.next();
IProject project = null;
if (element instanceof IProject) {
project = (IProject)element;
} else if (element instanceof IAdaptable) {
project = (IProject)((IAdaptable)element).getAdapter(IProject.class);
}
if (project != null) {
final IProject fproject = project;
new Job("Aidl preprocess") {
@Override
protected IStatus run(IProgressMonitor monitor) {
return createImportFile(fproject, monitor);
}
}.schedule();
}
}
}
}
public void selectionChanged(IAction action, ISelection selection) {
mSelection = selection;
}
private IStatus createImportFile(IProject project, IProgressMonitor monitor) {
try {
if (monitor != null) {
monitor.beginTask(String.format(
"Creating aid preprocess file for %1$s", project.getName()), 1);
}
ArrayList<String> parcelables = new ArrayList<String>();
IJavaProject javaProject = JavaCore.create(project);
IPackageFragmentRoot[] roots = javaProject.getPackageFragmentRoots();
for (IPackageFragmentRoot root : roots) {
if (root.isArchive() == false && root.isExternal() == false) {
parsePackageFragmentRoot(root, parcelables, monitor);
}
}
// create the file with the parcelables
if (parcelables.size() > 0) {
IPath path = project.getLocation();
path = path.append(AndroidConstants.FN_PROJECT_AIDL);
File f = new File(path.toOSString());
if (f.exists() == false) {
if (f.createNewFile() == false) {
return new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
"Failed to create /project.aidl");
}
}
FileWriter fw = new FileWriter(f);
fw.write("// This file is auto-generated by the\n");
fw.write("// 'Create Aidl preprocess file for Parcelable classes'\n");
fw.write("// action. Do not modify!\n\n");
for (String parcelable : parcelables) {
fw.write("parcelable "); //$NON-NLS-1$
fw.write(parcelable);
fw.append(";\n"); //$NON-NLS-1$
}
fw.close();
// need to refresh the level just below the project to make sure it's being picked
// up by eclipse.
project.refreshLocal(IResource.DEPTH_ONE, monitor);
}
if (monitor != null) {
monitor.worked(1);
monitor.done();
}
return Status.OK_STATUS;
} catch (JavaModelException e) {
return e.getJavaModelStatus();
} catch (IOException e) {
return new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
"Failed to create /project.aidl", e);
} catch (CoreException e) {
return e.getStatus();
} finally {
if (monitor != null) {
monitor.done();
}
}
}
private void parsePackageFragmentRoot(IPackageFragmentRoot root,
ArrayList<String> parcelables, IProgressMonitor monitor) throws JavaModelException {
IJavaElement[] elements = root.getChildren();
for (IJavaElement element : elements) {
if (element instanceof IPackageFragment) {
ICompilationUnit[] compilationUnits =
((IPackageFragment)element).getCompilationUnits();
for (ICompilationUnit unit : compilationUnits) {
IType[] types = unit.getTypes();
for (IType type : types) {
parseType(type, parcelables, monitor);
}
}
}
}
}
private void parseType(IType type, ArrayList<String> parcelables, IProgressMonitor monitor)
throws JavaModelException {
// first look in this type if it somehow extends parcelable.
ITypeHierarchy typeHierarchy = type.newSupertypeHierarchy(monitor);
IType[] superInterfaces = typeHierarchy.getAllSuperInterfaces(type);
for (IType superInterface : superInterfaces) {
if (AndroidConstants.CLASS_PARCELABLE.equals(superInterface.getFullyQualifiedName())) {
parcelables.add(type.getFullyQualifiedName());
}
}
// then look in inner types.
IType[] innerTypes = type.getTypes();
for (IType innerType : innerTypes) {
parseType(innerType, parcelables, monitor);
}
}
}

View File

@@ -0,0 +1,65 @@
/*
* 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.project;
import com.android.ide.eclipse.common.project.ExportHelper;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.ui.IObjectActionDelegate;
import org.eclipse.ui.IWorkbenchPart;
public class ExportAction implements IObjectActionDelegate {
private ISelection mSelection;
/**
* @see IObjectActionDelegate#setActivePart(IAction, IWorkbenchPart)
*/
public void setActivePart(IAction action, IWorkbenchPart targetPart) {
}
public void run(IAction action) {
if (mSelection instanceof IStructuredSelection) {
IStructuredSelection selection = (IStructuredSelection)mSelection;
// get the unique selected item.
if (selection.size() == 1) {
Object element = selection.getFirstElement();
// get the project object from it.
IProject project = null;
if (element instanceof IProject) {
project = (IProject) element;
} else if (element instanceof IAdaptable) {
project = (IProject) ((IAdaptable) element).getAdapter(IProject.class);
}
// and finally do the action
if (project != null) {
ExportHelper.exportProject(project);
}
}
}
}
public void selectionChanged(IAction action, ISelection selection) {
this.mSelection = selection;
}
}

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.adt.project.export.ExportWizard;
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 ExportWizardAction 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 export wizard on the current selection.
ExportWizard wizard = new ExportWizard();
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

@@ -0,0 +1,156 @@
/*
* 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.project;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.debug.launching.LaunchConfigDelegate;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.debug.core.DebugPlugin;
import org.eclipse.debug.core.ILaunchConfiguration;
import org.eclipse.debug.core.ILaunchConfigurationType;
import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy;
import org.eclipse.debug.core.ILaunchManager;
import org.eclipse.jdt.launching.IJavaLaunchConfigurationConstants;
import java.util.ArrayList;
/**
* Class to fix the launch configuration of a project if the java package
* defined in the manifest has been changed.<br>
* This fix can be done synchronously, or asynchronously.<br>
* <code>start()</code> will start a thread that will do the fix.<br>
* <code>run()</code> will do the fix in the current thread.<br><br>
* By default, the fix first display a dialog to the user asking if he/she wants to
* do the fix. This can be overriden by calling <code>setDisplayPrompt(false)</code>.
*
*/
public class FixLaunchConfig extends Thread {
private IProject mProject;
private String mOldPackage;
private String mNewPackage;
private boolean mDisplayPrompt = true;
public FixLaunchConfig(IProject project, String oldPackage, String newPackage) {
super();
mProject = project;
mOldPackage = oldPackage;
mNewPackage = newPackage;
}
/**
* Set the display prompt. If true run()/start() first ask the user if he/she wants
* to fix the Launch Config
* @param displayPrompt
*/
public void setDisplayPrompt(boolean displayPrompt) {
mDisplayPrompt = displayPrompt;
}
/**
* Fix the Launch configurations.
*/
@Override
public void run() {
if (mDisplayPrompt) {
// ask the user if he really wants to fix the launch config
boolean res = AdtPlugin.displayPrompt(
"Launch Configuration Update",
"The package definition in the manifest changed.\nDo you want to update your Launch Configuration(s)?");
if (res == false) {
return;
}
}
// get the list of config for the project
String projectName = mProject.getName();
ILaunchConfiguration[] configs = findConfigs(mProject.getName());
// loop through all the config and update the package
for (ILaunchConfiguration config : configs) {
try {
// get the working copy so that we can make changes.
ILaunchConfigurationWorkingCopy copy = config.getWorkingCopy();
// get the attributes for the activity
String activity = config.getAttribute(LaunchConfigDelegate.ATTR_ACTIVITY,
""); //$NON-NLS-1$
// manifests can define activities that are not in the defined package,
// so we need to make sure the activity is inside the old package.
if (activity.startsWith(mOldPackage)) {
// create the new activity
activity = mNewPackage + activity.substring(mOldPackage.length());
// put it in the copy
copy.setAttribute(LaunchConfigDelegate.ATTR_ACTIVITY, activity);
// save the config
copy.doSave();
}
} catch (CoreException e) {
// couldn't get the working copy. we output the error in the console
String msg = String.format("Failed to modify %1$s: %2$s", projectName,
e.getMessage());
AdtPlugin.printErrorToConsole(mProject, msg);
}
}
}
/**
* Looks for and returns all existing Launch Configuration object for a
* specified project.
* @param projectName The name of the project
* @return all the ILaunchConfiguration object. If none are present, an empty array is
* returned.
*/
private static ILaunchConfiguration[] findConfigs(String projectName) {
// get the launch manager
ILaunchManager manager = DebugPlugin.getDefault().getLaunchManager();
// now get the config type for our particular android type.
ILaunchConfigurationType configType = manager.
getLaunchConfigurationType(LaunchConfigDelegate.ANDROID_LAUNCH_TYPE_ID);
// create a temp list to hold all the valid configs
ArrayList<ILaunchConfiguration> list = new ArrayList<ILaunchConfiguration>();
try {
ILaunchConfiguration[] configs = manager.getLaunchConfigurations(configType);
for (ILaunchConfiguration config : configs) {
if (config.getAttribute(
IJavaLaunchConfigurationConstants.ATTR_PROJECT_NAME,
"").equals(projectName)) { //$NON-NLS-1$
list.add(config);
}
}
} catch (CoreException e) {
}
return list.toArray(new ILaunchConfiguration[list.size()]);
}
}

View File

@@ -0,0 +1,136 @@
/*
* 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.project;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.ui.IObjectActionDelegate;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.IWorkbenchWindowActionDelegate;
import java.util.Iterator;
/**
* Action to fix the project properties:
* <ul>
* <li>Make sure the framework archive is present with the link to the java
* doc</li>
* </ul>
*/
public class FixProjectAction implements IObjectActionDelegate {
private ISelection mSelection;
/**
* @see IObjectActionDelegate#setActivePart(IAction, IWorkbenchPart)
*/
public void setActivePart(IAction action, IWorkbenchPart targetPart) {
}
public void run(IAction action) {
if (mSelection instanceof IStructuredSelection) {
for (Iterator<?> it = ((IStructuredSelection) mSelection).iterator();
it.hasNext();) {
Object element = it.next();
IProject project = null;
if (element instanceof IProject) {
project = (IProject) element;
} else if (element instanceof IAdaptable) {
project = (IProject) ((IAdaptable) element)
.getAdapter(IProject.class);
}
if (project != null) {
fixProject(project);
}
}
}
}
public void selectionChanged(IAction action, ISelection selection) {
this.mSelection = selection;
}
private void fixProject(final IProject project) {
new Job("Fix Project Properties") {
@Override
protected IStatus run(IProgressMonitor monitor) {
try {
if (monitor != null) {
monitor.beginTask("Fix Project Properties", 6);
}
ProjectHelper.fixProject(project);
if (monitor != null) {
monitor.worked(1);
}
// fix the nature order to have the proper project icon
ProjectHelper.fixProjectNatureOrder(project);
if (monitor != null) {
monitor.worked(1);
}
// now we fix the builders
AndroidNature.configureResourceManagerBuilder(project);
if (monitor != null) {
monitor.worked(1);
}
AndroidNature.configurePreBuilder(project);
if (monitor != null) {
monitor.worked(1);
}
AndroidNature.configureApkBuilder(project);
if (monitor != null) {
monitor.worked(1);
}
return Status.OK_STATUS;
} catch (JavaModelException e) {
return e.getJavaModelStatus();
} catch (CoreException e) {
return e.getStatus();
} finally {
if (monitor != null) {
monitor.done();
}
}
}
}.schedule();
}
/**
* @see IWorkbenchWindowActionDelegate#init
*/
public void init(IWorkbenchWindow window) {
// pass
}
}

View File

@@ -0,0 +1,105 @@
/*
* 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 com.android.sdklib.SdkConstants;
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(SdkConstants.FD_ASSETS)) {
decorate(decoration, " [Android assets]");
decoration.addOverlay(mDescriptor, IDecoration.TOP_RIGHT);
} else if (name.equals(SdkConstants.FD_RESOURCES)) {
decorate(decoration, " [Android resources]");
decoration.addOverlay(mDescriptor, IDecoration.TOP_RIGHT);
} else if (name.equals(SdkConstants.FD_NATIVE_LIBS)) {
decorate(decoration, " [Native Libraries]");
}
}
}
} 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

@@ -0,0 +1,668 @@
/*
* 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.project;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.project.internal.AndroidClasspathContainerInitializer;
import com.android.ide.eclipse.common.AndroidConstants;
import com.android.ide.eclipse.common.project.AndroidManifestHelper;
import com.android.ide.eclipse.common.project.AndroidManifestParser;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IProjectDescription;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.IncrementalProjectBuilder;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.QualifiedName;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.launching.JavaRuntime;
import java.util.ArrayList;
/**
* Utility class to manipulate Project parameters/properties.
*/
public final class ProjectHelper {
public final static int COMPILER_COMPLIANCE_OK = 0;
public final static int COMPILER_COMPLIANCE_LEVEL = 1;
public final static int COMPILER_COMPLIANCE_SOURCE = 2;
public final static int COMPILER_COMPLIANCE_CODEGEN_TARGET = 3;
/**
* Adds the corresponding source folder to the class path entries.
*
* @param entries The class path entries to read. A copy will be returned.
* @param new_entry The parent source folder to remove.
* @return A new class path entries array.
*/
public static IClasspathEntry[] addEntryToClasspath(
IClasspathEntry[] entries, IClasspathEntry new_entry) {
int n = entries.length;
IClasspathEntry[] newEntries = new IClasspathEntry[n + 1];
System.arraycopy(entries, 0, newEntries, 0, n);
newEntries[n] = new_entry;
return newEntries;
}
/**
* Remove a classpath entry from the array.
* @param entries The class path entries to read. A copy will be returned
* @param index The index to remove.
* @return A new class path entries array.
*/
public static IClasspathEntry[] removeEntryFromClasspath(
IClasspathEntry[] entries, int index) {
int n = entries.length;
IClasspathEntry[] newEntries = new IClasspathEntry[n-1];
// copy the entries before index
System.arraycopy(entries, 0, newEntries, 0, index);
// copy the entries after index
System.arraycopy(entries, index + 1, newEntries, index,
entries.length - index - 1);
return newEntries;
}
/**
* Converts a OS specific path into a path valid for the java doc location
* attributes of a project.
* @param javaDocOSLocation The OS specific path.
* @return a valid path for the java doc location.
*/
public static String getJavaDocPath(String javaDocOSLocation) {
// first thing we do is convert the \ into /
String javaDoc = javaDocOSLocation.replaceAll("\\\\", //$NON-NLS-1$
AndroidConstants.WS_SEP);
// then we add file: at the beginning for unix path, and file:/ for non
// unix path
if (javaDoc.startsWith(AndroidConstants.WS_SEP)) {
return "file:" + javaDoc; //$NON-NLS-1$
}
return "file:/" + javaDoc; //$NON-NLS-1$
}
/**
* Look for a specific classpath entry by full path and return its index.
* @param entries The entry array to search in.
* @param entryPath The OS specific path of the entry.
* @param entryKind The kind of the entry. Accepted values are 0
* (no filter), IClasspathEntry.CPE_LIBRARY, IClasspathEntry.CPE_PROJECT,
* IClasspathEntry.CPE_SOURCE, IClasspathEntry.CPE_VARIABLE,
* and IClasspathEntry.CPE_CONTAINER
* @return the index of the found classpath entry or -1.
*/
public static int findClasspathEntryByPath(IClasspathEntry[] entries,
String entryPath, int entryKind) {
for (int i = 0 ; i < entries.length ; i++) {
IClasspathEntry entry = entries[i];
int kind = entry.getEntryKind();
if (kind == entryKind || entryKind == 0) {
// get the path
IPath path = entry.getPath();
String osPathString = path.toOSString();
if (osPathString.equals(entryPath)) {
return i;
}
}
}
// not found, return bad index.
return -1;
}
/**
* Look for a specific classpath entry for file name only and return its
* index.
* @param entries The entry array to search in.
* @param entryName The filename of the entry.
* @param entryKind The kind of the entry. Accepted values are 0
* (no filter), IClasspathEntry.CPE_LIBRARY, IClasspathEntry.CPE_PROJECT,
* IClasspathEntry.CPE_SOURCE, IClasspathEntry.CPE_VARIABLE,
* and IClasspathEntry.CPE_CONTAINER
* @param startIndex Index where to start the search
* @return the index of the found classpath entry or -1.
*/
public static int findClasspathEntryByName(IClasspathEntry[] entries,
String entryName, int entryKind, int startIndex) {
if (startIndex < 0) {
startIndex = 0;
}
for (int i = startIndex ; i < entries.length ; i++) {
IClasspathEntry entry = entries[i];
int kind = entry.getEntryKind();
if (kind == entryKind || entryKind == 0) {
// get the path
IPath path = entry.getPath();
String name = path.segment(path.segmentCount()-1);
if (name.equals(entryName)) {
return i;
}
}
}
// not found, return bad index.
return -1;
}
/**
* Fix the project. This checks the SDK location.
* @param project The project to fix.
* @throws JavaModelException
*/
public static void fixProject(IProject project) throws JavaModelException {
if (AdtPlugin.getOsSdkFolder().length() == 0) {
AdtPlugin.printToConsole(project, "Unknown SDK Location, project not fixed.");
return;
}
// get a java project
IJavaProject javaProject = JavaCore.create(project);
fixProjectClasspathEntries(javaProject);
}
/**
* Fix the project classpath entries. The method ensures that:
* <ul>
* <li>The project does not reference any old android.zip/android.jar archive.</li>
* <li>The project does not use its output folder as a sourc folder.</li>
* <li>The project does not reference a desktop JRE</li>
* <li>The project references the AndroidClasspathContainer.
* </ul>
* @param javaProject The project to fix.
* @throws JavaModelException
*/
public static void fixProjectClasspathEntries(IJavaProject javaProject)
throws JavaModelException {
// get the project classpath
IClasspathEntry[] entries = javaProject.getRawClasspath();
IClasspathEntry[] oldEntries = entries;
// check if the JRE is set as library
int jreIndex = ProjectHelper.findClasspathEntryByPath(entries, JavaRuntime.JRE_CONTAINER,
IClasspathEntry.CPE_CONTAINER);
if (jreIndex != -1) {
// the project has a JRE included, we remove it
entries = ProjectHelper.removeEntryFromClasspath(entries, jreIndex);
}
// get the output folder
IPath outputFolder = javaProject.getOutputLocation();
boolean foundContainer = false;
for (int i = 0 ; i < entries.length ;) {
// get the entry and kind
IClasspathEntry entry = entries[i];
int kind = entry.getEntryKind();
if (kind == IClasspathEntry.CPE_SOURCE) {
IPath path = entry.getPath();
if (path.equals(outputFolder)) {
entries = ProjectHelper.removeEntryFromClasspath(entries, i);
// continue, to skip the i++;
continue;
}
} else if (kind == IClasspathEntry.CPE_CONTAINER) {
if (AndroidClasspathContainerInitializer.checkPath(entry.getPath())) {
foundContainer = true;
}
}
i++;
}
// if the framework container is not there, we add it
if (foundContainer == false) {
// add the android container to the array
entries = ProjectHelper.addEntryToClasspath(entries,
AndroidClasspathContainerInitializer.getContainerEntry());
}
// set the new list of entries to the project
if (entries != oldEntries) {
javaProject.setRawClasspath(entries, new NullProgressMonitor());
}
// If needed, check and fix compiler compliance and source compatibility
ProjectHelper.checkAndFixCompilerCompliance(javaProject);
}
/**
* Checks the project compiler compliance level is supported.
* @param javaProject The project to check
* @return <ul>
* <li><code>COMPILER_COMPLIANCE_OK</code> if the project is properly configured</li>
* <li><code>COMPILER_COMPLIANCE_LEVEL</code> for unsupported compiler level</li>
* <li><code>COMPILER_COMPLIANCE_SOURCE</code> for unsupported source compatibility</li>
* <li><code>COMPILER_COMPLIANCE_CODEGEN_TARGET</code> for unsupported .class format</li>
* </ul>
*/
public static final int checkCompilerCompliance(IJavaProject javaProject) {
// get the project compliance level option
String compliance = javaProject.getOption(JavaCore.COMPILER_COMPLIANCE, true);
// check it against a list of valid compliance level strings.
if (checkCompliance(compliance) == false) {
// if we didn't find the proper compliance level, we return an error
return COMPILER_COMPLIANCE_LEVEL;
}
// otherwise we check source compatibility
String source = javaProject.getOption(JavaCore.COMPILER_SOURCE, true);
// check it against a list of valid compliance level strings.
if (checkCompliance(source) == false) {
// if we didn't find the proper compliance level, we return an error
return COMPILER_COMPLIANCE_SOURCE;
}
// otherwise check codegen level
String codeGen = javaProject.getOption(JavaCore.COMPILER_CODEGEN_TARGET_PLATFORM, true);
// check it against a list of valid compliance level strings.
if (checkCompliance(codeGen) == false) {
// if we didn't find the proper compliance level, we return an error
return COMPILER_COMPLIANCE_CODEGEN_TARGET;
}
return COMPILER_COMPLIANCE_OK;
}
/**
* Checks the project compiler compliance level is supported.
* @param project The project to check
* @return <ul>
* <li><code>COMPILER_COMPLIANCE_OK</code> if the project is properly configured</li>
* <li><code>COMPILER_COMPLIANCE_LEVEL</code> for unsupported compiler level</li>
* <li><code>COMPILER_COMPLIANCE_SOURCE</code> for unsupported source compatibility</li>
* <li><code>COMPILER_COMPLIANCE_CODEGEN_TARGET</code> for unsupported .class format</li>
* </ul>
*/
public static final int checkCompilerCompliance(IProject project) {
// get the java project from the IProject resource object
IJavaProject javaProject = JavaCore.create(project);
// check and return the result.
return checkCompilerCompliance(javaProject);
}
/**
* Checks, and fixes if needed, the compiler compliance level, and the source compatibility
* level
* @param project The project to check and fix.
*/
public static final void checkAndFixCompilerCompliance(IProject project) {
// get the java project from the IProject resource object
IJavaProject javaProject = JavaCore.create(project);
// Now we check the compiler compliance level and make sure it is valid
checkAndFixCompilerCompliance(javaProject);
}
/**
* Checks, and fixes if needed, the compiler compliance level, and the source compatibility
* level
* @param javaProject The Java project to check and fix.
*/
public static final void checkAndFixCompilerCompliance(IJavaProject javaProject) {
if (checkCompilerCompliance(javaProject) != COMPILER_COMPLIANCE_OK) {
// setup the preferred compiler compliance level.
javaProject.setOption(JavaCore.COMPILER_COMPLIANCE,
AndroidConstants.COMPILER_COMPLIANCE_PREFERRED);
javaProject.setOption(JavaCore.COMPILER_SOURCE,
AndroidConstants.COMPILER_COMPLIANCE_PREFERRED);
javaProject.setOption(JavaCore.COMPILER_CODEGEN_TARGET_PLATFORM,
AndroidConstants.COMPILER_COMPLIANCE_PREFERRED);
// clean the project to make sure we recompile
try {
javaProject.getProject().build(IncrementalProjectBuilder.CLEAN_BUILD,
new NullProgressMonitor());
} catch (CoreException e) {
AdtPlugin.printErrorToConsole(javaProject.getProject(),
"Project compiler settings changed. Clean your project.");
}
}
}
/**
* Returns a {@link IProject} by its running application name, as it returned by the AVD.
* <p/>
* <var>applicationName</var> will in most case be the package declared in the manifest, but
* can, in some cases, be a custom process name declared in the manifest, in the
* <code>application</code>, <code>activity</code>, <code>receiver</code>, or
* <code>service</code> nodes.
* @param applicationName The application name.
* @return a project or <code>null</code> if no matching project were found.
*/
public static IProject findAndroidProjectByAppName(String applicationName) {
// Get the list of project for the current workspace
IWorkspace workspace = ResourcesPlugin.getWorkspace();
IProject[] projects = workspace.getRoot().getProjects();
// look for a project that matches the packageName of the app
// we're trying to debug
for (IProject p : projects) {
if (p.isOpen()) {
try {
if (p.hasNature(AndroidConstants.NATURE) == false) {
// ignore non android projects
continue;
}
} catch (CoreException e) {
// failed to get the nature? skip project.
continue;
}
AndroidManifestHelper androidManifest = new AndroidManifestHelper(p);
// check that there is indeed a manifest file.
if (androidManifest.getManifestIFile() == null) {
// no file? skip this project.
continue;
}
AndroidManifestParser parser = null;
try {
parser = AndroidManifestParser.parseForData(
androidManifest.getManifestIFile());
} catch (CoreException e) {
// skip this project.
continue;
}
String manifestPackage = parser.getPackage();
if (manifestPackage != null && manifestPackage.equals(applicationName)) {
// this is the project we were looking for!
return p;
} else {
// if the package and application name don't match,
// we look for other possible process names declared in the manifest.
String[] processes = parser.getProcesses();
for (String process : processes) {
if (process.equals(applicationName)) {
return p;
}
}
}
}
}
return null;
}
public static void fixProjectNatureOrder(IProject project) throws CoreException {
IProjectDescription description = project.getDescription();
String[] natures = description.getNatureIds();
// if the android nature is not the first one, we reorder them
if (AndroidConstants.NATURE.equals(natures[0]) == false) {
// look for the index
for (int i = 0 ; i < natures.length ; i++) {
if (AndroidConstants.NATURE.equals(natures[i])) {
// if we try to just reorder the array in one pass, this doesn't do
// anything. I guess JDT check that we are actually adding/removing nature.
// So, first we'll remove the android nature, and then add it back.
// remove the android nature
removeNature(project, AndroidConstants.NATURE);
// now add it back at the first index.
description = project.getDescription();
natures = description.getNatureIds();
String[] newNatures = new String[natures.length + 1];
// first one is android
newNatures[0] = AndroidConstants.NATURE;
// next the rest that was before the android nature
System.arraycopy(natures, 0, newNatures, 1, natures.length);
// set the new natures
description.setNatureIds(newNatures);
project.setDescription(description, null);
// and stop
break;
}
}
}
}
/**
* Removes a specific nature from a project.
* @param project The project to remove the nature from.
* @param nature The nature id to remove.
* @throws CoreException
*/
public static void removeNature(IProject project, String nature) throws CoreException {
IProjectDescription description = project.getDescription();
String[] natures = description.getNatureIds();
// check if the project already has the android nature.
for (int i = 0; i < natures.length; ++i) {
if (nature.equals(natures[i])) {
String[] newNatures = new String[natures.length - 1];
if (i > 0) {
System.arraycopy(natures, 0, newNatures, 0, i);
}
System.arraycopy(natures, i + 1, newNatures, i, natures.length - i - 1);
description.setNatureIds(newNatures);
project.setDescription(description, null);
return;
}
}
}
/**
* Returns if the project has error level markers.
* @param includeReferencedProjects flag to also test the referenced projects.
* @throws CoreException
*/
public static boolean hasError(IProject project, boolean includeReferencedProjects)
throws CoreException {
IMarker[] markers = project.findMarkers(IMarker.PROBLEM, true, IResource.DEPTH_INFINITE);
if (markers != null && markers.length > 0) {
// the project has marker(s). even though they are "problem" we
// don't know their severity. so we loop on them and figure if they
// are warnings or errors
for (IMarker m : markers) {
int s = m.getAttribute(IMarker.SEVERITY, -1);
if (s == IMarker.SEVERITY_ERROR) {
return true;
}
}
}
// test the referenced projects if needed.
if (includeReferencedProjects) {
IProject[] projects = getReferencedProjects(project);
for (IProject p : projects) {
if (hasError(p, false)) {
return true;
}
}
}
return false;
}
/**
* Saves a String property into the persistent storage of a resource.
* @param resource The resource into which the string value is saved.
* @param propertyName the name of the property. The id of the plugin is added to this string.
* @param value the value to save
* @return true if the save succeeded.
*/
public static boolean saveStringProperty(IResource resource, String propertyName,
String value) {
QualifiedName qname = new QualifiedName(AdtPlugin.PLUGIN_ID, propertyName);
try {
resource.setPersistentProperty(qname, value);
} catch (CoreException e) {
return false;
}
return true;
}
/**
* Loads a String property from the persistent storage of a resource.
* @param resource The resource from which the string value is loaded.
* @param propertyName the name of the property. The id of the plugin is added to this string.
* @return the property value or null if it was not found.
*/
public static String loadStringProperty(IResource resource, String propertyName) {
QualifiedName qname = new QualifiedName(AdtPlugin.PLUGIN_ID, propertyName);
try {
String value = resource.getPersistentProperty(qname);
return value;
} catch (CoreException e) {
return null;
}
}
/**
* Saves a property into the persistent storage of a resource.
* @param resource The resource into which the boolean value is saved.
* @param propertyName the name of the property. The id of the plugin is added to this string.
* @param value the value to save
* @return true if the save succeeded.
*/
public static boolean saveBooleanProperty(IResource resource, String propertyName,
boolean value) {
return saveStringProperty(resource, propertyName, Boolean.toString(value));
}
/**
* Loads a boolean property from the persistent storage of the project.
* @param resource The resource from which the boolean value is loaded.
* @param propertyName the name of the property. The id of the plugin is added to this string.
* @param defaultValue The default value to return if the property was not found.
* @return the property value or the default value if the property was not found.
*/
public static boolean loadBooleanProperty(IResource resource, String propertyName,
boolean defaultValue) {
String value = loadStringProperty(resource, propertyName);
if (value != null) {
return Boolean.parseBoolean(value);
}
return defaultValue;
}
/**
* Saves the path of a resource into the persistent storate of the project.
* @param resource The resource into which the resource path is saved.
* @param propertyName the name of the property. The id of the plugin is added to this string.
* @param value The resource to save. It's its path that is actually stored. If null, an
* empty string is stored.
* @return true if the save succeeded
*/
public static boolean saveResourceProperty(IResource resource, String propertyName,
IResource value) {
if (value != null) {
IPath iPath = value.getProjectRelativePath();
return saveStringProperty(resource, propertyName, iPath.toString());
}
return saveStringProperty(resource, propertyName, ""); //$NON-NLS-1$
}
/**
* Loads the path of a resource from the persistent storage of the project, and returns the
* corresponding IResource object, if it exists in the same project as <code>resource</code>.
* @param resource The resource from which the resource path is loaded.
* @param propertyName the name of the property. The id of the plugin is added to this string.
* @return The corresponding IResource object (or children interface) or null
*/
public static IResource loadResourceProperty(IResource resource, String propertyName) {
String value = loadStringProperty(resource, propertyName);
if (value != null && value.length() > 0) {
return resource.getProject().findMember(value);
}
return null;
}
/**
* Returns the list of referenced project that are opened and Java projects.
* @param project
* @return list of opened referenced java project.
* @throws CoreException
*/
public static IProject[] getReferencedProjects(IProject project) throws CoreException {
IProject[] projects = project.getReferencedProjects();
ArrayList<IProject> list = new ArrayList<IProject>();
for (IProject p : projects) {
if (p.isOpen() && p.hasNature(JavaCore.NATURE_ID)) {
list.add(p);
}
}
return list.toArray(new IProject[list.size()]);
}
/**
* Checks a Java project compiler level option against a list of supported versions.
* @param optionValue the Compiler level option.
* @return true if the option value is supproted.
*/
private static boolean checkCompliance(String optionValue) {
for (String s : AndroidConstants.COMPILER_COMPLIANCE) {
if (s != null && s.equals(optionValue)) {
return true;
}
}
return false;
}
}

View File

@@ -0,0 +1,501 @@
/*
* 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.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;
import org.eclipse.core.runtime.IAdaptable;
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 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_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$
static final String PROPERTY_DESTINATION = "destination"; //$NON-NLS-1$
/**
* Base page class for the ExportWizard page. This class add the {@link #onShow()} callback.
*/
static abstract class ExportWizardPage extends WizardPage {
/** 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);
}
abstract void onShow();
@Override
public void setVisible(boolean visible) {
super.setVisible(visible);
if (visible) {
onShow();
mProjectDataChanged = 0;
}
}
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[5];
private IProject mProject;
private String mKeystore;
private String mKeystorePassword;
private boolean mKeystoreCreationMode;
private String mKeyAlias;
private String mKeyPassword;
private int mValidity;
private String mDName;
private PrivateKey mPrivateKey;
private X509Certificate mCertificate;
private String mDestinationPath;
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");
setImageDescriptor();
}
@Override
public void addPages() {
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 {
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;
}
}
// 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) {
displayError(e);
} catch (NoSuchAlgorithmException e) {
displayError(e);
} catch (IOException e) {
displayError(e);
} catch (GeneralSecurityException e) {
displayError(e);
} catch (KeytoolException e) {
displayError(e);
}
return false;
}
@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)
|| mKeystoreCreationMode || mKeyCreationMode) &&
mDestinationPath != null;
}
/*
* (non-Javadoc)
* @see org.eclipse.ui.IWorkbenchWizard#init(org.eclipse.ui.IWorkbench, org.eclipse.jface.viewers.IStructuredSelection)
*/
public void init(IWorkbench workbench, IStructuredSelection selection) {
// get the project from the selection
Object selected = selection.getFirstElement();
if (selected instanceof IProject) {
mProject = (IProject)selected;
} else if (selected instanceof IAdaptable) {
IResource r = (IResource)((IAdaptable)selected).getAdapter(IResource.class);
if (r != null) {
mProject = r.getProject();
}
}
}
ExportWizardPage getKeystoreSelectionPage() {
return mKeystoreSelectionPage;
}
ExportWizardPage getKeyCreationPage() {
return mKeyCreationPage;
}
ExportWizardPage getKeySelectionPage() {
return mKeySelectionPage;
}
ExportWizardPage getKeyCheckPage() {
return mKeyCheckPage;
}
/**
* Returns an image descriptor for the wizard logo.
*/
private void setImageDescriptor() {
ImageDescriptor desc = AdtPlugin.getImageDescriptor(PROJECT_LOGO_LARGE);
setDefaultPageImageDescriptor(desc);
}
IProject getProject() {
return mProject;
}
void setProject(IProject project, String apkFilePath, String filename) {
mProject = project;
mApkFilePath = apkFilePath;
mApkFileName = filename;
updatePageOnChange(ExportWizardPage.DATA_PROJECT);
}
String getApkFilename() {
return mApkFileName;
}
void setKeystore(String path) {
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 setKeyPassword(String password) {
mKeyPassword = password;
mPrivateKey = null;
mCertificate = null;
updatePageOnChange(ExportWizardPage.DATA_KEY);
}
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;
}
void setDestination(String path) {
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 t.getClass().getCanonicalName();
}
return message;
}
}

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

@@ -0,0 +1,260 @@
/*
* 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.Composite;
import org.eclipse.swt.widgets.FileDialog;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Text;
import java.io.File;
/**
* Keystore selection page. This page allows to choose to create a new keystore or use an
* existing one.
*/
final class KeystoreSelectionPage extends ExportWizardPage {
private final ExportWizard mWizard;
private Button mUseExistingKeystore;
private Button mCreateKeystore;
private Text mKeystore;
private Text mKeystorePassword;
private Label mConfirmLabel;
private Text mKeystorePassword2;
private boolean mDisableOnChange = false;
protected KeystoreSelectionPage(ExportWizard wizard, String pageName) {
super(pageName);
mWizard = wizard;
setTitle("Keystore 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;
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);
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);
browseButton.setText("Browse...");
browseButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
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) {
mKeystore.setText(fileName);
}
}
});
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;
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();
}
});
mKeystorePassword.addModifyListener(new ModifyListener() {
public void modifyText(ModifyEvent e) {
mWizard.setKeystorePassword(mKeystorePassword.getText());
onChange();
}
});
mKeystorePassword2.addModifyListener(new ModifyListener() {
public void modifyText(ModifyEvent e) {
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 ((mProjectDataChanged & DATA_PROJECT) != 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 keystore = ProjectHelper.loadStringProperty(project,
ExportWizard.PROPERTY_KEYSTORE);
if (keystore != null) {
mKeystore.setText(keystore);
}
// reset the passwords
mKeystorePassword.setText(""); //$NON-NLS-1$
mKeystorePassword2.setText(""); //$NON-NLS-1$
// enable onChange, and call it to display errors and enable/disable pageCompleted.
mDisableOnChange = false;
onChange();
}
}
/**
* Handles changes and update the error message and calls {@link #setPageComplete(boolean)}.
*/
private void onChange() {
if (mDisableOnChange) {
return;
}
setErrorMessage(null);
setMessage(null);
boolean createStore = !mUseExistingKeystore.getSelection();
// checks the keystore path is non null.
String keystore = mKeystore.getText().trim();
if (keystore.length() == 0) {
setErrorMessage("Enter path to keystore.");
setPageComplete(false);
return;
} else {
File f = new File(keystore);
if (f.exists() == false) {
if (createStore == false) {
setErrorMessage("Keystore does not exist.");
setPageComplete(false);
return;
}
} else if (f.isDirectory()) {
setErrorMessage("Keystore path is a directory.");
setPageComplete(false);
return;
} else if (f.isFile()) {
if (createStore) {
setErrorMessage("File already exists.");
setPageComplete(false);
return;
}
}
}
String value = mKeystorePassword.getText();
if (value.length() == 0) {
setErrorMessage("Enter keystore password.");
setPageComplete(false);
return;
} 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

@@ -0,0 +1,322 @@
/*
* 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.AdtPlugin;
import com.android.ide.eclipse.adt.project.ProjectHelper;
import com.android.ide.eclipse.adt.project.export.ExportWizard.ExportWizardPage;
import com.android.ide.eclipse.common.AndroidConstants;
import com.android.ide.eclipse.common.project.AndroidManifestParser;
import com.android.ide.eclipse.common.project.BaseProjectHelper;
import com.android.ide.eclipse.common.project.ProjectChooserHelper;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Text;
import java.io.File;
/**
* First Export Wizard Page. Display warning/errors.
*/
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$
private final ExportWizard mWizard;
private Display mDisplay;
private Image mError;
private Image mWarning;
private boolean mHasMessage = false;
private Composite mTopComposite;
private Composite mErrorComposite;
private Text mProjectText;
private ProjectChooserHelper mProjectChooserHelper;
private boolean mFirstOnShow = true;
protected ProjectCheckPage(ExportWizard wizard, String pageName) {
super(pageName);
mWizard = wizard;
setTitle("Project Checks");
setDescription("Performs a set of checks to make sure the application can be exported.");
}
public void createControl(Composite parent) {
mProjectChooserHelper = new ProjectChooserHelper(parent.getShell());
mDisplay = parent.getDisplay();
GridLayout gl = null;
GridData gd = null;
mTopComposite = new Composite(parent, SWT.NONE);
mTopComposite.setLayoutData(new GridData(GridData.FILL_BOTH));
mTopComposite.setLayout(new GridLayout(1, false));
// composite for the project selection.
Composite projectComposite = new Composite(mTopComposite, SWT.NONE);
projectComposite.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
projectComposite.setLayout(gl = new GridLayout(3, false));
gl.marginHeight = gl.marginWidth = 0;
Label label = new Label(projectComposite, SWT.NONE);
label.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL));
gd.horizontalSpan = 3;
label.setText("Select the project to export:");
new Label(projectComposite, SWT.NONE).setText("Project:");
mProjectText = new Text(projectComposite, SWT.BORDER);
mProjectText.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL));
mProjectText.addModifyListener(new ModifyListener() {
public void modifyText(ModifyEvent e) {
handleProjectNameChange();
}
});
Button browseButton = new Button(projectComposite, SWT.PUSH);
browseButton.setText("Browse...");
browseButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
IJavaProject javaProject = mProjectChooserHelper.chooseJavaProject(
mProjectText.getText().trim());
if (javaProject != null) {
IProject project = javaProject.getProject();
// set the new name in the text field. The modify listener will take
// care of updating the status and the ExportWizard object.
mProjectText.setText(project.getName());
}
}
});
setControl(mTopComposite);
}
@Override
void onShow() {
if (mFirstOnShow) {
// get the project and init the ui
IProject project = mWizard.getProject();
if (project != null) {
mProjectText.setText(project.getName());
}
mFirstOnShow = false;
}
}
private void buildErrorUi(IProject project) {
// Show description the first time
setErrorMessage(null);
setMessage(null);
setPageComplete(true);
mHasMessage = false;
// composite parent for the warning/error
GridLayout gl = null;
mErrorComposite = new Composite(mTopComposite, SWT.NONE);
mErrorComposite.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
gl = new GridLayout(2, false);
gl.marginHeight = gl.marginWidth = 0;
gl.verticalSpacing *= 3; // more spacing than normal.
mErrorComposite.setLayout(gl);
if (project == null) {
setErrorMessage("Select project to export.");
mHasMessage = true;
} else {
try {
if (project.hasNature(AndroidConstants.NATURE) == false) {
addError(mErrorComposite, "Project is not an Android project.");
} else {
// check for errors
if (ProjectHelper.hasError(project, true)) {
addError(mErrorComposite, "Project has compilation error(s)");
}
// check the project output
IFolder outputIFolder = BaseProjectHelper.getOutputFolder(project);
if (outputIFolder != null) {
String outputOsPath = outputIFolder.getLocation().toOSString();
String apkFilePath = outputOsPath + File.separator + project.getName() +
AndroidConstants.DOT_ANDROID_PACKAGE;
File f = new File(apkFilePath);
if (f.isFile() == false) {
addError(mErrorComposite,
String.format("%1$s/%2$s/%1$s%3$s does not exists!",
project.getName(),
outputIFolder.getName(),
AndroidConstants.DOT_ANDROID_PACKAGE));
}
} else {
addError(mErrorComposite,
"Unable to get the output folder of the project!");
}
// project is an android project, we check the debuggable attribute.
AndroidManifestParser manifestParser = AndroidManifestParser.parse(
BaseProjectHelper.getJavaProject(project), null /* errorListener */,
true /* gatherData */, false /* markErrors */);
Boolean debuggable = manifestParser.getDebuggable();
if (debuggable != null && debuggable == Boolean.TRUE) {
addWarning(mErrorComposite,
"The manifest 'debuggable' attribute is set to true.\nYou should set it to false for applications that you release to the public.");
}
// check for mapview stuff
}
} catch (CoreException e) {
// unable to access nature
addError(mErrorComposite, "Unable to get project nature");
}
}
if (mHasMessage == false) {
Label label = new Label(mErrorComposite, SWT.NONE);
GridData gd = new GridData(GridData.FILL_HORIZONTAL);
gd.horizontalSpan = 2;
label.setLayoutData(gd);
label.setText("No errors found. Click Next.");
}
mTopComposite.layout();
}
/**
* Adds an error label to a {@link Composite} object.
* @param parent the Composite parent.
* @param message the error message.
*/
private void addError(Composite parent, String message) {
if (mError == null) {
mError = AdtPlugin.getImageLoader().loadImage(IMG_ERROR, mDisplay);
}
new Label(parent, SWT.NONE).setImage(mError);
Label label = new Label(parent, SWT.NONE);
label.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
label.setText(message);
setErrorMessage("Application cannot be exported due to the error(s) below.");
setPageComplete(false);
mHasMessage = true;
}
/**
* Adds a warning label to a {@link Composite} object.
* @param parent the Composite parent.
* @param message the warning message.
*/
private void addWarning(Composite parent, String message) {
if (mWarning == null) {
mWarning = AdtPlugin.getImageLoader().loadImage(IMG_WARNING, mDisplay);
}
new Label(parent, SWT.NONE).setImage(mWarning);
Label label = new Label(parent, SWT.NONE);
label.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
label.setText(message);
mHasMessage = true;
}
/**
* Checks the parameters for correctness, and update the error message and buttons.
*/
private void handleProjectNameChange() {
setPageComplete(false);
if (mErrorComposite != null) {
mErrorComposite.dispose();
mErrorComposite = null;
}
// update the wizard with the new project
mWizard.setProject(null, null, null);
//test the project name first!
String text = mProjectText.getText().trim();
if (text.length() == 0) {
setErrorMessage("Select project to export.");
} else if (text.matches("[a-zA-Z0-9_ \\.-]+") == false) {
setErrorMessage("Project name contains unsupported characters!");
} else {
IJavaProject[] projects = mProjectChooserHelper.getAndroidProjects(null);
IProject found = null;
for (IJavaProject javaProject : projects) {
if (javaProject.getProject().getName().equals(text)) {
found = javaProject.getProject();
break;
}
}
if (found != null) {
setErrorMessage(null);
// update the wizard with the new project
setApkFilePathInWizard(found);
// now rebuild the error ui.
buildErrorUi(found);
} else {
setErrorMessage(String.format("There is no android project named '%1$s'",
text));
}
}
}
private void setApkFilePathInWizard(IProject project) {
if (project != null) {
IFolder outputIFolder = BaseProjectHelper.getOutputFolder(project);
if (outputIFolder != null) {
String outputOsPath = outputIFolder.getLocation().toOSString();
String apkFilePath = outputOsPath + File.separator + project.getName() +
AndroidConstants.DOT_ANDROID_PACKAGE;
File f = new File(apkFilePath);
if (f.isFile()) {
mWizard.setProject(project, apkFilePath, f.getName());
return;
}
}
}
mWizard.setProject(null, null, null);
}
}

View File

@@ -0,0 +1,60 @@
/*
* 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.project.internal;
import org.eclipse.core.runtime.IPath;
import org.eclipse.jdt.core.IClasspathContainer;
import org.eclipse.jdt.core.IClasspathEntry;
/**
* Classpath container for the Android projects.
*/
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 entries the entries representing the android framework and optional libraries.
* @param path the path containing the classpath container id.
* @param name the name of the container to display.
*/
AndroidClasspathContainer(IClasspathEntry[] entries, IPath path, String name) {
mClasspathEntry = entries;
mContainerPath = path;
mName = name;
}
public IClasspathEntry[] getClasspathEntries() {
return mClasspathEntry;
}
public String getDescription() {
return mName;
}
public int getKind() {
return IClasspathContainer.K_DEFAULT_SYSTEM;
}
public IPath getPath() {
return mContainerPath;
}
}

View File

@@ -0,0 +1,627 @@
/*
* 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.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 com.android.sdklib.IAndroidTarget.IOptionalLibrary;
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.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jdt.core.ClasspathContainerInitializer;
import org.eclipse.jdt.core.IAccessRule;
import org.eclipse.jdt.core.IClasspathAttribute;
import org.eclipse.jdt.core.IClasspathContainer;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import java.io.File;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.regex.Pattern;
/**
* Classpath container initializer responsible for binding {@link AndroidClasspathContainer} to
* {@link IProject}s. This removes the hard-coded path to the android.jar.
*/
public class AndroidClasspathContainerInitializer extends ClasspathContainerInitializer {
/** 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$
/** path separator to store multiple paths in a single property. This is guaranteed to not
* be in a path.
*/
private final static String PATH_SEPARATOR = "\u001C"; //$NON-NLS-1$
private final static String PROPERTY_CONTAINER_CACHE = "androidContainerCache"; //$NON-NLS-1$
private final static String PROPERTY_TARGET_NAME = "androidTargetCache"; //$NON-NLS-1$
private final static String CACHE_VERSION = "01"; //$NON-NLS-1$
private final static String CACHE_VERSION_SEP = CACHE_VERSION + PATH_SEPARATOR;
private final static int PATH_ANDROID_JAR = 0;
private final static int PATH_ANDROID_SRC = 1;
private final static int PATH_ANDROID_DOCS = 2;
private final static int PATH_ANDROID_OPT_DOCS = 3;
public AndroidClasspathContainerInitializer() {
// pass
}
/**
* Binds a classpath container to a {@link IClasspathContainer} for a given project,
* or silently fails if unable to do so.
* @param containerPath the container path that is the container id.
* @param project the project to bind
*/
@Override
public void initialize(IPath containerPath, IJavaProject project) throws CoreException {
if (CONTAINER_ID.equals(containerPath.toString())) {
JavaCore.setClasspathContainer(new Path(CONTAINER_ID),
new IJavaProject[] { project },
new IClasspathContainer[] { allocateAndroidContainer(project) },
new NullProgressMonitor());
}
}
/**
* Creates a new {@link IClasspathEntry} of type {@link IClasspathEntry#CPE_CONTAINER}
* linking to the Android Framework.
*/
public static IClasspathEntry getContainerEntry() {
return JavaCore.newContainerEntry(new Path(CONTAINER_ID));
}
/**
* Checks the {@link IPath} objects against the android framework container id and
* returns <code>true</code> if they are identical.
* @param path the <code>IPath</code> to check.
*/
public static boolean checkPath(IPath path) {
return CONTAINER_ID.equals(path.toString());
}
/**
* Updates the {@link IJavaProject} objects with new android framework container. This forces
* JDT to recompile them.
* @param androidProjects the projects to update.
* @return <code>true</code> if success, <code>false</code> otherwise.
*/
public static boolean updateProjects(IJavaProject[] androidProjects) {
try {
// 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.
int projectCount = androidProjects.length;
IClasspathContainer[] containers = new IClasspathContainer[projectCount];
for (int i = 0 ; i < projectCount; i++) {
containers[i] = allocateAndroidContainer(androidProjects[i]);
}
// give each project their new container in one call.
JavaCore.setClasspathContainer(
new Path(CONTAINER_ID),
androidProjects, containers, new NullProgressMonitor());
return true;
} catch (JavaModelException e) {
return false;
}
}
/**
* Allocates and returns an {@link AndroidClasspathContainer} object with the proper
* path to the framework jar file.
* @param javaProject The java project that will receive the container.
*/
private static IClasspathContainer allocateAndroidContainer(IJavaProject javaProject) {
final IProject iProject = javaProject.getProject();
String markerMessage = null;
boolean outputToConsole = true;
try {
AdtPlugin plugin = AdtPlugin.getDefault();
// get the lock object for project manipulation during SDK load.
Object lock = plugin.getSdkLockObject();
synchronized (lock) {
boolean sdkIsLoaded = plugin.getSdkLoadStatus() == LoadStatus.LOADED;
// 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 = target.getFullName();
return new AndroidClasspathContainer(
createClasspathEntries(iProject, target, targetName),
new Path(CONTAINER_ID), targetName);
}
// In case of error, we'll try different thing to provide the best error message
// possible.
// Get the project's target's hash string (if it exists)
String hashString = Sdk.getProjectTargetHashString(iProject);
if (hashString == null || hashString.length() == 0) {
// if there is no hash string we only show this if the SDK is loaded.
// For a project opened at start-up with no target, this would be displayed
// twice, once when the project is opened, and once after the SDK has
// finished loading.
// By testing the sdk is loaded, we only show this once in the console.
if (sdkIsLoaded) {
markerMessage = String.format(
"Project has no target set. Edit the project properties to set one.");
}
} else if (sdkIsLoaded) {
markerMessage = 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.
// We check if there is a cache of the needed information.
AndroidClasspathContainer container = getContainerFromCache(iProject);
if (container == null) {
// either the cache was wrong (ie folder does not exists anymore), or
// there was no cache. In this case we need to make sure the project
// is resolved again after the SDK is loaded.
plugin.setProjectToResolve(javaProject);
markerMessage = 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;
} else {
// we created a container from the cache, so we register the project
// to be checked for cache validity once the SDK is loaded
plugin.setProjectToCheck(javaProject);
// and return the container
return container;
}
}
// return a dummy container to replace the one we may have had before.
// It'll be replaced by the real when if/when the target is resolved if/when the
// SDK finishes loading.
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;
}
};
}
} finally {
if (markerMessage != null) {
// log the error and put the marker on the project if we can.
if (outputToConsole) {
AdtPlugin.printBuildToConsole(AdtConstants.BUILD_ALWAYS, iProject,
markerMessage);
}
try {
BaseProjectHelper.addMarker(iProject, AdtConstants.MARKER_TARGET, markerMessage,
-1, IMarker.SEVERITY_ERROR, IMarker.PRIORITY_HIGH);
} catch (CoreException e) {
// In some cases, the workspace may be locked for modification when we
// pass here.
// We schedule a new job to put the marker after.
final String fmessage = markerMessage;
Job markerJob = new Job("Android SDK: Resolving error markers") {
@Override
protected IStatus run(IProgressMonitor monitor) {
try {
BaseProjectHelper.addMarker(iProject, AdtConstants.MARKER_TARGET,
fmessage, -1, IMarker.SEVERITY_ERROR,
IMarker.PRIORITY_HIGH);
} catch (CoreException e2) {
return e2.getStatus();
}
return Status.OK_STATUS;
}
};
// build jobs are run after other interactive jobs
markerJob.setPriority(Job.BUILD);
markerJob.schedule();
}
} else {
// no error, remove potential MARKER_TARGETs.
try {
if (iProject.exists()) {
iProject.deleteMarkers(AdtConstants.MARKER_TARGET, true,
IResource.DEPTH_INFINITE);
}
} catch (CoreException ce) {
// In some cases, the workspace may be locked for modification when we pass
// here, so we schedule a new job to put the marker after.
Job markerJob = new Job("Android SDK: Resolving error markers") {
@Override
protected IStatus run(IProgressMonitor monitor) {
try {
iProject.deleteMarkers(AdtConstants.MARKER_TARGET, true,
IResource.DEPTH_INFINITE);
} catch (CoreException e2) {
return e2.getStatus();
}
return Status.OK_STATUS;
}
};
// build jobs are run after other interactive jobs
markerJob.setPriority(Job.BUILD);
markerJob.schedule();
}
}
}
}
/**
* Creates and returns an array of {@link IClasspathEntry} objects for the android
* framework and optional libraries.
* <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.
* The method also stores the paths used to create the entries in the project persistent
* properties. A new {@link AndroidClasspathContainer} can be created from the stored path
* using the {@link #getContainerFromCache(IProject)} method.
* @param project
* @param target The target that contains the libraries.
* @param targetName
*/
private static IClasspathEntry[] createClasspathEntries(IProject project,
IAndroidTarget target, String targetName) {
// get the path from the target
String[] paths = getTargetPaths(target);
// create the classpath entry from the paths
IClasspathEntry[] entries = createClasspathEntriesFromPaths(paths);
// paths now contains all the path required to recreate the IClasspathEntry with no
// target info. We encode them in a single string, with each path separated by
// OS path separator.
StringBuilder sb = new StringBuilder(CACHE_VERSION);
for (String p : paths) {
sb.append(PATH_SEPARATOR);
sb.append(p);
}
// store this in a project persistent property
ProjectHelper.saveStringProperty(project, PROPERTY_CONTAINER_CACHE, sb.toString());
ProjectHelper.saveStringProperty(project, PROPERTY_TARGET_NAME, targetName);
return entries;
}
/**
* Generates an {@link AndroidClasspathContainer} from the project cache, if possible.
*/
private static AndroidClasspathContainer getContainerFromCache(IProject project) {
// get the cached info from the project persistent properties.
String cache = ProjectHelper.loadStringProperty(project, PROPERTY_CONTAINER_CACHE);
String targetNameCache = ProjectHelper.loadStringProperty(project, PROPERTY_TARGET_NAME);
if (cache == null || targetNameCache == null) {
return null;
}
// the first 2 chars must match CACHE_VERSION. The 3rd char is the normal separator.
if (cache.startsWith(CACHE_VERSION_SEP) == false) {
return null;
}
cache = cache.substring(CACHE_VERSION_SEP.length());
// the cache contains multiple paths, separated by a character guaranteed to not be in
// the path (\u001C).
// The first 3 are for android.jar (jar, source, doc), the rest are for the optional
// libraries and should contain at least one doc and a jar (if there are any libraries).
// Therefore, the path count should be 3 or 5+
String[] paths = cache.split(Pattern.quote(PATH_SEPARATOR));
if (paths.length < 3 || paths.length == 4) {
return null;
}
// now we check the paths actually exist.
// There's an exception: If the source folder for android.jar does not exist, this is
// not a problem, so we skip it.
// Also paths[PATH_ANDROID_DOCS] is a URI to the javadoc, so we test it a bit differently.
try {
if (new File(paths[PATH_ANDROID_JAR]).exists() == false ||
new File(new URI(paths[PATH_ANDROID_DOCS])).exists() == false) {
return null;
}
} catch (URISyntaxException e) {
return null;
} finally {
}
for (int i = 3 ; i < paths.length; i++) {
String path = paths[i];
if (path.length() > 0) {
File f = new File(path);
if (f.exists() == false) {
return null;
}
}
}
IClasspathEntry[] entries = createClasspathEntriesFromPaths(paths);
return new AndroidClasspathContainer(entries,
new Path(CONTAINER_ID), targetNameCache);
}
/**
* Generates an array of {@link IClasspathEntry} from a set of paths.
* @see #getTargetPaths(IAndroidTarget)
*/
private static IClasspathEntry[] createClasspathEntriesFromPaths(String[] paths) {
ArrayList<IClasspathEntry> list = new ArrayList<IClasspathEntry>();
// First, we create the IClasspathEntry for the framework.
// now add the android framework to the class path.
// create the path object.
IPath android_lib = new Path(paths[PATH_ANDROID_JAR]);
IPath android_src = new Path(paths[PATH_ANDROID_SRC]);
// create the java doc link.
IClasspathAttribute cpAttribute = JavaCore.newClasspathAttribute(
IClasspathAttribute.JAVADOC_LOCATION_ATTRIBUTE_NAME,
paths[PATH_ANDROID_DOCS]);
// create the access rule to restrict access to classes in com.android.internal
IAccessRule accessRule = JavaCore.newAccessRule(
new Path("com/android/internal/**"), //$NON-NLS-1$
IAccessRule.K_NON_ACCESSIBLE);
IClasspathEntry frameworkClasspathEntry = JavaCore.newLibraryEntry(android_lib,
android_src, // source attachment path
null, // default source attachment root path.
new IAccessRule[] { accessRule },
new IClasspathAttribute[] { cpAttribute },
false // not exported.
);
list.add(frameworkClasspathEntry);
// now deal with optional libraries
if (paths.length >= 5) {
String docPath = paths[PATH_ANDROID_OPT_DOCS];
int i = 4;
while (i < paths.length) {
Path jarPath = new Path(paths[i++]);
IClasspathAttribute[] attributes = null;
if (docPath.length() > 0) {
attributes = new IClasspathAttribute[] {
JavaCore.newClasspathAttribute(
IClasspathAttribute.JAVADOC_LOCATION_ATTRIBUTE_NAME,
docPath)
};
}
IClasspathEntry entry = JavaCore.newLibraryEntry(
jarPath,
null, // source attachment path
null, // default source attachment root path.
null,
attributes,
false // not exported.
);
list.add(entry);
}
}
return list.toArray(new IClasspathEntry[list.size()]);
}
/**
* Checks the projects' caches. If the cache was valid, the project is removed from the list.
* @param projects the list of projects to check.
*/
public static void checkProjectsCache(ArrayList<IJavaProject> projects) {
int i = 0;
projectLoop: while (i < projects.size()) {
IJavaProject javaProject = projects.get(i);
IProject iProject = javaProject.getProject();
// get the target from the project and its paths
IAndroidTarget target = Sdk.getCurrent().getTarget(javaProject.getProject());
if (target == null) {
// this is really not supposed to happen. This would mean there are cached paths,
// but default.properties was deleted. Keep the project in the list to force
// a resolve which will display the error.
i++;
continue;
}
String[] targetPaths = getTargetPaths(target);
// now get the cached paths
String cache = ProjectHelper.loadStringProperty(iProject, PROPERTY_CONTAINER_CACHE);
if (cache == null) {
// this should not happen. We'll force resolve again anyway.
i++;
continue;
}
String[] cachedPaths = cache.split(Pattern.quote(PATH_SEPARATOR));
if (cachedPaths.length < 3 || cachedPaths.length == 4) {
// paths length is wrong. simply resolve the project again
i++;
continue;
}
// Now we compare the paths. The first 4 can be compared directly.
// because of case sensitiveness we need to use File objects
if (targetPaths.length != cachedPaths.length) {
// different paths, force resolve again.
i++;
continue;
}
// compare the main paths (android.jar, main sources, main javadoc)
if (new File(targetPaths[PATH_ANDROID_JAR]).equals(
new File(cachedPaths[PATH_ANDROID_JAR])) == false ||
new File(targetPaths[PATH_ANDROID_SRC]).equals(
new File(cachedPaths[PATH_ANDROID_SRC])) == false ||
new File(targetPaths[PATH_ANDROID_DOCS]).equals(
new File(cachedPaths[PATH_ANDROID_DOCS])) == false) {
// different paths, force resolve again.
i++;
continue;
}
if (cachedPaths.length > PATH_ANDROID_OPT_DOCS) {
// compare optional libraries javadoc
if (new File(targetPaths[PATH_ANDROID_OPT_DOCS]).equals(
new File(cachedPaths[PATH_ANDROID_OPT_DOCS])) == false) {
// different paths, force resolve again.
i++;
continue;
}
// testing the optional jar files is a little bit trickier.
// The order is not guaranteed to be identical.
// From a previous test, we do know however that there is the same number.
// The number of libraries should be low enough that we can simply go through the
// lists manually.
targetLoop: for (int tpi = 4 ; tpi < targetPaths.length; tpi++) {
String targetPath = targetPaths[tpi];
// look for a match in the other array
for (int cpi = 4 ; cpi < cachedPaths.length; cpi++) {
if (new File(targetPath).equals(new File(cachedPaths[cpi]))) {
// found a match. Try the next targetPath
continue targetLoop;
}
}
// if we stop here, we haven't found a match, which means there's a
// discrepancy in the libraries. We force a resolve.
i++;
continue projectLoop;
}
}
// at the point the check passes, and we can remove the project from the list.
// we do not increment i in this case.
projects.remove(i);
}
}
/**
* Returns the paths necessary to create the {@link IClasspathEntry} for this targets.
* <p/>The paths are always in the same order.
* <ul>
* <li>Path to android.jar</li>
* <li>Path to the source code for android.jar</li>
* <li>Path to the javadoc for the android platform</li>
* </ul>
* Additionally, if there are optional libraries, the array will contain:
* <ul>
* <li>Path to the librairies javadoc</li>
* <li>Path to the first .jar file</li>
* <li>(more .jar as needed)</li>
* </ul>
*/
private static String[] getTargetPaths(IAndroidTarget target) {
ArrayList<String> paths = new ArrayList<String>();
// first, we get the path for android.jar
// The order is: android.jar, source folder, docs folder
paths.add(target.getPath(IAndroidTarget.ANDROID_JAR));
paths.add(target.getPath(IAndroidTarget.SOURCES));
paths.add(AdtPlugin.getUrlDoc());
// now deal with optional libraries.
IOptionalLibrary[] libraries = target.getOptionalLibraries();
if (libraries != null) {
// all the optional libraries use the same javadoc, so we start with this
String targetDocPath = target.getPath(IAndroidTarget.DOCS);
if (targetDocPath != null) {
paths.add(targetDocPath);
} else {
// we add an empty string, to always have the same count.
paths.add("");
}
// because different libraries could use the same jar file, we make sure we add
// each jar file only once.
HashSet<String> visitedJars = new HashSet<String>();
for (IOptionalLibrary library : libraries) {
String jarPath = library.getJarPath();
if (visitedJars.contains(jarPath) == false) {
visitedJars.add(jarPath);
paths.add(jarPath);
}
}
}
return paths.toArray(new String[paths.size()]);
}
}

View File

@@ -0,0 +1,123 @@
/*
* 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.ApkConfigWidget;
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;
import java.util.Map;
/**
* 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;
private ApkConfigWidget mApkConfigWidget;
public AndroidPropertyPage() {
// pass
}
@Override
protected Control createContents(Composite parent) {
// get the element (this is not yet valid in the constructor).
mProject = (IProject)getElement();
// get the targets from the sdk
IAndroidTarget[] targets = null;
if (Sdk.getCurrent() != null) {
targets = Sdk.getCurrent().getTargets();
}
// build the UI.
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");
mSelector = new SdkTargetSelector(top, targets, false /*allowMultipleSelection*/);
l = new Label(top, SWT.SEPARATOR | SWT.HORIZONTAL);
l.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
l = new Label(top, SWT.NONE);
l.setText("Project APK Configurations");
mApkConfigWidget = new ApkConfigWidget(top);
// fill the ui
Sdk currentSdk = Sdk.getCurrent();
if (currentSdk != null && mProject.isOpen()) {
// get the target
IAndroidTarget target = currentSdk.getTarget(mProject);
if (target != null) {
mSelector.setSelection(target);
}
// get the apk configurations
Map<String, String> configs = currentSdk.getProjectApkConfigs(mProject);
mApkConfigWidget.fillTable(configs);
}
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);
}
});
if (mProject.isOpen() == false) {
// disable the ui.
}
return top;
}
@Override
public boolean performOk() {
Sdk currentSdk = Sdk.getCurrent();
if (currentSdk != null) {
currentSdk.setProject(mProject, mSelector.getFirstSelected(),
mApkConfigWidget.getApkConfigs());
}
return true;
}
}

View File

@@ -0,0 +1,431 @@
/*
* Copyright (C) 2007 The Android Open Source Project
*
* Licensed under the Eclipse Public License, Version 1.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.eclipse.org/org/documents/epl-v10.php
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.ide.eclipse.adt.sdk;
import com.android.ide.eclipse.common.AndroidConstants;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.SubMonitor;
import java.io.FileInputStream;
import java.io.IOException;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import javax.management.InvalidAttributeValueException;
/**
* Custom class loader able to load a class from the SDK jar file.
*/
public class AndroidJarLoader extends ClassLoader implements IAndroidClassLoader {
/**
* Wrapper around a {@link Class} to provide the methods of
* {@link IAndroidClassLoader.IClassDescriptor}.
*/
public final static class ClassWrapper implements IClassDescriptor {
private Class<?> mClass;
public ClassWrapper(Class<?> clazz) {
mClass = clazz;
}
public String getCanonicalName() {
return mClass.getCanonicalName();
}
public IClassDescriptor[] getDeclaredClasses() {
Class<?>[] classes = mClass.getDeclaredClasses();
IClassDescriptor[] iclasses = new IClassDescriptor[classes.length];
for (int i = 0 ; i < classes.length ; i++) {
iclasses[i] = new ClassWrapper(classes[i]);
}
return iclasses;
}
public IClassDescriptor getEnclosingClass() {
return new ClassWrapper(mClass.getEnclosingClass());
}
public String getSimpleName() {
return mClass.getSimpleName();
}
public IClassDescriptor getSuperclass() {
return new ClassWrapper(mClass.getSuperclass());
}
@Override
public boolean equals(Object clazz) {
if (clazz instanceof ClassWrapper) {
return mClass.equals(((ClassWrapper)clazz).mClass);
}
return super.equals(clazz);
}
@Override
public int hashCode() {
return mClass.hashCode();
}
public boolean isInstantiable() {
int modifiers = mClass.getModifiers();
return Modifier.isAbstract(modifiers) == false && Modifier.isPublic(modifiers) == true;
}
public Class<?> wrappedClass() {
return mClass;
}
}
private String mOsFrameworkLocation;
/** A cache for binary data extracted from the zip */
private final HashMap<String, byte[]> mEntryCache = new HashMap<String, byte[]>();
/** A cache for already defined Classes */
private final HashMap<String, Class<?> > mClassCache = new HashMap<String, Class<?> >();
/**
* Creates the class loader by providing the os path to the framework jar archive
*
* @param osFrameworkLocation OS Path of the framework JAR file
*/
public AndroidJarLoader(String osFrameworkLocation) {
super();
mOsFrameworkLocation = osFrameworkLocation;
}
public String getSource() {
return mOsFrameworkLocation;
}
/**
* Pre-loads all class binary data that belong to the given package by reading the archive
* once and caching them internally.
* <p/>
* This does not actually preload "classes", it just reads the unzipped bytes for a given
* class. To obtain a class, one must call {@link #findClass(String)} later.
* <p/>
* All classes which package name starts with "packageFilter" will be included and can be
* found later.
* <p/>
* May throw some exceptions if the framework JAR cannot be read.
*
* @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, 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, taskLabel == null ? "" : taskLabel, 100);
// create streams to read the intermediary archive
FileInputStream fis = new FileInputStream(mOsFrameworkLocation);
ZipInputStream zis = new ZipInputStream(fis);
ZipEntry entry;
while ((entry = zis.getNextEntry()) != null) {
// get the name of the entry.
String entryPath = entry.getName();
if (!entryPath.endsWith(AndroidConstants.DOT_CLASS)) {
// only accept class files
continue;
}
// check if it is part of the package to preload
if (pathFilter.length() > 0 && !entryPath.startsWith(pathFilter)) {
continue;
}
String className = entryPathToClassName(entryPath);
if (!mEntryCache.containsKey(className)) {
long entrySize = entry.getSize();
if (entrySize > Integer.MAX_VALUE) {
throw new InvalidAttributeValueException();
}
byte[] data = readZipData(zis, (int)entrySize);
mEntryCache.put(className, data);
}
// advance 5% of whatever is allocated on the progress bar
progress.setWorkRemaining(100);
progress.worked(5);
progress.subTask(String.format("Preload %1$s", className));
}
}
/**
* Finds and loads all classes that derive from a given set of super classes.
* <p/>
* As a side-effect this will load and cache most, if not all, classes in the input JAR file.
*
* @param packageFilter Base name of package of classes to find.
* Use an empty string to find everyting.
* @param superClasses The super classes of all the classes to find.
* @return An hash map which keys are the super classes looked for and which values are
* ArrayList of the classes found. The array lists are always created for all the
* valid keys, they are simply empty if no deriving class is found for a given
* super class.
* @throws IOException
* @throws InvalidAttributeValueException
* @throws ClassFormatError
*/
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<IClassDescriptor>> mClassesFound =
new HashMap<String, ArrayList<IClassDescriptor>>();
for (String className : superClasses) {
mClassesFound.put(className, new ArrayList<IClassDescriptor>());
}
// create streams to read the intermediary archive
FileInputStream fis = new FileInputStream(mOsFrameworkLocation);
ZipInputStream zis = new ZipInputStream(fis);
ZipEntry entry;
while ((entry = zis.getNextEntry()) != null) {
// get the name of the entry and convert to a class binary name
String entryPath = entry.getName();
if (!entryPath.endsWith(AndroidConstants.DOT_CLASS)) {
// only accept class files
continue;
}
if (packageFilter.length() > 0 && !entryPath.startsWith(packageFilter)) {
// only accept stuff from the requested root package.
continue;
}
String className = entryPathToClassName(entryPath);
Class<?> loaded_class = mClassCache.get(className);
if (loaded_class == null) {
byte[] data = mEntryCache.get(className);
if (data == null) {
// Get the class and cache it
long entrySize = entry.getSize();
if (entrySize > Integer.MAX_VALUE) {
throw new InvalidAttributeValueException();
}
data = readZipData(zis, (int)entrySize);
}
loaded_class = defineAndCacheClass(className, data);
}
for (Class<?> superClass = loaded_class.getSuperclass();
superClass != null;
superClass = superClass.getSuperclass()) {
String superName = superClass.getCanonicalName();
if (mClassesFound.containsKey(superName)) {
mClassesFound.get(superName).add(new ClassWrapper(loaded_class));
break;
}
}
}
return mClassesFound;
}
/** Helper method that converts a Zip entry path into a corresponding
* Java full qualified binary class name.
* <p/>
* F.ex, this converts "com/my/package/Foo.class" into "com.my.package.Foo".
*/
private String entryPathToClassName(String entryPath) {
return entryPath.replaceFirst("\\.class$", "").replaceAll("[/\\\\]", "."); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
/**
* Finds the class with the specified binary name.
*
* {@inheritDoc}
*/
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
// try to find the class in the cache
Class<?> cached_class = mClassCache.get(name);
if (cached_class == ClassNotFoundException.class) {
// we already know we can't find this class, don't try again
throw new ClassNotFoundException(name);
} else if (cached_class != null) {
return cached_class;
}
// if not found, look it up and cache it
byte[] data = loadClassData(name);
if (data != null) {
return defineAndCacheClass(name, data);
} else {
// if the class can't be found, record a CNFE class in the map so
// that we don't try to reload it next time
mClassCache.put(name, ClassNotFoundException.class);
throw new ClassNotFoundException(name);
}
} catch (ClassNotFoundException e) {
throw e;
} catch (Exception e) {
throw new ClassNotFoundException(e.getMessage());
}
}
/**
* Defines a class based on its binary data and caches the resulting class object.
*
* @param name The binary name of the class (i.e. package.class1$class2)
* @param data The binary data from the loader.
* @return The class defined
* @throws ClassFormatError if defineClass failed.
*/
private Class<?> defineAndCacheClass(String name, byte[] data) throws ClassFormatError {
Class<?> cached_class;
cached_class = defineClass(null, data, 0, data.length);
if (cached_class != null) {
// Add new class to the cache class and remove it from the zip entry data cache
mClassCache.put(name, cached_class);
mEntryCache.remove(name);
}
return cached_class;
}
/**
* Loads a class data from its binary name.
* <p/>
* This uses the class binary data that has been preloaded earlier by the preLoadClasses()
* method if possible.
*
* @param className the binary name
* @return an array of bytes representing the class data or null if not found
* @throws InvalidAttributeValueException
* @throws IOException
*/
private synchronized byte[] loadClassData(String className)
throws InvalidAttributeValueException, IOException {
byte[] data = mEntryCache.get(className);
if (data != null) {
return data;
}
// The name is a binary name. Something like "android.R", or "android.R$id".
// Make a path out of it.
String entryName = className.replaceAll("\\.", "/") + AndroidConstants.DOT_CLASS; //$NON-NLS-1$ //$NON-NLS-2$
// create streams to read the intermediary archive
FileInputStream fis = new FileInputStream(mOsFrameworkLocation);
ZipInputStream zis = new ZipInputStream(fis);
// loop on the entries of the intermediary package and put them in the final package.
ZipEntry entry;
while ((entry = zis.getNextEntry()) != null) {
// get the name of the entry.
String currEntryName = entry.getName();
if (currEntryName.equals(entryName)) {
long entrySize = entry.getSize();
if (entrySize > Integer.MAX_VALUE) {
throw new InvalidAttributeValueException();
}
data = readZipData(zis, (int)entrySize);
return data;
}
}
return null;
}
/**
* Reads data for the <em>current</em> entry from the zip input stream.
*
* @param zis The Zip input stream
* @param entrySize The entry size. -1 if unknown.
* @return The new data for the <em>current</em> entry.
* @throws IOException If ZipInputStream.read() fails.
*/
private byte[] readZipData(ZipInputStream zis, int entrySize) throws IOException {
int block_size = 1024;
int data_size = entrySize < 1 ? block_size : entrySize;
int offset = 0;
byte[] data = new byte[data_size];
while(zis.available() != 0) {
int count = zis.read(data, offset, data_size - offset);
if (count < 0) { // read data is done
break;
}
offset += count;
if (entrySize >= 1 && offset >= entrySize) { // we know the size and we're done
break;
}
// if we don't know the entry size and we're not done reading,
// expand the data buffer some more.
if (offset >= data_size) {
byte[] temp = new byte[data_size + block_size];
System.arraycopy(data, 0, temp, 0, data_size);
data_size += block_size;
data = temp;
block_size *= 2;
}
}
if (offset < data_size) {
// buffer was allocated too large, trim it
byte[] temp = new byte[offset];
if (offset > 0) {
System.arraycopy(data, 0, temp, 0, offset);
}
data = temp;
}
return data;
}
/**
* Returns a {@link IAndroidClassLoader.IClassDescriptor} by its fully-qualified name.
* @param className the fully-qualified name of the class to return.
* @throws ClassNotFoundException
*/
public IClassDescriptor getClass(String className) throws ClassNotFoundException {
try {
return new ClassWrapper(loadClass(className));
} catch (ClassNotFoundException e) {
throw e; // useful for debugging
}
}
}

View File

@@ -0,0 +1,321 @@
/*
* 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.adt.build.DexWrapper;
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 com.android.sdklib.IAndroidTarget.IOptionalLibrary;
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 int DESCRIPTOR_GADGET_PROVIDER = 8;
public final static class LayoutBridge {
/** Link to the layout bridge */
public ILayoutBridge bridge;
public LoadStatus status = LoadStatus.LOADING;
public ClassLoader classLoader;
public int apiLevel;
}
private final IAndroidTarget mTarget;
private DexWrapper mDexWrapper;
/**
* 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 Hashtable<String, String[]> mAttributeValues = new Hashtable<String, String[]>();
private IResourceRepository mSystemResourceRepository;
private AndroidManifestDescriptors mManifestDescriptors;
private LayoutDescriptors mLayoutDescriptors;
private MenuDescriptors mMenuDescriptors;
private XmlDescriptors mXmlDescriptors;
private Map<String, Map<String, Integer>> mEnumValueMap;
private ProjectResources mFrameworkResources;
private LayoutBridge mLayoutBridge;
private boolean mLayoutBridgeInit = false;
AndroidTargetData(IAndroidTarget androidTarget) {
mTarget = androidTarget;
}
void setDexWrapper(DexWrapper wrapper) {
mDexWrapper = wrapper;
}
/**
* Creates an AndroidTargetData object.
* @param optionalLibraries
*/
void setExtraData(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,
IOptionalLibrary[] optionalLibraries,
ProjectResources resources,
LayoutBridge layoutBridge) {
mSystemResourceRepository = systemResourceRepository;
mManifestDescriptors = manifestDescriptors;
mLayoutDescriptors = layoutDescriptors;
mMenuDescriptors = menuDescriptors;
mXmlDescriptors = xmlDescriptors;
mEnumValueMap = enumValueMap;
mFrameworkResources = resources;
mLayoutBridge = layoutBridge;
setPermissions(permissionValues);
setIntentFilterActionsAndCategories(activityIntentActionValues, broadcastIntentActionValues,
serviceIntentActionValues, intentCategoryValues);
setOptionalLibraries(optionalLibraries);
}
public DexWrapper getDexWrapper() {
return mDexWrapper;
}
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_GADGET_PROVIDER:
return mXmlDescriptors.getGadgetProvider();
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$
}
private void setOptionalLibraries(IOptionalLibrary[] optionalLibraries) {
String[] values;
if (optionalLibraries == null) {
values = new String[0];
} else {
values = new String[optionalLibraries.length];
for (int i = 0; i < optionalLibraries.length; i++) {
values[i] = optionalLibraries[i].getName();
}
}
setValues("(uses-library,android:name)", values);
}
/**
* 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

@@ -0,0 +1,704 @@
/*
* 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.build.DexWrapper;
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.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;
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.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.management.InvalidAttributeValueException;
/**
* Parser for the platform data in an SDK.
* <p/>
* This gather the following information:
* <ul>
* <li>Resource ID from <code>android.R</code></li>
* <li>The list of permissions values from <code>android.Manifest$permission</code></li>
* <li></li>
* </ul>
*/
public final class AndroidTargetParser {
private static final String TAG = "Framework Resource Parser";
private final IAndroidTarget mAndroidTarget;
/**
* Creates a platform data parser.
*/
public AndroidTargetParser(IAndroidTarget platformTarget) {
mAndroidTarget = platformTarget;
}
/**
* Parses the framework, collects all interesting information and stores them in the
* {@link IAndroidTarget} given to the constructor.
*
* @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 IStatus run(IProgressMonitor monitor) {
try {
SubMonitor progress = SubMonitor.convert(monitor,
String.format("Parsing SDK %1$s", mAndroidTarget.getName()),
14);
AndroidTargetData targetData = new AndroidTargetData(mAndroidTarget);
// load DX.
DexWrapper dexWrapper = new DexWrapper();
IStatus res = dexWrapper.loadDex(mAndroidTarget.getPath(IAndroidTarget.DX_JAR));
if (res != Status.OK_STATUS) {
return new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
String.format("dx.jar loading failed for target '%1$s'",
mAndroidTarget.getFullName()));
}
// we have loaded dx.
targetData.setDexWrapper(dexWrapper);
progress.worked(1);
// parse the rest of the data.
AndroidJarLoader classLoader =
new AndroidJarLoader(mAndroidTarget.getPath(IAndroidTarget.ANDROID_JAR));
preload(classLoader, progress.newChild(40, SubMonitor.SUPPRESS_NONE));
if (progress.isCanceled()) {
return Status.CANCEL_STATUS;
}
// get the resource Ids.
progress.subTask("Resource IDs");
IResourceRepository frameworkRepository = collectResourceIds(classLoader);
progress.worked(1);
if (progress.isCanceled()) {
return Status.CANCEL_STATUS;
}
// get the permissions
progress.subTask("Permissions");
String[] permissionValues = collectPermissions(classLoader);
progress.worked(1);
if (progress.isCanceled()) {
return Status.CANCEL_STATUS;
}
// 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(activity_actions, broadcast_actions,
service_actions, categories);
progress.worked(1);
if (progress.isCanceled()) {
return Status.CANCEL_STATUS;
}
// gather the attribute definition
progress.subTask("Attributes definitions");
AttrsXmlParser attrsXmlParser = new AttrsXmlParser(
mAndroidTarget.getPath(IAndroidTarget.ATTRIBUTES));
attrsXmlParser.preload();
progress.worked(1);
progress.subTask("Manifest definitions");
AttrsXmlParser attrsManifestXmlParser = new AttrsXmlParser(
mAndroidTarget.getPath(IAndroidTarget.MANIFEST_ATTRIBUTES),
attrsXmlParser);
attrsManifestXmlParser.preload();
progress.worked(1);
Collection<ViewClassInfo> mainList = new ArrayList<ViewClassInfo>();
Collection<ViewClassInfo> groupList = new ArrayList<ViewClassInfo>();
// collect the layout/widgets classes
progress.subTask("Widgets and layouts");
collectLayoutClasses(classLoader, attrsXmlParser, mainList, groupList,
progress.newChild(1));
if (progress.isCanceled()) {
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(1));
if (progress.isCanceled()) {
return Status.CANCEL_STATUS;
}
ViewClassInfo[] preferencesInfo = mainList.toArray(new ViewClassInfo[mainList.size()]);
ViewClassInfo[] preferenceGroupsInfo = groupList.toArray(
new ViewClassInfo[groupList.size()]);
Map<String, DeclareStyleableInfo> xmlMenuMap = collectMenuDefinitions(attrsXmlParser);
Map<String, DeclareStyleableInfo> xmlSearchableMap = collectSearchableDefinitions(
attrsXmlParser);
Map<String, DeclareStyleableInfo> manifestMap = collectManifestDefinitions(
attrsManifestXmlParser);
Map<String, Map<String, Integer>> enumValueMap = attrsXmlParser.getEnumFlagValues();
Map<String, DeclareStyleableInfo> xmlGadgetMap = null;
if (mAndroidTarget.getApiVersionNumber() >= 3) {
xmlGadgetMap = collectGadgetDefinitions(attrsXmlParser);
}
if (progress.isCanceled()) {
return Status.CANCEL_STATUS;
}
// 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(1);
if (progress.isCanceled()) {
return Status.CANCEL_STATUS;
}
LayoutDescriptors layoutDescriptors = new LayoutDescriptors();
layoutDescriptors.updateDescriptors(layoutViewsInfo, layoutGroupsInfo);
progress.worked(1);
if (progress.isCanceled()) {
return Status.CANCEL_STATUS;
}
MenuDescriptors menuDescriptors = new MenuDescriptors();
menuDescriptors.updateDescriptors(xmlMenuMap);
progress.worked(1);
if (progress.isCanceled()) {
return Status.CANCEL_STATUS;
}
XmlDescriptors xmlDescriptors = new XmlDescriptors();
xmlDescriptors.updateDescriptors(
xmlSearchableMap,
xmlGadgetMap,
preferencesInfo,
preferenceGroupsInfo);
progress.worked(1);
// load the framework resources.
ProjectResources resources = ResourceManager.getInstance().loadFrameworkResources(
mAndroidTarget);
progress.worked(1);
// now load the layout lib bridge
LayoutBridge layoutBridge = loadLayoutBridge();
progress.worked(1);
// and finally create the PlatformData with all that we loaded.
targetData.setExtraData(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()]),
mAndroidTarget.getOptionalLibraries(),
resources,
layoutBridge);
Sdk.getCurrent().setTargetData(mAndroidTarget, targetData);
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);
}
}
/**
* Preloads all "interesting" classes from the framework SDK jar.
* <p/>
* Currently this preloads all classes from the framework jar
*
* @param classLoader The framework SDK jar classloader
* @param monitor A progress monitor. Can be null. Caller is responsible for calling done.
*/
private void preload(AndroidJarLoader classLoader, IProgressMonitor monitor) {
try {
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) {
AdtPlugin.log(e, "Problem preloading classes"); //$NON-NLS-1$
}
}
/**
* 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 IResourceRepository collectResourceIds(
AndroidJarLoader classLoader) {
try {
Class<?> r = classLoader.loadClass(AndroidConstants.CLASS_R);
if (r != null) {
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,
mAndroidTarget.getPath(IAndroidTarget.ANDROID_JAR));
}
return null;
}
/**
* Parse the R class and build the resource map.
*
* @param rClass the Class object representing the Resources.
* @return a map of the resource or null
*/
private Map<ResourceType, List<ResourceItem>> parseRClass(Class<?> rClass) {
// get the sub classes.
Class<?>[] classes = rClass.getClasses();
if (classes.length > 0) {
HashMap<ResourceType, List<ResourceItem>> map =
new HashMap<ResourceType, List<ResourceItem>>();
// get the fields of each class.
for (int c = 0 ; c < classes.length ; c++) {
Class<?> subClass = classes[c];
String name = subClass.getSimpleName();
// get the matching ResourceType
ResourceType type = ResourceType.getEnum(name);
if (type != null) {
List<ResourceItem> list = new ArrayList<ResourceItem>();
map.put(type, list);
Field[] fields = subClass.getFields();
for (Field f : fields) {
list.add(new ResourceItem(f.getName()));
}
}
}
return map;
}
return null;
}
/**
* Loads, collects and returns the list of default permissions from the framework.
*
* @param classLoader The framework SDK jar classloader
* @return a non null (but possibly empty) array containing the permission values.
*/
private String[] collectPermissions(AndroidJarLoader classLoader) {
try {
Class<?> permissionClass =
classLoader.loadClass(AndroidConstants.CLASS_MANIFEST_PERMISSION);
if (permissionClass != null) {
ArrayList<String> list = new ArrayList<String>();
Field[] fields = permissionClass.getFields();
for (Field f : fields) {
int modifiers = f.getModifiers();
if (Modifier.isStatic(modifiers) && Modifier.isFinal(modifiers) &&
Modifier.isPublic(modifiers)) {
try {
Object value = f.get(null);
if (value instanceof String) {
list.add((String)value);
}
} catch (IllegalArgumentException e) {
// since we provide null this should not happen
} catch (IllegalAccessException e) {
// if the field is inaccessible we ignore it.
} catch (NullPointerException npe) {
// looks like this is not a static field. we can ignore.
} catch (ExceptionInInitializerError eiie) {
// lets just ignore the field again
}
}
}
return list.toArray(new String[list.size()]);
}
} catch (ClassNotFoundException e) {
AdtPlugin.logAndPrintError(e, TAG,
"Collect permissions failed, class %1$s not found in %2$s", //$NON-NLS-1$
AndroidConstants.CLASS_MANIFEST_PERMISSION,
mAndroidTarget.getPath(IAndroidTarget.ANDROID_JAR));
}
return new String[0];
}
/**
* Loads and collects the action and category default values from the framework.
* The values are added to the <code>actions</code> and <code>categories</code> lists.
*
* @param activityActions the list which will receive the activity action values.
* @param broadcastActions the list which will receive the broadcast action values.
* @param serviceActions the list which will receive the service action values.
* @param categories the list which will receive the category values.
*/
private void collectIntentFilterActionsAndCategories(ArrayList<String> activityActions,
ArrayList<String> broadcastActions,
ArrayList<String> serviceActions, ArrayList<String> 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);
}
/**
* Collects values from a text file located in the SDK
* @param osFilePath The path to the text file.
* @param values the {@link ArrayList} to fill with the values.
*/
private void collectValues(String osFilePath, ArrayList<String> values) {
FileReader fr = null;
BufferedReader reader = null;
try {
fr = new FileReader(osFilePath);
reader = new BufferedReader(fr);
String line;
while ((line = reader.readLine()) != null) {
line = line.trim();
if (line.length() > 0 && line.startsWith("#") == false) { //$NON-NLS-1$
values.add(line);
}
}
} catch (IOException e) {
AdtPlugin.log(e, "Failed to read SDK values"); //$NON-NLS-1$
} finally {
try {
if (reader != null) {
reader.close();
}
} catch (IOException e) {
AdtPlugin.log(e, "Failed to read SDK values"); //$NON-NLS-1$
}
try {
if (fr != null) {
fr.close();
}
} catch (IOException e) {
AdtPlugin.log(e, "Failed to read SDK values"); //$NON-NLS-1$
}
}
}
/**
* Collects all layout classes information from the class loader and the
* attrs.xml and sets the corresponding structures in the resource manager.
*
* @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(AndroidJarLoader classLoader,
AttrsXmlParser attrsXmlParser,
Collection<ViewClassInfo> mainList, Collection<ViewClassInfo> groupList,
IProgressMonitor monitor) {
LayoutParamsParser ldp = null;
try {
WidgetClassLoader loader = new WidgetClassLoader(
mAndroidTarget.getPath(IAndroidTarget.WIDGETS));
if (loader.parseWidgetList(monitor)) {
ldp = new LayoutParamsParser(loader, attrsXmlParser);
}
// if the parsing failed, we'll use the old loader below.
} catch (FileNotFoundException e) {
AdtPlugin.log(e, "Android Framework Parser"); //$NON-NLS-1$
// the file does not exist, we'll use the old loader below.
}
if (ldp == null) {
ldp = new LayoutParamsParser(classLoader, attrsXmlParser);
}
ldp.parseLayoutClasses(monitor);
List<ViewClassInfo> views = ldp.getViews();
List<ViewClassInfo> groups = ldp.getGroups();
if (views != null && groups != null) {
mainList.addAll(views);
groupList.addAll(groups);
}
}
/**
* Collects all preferences definition information from the attrs.xml and
* sets the corresponding structures in the resource manager.
*
* @param classLoader The framework SDK jar classloader
* @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 collectPreferenceClasses(AndroidJarLoader classLoader,
AttrsXmlParser attrsXmlParser, Collection<ViewClassInfo> mainList,
Collection<ViewClassInfo> groupList, IProgressMonitor monitor) {
LayoutParamsParser ldp = new LayoutParamsParser(classLoader, attrsXmlParser);
try {
ldp.parsePreferencesClasses(monitor);
List<ViewClassInfo> prefs = ldp.getViews();
List<ViewClassInfo> groups = ldp.getGroups();
if (prefs != null && groups != null) {
mainList.addAll(prefs);
groupList.addAll(groups);
}
} catch (NoClassDefFoundError e) {
AdtPlugin.logAndPrintError(e, TAG,
"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");
}
}
/**
* Collects all menu definition information from the attrs.xml and returns it.
*
* @param attrsXmlParser The parser of the attrs.xml file
*/
private Map<String, DeclareStyleableInfo> collectMenuDefinitions(
AttrsXmlParser attrsXmlParser) {
Map<String, DeclareStyleableInfo> map = attrsXmlParser.getDeclareStyleableList();
Map<String, DeclareStyleableInfo> map2 = new HashMap<String, DeclareStyleableInfo>();
for (String key : new String[] { "Menu", //$NON-NLS-1$
"MenuItem", //$NON-NLS-1$
"MenuGroup" }) { //$NON-NLS-1$
if (map.containsKey(key)) {
map2.put(key, map.get(key));
} else {
AdtPlugin.log(IStatus.WARNING,
"Menu declare-styleable %1$s not found in file %2$s", //$NON-NLS-1$
key, attrsXmlParser.getOsAttrsXmlPath());
AdtPlugin.printErrorToConsole("Android Framework Parser",
String.format("Menu declare-styleable %1$s not found in file %2$s", //$NON-NLS-1$
key, attrsXmlParser.getOsAttrsXmlPath()));
}
}
return Collections.unmodifiableMap(map2);
}
/**
* Collects all searchable definition information from the attrs.xml and returns it.
*
* @param attrsXmlParser The parser of the attrs.xml file
*/
private Map<String, DeclareStyleableInfo> collectSearchableDefinitions(
AttrsXmlParser attrsXmlParser) {
Map<String, DeclareStyleableInfo> map = attrsXmlParser.getDeclareStyleableList();
Map<String, DeclareStyleableInfo> map2 = new HashMap<String, DeclareStyleableInfo>();
for (String key : new String[] { "Searchable", //$NON-NLS-1$
"SearchableActionKey" }) { //$NON-NLS-1$
if (map.containsKey(key)) {
map2.put(key, map.get(key));
} else {
AdtPlugin.log(IStatus.WARNING,
"Searchable declare-styleable %1$s not found in file %2$s", //$NON-NLS-1$
key, attrsXmlParser.getOsAttrsXmlPath());
AdtPlugin.printErrorToConsole("Android Framework Parser",
String.format("Searchable declare-styleable %1$s not found in file %2$s", //$NON-NLS-1$
key, attrsXmlParser.getOsAttrsXmlPath()));
}
}
return Collections.unmodifiableMap(map2);
}
/**
* Collects all gadgetProviderInfo definition information from the attrs.xml and returns it.
*
* @param attrsXmlParser The parser of the attrs.xml file
*/
private Map<String, DeclareStyleableInfo> collectGadgetDefinitions(
AttrsXmlParser attrsXmlParser) {
Map<String, DeclareStyleableInfo> map = attrsXmlParser.getDeclareStyleableList();
Map<String, DeclareStyleableInfo> map2 = new HashMap<String, DeclareStyleableInfo>();
for (String key : new String[] { "GadgetProviderInfo" }) { //$NON-NLS-1$
if (map.containsKey(key)) {
map2.put(key, map.get(key));
} else {
AdtPlugin.log(IStatus.WARNING,
"Gadget declare-styleable %1$s not found in file %2$s", //$NON-NLS-1$
key, attrsXmlParser.getOsAttrsXmlPath());
AdtPlugin.printErrorToConsole("Android Framework Parser",
String.format("Gadget declare-styleable %1$s not found in file %2$s", //$NON-NLS-1$
key, attrsXmlParser.getOsAttrsXmlPath()));
}
}
return Collections.unmodifiableMap(map2);
}
/**
* Collects all manifest definition information from the attrs_manifest.xml and returns it.
*/
private Map<String, DeclareStyleableInfo> collectManifestDefinitions(
AttrsXmlParser attrsXmlParser) {
return attrsXmlParser.getDeclareStyleableList();
}
/**
* Loads the layout bridge from the dynamically loaded layoutlib.jar
*/
private LayoutBridge loadLayoutBridge() {
LayoutBridge layoutBridge = new LayoutBridge();
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();
// 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;
}
}
}
if (layoutBridge.bridge == null) {
layoutBridge.status = LoadStatus.FAILED;
AdtPlugin.log(IStatus.ERROR, "Failed to load " + AndroidConstants.CLASS_BRIDGE); //$NON-NLS-1$
} else {
// get the api level
try {
layoutBridge.apiLevel = layoutBridge.bridge.getApiLevel();
} catch (AbstractMethodError e) {
// the first version of the api did not have this method
layoutBridge.apiLevel = 1;
}
// and mark the lib as loaded.
layoutBridge.status = LoadStatus.LOADED;
}
}
} catch (Throwable t) {
layoutBridge.status = LoadStatus.FAILED;
// log the error.
AdtPlugin.log(t, "Failed to load the LayoutLib");
}
return layoutBridge;
}
}

View File

@@ -0,0 +1,76 @@
/*
* 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.common.resources.IResourceRepository;
import com.android.ide.eclipse.common.resources.ResourceItem;
import com.android.ide.eclipse.common.resources.ResourceType;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Implementation of the {@link IResourceRepository} interface to hold the system resource Ids
* parsed by {@link AndroidTargetParser}.
*/
final class FrameworkResourceRepository implements IResourceRepository {
private Map<ResourceType, List<ResourceItem>> mResourcesMap;
public FrameworkResourceRepository(Map<ResourceType, List<ResourceItem>> systemResourcesMap) {
mResourcesMap = systemResourcesMap;
}
public ResourceType[] getAvailableResourceTypes() {
if (mResourcesMap != null) {
Set<ResourceType> types = mResourcesMap.keySet();
if (types != null) {
return types.toArray(new ResourceType[types.size()]);
}
}
return null;
}
public ResourceItem[] getResources(ResourceType type) {
if (mResourcesMap != null) {
List<ResourceItem> items = mResourcesMap.get(type);
if (items != null) {
return items.toArray(new ResourceItem[items.size()]);
}
}
return null;
}
public boolean hasResources(ResourceType type) {
if (mResourcesMap != null) {
List<ResourceItem> items = mResourcesMap.get(type);
return (items != null && items.size() > 0);
}
return false;
}
public boolean isSystemRepository() {
return true;
}
}

View File

@@ -0,0 +1,81 @@
/*
* 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 java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import javax.management.InvalidAttributeValueException;
/**
* Classes which implements this interface provide methods to access framework resource
* data loaded from the SDK.
*/
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.
*
* @param rootPackage Root package of classes to find. Use an empty string to find everyting.
* @param superClasses The super classes of all the classes to find.
* @return An hash map which keys are the super classes looked for and which values are
* ArrayList of the classes found. The array lists are always created for all the
* valid keys, they are simply empty if no deriving class is found for a given
* super class.
* @throws IOException
* @throws InvalidAttributeValueException
* @throws ClassFormatError
*/
public HashMap<String, ArrayList<IClassDescriptor>> findClassesDerivingFrom(
String rootPackage, String[] superClasses)
throws IOException, InvalidAttributeValueException, ClassFormatError;
/**
* Returns a {@link IClassDescriptor} by its fully-qualified name.
* @param className the fully-qualified name of the class to return.
* @throws ClassNotFoundException
*/
public IClassDescriptor getClass(String className) throws ClassNotFoundException;
/**
* Returns a string indicating the source of the classes, typically for debugging
* or in error messages. This would typically be a JAR file name or some kind of
* identifier that would mean something to the user when looking at error messages.
*
* @return An informal string representing the source of the classes.
*/
public String getSource();
}

View File

@@ -0,0 +1,372 @@
/*
* 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.sdk.IAndroidClassLoader.IClassDescriptor;
import com.android.ide.eclipse.common.AndroidConstants;
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;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.SubMonitor;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.SortedMap;
import java.util.TreeMap;
import javax.management.InvalidAttributeValueException;
/*
* TODO: refactor this. Could use some cleanup.
*/
/**
* Parser for the framework library.
* <p/>
* This gather the following information:
* <ul>
* <li>Resource ID from <code>android.R</code></li>
* <li>The list of permissions values from <code>android.Manifest$permission</code></li>
* <li></li>
* </ul>
*/
public class LayoutParamsParser {
/**
* 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.
*/
final static class ExtViewClassInfo extends ViewClassInfo {
private boolean mIsInstantiable;
ExtViewClassInfo(boolean instantiable, boolean isLayout, String canonicalClassName,
String shortClassName) {
super(isLayout, canonicalClassName, shortClassName);
mIsInstantiable = instantiable;
}
boolean isInstantiable() {
return mIsInstantiable;
}
}
/* Note: protected members/methods are overridden in unit tests */
/** Reference to android.view.View */
protected IClassDescriptor mTopViewClass;
/** Reference to android.view.ViewGroup */
protected IClassDescriptor mTopGroupClass;
/** Reference to android.view.ViewGroup$LayoutParams */
protected IClassDescriptor mTopLayoutParamsClass;
/** Input list of all classes deriving from android.view.View */
protected ArrayList<IClassDescriptor> mViewList;
/** Input list of all classes deriving from android.view.ViewGroup */
protected ArrayList<IClassDescriptor> mGroupList;
/** Output map of FQCN => info on View classes */
protected TreeMap<String, ExtViewClassInfo> mViewMap;
/** Output map of FQCN => info on ViewGroup classes */
protected TreeMap<String, ExtViewClassInfo> mGroupMap;
/** Output map of FQCN => info on LayoutParams classes */
protected HashMap<String, LayoutParamsInfo> mLayoutParamsMap;
/** The attrs.xml parser */
protected AttrsXmlParser mAttrsXmlParser;
/** The android.jar class loader */
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(IAndroidClassLoader classLoader,
AttrsXmlParser attrsXmlParser) {
mClassLoader = classLoader;
mAttrsXmlParser = attrsXmlParser;
}
/** Returns the map of FQCN => info on View classes */
public List<ViewClassInfo> getViews() {
return getInstantiables(mViewMap);
}
/** Returns the map of FQCN => info on ViewGroup classes */
public List<ViewClassInfo> getGroups() {
return getInstantiables(mGroupMap);
}
/**
* TODO: doc here.
* <p/>
* 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.
*/
public void parseLayoutClasses(IProgressMonitor monitor) {
parseClasses(monitor,
AndroidConstants.CLASS_VIEW,
AndroidConstants.CLASS_VIEWGROUP,
AndroidConstants.CLASS_VIEWGROUP_LAYOUTPARAMS);
}
public void parsePreferencesClasses(IProgressMonitor monitor) {
parseClasses(monitor,
AndroidConstants.CLASS_PREFERENCE,
AndroidConstants.CLASS_PREFERENCEGROUP,
null /* paramsClassName */ );
}
private void parseClasses(IProgressMonitor monitor,
String rootClassName,
String groupClassName,
String paramsClassName) {
try {
SubMonitor progress = SubMonitor.convert(monitor, 100);
String[] superClasses = new String[2 + (paramsClassName == null ? 0 : 1)];
superClasses[0] = groupClassName;
superClasses[1] = rootClassName;
if (paramsClassName != null) {
superClasses[2] = paramsClassName;
}
HashMap<String, ArrayList<IClassDescriptor>> found =
mClassLoader.findClassesDerivingFrom("android.", superClasses);
mTopViewClass = mClassLoader.getClass(rootClassName);
mTopGroupClass = mClassLoader.getClass(groupClassName);
if (paramsClassName != null) {
mTopLayoutParamsClass = mClassLoader.getClass(paramsClassName);
}
mViewList = found.get(rootClassName);
mGroupList = found.get(groupClassName);
mViewMap = new TreeMap<String, ExtViewClassInfo>();
mGroupMap = new TreeMap<String, ExtViewClassInfo>();
if (mTopLayoutParamsClass != null) {
mLayoutParamsMap = new HashMap<String, LayoutParamsInfo>();
}
// Add top classes to the maps since by design they are not listed in classes deriving
// from themselves.
addGroup(mTopGroupClass);
addView(mTopViewClass);
// ViewGroup derives from View
mGroupMap.get(groupClassName).setSuperClass(
mViewMap.get(rootClassName));
progress.setWorkRemaining(mGroupList.size() + mViewList.size());
for (IClassDescriptor groupChild : mGroupList) {
addGroup(groupChild);
progress.worked(1);
}
for (IClassDescriptor viewChild : mViewList) {
if (viewChild != mTopGroupClass) {
addView(viewChild);
}
progress.worked(1);
}
} catch (ClassNotFoundException e) {
AdtPlugin.log(e, "Problem loading class %1$s or %2$s", //$NON-NLS-1$
rootClassName, groupClassName);
} catch (InvalidAttributeValueException e) {
AdtPlugin.log(e, "Problem loading classes"); //$NON-NLS-1$
} catch (ClassFormatError e) {
AdtPlugin.log(e, "Problem loading classes"); //$NON-NLS-1$
} catch (IOException e) {
AdtPlugin.log(e, "Problem loading classes"); //$NON-NLS-1$
}
}
/**
* 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(IClassDescriptor viewClass) {
String fqcn = viewClass.getCanonicalName();
if (mViewMap.containsKey(fqcn)) {
return mViewMap.get(fqcn);
} else if (mGroupMap.containsKey(fqcn)) {
return mGroupMap.get(fqcn);
}
ExtViewClassInfo info = new ExtViewClassInfo(viewClass.isInstantiable(),
false /* layout */, fqcn, viewClass.getSimpleName());
mViewMap.put(fqcn, info);
// All view classes derive from mTopViewClass by design.
// Do not lookup the super class for mTopViewClass itself.
if (viewClass.equals(mTopViewClass) == false) {
IClassDescriptor superClass = viewClass.getSuperclass();
ExtViewClassInfo superClassInfo = addView(superClass);
info.setSuperClass(superClassInfo);
}
mAttrsXmlParser.loadViewAttributes(info);
return info;
}
/**
* 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(IClassDescriptor groupClass) {
String fqcn = groupClass.getCanonicalName();
if (mGroupMap.containsKey(fqcn)) {
return mGroupMap.get(fqcn);
}
ExtViewClassInfo info = new ExtViewClassInfo(groupClass.isInstantiable(),
true /* layout */, fqcn, groupClass.getSimpleName());
mGroupMap.put(fqcn, info);
// All groups derive from android.view.ViewGroup, which in turns derives from
// 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.
IClassDescriptor superClass = groupClass.getSuperclass();
// Assertion: at this point, we should have
// superClass != mTopViewClass || fqcn.equals(AndroidConstants.CLASS_VIEWGROUP);
if (superClass != null && superClass.equals(mTopViewClass) == false) {
ExtViewClassInfo superClassInfo = addGroup(superClass);
// Assertion: we should have superClassInfo != null && superClassInfo != info;
if (superClassInfo != null && superClassInfo != info) {
info.setSuperClass(superClassInfo);
}
}
mAttrsXmlParser.loadViewAttributes(info);
if (mTopLayoutParamsClass != null) {
info.setLayoutParams(addLayoutParams(groupClass));
}
return info;
}
/**
* Parses a ViewGroup class and returns an info object on its inner LayoutParams.
*
* @return The {@link LayoutParamsInfo} for the ViewGroup class or null.
*/
private LayoutParamsInfo addLayoutParams(IClassDescriptor groupClass) {
// Is there a LayoutParams in this group class?
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 (IClassDescriptor superClass = groupClass.getSuperclass();
layoutParamsClass == null &&
superClass != null &&
superClass.equals(mTopViewClass) == false;
superClass = superClass.getSuperclass()) {
layoutParamsClass = findLayoutParams(superClass);
}
}
if (layoutParamsClass != null) {
return getLayoutParamsInfo(layoutParamsClass);
}
return null;
}
/**
* 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(IClassDescriptor layoutParamsClass) {
String fqcn = layoutParamsClass.getCanonicalName();
LayoutParamsInfo layoutParamsInfo = mLayoutParamsMap.get(fqcn);
if (layoutParamsInfo != null) {
return layoutParamsInfo;
}
// Find the link on the LayoutParams super class
LayoutParamsInfo superClassInfo = null;
if (layoutParamsClass.equals(mTopLayoutParamsClass) == false) {
IClassDescriptor superClass = layoutParamsClass.getSuperclass();
superClassInfo = getLayoutParamsInfo(superClass);
}
// Find the link on the enclosing ViewGroup
ExtViewClassInfo enclosingGroupInfo = addGroup(layoutParamsClass.getEnclosingClass());
layoutParamsInfo = new ExtViewClassInfo.LayoutParamsInfo(
enclosingGroupInfo, layoutParamsClass.getSimpleName(), superClassInfo);
mLayoutParamsMap.put(fqcn, layoutParamsInfo);
mAttrsXmlParser.loadLayoutParamsAttributes(layoutParamsInfo);
return layoutParamsInfo;
}
/**
* Given a ViewGroup-derived class, looks for an inner class named LayoutParams
* and if found returns its class definition.
* <p/>
* This uses the actual defined inner classes and does not look at inherited classes.
*
* @param groupClass The ViewGroup derived class
* @return The Class of the inner LayoutParams or null if none is declared.
*/
private IClassDescriptor findLayoutParams(IClassDescriptor groupClass) {
IClassDescriptor[] innerClasses = groupClass.getDeclaredClasses();
for (IClassDescriptor innerClass : innerClasses) {
if (innerClass.getSimpleName().equals(AndroidConstants.CLASS_LAYOUTPARAMS)) {
return innerClass;
}
}
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.isInstantiable()) {
list.add(info);
}
}
return list;
}
}

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,492 @@
/*
* 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.ide.eclipse.adt.sdk.AndroidTargetData.LayoutBridge;
import com.android.ide.eclipse.editors.resources.manager.ResourceMonitor;
import com.android.ide.eclipse.editors.resources.manager.ResourceMonitor.IProjectListener;
import com.android.prefs.AndroidLocation.AndroidLocationException;
import com.android.sdklib.IAndroidTarget;
import com.android.sdklib.ISdkLog;
import com.android.sdklib.SdkConstants;
import com.android.sdklib.SdkManager;
import com.android.sdklib.avd.AvdManager;
import com.android.sdklib.project.ApkConfigurationHelper;
import com.android.sdklib.project.ProjectProperties;
import com.android.sdklib.project.ProjectProperties.PropertyType;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IncrementalProjectBuilder;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IStatus;
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;
import java.util.Map;
/**
* 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 or add-ons present in the SDK, call {@link #getTargets()}.
*/
public class Sdk implements IProjectListener {
private static Sdk sCurrentSdk = null;
private final SdkManager mManager;
private final AvdManager mAvdManager;
private final HashMap<IProject, IAndroidTarget> mProjectTargetMap =
new HashMap<IProject, IAndroidTarget>();
private final HashMap<IAndroidTarget, AndroidTargetData> mTargetDataMap =
new HashMap<IAndroidTarget, AndroidTargetData>();
private final HashMap<IProject, Map<String, String>> mProjectApkConfigMap =
new HashMap<IProject, Map<String, String>>();
private final String mDocBaseUrl;
/**
* Classes implementing this interface will receive notification when targets are changed.
*/
public interface ITargetChangeListener {
/**
* Sent when project has its target changed.
*/
void onProjectTargetChange(IProject changedProject);
/**
* Called when the targets are loaded (either the SDK finished loading when Eclipse starts,
* or the SDK is changed).
*/
void onTargetsLoaded();
}
/**
* 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) {
sCurrentSdk.dispose();
sCurrentSdk = null;
}
final ArrayList<String> logMessages = new ArrayList<String>();
ISdkLog log = new ISdkLog() {
public void error(Throwable throwable, String errorFormat, Object... arg) {
if (errorFormat != null) {
logMessages.add(String.format(errorFormat, arg));
}
if (throwable != null) {
logMessages.add(throwable.getMessage());
}
}
public void warning(String warningFormat, Object... arg) {
logMessages.add(String.format(warningFormat, arg));
}
public void printf(String msgFormat, Object... arg) {
logMessages.add(String.format(msgFormat, arg));
}
};
// get an SdkManager object for the location
SdkManager manager = SdkManager.createManager(sdkLocation, log);
if (manager != null) {
AvdManager avdManager = null;
try {
avdManager = new AvdManager(manager, log);
} catch (AndroidLocationException e) {
log.error(e, "Error parsing the AVDs");
}
sCurrentSdk = new Sdk(manager, avdManager);
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 location (OS path) of the current SDK.
*/
public String getSdkLocation() {
return mManager.getLocation();
}
/**
* 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 {@link IAndroidTarget} hash string.
* @return The matching {@link IAndroidTarget} or null.
*/
public IAndroidTarget getTargetFromHashString(String hash) {
return mManager.getTargetFromHashString(hash);
}
/**
* Sets a new target and a new list of Apk configuration for a given project.
*
* @param project the project to receive the new apk configurations
* @param target The new target to set, or <code>null</code> to not change the current target.
* @param apkConfigMap a map of apk configurations. The map contains (name, filter) where name
* is the name of the configuration (a-zA-Z0-9 only), and filter is the comma separated list of
* resource configuration to include in the apk (see aapt -c). Can be <code>null</code> if the
* apk configurations should not be updated.
*/
public void setProject(IProject project, IAndroidTarget target,
Map<String, String> apkConfigMap) {
synchronized (mProjectTargetMap) {
boolean resolveProject = false;
boolean compileProject = false;
boolean cleanProject = false;
ProjectProperties properties = ProjectProperties.load(
project.getLocation().toOSString(), PropertyType.DEFAULT);
if (properties == null) {
// doesn't exist yet? we create it.
properties = ProjectProperties.create(project.getLocation().toOSString(),
PropertyType.DEFAULT);
}
if (target != null) {
// look for the current target of the project
IAndroidTarget previousTarget = mProjectTargetMap.get(project);
if (target != previousTarget) {
// save the target hash string in the project persistent property
properties.setAndroidTarget(target);
// put it in a local map for easy access.
mProjectTargetMap.put(project, target);
resolveProject = true;
}
}
if (apkConfigMap != null) {
// save the apk configs in the project persistent property
cleanProject = ApkConfigurationHelper.setConfigs(properties, apkConfigMap);
// put it in a local map for easy access.
mProjectApkConfigMap.put(project, apkConfigMap);
compileProject = true;
}
// we are done with the modification. Save the property file.
try {
properties.save();
} catch (IOException e) {
AdtPlugin.log(e, "Failed to save default.properties for project '%s'",
project.getName());
}
if (resolveProject) {
// force a resolve of the project by updating the classpath container.
IJavaProject javaProject = JavaCore.create(project);
AndroidClasspathContainerInitializer.updateProjects(
new IJavaProject[] { javaProject });
} else if (compileProject) {
// If there was removed configs, we clean instead of build
// (to remove the obsolete ap_ and apk file from removed configs).
try {
project.build(cleanProject ?
IncrementalProjectBuilder.CLEAN_BUILD :
IncrementalProjectBuilder.FULL_BUILD,
null);
} catch (CoreException e) {
// failed to build? force resolve instead.
IJavaProject javaProject = JavaCore.create(project);
AndroidClasspathContainerInitializer.updateProjects(
new IJavaProject[] { javaProject });
}
}
// finally, update the opened editors.
if (resolveProject) {
AdtPlugin.getDefault().updateTargetListener(project);
}
}
}
/**
* Returns the {@link IAndroidTarget} object associated with the given {@link IProject}.
*/
public IAndroidTarget getTarget(IProject project) {
synchronized (mProjectTargetMap) {
IAndroidTarget target = mProjectTargetMap.get(project);
if (target == null) {
// get the value from the project persistent property.
String targetHashString = loadProjectProperties(project, this);
if (targetHashString != null) {
target = mManager.getTargetFromHashString(targetHashString);
}
}
return target;
}
}
/**
* Parses the project properties and returns the hash string uniquely identifying the
* target of the given project.
* <p/>
* This methods reads the content of the <code>default.properties</code> file present in
* the root folder of the project.
* <p/>The returned string is equivalent to the return of {@link IAndroidTarget#hashString()}.
* @param project The project for which to return the target hash string.
* @param sdkStorage The sdk in which to store the Apk Configs. Can be null.
* @return the hash string or null if the project does not have a target set.
*/
private static String loadProjectProperties(IProject project, Sdk sdkStorage) {
// load the default.properties from the project folder.
IPath location = project.getLocation();
if (location == null) { // can return null when the project is being deleted.
// do nothing and return null;
return null;
}
ProjectProperties properties = ProjectProperties.load(location.toOSString(),
PropertyType.DEFAULT);
if (properties == null) {
AdtPlugin.log(IStatus.ERROR, "Failed to load properties file for project '%s'",
project.getName());
return null;
}
if (sdkStorage != null) {
Map<String, String> configMap = ApkConfigurationHelper.getConfigs(properties);
if (configMap != null) {
sdkStorage.mProjectApkConfigMap.put(project, configMap);
}
}
return properties.getProperty(ProjectProperties.PROPERTY_TARGET);
}
/**
* Returns the hash string uniquely identifying the target of a project.
* <p/>
* This methods reads the content of the <code>default.properties</code> file present in
* the root folder of the project.
* <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) {
return loadProjectProperties(project, null /*storeConfigs*/);
}
/**
* Sets a target hash string in given project's <code>default.properties</code> file.
* @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(),
PropertyType.DEFAULT);
if (properties == null) {
// doesn't exist yet? we create it.
properties = ProjectProperties.create(project.getLocation().toOSString(),
PropertyType.DEFAULT);
}
// 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 AndroidTargetData} for a given {@link IAndroidTarget}.
*/
public AndroidTargetData getTargetData(IAndroidTarget target) {
synchronized (mTargetDataMap) {
return mTargetDataMap.get(target);
}
}
/**
* Returns the configuration map for a given project.
* <p/>The Map key are name to be used in the apk filename, while the values are comma separated
* config values. The config value can be passed directly to aapt through the -c option.
*/
public Map<String, String> getProjectApkConfigs(IProject project) {
return mProjectApkConfigMap.get(project);
}
/**
* Returns the {@link AvdManager}. If the AvdManager failed to parse the AVD folder, this could
* be <code>null</code>.
*/
public AvdManager getAvdManager() {
return mAvdManager;
}
private Sdk(SdkManager manager, AvdManager avdManager) {
mManager = manager;
mAvdManager = avdManager;
// listen to projects closing
ResourceMonitor monitor = ResourceMonitor.getMonitor();
monitor.addProjectListener(this);
// pre-compute some paths
mDocBaseUrl = getDocumentationBaseUrl(mManager.getLocation() +
SdkConstants.OS_SDK_DOCS_FOLDER);
}
/**
* Cleans and unloads the SDK.
*/
private void dispose() {
ResourceMonitor.getMonitor().removeProjectListener(this);
}
void setTargetData(IAndroidTarget target, AndroidTargetData data) {
synchronized (mTargetDataMap) {
mTargetDataMap.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;
}
public void projectClosed(IProject project) {
// get the target project
synchronized (mProjectTargetMap) {
IAndroidTarget target = mProjectTargetMap.get(project);
if (target != null) {
// get the bridge for the target, and clear the cache for this project.
AndroidTargetData data = mTargetDataMap.get(target);
if (data != null) {
LayoutBridge bridge = data.getLayoutBridge();
if (bridge != null && bridge.status == LoadStatus.LOADED) {
bridge.bridge.clearCaches(project);
}
}
}
// now remove the project for the maps.
mProjectTargetMap.remove(project);
mProjectApkConfigMap.remove(project);
}
}
public void projectDeleted(IProject project) {
projectClosed(project);
}
public void projectOpened(IProject project) {
// ignore this. The project will be added to the map the first time the target needs
// to be resolved.
}
public void projectOpenedWithWorkspace(IProject project) {
// ignore this. The project will be added to the map the first time the target needs
// to be resolved.
}
}

View File

@@ -0,0 +1,334 @@
/*
* 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.common.AndroidConstants;
import org.eclipse.core.runtime.IProgressMonitor;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;
import javax.management.InvalidAttributeValueException;
/**
* Parser for the text file containing the list of widgets, layouts and layout params.
* <p/>
* The file is a straight text file containing one class per line.<br>
* Each line is in the following format<br>
* <code>[code][class name] [super class name] [super class name]...</code>
* 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 WidgetClassLoader implements IAndroidClassLoader {
/**
* Basic class containing the class descriptions found in the text file.
*/
private final static class ClassDescriptor implements IClassDescriptor {
private String mName;
private String mSimpleName;
private ClassDescriptor mSuperClass;
private ClassDescriptor mEnclosingClass;
private final ArrayList<IClassDescriptor> mDeclaredClasses =
new ArrayList<IClassDescriptor>();
private boolean mIsInstantiable = false;
ClassDescriptor(String fqcn) {
mName = fqcn;
mSimpleName = getSimpleName(fqcn);
}
public String getCanonicalName() {
return mName;
}
public String getSimpleName() {
return mSimpleName;
}
public IClassDescriptor[] getDeclaredClasses() {
return mDeclaredClasses.toArray(new IClassDescriptor[mDeclaredClasses.size()]);
}
private void addDeclaredClass(ClassDescriptor declaredClass) {
mDeclaredClasses.add(declaredClass);
}
public IClassDescriptor getEnclosingClass() {
return mEnclosingClass;
}
void setEnclosingClass(ClassDescriptor enclosingClass) {
// set the enclosing class.
mEnclosingClass = enclosingClass;
// add this to the list of declared class in the enclosing class.
mEnclosingClass.addDeclaredClass(this);
// finally change the name of declared class to make sure it uses the
// convention: package.enclosing$declared instead of package.enclosing.declared
mName = enclosingClass.mName + "$" + mName.substring(enclosingClass.mName.length() + 1);
}
public IClassDescriptor getSuperclass() {
return mSuperClass;
}
void setSuperClass(ClassDescriptor superClass) {
mSuperClass = superClass;
}
@Override
public boolean equals(Object clazz) {
if (clazz instanceof ClassDescriptor) {
return mName.equals(((ClassDescriptor)clazz).mName);
}
return super.equals(clazz);
}
@Override
public int hashCode() {
return mName.hashCode();
}
public boolean isInstantiable() {
return mIsInstantiable;
}
void setInstantiable(boolean state) {
mIsInstantiable = state;
}
private String getSimpleName(String fqcn) {
String[] segments = fqcn.split("\\.");
return segments[segments.length-1];
}
}
private BufferedReader mReader;
/** Output map of FQCN => descriptor on all classes */
private final Map<String, ClassDescriptor> mMap = new TreeMap<String, ClassDescriptor>();
/** Output map of FQCN => descriptor on View classes */
private final Map<String, ClassDescriptor> mWidgetMap = new TreeMap<String, ClassDescriptor>();
/** Output map of FQCN => descriptor on ViewGroup classes */
private final Map<String, ClassDescriptor> mLayoutMap = new TreeMap<String, ClassDescriptor>();
/** Output map of FQCN => descriptor on LayoutParams classes */
private final Map<String, ClassDescriptor> mLayoutParamsMap =
new HashMap<String, ClassDescriptor>();
/** File path of the source text file */
private String mOsFilePath;
/**
* Creates a loader with a given file path.
* @param osFilePath the OS path of the file to load.
* @throws FileNotFoundException if the file is not found.
*/
WidgetClassLoader(String osFilePath) throws FileNotFoundException {
mOsFilePath = osFilePath;
mReader = new BufferedReader(new FileReader(osFilePath));
}
public String getSource() {
return mOsFilePath;
}
/**
* Parses the text file and return true if the file was successfully parsed.
* @param monitor
*/
boolean parseWidgetList(IProgressMonitor monitor) {
try {
String line;
while ((line = mReader.readLine()) != null) {
if (line.length() > 0) {
char prefix = line.charAt(0);
String[] classes = null;
ClassDescriptor clazz = null;
switch (prefix) {
case 'W':
classes = line.substring(1).split(" ");
clazz = processClass(classes, 0, null /* map */);
if (clazz != null) {
clazz.setInstantiable(true);
mWidgetMap.put(classes[0], clazz);
}
break;
case 'L':
classes = line.substring(1).split(" ");
clazz = processClass(classes, 0, null /* map */);
if (clazz != null) {
clazz.setInstantiable(true);
mLayoutMap.put(classes[0], clazz);
}
break;
case 'P':
classes = line.substring(1).split(" ");
clazz = processClass(classes, 0, mLayoutParamsMap);
if (clazz != null) {
clazz.setInstantiable(true);
}
break;
case '#':
// comment, do nothing
break;
default:
throw new IllegalArgumentException();
}
}
}
// reconciliate the layout and their layout params
postProcess();
return true;
} catch (IOException e) {
} finally {
try {
mReader.close();
} catch (IOException e) {
}
}
return false;
}
/**
* Parses a View class and adds a ViewClassInfo for it in mWidgetMap.
* It calls itself recursively to handle super classes which are also Views.
* @param classes the inheritance list of the class to process.
* @param index the index of the class to process in the <code>classes</code> array.
* @param map an optional map in which to put every {@link ClassDescriptor} created.
*/
private ClassDescriptor processClass(String[] classes, int index,
Map<String, ClassDescriptor> map) {
if (index >= classes.length) {
return null;
}
String fqcn = classes[index];
if ("java.lang.Object".equals(fqcn)) { //$NON-NLS-1$
return null;
}
// check if the ViewInfoClass has not yet been created.
if (mMap.containsKey(fqcn)) {
return mMap.get(fqcn);
}
// create the custom class.
ClassDescriptor clazz = new ClassDescriptor(fqcn);
mMap.put(fqcn, clazz);
if (map != null) {
map.put(fqcn, clazz);
}
// get the super class
ClassDescriptor superClass = processClass(classes, index+1, map);
if (superClass != null) {
clazz.setSuperClass(superClass);
}
return clazz;
}
/**
* Goes through the layout params and look for the enclosed class. If the layout params
* has no known enclosed type it is dropped.
*/
private void postProcess() {
Collection<ClassDescriptor> params = mLayoutParamsMap.values();
for (ClassDescriptor param : params) {
String fqcn = param.getCanonicalName();
// get the enclosed name.
String enclosed = getEnclosedName(fqcn);
// look for a match in the layouts. We don't use the layout map as it only contains the
// end classes, but in this case we also need to process the layout params for the base
// layout classes.
ClassDescriptor enclosingType = mMap.get(enclosed);
if (enclosingType != null) {
param.setEnclosingClass(enclosingType);
// remove the class from the map, and put it back with the fixed name
mMap.remove(fqcn);
mMap.put(param.getCanonicalName(), param);
}
}
}
private String getEnclosedName(String fqcn) {
int index = fqcn.lastIndexOf('.');
return fqcn.substring(0, index);
}
/**
* Finds and loads all classes that derive from a given set of super classes.
*
* @param rootPackage Root package of classes to find. Use an empty string to find everyting.
* @param superClasses The super classes of all the classes to find.
* @return An hash map which keys are the super classes looked for and which values are
* ArrayList of the classes found. The array lists are always created for all the
* valid keys, they are simply empty if no deriving class is found for a given
* super class.
* @throws IOException
* @throws InvalidAttributeValueException
* @throws ClassFormatError
*/
public HashMap<String, ArrayList<IClassDescriptor>> findClassesDerivingFrom(String rootPackage,
String[] superClasses) throws IOException, InvalidAttributeValueException,
ClassFormatError {
HashMap<String, ArrayList<IClassDescriptor>> map =
new HashMap<String, ArrayList<IClassDescriptor>>();
ArrayList<IClassDescriptor> list = new ArrayList<IClassDescriptor>();
list.addAll(mWidgetMap.values());
map.put(AndroidConstants.CLASS_VIEW, list);
list = new ArrayList<IClassDescriptor>();
list.addAll(mLayoutMap.values());
map.put(AndroidConstants.CLASS_VIEWGROUP, list);
list = new ArrayList<IClassDescriptor>();
list.addAll(mLayoutParamsMap.values());
map.put(AndroidConstants.CLASS_VIEWGROUP_LAYOUTPARAMS, list);
return map;
}
/**
* Returns a {@link IAndroidClassLoader.IClassDescriptor} by its fully-qualified name.
* @param className the fully-qualified name of the class to return.
* @throws ClassNotFoundException
*/
public IClassDescriptor getClass(String className) throws ClassNotFoundException {
return mMap.get(className);
}
}

View File

@@ -0,0 +1,34 @@
/*
* Copyright (C) 2009 The Android Open Source Project
*
* Licensed under the Eclipse Public License, Version 1.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.eclipse.org/org/documents/epl-v10.php
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.ide.eclipse.adt.wizards.actions;
import com.android.ide.eclipse.adt.wizards.newproject.NewProjectWizard;
import org.eclipse.jface.action.IAction;
import org.eclipse.ui.IWorkbenchWizard;
/**
* Delegate for the toolbar action "Android Project".
* It displays the Android New Project wizard.
*/
public class NewProjectAction extends OpenWizardAction {
@Override
protected IWorkbenchWizard instanciateWizard(IAction action) {
return new NewProjectWizard();
}
}

View File

@@ -0,0 +1,34 @@
/*
* Copyright (C) 2009 The Android Open Source Project
*
* Licensed under the Eclipse Public License, Version 1.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.eclipse.org/org/documents/epl-v10.php
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.ide.eclipse.adt.wizards.actions;
import com.android.ide.eclipse.editors.wizards.NewXmlFileWizard;
import org.eclipse.jface.action.IAction;
import org.eclipse.ui.IWorkbenchWizard;
/**
* Delegate for the toolbar action "Android Project".
* It displays the Android New XML file wizard.
*/
public class NewXmlFileAction extends OpenWizardAction {
@Override
protected IWorkbenchWizard instanciateWizard(IAction action) {
return new NewXmlFileWizard();
}
}

View File

@@ -0,0 +1,139 @@
/*
* Copyright (C) 2009 The Android Open Source Project
*
* Licensed under the Eclipse Public License, Version 1.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.eclipse.org/org/documents/epl-v10.php
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.ide.eclipse.adt.wizards.actions;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.wizard.WizardDialog;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.IWorkbenchWindowActionDelegate;
import org.eclipse.ui.IWorkbenchWizard;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.internal.IWorkbenchHelpContextIds;
import org.eclipse.ui.internal.LegacyResourceSupport;
import org.eclipse.ui.internal.actions.NewWizardShortcutAction;
import org.eclipse.ui.internal.util.Util;
/**
* An abstract action that displays one of our wizards.
* Derived classes must provide the actual wizard to display.
*/
/*package*/ abstract class OpenWizardAction implements IWorkbenchWindowActionDelegate {
/**
* The wizard dialog width, extracted from {@link NewWizardShortcutAction}
*/
private static final int SIZING_WIZARD_WIDTH = 500;
/**
* The wizard dialog height, extracted from {@link NewWizardShortcutAction}
*/
private static final int SIZING_WIZARD_HEIGHT = 500;
/* (non-Javadoc)
* @see org.eclipse.ui.IWorkbenchWindowActionDelegate#dispose()
*/
public void dispose() {
// pass
}
/* (non-Javadoc)
* @see org.eclipse.ui.IWorkbenchWindowActionDelegate#init(org.eclipse.ui.IWorkbenchWindow)
*/
public void init(IWorkbenchWindow window) {
// pass
}
/**
* Opens and display the Android New Project Wizard.
* <p/>
* Most of this implementation is extracted from {@link NewWizardShortcutAction#run()}.
*
* @see org.eclipse.ui.IActionDelegate#run(org.eclipse.jface.action.IAction)
*/
public void run(IAction action) {
// get the workbench and the current window
IWorkbench workbench = PlatformUI.getWorkbench();
IWorkbenchWindow window = workbench.getActiveWorkbenchWindow();
// This code from NewWizardShortcutAction#run() gets the current window selection
// and converts it to a workbench structured selection for the wizard, if possible.
ISelection selection = window.getSelectionService().getSelection();
IStructuredSelection selectionToPass = StructuredSelection.EMPTY;
if (selection instanceof IStructuredSelection) {
selectionToPass = (IStructuredSelection) selection;
} else {
// Build the selection from the IFile of the editor
IWorkbenchPart part = window.getPartService().getActivePart();
if (part instanceof IEditorPart) {
IEditorInput input = ((IEditorPart) part).getEditorInput();
Class<?> fileClass = LegacyResourceSupport.getFileClass();
if (input != null && fileClass != null) {
Object file = Util.getAdapter(input, fileClass);
if (file != null) {
selectionToPass = new StructuredSelection(file);
}
}
}
}
// Create the wizard and initialize it with the selection
IWorkbenchWizard wizard = instanciateWizard(action);
wizard.init(workbench, selectionToPass);
// It's not visible yet until a dialog is created and opened
Shell parent = window.getShell();
WizardDialog dialog = new WizardDialog(parent, wizard);
dialog.create();
// This code comes straight from NewWizardShortcutAction#run()
Point defaultSize = dialog.getShell().getSize();
dialog.getShell().setSize(
Math.max(SIZING_WIZARD_WIDTH, defaultSize.x),
Math.max(SIZING_WIZARD_HEIGHT, defaultSize.y));
window.getWorkbench().getHelpSystem().setHelp(dialog.getShell(),
IWorkbenchHelpContextIds.NEW_WIZARD_SHORTCUT);
dialog.open();
}
/**
* Called by {@link #run(IAction)} to instantiate the actual wizard.
*
* @param action The action parameter from {@link #run(IAction)}.
* @return A new wizard instance. Must not be null.
*/
protected abstract IWorkbenchWizard instanciateWizard(IAction action);
/* (non-Javadoc)
* @see org.eclipse.ui.IActionDelegate#selectionChanged(org.eclipse.jface.action.IAction, org.eclipse.jface.viewers.ISelection)
*/
public void selectionChanged(IAction action, ISelection selection) {
// pass
}
}

View File

@@ -0,0 +1,737 @@
/*
* 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.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 com.android.sdklib.SdkConstants;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IProjectDescription;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceStatus;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.ui.actions.OpenJavaPerspectiveAction;
import org.eclipse.jface.dialogs.ErrorDialog;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.wizard.Wizard;
import org.eclipse.ui.INewWizard;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.actions.WorkspaceModifyOperation;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
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.
* <p/>
* Note: this class is public so that it can be accessed from unit tests.
* It is however an internal class. Its API may change without notice.
* It should semantically be considered as a private final class.
* Do not derive from this class.
*/
public class NewProjectWizard extends Wizard implements INewWizard {
private static final String PARAM_SDK_TOOLS_DIR = "ANDROID_SDK_TOOLS"; //$NON-NLS-1$
private static final String PARAM_ACTIVITY = "ACTIVITY_NAME"; //$NON-NLS-1$
private static final String PARAM_APPLICATION = "APPLICATION_NAME"; //$NON-NLS-1$
private static final String PARAM_PACKAGE = "PACKAGE"; //$NON-NLS-1$
private static final String PARAM_PROJECT = "PROJECT_NAME"; //$NON-NLS-1$
private static final String PARAM_STRING_NAME = "STRING_NAME"; //$NON-NLS-1$
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 PARAM_MIN_SDK_VERSION = "MIN_SDK_VERSION"; //$NON-NLS-1$
private static final String PH_ACTIVITIES = "ACTIVITIES"; //$NON-NLS-1$
private static final String PH_USES_SDK = "USES-SDK"; //$NON-NLS-1$
private static final String PH_INTENT_FILTERS = "INTENT_FILTERS"; //$NON-NLS-1$
private static final String PH_STRINGS = "STRINGS"; //$NON-NLS-1$
private static final String BIN_DIRECTORY =
SdkConstants.FD_OUTPUT + AndroidConstants.WS_SEP;
private static final String RES_DIRECTORY =
SdkConstants.FD_RESOURCES + AndroidConstants.WS_SEP;
private static final String ASSETS_DIRECTORY =
SdkConstants.FD_ASSETS + AndroidConstants.WS_SEP;
private static final String DRAWABLE_DIRECTORY =
SdkConstants.FD_DRAWABLE + AndroidConstants.WS_SEP;
private static final String LAYOUT_DIRECTORY =
SdkConstants.FD_LAYOUT + AndroidConstants.WS_SEP;
private static final String VALUES_DIRECTORY =
SdkConstants.FD_VALUES + AndroidConstants.WS_SEP;
private static final String GEN_SRC_DIRECTORY =
SdkConstants.FD_GEN_SOURCES + AndroidConstants.WS_SEP;
private static final String TEMPLATES_DIRECTORY = "templates/"; //$NON-NLS-1$
private static final String TEMPLATE_MANIFEST = TEMPLATES_DIRECTORY
+ "AndroidManifest.template"; //$NON-NLS-1$
private static final String TEMPLATE_ACTIVITIES = TEMPLATES_DIRECTORY
+ "activity.template"; //$NON-NLS-1$
private static final String TEMPLATE_USES_SDK = TEMPLATES_DIRECTORY
+ "uses-sdk.template"; //$NON-NLS-1$
private static final String TEMPLATE_INTENT_LAUNCHER = TEMPLATES_DIRECTORY
+ "launcher_intent_filter.template"; //$NON-NLS-1$
private static final String TEMPLATE_STRINGS = TEMPLATES_DIRECTORY
+ "strings.template"; //$NON-NLS-1$
private static final String TEMPLATE_STRING = TEMPLATES_DIRECTORY
+ "string.template"; //$NON-NLS-1$
private static final String ICON = "icon.png"; //$NON-NLS-1$
private static final String STRINGS_FILE = "strings.xml"; //$NON-NLS-1$
private static final String STRING_RSRC_PREFIX = "@string/"; //$NON-NLS-1$
private static final String STRING_APP_NAME = "app_name"; //$NON-NLS-1$
private static final String STRING_HELLO_WORLD = "hello"; //$NON-NLS-1$
private static final String[] DEFAULT_DIRECTORIES = new String[] {
BIN_DIRECTORY, RES_DIRECTORY, ASSETS_DIRECTORY };
private static final String[] RES_DIRECTORIES = new String[] {
DRAWABLE_DIRECTORY, LAYOUT_DIRECTORY, VALUES_DIRECTORY};
private static final String PROJECT_LOGO_LARGE = "icons/android_large.png"; //$NON-NLS-1$
private static final String JAVA_ACTIVITY_TEMPLATE = "java_file.template"; //$NON-NLS-1$
private static final String LAYOUT_TEMPLATE = "layout.template"; //$NON-NLS-1$
private static final String MAIN_LAYOUT_XML = "main.xml"; //$NON-NLS-1$
protected static final String MAIN_PAGE_NAME = "newAndroidProjectPage"; //$NON-NLS-1$
private NewProjectCreationPage mMainPage;
/**
* Initializes this creation wizard using the passed workbench and object
* selection. Inherited from org.eclipse.ui.IWorkbenchWizard
*/
public void init(IWorkbench workbench, IStructuredSelection selection) {
setHelpAvailable(false); // TODO have help
setWindowTitle("New Android Project");
setImageDescriptor();
mMainPage = createMainPage();
mMainPage.setTitle("New Android Project");
mMainPage.setDescription("Creates a new Android Project resource.");
}
/**
* Creates the wizard page.
* <p/>
* Please do NOT override this method.
* <p/>
* This is protected so that it can be overridden by unit tests.
* However the contract of this class is private and NO ATTEMPT will be made
* to maintain compatibility between different versions of the plugin.
*/
protected NewProjectCreationPage createMainPage() {
return new NewProjectCreationPage(MAIN_PAGE_NAME);
}
// -- Methods inherited from org.eclipse.jface.wizard.Wizard --
// The Wizard class implements most defaults and boilerplate code needed by
// IWizard
/**
* Adds pages to this wizard.
*/
@Override
public void addPages() {
addPage(mMainPage);
}
/**
* Performs any actions appropriate in response to the user having pressed
* the Finish button, or refuse if finishing now is not permitted: here, it
* actually creates the workspace project and then switch to the Java
* perspective.
*
* @return True
*/
@Override
public boolean performFinish() {
if (!createAndroidProject()) {
return false;
}
// Open the default Java Perspective
OpenJavaPerspectiveAction action = new OpenJavaPerspectiveAction();
action.run();
return true;
}
// -- Custom Methods --
/**
* Before actually creating the project for a new project (as opposed to using an
* existing project), we check if the target location is a directory that either does
* not exist or is empty.
*
* If it's not empty, ask the user for confirmation.
*
* @param destination The destination folder where the new project is to be created.
* @return True if the destination doesn't exist yet or is an empty directory or is
* accepted by the user.
*/
private boolean validateNewProjectLocationIsEmpty(IPath destination) {
File f = new File(destination.toOSString());
if (f.isDirectory() && f.list().length > 0) {
return AdtPlugin.displayPrompt("New Android Project",
"You are going to create a new Android Project in an existing, non-empty, directory. Are you sure you want to proceed?");
}
return true;
}
/**
* Creates the android project.
* @return True if the project could be created.
*/
private boolean createAndroidProject() {
IWorkspace workspace = ResourcesPlugin.getWorkspace();
final IProject project = workspace.getRoot().getProject(mMainPage.getProjectName());
final IProjectDescription description = workspace.newProjectDescription(project.getName());
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, mMainPage.isNewProject());
parameters.put(PARAM_SRC_FOLDER, mMainPage.getSourceFolder());
parameters.put(PARAM_SDK_TARGET, mMainPage.getSdkTarget());
parameters.put(PARAM_MIN_SDK_VERSION, mMainPage.getMinSdkVersion());
if (mMainPage.isCreateActivity()) {
// An activity name can be of the form ".package.Class" or ".Class".
// The initial dot is ignored, as it is always added later in the templates.
String activityName = mMainPage.getActivityName();
if (activityName.startsWith(".")) { //$NON-NLS-1$
activityName = activityName.substring(1);
}
parameters.put(PARAM_ACTIVITY, activityName);
}
// create a dictionary of string that will contain name+content.
// we'll put all the strings into values/strings.xml
final HashMap<String, String> stringDictionary = new HashMap<String, String>();
stringDictionary.put(STRING_APP_NAME, mMainPage.getApplicationName());
IPath path = mMainPage.getLocationPath();
IPath defaultLocation = Platform.getLocation();
if (!path.equals(defaultLocation)) {
description.setLocation(path);
}
if (mMainPage.isNewProject() && !mMainPage.useDefaultLocation() &&
!validateNewProjectLocationIsEmpty(path)) {
return false;
}
// Create a monitored operation to create the actual project
WorkspaceModifyOperation op = new WorkspaceModifyOperation() {
@Override
protected void execute(IProgressMonitor monitor) throws InvocationTargetException {
createProjectAsync(project, description, monitor, parameters, stringDictionary);
}
};
// Run the operation in a different thread
runAsyncOperation(op);
return true;
}
/**
* Runs the operation in a different thread and display generated
* exceptions.
*
* @param op The asynchronous operation to run.
*/
private void runAsyncOperation(WorkspaceModifyOperation op) {
try {
getContainer().run(true /* fork */, true /* cancelable */, op);
} catch (InvocationTargetException e) {
// The runnable threw an exception
Throwable t = e.getTargetException();
if (t instanceof CoreException) {
CoreException core = (CoreException) t;
if (core.getStatus().getCode() == IResourceStatus.CASE_VARIANT_EXISTS) {
// The error indicates the file system is not case sensitive
// and there's a resource with a similar name.
MessageDialog.openError(getShell(), "Error", "Error: Case Variant Exists");
} else {
ErrorDialog.openError(getShell(), "Error", null, core.getStatus());
}
} else {
// Some other kind of exception
MessageDialog.openError(getShell(), "Error", t.getMessage());
}
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/**
* Creates the actual project, sets its nature and adds the required folders
* and files to it. This is run asynchronously in a different thread.
*
* @param project The project to create.
* @param description A description of the project.
* @param monitor An existing monitor.
* @param parameters Template parameters.
* @param stringDictionary String definition.
* @throws InvocationTargetException to wrap any unmanaged exception and
* return it to the calling thread. The method can fail if it fails
* 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, Object> parameters,
Map<String, String> stringDictionary)
throws InvocationTargetException {
monitor.beginTask("Create Android Project", 100);
try {
// Create project and open it
project.create(description, new SubProgressMonitor(monitor, 10));
if (monitor.isCanceled()) throw new OperationCanceledException();
project.open(IResource.BACKGROUND_REFRESH, new SubProgressMonitor(monitor, 10));
// Add the Java and android nature to the project
AndroidNature.setupProjectNatures(project, monitor);
// Create folders in the project if they don't already exist
addDefaultDirectories(project, AndroidConstants.WS_ROOT, DEFAULT_DIRECTORIES, monitor);
String[] sourceFolders = new String[] {
(String) parameters.get(PARAM_SRC_FOLDER),
GEN_SRC_DIRECTORY
};
addDefaultDirectories(project, AndroidConstants.WS_ROOT, sourceFolders, monitor);
// Create the resource folders in the project if they don't already exist.
addDefaultDirectories(project, RES_DIRECTORY, RES_DIRECTORIES, monitor);
// Setup class path
IJavaProject javaProject = JavaCore.create(project);
for (String sourceFolder : sourceFolders) {
setupSourceFolder(javaProject, sourceFolder, monitor);
}
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);
// add the default app icon
addIcon(project, monitor);
// Create the default package components
addSampleCode(project, sourceFolders[0], parameters, stringDictionary, monitor);
// add the string definition file if needed
if (stringDictionary.size() > 0) {
addStringDictionaryFile(project, stringDictionary, monitor);
}
// Set output location
javaProject.setOutputLocation(project.getFolder(BIN_DIRECTORY).getFullPath(),
monitor);
}
Sdk.getCurrent().setProject(project, (IAndroidTarget) parameters.get(PARAM_SDK_TARGET),
null /* apkConfigMap*/);
// Fix the project to make sure all properties are as expected.
// Necessary for existing projects and good for new ones to.
ProjectHelper.fixProject(project);
} catch (CoreException e) {
throw new InvocationTargetException(e);
} catch (IOException e) {
throw new InvocationTargetException(e);
} finally {
monitor.done();
}
}
/**
* Adds default directories to the project.
*
* @param project The Java Project to update.
* @param parentFolder The path of the parent folder. Must end with a
* separator.
* @param folders Folders to be added.
* @param monitor An existing monitor.
* @throws CoreException if the method fails to create the directories in
* the project.
*/
private void addDefaultDirectories(IProject project, String parentFolder,
String[] folders, IProgressMonitor monitor) throws CoreException {
for (String name : folders) {
if (name.length() > 0) {
IFolder folder = project.getFolder(parentFolder + name);
if (!folder.exists()) {
folder.create(true /* force */, true /* local */,
new SubProgressMonitor(monitor, 10));
}
}
}
}
/**
* Adds the manifest to the project.
*
* @param project The Java Project to update.
* @param parameters Template Parameters.
* @param stringDictionary String List to be added to a string definition
* file. This map will be filled by this method.
* @param monitor An existing monitor.
* @throws CoreException if the method fails to update the project.
* @throws IOException if the method fails to create the files in the
* project.
*/
private void addManifest(IProject project, Map<String, Object> parameters,
Map<String, String> stringDictionary, IProgressMonitor monitor)
throws CoreException, IOException {
// get IFile to the manifest and check if it's not already there.
IFile file = project.getFile(AndroidConstants.FN_ANDROID_MANIFEST);
if (!file.exists()) {
// Read manifest template
String manifestTemplate = AdtPlugin.readEmbeddedTextFile(TEMPLATE_MANIFEST);
// Replace all keyword parameters
manifestTemplate = replaceParameters(manifestTemplate, parameters);
if (parameters.containsKey(PARAM_ACTIVITY)) {
// now get the activity template
String activityTemplate = AdtPlugin.readEmbeddedTextFile(TEMPLATE_ACTIVITIES);
// Replace all keyword parameters to make main activity.
String activities = replaceParameters(activityTemplate, parameters);
// set the intent.
String intent = AdtPlugin.readEmbeddedTextFile(TEMPLATE_INTENT_LAUNCHER);
// set the intent to the main activity
activities = activities.replaceAll(PH_INTENT_FILTERS, intent);
// set the activity(ies) in the manifest
manifestTemplate = manifestTemplate.replaceAll(PH_ACTIVITIES, activities);
} else {
// remove the activity(ies) from the manifest
manifestTemplate = manifestTemplate.replaceAll(PH_ACTIVITIES, "");
}
String minSdkVersion = (String) parameters.get(PARAM_MIN_SDK_VERSION);
if (minSdkVersion != null && minSdkVersion.length() > 0) {
String usesSdkTemplate = AdtPlugin.readEmbeddedTextFile(TEMPLATE_USES_SDK);
String usesSdk = replaceParameters(usesSdkTemplate, parameters);
manifestTemplate = manifestTemplate.replaceAll(PH_USES_SDK, usesSdk);
} else {
manifestTemplate = manifestTemplate.replaceAll(PH_USES_SDK, "");
}
// Save in the project as UTF-8
InputStream stream = new ByteArrayInputStream(
manifestTemplate.getBytes("UTF-8")); //$NON-NLS-1$
file.create(stream, false /* force */, new SubProgressMonitor(monitor, 10));
}
}
/**
* Adds the string resource file.
*
* @param project The Java Project to update.
* @param strings The list of strings to be added to the string file.
* @param monitor An existing monitor.
* @throws CoreException if the method fails to update the project.
* @throws IOException if the method fails to create the files in the
* project.
*/
private void addStringDictionaryFile(IProject project,
Map<String, String> strings, IProgressMonitor monitor)
throws CoreException, IOException {
// create the IFile object and check if the file doesn't already exist.
IFile file = project.getFile(RES_DIRECTORY + AndroidConstants.WS_SEP
+ VALUES_DIRECTORY + AndroidConstants.WS_SEP + STRINGS_FILE);
if (!file.exists()) {
// get the Strings.xml template
String stringDefinitionTemplate = AdtPlugin.readEmbeddedTextFile(TEMPLATE_STRINGS);
// get the template for one string
String stringTemplate = AdtPlugin.readEmbeddedTextFile(TEMPLATE_STRING);
// get all the string names
Set<String> stringNames = strings.keySet();
// loop on it and create the string definitions
StringBuilder stringNodes = new StringBuilder();
for (String key : stringNames) {
// get the value from the key
String value = strings.get(key);
// place them in the template
String stringDef = stringTemplate.replace(PARAM_STRING_NAME, key);
stringDef = stringDef.replace(PARAM_STRING_CONTENT, value);
// append to the other string
if (stringNodes.length() > 0) {
stringNodes.append("\n");
}
stringNodes.append(stringDef);
}
// put the string nodes in the Strings.xml template
stringDefinitionTemplate = stringDefinitionTemplate.replace(PH_STRINGS,
stringNodes.toString());
// write the file as UTF-8
InputStream stream = new ByteArrayInputStream(
stringDefinitionTemplate.getBytes("UTF-8")); //$NON-NLS-1$
file.create(stream, false /* force */, new SubProgressMonitor(monitor, 10));
}
}
/**
* Adds default application icon to the project.
*
* @param project The Java Project to update.
* @param monitor An existing monitor.
* @throws CoreException if the method fails to update the project.
*/
private void addIcon(IProject project, IProgressMonitor monitor)
throws CoreException {
IFile file = project.getFile(RES_DIRECTORY + AndroidConstants.WS_SEP
+ DRAWABLE_DIRECTORY + AndroidConstants.WS_SEP + ICON);
if (!file.exists()) {
// read the content from the template
byte[] buffer = AdtPlugin.readEmbeddedFile(TEMPLATES_DIRECTORY + ICON);
// if valid
if (buffer != null) {
// Save in the project
InputStream stream = new ByteArrayInputStream(buffer);
file.create(stream, false /* force */, new SubProgressMonitor(monitor, 10));
}
}
}
/**
* Creates the package folder and copies the sample code in the project.
*
* @param project The Java Project to update.
* @param parameters Template Parameters.
* @param stringDictionary String List to be added to a string definition
* file. This map will be filled by this method.
* @param monitor An existing monitor.
* @throws CoreException if the method fails to update the project.
* @throws IOException if the method fails to create the files in the
* project.
*/
private void addSampleCode(IProject project, String sourceFolder,
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 = (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 = (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
// those sub packages and remove them from activityName.
packageName += "." + activityName; //$NON-NLS-1$
int pos = packageName.lastIndexOf('.');
activityName = packageName.substring(pos + 1);
packageName = packageName.substring(0, pos);
// 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, Object>(parameters);
java_activity_parameters.put(PARAM_PACKAGE, packageName);
java_activity_parameters.put(PARAM_ACTIVITY, activityName);
}
}
String[] components = packageName.split(AndroidConstants.RE_DOT);
for (String component : components) {
pkgFolder = pkgFolder.getFolder(component);
if (!pkgFolder.exists()) {
pkgFolder.create(true /* force */, true /* local */,
new SubProgressMonitor(monitor, 10));
}
}
if (activityName != null) {
// create the main activity Java file
String activityJava = activityName + AndroidConstants.DOT_JAVA;
IFile file = pkgFolder.getFile(activityJava);
if (!file.exists()) {
copyFile(JAVA_ACTIVITY_TEMPLATE, file, java_activity_parameters, monitor);
}
}
// create the layout file
IFolder layoutfolder = project.getFolder(RES_DIRECTORY).getFolder(LAYOUT_DIRECTORY);
IFile file = layoutfolder.getFile(MAIN_LAYOUT_XML);
if (!file.exists()) {
copyFile(LAYOUT_TEMPLATE, file, parameters, monitor);
if (activityName != null) {
stringDictionary.put(STRING_HELLO_WORLD, "Hello World, " + activityName + "!");
} else {
stringDictionary.put(STRING_HELLO_WORLD, "Hello World!");
}
}
}
/**
* Adds the given folder to the project's class path.
*
* @param javaProject The Java Project to update.
* @param sourceFolder Template Parameters.
* @param monitor An existing monitor.
* @throws JavaModelException if the classpath could not be set.
*/
private void setupSourceFolder(IJavaProject javaProject, String sourceFolder,
IProgressMonitor monitor) throws JavaModelException {
IProject project = javaProject.getProject();
// Add "src" to class path
IFolder srcFolder = project.getFolder(sourceFolder);
IClasspathEntry[] entries = javaProject.getRawClasspath();
entries = removeSourceClasspath(entries, srcFolder);
entries = removeSourceClasspath(entries, srcFolder.getParent());
entries = ProjectHelper.addEntryToClasspath(entries,
JavaCore.newSourceEntry(srcFolder.getFullPath()));
javaProject.setRawClasspath(entries, new SubProgressMonitor(monitor, 10));
}
/**
* Removes the corresponding source folder from the class path entries if
* found.
*
* @param entries The class path entries to read. A copy will be returned.
* @param folder The parent source folder to remove.
* @return A new class path entries array.
*/
private IClasspathEntry[] removeSourceClasspath(IClasspathEntry[] entries, IContainer folder) {
if (folder == null) {
return entries;
}
IClasspathEntry source = JavaCore.newSourceEntry(folder.getFullPath());
int n = entries.length;
for (int i = n - 1; i >= 0; i--) {
if (entries[i].equals(source)) {
IClasspathEntry[] newEntries = new IClasspathEntry[n - 1];
if (i > 0) System.arraycopy(entries, 0, newEntries, 0, i);
if (i < n - 1) System.arraycopy(entries, i + 1, newEntries, i, n - i - 1);
n--;
entries = newEntries;
}
}
return entries;
}
/**
* Copies the given file from our resource folder to the new project.
* Expects the file to the US-ASCII or UTF-8 encoded.
*
* @throws CoreException from IFile if failing to create the new file.
* @throws MalformedURLException from URL if failing to interpret the URL.
* @throws FileNotFoundException from RandomAccessFile.
* @throws IOException from RandomAccessFile.length() if can't determine the
* length.
*/
private void copyFile(String resourceFilename, IFile destFile,
Map<String, Object> parameters, IProgressMonitor monitor)
throws CoreException, IOException {
// Read existing file.
String template = AdtPlugin.readEmbeddedTextFile(
TEMPLATES_DIRECTORY + resourceFilename);
// Replace all keyword parameters
template = replaceParameters(template, parameters);
// Save in the project as UTF-8
InputStream stream = new ByteArrayInputStream(template.getBytes("UTF-8")); //$NON-NLS-1$
destFile.create(stream, false /* force */, new SubProgressMonitor(monitor, 10));
}
/**
* Returns an image descriptor for the wizard logo.
*/
private void setImageDescriptor() {
ImageDescriptor desc = AdtPlugin.getImageDescriptor(PROJECT_LOGO_LARGE);
setDefaultPageImageDescriptor(desc);
}
/**
* 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
* @return A new String object with the placeholder replaced by the values.
*/
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

@@ -0,0 +1,219 @@
/*
* 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.common;
import com.android.sdklib.SdkConstants;
import java.io.File;
import java.util.regex.Pattern;
/**
* Constant definition class.<br>
* <br>
* Most constants have a prefix defining the content.
* <ul>
* <li><code>WS_</code> Workspace path constant. Those are absolute paths,
* from the project root.</li>
* <li><code>OS_</code> OS path constant. These paths are different depending on the platform.</li>
* <li><code>FN_</code> File name constant.</li>
* <li><code>FD_</code> Folder name constant.</li>
* <li><code>MARKER_</code> Resource Marker Ids constant.</li>
* <li><code>EXT_</code> File extension constant. This does NOT include a dot.</li>
* <li><code>DOT_</code> File extension constant. This start with a dot.</li>
* <li><code>RE_</code> Regexp constant.</li>
* <li><code>NS_</code> Namespace constant.</li>
* <li><code>CLASS_</code> Fully qualified class name.</li>
* </ul>
*
*/
public class AndroidConstants {
/**
* 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$
/** Separator for workspace path, i.e. "/". */
public final static String WS_SEP = "/"; //$NON-NLS-1$
/** Separator character for workspace path, i.e. '/'. */
public final static char WS_SEP_CHAR = '/';
/** Extension of the Application package Files, i.e. "apk". */
public final static String EXT_ANDROID_PACKAGE = "apk"; //$NON-NLS-1$
/** Extension of java files, i.e. "java" */
public final static String EXT_JAVA = "java"; //$NON-NLS-1$
/** Extension of compiled java files, i.e. "class" */
public final static String EXT_CLASS = "class"; //$NON-NLS-1$
/** Extension of xml files, i.e. "xml" */
public final static String EXT_XML = "xml"; //$NON-NLS-1$
/** Extension of jar files, i.e. "jar" */
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$
/** Dot-Extension of the Application package Files, i.e. ".apk". */
public final static String DOT_ANDROID_PACKAGE = DOT + EXT_ANDROID_PACKAGE;
/** Dot-Extension of java files, i.e. ".java" */
public final static String DOT_JAVA = DOT + EXT_JAVA;
/** Dot-Extension of compiled java files, i.e. ".class" */
public final static String DOT_CLASS = DOT + EXT_CLASS;
/** Dot-Extension of xml files, i.e. ".xml" */
public final static String DOT_XML = DOT + EXT_XML;
/** Dot-Extension of jar files, i.e. ".jar" */
public final static String DOT_JAR = DOT + EXT_JAR;
/** Dot-Extension of aidl files, i.e. ".aidl" */
public final static String DOT_AIDL = DOT + EXT_AIDL;
/** Name of the manifest file, i.e. "AndroidManifest.xml". */
public static final String FN_ANDROID_MANIFEST = "AndroidManifest.xml"; //$NON-NLS-1$
public static final String FN_PROJECT_AIDL = "project.aidl"; //$NON-NLS-1$
/** Name of the android sources directory */
public static final String FD_ANDROID_SOURCES = "sources"; //$NON-NLS-1$
/** Resource java class filename, i.e. "R.java" */
public final static String FN_RESOURCE_CLASS = "R.java"; //$NON-NLS-1$
/** Resource class file filename, i.e. "R.class" */
public final static String FN_COMPILED_RESOURCE_CLASS = "R.class"; //$NON-NLS-1$
/** Manifest java class filename, i.e. "Manifest.java" */
public final static String FN_MANIFEST_CLASS = "Manifest.java"; //$NON-NLS-1$
/** Dex conversion output filname, i.e. "classes.dex" */
public final static String FN_CLASSES_DEX = "classes.dex"; //$NON-NLS-1$
/** Temporary packaged resources file name, i.e. "resources.ap_" */
public final static String FN_RESOURCES_AP_ = "resources.ap_"; //$NON-NLS-1$
/** Temporary packaged resources file name for a specific set of configuration */
public final static String FN_RESOURCES_S_AP_ = "resources-%s.ap_"; //$NON-NLS-1$
public final static Pattern PATTERN_RESOURCES_S_AP_ =
Pattern.compile("resources-.*\\.ap_", Pattern.CASE_INSENSITIVE);
public final static String FN_ADB =
(SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_WINDOWS) ?
"adb.exe" : "adb"; //$NON-NLS-1$ //$NON-NLS-2$
public final static String FN_EMULATOR =
(SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_WINDOWS) ?
"emulator.exe" : "emulator"; //$NON-NLS-1$ //$NON-NLS-2$
public final static String FN_TRACEVIEW =
(SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_WINDOWS) ?
"traceview.exe" : "traceview"; //$NON-NLS-1$ //$NON-NLS-2$
/** Absolute path of the workspace root, i.e. "/" */
public final static String WS_ROOT = WS_SEP;
/** Absolute path of the resource folder, eg "/res".<br> This is a workspace path. */
public final static String WS_RESOURCES = WS_SEP + SdkConstants.FD_RESOURCES;
/** Absolute path of the resource folder, eg "/assets".<br> This is a workspace path. */
public final static String WS_ASSETS = WS_SEP + SdkConstants.FD_ASSETS;
/** Leaf of the javaDoc folder. Does not start with a 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.
* FIXME: remove once the NPW is fixed. */
public final static String OS_SDK_SAMPLES_FOLDER = SdkConstants.FD_SAMPLES + File.separator;
public final static String RE_DOT = "\\."; //$NON-NLS-1$
/** Regexp for java extension, i.e. "\.java$" */
public final static String RE_JAVA_EXT = "\\.java$"; //$NON-NLS-1$
/** Regexp for aidl extension, i.e. "\.aidl$" */
public final static String RE_AIDL_EXT = "\\.aidl$"; //$NON-NLS-1$
/** 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 when running the compile command */
public final static String MARKER_AAPT_COMPILE = COMMON_PLUGIN_ID + ".aaptProblem"; //$NON-NLS-1$
/** aapt marker error when running the package command */
public final static String MARKER_AAPT_PACKAGE = COMMON_PLUGIN_ID + ".aapt2Problem"; //$NON-NLS-1$
/** XML marker error. */
public final static String MARKER_XML = COMMON_PLUGIN_ID + ".xmlProblem"; //$NON-NLS-1$
/** aidl marker error. */
public final static String MARKER_AIDL = COMMON_PLUGIN_ID + ".aidlProblem"; //$NON-NLS-1$
/** android marker error */
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$
/** Name for the "class" marker attribute */
public final static String MARKER_ATTR_CLASS = "android.class"; //$NON-NLS-1$
/** activity value for marker attribute "type" */
public final static String MARKER_ATTR_TYPE_ACTIVITY = "activity"; //$NON-NLS-1$
/** service value for marker attribute "type" */
public final static String MARKER_ATTR_TYPE_SERVICE = "service"; //$NON-NLS-1$
/** receiver value for marker attribute "type" */
public final static String MARKER_ATTR_TYPE_RECEIVER = "receiver"; //$NON-NLS-1$
/** provider value for marker attribute "type" */
public final static String MARKER_ATTR_TYPE_PROVIDER = "provider"; //$NON-NLS-1$
public final static String CLASS_ACTIVITY = "android.app.Activity"; //$NON-NLS-1$
public final static String CLASS_SERVICE = "android.app.Service"; //$NON-NLS-1$
public final static String CLASS_BROADCASTRECEIVER = "android.content.BroadcastReceiver"; //$NON-NLS-1$
public final static String CLASS_CONTENTPROVIDER = "android.content.ContentProvider"; //$NON-NLS-1$
public final static String CLASS_INSTRUMENTATION = "android.app.Instrumentation"; //$NON-NLS-1$
public final static String CLASS_BUNDLE = "android.os.Bundle"; //$NON-NLS-1$
public final static String CLASS_R = "android.R"; //$NON-NLS-1$
public final static String CLASS_MANIFEST_PERMISSION = "android.Manifest$permission"; //$NON-NLS-1$
public final static String CLASS_INTENT = "android.content.Intent"; //$NON-NLS-1$
public final static String CLASS_CONTEXT = "android.content.Context"; //$NON-NLS-1$
public final static String CLASS_VIEW = "android.view.View"; //$NON-NLS-1$
public final static String CLASS_VIEWGROUP = "android.view.ViewGroup"; //$NON-NLS-1$
public final static String CLASS_LAYOUTPARAMS = "LayoutParams"; //$NON-NLS-1$
public final static String CLASS_VIEWGROUP_LAYOUTPARAMS =
CLASS_VIEWGROUP + "$" + CLASS_LAYOUTPARAMS; //$NON-NLS-1$
public final static String CLASS_FRAMELAYOUT = "FrameLayout"; //$NON-NLS-1$
public final static String CLASS_PREFERENCE = "android.preference.Preference"; //$NON-NLS-1$
public final static String CLASS_PREFERENCE_SCREEN = "PreferenceScreen"; //$NON-NLS-1$
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$
/**
* Prefered compiler level, i.e. "1.5".
*/
public final static String COMPILER_COMPLIANCE_PREFERRED = "1.5"; //$NON-NLS-1$
/**
* List of valid compiler level, i.e. "1.5" and "1.6"
*/
public final static String[] COMPILER_COMPLIANCE = {
"1.5", //$NON-NLS-1$
"1.6", //$NON-NLS-1$
};
/** The base URL where to find the Android class & manifest documentation */
public static final String CODESITE_BASE_URL = "http://code.google.com/android"; //$NON-NLS-1$
}

View File

@@ -0,0 +1,64 @@
/*
* 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.common;
import org.eclipse.ui.IViewPart;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.PlatformUI;
/**
* Helpers for Eclipse UI related stuff.
*/
public final class EclipseUiHelper {
/** View Id for the default Eclipse Content Outline view. */
public static final String CONTENT_OUTLINE_VIEW_ID = "org.eclipse.ui.views.ContentOutline";
/** View Id for the default Eclipse Property Sheet view. */
public static final String PROPERTY_SHEET_VIEW_ID = "org.eclipse.ui.views.PropertySheet";
/** This class never gets instantiated. */
private EclipseUiHelper() {
}
/**
* Shows the corresponding view.
* <p/>
* Silently fails in case of error.
*
* @param viewId One of {@link #CONTENT_OUTLINE_VIEW_ID}, {@link #PROPERTY_SHEET_VIEW_ID}.
* @param activate True to force activate (i.e. takes focus), false to just make visible (i.e.
* does not steal focus.)
*/
public static void showView(String viewId, boolean activate) {
IWorkbenchWindow win = PlatformUI.getWorkbench().getActiveWorkbenchWindow();
if (win != null) {
IWorkbenchPage page = win.getActivePage();
if (page != null) {
try {
IViewPart part = page.showView(viewId,
null /* secondaryId */,
activate ? IWorkbenchPage.VIEW_ACTIVATE : IWorkbenchPage.VIEW_VISIBLE);
} catch (PartInitException e) {
// ignore
}
}
}
}
}

View File

@@ -0,0 +1,21 @@
package com.android.ide.eclipse.common;
import org.eclipse.osgi.util.NLS;
public class Messages extends NLS {
private static final String BUNDLE_NAME = "com.android.ide.eclipse.common.messages"; //$NON-NLS-1$
public static String Console_Data_Project_Tag;
public static String Console_Date_Tag;
static {
// initialize resource bundle
NLS.initializeMessages(BUNDLE_NAME, Messages.class);
}
private Messages() {
}
}

View File

@@ -0,0 +1,39 @@
/*
* 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.common;
import com.android.sdkstats.SdkStatsService;
import org.osgi.framework.Version;
/**
* Helper class to access the ping usage stat server.
*/
public class SdkStatsHelper {
/**
* Pings the usage start server.
* @param pluginName the name of the plugin to appear in the stats
* @param pluginVersion the {@link Version} of the plugin.
*/
public static void pingUsageServer(String pluginName, Version pluginVersion) {
String versionString = String.format("%1$d.%2$d.%3$d", pluginVersion.getMajor(),
pluginVersion.getMinor(), pluginVersion.getMicro());
SdkStatsService.ping(pluginName, versionString);
}
}

View File

@@ -0,0 +1,63 @@
/*
* 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.common;
import org.eclipse.ui.console.MessageConsoleStream;
import java.util.Calendar;
/**
* Stream helper class.
*/
public class StreamHelper {
/**
* Prints messages, associated with a project to the specified stream
* @param stream The stream to write to
* @param tag The tag associated to the message. Can be null
* @param objects The objects to print through their toString() method (or directly for
* {@link String} objects.
*/
public static synchronized void printToStream(MessageConsoleStream stream, String tag,
Object... objects) {
String dateTag = getMessageTag(tag);
for (Object obj : objects) {
stream.print(dateTag);
if (obj instanceof String) {
stream.println((String)obj);
} else {
stream.println(obj.toString());
}
}
}
/**
* Creates a string containing the current date/time, and the tag
* @param tag The tag associated to the message. Can be null
* @return The dateTag
*/
public static String getMessageTag(String tag) {
Calendar c = Calendar.getInstance();
if (tag == null) {
return String.format(Messages.Console_Date_Tag, c);
}
return String.format(Messages.Console_Data_Project_Tag, c, tag);
}
}

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